Muscle's profileMusclePhotosBlogListsMore Tools Help

Blog


    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进行编译的,只要把类改过来后项目运行时会自动对它编译的。。