001: /* ZkDesktop.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: May 29, 2007 9:41:29 AM, Created by henrichen
010: }}IS_NOTE
011:
012: Copyright (C) 2007 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.zkmob.ui;
020:
021: import java.io.ByteArrayInputStream;
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.io.UnsupportedEncodingException;
025: import java.util.Enumeration;
026: import java.util.Hashtable;
027: import java.util.Vector;
028:
029: import javax.microedition.io.Connector;
030: import javax.microedition.io.HttpConnection;
031: import javax.microedition.lcdui.Alert;
032: import javax.microedition.lcdui.AlertType;
033: import javax.microedition.lcdui.ChoiceGroup;
034: import javax.microedition.lcdui.Command;
035: import javax.microedition.lcdui.CommandListener;
036: import javax.microedition.lcdui.CustomItem;
037: import javax.microedition.lcdui.DateField;
038: import javax.microedition.lcdui.Display;
039: import javax.microedition.lcdui.Displayable;
040: import javax.microedition.lcdui.Form;
041: import javax.microedition.lcdui.Gauge;
042: import javax.microedition.lcdui.Item;
043: import javax.microedition.lcdui.ItemCommandListener;
044: import javax.microedition.lcdui.ItemStateListener;
045: import javax.microedition.lcdui.TextField;
046:
047: import org.xml.sax.SAXException;
048: import org.zkoss.zkmob.Browser;
049: import org.zkoss.zkmob.Event;
050: import org.zkoss.zkmob.Inputable;
051: import org.zkoss.zkmob.Itemable;
052: import org.zkoss.zkmob.Listable;
053: import org.zkoss.zkmob.UiManager;
054: import org.zkoss.zkmob.UpdateHandler;
055: import org.zkoss.zkmob.UpdateRequest;
056: import org.zkoss.zkmob.ZkComponent;
057:
058: /**
059: * A virtual component that holds information regarding ZK server's Desktop, Page, Device, and other things.
060: * @author henrichen
061: *
062: */
063: public class ZkDesktop implements ZkComponent {
064: private Browser _browser;
065: private Displayable _current;
066: private Vector _displayables = new Vector(8);
067: private Hashtable _uiMap = new Hashtable(16);
068: private Inputable _toSendOnChange;
069:
070: private String _dtid; //desktop id
071: private String _action; //zk_action
072: private int _procto; //zk_procto, millisecond to show progress meter
073: private int _tipto; //zk_tipto, millisecond to popup the tip
074: private String _ver; //zk_ver
075: private String _hostURL; //URL prefix for this desktop
076: private String _pathURL; //URL prefix for relative path
077: private String _jsessionid; //session id
078:
079: private Vector _events = new Vector(); //event list
080:
081: private CommandListener _cmdListener;
082: private ItemStateListener _itemStateListener;
083: private ItemCommandListener _itemCommandListener;
084:
085: public ZkDesktop(String dtid, String action, int procto, int tipto,
086: String ver, String hostURL, String pathURL) {
087: _dtid = dtid;
088:
089: if (action != null) {
090: int j = action.indexOf(";jsessionid");
091: if (j >= 0) {
092: _jsessionid = action.substring(j);
093: }
094: } else {
095: action = "";
096: }
097: _hostURL = hostURL;
098: _pathURL = pathURL;
099: _action = _hostURL + action;
100: _procto = procto;
101: _tipto = tipto;
102: _ver = ver;
103:
104: _cmdListener = new CommandListener() {
105: public void commandAction(Command command, Displayable disp) {
106: final ZkComponent comp = (ZkComponent) disp;
107: sendOnChange(comp);
108:
109: final ZkCommand cmd = (ZkCommand) command;
110: send(new Event(comp.getId(), "onCommand",
111: new Object[] { cmd.getId() }), true);
112: }
113: };
114:
115: _itemCommandListener = new ItemCommandListener() {
116: public void commandAction(Command command, Item item) {
117: final ZkComponent comp = (ZkComponent) item;
118: sendOnChange(comp);
119:
120: final ZkCommand cmd = (ZkCommand) command;
121: send(new Event(comp.getId(), "onCommand",
122: new Object[] { cmd.getId() }), true);
123: }
124: };
125:
126: _itemStateListener = new ItemStateListener() {
127: public void itemStateChanged(Item item) {
128: ZkComponent comp = (ZkComponent) item;
129: System.out.println("ItemStateListener, item=" + comp);
130: sendOnChange(comp);
131: final String id = comp.getId();
132: if (comp instanceof ChoiceGroup) {
133: final ZkChoiceGroup cg = (ZkChoiceGroup) comp;
134: final Boolean onSelect = cg.getOnSelect();
135: if (onSelect != null) {
136: final String[] src = new String[cg.size()];
137: int k = 0;
138: int j = 0;
139: for (Enumeration it = cg.getItems().elements(); j < src.length; ++j) {
140: final ZkListItem li = (ZkListItem) it
141: .nextElement();
142: if (cg.isSelected(j)) {
143: src[k++] = li.getId();
144: }
145: }
146: if (k < src.length) {
147: String[] dst = new String[k];
148: System.arraycopy(src, 0, dst, 0, k);
149: send(new Event(comp.getId(), "onSelect",
150: dst), onSelect.booleanValue());
151: } else {
152: send(new Event(comp.getId(), "onSelect",
153: src), onSelect.booleanValue());
154: }
155: }
156: } else if (comp instanceof Gauge) {
157: System.out.println("onSlide,id=" + id);
158: } else if (comp instanceof TextField) {
159: final ZkTextField tf = (ZkTextField) comp;
160: final Boolean onChanging = tf.getOnChanging();
161: if (onChanging != null) {
162: send(new Event(comp.getId(), "onChanging",
163: new Object[] { ((TextField) comp)
164: .getString() }), onChanging
165: .booleanValue());
166: }
167: System.out.println("onChanging,id=" + id);
168: } else if (comp instanceof DateField) {
169: final ZkDateField df = (ZkDateField) comp;
170: final Boolean onChange = df.getOnChange();
171: if (onChange != null) {
172: send(new Event(comp.getId(), "onChange",
173: new Object[] { ("" + df.getDate()
174: .getTime()) }), onChange
175: .booleanValue());
176: }
177: System.out.println("onChange,id=" + id);
178: } else if (comp instanceof CustomItem) {
179: System.out.println("onNotify,id=" + id);
180: }
181: }
182: };
183: }
184:
185: public String getHostURL() {
186: return _hostURL;
187: }
188:
189: public String getPathURL() {
190: return _pathURL;
191: }
192:
193: public void setBrowser(Browser browser) {
194: _browser = browser;
195: if (_current != null && _browser != null) {
196: _browser.getDisplay().setCurrent(_current);
197: }
198: }
199:
200: public void addDisplayable(Displayable disp) {
201: _displayables.addElement(disp);
202: }
203:
204: //will be called whenever possible
205: private void sendOnChange(ZkComponent comp) {
206: if (_toSendOnChange != null && _toSendOnChange != comp) {
207: final Inputable toSend = _toSendOnChange;
208: _toSendOnChange = null;
209: send(new Event(((ZkComponent) toSend).getId(), "onChange",
210: new Object[] { toSend.getString() }), toSend
211: .getOnChange().booleanValue());
212: }
213:
214: if (comp instanceof Inputable) {
215: final Inputable icomp = (Inputable) comp;
216: final Boolean onChange = icomp.getOnChange();
217: if (onChange != null) {
218: _toSendOnChange = icomp; //prepare to send the onChange event
219: }
220: }
221: }
222:
223: //remove this desktop
224: public void removeDesktop() {
225: send(new Event(null, "rmDesktop", null), true);
226: }
227:
228: /** register an Ui Object to an id.
229: * @param id the component id
230: * @param comp the component
231: */
232: public void registerUi(String id, ZkComponent comp) {
233: _uiMap.put(id, comp);
234: }
235:
236: /** unregister an Ui Object.
237: * @param id the component id
238: */
239: public void unregisterUi(String id) {
240: _uiMap.remove(id);
241: }
242:
243: /** lookup a registered component by id.
244: * @param id the component id
245: */
246: public ZkComponent lookupUi(String id) {
247: return (ZkComponent) _uiMap.get(id);
248: }
249:
250: public Displayable getCurrent() {
251: return _current;
252: }
253:
254: public void setCurrent(Displayable current) {
255: _current = current;
256: if (_browser != null) {
257: final Display disp = _browser.getDisplay();
258: if (disp != null) {
259: disp.setCurrent(_current);
260: }
261: }
262: }
263:
264: public Vector getEvents() {
265: return _events;
266: }
267:
268: public CommandListener getCommandListener() {
269: return _cmdListener;
270: }
271:
272: public ItemStateListener getItemStateListener() {
273: return _itemStateListener;
274: }
275:
276: public ItemCommandListener getItemCommandListener() {
277: return _itemCommandListener;
278: }
279:
280: //--ZkComponent--//
281: public String getId() {
282: return _dtid;
283: }
284:
285: public ZkComponent getParent() {
286: return this ;
287: }
288:
289: public void setParent(ZkComponent parent) {
290: //do nothing
291: }
292:
293: public ZkDesktop getZkDesktop() {
294: return this ;
295: }
296:
297: public void setAttr(String attr, String val) {
298: //do nothing
299: }
300:
301: public void addCommand(Command cmd) {
302: //do nothing
303: }
304:
305: public void removeCommand(Command cmd) {
306: //do nothing
307: }
308:
309: //utility to send MIL event from ZK Mobile to ZK Server
310: private void send(Event evt, boolean asap) {
311: _events.addElement(evt);
312: if (!asap) {
313: evt.setImplicit(true);
314: } else {
315: sendEvents();
316: }
317: }
318:
319: private void sendEvents() {
320: final StringBuffer sb = new StringBuffer(64);
321: int j = 0;
322: for (Enumeration e = _events.elements(); e.hasMoreElements(); ++j) {
323: final Event evt = (Event) e.nextElement();
324: sb.append("&cmd." + j + "=" + evt.getCmd());
325: if (evt.getUuid() != null) {
326: sb.append("&uuid." + j + "=" + evt.getUuid());
327: }
328: if (evt.getData() != null) {
329: for (int k = 0; k < evt.getData().length; ++k) {
330: Object data = evt.getData()[k];
331: sb.append("&data."
332: + j
333: + "="
334: + (data != null ? encodeURIComponent(data
335: .toString()) : "zk_null~q"));
336: }
337: }
338: }
339:
340: if (sb.length() == 0)
341: return; //nothing to do
342: _events.removeAllElements();
343:
344: final String content = "dtid=" + _dtid + sb.toString();
345: updateOnThread(_action, content);
346: }
347:
348: private void updateOnThread(String url, String request) {
349: if (url.startsWith("~.")) {
350: throw new IllegalArgumentException("url: " + url);
351: }
352: new Thread(new UpdateRequest(this , url, request)).start();
353: }
354:
355: public void update(String url, String request) {
356: try {
357: myUpdate(url, request);
358: } catch (Throwable e) {
359: System.out.println("update url=" + url);
360: e.printStackTrace();
361: UiManager.alert(_browser.getDisplay(), e);
362: }
363: }
364:
365: private void myUpdate(String url, String request)
366: throws IOException, SAXException {
367: HttpConnection conn = null;
368: InputStream is = null;
369:
370: try {
371: conn = (HttpConnection) Connector.open(url);
372: is = UiManager.request(conn, request);
373: // Load the update response
374: UiManager.getSAXParser().parse(is, new UpdateHandler(this ));
375: } finally {
376: if (is != null)
377: is.close();
378: if (conn != null)
379: conn.close();
380: }
381: }
382:
383: public void executeResponse(RTag rtag) {
384: final String cmd = rtag.getCommand();
385: final String[] data = rtag.getData();
386:
387: if ("setAttr".equals(cmd)) {
388: executeSetAttr(data);
389: } else if ("alert".equals(cmd)) {
390: executeAlert(data);
391: } else if ("home".equals(cmd)) {
392: executeHome(data);
393: } else if ("rm".equals(cmd)) {
394: executeRm(data);
395: } else if ("addAft".equals(cmd)) {
396: executeAddAft(data);
397: } else if ("addBfr".equals(cmd)) {
398: executeAddBfr(data);
399: } else if ("addChd".equals(cmd)) {
400: executeAddChd(data);
401: } else if ("redirect".equals(cmd)) {
402: executeRedirect(data);
403: } else if ("outer".equals(cmd)) {
404: executeOuter(data);
405: } else {
406: System.out.println("Unknown response command: " + cmd);
407: // throw new IllegalArgumentException("Unknown response command: "+cmd);
408: }
409: }
410:
411: //addChd command
412: private void executeAddChd(String[] data) {
413: final String uuid = data[0];
414: final ZkComponent parent = lookupUi(uuid);
415: createComponents(parent, data[1]);
416: }
417:
418: //outer command
419: private void executeOuter(String[] data) {
420: final String uuid = data[0];
421: final ZkComponent comp = lookupUi(uuid);
422: final Vector comps = createComponents(this , data[1]);
423: final ZkComponent newcomp = (ZkComponent) comps.elements()
424: .nextElement();
425:
426: if (comp instanceof ZkListItem) {
427: final Listable owner = (Listable) comp.getParent();
428: final int index = owner.indexOf(comp);
429: owner.delete(index);
430: owner.insertChild(index, newcomp);
431: } else if (comp instanceof Itemable) {
432: final ZkForm form = (ZkForm) ((Itemable) comp).getForm();
433: final int index = form.indexOf((Item) comp);
434: form.delete(index);
435: form.insertChild(index, (ZkComponent) newcomp);
436: } else if (comp instanceof ZkCommand) {
437: final ZkComponent parent = comp.getParent();
438: parent.removeCommand((Command) comp);
439: parent.addCommand((Command) newcomp);
440: comp.setParent(parent);
441: } else if (comp instanceof Displayable) {
442: final Displayable disp = (Displayable) comp;
443: if (_browser.getDisplay().getCurrent() == disp) { //the current showing display
444: _current = null;
445: _browser.getDisplay().setCurrent(new Form(null)); //blank Form
446: }
447: _displayables.removeElement(comp);
448: addDisplayable((Displayable) newcomp);
449: }
450: }
451:
452: //redirect command
453: private void executeRedirect(String[] data) {
454: final String href = data[0];
455: final String url = UiManager
456: .prefixURL(_hostURL, _pathURL, href);
457: _browser.setHomeURL(url);
458: UiManager.loadPageOnThread(_browser, url);
459: }
460:
461: //home command
462: private void executeHome(String[] data) {
463: _browser.goHome(data.length > 0 ? UiManager.prefixURL(_hostURL,
464: _pathURL, data[0]) : null);
465: }
466:
467: //setAttr command
468: private void executeSetAttr(String[] data) {
469: final String uuid = data[0];
470: final String attr = data[1];
471: final String val = data[2];
472:
473: final ZkComponent comp = lookupUi(uuid);
474: if (comp != null) {
475: //handle Item common attributes
476: if (comp instanceof Item) {
477: if ("lb".equals(attr)) { //label
478: ((Item) comp).setLabel(val);
479: return;
480: }
481: if ("ps".equals(attr)) { //preferredSize
482: int[] sz = UiManager.parseSize(val);
483: ((Item) comp).setPreferredSize(sz[0], sz[1]);
484: return;
485: }
486: if ("lo".equals(attr)) { //layout
487: final int layout = Integer.parseInt(val);
488: ((Item) comp).setLayout(layout);
489: return;
490: }
491: }
492: comp.setAttr(attr, val);
493: }
494: }
495:
496: //alert command
497: private void executeAlert(String[] data) {
498: //TODO: exception alert can add a local image icon.
499: final Alert alert = new Alert("Exception", data[0], null,
500: AlertType.ERROR);
501: alert.setTimeout(Alert.FOREVER); //15 seconds
502: _browser.getDisplay().setCurrent(alert);
503: }
504:
505: //rm command
506: private void executeRm(String[] data) {
507: final String uuid = data[0];
508:
509: final ZkComponent comp = lookupUi(uuid);
510: if (comp instanceof ZkListItem) {
511: final Listable owner = (Listable) comp.getParent();
512: final int index = owner.indexOf(comp);
513: owner.delete(index);
514: } else if (comp instanceof Itemable) {
515: final ZkForm form = (ZkForm) ((Itemable) comp).getForm();
516: form.removeItem((Item) comp);
517: } else if (comp instanceof ZkCommand) {
518: final ZkComponent parent = comp.getParent();
519: if (parent instanceof Item) {
520: ((Item) parent).removeCommand((Command) comp);
521: } else { //Displayable
522: ((Displayable) parent).removeCommand((Command) comp);
523: }
524: } else if (comp instanceof Displayable) {
525: final Displayable disp = (Displayable) comp;
526: if (_browser.getDisplay().getCurrent() == disp) { //the current showing display
527: _current = null;
528: _browser.getDisplay().setCurrent(new Form(null)); //blank Form
529: }
530: _displayables.removeElement(comp);
531: }
532:
533: unregisterUi(uuid);
534: }
535:
536: //addAft command
537: private void executeAddAft(String[] data) {
538: executeAdd(data, 1);
539: }
540:
541: private void executeAddBfr(String[] data) {
542: executeAdd(data, 0);
543: }
544:
545: //after: 0 means add before
546: //after: 1 means add after
547: private void executeAdd(String[] data, int after) {
548: final String uuid = data[0];
549: final String rmil = data[1].trim();
550: final ZkComponent ref = lookupUi(uuid);
551: executeAdd(rmil, ref, after);
552: }
553:
554: private void executeAdd(String rmil, ZkComponent ref, int after) {
555: //load the rmil and generate a set of components
556: final Vector comps = createComponents(this , rmil);
557:
558: if (ref instanceof ZkListItem || ref instanceof ZkCommand) {
559: final Listable owner = (Listable) ref.getParent();
560: final int index = owner.indexOf(ref) + after;
561: if (index == owner.size()) { //append to the last one
562: for (Enumeration it = comps.elements(); it
563: .hasMoreElements();) {
564: final Object kid = it.nextElement();
565: ((ZkComponent) kid).setParent((ZkComponent) owner);
566: }
567: } else {
568: int k = 0;
569: for (Enumeration it = comps.elements(); it
570: .hasMoreElements(); ++k) {
571: final Object kid = it.nextElement();
572: ((ZkComponent) kid).setParent((ZkComponent) owner);
573: }
574: }
575: } else if (ref instanceof Item) {
576: final ZkForm form = (ZkForm) ((Itemable) ref).getForm();
577: final int index = form.indexOf((Item) ref) + after;
578: if (index == form.size()) { //append to the last one
579: for (Enumeration it = comps.elements(); it
580: .hasMoreElements();) {
581: final ZkComponent comp = (ZkComponent) it
582: .nextElement();
583: form.appendChild(comp);
584: }
585: } else {
586: int k = 0;
587: for (Enumeration it = comps.elements(); it
588: .hasMoreElements(); ++k) {
589: final ZkComponent kid = (ZkComponent) it
590: .nextElement();
591: form.insertChild(index + k, kid);
592: }
593: }
594: } else if (ref instanceof Displayable) {
595: final Displayable disp = (Displayable) ref;
596: final int index = _displayables.indexOf(disp) + after;
597: if (index == _displayables.size()) { //append
598: for (Enumeration it = comps.elements(); it
599: .hasMoreElements();) {
600: final Displayable comp = (Displayable) it
601: .nextElement();
602: addDisplayable(comp);
603: }
604: } else {
605: int k = 0;
606: for (Enumeration it = comps.elements(); it
607: .hasMoreElements(); ++k) {
608: final Displayable comp = (Displayable) it
609: .nextElement();
610: _displayables.insertElementAt(comp, index + k);
611: }
612: }
613: }
614: }
615:
616: private Vector createComponents(ZkComponent parent, String rmil) {
617: InputStream is = null;
618: try {
619: is = new ByteArrayInputStream(rmil.getBytes("UTF-8"));
620: } catch (UnsupportedEncodingException e) {
621: UiManager.alert(_browser.getDisplay(), e);
622: }
623: Vector comps = null;
624: try {
625: comps = UiManager.createComponents(parent, is, _hostURL,
626: _pathURL);
627: } catch (IOException e) {
628: // ignore
629: } catch (SAXException e) {
630: throw new IllegalArgumentException("add component failed: "
631: + e.toString());
632: } catch (RuntimeException e) {
633: e.printStackTrace();
634: UiManager.alert(_browser.getDisplay(), e);//load the rmil and generate a set of components
635: }
636: return comps;
637: }
638:
639: /** Does the HTTP encoding for the URI location.
640: * For example, '%' is translated to '%25'.
641: *
642: * @param s the string to encode; null is OK
643: * @return the encoded string or null if s is null
644: * @see #encodeURIComponent
645: */
646: public static final String encodeURI(String s) {
647: try {
648: return encodeURI0(s, URI_UNSAFE);
649: } catch (UnsupportedEncodingException e) {
650: // TODO Auto-generated catch block
651: e.printStackTrace();
652: }
653: return s;
654: }
655:
656: /** Does the HTTP encoding for a URI query parameter.
657: * For example, '/' is translated to '%2F'.
658: * Both name and value must be encoded seperately. Example,
659: * <code>encodeURIComponent(name) + '=' + encodeURIComponent(value)</code>.
660: *
661: * @param s the string to encode; null is OK
662: * @return the encoded string or null if s is null
663: * @see #addToQueryString(StringBuffer,String,Object)
664: * @see #encodeURI
665: */
666: public static final String encodeURIComponent(String s) {
667: try {
668: return encodeURI0(s, URI_COMP_UNSAFE);
669: } catch (UnsupportedEncodingException e) {
670: // TODO Auto-generated catch block
671: e.printStackTrace();
672: }
673: return s;
674: }
675:
676: /** Encodes a string to HTTP URI compliant by use of
677: * {@link Charsets#getURICharset}.
678: *
679: * <p>Besides two-byte characters, it also encodes any character found
680: * in unsafes.
681: *
682: * @param unsafes the set of characters that must be encoded; never null.
683: * It must be sorted.
684: */
685: private static final String encodeURI0(String s, String unsafes)
686: throws UnsupportedEncodingException {
687: if (s == null)
688: return null;
689:
690: final String charset = "UTF-8";
691: final byte[] in = s.getBytes(charset);
692: final byte[] out = new byte[in.length * 3];//at most: %xx
693: int j = 0, k = 0;
694: for (; j < in.length; ++j) {
695: //Though it is ok to use '+' for ' ', Jetty has problem to
696: //handle space between chinese characters.
697: final char cc = (char) (((int) in[j]) & 0xff);
698: if (cc >= 0x80 || cc <= ' ' || unsafes.indexOf(cc) >= 0) {
699: out[k++] = (byte) '%';
700: String cvt = Integer.toHexString(cc);
701: if (cvt.length() == 1) {
702: out[k++] = (byte) '0';
703: out[k++] = (byte) cvt.charAt(0);
704: } else {
705: out[k++] = (byte) cvt.charAt(0);
706: out[k++] = (byte) cvt.charAt(1);
707: }
708: } else {
709: out[k++] = in[j];
710: }
711: }
712: return j == k ? s : new String(out, 0, k, charset);
713: }
714:
715: /** unsafe character when that are used in url's localtion. */
716: private static final String URI_UNSAFE = "`%^{}[]\\\"<>|";
717: /** unsafe character when that are used in url's query. */
718: private static final String URI_COMP_UNSAFE = "`%^{}[]\\\"<>|$&,/:;=?";
719: }
|