/* * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores * CA 94065 USA or visit www.oracle.com if you need additional information or * have any questions. */ package com.sun.lwuit.util; import com.sun.lwuit.Button; import com.sun.lwuit.CheckBox; import com.sun.lwuit.ComboBox; import com.sun.lwuit.Command; import com.sun.lwuit.Component; import com.sun.lwuit.Container; import com.sun.lwuit.Dialog; import com.sun.lwuit.Display; import com.sun.lwuit.Form; import com.sun.lwuit.Image; import com.sun.lwuit.Label; import com.sun.lwuit.List; import com.sun.lwuit.RadioButton; import com.sun.lwuit.Slider; import com.sun.lwuit.Tabs; import com.sun.lwuit.TextArea; import com.sun.lwuit.TextField; import com.sun.lwuit.VideoComponent; import com.sun.lwuit.animations.CommonTransitions; import com.sun.lwuit.animations.Transition; import com.sun.lwuit.events.ActionEvent; import com.sun.lwuit.events.ActionListener; import com.sun.lwuit.events.DataChangedListener; import com.sun.lwuit.events.FocusListener; import com.sun.lwuit.events.SelectionListener; import com.sun.lwuit.layouts.BorderLayout; import com.sun.lwuit.layouts.BoxLayout; import com.sun.lwuit.layouts.FlowLayout; import com.sun.lwuit.layouts.GridLayout; import com.sun.lwuit.layouts.LayeredLayout; import com.sun.lwuit.layouts.Layout; import com.sun.lwuit.list.CellRenderer; import com.sun.lwuit.list.DefaultListModel; import com.sun.lwuit.list.GenericListCellRenderer; import com.sun.lwuit.plaf.UIManager; import com.sun.lwuit.table.TableLayout; import java.io.DataInputStream; import java.io.IOException; import java.util.Calendar; import java.util.Hashtable; import java.util.Vector; /** * The UI builder can create a user interface based on the UI designed in the * resource editor and allows us to bind to said UI. Notice that if a Component * was used in the GUI that is not a part of the com.sun.lwuit package (even a * Component from sub packages such as table or tree) it MUST be registered * before loading a GUI! * * @author Shai Almog */ public class UIBuilder { /** * A key in the form state hashtable used in the back command navigation */ public static final String FORM_STATE_KEY_NAME = "$name"; /** * A key in the form state hashtable used in the back command navigation */ public static final String FORM_STATE_KEY_FOCUS = "$focus"; /** * A key in the form state hashtable used in the back command navigation */ public static final String FORM_STATE_KEY_SELECTION = "$sel"; /** * A key in the form state hashtable used in the back command navigation */ private static final String FORM_STATE_KEY_CONTAINER = "$cnt"; private static Hashtable componentRegistry; public static final int BACK_COMMAND_ID = 99999999; private static final String COMMAND_ACTION = "$COMMAND_ACTION$"; private static final String COMMAND_ARGUMENTS = "$COMMAND_ARGUMENTS$"; private static final String TYPE_KEY = "$TYPE_NAME$"; private static final String EMBEDDED_FORM_FLAG = "$EMBED$"; static final int PROPERTY_CUSTOM = 1000; static final int PROPERTY_TEXT = 1; static final int PROPERTY_ALIGNMENT = 2; static final int PROPERTY_LAYOUT = 3; static final int PROPERTY_LABEL_FOR = 4; static final int PROPERTY_PREFERRED_WIDTH = 5; static final int PROPERTY_PREFERRED_HEIGHT = 6; static final int PROPERTY_NEXT_FOCUS_UP = 7; static final int PROPERTY_NEXT_FOCUS_DOWN = 8; static final int PROPERTY_NEXT_FOCUS_LEFT = 9; static final int PROPERTY_NEXT_FOCUS_RIGHT = 10; static final int PROPERTY_UIID = 11; static final int PROPERTY_FOCUSABLE = 14; static final int PROPERTY_ENABLED = 15; static final int PROPERTY_SCROLL_VISIBLE = 16; static final int PROPERTY_ICON = 17; static final int PROPERTY_GAP = 18; static final int PROPERTY_VERTICAL_ALIGNMENT = 19; static final int PROPERTY_TEXT_POSITION = 20; static final int PROPERTY_NAME = 21; static final int PROPERTY_LAYOUT_CONSTRAINT = 22; static final int PROPERTY_TITLE = 23; static final int PROPERTY_COMPONENTS = 24; static final int PROPERTY_COLUMNS = 25; static final int PROPERTY_ROWS = 26; static final int PROPERTY_HINT = 27; static final int PROPERTY_ITEM_GAP = 28; static final int PROPERTY_CYCLIC_FOCUS = 32; static final int PROPERTY_SCROLLABLE_X = 33; static final int PROPERTY_SCROLLABLE_Y = 34; static final int PROPERTY_NEXT_FORM = 35; static final int PROPERTY_RADIO_GROUP = 36; static final int PROPERTY_SELECTED = 37; static final int PROPERTY_LIST_ITEMS_LEGACY = 29; static final int PROPERTY_LIST_ITEMS = 38; static final int PROPERTY_LIST_RENDERER = 39; static final int PROPERTY_BASE_FORM = 40; static final int PROPERTY_ROLLOVER_ICON = 41; static final int PROPERTY_PRESSED_ICON = 42; static final int PROPERTY_RTL = 43; static final int PROPERTY_INFINITE = 44; static final int PROPERTY_PROGRESS = 45; static final int PROPERTY_VERTICAL = 46; static final int PROPERTY_EDITABLE = 47; static final int PROPERTY_INCREMENTS = 48; static final int PROPERTY_RENDER_PERCENTAGE_ON_TOP = 49; static final int PROPERTY_MAX_VALUE = 50; static final int PROPERTY_MIN_VALUE = 51; static final int PROPERTY_DIALOG_UIID = 52; static final int PROPERTY_LIST_FIXED = 53; static final int PROPERTY_LIST_ORIENTATION = 54; static final int PROPERTY_LEAD_COMPONENT = 55; static final int PROPERTY_TAB_PLACEMENT = 56; static final int PROPERTY_TAB_TEXT_POSITION = 57; static final int PROPERTY_TOGGLE_BUTTON = 58; static final int PROPERTY_DISABLED_ICON = 60; static final int PROPERTY_EMBED = 61; static final int PROPERTY_HINT_ICON = 62; static final int PROPERTY_SLIDER_THUMB = 63; static final int PROPERTY_DIALOG_POSITION = 64; static final int PROPERTY_TEXT_AREA_GROW = 65; static final int PROPERTY_COMMANDS_LEGACY = 30; static final int PROPERTY_COMMAND_LEGACY = 31; static final int PROPERTY_COMMANDS = 67; static final int PROPERTY_COMMAND = 66; static final int PROPERTY_TEXT_CONSTRAINT = 68; static final int PROPERTY_TEXT_MAX_LENGTH = 69; static final int PROPERTY_TENSILE_DRAG_ENABLED = 70; static final int PROPERTY_TACTILE_TOUCH = 71; static final int PROPERTY_SNAP_TO_GRID = 72; static final int PROPERTY_FLATTEN = 73; static final int PROPERTY_DISPOSE_WHEN_POINTER_OUT = 74; static final int LAYOUT_BOX_X = 5002; static final int LAYOUT_BOX_Y = 5003; static final int LAYOUT_GRID = 5006; static final int LAYOUT_TABLE = 5007; static final int LAYOUT_BORDER_LEGACY = 5001; static final int LAYOUT_BORDER_ANOTHER_LEGACY = 5008; static final int LAYOUT_BORDER = 5010; static final int LAYOUT_FLOW_LEGACY = 5004; static final int LAYOUT_FLOW = 5009; static final int LAYOUT_LAYERED = 5011; private String resourceFilePath; private Resources resourceFile; private Hashtable localCommandListeners; private EventDispatcher globalCommandListeners; private Hashtable localComponentListeners; private boolean keepResourcesInRam = Display.getInstance().getProperty("cacheResFile", "false").equals("true"); // used by the resource editor static boolean ignorBaseForm; private Vector baseFormNavigationStack = new Vector(); private Vector backCommands; /** * When reaching the home form the navigation stack is cleared */ private String homeForm; private static Hashtable getComponentRegistry() { if(componentRegistry == null) { componentRegistry = new Hashtable(); componentRegistry.put("Button", Button.class); componentRegistry.put("Calendar", Calendar.class); componentRegistry.put("CheckBox", CheckBox.class); componentRegistry.put("ComboBox", ComboBox.class); componentRegistry.put("Container", Container.class); componentRegistry.put("Dialog", Dialog.class); componentRegistry.put("Form", Form.class); componentRegistry.put("Label", Label.class); componentRegistry.put("List", List.class); componentRegistry.put("RadioButton", RadioButton.class); componentRegistry.put("Slider", Slider.class); componentRegistry.put("Tabs", Tabs.class); componentRegistry.put("TextArea", TextArea.class); componentRegistry.put("TextField", TextField.class); componentRegistry.put("VideoComponent", VideoComponent.class); componentRegistry.put("EmbeddedContainer", EmbeddedContainer.class); } return componentRegistry; } /** * Seamlessly inserts a back command to all the forms * * @param back true to automatically add a back command */ public void setBackCommandEnabled(boolean back) { if(back) { if(baseFormNavigationStack == null) { baseFormNavigationStack = new Vector(); } } else { baseFormNavigationStack = null; } } private Vector getFormNavigationStackForComponent(Component c) { if(baseFormNavigationStack == null) { return null; } if(c == null) { return baseFormNavigationStack; } Component root = getRootAncestor(c); if(root.getParent() instanceof EmbeddedContainer) { Vector nav = (Vector)root.getParent().getClientProperty("$baseNav"); if(nav == null) { nav = new Vector(); root.getParent().putClientProperty("$baseNav", nav); } return nav; } return baseFormNavigationStack; } /** * Seamlessly inserts a back command to all the forms * * @return true if a back command is automatically added */ public boolean isBackCommandEnabled() { return baseFormNavigationStack != null; } /** * This method allows the UIBuilder to package a smaller portion of LWUIT into the JAR * and add support for additional 3rd party components to the GUI builder. Components * must be registered using their UIID name, by default all the content of com.sun.lwuit is * registered however subpackages and 3rd party components are not. * Registeration is essential for obfuscation to work properly! * * @param name the name of the component (UIID) * @param cmp the class for the given component */ public static void registerCustomComponent(String name, Class cmp) { getComponentRegistry().put(name, cmp); } /** * Creates the container defined under the given name in the res file * * @param resPath the path to the res file containing the UI widget * @param resourceName the name of the widget in the res file * @return a LWUIT container instance */ public Container createContainer(String resPath, String resourceName) { try { setResourceFilePath(resPath); return createContainer(Resources.open(resPath), resourceName); } catch (IOException ex) { ex.printStackTrace(); return null; } } /** * Creates the container defined under the given name in the res file * * @param res the res file containing the UI widget * @param resourceName the name of the widget in the res file * @return a LWUIT container instance */ public Container createContainer(Resources res, String resourceName) { return createContainer(res, resourceName, null); } private Container createContainer(Resources res, String resourceName, EmbeddedContainer parentContainer) { onCreateRoot(resourceName); DataInputStream in = new DataInputStream(res.getUi(resourceName)); try { Hashtable h = null; if(localComponentListeners != null) { h = (Hashtable)localComponentListeners.get(resourceName); } Container c = (Container)createComponent(in, null, null, res, h, parentContainer); c.setName(resourceName); postCreateComponents(in, c, res); // try to be smart about initializing the home form if(homeForm == null) { if(c instanceof Form) { String nextForm = (String)c.getClientProperty("%next_form%"); if(nextForm != null) { homeForm = nextForm; } else { homeForm = resourceName; } } } return c; } catch (Exception ex) { // If this happens its probably a serious bug ex.printStackTrace(); return null; } } private void readCommand(DataInputStream in, Component c, Container parent, Resources res, boolean legacy) throws IOException { String commandName = in.readUTF(); String commandImageName = in.readUTF(); String rollover = null; String pressed = null; String disabled = null; if(!legacy) { rollover = in.readUTF(); pressed = in.readUTF(); disabled = in.readUTF(); } int commandId = in.readInt(); String commandAction = in.readUTF(); boolean isBack = in.readBoolean(); String commandArgument = ""; if(commandAction.equals("$Execute")) { commandArgument = in.readUTF(); } Command cmd = createCommandImpl(commandName, res.getImage(commandImageName), commandId, commandAction, isBack, commandArgument); if(rollover != null && rollover.length() > 0) { cmd.setRolloverIcon(res.getImage(rollover)); } if(pressed != null && pressed.length() > 0) { cmd.setPressedIcon(res.getImage(pressed)); } if(disabled != null && disabled.length() > 0) { cmd.setPressedIcon(res.getImage(pressed)); } if(isBack) { Form f = c.getComponentForm(); if(f != null) { f.setBackCommand(cmd); } else { if(backCommands == null) { backCommands = new Vector(); } backCommands.addElement(cmd); } } ((Button)c).setCommand(cmd); // prevent duplicate action handling only in the case of a component form // the embeded component doesn't have a global command listener since it has // no menu if(c.getComponentForm() != null) { ((Button)c).removeActionListener(getFormListenerInstance(parent, null)); } cmd.putClientProperty(COMMAND_ARGUMENTS, commandArgument); cmd.putClientProperty(COMMAND_ACTION, commandAction); if(commandAction.length() > 0 && resourceFilePath == null || isKeepResourcesInRam()) { resourceFile = res; } } /** * Invoked after the components were created to allow properties that require the entire * tree to exist to update the component. This is useful for properties that point * at other components. */ private void postCreateComponents(DataInputStream in, Container parent, Resources res) throws Exception { // finds the component whose properties need to update String name = in.readUTF(); Component lastComponent = null; while(name.length() > 0) { if(lastComponent == null || !lastComponent.getName().equals(name)) { lastComponent = findByName(name, parent); } Component c = lastComponent; int property = in.readInt(); modifyingProperty(c, property); switch(property) { case PROPERTY_COMMAND_LEGACY: { readCommand(in, c, parent, res, true); break; } case PROPERTY_COMMAND: { readCommand(in, c, parent, res, false); break; } case PROPERTY_LABEL_FOR: c.setLabelForComponent((Label)findByName(in.readUTF(), parent)); break; case PROPERTY_LEAD_COMPONENT: ((Container)c).setLeadComponent(findByName(in.readUTF(), parent)); break; case PROPERTY_NEXT_FOCUS_UP: c.setNextFocusUp(findByName(in.readUTF(), parent)); break; case PROPERTY_NEXT_FOCUS_DOWN: c.setNextFocusDown(findByName(in.readUTF(), parent)); break; case PROPERTY_NEXT_FOCUS_LEFT: c.setNextFocusLeft(findByName(in.readUTF(), parent)); break; case PROPERTY_NEXT_FOCUS_RIGHT: c.setNextFocusRight(findByName(in.readUTF(), parent)); break; } name = in.readUTF(); } } /** * Finds the given component by its name * * @param name the name of the component as defined in the resource editor * @param rootComponent the root container * @return the component matching the given name or null if its not found */ public Component findByName(String name, Container rootComponent) { Component c = (Component)rootComponent.getClientProperty("%" + name + "%"); if(c == null) { Container newRoot = getRootAncestor(rootComponent); if(newRoot != null && rootComponent != newRoot) { return findByName(name, newRoot); } } return c; } /** * This method can be overriden to create custom components in a custom way, the component * type is a shorthand for the component name and not the full name of the class. * By default this method returns null which indicates LWUIT should try to reolve the component * on its own. * * @param componentType the type of the component from the UI builder * @param cls assumed component class based on the component registry * @return a new component instance or null */ protected Component createComponentInstance(String componentType, Class cls) { return null; } /** * Callback to allow binding custom logic/listeners to a component after its major properties were set * (notice that not all properties or the full hierarchy will be available at this stage). This * is the perfect place to bind models/renderers etc. to components. * * @param cmp the component */ protected void postCreateComponent(Component cmp) { } /** * Binds the given listener object to the component, this works seamlessly for * common LWUIT events but might be an issue with custom components and custom * listener types so this method can be overloaded to add support for such cases. * * @param cmp the component to bind the listener to * @param listener the listener object */ protected void bindListenerToComponent(Component cmp, Object listener) { if(listener instanceof FocusListener) { cmp.addFocusListener((FocusListener)listener); return; } if(listener instanceof ActionListener) { if(cmp instanceof Button) { ((Button)cmp).addActionListener((ActionListener)listener); return; } if(cmp instanceof List) { ((List)cmp).addActionListener((ActionListener)listener); return; } ((TextArea)cmp).addActionListener((ActionListener)listener); return; } if(listener instanceof DataChangedListener) { if(cmp instanceof TextField) { ((TextField)cmp).addDataChangeListener((DataChangedListener)listener); return; } ((Slider)cmp).addDataChangedListener((DataChangedListener)listener); return; } if(listener instanceof SelectionListener) { if(cmp instanceof List) { ((List)cmp).addSelectionListener((SelectionListener)listener); return; } ((Slider)cmp).addDataChangedListener((DataChangedListener)listener); return; } } void initBaseForm(String formName) { } private Container findEmptyContainer(Container c) { int count = c.getComponentCount(); if(count == 0) { return c; } for(int iter = 0 ; iter < count ; iter++) { Component x = c.getComponentAt(iter); if(x instanceof Container) { Container current = findEmptyContainer((Container)x); if(current != null) { return current; } } } return null; } /** * Allows a subclass to set the list model for the given component * * @param cmp the list whose model may be set * @return true if a model was set by this method */ protected boolean setListModel(List cmp) { return false; } private void readCommands(DataInputStream in, Component cmp, Resources res, boolean legacy) throws IOException { int commandCount = in.readInt(); final String[] commandActions = new String[commandCount]; final Command[] commands = new Command[commandCount]; final String[] commandArguments = new String[commandCount]; boolean hasAction = false; for(int iter = 0 ; iter < commandCount ; iter++) { String commandName = in.readUTF(); String commandImageName = in.readUTF(); String rollover = null; String pressed = null; String disabled = null; if(!legacy) { rollover = in.readUTF(); pressed = in.readUTF(); disabled = in.readUTF(); } int commandId = in.readInt(); commandActions[iter] = in.readUTF(); if(commandActions[iter].length() > 0) { hasAction = true; } boolean isBack = in.readBoolean(); commandArguments[iter] = ""; if(commandActions[iter].equals("$Execute")) { commandArguments[iter] = in.readUTF(); } commands[iter] = createCommandImpl(commandName, res.getImage(commandImageName), commandId, commandActions[iter], isBack, commandArguments[iter]); if(rollover != null && rollover.length() > 0) { commands[iter].setRolloverIcon(res.getImage(rollover)); } if(pressed != null && pressed.length() > 0) { commands[iter].setPressedIcon(res.getImage(pressed)); } if(disabled != null && disabled.length() > 0) { commands[iter].setPressedIcon(res.getImage(pressed)); } if(isBack) { ((Form)cmp).setBackCommand(commands[iter]); } // trigger listener creation if this is the only command in the form getFormListenerInstance(((Form)cmp), null); ((Form)cmp).addCommand(commands[iter]); } if(hasAction) { for(int iter = 0 ; iter < commands.length ; iter++) { commands[iter].putClientProperty(COMMAND_ARGUMENTS, commandArguments[iter]); commands[iter].putClientProperty(COMMAND_ACTION, commandActions[iter]); } if(resourceFilePath == null || isKeepResourcesInRam()) { resourceFile = res; } } } private Object[] readObjectArrayForListModel(DataInputStream in, Resources res) throws IOException { Object[] elements = new Object[in.readInt()]; for(int iter = 0 ; iter < elements.length ; iter++) { switch(in.readByte()) { case 1: // String elements[iter] = in.readUTF(); break; case 2: // hashtable of Strings int hashSize = in.readInt(); Hashtable val = new Hashtable(); elements[iter] = val; for(int i = 0 ; i < hashSize ; i++) { int type = in.readInt(); if(type == 1) { // String String key = in.readUTF(); Object value = in.readUTF(); if(key.equals("$navigation")) { value = createCommandImpl((String)value, null, -1, "" + value, false, ""); } val.put(key, value); } else { val.put(in.readUTF(), res.getImage(in.readUTF())); } } break; } } return elements; } private GenericListCellRenderer readRendererer(Resources res, DataInputStream in) throws IOException { int rendererComponentCount = in.readByte(); String f = in.readUTF(); String s = in.readUTF(); if(rendererComponentCount == 2) { Component selected = createContainer(res, f); Component unselected = createContainer(res, s); GenericListCellRenderer g = new GenericListCellRenderer(selected, unselected); g.setFisheye(!f.equals(s)); return g; } else { Component selected = createContainer(res, f); Component unselected = createContainer(res, s); Component even = createContainer(res, in.readUTF()); Component evenU = createContainer(res, in.readUTF()); GenericListCellRenderer g = new GenericListCellRenderer(selected, unselected, even, evenU); g.setFisheye(!f.equals(s)); return g; } } private Object readCustomPropertyValue(DataInputStream in, Class type, Resources res) throws IOException { if(type == String.class) { return in.readUTF(); } if(type == String[].class) { String[] result = new String[in.readInt()]; for(int i = 0 ; i < result.length ; i++) { result[i] = in.readUTF(); } return result; } if(type == String[][].class) { String[][] result = new String[in.readInt()][]; for(int i = 0 ; i < result.length ; i++) { result[i] = new String[in.readInt()]; for(int j = 0 ; j < result[i].length ; j++) { result[i][j] = in.readUTF(); } } return result; } if(type == Integer.class) { return new Integer(in.readInt()); } if(type == Long.class) { return new Long(in.readLong()); } if(type == Double.class) { return new Double(in.readDouble()); } if(type == Float.class) { return new Float(in.readFloat()); } if(type == Byte.class) { return new Byte(in.readByte()); } if(type == Boolean.class) { return new Boolean(in.readBoolean()); } if(type == Image[].class) { Image[] result = new Image[in.readInt()]; for(int i = 0 ; i < result.length ; i++) { result[i] = res.getImage(in.readUTF()); } return result; } if(type == Container.class) { // resource might have been removed we need to fail gracefully String[] uiNames = res.getUIResourceNames(); String currentName = in.readUTF(); for(int iter = 0 ; iter < uiNames.length ; iter++) { if(uiNames[iter].equals(currentName)) { return createContainer(res, currentName); } } return null; } if(type == CellRenderer.class) { return readRendererer(res, in); } if(type == Object[].class) { return readObjectArrayForListModel(in, res); } return new Character(in.readChar()); } private Component createComponent(DataInputStream in, Container parent, Container root, Resources res, Hashtable componentListeners, EmbeddedContainer embedded) throws Exception { String name = in.readUTF(); int property = in.readInt(); // special case for the base form if(property == PROPERTY_BASE_FORM) { String baseFormName = name; initBaseForm(baseFormName); if(!ignorBaseForm) { Form base = (Form)createContainer(res, baseFormName); Container destination = (Container)findByName("destination", base); // try finding an appropriate empty container if no "fixed" destination is defined if(destination == null) { destination = findEmptyContainer(base.getContentPane()); if(destination == null) { System.out.println("Couldn't find appropriate 'destination' container in base form: " + baseFormName); return null; } } root = base; Component cmp = createComponent(in, destination, root, res, componentListeners, embedded); if(destination.getLayout() instanceof BorderLayout) { destination.addComponent(BorderLayout.CENTER, cmp); } else { destination.addComponent(cmp); } return root; } else { name = in.readUTF(); property = in.readInt(); } } Class c = (Class)getComponentRegistry().get(name); Component cmp = createComponentInstance(name, c); if(cmp == null) { if(c == null) { throw new RuntimeException("Component not found use UIBuilder.registerCustomComponent(" + name + ", class);"); } cmp = (Component)c.newInstance(); } if(componentListeners != null) { Object listeners = componentListeners.get(name); if(listeners != null) { if(listeners instanceof Vector) { Vector v = (Vector)listeners; for(int iter = 0 ; iter < v.size() ; iter++) { bindListenerToComponent(cmp, v.elementAt(iter)); } } else { bindListenerToComponent(cmp, listeners); } } } if(cmp instanceof Button) { ActionListener l = getFormListenerInstance(root, embedded); if(l != null) { ((Button)cmp).addActionListener(l); } } else { if(cmp instanceof TextArea) { ActionListener l = getFormListenerInstance(root, embedded); if(l != null) { ((TextArea)cmp).addActionListener(l); } } else { if(cmp instanceof List) { ActionListener l = getFormListenerInstance(root, embedded); if(l != null) { ((List)cmp).addActionListener(l); } } } } cmp.putClientProperty(TYPE_KEY, name); if(root == null) { root = (Container)cmp; } while(property != -1) { modifyingProperty(cmp, property); switch(property) { case PROPERTY_CUSTOM: String customPropertyName = in.readUTF(); modifyingCustomProperty(cmp, customPropertyName); boolean isNull = in.readBoolean(); if(isNull) { cmp.setPropertyValue(customPropertyName, null); break; } String[] propertyNames = cmp.getPropertyNames(); for(int iter = 0 ; iter < propertyNames.length ; iter++) { if(propertyNames[iter].equals(customPropertyName)) { Class type = cmp.getPropertyTypes()[iter]; Object value = readCustomPropertyValue(in, type, res); cmp.setPropertyValue(customPropertyName, value); break; } } break; case PROPERTY_EMBED: root.putClientProperty(EMBEDDED_FORM_FLAG, ""); ((EmbeddedContainer)cmp).setEmbed(in.readUTF()); Container embed = createContainer(res, ((EmbeddedContainer)cmp).getEmbed(), (EmbeddedContainer)cmp); if(embed != null) { if(embed instanceof Form) { embed = formToContainer((Form)embed); } ((EmbeddedContainer)cmp).addComponent(BorderLayout.CENTER, embed); // this isn't exactly the "right thing" but its the best we can do to make all // use cases work beforeShowContainer(embed); postShowContainer(embed); } break; case PROPERTY_TOGGLE_BUTTON: ((Button)cmp).setToggle(in.readBoolean()); break; case PROPERTY_RADIO_GROUP: ((RadioButton)cmp).setGroup(in.readUTF()); break; case PROPERTY_SELECTED: boolean isSelected = in.readBoolean(); if(cmp instanceof RadioButton) { ((RadioButton)cmp).setSelected(isSelected); } else { ((CheckBox)cmp).setSelected(isSelected); } break; case PROPERTY_SCROLLABLE_X: ((Container)cmp).setScrollableX(in.readBoolean()); break; case PROPERTY_SCROLLABLE_Y: ((Container)cmp).setScrollableY(in.readBoolean()); break; case PROPERTY_TENSILE_DRAG_ENABLED: cmp.setTensileDragEnabled(in.readBoolean()); break; case PROPERTY_TACTILE_TOUCH: cmp.setTactileTouch(in.readBoolean()); break; case PROPERTY_SNAP_TO_GRID: cmp.setSnapToGrid(in.readBoolean()); break; case PROPERTY_FLATTEN: cmp.setFlatten(in.readBoolean()); break; case PROPERTY_TEXT: if(cmp instanceof Label) { ((Label)cmp).setText(in.readUTF()); } else { ((TextArea)cmp).setText(in.readUTF()); } break; case PROPERTY_TEXT_MAX_LENGTH: ((TextArea)cmp).setMaxSize(in.readInt()); break; case PROPERTY_TEXT_CONSTRAINT: ((TextArea)cmp).setConstraint(in.readInt()); if(cmp instanceof TextField) { int cons = ((TextArea)cmp).getConstraint(); if((cons & TextArea.NUMERIC) == TextArea.NUMERIC) { ((TextField)cmp).setDefaultInputModeOrder(new String[]{"123"}); } } break; case PROPERTY_ALIGNMENT: if(cmp instanceof Label) { ((Label)cmp).setAlignment(in.readInt()); } else { ((TextArea)cmp).setAlignment(in.readInt()); } break; case PROPERTY_TEXT_AREA_GROW: ((TextArea)cmp).setGrowByContent(in.readBoolean()); break; case PROPERTY_LAYOUT: Layout layout = null; switch(in.readShort()) { case LAYOUT_BORDER_LEGACY: layout = new BorderLayout(); break; case LAYOUT_BORDER_ANOTHER_LEGACY: { BorderLayout b = new BorderLayout(); if(in.readBoolean()) { b.defineLandscapeSwap(BorderLayout.NORTH, in.readUTF()); } if(in.readBoolean()) { b.defineLandscapeSwap(BorderLayout.EAST, in.readUTF()); } if(in.readBoolean()) { b.defineLandscapeSwap(BorderLayout.WEST, in.readUTF()); } if(in.readBoolean()) { b.defineLandscapeSwap(BorderLayout.SOUTH, in.readUTF()); } if(in.readBoolean()) { b.defineLandscapeSwap(BorderLayout.CENTER, in.readUTF()); } layout = b; break; } case LAYOUT_BORDER: { BorderLayout b = new BorderLayout(); if(in.readBoolean()) { b.defineLandscapeSwap(BorderLayout.NORTH, in.readUTF()); } if(in.readBoolean()) { b.defineLandscapeSwap(BorderLayout.EAST, in.readUTF()); } if(in.readBoolean()) { b.defineLandscapeSwap(BorderLayout.WEST, in.readUTF()); } if(in.readBoolean()) { b.defineLandscapeSwap(BorderLayout.SOUTH, in.readUTF()); } if(in.readBoolean()) { b.defineLandscapeSwap(BorderLayout.CENTER, in.readUTF()); } b.setAbsoluteCenter(in.readBoolean()); layout = b; break; } case LAYOUT_BOX_X: layout = new BoxLayout(BoxLayout.X_AXIS); break; case LAYOUT_BOX_Y: layout = new BoxLayout(BoxLayout.Y_AXIS); break; case LAYOUT_FLOW_LEGACY: layout = new FlowLayout(); break; case LAYOUT_FLOW: FlowLayout f = new FlowLayout(); f.setFillRows(in.readBoolean()); f.setAlign(in.readInt()); f.setValign(in.readInt()); layout = f; break; case LAYOUT_LAYERED: layout = new LayeredLayout(); break; case LAYOUT_GRID: layout = new GridLayout(in.readInt(), in.readInt()); break; case LAYOUT_TABLE: layout = new TableLayout(in.readInt(), in.readInt()); break; } ((Container)cmp).setLayout(layout); break; case PROPERTY_TAB_PLACEMENT: ((Tabs)cmp).setTabPlacement(in.readInt()); break; case PROPERTY_TAB_TEXT_POSITION: ((Tabs)cmp).setTabTextPosition(in.readInt()); break; case PROPERTY_PREFERRED_WIDTH: cmp.setPreferredW(in.readInt()); break; case PROPERTY_PREFERRED_HEIGHT: cmp.setPreferredH(in.readInt()); break; case PROPERTY_UIID: cmp.setUIID(in.readUTF()); break; case PROPERTY_DIALOG_UIID: ((Dialog)cmp).setDialogUIID(in.readUTF()); break; case PROPERTY_DISPOSE_WHEN_POINTER_OUT: ((Dialog)cmp).setDisposeWhenPointerOutOfBounds(in.readBoolean()); break; case PROPERTY_DIALOG_POSITION: String pos = in.readUTF(); if(pos.length() > 0) { ((Dialog)cmp).setDialogPosition(pos); } break; case PROPERTY_FOCUSABLE: cmp.setFocusable(in.readBoolean()); break; case PROPERTY_ENABLED: cmp.setEnabled(in.readBoolean()); break; case PROPERTY_SCROLL_VISIBLE: cmp.setScrollVisible(in.readBoolean()); break; case PROPERTY_ICON: ((Label)cmp).setIcon(res.getImage(in.readUTF())); break; case PROPERTY_ROLLOVER_ICON: ((Button)cmp).setRolloverIcon(res.getImage(in.readUTF())); break; case PROPERTY_PRESSED_ICON: ((Button)cmp).setPressedIcon(res.getImage(in.readUTF())); break; case PROPERTY_DISABLED_ICON: ((Button)cmp).setDisabledIcon(res.getImage(in.readUTF())); break; case PROPERTY_GAP: ((Label)cmp).setGap(in.readInt()); break; case PROPERTY_VERTICAL_ALIGNMENT: ((Label)cmp).setVerticalAlignment(in.readInt()); break; case PROPERTY_TEXT_POSITION: ((Label)cmp).setTextPosition(in.readInt()); break; case PROPERTY_NAME: String componentName = in.readUTF(); cmp.setName(componentName); root.putClientProperty("%" + componentName + "%", cmp); break; case PROPERTY_LAYOUT_CONSTRAINT: if(parent.getLayout() instanceof BorderLayout) { cmp.putClientProperty("layoutConstraint", in.readUTF()); } else { TableLayout tl = (TableLayout)parent.getLayout(); TableLayout.Constraint con = tl.createConstraint(in.readInt(), in.readInt()); con.setHeightPercentage(in.readInt()); con.setWidthPercentage(in.readInt()); con.setHorizontalAlign(in.readInt()); con.setHorizontalSpan(in.readInt()); con.setVerticalAlign(in.readInt()); con.setVerticalSpan(in.readInt()); cmp.putClientProperty("layoutConstraint", con); } break; case PROPERTY_TITLE: ((Form)cmp).setTitle(in.readUTF()); break; case PROPERTY_COMPONENTS: int componentCount = in.readInt(); if(cmp instanceof Tabs) { for(int iter = 0 ; iter < componentCount ; iter++) { String tab = in.readUTF(); Component child = createComponent(in, (Container)cmp, root, res, componentListeners, embedded); ((Tabs)cmp).addTab(tab, child); } } else { for(int iter = 0 ; iter < componentCount ; iter++) { Component child = createComponent(in, (Container)cmp, root, res, componentListeners, embedded); Object con = child.getClientProperty("layoutConstraint"); if(con != null) { ((Container)cmp).addComponent(con, child); } else { ((Container)cmp).addComponent(child); } } } break; case PROPERTY_COLUMNS: ((TextArea)cmp).setColumns(in.readInt()); break; case PROPERTY_ROWS: ((TextArea)cmp).setRows(in.readInt()); break; case PROPERTY_HINT: if(cmp instanceof List) { ((List)cmp).setHint(in.readUTF()); } else { ((TextArea)cmp).setHint(in.readUTF()); } break; case PROPERTY_HINT_ICON: if(cmp instanceof List) { ((List)cmp).setHintIcon(res.getImage(in.readUTF())); } else { ((TextArea)cmp).setHintIcon(res.getImage(in.readUTF())); } break; case PROPERTY_ITEM_GAP: ((List)cmp).setItemGap(in.readInt()); break; case PROPERTY_LIST_FIXED: ((List)cmp).setFixedSelection(in.readInt()); break; case PROPERTY_LIST_ORIENTATION: ((List)cmp).setOrientation(in.readInt()); break; case PROPERTY_LIST_ITEMS_LEGACY: String[] items = new String[in.readInt()]; for(int iter = 0 ; iter < items.length ; iter++) { items[iter] = in.readUTF(); } if(!setListModel(((List)cmp))) { ((List)cmp).setModel(new DefaultListModel(items)); } break; case PROPERTY_LIST_ITEMS: Object[] elements = readObjectArrayForListModel(in, res); if(!setListModel(((List)cmp))) { ((List)cmp).setModel(new DefaultListModel(elements)); } break; case PROPERTY_LIST_RENDERER: ((List)cmp).setRenderer(readRendererer(res, in)); break; case PROPERTY_NEXT_FORM: String nextForm = in.readUTF(); cmp.putClientProperty("%next_form%", nextForm); if(resourceFilePath == null || isKeepResourcesInRam()) { resourceFile = res; } ((Form)root).addShowListener(new FormListener((Form)root, nextForm)); break; case PROPERTY_COMMANDS: readCommands(in, cmp, res, false); break; case PROPERTY_COMMANDS_LEGACY: readCommands(in, cmp, res, true); break; case PROPERTY_CYCLIC_FOCUS: ((Form)cmp).setCyclicFocus(in.readBoolean()); break; case PROPERTY_RTL: cmp.setRTL(in.readBoolean()); break; case PROPERTY_SLIDER_THUMB: ((Slider)cmp).setThumbImage(res.getImage(in.readUTF())); break; case PROPERTY_INFINITE: ((Slider)cmp).setInfinite(in.readBoolean()); break; case PROPERTY_PROGRESS: ((Slider)cmp).setProgress(in.readInt()); break; case PROPERTY_VERTICAL: ((Slider)cmp).setVertical(in.readBoolean()); break; case PROPERTY_EDITABLE: if(cmp instanceof TextArea) { ((TextArea)cmp).setEditable(in.readBoolean()); } else { ((Slider)cmp).setEditable(in.readBoolean()); } break; case PROPERTY_INCREMENTS: ((Slider)cmp).setIncrements(in.readInt()); break; case PROPERTY_RENDER_PERCENTAGE_ON_TOP: ((Slider)cmp).setRenderPercentageOnTop(in.readBoolean()); break; case PROPERTY_MAX_VALUE: ((Slider)cmp).setMaxValue(in.readInt()); break; case PROPERTY_MIN_VALUE: ((Slider)cmp).setMinValue(in.readInt()); break; } property = in.readInt(); } postCreateComponent(cmp); return cmp; } // for internal use in the resource editor void modifyingProperty(Component c, int p) { } // for internal use in the resource editor void modifyingCustomProperty(Component c, String name) { } /** * Creates a command instance. This method is invoked by the loading code and * can be overriden to create a subclass of the Command class. * * @param commandName the label on the command * @param icon the icon for the command * @param commandId the id of the command * @param action the action assigned to the command if such an action is defined * @return a new command instance */ protected Command createCommand(String commandName, Image icon, int commandId, String action) { return new Command(commandName, icon, commandId); } Command createCommandImpl(String commandName, Image icon, int commandId, String action, boolean isBack, String argument) { return createCommand(commandName, icon, commandId, action); } /** * This method may be overriden by subclasses to provide a way to dynamically load * a resource file. Normally the navigation feature of the UIBuilder requires the resource * file present in RAM. However, that might be expensive to maintain. * By implementing this method and replacing the storeResourceFile() with an empty * implementation the resource file storage can be done strictly in RAM. * * @return the instance of the resource file */ protected Resources fetchResourceFile() { try { if (resourceFile != null) { return resourceFile; } return Resources.open(getResourceFilePath()); } catch (IOException ex) { ex.printStackTrace(); return null; } } /** * Allows the navigation code to avoid storing the resource file and lets the GC * remove it from memory when its not in use * * @return the resourceFilePath */ public String getResourceFilePath() { return resourceFilePath; } /** * Allows the navigation code to avoid storing the resource file and lets the GC * remove it from memory when its not in use * * @param resourceFilePath the resourceFilePath to set */ public void setResourceFilePath(String resourceFilePath) { this.resourceFilePath = resourceFilePath; if(resourceFilePath != null) { resourceFile = null; } } /** * Sets the resource file if keep in rum or no path is defined * * @param res the resource file */ protected void setResourceFile(Resources res) { if(keepResourcesInRam || resourceFilePath == null) { this.resourceFile = res; } } /** * Invoked to process a given command before naviation or any other internal * processing occurs. The event can be consumed to prevent further processing. * * @param ev the action event source of the command * @param cmd the command to process */ protected void processCommand(ActionEvent ev, Command cmd) { } private void processCommandImpl(ActionEvent ev, Command cmd) { processCommand(ev, cmd); if(ev.isConsumed()) { return; } if(globalCommandListeners != null) { globalCommandListeners.fireActionEvent(ev); if(ev.isConsumed()) { return; } } if(localCommandListeners != null) { Form f = Display.getInstance().getCurrent(); EventDispatcher e = (EventDispatcher)localCommandListeners.get(f.getName()); if(e != null) { e.fireActionEvent(ev); } } } /** * Adds a command listener that would be bound to all forms in the GUI seamlessly * * @param l the listener to bind */ public void addCommandListener(ActionListener l) { if(globalCommandListeners == null) { globalCommandListeners = new EventDispatcher(); } globalCommandListeners.addListener(l); } /** * Removes a command listener * * @param l the listener to remove */ public void removeCommandListener(ActionListener l) { if(globalCommandListeners == null) { return; } globalCommandListeners.removeListener(l); } /** * Adds a component listener that would be bound when a UI for this form is created. * Notice that this method is only effective before the form was created and would do * nothing for an existing form * * @param formName the name of the form to which the listener should be bound * @param componentName the name of the component to bind to * @param listener the listener to bind, common listener types are supported */ public void addComponentListener(String formName, String componentName, Object listener) { if(localComponentListeners == null) { localComponentListeners = new Hashtable(); Hashtable formListeners = new Hashtable(); formListeners.put(componentName, listener); localComponentListeners.put(formName, formListeners); return; } Hashtable formListeners = (Hashtable)localComponentListeners.get(formName); if(formListeners == null) { formListeners = new Hashtable(); formListeners.put(componentName, listener); localComponentListeners.put(formName, formListeners); return; } Object currentListeners = formListeners.get(componentName); if(currentListeners == null) { formListeners.put(componentName, listener); } else { if(currentListeners instanceof Vector) { ((Vector)currentListeners).addElement(listener); } else { Vector v = new Vector(); v.addElement(currentListeners); v.addElement(listener); formListeners.put(componentName, v); } } } /** * Removes a component listener bound to a specific component * * @param formName the name of the form * @param componentName the name of the component * @param listener the listener instance */ public void removeComponentListener(String formName, String componentName, Object listener) { if(localComponentListeners == null) { return; } Hashtable formListeners = (Hashtable)localComponentListeners.get(formName); if(formListeners == null) { return; } Object currentListeners = formListeners.get(componentName); if(currentListeners == null) { return; } else { if(currentListeners instanceof Vector) { ((Vector)currentListeners).removeElement(listener); if(((Vector)currentListeners).size() == 0) { formListeners.remove(componentName); } } else { formListeners.remove(componentName); } } } /** * Adds a command listener to be invoked for commands on a specific form * * @param formName the name of the form to which the listener should be bound * @param l the listener to bind */ public void addCommandListener(String formName, ActionListener l) { if(localCommandListeners == null) { localCommandListeners = new Hashtable(); } EventDispatcher d = (EventDispatcher)localCommandListeners.get(formName); if(d == null) { d = new EventDispatcher(); localCommandListeners.put(formName, d); } d.addListener(l); } /** * Removes a command listener on a specific form * * @param formName the name of the form * @param l the listener to remove */ public void removeCommandListener(String formName, ActionListener l) { if(localCommandListeners == null) { return; } EventDispatcher d = (EventDispatcher)localCommandListeners.get(formName); if(d == null) { return; } d.removeListener(l); } /** * This method is invoked for every component to which an action event listener can be bound * and delivers the event data for the given component seamlessly. * * @param c the component broadcasting the event * @param event the event meta data */ protected void handleComponentAction(Component c, ActionEvent event) { } private FormListener getFormListenerInstance(Component cmp, EmbeddedContainer embedded) { if(embedded != null) { FormListener fc = (FormListener)embedded.getClientProperty("!FormListener!"); if(fc != null) { return fc; } fc = new FormListener(); embedded.putClientProperty("!FormListener!", fc); return fc; } Form f = cmp.getComponentForm(); if(f == null) { return null; } FormListener fc = (FormListener)f.getClientProperty("!FormListener!"); if(fc != null) { return fc; } fc = new FormListener(); f.putClientProperty("!FormListener!", fc); f.addCommandListener(fc); return fc; } /** * Warning: This method is invoked OFF the EDT and is intended for usage with asynchronous * command processing. This method is invoked when the UI indicates that an operation * should occur in the background. To finish the processing of the operation within the * EDT one should overide the postAsyncCommand() method. * * @param cmd the command requiring background processing * @param sourceEvent the triggering event */ protected void asyncCommandProcess(Command cmd, ActionEvent sourceEvent) { } /** * This method is invoked in conjunction with asyncCommandProcess after the * command was handled asynchroniously on the separate thread. Here LWUIT * code can be execute to update the UI with the results from the separate thread. * * @param cmd the command * @param sourceEvent the source event */ protected void postAsyncCommand(Command cmd, ActionEvent sourceEvent) { } /** * <b>Warning:</b> this method is invoked on a separate thread. * This method is invoked when a next form property is defined, this property * indicates a background process for a form of a transitional nature should * take place (e.g. splash screen, IO etc.) after which the next form should be shown. * After this method completes the next form is shown. * * @param f the form for which the background thread was constructed, notice * that most methods are not threadsafe and one should use callSerially* in this * method when mutating the form. * @return if false is returned from this method navigation should not proceed * to that given form */ protected boolean processBackground(Form f) { try { Thread.sleep(3000); } catch (InterruptedException ex) { ex.printStackTrace(); } return true; } /** * Returns the state of the current form which we are about to leave as part * of the navigation logic. When a back command will return to this form the * state would be restored using setFormState. * The default implementation of this method restores focus and list selection. * You can add arbitrary keys to the form state, keys starting with a $ sign * are reserved for the UIBuilder base class use. * * @param f the form whose state should be preserved * @return arbitrary state object */ protected Hashtable getFormState(Form f) { Component c = f.getFocused(); Hashtable h = new Hashtable(); h.put(FORM_STATE_KEY_NAME, f.getName()); if(c != null) { if(c instanceof List) { h.put(FORM_STATE_KEY_SELECTION, new Integer(((List)c).getSelectedIndex())); } if(c.getName() != null) { h.put(FORM_STATE_KEY_FOCUS, c.getName()); } } return h; } /** * Sets the state of the current form to which we are returing as part * of the navigation logic. When a back command is pressed this form * state should be restored, it was obtained via getFormState. * The default implementation of this method restores focus and list selection. * * @param f the form whose state should be preserved * @param state arbitrary state object */ protected void setFormState(Form f, Hashtable state) { setContainerStateImpl(f, state); } private boolean isParentOf(Container cnt, Component c) { while(c != null) { if(c == cnt) { return true; } c = c.getParent(); } return false; } /** * This method is the container navigation equivalent of getFormState() see * that method for details. * @param cnt the container * @return the state */ protected Hashtable getContainerState(Container cnt) { Component c = null; Form parentForm = cnt.getComponentForm(); if(parentForm != null) { c = parentForm.getFocused(); } Hashtable h = new Hashtable(); h.put(FORM_STATE_KEY_NAME, cnt.getName()); h.put(FORM_STATE_KEY_CONTAINER, ""); if(c != null && isParentOf(cnt, c)) { if(c instanceof List) { h.put(FORM_STATE_KEY_SELECTION, new Integer(((List)c).getSelectedIndex())); } if(c.getName() != null) { h.put(FORM_STATE_KEY_FOCUS, c.getName()); } return h; } return h; } private void setContainerStateImpl(Container cnt, Hashtable state) { if(state != null) { String cmpName = (String)state.get(FORM_STATE_KEY_FOCUS); if(cmpName == null) { return; } Component c = findByName(cmpName, cnt); if(c != null) { c.requestFocus(); if(c instanceof List) { Integer i = (Integer)state.get(FORM_STATE_KEY_SELECTION); if(i != null) { ((List)c).setSelectedIndex(i.intValue()); } } } } } /** * This method is the container navigation equivalent of setFormState() see * that method for details. * * @param cnt the container * @param state the state */ protected void setContainerState(Container cnt, Hashtable state) { setContainerStateImpl(cnt, state); } /** * When reaching the home form the navigation stack is cleared * @return the homeForm */ public String getHomeForm() { return homeForm; } /** * When reaching the home form the navigation stack is cleared * @param homeForm the homeForm to set */ public void setHomeForm(String homeForm) { this.homeForm = homeForm; } /** * This method effectively pops the form navigation stack and goes back * to the previous form if back navigation is enabled and there is * a previous form. */ public void back() { back(null); } /** * This method effectively pops the form navigation stack and goes back * to the previous form if back navigation is enabled and there is * a previous form. * * @param sourceComponent the component that triggered the back command which effectively * allows us to find the EmbeddedContainer for a case of container navigation. Null * can be used if not applicable. */ public void back(Component sourceComponent) { Vector formNavigationStack = getFormNavigationStackForComponent(sourceComponent); if(formNavigationStack != null && formNavigationStack.size() > 0) { Hashtable h = (Hashtable)formNavigationStack.elementAt(formNavigationStack.size() - 1); if(h.containsKey(FORM_STATE_KEY_CONTAINER)) { Form currentForm = Display.getInstance().getCurrent(); if(currentForm != null) { exitForm(currentForm); } } String formName = (String)h.get(FORM_STATE_KEY_NAME); if(!h.containsKey(FORM_STATE_KEY_CONTAINER)) { Form f = (Form)createContainer(fetchResourceFile(), formName); initBackForm(f); beforeShow(f); f.showBack(); postShow(f); } else { showContainerImpl(formName, null, sourceComponent, true); } } } private void initBackForm(Form f) { Vector formNavigationStack = baseFormNavigationStack; if(formNavigationStack != null && formNavigationStack.size() > 0) { setFormState(f, (Hashtable)formNavigationStack.elementAt(formNavigationStack.size() - 1)); formNavigationStack.removeElementAt(formNavigationStack.size() - 1); if(formNavigationStack.size() > 0) { Hashtable previous = (Hashtable)formNavigationStack.elementAt(formNavigationStack.size() - 1); String commandAction = (String)previous.get(FORM_STATE_KEY_NAME); Command backCommand = createCommandImpl("Back", null, BACK_COMMAND_ID, commandAction, true, ""); f.addCommand(backCommand, f.getCommandCount()); f.setBackCommand(backCommand); // trigger listener creation if this is the only command in the form getFormListenerInstance(f, null); backCommand.putClientProperty(COMMAND_ARGUMENTS, ""); backCommand.putClientProperty(COMMAND_ACTION, commandAction); } } } private void initBackContainer(Container cnt, Form destForm, Vector formNavigationStack) { setContainerState(cnt, (Hashtable)formNavigationStack.elementAt(formNavigationStack.size() - 1)); formNavigationStack.removeElementAt(formNavigationStack.size() - 1); if(formNavigationStack.size() > 0) { Hashtable previous = (Hashtable)formNavigationStack.elementAt(formNavigationStack.size() - 1); String commandAction = (String)previous.get(FORM_STATE_KEY_NAME); Command backCommand = createCommandImpl("Back", null, BACK_COMMAND_ID, commandAction, true, ""); destForm.setBackCommand(backCommand); backCommand.putClientProperty(COMMAND_ARGUMENTS, ""); backCommand.putClientProperty(COMMAND_ACTION, commandAction); } } /** * This method is equivalent to the internal navigation behavior, it adds * functionality such as the back command into the given form resource and * shows it. If the source command is the back command the showBack() method * will run. * Notice that container navigation (none-form) doesn't support the back() method * or the form stack. However a command marked as back command will be respected. * * @param resourceName the name of the resource for the form to show * @param sourceCommand the command of the resource (may be null) * @param sourceComponent the component that activated the show (may be null) * @return the container thats being shown, notice that you can still manipulate * some states of the container before it actually appears */ public Container showContainer(String resourceName, Command sourceCommand, Component sourceComponent) { return showContainerImpl(resourceName, sourceCommand, sourceComponent, false); } /** * Useful tool to refresh the current state of a container shown using show container * without pushing another instance to the back stack * * @param cnt the container thats embedded into the application */ public void reloadContainer(Component cnt) { Container newCnt = createContainer(fetchResourceFile(), cnt.getName(), (EmbeddedContainer)cnt.getParent()); beforeShowContainer(newCnt); cnt.getParent().replace(cnt, newCnt, null); postShowContainer(newCnt); } /** * Useful tool to refresh the current state of a form shown using show form * without pushing another instance to the back stack */ public void reloadForm() { Form currentForm = Display.getInstance().getCurrent(); Form newForm = (Form)createContainer(fetchResourceFile(), currentForm.getName()); beforeShow(newForm); Transition tin = newForm.getTransitionInAnimator(); Transition tout = newForm.getTransitionOutAnimator(); currentForm.setTransitionInAnimator(CommonTransitions.createEmpty()); currentForm.setTransitionOutAnimator(CommonTransitions.createEmpty()); newForm.setTransitionInAnimator(CommonTransitions.createEmpty()); newForm.setTransitionOutAnimator(CommonTransitions.createEmpty()); newForm.show(); postShow(newForm); newForm.setTransitionInAnimator(tin); newForm.setTransitionOutAnimator(tout); } private Container showContainerImpl(String resourceName, Command sourceCommand, Component sourceComponent, boolean forceBack) { if(sourceComponent != null) { Form currentForm = sourceComponent.getComponentForm(); // avoid the overhead of searching if no embedding is used if(currentForm.getClientProperty(EMBEDDED_FORM_FLAG) != null) { Container destContainer = sourceComponent.getParent(); while(!(destContainer instanceof EmbeddedContainer || destContainer instanceof Form)) { // race condition, container was already removed by someone else if(destContainer == null) { return null; } destContainer = destContainer.getParent(); } if(destContainer instanceof EmbeddedContainer) { Container cnt = createContainer(fetchResourceFile(), resourceName, (EmbeddedContainer)destContainer); if(cnt instanceof Form) { //Form f = (Form)cnt; //cnt = formToContainer(f); showForm((Form)cnt, sourceCommand, sourceComponent); return cnt; } Component fromCmp = destContainer.getComponentAt(0); // This seems to be no longer necessary now that we have the replaceAndWait version that drops events // block the user from the ability to press the button twice by mistake //fromCmp.setEnabled(false); boolean isBack = forceBack; Transition t = UIManager.getInstance().getLookAndFeel().getDefaultFormTransitionOut(); if(forceBack) { initBackContainer(cnt, destContainer.getComponentForm(), getFormNavigationStackForComponent(sourceComponent)); t = t.copy(true); } else { if(sourceCommand != null) { if(t != null && backCommands != null && backCommands.contains(sourceCommand) || Display.getInstance().getCurrent().getBackCommand() == sourceCommand) { isBack = true; t = t.copy(true); } } } // create a back command if supported String commandAction = cnt.getName(); Vector formNavigationStack = getFormNavigationStackForComponent(fromCmp); if(formNavigationStack != null && !isBack && allowBackTo(commandAction) && !isSameBackDestination((Container)fromCmp, cnt)) { // trigger listener creation if this is the only command in the form getFormListenerInstance(destContainer.getComponentForm(), null); formNavigationStack.addElement(getContainerState((com.sun.lwuit.Container)fromCmp)); } beforeShowContainer(cnt); destContainer.replaceAndWait(fromCmp, cnt, t, true); postShowContainer(cnt); return cnt; } else { Container cnt = createContainer(fetchResourceFile(), resourceName); showForm((Form)cnt, sourceCommand, sourceComponent); return cnt; } } } Container cnt = createContainer(fetchResourceFile(), resourceName); if(cnt instanceof Form) { showForm((Form)cnt, sourceCommand, sourceComponent); } else { Form f = new Form(); f.setLayout(new BorderLayout()); f.addComponent(BorderLayout.CENTER, cnt); showForm(f, sourceCommand, sourceComponent); } return cnt; } private Container formToContainer(Form f) { Container cnt = new Container(f.getContentPane().getLayout()); if(f.getContentPane().getLayout() instanceof BorderLayout || f.getContentPane().getLayout() instanceof TableLayout) { while(f.getContentPane().getComponentCount() > 0) { Component src = f.getContentPane().getComponentAt(0); Object o = f.getContentPane().getLayout().getComponentConstraint(src); f.getContentPane().removeComponent(src); cnt.addComponent(o, src); } } else { while(f.getContentPane().getComponentCount() > 0) { Component src = f.getContentPane().getComponentAt(0); f.getContentPane().removeComponent(src); cnt.addComponent(src); } } return cnt; } private void showForm(Form f, Command sourceCommand, Component sourceComponent) { if(Display.getInstance().getCurrent() instanceof Dialog) { ((Dialog)Display.getInstance().getCurrent()).dispose(); } Vector formNavigationStack = baseFormNavigationStack; if(sourceCommand != null && Display.getInstance().getCurrent().getBackCommand() == sourceCommand) { Form currentForm = Display.getInstance().getCurrent(); if(currentForm != null) { exitForm(currentForm); } if(formNavigationStack != null && formNavigationStack.size() > 0) { initBackForm(f); } beforeShow(f); f.showBack(); postShow(f); } else { if(formNavigationStack != null && !(f instanceof Dialog) && !f.getName().equals(homeForm)) { Form currentForm = Display.getInstance().getCurrent(); if(currentForm != null) { exitForm(currentForm); String nextForm = (String)f.getClientProperty("%next_form%"); // don't add back commands to transitional forms if(nextForm == null) { String commandAction = currentForm.getName(); if(allowBackTo(commandAction) && f.getBackCommand() == null) { Command backCommand; if(isSameBackDestination(currentForm, f)) { backCommand = currentForm.getBackCommand(); } else { backCommand = createCommandImpl("Back", null, BACK_COMMAND_ID, commandAction, true, ""); backCommand.putClientProperty(COMMAND_ARGUMENTS, ""); backCommand.putClientProperty(COMMAND_ACTION, commandAction); } f.addCommand(backCommand, f.getCommandCount()); f.setBackCommand(backCommand); // trigger listener creation if this is the only command in the form getFormListenerInstance(f, null); formNavigationStack.addElement(getFormState(Display.getInstance().getCurrent())); } } } } if(f instanceof Dialog) { beforeShow(f); if(sourceComponent != null) { // we are cheating with the post show here since we are using a modal // dialog to prevent the "double clicking button" problem by using // a modal dialog sourceComponent.setEnabled(false); postShow(f); f.show(); sourceComponent.setEnabled(true); exitForm(f); } else { ((Dialog)f).showModeless(); postShow(f); } } else { beforeShow(f); f.show(); postShow(f); } } } /** * Indicates whether a back command to this form should be generated automatically when * leaving said form. * * @param formName the name of the form * @return true to autogenerate and add a back command to the destination form */ protected boolean allowBackTo(String formName) { return true; } /** * When navigating from one form/container to another we sometimes might not want the * back command to return to the previous container/form but rather to the one before * source. A good example would be a "refresh" command or a toggle button that changes * the form state. * * @param source the form or container we are leaving * @param destination the container or form we are navigating to * @return false if we want a standard back button to source, true if we want to use * the same back button as the one in source */ protected boolean isSameBackDestination(Container source, Container destination) { return source.getName().equals(destination.getName()); } /** * This method is equivalent to the internal navigation behavior, it adds * functionality such as the back command into the given form resource and * shows it. If the source command is the back command the showBack() method * will run. * * @param resourceName the name of the resource for the form to show * @param sourceCommand the command of the resource (may be null) * @return the form thats being shown, notice that you can still manipulate * some states of the form before it actually appears */ public Form showForm(String resourceName, Command sourceCommand) { Form f = (Form)createContainer(fetchResourceFile(), resourceName); showForm(f, sourceCommand, null); return f; } /** * This method allows binding an action that should occur before leaving the given * form, e.g. memory cleanup * * @param f the form being left */ protected void exitForm(Form f) { } /** * This method allows binding an action that should occur before showing the given * form * * @param f the form about to be shown */ protected void beforeShow(Form f) { } /** * This method allows binding an action that should occur immediately after showing the given * form * * @param f the form that was just shown */ protected void postShow(Form f) { } /** * This method allows binding an action that should occur before showing the given * container * * @param c the container about to be shown */ protected void beforeShowContainer(Container c) { } /** * This method allows binding an action that should occur immediately after showing the given * container * * @param c the container that was just shown */ protected void postShowContainer(Container c) { } /** * This method allows binding logic that should occur before creating the root object * e.g. a case where a created form needs data fetched for it. * * @param rootName the name of the root to be created from the resource file */ protected void onCreateRoot(String rootName) { } /** * Indicates that the UIBuilder should cache resources in memory and never release them. * This is useful with small resource files or high RAM devices since it saves the cost * of constantly fetching the res file from the jar whenever moving between forms. * This can be toggled in the properties (e.g. jad) using the flag: cacheResFile (true/false) * which defaults to false. * * @return the keepResourcesInRam */ public boolean isKeepResourcesInRam() { return keepResourcesInRam; } /** * Indicates that the UIBuilder should cache resources in memory and never release them. * This is useful with small resource files or high RAM devices since it saves the cost * of constantly fetching the res file from the jar whenever moving between forms. * This can be toggled in the properties (e.g. jad) using the flag: cacheResFile (true/false) * which defaults to false. * * @param keepResourcesInRam the keepResourcesInRam to set */ public void setKeepResourcesInRam(boolean keepResourcesInRam) { this.keepResourcesInRam = keepResourcesInRam; } /** * Returns either the parent form or the component bellow the embedded container * above c. * * @param c the component whose root ancestor we should find * @return the root */ protected Container getRootAncestor(Component c) { while(c.getParent() != null && !(c.getParent() instanceof EmbeddedContainer)) { c = c.getParent(); } return (Container)c; } class FormListener implements ActionListener, Runnable { private Command currentAction; private ActionEvent currentActionEvent; private Form destForm; private String nextForm; public FormListener() { } public FormListener(Form destForm, String nextForm) { this.destForm = destForm; this.nextForm = nextForm; } public FormListener(Command currentAction, ActionEvent currentActionEvent, Form destForm) { this.currentAction = currentAction; this.currentActionEvent = currentActionEvent; this.destForm = destForm; } private void waitForForm(Form f) { while(Display.getInstance().getCurrent() != f) { try { Thread.sleep(5); } catch (InterruptedException ex) { ex.printStackTrace(); } } Display.getInstance().callSerially(this); } public void run() { if(currentAction != null) { if(Display.getInstance().isEdt()) { postAsyncCommand(currentAction, currentActionEvent); } else { asyncCommandProcess(currentAction, currentActionEvent); // wait for the destination form to appear before moving back into the LWUIT thread waitForForm(destForm); } } else { if(Display.getInstance().isEdt()) { if(Display.getInstance().getCurrent() != null) { exitForm(Display.getInstance().getCurrent()); } Form f = (Form)createContainer(fetchResourceFile(), nextForm); beforeShow(f); f.show(); postShow(f); } else { if(processBackground(destForm)) { waitForForm(destForm); } } } } public void actionPerformed(ActionEvent evt) { Command cmd = evt.getCommand(); if(cmd == null) { // this is a show listener we should spawn the background thread if(evt.getSource() instanceof Form) { // prevent a dialog from triggerring the background processing again ((Form)evt.getSource()).removeShowListener(this); new Thread(this).start(); return; } handleComponentAction((Component)evt.getSource(), evt); return; } processCommandImpl(evt, cmd); if(evt.isConsumed()) { return; } String action = (String)cmd.getClientProperty(COMMAND_ACTION); if(action != null && action.length() > 0) { if(action.equals("$Minimize")) { Display.getInstance().minimizeApplication(); return; } if(action.equals("$Exit")) { Display.getInstance().exitApplication(); return; } if(action.equals("$Execute")) { Display.getInstance().execute(action); return; } if(action.equals("$Back")) { back(); return; } if(action.startsWith("!")) { action = action.substring(1); Form currentForm = Display.getInstance().getCurrent(); if(currentForm != null) { exitForm(currentForm); } int pos = action.indexOf(';'); String firstScreen = action.substring(0, pos); String nextScreen = action.substring(pos + 1, action.length()); Form f = (Form)createContainer(fetchResourceFile(), firstScreen); beforeShow(f); if(Display.getInstance().getCurrent().getBackCommand() == cmd) { f.showBack(); } else { f.show(); } postShow(f); new Thread(new FormListener(f, nextScreen)).start(); return; } if(action.startsWith("@")) { action = action.substring(1); Form currentForm = Display.getInstance().getCurrent(); if(currentForm != null) { exitForm(currentForm); } Form f = (Form)createContainer(fetchResourceFile(), action); beforeShow(f); if(Display.getInstance().getCurrent().getBackCommand() == cmd) { f.showBack(); } else { f.show(); } postShow(f); new Thread(new FormListener(cmd, evt, f)).start(); return; } showContainer(action, cmd, evt.getComponent()); } } } }