More servicesWindows Live
HomeHotmailSpacesOneCare
 
MSN
Sign in
 
 
Spaces home  MusclePhotosProfileFriendsMore Tools Explore the Spaces community

Muscle

No list items have been added yet.
No list items have been added yet.
View space
honne
View space
zhang
View space
(no name)

Updated 8/4/2007
August 17

j2me polish 学习(三)

现在我们来说说上边的ExpensesTracker里边用到的j2me知识,同时我会介绍polish,通过polish来美化ExpensesTracker。我会从j2me基本的GUI(主要是javax.microedition.lcdui;类)编程,然后会讨论j2me里的数据存储。

ExpensesTracker 程序里边由一个单独的MIDlet(Expenses)和一个数据类(ExpenseInfo)组成。Expenses提供了简单的UI,而ExpenseInfo则定义了数据的结构(fields for data and time, description, amount, and category )。因为lcdc1.0是不支持floating-point数据的(lcdc1.1支持),所在在ExpenseInfo里用两个独立的int 来记录expense amount的dollars和cents。

基本UI:

从上边工程运行的效果来看,Expenses UI 由一个List 组件(显示了你记录了的Expense条目)和三个Command 组件(quitting the app, editing an item, adding a new item)组成。

List是继承Screen类而来的,在j2me中的List组件其有三种类型:

· Implicit Lists (Expenses里是用此类型) :允许只能选择一个item, 当你选择某个item时会触发commandAction 事件。

· Exclusive Lists(单选):以radio按钮形式显示,一次只能选择一个item,选择时不会触发commandAction 事件。

· Multiple Lists(多选): 以check boxes 按钮形式显示,允许多选,选择时不会触发commandAction事件。

当用户选择之后,可以通过下面的方法返回选定的索引值:

getSelectedIndex()--------返回List中一个选定项的索引值。

Polish工程项目的编译,运行,打包等等,是由build.xml文件控制的。下边是ExpensesTracker工程里的build.xml:

<project
    name="ExpenseTracker"
    default="j2mepolish">
    <!-- extend the default NetBeans build script:           -->
    <import file="nbproject/build-impl.xml"/>
    <!-- import user specific properties                          -->
    <property file="${user.name}.properties" />
    <!-- The polish.home property needs to point to the directory -->
    <!-- containing the J2ME Polish installation.                 -->
    <property name="polish.home" location="C:\Program Files\J2ME-Polish" />
    <!-- import global properties                                 -->
    <property file="${polish.home}/global.properties" />

    <!-- Definition of the J2ME Polish task:                      -->
    <taskdef name="j2mepolish"
        classname="de.enough.polish.ant.PolishTask"
        classpath="${polish.home}/lib/enough-j2mepolish-build.jar:${polish.home}/lib/jdom.jar"
    />
    <!-- build targets, each target can be called via "ant [name]",
         e.g. "ant clean", "ant test j2mepolish" or just "ant" for calling the default-target -->
    <target name="j2mepolish-init"
            depends="init"
        >
          <property name="test" value="false" />
          <property name="customization" value="" />
        <property name="dir.work" value="build/real/${customization}" />
        <property name="dir.dist" value="dist/${customization}" />
        <property name="deploy-url" value="" />
    </target>
    <!-- In this target the J2ME Polish task is used.             -->
    <!-- It has 3 sections:                                       -->
    <!--    1. The info-section defines some general information  -->
    <!--    2. The deviceRequirements-section chooses the devices -->
    <!--       for which the application is optimized.            -->
    <!--    3. The build-section controls the actual build        -->
    <!--       process.                                           -->   
    <target name="j2mepolish"
            depends="j2mepolish-init"
            description="This is the controller for the J2ME build process."
            >
        <j2mepolish>
            <!-- general settings -->
            <info copyright="Copyright 2006, 2007 Your Company. All rights reserved."
                description="Please describe me."
                infoUrl="http://www.j2mepolish.org"
                jarName="${polish.vendor}-${polish.name}-${polish.locale}-ExpenseTracker.jar"
                jarUrl="${deploy-url}${polish.jarName}"
                name="ExpenseTracker"
                vendorName="Enough Software"
                version="1.0.0"
            />
            <!-- selection of supported devices -->
                        <deviceRequirements if="config.active:defined and (test or enableCompilerMode)" >       
                            <requirement name="Identifier" value="${config.active}" />
                        </deviceRequirements>
                        <deviceRequirements if="device:defined and (test or enableCompilerMode)" unless="config.active:defined">
                            <requirement name="Identifier" value="${device}" />
                        </deviceRequirements>
                        <deviceRequirements unless="test or enableCompilerMode">
                            <requirement name="Identifier" value="${devices}" />
                        </deviceRequirements>
            <!-- build settings -->
            <build fullscreen="menu"
                    symbols="polish.skipArgumentCheck"
                    usePolishGui="true"
                    workDir="${dir.work}"
                    destDir="${dir.dist}"
                >
                <!-- midlets definition -->
                <!-- NetBeans defines this property automatically:  -->
                <midlets definition="${manifest.midlets}" if="manifest.midlets:defined" />
                <midlets unless="manifest.midlets:defined">
                    <midlet class="ExpensesApp.Expenses" name="ExpensesTracker" />
                </midlets>
                <!-- project-wide variables - used for preprocessing  -->
                <variables>
                    <!-- Sample configuration options for J2ME Polish
                    <variable name="polish.TextField.useDirectInput" value="true" />
                    <variable name="polish.TextField.supportSymbolsEntry" value="true" />
                    <variable name="polish.MenuBar.useExtendedMenuBar" value="true" />
                    <variable name="polish.useScrollBar" value="true" />
                    -->
                </variables>
                <!-- Configure the customization settings here: -->
                <resources
                    dir="resources/base"
                    defaultexcludes="yes"
                    excludes="readme.txt"
                >
                    <root dir="resources/base/images" />
                    <root dir="resources/base/sounds" />
                    <root dir="resources/${customization}" if="build.${customization}" />
                    <root dir="resources/${customization}/images" if="build.${customization}" />
                    <root dir="resources/${customization}/sounds" if="build.${customization}" />
                    <!-- add the localization element for created localized
                         versions of your application:
                    <localization>
                        <locale name="en_US" />
                        <locale name="de_DE" encoding="utf-8" unless="test" />
                    </localization>
                    -->
                </resources>
                <!-- obfuscator settings: do not obfuscate when the test-property is true -->
                <obfuscator name="ProGuard" unless="test" >
                    <!--
                    You can set additional parameters here, e.g.:
                    <parameter name="optimize" value="false" />
                    -->
                </obfuscator>
                <!-- log settings: only use debug setting when the test-property is true -->
                <debug if="test" showLogOnError="true" verbose="true" level="error">
                    <filter pattern="de.enough.polish.example.*" level="debug" />
                    <filter pattern="de.enough.polish.ui.*" level="warn" />
                    <!-- example for writing log entries to the Recordstore Management System:
                    <handler name="rms" />
                    -->
                </debug>
                <!-- user defined JAD attributes can also be used: -->
                <jad>
                    <attribute name="Nokia-MIDlet-Category" value="Game" if="polish.group.Series40" />
                </jad>   
            </build>
            <!-- execution of emulator(s) -->
            <emulator
                wait="true"
                trace="none"
                securityDomain="trusted"
                enableProfiler="false"
                enableMemoryMonitor="false"
                enableNetworkMonitor="false"
                if="test and not debug"
                >
                <!--
                <parameter name="-Xjam" value="transient=http://localhost:8080/${polish.jadName}" />
                -->
            </emulator>
            <emulator
                wait="true"
                securityDomain="trusted"
                enableProfiler="false"
                enableMemoryMonitor="false"
                enableNetworkMonitor="false"
                if="debug"
                >
                <!-- Attach the emulator to the NetBeans debugger:    -->
                <debugger name="antcall" target="connect-debugger" port="6001" />
            </emulator>
        </j2mepolish>
    </target>
    <target name="setdeploy"
        description="Call this target first to set the OTA download-URL, e.g. ant setdeploy j2mepolish"
        >
        <property name="deploy-url" value="http://www.company.com/download/" />
    </target>
    <target name="enableDebug"
        description="Call this target first to skip the obfuscation step, call the emulator and start the debugger, e.g. ant enableDebug j2mepolish"
        >
        <property name="debug" value="true" />
    </target>
    <target name="enableEmulator"
        description="Call this target first to skip the obfuscation step and call the emulator, e.g. ant test j2mepolish"
        >
        <property name="test" value="true" />
        <property name="dir.work" value="build/test" />
    </target>
    <target
        name="emulator"
        depends="enableEmulator,j2mepolish"
        description="invokes the emulator"
    >
    </target>

    <target name="clean"
              description="allows a clean build. You should call [ant clean] whenever you made changes to devices.xml, vendors.xml or groups.xml">
        <delete dir="build" />
        <delete dir="dist" includes="**/*" />
    </target>

    <target 
        name="cleanbuild"
        description="allows a clean build. You should call [ant cleanbuild] whenever you made changes to devices.xml, vendors.xml or groups.xml"
        depends="clean, j2mepolish"
    />
    <target name="debug" description="debugs the project" depends="enableDebug, enableEmulator, j2mepolish" />
    <target name="deploy"
            description="Deploys the applications. Currently empty."
            depends="j2mepolish"
    />

    <!-- NetBeans specific build targets: -->
            <target name="run"
                    depends="enableEmulator, j2mepolish"
            >
            </target>

            <target name="rebuild"
                    depends="clean, j2mepolish"
            >
            </target>

            <target name="rebuild-all"
                    depends="clean, j2mepolish"
            >
            </target>
            <target name="jar"
                    depends="j2mepolish"
            >
            </target>

            <target name="jar-all"
                    depends="j2mepolish"
            >
            </target>

            <target name="clean-all"
                    depends="clean"
            >
            </target>
            <target name="deploy-all"
                    depends="deploy"
            >
            </target>
            <target name="enable-compiler-mode">
                <property name="enableCompilerMode" value="true" />
            </target>

            <target name="compile-single"
                    depends="enable-compiler-mode, j2mepolish"
            >
            </target>
        <target name="connect-debugger">
            <property name="jpda.port" value="${polish.debug.port}" />
            <antcall target="nbdebug"/>
        </target>

    <target name="enableCustomization1">
        <property name="dir.work" value="build/customization1" />
        <property name="cfg.customization1" value="true" />
    </target>
    <target name="customization1"
            description="customizes this project with the settings found in resources/customization1"
            depends="enableCustomization1, j2mepolish"
    />
    <target name="enableCustomization2">
        <property name="dir.work" value="build/customization2" />
        <property name="cfg.customization2" value="true" />
    </target>
    <target name="customization2"
            description="customizes this project with the settings found in resources/customization2"
            depends="enableCustomization2, j2mepolish"
    />
</project>

build.xml文件中最主要的是<info>,<deviceRequirements>和<build>三部分。

其中<info>元素指定说明了工程中的一般信息,如,工程名和版本。<deviceRequirements>元素是用来指定程序编译时所选择的模拟器。<build>元素是工程编译构件时的主要控制器(如,可以设定属性 usePolishGui=”true”来指定把polish应用当前工程中,而设为false时,则说明当前工程不引入polish,此时工程与一般的mobile应用程序一样)。

当build.xml文件中的<build>属性usePolishGui=”true”时,我们就可以修改工程目录中resources里的polish.css文件控制程序的显示(美化j2me介面)。在j2me程序中只需要以“//#style”指令就可以调用相应的css属性(请注意这指令与注释“//”的区别)。 其中,polish.css文件包含对应用程序的全部items和screens的style定义。

polish.css文件中常用的预定义的style(用于程序中的所有itemsscreens,但当引用自定义style时,预定义的style会被自定义style覆盖掉

colors{}:用来定义一系列的颜色,供其它style中的color属性引用。

backgrounds{}:用来定义一系列的背景,供其它style中的background属性引用。

borders{}:用来定义一系列的边框,供其它style中的border属性引用。

focused{}:当获取某个item或screen的焦点时的style。

title{}:应用于所以screen中的title的style。

scrollbar{}:当build.xml文件中“polish.useScrollBar“变量设为true时被scrollbar引用。

label{}:应用于所以item或menu中标签的style。

以下style是当build.xml文件中“polish.MenuBar.useExtendMenuBar“变量设为true时被引用的:

menubar{}, menu{},menuItem{},leftcommand{},rightcommand{}

polish.css文件中自定义style

注意:styel可以继承的,且引用子style时既保留父style的属性又有自己的属性,同时子style可以重写父style属性。

自定义style名称:如“.mystyle{}”是由点加字符串。注意不能是与预定义的名称相同。

下面我们来对ExpensesTracker中的UI---List以CSS进行修改。

修改后程序运行如下:

其polish.css文件:

colors {
    fontColor: rgb( 30, 85, 86 );
    focusedFontColor: #000;
    bgColor:  #eee;
    focusedBgColor:  #fff;
    borderColor: fontColor;
    focusedBorderColor: focusedFontColor;   
}

focused {
    margin: 1;   
    margin: 3;    
    background {
        type: round-rect;
        arc: 4;
        color: focusedBgColor;
        border-color: focusedBorderColor;
        border-width: 2;
    }
    font {
        style: bold;
        color: focusedFontColor;
        size: small;
    }
    layout: expand;   
}

/**
  * The title style is a predefined style which is used
  * for all screen-titles.
  */
title {
    padding: 2;
    font-face: proportional;
    font-size: large;
    font-style: bold;
    font-color: focusedFontColor;
    border: none;
        background {
            type: vertical-gradient;
            top-color: #fff;
            bottom-color: rgb(140,206,220);
            start: 10%;
            end: 90%;
        }
       layout: expand|center;
}
.expenseList{
    background {
       image:url(BG.png);
       color: rgb(184,235,246)
     }
}
.expenseItem{
        margin: 3;
        padding-left: 3;
        font-face: proportional;
    font-size: small;
    font-style: bold;
    font-color: focusedFontColor;
        focused-style: expenseItemFocused;
}
.expenseItemFocused extends .expenseItem{
        background-color: argb( 150, 255, 255, 255 );
        after: url(listItem.png);
        layout:expand;
}

而Expenses.java中引用CSS的地方(绿色字体):

public Expenses() {
        display = Display.getDisplay(this);
        //get some play data
        expenseItems = ExpenseInfo.LoadExpenses();
        itemCount = expenseItems.size();
        //Setup the UI
       //#style expenseList
        lsMain = new List("Expenses", List.IMPLICIT) ;
        cmAdd = new Command("New", Command.SCREEN, 3);
        cmEdit = new Command("Edit", Command.ITEM, 2);
        cmExit = new Command("Exit", Command.EXIT, 1);
        lsMain.addCommand(cmAdd);
        lsMain.addCommand(cmEdit);
        lsMain.addCommand(cmExit);

        lsMain.setCommandListener(this);
        //add all the Expense items to lsMain
        rebuildList();
    }

private void rebuildList(){
        //add all the expense items to lsMain
        for(int i=0; i<expenseItems.size(); i++){
            if (expenseItems.elementAt(i) != null){
                ExpenseInfo exp = (ExpenseInfo) expenseItems.elementAt(i);
                //#style expenseItem
                lsMain.append(exp.getDescription(),null);
            }
        }
    } 

August 04

j2me polish 学习(二)

上次介绍了一下polish的大概。其实本人我对j2me和polish都是刚接触的,以下来我会同时介绍j2me和polish的学习心得。以下我们以一个expenses tracker(builder.com里的一个例子)的例子来介绍建立polish项目(这个例子可以在)。

在netbeans我们可以先建立一个一般的Mobile的项目,按netbeans 向导很容易建立。这里工程expenses tracker里有两个类:

Expenses.java:

/*

Expenses

A sample J2ME MIDP application that illustrates the use

of List and Command UI components

Copyright 2002 CNet Networks

*/

package ExpensesApp;

import javax.microedition.midlet.*;

import javax.microedition.lcdui.*;

import java.util.Vector;

public class Expenses extends MIDlet implements CommandListener {

private Display display;

private Vector expenseItems;

private List lsMain;

private Command cmAdd, cmEdit, cmExit, cmMenu;

private int itemCount = 0;

public Expenses() {

display = Display.getDisplay(this);

//get some play data

expenseItems = ExpenseInfo.LoadExpenses();

itemCount = expenseItems.size();

//Setup the UI

lsMain = new List("Expenses", List.IMPLICIT) ;

cmAdd = new Command("New", Command.SCREEN, 3);

cmEdit = new Command("Edit", Command.ITEM, 2);

cmExit = new Command("Exit", Command.EXIT, 1);

lsMain.addCommand(cmAdd);

lsMain.addCommand(cmEdit);

lsMain.addCommand(cmExit);

lsMain.setCommandListener(this);

//add all the Expense items to lsMain

rebuildList();

}

public void startApp() throws MIDletStateChangeException {

//Show the main UI form

display.setCurrent(lsMain);

}

public void commandAction(Command cm, Displayable d) {

if (cm == List.SELECT_COMMAND || cm == cmEdit) {

//User selected an item in lsMain or

//invoked the "Edit" command from the menu

//Edit an item, we'll implement this later

} else if (cm == cmAdd) {

//User invoked the "Add" command from the menu

//Add a new item, we'll implement this later

} else if (cm == cmExit) {

//User invoked the "Exit" command

destroyApp(false);

notifyDestroyed();

}

}

public void pauseApp() {

}

public void destroyApp(boolean b) {

}

private void rebuildList(){

//add all the expense items to lsMain

for(int i=0; i<expenseItems.size(); i++){

if (expenseItems.elementAt(i) != null){

ExpenseInfo exp = (ExpenseInfo) expenseItems.elementAt(i);

lsMain.append(exp.getDescription(),null);

}

}

}

}

ExpenseInfo.java:

/*

ExpenseInfo

Models an expense item for the sample Expenses

J2ME MIDP application

Copyright 2002 CNet Networks

*/

package ExpensesApp;

import java.util.Date;

import java.util.Vector;

public class ExpenseInfo {

//Expense Item categories

public static final String[] Categories = {"Meals","Lodging","Car","Entertain","Misc"};

public static final int CATEGORY_MEALS = 0, CATEGORY_LODGING = 1,

CATEGORY_CAR = 2, CATEGORY_ENTER = 3, CATEGORY_MISC = 4;

//member variables

private Date ExpenseDate;

private String ExpenseDescription;

//We don't have floating point support,

//so we have to fake it with two int variables

//to store the amount of the expense

private int ExpenseDollars;

private int ExpenseCents;

private int ExpenseId;

private String ExpenseCategory;

private int ExpenseCategoryID;

public static Vector LoadExpenses() {

//Eventually, we'll need to load expenses from

//the data store into this vector, but for now

//let's just load some play data

Vector v = new Vector();

ExpenseInfo exp = new ExpenseInfo();

exp.setDescription("Car Rental");

exp.setDollars(25);

exp.setCents(99);

exp.setCategoryID(CATEGORY_CAR);

v.addElement(exp);

exp = new ExpenseInfo();

exp.setDescription("Hotel");

exp.setDollars(54);

exp.setCents(00);

exp.setCategoryID(ExpenseInfo.CATEGORY_LODGING);

v.addElement(exp);

exp = new ExpenseInfo();

exp.setDescription("Dinner");

exp.setDollars(18);

exp.setCents(78);

exp.setCategoryID(ExpenseInfo.CATEGORY_MEALS);

v.addElement(exp);

return v;

}

public ExpenseInfo(){

ExpenseDate = new Date();

ExpenseDescription = "";

ExpenseDollars = 0;

ExpenseCents = 0;

ExpenseCategoryID = ExpenseInfo.CATEGORY_MISC;

}

public void save() {

//mock saving data by printing to the console

System.out.println (ExpenseDate.toString() + " " + ExpenseDescription

+ " $" + String.valueOf(ExpenseDollars) + "." + String.valueOf(ExpenseCents));

}

//The rest of these are simply accessor methods

//for the class's private fields.

public void setDate(Date newDate){

ExpenseDate = newDate;

}

public Date getDate(){

return ExpenseDate;

}

public void setDescription(String newDescription){

ExpenseDescription = newDescription;

}

public String getDescription(){

return ExpenseDescription;

}

public void setDollars(int newDollars){

ExpenseDollars = newDollars;

}

public int getDollars(){

return ExpenseDollars;

}

public void setCents(int newCents){

ExpenseCents = newCents;

}

public int getCents() {

return ExpenseCents;

}

public String getCategory(){

return ExpenseCategory;

}

public int getCategoryID(){

return ExpenseCategoryID;

}

public void setCategoryID(int newID){

ExpenseCategoryID = newID;

ExpenseCategory = Categories[newID];

}

}

运行此工程如下:

现在我们来让这个工程变为polish工程:

我们可以把{polish.home}/sample/blank里的blank例子里的全部文件copy到expenses tracker 工程下,然后把原来expenses tracker工程文件夹下的src文件夹移入source文件夹里。把build-netbeans.xml改名为build.xml替换旧的build.xml文件。

然后我们来修改build.xml: 1)、工程名可以可以改为
ExpensesTracker, <j2mepolish></j2mepolish>中的<info> jarName可改为: jarName="${polish.vendor}-${polish.name}-${polish.locale}-ExpensesTracker.jar"(这是生成的jar和jad的名称,你也可以不改)。

2)、把全部<deviceRequirements> 注释掉改为:

<deviceRequirements>
<requirement name="Identifier" value="Generic/midp1" />
</deviceRequirements>

这里你可以把value值改为你想显示的模拟器,但是模拟器要安装在你机上,且polish支持(可以查看{polish.home}里的devices.xml文件看polish是否支持)。

3)、<midlet />属性改为你工程的midlet类本例子如下:
<midlet class="ExpensesApp.ExpenseInfo" name="ExpenseInfo" />

4)、引入{polish.home}/import/enough-j2mepolish-client.jar;

(注:我们也可以不用建立工程,直接把blank工程copy到你的工程目录下, 然后把你的旧工程代码src目录放入cource文件夹, 然后按上边的修改后在netbeans打开工程就可以了。)

现在就可以运行程序看看效果:

July 25

j2me polish学习(一)

     工作两差吾多两个月了, 里段时间里我都在做一个j2me项目, 项目是用到polish进行UI开发。回想起刚开始对polish的认识, 我系里度谈谈对polish噶认识。
 
     J2ME Polish是德国人的一个开源项目,主页是:http://www.j2mepolish.org/,类似于一个java开发环境的插件。本人开发环境是:window下netbeans 5+jdk1.5+Mobility +polish 2.0 RC4。安装polish很简单,从主页下载落来双击就可以安装了,如果你的机不能运行jar文件,则可以在cmd中用 “java -jar j2mepolish-2.0.jar ”就可以运行了(前提是你安装着jdk,且环境变量设置正确)。
     安装过程中,选择wtk.home和选择安装在netbeans和全部api,其他默认就可以了。安装后重启netbeans 就会自动嵌入netbeans中的了。下面我们来创建一个polish项目:
        有两种方法,一是在netbeans中创建polish项目(polish自带的samples),但这要清楚samples对应要用到的api 和支持的devices(相应你要装有此device),所以本人一般不用此方法。二是创建普通的Mobile application,然后把polish里边sample里的build.xml文件copy到工程目录下,修改<midlet /> 属性值,同时引入相应的api 包(/lib/enough-j2mepolish-build.jar 和 /lib/jdom.jar 一定要引入) 就行了。 
 
July 04

Dynamic TabbedForm in j2me polish

最近的一个J2ME项目中用到polish中的TabbedForm, 但polish中的TabbedForm并没有实现动态增删TabBar的功能,要实现此功能就要修改polish中的TabBar.java和TabbedForm.java类, 再重新编译polish....在http://developer.berlios.de/forum/forum.php 中有实现此功能的新的TabBar.java和TabbedForm.java类:
<code>
TabBar.java:
//#condition polish.usePolishGui
/*
 * Created on 23-Jan-2005 at 19:04:14.
 *
 * Copyright (c) 2005 Robert Virkus / Enough Software
 *
 * This file is part of J2ME Polish.
 *
 * J2ME Polish is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * J2ME Polish is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with J2ME Polish; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Commercial licenses are also available, please
 * refer to the accompanying LICENSE.txt or visit
 * http://www.j2mepolish.org for details.
 */
package de.enough.polish.ui;
import java.io.IOException;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import de.enough.polish.util.ArrayList;

/**
 * <p>Manages and paints the tabs of a tabbed form (or another Screen).</p>
 *
 * <p>Copyright (c) 2005, 2006 Enough Software</p>
 * <pre>
 * history
 *        23-Jan-2005 - rob creation
 * </pre>
 * @author Robert Virkus, j2mepolish@enough.de
 */
public class TabBar extends Item {
 private final ArrayList tabs;
 //#if !polish.LibraryBuild
  //# private final Style activeStyle;
  //# private final Style inactiveStyle;
 //#else
  private Style activeStyle;
  private Style inactiveStyle;
 //#endif
 
 private int activeTabIndex;
 //#ifdef polish.hasPointerEvents
  protected int newActiveTabIndex;
 //#endif
 private int xOffset;
 private int scrollArrowHeight = 10;
 private int scrollArrowPadding = 2;
 private int scrollArrowColor = 0xFFFFFF;
 //#ifdef polish.css.tabbar-left-arrow
  private Image leftArrow;
 //#endif
 //#ifdef polish.css.tabbar-right-arrow
  private Image rightArrow;
 //#endif
 private int arrowYOffset;
 private int arrowXOffset;
 private boolean allowRoundtrip;
 private int nextTabIndex;
 
 /**
  * Creates a new tab bar.
  *
  * @param tabNames the names of the tabs
  * @param tabImages the images of the tabs, can be null
  */
 public TabBar(String[] tabNames, Image[] tabImages) {
  this( tabNames, tabImages, null );
 }
 /**
  * Creates a new tab bar.
  *
  * @param tabNames the names of the tabs
  * @param tabImages the images of the tabs, can be null
  * @param style the style of the bar
  */
 public TabBar(String[] tabNames, Image[] tabImages, Style style) {
  super( null, 0, Item.BUTTON, style);
  if (tabImages == null) {
   tabImages = new Image[ tabNames.length ];
  } else if (tabNames == null) {
   tabNames = new String[ tabImages.length ];
  }
  // getting styles:
  //#if !polish.LibraryBuild
   //#style activetab, tab, default
 this.activeStyle = ();
   //#style inactivetab, tab, default
 this.inactiveStyle = ();
  //#endif
  
  this.tabs = new ArrayList(tabNames.length + 5);
  for (int i = 0; i < tabImages.length; i++) {
   String name = tabNames[i];
   Image image = tabImages[i];
   ImageItem tab = new ImageItem( null, image, 0, name, this.inactiveStyle );
   this.tabs.add(tab);
  }
  ((ImageItem) this.tabs.get(0)).style = this.activeStyle;
 }
 /**
  * add a new tabbar at the end and set it active as specified.
  * @param tabName
  * @param tabImage
  * @param active
  */
 protected void addTab(String tabName, Image tabImage, boolean active) {
  ImageItem tab = null;
  if (active) {
   ((ImageItem)this.tabs.get(this.activeTabIndex)).setStyle(this.inactiveStyle);
   this.activeTabIndex = tabs.size() - 1;
   tab = new ImageItem(null, tabImage, 0, tabName, this.activeStyle);
  } else {
   tab = new ImageItem(null, tabImage, 0, tabName, this.inactiveStyle);
  }
  this.tabs.add(tab);
  this.isInitialized = false;
 }
 
 /**
  * add a new tabbar and set it active as specified.
  * @param index the index of this newly added tab, start from zero to (size - 1);
  * will throw a IndexOutOfBoundsException if not.
  * @param tabName
  * @param tabImage
  * @param active
  */
 protected void addTab(int index, String tabName, Image tabImage, boolean active){
  ImageItem tab = null;
  if (active) {
   ((ImageItem)this.tabs.get(this.activeTabIndex)).setStyle(this.inactiveStyle);
   tab = new ImageItem(null, tabImage, 0, tabName, this.activeStyle);
   this.activeTabIndex = index;
  } else {
   tab = new ImageItem(null, tabImage, 0, tabName, this.inactiveStyle);
   //activeTabIndex needs to be updated if it is after the newly added tab.
   if (this.activeTabIndex >= index)
    this.activeTabIndex++;
  }
  this.tabs.add(index, tab);
  this.isInitialized = false;
 }
 
 /**
  * set a tabbar at the given index with a new one and set it activity as the old ones.
  * @param index
  * @param tabName
  * @param tabImage
  */
 protected void setTab(int index, String tabName, Image tabImage) {
  if (this.activeTabIndex == index) {
   this.setTab(index, tabName, tabImage, true);
  } else {
   this.setTab(index, tabName, tabImage, false);
  }
 }
 /**
  *
  * set a tabbar at the given index with a new one. if the tabbar is active currently
  * but want to be inactive, the first one will be set active.
  * @param index
  * @param tabName
  * @param tabImage
  * @param active
  */
 protected void setTab(int index, String tabName, Image tabImage, boolean active) {
  ImageItem tab = ((ImageItem) this.tabs.get(index));
  if (tab == null)
   return;
  
  if (active) {
   tabs.set(index, new ImageItem(null, tabImage, 0, tabName,this.activeStyle));
   if (index != this.activeTabIndex) {
    ((ImageItem)this.tabs.get(this.activeTabIndex)).setStyle(this.inactiveStyle);
    this.activeTabIndex = index;
   }
  } else {
   tabs.set(index, new ImageItem(null, tabImage, 0, tabName,this.inactiveStyle));
   if (index == this.activeTabIndex) {
    ((ImageItem) this.tabs.get(0)).setStyle(this.activeStyle);
   }
  }
  this.isInitialized = false;
 }
 
 /**
  * remove the last tab.if the tabbar is active currently, the first
  * one will be set active.
  *
  */
 protected void removeTab() {
  this.removeTab(this.tabs.size() - 1);
 }
 
 /**
  * delete a tabbar at the given index. if the tabbar is active currently, thefirst
  * one will be set active.
  * @param index
  */
 protected void removeTab(int index) {
  ImageItem tab = ((ImageItem) this.tabs.get(index));
  if (tab == null)
   return;
  tabs.remove(index);
  
  if (this.activeTabIndex == index) {
   ((ImageItem) this.tabs.get(0)).setStyle(this.activeStyle);
   this.activeTabIndex = 0;
  } else if (index < this.activeTabIndex) {//activeTabIndex needs to be updated if it is after the removed one.
   this.activeTabIndex--;
  }
  this.isInitialized = false;
 }
 
 /**
  * Changes the active/selected tab.
  *
  * @param index the index of the active tab, the first tab has the index 0.
  */
 public void setActiveTab( int index ) {
  // deactivating the old tab:
  ((ImageItem)this.tabs.get( this.activeTabIndex )).setStyle(this.inactiveStyle);
  // activating the new tab:
  ((ImageItem)this.tabs.get( index )).setStyle(this.activeStyle);
  this.activeTabIndex = index;
  this.isInitialized = false;
 }
 
 /* (non-Javadoc)
  * @see de.enough.polish.ui.Item#initContent(int, int)
  */
 protected void initContent(int firstLineWidth, int lineWidth) {
  int scrollerWidth = this.scrollArrowHeight + 2 * this.scrollArrowPadding;
  int maxHeight = scrollerWidth;
  int completeWidth = 0;
  int rightBorder = lineWidth - scrollerWidth;
  //#if polish.css.tabbar-roundtrip
   if (this.allowRoundtrip || this.activeTabIndex == 0 || this.activeTabIndex == this.tabs.size() -1) {
  //#else
                if (this.activeTabIndex == 0 || this.activeTabIndex == this.tabs.size() -1) {
  //#endif
   // only one scroll indicator needs to be painted
   //#if polish.css.tabbar-roundtrip
                if (this.activeTabIndex != 0 && !this.allowRoundtrip) {
                //#else
                        if (this.activeTabIndex != 0) {
   //#endif
    rightBorder = lineWidth;
   }
   lineWidth -= maxHeight;
   completeWidth = maxHeight;
  } else {
   lineWidth -= 2 * maxHeight;
   completeWidth = 2 * maxHeight;
  }
  
  int activeTabXPos = 0;
  int activeTabWidth = 0;
  for (int i = 0; i < this.tabs.size(); i++) {
   //ImageItem tab = this.tabs[i];
                        ImageItem tab = (ImageItem) this.tabs.get(i);
   int tabHeight = tab.getItemHeight(firstLineWidth, lineWidth);
   if (tabHeight > maxHeight ) {
    maxHeight = tabHeight;
   }
   if (i == this.activeTabIndex) {
    activeTabXPos = completeWidth;
    activeTabWidth = tab.itemWidth;
   }
   // I can use the itemWidth field, since I have called getItemHeight(..) above,
   // which initialises the tab.
   tab.relativeX = completeWidth;
   completeWidth += tab.itemWidth;
  }
  this.contentHeight = maxHeight;
  this.contentWidth = completeWidth;
  if (this.activeTabIndex == 0) {
   this.xOffset = 0;
  } else if ( this.xOffset + activeTabXPos < scrollerWidth ) {
   // tab is too much left:
   this.xOffset = scrollerWidth - activeTabXPos;
   //System.out.println("this.xOffset + activeTabXPos < scrollerWidth ");
  } else if ( this.xOffset + activeTabXPos + activeTabWidth > rightBorder ) {
   // tab is too much right:
   //#if polish.css.tabbar-roundtrip
    if (this.allowRoundtrip) {
     this.xOffset = (rightBorder - activeTabWidth - activeTabXPos);
    } else {
   //#endif
     this.xOffset = (rightBorder - activeTabWidth) - (activeTabXPos - scrollerWidth);
   //#if polish.css.tabbar-roundtrip
    }
   //#endif
   //System.out.println("this.xOffset + activeTabXPos + activeTabWidth > rightBorder");
   //System.out.println("xOffset=" + this.xOffset + ", activeTabXPos=" + activeTabXPos + ", activeTabWidth=" + activeTabWidth + ", rightBorder=" + rightBorder);
  }
 }
         //               }
       // }
 /* (non-Javadoc)
  * @see de.enough.polish.ui.Item#paintContent(int, int, int, int, javax.microedition.lcdui.Graphics)
  */
 protected void paintContent(int x, int y, int leftBorder, int rightBorder, Graphics g)
 {
  // draw scrolling indicators:
  g.setColor( this.scrollArrowColor );
  int cHeight = this.contentHeight;
  y += (cHeight - this.scrollArrowHeight) / 2;
  int originalX = x;
  //#if polish.css.tabbar-roundtrip
   if ( this.allowRoundtrip || this.activeTabIndex > 0 ) {
  //#else
if ( this.activeTabIndex > 0 ) {
  //#endif
   // draw left scrolling indicator:
   x += this.scrollArrowPadding;
   //#ifdef polish.css.tabbar-left-arrow
    if (this.leftArrow != null) {
     g.drawImage(this.leftArrow, x + this.arrowXOffset, y + this.arrowYOffset, Graphics.LEFT |  Graphics.TOP );
    } else {
   //#endif
     int halfWidth = this.scrollArrowHeight / 2;
     //#ifdef polish.midp2
//#       g.fillTriangle(x, y + halfWidth-1, x + this.scrollArrowHeight, y, x + this.scrollArrowHeight, y + this.scrollArrowHeight );
     //#else
      g.drawLine( x, y + halfWidth-1, x + this.scrollArrowHeight, y );
      g.drawLine( x + this.scrollArrowHeight, y, x + this.scrollArrowHeight, y + this.scrollArrowHeight);
      g.drawLine( x, y + halfWidth-1, x + this.scrollArrowHeight, y  + this.scrollArrowHeight );
     //#endif
   //#ifdef polish.css.tabbar-left-arrow
    }
   //#endif
   x += this.scrollArrowHeight + this.scrollArrowPadding;
   //#if polish.css.tabbar-roundtrip
    if (this.allowRoundtrip) {
     originalX = x;
    }
   //#endif
  }
   
  //#if polish.css.tabbar-roundtrip
   if ( this.allowRoundtrip || this.activeTabIndex < this.tabs.size() - 1 ) {
  //#else
if ( this.activeTabIndex < this.tabs.size() - 1 ) {
  //#endif  
   // draw right scrolling indicator:
   rightBorder -=  (this.scrollArrowHeight + this.scrollArrowPadding);
   //#ifdef polish.css.tabbar-right-arrow
    if (this.rightArrow != null) {
     g.drawImage(this.rightArrow, rightBorder + this.arrowXOffset, y + this.arrowYOffset, Graphics.LEFT |  Graphics.TOP );
    } else {
   //#endif
     int halfWidth = this.scrollArrowHeight / 2;
     //#ifdef polish.midp2
//#       g.fillTriangle(rightBorder, y, rightBorder, y  + this.scrollArrowHeight, rightBorder + this.scrollArrowHeight, y + halfWidth - 1 );
     //#else
      g.drawLine( rightBorder, y, rightBorder, y  + this.scrollArrowHeight );
      g.drawLine( rightBorder, y, rightBorder + this.scrollArrowHeight, y + halfWidth - 1);
      g.drawLine( rightBorder, y  + this.scrollArrowHeight, rightBorder + this.scrollArrowHeight, y + halfWidth - 1);
     //#endif
   //#ifdef polish.css.tabbar-right-arrow
    }
   //#endif
   rightBorder -=  this.scrollArrowPadding;
  }
  // draw the tabs:
  y -= (cHeight - this.scrollArrowHeight) / 2;
  int clipX = g.getClipX();
  int clipY = g.getClipY();
  int clipWidth = g.getClipWidth();
  int clipHeight = g.getClipHeight();
  g.setClip( x, y, rightBorder - x, clipHeight);
  x = originalX + this.xOffset;
  for (int i = 0; i < this.tabs.size(); i++) {
   ImageItem tab = (ImageItem)this.tabs.get(i);
   int tabHeight = tab.itemHeight;
   tab.paint( x, y + (cHeight - tabHeight), leftBorder, rightBorder, g );
   x += tab.itemWidth;
  }
  g.setClip( clipX, clipY, clipWidth, clipHeight);  
 }
                     //   }
        //}
 //#ifdef polish.useDynamicStyles 
//#  /* (non-Javadoc)
//#   * @see de.enough.polish.ui.Item#createCssSelector()
//#   */
//#  protected String createCssSelector() {
//#   return "tabbar";
//#  }
 //#endif

 //#ifdef polish.useDynamicStyles 
 /* (non-Javadoc)
  * @see de.enough.polish.ui.Item#createCssSelector()
  */
 protected String createCssSelector() {
  return "tabbar";
 }
 //#endif
 
 public void setStyle(Style style) {
  super.setStyle(style);
  //#ifdef polish.css.tabbar-scrolling-indicator-color
   Integer scrollColorInt = style.getIntProperty("tabbar-scrolling-indicator-color");
   if (scrollColorInt != null) {
    this.scrollArrowColor = scrollColorInt.intValue();
   }
  //#endif
  //#ifdef polish.css.tabbar-left-arrow
   String leftArrowUrl = style.getProperty("tabbar-left-arrow");
   if (leftArrowUrl != null) {
    try {
     this.leftArrow = StyleSheet.getImage(leftArrowUrl, this, false);
     this.scrollArrowHeight = this.leftArrow.getHeight();
    } catch (IOException e) {
     //#debug error
     System.out.println("Unable to load tabbar-left-arrow " + leftArrowUrl);
    }
   }
  //#endif
  //#ifdef polish.css.tabbar-right-arrow
   String rightArrowUrl = style.getProperty("tabbar-right-arrow");
   if (rightArrowUrl != null) {
    try {
     this.rightArrow = StyleSheet.getImage(rightArrowUrl, this, false);
     this.scrollArrowHeight = this.rightArrow.getHeight();
    } catch (IOException e) {
     //#debug error
     System.out.println("Unable to load tabbar-right-arrow " + rightArrowUrl);
    }
   }
  //#endif
  //#ifdef polish.css.tabbar-arrow-y-offset
   Integer arrowYOffsetInt = style.getIntProperty("tabbar-arrow-y-offset");
   if (arrowYOffsetInt != null) {
    this.arrowYOffset = arrowYOffsetInt.intValue();
   }
  //#endif
  //#ifdef polish.css.tabbar-arrow-x-offset
   Integer arrowXOffsetInt = style.getIntProperty("tabbar-arrow-x-offset");
   if (arrowXOffsetInt != null) {
    this.arrowXOffset = arrowXOffsetInt.intValue();
   }
  //#endif
  //#if polish.css.tabbar-roundtrip
   Boolean allowRoundtripBool = style.getBooleanProperty("tabbar-roundtrip");
   if (allowRoundtripBool != null) {
    this.allowRoundtrip = allowRoundtripBool.booleanValue();
   }
  //#endif
 }
 
 
 
 /* (non-Javadoc)
  * @see de.enough.polish.ui.Item#handleKeyPressed(int, int)
  */
 protected boolean handleKeyPressed(int keyCode, int gameAction) {
  //#if polish.css.tabbar-roundtrip
   if (this.allowRoundtrip) {
    if (gameAction == Canvas.RIGHT) {
     this.nextTabIndex = this.activeTabIndex + 1;
     if (this.nextTabIndex >= this.tabs.size()) {
      this.nextTabIndex = 0;
     }
     return true;
    } else if (gameAction == Canvas.LEFT) {
     this.nextTabIndex = this.activeTabIndex - 1;
     if (this.nextTabIndex < 0) {
      this.nextTabIndex = this.tabs.size() - 1;
     }  
     return true;
    }
   }
  //#endif
  if (gameAction == Canvas.RIGHT && this.activeTabIndex < this.tabs.size() -1 ) {
   this.nextTabIndex = this.activeTabIndex + 1;
   return true;
  } else if (gameAction == Canvas.LEFT && this.activeTabIndex > 0) {
   this.nextTabIndex = this.activeTabIndex - 1;
   return true;
  }
  return super.handleKeyPressed(keyCode, gameAction);
 }
 //#ifdef polish.hasPointerEvents
//#  protected boolean handlePointerPressed(int x, int y) {
//#   if (!isInContentArea(x, y)) {
//#    return false;
//#   }
//#   //System.out.println( "pointer-pressed: " + x + ", " + y);
//#   int scrollerWidth = this.scrollArrowHeight + 2 * this.scrollArrowPadding;
//#   if ( (this.activeTabIndex > 0 || this.allowRoundtrip) && x <= scrollerWidth ) {
//#    //System.out.println("left: x <= " + scrollerWidth );
//#    int index = this.activeTabIndex - 1;
//#    if (index < 0) {
//#     index = this.tabs.size() - 1;
//#    }
//#    this.newActiveTabIndex = index;
//#   } else if ( (this.activeTabIndex < this.tabs.size() -1 || this.allowRoundtrip) && x >= this.contentWidth - scrollerWidth) {
//#    //System.out.println("right: x >= " + (this.xRightPos - scrollerWidth) );
//#    this.newActiveTabIndex = (this.activeTabIndex + 1) % this.tabs.size();
//#   } else {
//#    if (this.activeTabIndex > 0 || this.allowRoundtrip) {
//# //    x -= scrollerWidth;
//#    }
//#    for (int i = 0; i < this.tabs.size(); i++) {
//#     ImageItem tab = (ImageItem)this.tabs.get(i);
//# //    System.out.println( "x=" + x + ", tab.relativeX=" + tab.relativeX + ", xOffset=" + this.xOffset +  ", tab=" + tab);
//#     int tabX = tab.relativeX + this.xOffset;
//#     if (tabX <= x && x <= tabX + tab.itemWidth) {
//#      this.newActiveTabIndex = i;
//#      break;
//#     }
//#    }
//#   }
//#   if (this.screen instanceof TabbedForm) {
//#    ((TabbedForm)this.screen).setActiveTab( this.newActiveTabIndex );
//#   }
//#   
//#   return (this.newActiveTabIndex != this.activeTabIndex);
//#  }
 //#endif
 /**
  * Sets the image for the specified tab.
  *
  * @param tabIndex the index of the tab
  * @param image the image
  */
 public void setImage(int tabIndex, Image image) {
  ((ImageItem)this.tabs.get(tabIndex)).setImage(image);  
 }
 
 /**
  * Sets the text for the specified tab.
  *
  * @param tabIndex the index of the tab
  * @param text the text
  */
 public void setText(int tabIndex, String text ) {
  ((ImageItem)this.tabs.get(tabIndex)).setAltText(text);  
 }
 /**
  * Retrieves the index of the currently selected tab.
  *
  * @return the index of the currently selected tab, 0 is the index of the first tab.
  */
 public int getNextTab() {
  return this.nextTabIndex;
 }
}
//#condition polish.usePolishGui
/*
 * Created on 23-Jan-2005 at 18:46:50.
 *
 * Copyright (c) 2005 Robert Virkus / Enough Software
 *
 * This file is part of J2ME Polish.
 *
 * J2ME Polish is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * J2ME Polish is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with J2ME Polish; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Commercial licenses are also available, please
 * refer to the accompanying LICENSE.txt or visit
 * http://www.j2mepolish.org for details.
 */
package de.enough.polish.ui;
import javax.microedition.lcdui.Image;
import de.enough.polish.util.ArrayList;
/**
 * <p>Separates a form into several tabs.</p>
 *
 * <p>Copyright (c) 2005, 2006 Enough Software</p>
 * <pre>
 * history
 *        23-Jan-2005 - rob creation
 * </pre>
 * @author Robert Virkus, j2mepolish@enough.de
 */
public class TabbedForm extends Form {
 
 //#if polish.classes.TabBar:defined
  //#= private final ${polish.classes.TabBar} tabBar;
 //#else
  private final TabBar tabBar;
 //#endif
 private final ArrayList tabContainers;
 private int activeTabIndex;
 private TabbedFormListener tabbedFormListener;
 /**
  * Creates a new tabbed form without a style.
  *
  * @param title the title of the form.
  * @param tabNames the names of the tabs
  * @param tabImages the images of the tabs, can be null
  */
 public TabbedForm(String title, String[] tabNames, Image[] tabImages ) {
  this( title, tabNames, tabImages, null );
 }
 /**
  * Creates a new tabbed form.
  * 
  * @param title the title of the form.
  * @param tabNames the names of the tabs
  * @param tabImages the images of the tabs, can be null
  * @param style the style of this tabbed form.
  * @throws NullPointerException if tabNames is null
  */
 public TabbedForm(String title, String[] tabNames, Image[] tabImages, Style style) {
  super(title, style );
  //#if polish.classes.TabBar:defined
   //#style tabbar, default
   //#= this.tabBar = new ${polish.classes.TabBar} ( tabNames, tabImages );
  //#else
   //#style tabbar, default
   this.tabBar = new TabBar( tabNames, tabImages );
  //#endif
  int length;
  if (tabNames != null) {
   length = tabNames.length;
  } else {
   length = tabImages.length;
  }
  this.tabContainers = new ArrayList(length + 5);
  //this.tabContainers[0] = this.container;
  this.tabContainers.add(this.container);
  for (int i = 1; i < length; i++) {
   Container tabContainer = new Container( null, true, null, this.screenHeight );
   if (style != null) {
    tabContainer.setStyle( style, true );
   }
   tabContainer.screen = this;
   this.tabContainers.add(tabContainer);
  }
  setSubTitle( this.tabBar );
 }
 //#if polish.LibraryBuild
//#  /**
//#   * Adds the item  to this form.
//#   *
//#   * @param tabIndex the index of the tab to which the item should be added,
//#   *        the first tab has the index 0.
//#   * @param item the item which should be added.
//#   */
//#  public void append( int tabIndex, javax.microedition.lcdui.Item item ) {
//#   throw new RuntimeException("Unable to use standard items in a tabbed form - please append only J2ME Polish items.");
//#  }
 //#endif
 //#if polish.LibraryBuild
//#  /**
//#   * Changes the item of a tab.
//#   *
//#   * @param tabIndex the index of the tab,
//#   *        the first tab has the index 0.
//#   * @param itemIndex the index of the item in the tab
//#   * @param item the item which should be added.
//#   */
//#  public void set( int tabIndex, int itemIndex, javax.microedition.lcdui.Item item ) {
//#   throw new RuntimeException("Unable to use standard items in a tabbed form - please append only J2ME Polish items.");
//#  }
 //#endif
 
 //#if polish.LibraryBuild
//#  /**
//#   * Deletes the item from this form.
//#   *
//#   * @param tabIndex the index of the tab from which the item should be removed,
//#   *        the first tab has the index 0.
//#   * @param item the item which should be removed.
//#   */
//#  public void delete( int tabIndex, javax.microedition.lcdui.Item item ) {
//#   throw new RuntimeException("Unable to use standard items in a tabbed form - please append only J2ME Polish items.");
//#  }
 //#endif
 
 
 //#if polish.LibraryBuild
//#  public void setItemStateListener( javax.microedition.lcdui.ItemStateListener listener ) {
//#   throw new RuntimeException("Unable to use standard ItemStateListener in a tabbed form.");
//#  }
 //#endif
 
 /**
  * Adds the  item  to the first tab of this form.
  *
  * @param item the item which should be added.
  * @param itemStyle the style for that item
   * @return the assigned index of the Item within the specified tab
  */
 public int append( Item item, Style itemStyle ) {
  return append( 0, item, itemStyle );
 }
 
 /**
  * Adds the  item  to this form.
  *
  * @param tabIndex the index of the tab to which the item should be added,
  *        the first tab has the index 0.
  * @param item the item which should be added.
  * @return the assigned index of the Item within the specified tab
  */
 public int append( int tabIndex, Item item ) {
  return append( tabIndex, item, null );  
 }
 /**
  * Adds the  item  to this form.
  *
  * @param tabIndex the index of the tab to which the item should be added,
  *        the first tab has the index 0.
  * @param item the item which should be added.
   * @param itemStyle the style for that item
  * @return the assigned index of the Item within the specified tab
  */
 public int append( int tabIndex, Item item, Style itemStyle ) {
  //#if polish.Container.allowCycling != false
//#    if (item instanceof Container) {
//#     ((Container)item).allowCycling = false;
//#    }
  //#endif
  if (itemStyle != null) {
   item.setStyle( itemStyle );
  }
   Container tabContainer = (Container)this.tabContainers.get(tabIndex);
  tabContainer.add(item);
  return tabContainer.size() - 1;
 }

 /**
  * Add a tab to the end of all the tabs and set it active as specified.
  * @param tabName
  * @param tabImage
  * @param active
  */
 public void addTab(String tabName, Image tabImage, boolean active) {
  this.tabBar.addTab(tabName, tabImage, active);
  
  Container tabContainer = new Container( null, true, null,  this.screenHeight );
  tabContainer.setStyle( style, true );
  tabContainer.screen = this;
  this.tabContainers.add(tabContainer);
  
  if (active) {
   this.activeTabIndex = this.tabContainers.size() - 1;
  }
  
  this.setActiveTab(this.activeTabIndex);
 }
 
 /**
  * Adds a tab at the given index and set it active as specified. All existing tabs
  * behinded it will be shifted afterwards.
  * Caution! tabIndex can not be zero, if you are using zero in the code, it will be a mess here.
  * @param tabIndex tabIndex can not be zero, although not checked,
  * but if you are using zero in your code, it will be a mess here, I promise you this.
  * @param tabName
  * @param tabImage
  * @param active
  */
 public void addTab(int tabIndex, String tabName, Image tabImage, boolean active) {
  this.tabBar.addTab(tabIndex, tabName, tabImage, active);
  
  Container tabContainer = new Container(  null, true, null, this.screenHeight );
  tabContainer.setStyle( style, true );
  tabContainer.screen = this;
  this.tabContainers.add(tabIndex, tabContainer);
  
  if (!active) {
   if (tabIndex <= this.activeTabIndex)
    this.activeTabIndex++;
  } else {
   this.activeTabIndex = tabIndex;
  }
  
  this.setActiveTab(this.activeTabIndex);
 }
 
 /**
  * Changes the tab at the given index with a new one. All items belongs to the
  * old tab will be removed according to the keepItems argument.
  * @param tabIndex
  * @param tabName
  * @param tabImage
  * @parma keepItems
  * @param active
  */
 public void setTab(int tabIndex, String tabName, Image tabImage, boolean keepItems, boolean active) {
  if (!keepItems) {
   Container tabContainer = (Container) this.tabContainers.get( tabIndex );
   tabContainer.clear();
  }
  
  tabBar.setTab(tabIndex, tabName, tabImage, active);
  if (active && tabIndex != this.activeTabIndex) {
   this.activeTabIndex = tabIndex;
  }
  this.setActiveTab(this.activeTabIndex);
 }
 
 /**
  * Remove the last tab in this form. Caution! if there is only one, although
  * the tab will be removed, but the form will be a mess.
  *
  */
 public void deleteTab() {
  this.deleteTab(this.tabContainers.size() - 1);
 }
 
 /**
  * Caution! tabIndex can not be zero, if you are using zero in the code, it will be a mess here.
  * @param tabIndex tabIndex can not be zero, although not checked,
  * but if you are using zero in your code, it will be a mess here, I promise you this.
  */
 public void deleteTab(int tabIndex) {
  Container tabContainer = (Container) this.tabContainers.get( tabIndex );
  
  tabContainer.clear();
  this.tabContainers.remove(tabIndex);
  this.tabBar.removeTab(tabIndex);
  
  if (tabIndex == this.activeTabIndex) {
   this.activeTabIndex = 0;
  } else if (tabIndex < this.activeTabIndex) {
   this.activeTabIndex--;
  }
  this.setActiveTab(this.activeTabIndex);
 }

 /**
  * Changes the item of the first tab.
  *
  * @param itemIndex the index of the item in the tab
  * @param item the item which should be added.
  */
 public void set( int itemIndex, Item item ) {
  set( 0, itemIndex, item );
 }
 /**
  * Changes the item of a tab.
  *
  * @param tabIndex the index of the tab,
  *        the first tab has the index 0.
  * @param itemIndex the index of the item in the tab
  * @param item the item which should be added.
  */
 public void set( int tabIndex, int itemIndex, Item item ) {
  //#if polish.Container.allowCycling != false
//#    if (item instanceof Container) {
//#     ((Container)item).allowCycling = false;
//#    }
  //#endif
  Container tabContainer = (Container)this.tabContainers.get(tabIndex);
  tabContainer.set(itemIndex, item);
 }
 
 /**
  * Gets the item at given position within the specified tab. 
  * The contents of the
  * <code>TabbedForm</code> are left unchanged.
  * The <code>itemNum</code> parameter must be
  * within the range <code>[0..size()-1]</code>, inclusive.
  *
  * @param tabIndex the index of the tab,
  *        the first tab has the index 0.
  * @param itemNum the index of item
  * @return the item at the given position
  * @throws IndexOutOfBoundsException - if itemNum is invalid
  */
 //#if polish.LibraryBuild
//#   public javax.microedition.lcdui.Item get(int tabIndex, int itemNum)
//#   {
//#    return null;
//#   }
 //#else
public Item get(int tabIndex, int itemNum)
{
Container tabContainer = (Container)this.tabContainers.get(tabIndex);
return tabContainer.get( itemNum );
}
 //#endif
 
 /**
  * Deletes the item from this form.
  *
  * @param tabIndex the index of the tab from which the item should be removed,
  *        the first tab has the index 0.
  * @param item the item which should be removed.
  */
 public void delete( int tabIndex, Item item ) {
  Container tabContainer = (Container)this.tabContainers.get( tabIndex );
  tabContainer.remove( item );
  if (this.isShown() ) {
   repaint();
  }
 }
 
 /**
  * Deletes the item from this form.
  *
  * @param tabIndex the index of the tab from which the item should be removed,
  *        the first tab has the index 0.
  * @param itemIndex the index of the item which should be removed.
  */
 public void delete( int tabIndex, int itemIndex ) {
  Container tabContainer = (Container)this.tabContainers.get( tabIndex );
  tabContainer.remove( itemIndex );
  if (this.isShown() ) {
   repaint();
  }
 }
 /**
  * Deletes the all items from the specified tab.
  *
  * @param tabIndex the index of the tab from which all items should be removed,
  *        the first tab has the index 0.
  */
 public void deleteAll( int tabIndex ) {
  Container tabContainer = (Container)this.tabContainers.get( tabIndex );
  tabContainer.clear();
 }
 
 /**
  * Retrieves the number of elements within the specified tab.
  *
  * @param tabIndex the tab, the first tab has the index 0
  * @return the number of elements within that tab
  */
 public int size( int tabIndex ) {
  Container tabContainer = (Container)this.tabContainers.get( tabIndex );
  return tabContainer.size();
 }
 /**
  * Retrieves the number of tabs in this <code>TabbedForm</code>.
  *
  * @return the number of tabs
  */
 public int getTabCount() {
  return this.tabContainers.size();
 }
 /**
  * Focuses the specified tab.
  *
  * @param tabIndex the index of the tab, the first tab has the index 0.
  */
 public void setActiveTab( int tabIndex ) {
  if (!notifyTabbedChangeRequested( this.activeTabIndex, tabIndex )) {
   return;
  }
  //#debug
  System.out.println("Activating tab [" + tabIndex + "].");
  if (this.container.isInitialized) {
   //System.out.println("defocus of container " + this.container);
   this.container.hideNotify();
   this.container.defocus( this.container.style );
   //#if polish.TabbedForm.releaseResourcesOnTabChange
//#     this.container.releaseResources();
   //#endif
  }
  int oldTabIndex = this.activeTabIndex;
  this.activeTabIndex = tabIndex;
  this.tabBar.setActiveTab(tabIndex);
  Container tabContainer = (Container)this.tabContainers.get(tabIndex);
  this.container = tabContainer;
  tabContainer.setScrollHeight( this.contentHeight );
  if (!tabContainer.isInitialized) {
   tabContainer.init( this.contentWidth, this.contentWidth );
  }
  if (tabContainer.appearanceMode != Item.PLAIN) {
   //#debug
   System.out.println("Focusing tab [" + tabIndex + "].");
   tabContainer.focus( tabContainer.style, 0 );
  }
        //#if polish.blackberry
//#          else {
             //# setFocus( tabContainer );
//#          }
     //#endif
  tabContainer.background = null;
  tabContainer.border = null;
  if (isShown()) {
   tabContainer.showNotify();
   repaint();
  }
  notifyTabbedChangeCompleted(oldTabIndex, this.activeTabIndex);
 }
 
 /**
  * Sets the image for the specified tab.
  *
  * @param tabIndex the index of the tab
  * @param image the image
  */
 public void setTabImage( int tabIndex, Image image ) {
  this.tabBar.setImage( tabIndex, image );
 }
 
 /**
  * Sets the text for the specified tab.
  *
  * @param tabIndex the index of the tab
  * @param text the text
  */
 public void setText(int tabIndex, String text ) {
  this.tabBar.setText( tabIndex, text ); 
 }

 //#ifdef polish.useDynamicStyles 
//#  /* (non-Javadoc)
//#   * @see de.enough.polish.ui.Screen#createCssSelector()
//#   */
//#  protected String createCssSelector() {
//#   return "tabbedform";
//#  }
 //#endif
 
 
 
 protected boolean handleKeyPressed(int keyCode, int gameAction) {
  Item focusedItem = this.container.focusedItem;
//  if (focusedItem instanceof Container) {
//   focusedItem = ((Container) focusedItem).focusedItem;
//  }
  if (focusedItem != null && focusedItem.handleKeyPressed(keyCode, gameAction)) {
   if (focusedItem.internalX != -9999) {
    this.container.scroll( gameAction, focusedItem );
   }
   return true;
  } else if ( this.tabBar.handleKeyPressed(keyCode, gameAction)) {
   setActiveTab( this.tabBar.getNextTab() );
   notifyScreenStateChanged();
   return true;
  }
  return super.handleKeyPressed(keyCode, gameAction);
 }
 
 
 public void focus(Item item) {
  for (int i = 0; i < this.tabContainers.size(); i++) {
   Container tabContainer = (Container)this.tabContainers.get(i);
   if ( tabContainer.itemsList.contains(item)) {
    if (i != this.activeTabIndex) {
     setActiveTab( i );
    }
    super.focus(item);
    return;
   }
  }
 }
 
 
 /**
  * Retrieves the index of the currently active tab.
  *
  * @return the index of the currently active tab, 0 is the first tab.
  */
 public int getSelectedTab() {
  return this.activeTabIndex;
 }
 
 /**
  * Sets the screen listener for this TabbedForm.
  *
  * @param listener the listener that is notified whenever the user selects another tab,
  */
 public void setScreenStateListener( ScreenStateListener listener ) {
  this.screenStateListener = listener;
 }
 /**
  * @param oldTabIndex
  * @param newTabIndex
  * @return
  */
 public boolean notifyTabbedChangeRequested(int oldTabIndex, int newTabIndex) {
  if (this.tabbedFormListener != null) {
   return this.tabbedFormListener.notifyTabChangeRequested(oldTabIndex, newTabIndex);
  }
  return true;
 }
 /**
  * @param oldTabIndex
  * @param newTabIndex
  */
 public void notifyTabbedChangeCompleted(int oldTabIndex, int newTabIndex) {
  if (this.tabbedFormListener != null) {
   this.tabbedFormListener.notifyTabChangeCompleted(oldTabIndex, newTabIndex);
  }
 }
 /**
  * @param listener the listener that is notified whenever the user selects another tab,
  */
 public void setTabbedFormListener( TabbedFormListener listener ) {
  this.tabbedFormListener = listener;
 }
}
 
 
TabbedForm.java:
 
 
</code>
但本人以此重编译polish时不成功。后来,请教了同事才知道,只要对J2ME-Polish\lib\enough-j2mepolish-build.jar中的TabBar.java和TabbedForm.java类修改,增加相应的方法就行了,并不需要重编译polish, 因为运行polish项目时是主要是调用enough-j2mepolish-build.jar进行编译的,只要把类改过来后项目运行时会自动对它编译的。。
 
感谢访问!