001: /*******************************************************************************
002: * Copyright (c) 2007 MyGWT.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *******************************************************************************/package net.mygwt.ui.client.viewer;
008:
009: import java.util.ArrayList;
010: import java.util.HashMap;
011: import java.util.Iterator;
012: import java.util.List;
013: import java.util.Map;
014:
015: import net.mygwt.ui.client.Events;
016: import net.mygwt.ui.client.data.DataLoader;
017: import net.mygwt.ui.client.data.LoadEvent;
018: import net.mygwt.ui.client.data.Loader;
019: import net.mygwt.ui.client.event.BaseEvent;
020: import net.mygwt.ui.client.event.Listener;
021: import net.mygwt.ui.client.widget.Component;
022: import net.mygwt.ui.client.widget.LoadingPanel;
023: import net.mygwt.ui.client.widget.table.TableColumn;
024: import net.mygwt.ui.client.widget.tree.Tree;
025: import net.mygwt.ui.client.widget.tree.TreeItem;
026: import net.mygwt.ui.client.widget.treetable.TreeTable;
027: import net.mygwt.ui.client.widget.treetable.TreeTableItem;
028:
029: import com.google.gwt.user.client.DOM;
030: import com.google.gwt.user.client.Element;
031: import com.google.gwt.user.client.ui.Widget;
032:
033: /**
034: * An interface to content providers for treetable-structure-oriented viewers.
035: *
036: * Content providers for treetable viewers must implement either the
037: * <code>ITreeContentProvider</code> interface or the
038: * <code>IAsyncTreeContentProvider</code> interface.
039: *
040: * <p>
041: * This code is based on JFace API from the Eclipse Project.
042: * </p>
043: *
044: * @see TreeViewer
045: */
046: public class TreeTableViewer extends ColumnViewer implements
047: ICheckable, ITreeViewer {
048: protected boolean checkable;
049: private ViewerCell viewerCell = new ViewerCell();
050: private TreeTable treeTable;
051: private List checkChangeListener;
052: private boolean caching = true;
053: private Map nodeMap = new HashMap();
054:
055: private Loader loader;
056: private Listener loadListener;
057:
058: /**
059: * Creates a new treetable viewer instance.
060: *
061: * @param treeTable the underlying treetable widget
062: */
063: public TreeTableViewer(TreeTable treeTable) {
064: this .treeTable = treeTable;
065:
066: // force render
067: treeTable.getElement();
068: hookWidget(treeTable);
069:
070: Listener l = new Listener() {
071: public void handleEvent(BaseEvent be) {
072: switch (be.type) {
073: case Events.BeforeExpand: {
074: TreeItem item = (TreeItem) be.widget;
075: Object loaded = item.getData("loaded");
076: if (loaded == null) {
077: be.doit = false;
078: loadChildren(item, true);
079: }
080: break;
081: }
082: case Events.Collapse: {
083: if (!caching) {
084: TreeItem item = (TreeItem) be.widget;
085: int count = item.getItemCount();
086: for (int i = 0; i < count; i++) {
087: item.remove(item.getItem(0));
088: }
089: item.setData("loaded", null);
090: }
091: break;
092: }
093:
094: }
095: }
096: };
097:
098: treeTable.addListener(Events.BeforeExpand, l);
099: treeTable.addListener(Events.Collapse, l);
100: }
101:
102: /**
103: * Adds the given child element to this viewer as a child of the given parent
104: * element.
105: *
106: * @param parent the parent element
107: * @param child the child element
108: */
109: public void add(Object parent, Object child) {
110: TreeItem p = (TreeItem) findItem(parent);
111: internalAdd(p, child, p.getItemCount());
112: }
113:
114: public void addCheckStateListener(ICheckStateListener listener) {
115: if (checkChangeListener == null) {
116: checkChangeListener = new ArrayList();
117: }
118: if (!checkChangeListener.contains(listener)) {
119: checkChangeListener.add(listener);
120: }
121: }
122:
123: /**
124: * Applies the viewer's filters to the current elements.
125: */
126: public void applyFilters() {
127: filterItems(treeTable.getRootItem());
128: }
129:
130: public Widget findItem(Object elem) {
131: TreeItem[] items = treeTable.getAllItems();
132: for (int i = 0; i < items.length; i++) {
133: TreeItem item = items[i];
134: Object data = item.getData();
135:
136: if (getComparer() != null) {
137: if (getComparer().equals(elem, data)) {
138: return item;
139: } else {
140: continue;
141: }
142: }
143:
144: if (elem == data || (elem != null && elem.equals(data))) {
145: return item;
146: }
147: }
148: return null;
149: }
150:
151: /**
152: * Returns <code>true</code> if the viewer is caching.
153: *
154: * @return the caching state
155: */
156: public boolean getCaching() {
157: return caching;
158: }
159:
160: public boolean getChecked(Object element) {
161: TreeItem item = (TreeItem) findItem(element);
162:
163: if (item != null) {
164: return item.isChecked();
165: }
166:
167: return false;
168: }
169:
170: /**
171: * Returns the viewer's tree widget.
172: *
173: * @return the tree
174: */
175: public Tree getTree() {
176: return treeTable;
177: }
178:
179: /**
180: * Returns the underlying treetable widget
181: *
182: * @return the treetable
183: */
184: public TreeTable getTreeTable() {
185: return treeTable;
186: }
187:
188: /**
189: * Returns the table viewer column for the specified column.
190: *
191: * @param columnId the column id
192: * @return the table viewer column
193: */
194: public TreeTableViewerColumn getViewerColumn(int columnId) {
195: TableColumn column = getTreeTable().getColumnModel().getColumn(
196: columnId);
197: TreeTableViewerColumn vc = (TreeTableViewerColumn) column
198: .getData(ViewerColumn.COLUMN_VIEWER_KEY);
199: if (vc == null) {
200: vc = new TreeTableViewerColumn(this , column);
201: }
202: return vc;
203: }
204:
205: public Widget getWidget() {
206: return treeTable;
207: }
208:
209: /**
210: * Inserts the given element as a new child element of the given parent
211: * element at the given position.
212: *
213: * @param parent the parent element
214: * @param child the child element
215: * @param position the insert position
216: */
217: public void insert(Object parent, Object child, int position) {
218: TreeItem p = (TreeItem) findItem(parent);
219: internalAdd(p, child, position);
220: }
221:
222: /**
223: * Refreshes this viewer starting with the given element.
224: *
225: * @param elem the element
226: */
227: public void refresh(Object elem) {
228: TreeItem item = (TreeItem) findItem(elem);
229: if (item != null) {
230: int count = item.getItemCount();
231: for (int i = 0; i < count; i++) {
232: item.remove(item.getItem(0));
233: }
234: item.setData("loaded", null);
235: loadChildren(item, item.isExpanded());
236: }
237: }
238:
239: /**
240: * Removes the given element from the viewer.
241: *
242: * @param element the element to be removed
243: */
244: public void remove(Object element) {
245: TreeItem item = (TreeItem) findItem(element);
246: if (item != null) {
247: TreeItem parent = item.getParentItem();
248: parent.remove(item);
249: removeElement(item.getData());
250: nodeMap.remove(element);
251: item.setData(null);
252: }
253: }
254:
255: public void removeCheckStateListener(ICheckStateListener listener) {
256: if (checkChangeListener != null) {
257: checkChangeListener.remove(listener);
258: }
259: }
260:
261: /**
262: * Selects the elements.
263: *
264: * @param elem the element to be selected
265: */
266: public void select(Object elem) {
267: TreeItem item = (TreeItem) findItem(elem);
268: treeTable.getSelectionModel().select(item);
269: }
270:
271: /**
272: * Sets whether the children should be cached after first being retrieved from
273: * the content provider. When <code>false</code>, the tree items will be
274: * removed when collapsed.Default value is <code>true</code>.
275: *
276: * @param caching
277: */
278: public void setCaching(boolean caching) {
279: this .caching = caching;
280: }
281:
282: public boolean setChecked(Object element, boolean state) {
283: TreeItem item = (TreeItem) findItem(element);
284:
285: if (item != null) {
286: item.setChecked(state);
287: return true;
288: }
289:
290: return false;
291: }
292:
293: public void setContentProvider(IContentProvider contentProvider) {
294: super .setContentProvider(contentProvider);
295: if (contentProvider instanceof ICheckable) {
296: checkable = true;
297: }
298: if (contentProvider instanceof Loader) {
299: bind((Loader) contentProvider);
300: }
301: }
302:
303: /**
304: * Sets the expanded state for the element.
305: *
306: * @param element the element
307: * @param expanded the expand state
308: */
309: public void setExpanded(Object element, boolean expanded) {
310: TreeItem item = (TreeItem) findItem(element);
311: if (item != null) {
312: item.setExpanded(expanded);
313: }
314: }
315:
316: public void setInput(final Object input) {
317: ITreeContentProvider cp = (ITreeContentProvider) getContentProvider();
318: cp.inputChanged(this , this .input, input);
319: this .input = input;
320: cp.getChildren(input, new IAsyncContentCallback() {
321: public void setElements(Object[] elems) {
322: elements = elems;
323: onInputReceived(input);
324: }
325: });
326: }
327:
328: public void setSelection(ISelection selection, boolean reveal) {
329: List selected = selection.toList();
330: treeTable.getSelectionModel().deselectAll();
331: TreeItem[] items = treeTable.getAllItems();
332: for (int i = 0; i < items.length; i++) {
333: TreeItem item = items[i];
334: Object elem = item.getData();
335: if (selected.contains(elem)) {
336: treeTable.getSelectionModel().select(item);
337: }
338: }
339: }
340:
341: public void update() {
342: TreeItem[] items = treeTable.getAllItems();
343: for (int i = 0; i < items.length; i++) {
344: TreeTableItem item = (TreeTableItem) items[i];
345: updateInternal(item);
346: }
347: }
348:
349: public void update(Object elem) {
350: TreeTableItem item = (TreeTableItem) findItem(elem);
351: if (item != null) {
352: item.setData(elem);
353: updateInternal(item);
354: }
355: }
356:
357: protected void add(Object elem) {
358:
359: }
360:
361: protected List getSelectedFromWidget() {
362: ArrayList elems = new ArrayList();
363: TreeItem[] items = treeTable.getSelection();
364: for (int i = 0; i < items.length; i++) {
365: TreeItem item = items[i];
366: elems.add(item.getData());
367: }
368: return elems;
369: }
370:
371: protected void hookWidget(Component widget) {
372: super .hookWidget(widget);
373: widget.addListener(Events.CheckChange, new Listener() {
374:
375: public void handleEvent(BaseEvent be) {
376: fireCheckStateChanged(be);
377: }
378: });
379: }
380:
381: protected void insert(Object elem, int index) {
382: // do nothing
383: }
384:
385: protected void onInputReceived(Object input) {
386: renderTree();
387: }
388:
389: protected void renderItem(TreeItem parent, Object elem, int position) {
390: boolean hasChildren = false;
391:
392: hasChildren = ((ITreeContentProvider) getContentProvider())
393: .hasChildren(elem);
394:
395: int cols = getTreeTable().getColumnCount();
396: String[] values = new String[cols];
397: String[] toolTips = new String[cols];
398: String[] styles = new String[cols];
399:
400: String iconStyle = null;
401:
402: for (int j = 0; j < cols; j++) {
403: CellLabelProvider lp = (CellLabelProvider) getViewerColumn(
404: j).getLabelProvider();
405: if (lp != null) {
406: viewerCell.reset(elem, null, j, getTreeTable()
407: .getColumn(j).getID());
408: lp.update(viewerCell);
409: values[j] = viewerCell.getText();
410: toolTips[j] = viewerCell.getToolTipText();
411: styles[j] = viewerCell.getTextStyle();
412: if (j == 0 && viewerCell.getIconStyle() != null) {
413: iconStyle = viewerCell.getIconStyle();
414: }
415: }
416: }
417:
418: LabelProvider lp = (LabelProvider) getLabelProvider();
419: if (lp != null) {
420: values[0] = lp.getText(elem);
421: if (lp.getIconStyle(elem) != null) {
422: iconStyle = lp.getIconStyle(elem);
423: }
424: }
425:
426: TreeTableItem item = new TreeTableItem(values);
427: item.setData(elem);
428: item.setText(values[0]);
429: item.setCellToolTips(toolTips);
430: item.setIconStyle(iconStyle);
431: item.setLeaf(!hasChildren);
432:
433: nodeMap.put(elem, item);
434:
435: if (checkable) {
436: item.setChecked(((ICheckable) getContentProvider())
437: .getChecked(elem));
438: }
439:
440: if (position == -1) {
441: parent.add(item);
442: } else {
443: parent.insert(item, position);
444: }
445:
446: for (int i = 0; i < styles.length; i++) {
447: if (styles[i] != null) {
448: item.setCellStyle(i, styles[i]);
449: }
450: }
451:
452: }
453:
454: private Object[] filterChildren(Object parent, Object[] elems) {
455: List temp = new ArrayList();
456: for (int i = 0; i < elems.length; i++) {
457: if (isOrDecendantSelected(elems[i])) {
458: temp.add(elems[i]);
459: }
460: }
461: return temp.toArray();
462: }
463:
464: private void filterItems(TreeItem item) {
465: if (item.isRoot() || isOrDecendantSelected(item.getData())) {
466: item.setVisible(true);
467: int count = item.getItemCount();
468: for (int i = 0; i < count; i++) {
469: filterItems(item.getItem(i));
470: }
471: } else {
472: item.setVisible(false);
473: }
474: }
475:
476: private void fireCheckStateChanged(BaseEvent be) {
477: if (checkChangeListener != null) {
478: TreeItem item = (TreeItem) be.widget;
479:
480: CheckStateChangedEvent evt = new CheckStateChangedEvent(
481: this , item.getData(), item.isChecked());
482: Iterator it = checkChangeListener.iterator();
483:
484: while (it.hasNext()) {
485: ((ICheckStateListener) it.next())
486: .checkStateChanged(evt);
487: }
488: }
489: }
490:
491: private void internalAdd(TreeItem parent, Object elem, int position) {
492: // if the children have not been loaded then do nothing
493: if (parent.getData("loaded") != null) {
494: if (!isFiltered(parent, elem)) {
495: renderItem(parent, elem, position);
496: if (getSorter() != null) {
497: sortChildren(parent);
498: }
499: }
500: }
501: }
502:
503: /**
504: * Returns <code>true</code> if the element or any child elements is not
505: * filtered.
506: *
507: * @param elem the element
508: * @return the select state
509: */
510:
511: private boolean isOrDecendantSelected(Object elem) {
512: if (!isFiltered(null, elem)) {
513: return true;
514: }
515:
516: TreeItem item = (TreeItem) nodeMap.get(elem);
517: if (item != null) {
518: int count = item.getItemCount();
519: for (int i = 0; i < count; i++) {
520: TreeItem child = item.getItem(i);
521: boolean result = isOrDecendantSelected(child.getData());
522: if (result) {
523: return true;
524: }
525: }
526: }
527: return false;
528: }
529:
530: private void loadChildren(final TreeItem item, boolean expand) {
531:
532: ITreeContentProvider cp = (ITreeContentProvider) getContentProvider();
533:
534: // if there is an async call out for my children already, I want to make
535: // sure that I don't make another call and load the same items twice
536: if (!item.isEnabled()) {
537: return;
538: }
539: item.setEnabled(false);
540:
541: item.getUI().onLoadingChange(true);
542: cp.getChildren(item.getData(), new IAsyncContentCallback() {
543:
544: public void setElements(Object[] children) {
545: if (children == null) {
546: item.setData("state", null);
547: item.getUI().onLoadingChange(false);
548: return;
549: }
550: item.getUI().onLoadingChange(false);
551: item.setEnabled(true);
552: children = filterChildren(item, children);
553: sortElements(children);
554: for (int i = 0; i < children.length; i++) {
555: renderItem(item, children[i], -1);
556: }
557: item.setData("loaded", "true");
558: if (item.hasChildren()) {
559: item.setExpanded(true);
560: } else {
561: item.setLeaf(true);
562: item.getUI().updateJoint();
563: }
564:
565: }
566: });
567:
568: }
569:
570: private void renderTree() {
571: TreeItem root = treeTable.getRootItem();
572: root.setData(input);
573:
574: int count = root.getItemCount();
575: for (int i = 0; i < count; i++) {
576: root.remove(root.getItem(0));
577: }
578:
579: Object[] elems = elements;
580: elems = sortElements(elems);
581: for (int i = 0; i < elems.length; i++) {
582: Object elem = elems[i];
583: if (getFilters().length > 0) {
584: if (isOrDecendantSelected(elem)) {
585: renderItem(root, elem, -1);
586: }
587: } else {
588: renderItem(root, elem, -1);
589: }
590: }
591: }
592:
593: private void sortChildren(TreeItem parent) {
594: Object[] elems = new Object[parent.getItemCount()];
595: Element p = parent.getContainer();
596: for (int i = 0; i < elems.length; i++) {
597: TreeItem item = parent.getItem(i);
598: DOM.removeChild(p, item.getElement());
599: elems[i] = item.getData();
600: }
601: sortElements(elems);
602:
603: for (int i = 0; i < elems.length; i++) {
604: TreeItem item = (TreeItem) findItem(elems[i]);
605: Element elem = item.getElement();
606: DOM.appendChild(p, elem);
607: }
608:
609: }
610:
611: private void updateInternal(TreeTableItem item) {
612: Object elem = item.getData();
613:
614: String iconStyle = null;
615: String text = null;
616:
617: int cols = treeTable.getColumnCount();
618: for (int j = 0; j < cols; j++) {
619: viewerCell.reset(elem, item, j, getTreeTable().getColumn(j)
620: .getID());
621: CellLabelProvider clp = (CellLabelProvider) getViewerColumn(
622: j).getLabelProvider();
623: if (clp != null) {
624: clp.update(viewerCell);
625: item.setValue(j, viewerCell.getText());
626: item.setCellToolTip(j, viewerCell.getToolTipText());
627: String style = viewerCell.getTextStyle();
628: if (style != null) {
629: item.setCellStyle(j, style);
630: }
631: if (j == 0) {
632: text = viewerCell.getText();
633: iconStyle = viewerCell.getIconStyle();
634: }
635: }
636: }
637:
638: LabelProvider lp = (LabelProvider) getLabelProvider();
639: if (lp != null) {
640: text = lp.getText(elem);
641: if (lp.getIconStyle(elem) != null) {
642: iconStyle = lp.getIconStyle(elem);
643: }
644: }
645: item.setText(text);
646: item.setIconStyle(iconStyle);
647: }
648:
649: private void onBeforeLoad(LoadEvent be) {
650: LoadingPanel.get().show(treeTable);
651: }
652:
653: private void onLoad(LoadEvent de) {
654: ISelection sel = getSelection();
655: // apply sort if sorting locally
656: if (!de.loader.getRemoteSort()) {
657: if (de.loader.getSortField() != null) {
658: String field = de.loader.getSortField();
659: TableColumn column = treeTable.getColumn(field);
660: if (column != null) {
661: // int index = treeTable.getColumnModel().indexOf(column);
662: // TODO doLocalSort(index);
663: }
664: }
665: }
666: setInput(de.result.getData());
667: preserveSelections(sel);
668: LoadingPanel.get().hide();
669: }
670:
671: private void onLoadException(LoadEvent le) {
672: LoadingPanel.get().hide();
673: }
674:
675: private void bind(Loader loader) {
676: if (loadListener == null) {
677: loadListener = new Listener() {
678: public void handleEvent(BaseEvent be) {
679: LoadEvent le = (LoadEvent) be;
680: switch (be.type) {
681: case Loader.BeforeLoad:
682: onBeforeLoad(le);
683: break;
684: case DataLoader.Load:
685: onLoad(le);
686: break;
687: case DataLoader.LoadException:
688: onLoadException(le);
689: break;
690: }
691: }
692: };
693: }
694: if (loader != null) {
695: loader.removeListener(DataLoader.BeforeLoad, loadListener);
696: loader.removeListener(DataLoader.Load, loadListener);
697: loader.removeListener(DataLoader.LoadException,
698: loadListener);
699: }
700: this.loader = loader;
701: this.loader.addListener(DataLoader.BeforeLoad, loadListener);
702: this.loader.addListener(DataLoader.Load, loadListener);
703: this.loader.addListener(DataLoader.LoadException, loadListener);
704: }
705: }
|