/* Copyright (c) 2008 Bluendo S.r.L. * See about.html for details about license. * * $Id: DataFormScreen.java 1578 2009-06-16 11:07:59Z luca $ */ /** * */ package lampiro.screens; import it.yup.ui.UIButton; import it.yup.ui.UICanvas; import it.yup.ui.UICheckbox; import it.yup.ui.UICombobox; import it.yup.ui.UIGauge; import it.yup.ui.UIHLayout; import it.yup.ui.UIItem; import it.yup.ui.UILabel; import it.yup.ui.UILayout; import it.yup.ui.UIMenu; import it.yup.ui.UIPanel; import it.yup.ui.UIScreen; import it.yup.ui.UISeparator; import it.yup.ui.UITextField; import it.yup.ui.UIUtils; import it.yup.util.ResourceIDs; import it.yup.util.ResourceManager; import it.yup.xmpp.DataFormListener; import it.yup.xmpp.packets.DataForm; import javax.microedition.lcdui.Canvas; import javax.microedition.lcdui.Gauge; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.TextField; import lampiro.screens.RosterScreen.WaitScreen; /** * <p> * This class handles the data form input from the user, rendering a given Data * Form using the base controls offered by J2ME UI. The input result is then * sent to a DataFormListener that handles the form outcome. * </p> * * <p> * DataForms are rendered as follows. * <ul> * <li><b>hidden</b>: are skipped.</li> * <li><b>boolean</b>: are rendered with a ChoiceGroup flagged with "MULTIPLE" * and with a single choice item that may be checked (true) or unchecked * (false).</li> * <li><b>list-multi and list-single</b>: they show a button that opens a * separate List, List is "EXCLUSIVE" (a single voice can be selected) or * "MULTIPLE" (more than one voice selected) resp. for list-single and * list-multi.</li> * <li><b>jid-single</b>, <b>jid-multi</b>, <b>text-single</b>, * <b>text-multi</b>, <b>text-private</b>, <b>fixed</b>: are shown as a * single TextField, *-multi are split on '\n' chars when sending data; * text-private are flagged as PASSWORD fields, jid-single are flagged as * EMAILADDRESS fields. fixed are uneditable.</li> * </ul> * * All fields have a Label before if the DataForm field has one. Commands are * placed on the menu. Instructions, if present, make a command "instructions" * appear on the menu and that voice pops up an alert showing the instructions. * Field desc are ignored. * * At the bottom of the forms the button for the available actions are placed. * Available actions are passed via the method setActions() * * <i>TODO LIST: * <ul> * <li>list-single and list-multi should be changed: they should show a non * editable control that exposes the label and the current selected content plus * a button that spawns the pop-up screen for the selection</li> * <li>text-multi and jid-multi should open a separate TextBox item (note. on * SonyEricsson it seems that there's no difference between the two...).</li> * <li>jid-multi should be checked for emailaddress format (emailaddress is not * enforceable for multiline TextBoxes</li> * <li>check '\n' in fields that are not *-multi and pop up error.</li> * <li>add a voice "field description" on the menu (or place a button with (?)) * to honour the "desc" for each field.</li> * <li>Heuristics: when a form has a single list-* item or the list-* item has * no more than 2 or 3 options, there shouldn't be a need for a pop up screen. * </ul> * </i> * */ public class DataFormScreen extends UIScreen implements WaitScreen { private static ResourceManager rm = ResourceManager.getManager("common", "en"); /** The handled data form */ private DataForm df; /** the listener to be notified of commands */ private DataFormListener dfl; /** the available actions */ private int actions; private UILabel cmd_submit = new UIButton(rm .getString(ResourceIDs.STR_SUBMIT)); private UILabel cmd_cancel = new UIButton(rm .getString(ResourceIDs.STR_CANCEL)); private UILabel menu_cancel = new UILabel(rm.getString( ResourceIDs.STR_CLOSE).toUpperCase()); private UILabel cmd_prev = new UIButton(rm.getString(ResourceIDs.STR_PREV)); private UILabel cmd_next = new UIButton(rm.getString(ResourceIDs.STR_NEXT)); private UILabel cmd_delay = new UILabel(rm .getString(ResourceIDs.STR_FILLLATER)); private UIMenu show_instruction; private UILabel show_instruction_label = new UILabel(rm.getString( ResourceIDs.STR_INSTRUCTIONS).toUpperCase()); private UILabel show_desc_label = new UILabel(rm.getString( ResourceIDs.STR_DESC).toUpperCase()); private UIMenu instruction_menu = null; private UIMenu desc_menu = new UIMenu(""); private UILabel si_instructions = new UILabel(""); /** the item array created to represent the form */ private UIItem[] items; UIGauge progress_gauge = new UIGauge(rm.getString(ResourceIDs.STR_WAIT), false, Gauge.INDEFINITE, Gauge.CONTINUOUS_RUNNING); /* * To construct the "Expand" support */ UIMenu zoomSubmenu; UILabel zoomLabel = new UILabel("EXPAND"); private UIHLayout mainLayout = new UIHLayout(3); private UIPanel mainPanel = new UIPanel(true, false); public DataFormScreen(DataForm df, DataFormListener dfl) { setTitle(rm.getString(ResourceIDs.STR_FILL_FORM)); UISeparator separator = new UISeparator(0); mainLayout.setGroup(false); mainLayout.insert(separator, 0, 3, UILayout.CONSTRAINT_PIXELS); mainLayout.insert(mainPanel, 1, 100, UILayout.CONSTRAINT_PERCENTUAL); mainLayout.insert(separator, 2, 3, UILayout.CONSTRAINT_PIXELS); this.append(mainLayout); this.df = df; this.dfl = dfl; if (df.title != null) { setTitle(df.title); } setMenu(new UIMenu("")); UIMenu menu = getMenu(); menu.append(menu_cancel); //menu.append(cmd_delay); actions = DataFormListener.CMD_SUBMIT | DataFormListener.CMD_CANCEL; instruction_menu = UIUtils.easyMenu(rm .getString(ResourceIDs.STR_INSTRUCTIONS), 10, 20, this .getWidth() - 10, null); // desc_menu.setAbsoluteX(10); // desc_menu.setAbsoluteY(20); // desc_menu.setWidth(this.getWidth() - 10); desc_menu.append(show_desc_label); show_instruction = UIUtils.easyMenu("", 10, 20, this.getWidth() - 10, show_instruction_label); // prepare zoomSubMenu zoomSubmenu = UIUtils.easyMenu("", 10, 10, this.getWidth() - 30, zoomLabel); zoomLabel.setAnchorPoint(Graphics.HCENTER); createControls(); } /** * Sets the available command buttons. Actions should be one of the CMD_* * flags defined in the DataFormListener interface. * * @param cmds */ public void setActions(int _ac) { /* submit and cancel are always shown */ actions = _ac | DataFormListener.CMD_SUBMIT | DataFormListener.CMD_CANCEL; createControls(); } /** * Show the form, dynamically adding all the controls */ private void createControls() { // as always: many operations on the gui need a freeze since // i love the battery life this.setFreezed(true); this.mainPanel.removeAllItems(); /* do I create this only once? */ items = new UIItem[df.fields.size()]; for (int i = 0; i < df.fields.size(); i++) { DataForm.Field fld = (DataForm.Field) df.fields.elementAt(i); if (fld.type == DataForm.FLT_HIDDEN) { continue; } if (fld.type == DataForm.FLT_BOOLEAN) { // XXX: check how to render this String fldName = (fld.label == null ? fld.varName : fld.label); UICheckbox cgrp = new UICheckbox(fldName); if ("1".equals(fld.dValue) || "true".equals(fld.dValue)) { cgrp.setChecked(true); } else { cgrp.setChecked(false); } mainPanel.addItem(cgrp); items[i] = cgrp; continue; } if (fld.type == DataForm.FLT_LISTMULTI || fld.type == DataForm.FLT_LISTSINGLE) { String title = (fld.label == null ? fld.varName : fld.label); UICombobox cgrp = new UICombobox(title, (fld.type == DataForm.FLT_LISTMULTI)); boolean[] flags = new boolean[fld.options.size()]; for (int j = 0; j < fld.options.size(); j++) { String[] opt = (String[]) fld.options.elementAt(j); cgrp.append(opt[1]); if (fld.dValue != null && fld.dValue.indexOf(opt[0]) != -1) { flags[j] = true; } else { flags[j] = false; } } cgrp.setSelectedFlags(flags); mainPanel.addItem(cgrp); items[i] = cgrp; continue; } if (fld.type == DataForm.FLT_JIDSINGLE || fld.type == DataForm.FLT_TXTPRIV || fld.type == DataForm.FLT_TXTSINGLE || fld.type == DataForm.FLT_JIDMULTI || fld.type == DataForm.FLT_TXTMULTI || fld.type == DataForm.FLT_FIXED) { String title = (fld.label == null ? ""/* fld.varName */ : fld.label); int flags = TextField.ANY; if (fld.type == DataForm.FLT_TXTPRIV) { flags |= TextField.PASSWORD; } if (fld.type == DataForm.FLT_JIDSINGLE) { flags |= TextField.EMAILADDR; } if (fld.type == DataForm.FLT_FIXED) { flags |= TextField.UNEDITABLE; } // XXX: Which the maximum allowed length? We use 1k for the // moment UITextField tf = new UITextField(title, fld.dValue, 1024, flags); mainPanel.addItem(tf); if (fld.type == DataForm.FLT_TXTMULTI || fld.type == DataForm.FLT_FIXED) { if (fld.type == DataForm.FLT_TXTMULTI) { tf.setMinLines(4); } tf.setWrappable(true); } items[i] = tf; continue; } } if (df.instructions != null) { for (int i = 0; i < items.length; i++) { if (items[i] != null) { items[i].setSubmenu(show_instruction); } } si_instructions.setText(df.instructions); if (instruction_menu.getItemList().contains(si_instructions) == false) instruction_menu .append(si_instructions); si_instructions .setWrappable(true, instruction_menu.getWidth() - 10); } // add the desc this.addDesc(); /* * Spacer sp = new Spacer(10, 5); sp.setLayout(Item.LAYOUT_NEWLINE_AFTER | * Item.LAYOUT_NEWLINE_BEFORE); append(sp); */ /* Buttons: should be placed in-line */ /* show actions. order is CANCEL, [PREV], [NEXT], SUBMIT */ UILabel dummyLabel = new UILabel(""); UIItem insertItem = null; UIHLayout uhl1 = new UIHLayout(2); UIHLayout uhl2 = new UIHLayout(2); boolean addUhl1 = false; boolean addUhl2 = false; uhl1.setGroup(false); uhl2.setGroup(false); if ((actions & DataFormListener.CMD_CANCEL) != 0) { insertItem = cmd_cancel; addUhl2 = true; if (df.instructions != null) { insertItem.setSubmenu(show_instruction); } } else { insertItem = dummyLabel; } uhl2.insert(insertItem, 0, 50, UILayout.CONSTRAINT_PERCENTUAL); if ((actions & DataFormListener.CMD_PREV) != 0) { insertItem = cmd_prev; addUhl1 = true; if (df.instructions != null) { insertItem.setSubmenu(show_instruction); } } else { insertItem = dummyLabel; } uhl1.insert(insertItem, 0, 50, UILayout.CONSTRAINT_PERCENTUAL); if ((actions & DataFormListener.CMD_NEXT) != 0) { insertItem = cmd_next; addUhl1 = true; if (df.instructions != null) { insertItem.setSubmenu(show_instruction); } } else { insertItem = dummyLabel; } uhl1.insert(insertItem, 1, 50, UILayout.CONSTRAINT_PERCENTUAL); if ((actions & DataFormListener.CMD_SUBMIT) != 0) { insertItem = cmd_submit; addUhl2 = true; if (df.instructions != null) { insertItem.setSubmenu(show_instruction); } } else { insertItem = dummyLabel; } uhl2.insert(insertItem, 1, 50, UILayout.CONSTRAINT_PERCENTUAL); if (addUhl1) mainPanel.addItem(uhl1); if (addUhl2) mainPanel.addItem(uhl2); this.setSelectedIndex(0); this.setFreezed(false); this.askRepaint(); } private void addDesc() { for (int i = 0; i < df.fields.size(); i++) { DataForm.Field fld = (DataForm.Field) df.fields.elementAt(i); if (fld.desc != null) { items[i].setSubmenu(desc_menu); } } } protected void paint(Graphics g, int w, int h) { super.paint(g, w, h); // longest textfield handling UIItem panelItem = mainPanel.getSelectedItem(); if (panelItem instanceof UITextField) { Graphics tg = getGraphics(); int labelHeight = panelItem.getHeight(tg); int availableHeight = UICanvas.getInstance().getClipHeight() - this.headerLayout.getHeight(tg) - this.footer.getHeight(tg); UIMenu itemSubMenu = panelItem.getSubmenu(); if (labelHeight > availableHeight && (itemSubMenu == null || itemSubMenu != zoomSubmenu)) { panelItem.setSubmenu(zoomSubmenu); // always reset these values when asking a "repaint" within a "paint" UICanvas ci = UICanvas.getInstance(); g.translate(-g.getTranslateX(), -g.getTranslateY()); g.setClip(0, 0, ci.getWidth(), ci.getHeight() + 1); this.askRepaint(); } } } /** * Command handler */ public void menuAction(UIMenu menu, UIItem cmd) { int comm = -1; boolean setWaiting = false; if (cmd == cmd_cancel || cmd == menu_cancel) { comm = DataFormListener.CMD_CANCEL; } else if (cmd == cmd_submit) { comm = DataFormListener.CMD_SUBMIT; setWaiting = true; } else if (cmd == cmd_next) { comm = DataFormListener.CMD_NEXT; setWaiting = true; } else if (cmd == cmd_prev) { comm = DataFormListener.CMD_PREV; setWaiting = true; } else if (cmd == cmd_delay) { comm = DataFormListener.CMD_DELAY; setWaiting = true; } else if (cmd == this.zoomLabel) { UITextField selLabel = (UITextField) this.getSelectedItem(); selLabel.handleScreen(); } else if (cmd == show_desc_label) { int index = this.getSelectedIndex(); String desc = ((DataForm.Field) this.df.fields.elementAt(index)).desc; UITextField descField = new UITextField("", desc, desc.length(), TextField.UNEDITABLE); descField.setWrappable(true); UIMenu descriptionMenu = UIUtils.easyMenu(rm .getString(ResourceIDs.STR_DESC), 10, 20, this.getWidth() - 20, descField); //descPanel.setMaxHeight(UICanvas.getInstance().getClipHeight() / 2); descriptionMenu.cancelMenuString = ""; descriptionMenu.selectMenuString = rm.getString( ResourceIDs.STR_CLOSE).toUpperCase(); descriptionMenu.setSelectedIndex(1); this.addPopup(descriptionMenu); descField.expand(); } else if (cmd == show_instruction_label) { /* show/hide instructions */ this.addPopup(this.instruction_menu); return; } if (comm == -1) { /* ???? */ return; } fillForm(); // if the dataform will have an answer, e.g. an IQ contained dataform setWaiting &= dfl.execute(comm); // #ifdef UI if (setWaiting == true) { mainPanel.removeAllItems(); mainPanel.addItem(progress_gauge); progress_gauge.start(); RosterScreen.getInstance().setWaitingDF(this); this.askRepaint(); } else { this.stopWaiting(); } // #endif } public boolean keyPressed(int kc) { if (super.keyPressed(kc)) return true; if (this.popupList.size() == 0 && this.getMenu().isOpenedState() == false) { int ga = UICanvas.getInstance().getGameAction(kc); switch (ga) { case Canvas.RIGHT: { RosterScreen.showNextScreen(this); return true; } case Canvas.LEFT: { RosterScreen.showPreviousScreen(this); return true; } } } return false; } /** * Command handler for on-screen buttons */ public void itemAction(UIItem cmd) { menuAction(null, cmd); } /** * Called when submit is pressed */ private void fillForm() { // XXX: here we could verify the required fields for (int i = 0; i < df.fields.size(); i++) { DataForm.Field fld = (DataForm.Field) df.fields.elementAt(i); if (fld.type == DataForm.FLT_HIDDEN) { continue; } if (fld.type == DataForm.FLT_BOOLEAN) { UICheckbox cgrp = (UICheckbox) items[i]; fld.dValue = (cgrp.isChecked() ? "true" : "false"); continue; } if (fld.type == DataForm.FLT_LISTMULTI || fld.type == DataForm.FLT_LISTSINGLE) { UICombobox cmb = (UICombobox) items[i]; boolean[] flags = cmb.getSelectedFlags(); StringBuffer dtext = new StringBuffer(); int scount = 0; for (int j = 0; j < flags.length; j++) { if (flags[j]) { scount++; String[] opt = (String[]) fld.options.elementAt(j); if (scount > 1) { dtext.append("\n"); } dtext.append(opt[0]); } } if (scount == 0) { fld.dValue = ""; } else { fld.dValue = dtext.toString(); } continue; } if (fld.type == DataForm.FLT_JIDSINGLE || fld.type == DataForm.FLT_TXTPRIV || fld.type == DataForm.FLT_TXTSINGLE || fld.type == DataForm.FLT_JIDMULTI || fld.type == DataForm.FLT_TXTMULTI || fld.type == DataForm.FLT_FIXED) { UITextField tf = (UITextField) items[i]; fld.dValue = tf.getText(); continue; } } } public void stopWaiting() { progress_gauge.cancel(); UICanvas.getInstance().close(this); } }