001: package com.xoetrope.swing;
002:
003: import com.xoetrope.swing.table.XAltRowTableCellRenderer;
004: import com.xoetrope.swing.table.XColorTableCellRenderer;
005: import com.xoetrope.swing.table.XTableHeaderRenderer;
006: import com.xoetrope.swing.treetable.AbstractTreeTableModel;
007: import com.xoetrope.swing.treetable.TreeTableModel;
008: import com.xoetrope.swing.treetable.TreeTableModelAdapter;
009: import com.xoetrope.swing.treetable.XTreeTableModelNode;
010: import com.xoetrope.swing.table.XFormattedTableCellRenderer;
011: import java.awt.Color;
012:
013: import java.awt.Dimension;
014: import java.awt.Component;
015: import java.awt.Container;
016: import java.awt.Graphics;
017: import java.awt.Point;
018: import java.awt.Rectangle;
019:
020: import java.awt.event.MouseEvent;
021:
022: import java.text.Format;
023: import java.util.EventObject;
024: import java.util.Hashtable;
025:
026: import javax.swing.ImageIcon;
027: import javax.swing.JScrollPane;
028: import javax.swing.JTable;
029: import javax.swing.JTree;
030: import javax.swing.JViewport;
031: import javax.swing.ListSelectionModel;
032: import javax.swing.LookAndFeel;
033: import javax.swing.SwingConstants;
034: import javax.swing.SwingUtilities;
035: import javax.swing.UIManager;
036: import javax.swing.border.EmptyBorder;
037: import javax.swing.event.ListSelectionEvent;
038: import javax.swing.event.ListSelectionListener;
039: import javax.swing.event.TableModelEvent;
040: import javax.swing.table.DefaultTableColumnModel;
041: import javax.swing.table.JTableHeader;
042: import javax.swing.table.TableCellEditor;
043: import javax.swing.table.TableCellRenderer;
044: import javax.swing.table.TableColumn;
045: import javax.swing.table.TableColumnModel;
046: import javax.swing.table.TableModel;
047: import javax.swing.tree.DefaultTreeCellRenderer;
048: import javax.swing.tree.DefaultTreeSelectionModel;
049: import javax.swing.tree.TreeCellRenderer;
050: import javax.swing.tree.TreeModel;
051: import javax.swing.tree.TreePath;
052:
053: import net.xoetrope.xui.XAttributedComponent;
054: import net.xoetrope.xui.XModelHolder;
055: import net.xoetrope.xui.XProject;
056: import net.xoetrope.xui.XProjectManager;
057: import net.xoetrope.xui.data.XModel;
058: import net.xoetrope.xui.data.XRowSelector;
059: import net.xoetrope.xui.events.XHandlerInvoker;
060: import net.xoetrope.xui.events.XListenerHelper;
061: import net.xoetrope.xui.helper.XuiUtilities;
062: import net.xoetrope.xui.style.XStyle;
063: import net.xoetrope.xui.style.XStyleComponent;
064: import net.xoetrope.xui.style.XStyleManager;
065:
066: /**
067: * This example shows how to create a simple XTreeTable component,
068: * by using a JTree as a renderer (and editor) for the cells in a
069: * particular column in the JTable.
070: *
071: * <p> Copyright (c) Xoetrope Ltd., 2001-2006, This software is licensed under
072: * the GNU Public License (GPL), please see license.txt for more details. If
073: * you make commercial use of this software you must purchase a commercial
074: * license from Xoetrope.</p>
075: * <p> $Revision: 1.22 $</p>
076: */
077: public class XTreeTable extends JTable implements XModelHolder,
078: XRowSelector, XAttributedComponent, XStyleComponent,
079: XListenerHelper {
080: protected XModel xmodel;
081: protected AbstractTreeTableModel model;
082:
083: protected boolean updateModelSelection = false;
084: protected XHandlerInvoker invoker;
085:
086: /** A subclass of JTree. */
087: protected TreeTableCellRenderer tree;
088: private XProject currentProject;
089: protected Hashtable renderers;
090:
091: protected Color altUnselectedForeground;
092: protected Color altUnselectedBackground;
093:
094: private ListToTreeSelectionModelWrapper selectionWrapper;
095: private int percentage;
096: private int selectedRow;
097:
098: private String headingStyle, selectionStyle;
099:
100: /**
101: * Create a new XTreeTable
102: */
103: public XTreeTable() {
104: super ();
105: currentProject = XProjectManager.getCurrentProject();
106: renderers = new Hashtable();
107: }
108:
109: public int getSelectedRow() {
110: int r = super .getSelectedRow();
111: return selectedRow = r;
112: }
113:
114: /**
115: * Gets a field value object from the currently selected row
116: * @param fieldIdx the field offset
117: * @return the value
118: */
119: public Object getValue(int fieldIdx) {
120: int row = getSelectedRow();
121: return getValue(row, fieldIdx);
122: }
123:
124: /**
125: * Gets a field value object from the specified row
126: * @param rowIdx the row offset
127: * @param fieldIdx the field offset
128: * @return the value
129: */
130: public Object getValue(int rowIdx, int fieldIdx) {
131: TreePath treePath = tree.getPathForRow(rowIdx);
132: Object node = treePath.getLastPathComponent();
133: return model.getValueAt(node, fieldIdx);
134: }
135:
136: /**
137: * Gets a field value as a string from the currently selected row
138: * @param fieldIdx the field offset
139: * @return the value
140: */
141: public String getFieldValue(int fieldIdx) {
142: return (String) getValue(fieldIdx);
143:
144: }
145:
146: /**
147: * Gets a field value as a string from the specified row
148: * @param rowIdx the row offset
149: * @param fieldIdx the field offset
150: * @return the value
151: */
152: public String getFieldValue(int rowIdx, int fieldIdx) {
153: return (String) getValue(rowIdx, fieldIdx);
154: }
155:
156: /**
157: * Update the table's model
158: */
159: public void update() {
160: if (dataModel != null) {
161: /** @todo add some checks to ensure it is correct to restore the row selection in all cases */
162: getSelectedRow();
163: updateTable();
164: SwingUtilities.invokeLater(new Runnable() {
165: public void run() {
166: if (selectedRow >= 0)
167: setSelection(selectedRow);
168: }
169: });
170: }
171: }
172:
173: /**
174: * Force the table to update itself
175: */
176: public void updateTable() {
177: if (model != null)
178: setModel(((XTreeTableModelNode.XNode) model.getRoot())
179: .getModel());
180: tableChanged(new TableModelEvent(dataModel));
181: }
182:
183: public void setModel(XModel _xmodel) {
184: setModel(_xmodel, null);
185: }
186:
187: /**
188: * Set the XModel which we will be generating the table from
189: * @param _xmodel The XModel of data
190: */
191: public void setModel(XModel _xmodel, TableModel delegModel) {
192: xmodel = _xmodel;
193: if (xmodel != null) {
194: xmodel.get();
195:
196: // model = new XTreeTableModelNode( xmodel );
197: if (delegModel != null)
198: model = new XTreeTableModelNode(xmodel, delegModel);
199: else
200: model = new XTreeTableModelNode(xmodel);
201:
202: // Create the tree. It will be used as a renderer and editor.
203: tree = new TreeTableCellRenderer(model);
204: tree.setTreeIcons(null, null, null);
205: tree.setRootVisible(false);
206: tree.setShowsRootHandles(true);
207: tree.setAutoscrolls(true);
208: tree.setScrollsOnExpand(true);
209:
210: // Install a tableModel representing the visible rows in the tree.
211: super .setModel(new TreeTableModelAdapter(model, tree));
212:
213: // Force the JTable and JTree to share their row selection models.
214: if (selectionWrapper == null)
215: selectionWrapper = new ListToTreeSelectionModelWrapper();
216: tree.setSelectionModel(selectionWrapper);
217: setSelectionModel(selectionWrapper.getListSelectionModel());
218:
219: // Install the tree editor renderer and editor.
220: setDefaultRenderer(TreeTableModel.class, tree);
221: setDefaultEditor(TreeTableModel.class,
222: new TreeTableCellEditor());
223:
224: // No grid.
225: setShowGrid(false);
226: setHeaderRenderer();
227:
228: // No intercell spacing
229: setIntercellSpacing(new Dimension(0, 0));
230:
231: // And update the height of the trees row to match that of
232: // the table.
233: if (tree.getRowHeight() < 1)
234: setRowHeight(18);// Metal looks better like this.
235:
236: reload(model.getRoot());
237: }
238: }
239:
240: public void reload() {
241: reload(model.getRoot());
242: }
243:
244: /**
245: * Set the column width. Sets the maximum width as a workaround for a Swing problem
246: * @param columnIdx the column index
247: * @param width the new width
248: */
249: public void setColumnWidth(int columnIdx, int width) {
250: if (isShowing() && isVisible()) {
251: DefaultTableColumnModel colModel = (DefaultTableColumnModel) getColumnModel();
252: TableColumn column = colModel.getColumn(columnIdx);
253: column.setMaxWidth(width);
254: } else {
255: try {
256: final int w = width;
257: final int col = columnIdx;
258: SwingUtilities.invokeLater(new Runnable() {
259: public void run() {
260: setColumnWidth(col, w);
261: }
262: });
263: } catch (Exception e) {
264: }
265: }
266: }
267:
268: /**
269: * Set the colors for alternate ( odd ) row colors
270: * @param frgd the foreground color
271: * @param bkgd the background color
272: */
273: public void setAltUnselectedColors(Color frgd, Color bkgd) {
274: altUnselectedForeground = frgd;
275: altUnselectedBackground = bkgd;
276: }
277:
278: /**
279: * Get a render for this cell
280: */
281: public TableCellRenderer getCellRenderer(int row, int column) {
282: TableCellRenderer renderer = (TableCellRenderer) renderers
283: .get("" + column);
284: if (renderer != null)
285: return renderer;
286: else if (altUnselectedBackground != null) {
287: if (column > 0) {
288: XAltRowTableCellRenderer altRenderer = new XAltRowTableCellRenderer();
289: altRenderer.setAltUnselectedColors(
290: altUnselectedForeground,
291: altUnselectedBackground);
292: renderers.put("" + column, altRenderer);
293: return altRenderer;
294: }
295: }
296: return super .getCellRenderer(row, column);
297: }
298:
299: /**
300: * Set the enabled state
301: * @param b true to enable
302: */
303: public void setEnabled(boolean b) {
304: super .setEnabled(b);
305: getTableHeader().repaint();
306: repaint();
307: }
308:
309: /**
310: * Set the alignment of a column
311: * @param column the column index
312: * @param alignment the SwingContants aliignment constant
313: */
314: public void setAlignment(int column, int alignment) {
315: TableCellRenderer tr = getCellRenderer(0, column);
316: if (tr instanceof XAltRowTableCellRenderer)
317: ((XAltRowTableCellRenderer) tr)
318: .setHorizontalAlignment(alignment);
319: }
320:
321: /**
322: * Display the field as a numeric value
323: * @param column the column or field index
324: * @param format format the number format to use in displaying the field
325: */
326: public void displayAsNumericField(int column, Format format) {
327: XFormattedTableCellRenderer renderer = new XFormattedTableCellRenderer(
328: format);
329: renderer.setAltUnselectedColors(altUnselectedForeground,
330: altUnselectedBackground);
331: renderer.setHorizontalAlignment(SwingConstants.RIGHT);
332: renderers.put("" + column, renderer);
333: }
334:
335: /**
336: * Display the field formatted with a background color from the hashtable such
337: * that the field value acts as a key into the hashtable of colors
338: * @param column the column or field index
339: * @param colors the color hashtable
340: */
341: public void displayAsColorField(int column, Hashtable colors) {
342: XColorTableCellRenderer renderer = new XColorTableCellRenderer(
343: colors);
344: renderers.put("" + column, renderer);
345: }
346:
347: /**
348: * Add a cell renderer for the specified column
349: * @param column the column or field index
350: */
351: public void addRenderer(int column, TableCellRenderer renderer) {
352: renderers.put("" + column, renderer);
353: }
354:
355: /**
356: * Set a header renderer for thetable.
357: */
358: public void setHeaderRenderer() {
359: XTableHeaderRenderer renderer = new XTableHeaderRenderer(
360: getTableHeader());
361: TableColumnModel columnModel = getColumnModel();
362: int numColumns = columnModel.getColumnCount();
363: for (int i = 0; i < numColumns; i++)
364: columnModel.getColumn(i).setHeaderRenderer(renderer);
365: }
366:
367: /**
368: * Set one or more attributes of the component. Currently this handles the
369: * attributes
370: * <OL>
371: * <LI>headingStyle, value=the table header style</LI>
372: * <LI>selectionStyle, value=the selected row style</LI>
373: * <LI>updateModel, value=true|false update the underlying model selection (the selected row)</LI>
374: * <LI>border, 0 to turn off the border</LI>
375: * </OL>
376: * @param attribName the attribute name
377: * @param attribValue the attribute value
378: * @return 0 for success, non zero otherwise
379: */
380: public int setAttribute(String attribName, Object attribValue) {
381: String attribNameLwr = attribName.toLowerCase();
382: String attribValueStr = (String) attribValue;
383: String attribValueLwr = attribValueStr.toLowerCase();
384: if (attribNameLwr.equals("headingstyle")
385: || attribNameLwr.equals("headerstyle")) // headerstyle provided for backward compatibility
386: setHeadingStyle(attribValueStr);
387: else if (attribNameLwr.equals("selectionstyle")
388: || attribNameLwr.equals("selectedstyle")) // headerstyle provided for backward compatibility
389: setSelectionStyle(attribValueStr);
390: else if (attribNameLwr.equals("borderstyle"))
391: setBorderStyle(attribValueStr);
392: else if (attribNameLwr.equals("border")) {
393: if ("0".equals(attribValueStr))
394: setBorder(new EmptyBorder(0, 0, 0, 0));
395: } else if (attribNameLwr.equals("updatemodel"))
396: setUpdateModelSelection(attribValueLwr.equals("true"));
397: else if (attribNameLwr.equals("altstyle"))
398: setAltStyle(attribValueStr);
399:
400: return 0;
401: }
402:
403: /**
404: * Set the general style of the XTable
405: * @param style XStyle
406: */
407: public void setStyle(String style) {
408: XStyleManager styleMgr = currentProject.getStyleManager();
409: XStyle xstyle = styleMgr.getStyle(style);
410: setForeground(xstyle.getStyleAsColor(XStyle.COLOR_FORE));
411: setBackground(xstyle.getStyleAsColor(XStyle.COLOR_BACK));
412: setFont(styleMgr.getFont(xstyle));
413: }
414:
415: /**
416: * Set the style of the header data
417: * @param style XStyle
418: */
419: public void setHeadingStyle(String style) {
420: headingStyle = style;
421: XStyleManager styleMgr = currentProject.getStyleManager();
422: XStyle xstyle = styleMgr.getStyle(style);
423: JTableHeader th = getTableHeader();
424: th.setForeground(xstyle.getStyleAsColor(XStyle.COLOR_FORE));
425: th.setBackground(xstyle.getStyleAsColor(XStyle.COLOR_BACK));
426: th.setFont(styleMgr.getFont(xstyle));
427: }
428:
429: /**
430: * Set the style of the selected row
431: * @param style XStyle
432: */
433: public void setSelectionStyle(String style) {
434: selectionStyle = style;
435: XStyle xstyle = currentProject.getStyleManager()
436: .getStyle(style);
437: setSelectionForeground(xstyle
438: .getStyleAsColor(XStyle.COLOR_FORE));
439: setSelectionBackground(xstyle
440: .getStyleAsColor(XStyle.COLOR_BACK));
441: }
442:
443: /**
444: * Set the style of the alternate rows
445: * @param style XStyle
446: */
447: public void setAltStyle(String style) {
448: XStyle xstyle = currentProject.getStyleManager()
449: .getStyle(style);
450: altUnselectedForeground = xstyle
451: .getStyleAsColor(XStyle.COLOR_FORE);
452: altUnselectedBackground = xstyle
453: .getStyleAsColor(XStyle.COLOR_BACK);
454: }
455:
456: /**
457: * Set the style of the header data
458: * @return the header styles
459: */
460: public String getHeadingStyle() {
461: return headingStyle;
462: }
463:
464: /**
465: * Get the style of the selected row
466: * @return the selected style
467: */
468: public String getSelectionStyle() {
469: return selectionStyle;
470: }
471:
472: /**
473: * Set the style of the border
474: * @param style XStyle
475: */
476: public void setBorderStyle(String style) {
477: // content.setBorderStyle( style );
478: }
479:
480: /**
481: * Set the row selection index
482: * @param rowIdx the new row selection index (zero based)
483: */
484: public void setSelectedRow(int rowIdx) {
485: setSelection(rowIdx);
486: }
487:
488: /**
489: * Add an event handler response method to a component such that the page's
490: * response method is invoked when the event occurs
491: * @param page the page containing the method
492: * @param handlerType the type of event handler
493: * @param methodName the method to invoke
494: * @throws NoSuchMethodException cannot add the handler
495: */
496: public void addHandler(Object page, String handlerType,
497: String methodName) throws NoSuchMethodException {
498: invoker = new XHandlerInvoker(page, this , methodName);
499: }
500:
501: /**
502: * Tie the model selection to this table's selection
503: * @param doUpdate true to tie the selections together, false to ignore
504: */
505: public void setUpdateModelSelection(boolean doUpdate) {
506: updateModelSelection = doUpdate;
507: }
508:
509: /**
510: * Get the model selection update flag
511: * @rreturn true if the selections and model are tied together
512: */
513: public boolean getUpdateModelSelection() {
514: return updateModelSelection;
515: }
516:
517: /**
518: * Overridden to message super and forward the method to the tree.
519: * Since the tree is not actually in the component hieachy it will
520: * never receive this unless we forward it in this manner.
521: */
522: public void updateUI() {
523: super .updateUI();
524: if (tree != null)
525: tree.updateUI();
526:
527: // Use the tree's default foreground and background colors in the
528: // table.
529: LookAndFeel.installColorsAndFont(this , "Tree.background",
530: "Tree.foreground", "Tree.font");
531: }
532:
533: /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
534: * paint the editor. The UI currently uses different techniques to
535: * paint the renderers and editors and overriding setBounds() below
536: * is not the right thing to do for an editor. Returning -1 for the
537: * editing row in this case, ensures the editor is never painted.
538: */
539: public int getEditingRow() {
540: return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1
541: : editingRow;
542: }
543:
544: /**
545: * Overridden to pass the new rowHeight to the tree.
546: */
547: public void setRowHeight(int rowHeight) {
548: super .setRowHeight(rowHeight);
549: if (tree != null && tree.getRowHeight() != rowHeight)
550: tree.setRowHeight(getRowHeight());
551: }
552:
553: /**
554: * Returns the tree that is being shared between the model.
555: */
556: public JTree getTree() {
557: return tree;
558: }
559:
560: /**
561: * Select the nth visible row one level down from the parent.
562: * @param rowIdx the row index
563: */
564: public void setSelection(int rowIdx) {
565: selectedRow = rowIdx;
566: TreePath selectedPath = tree.getPathForRow(rowIdx);
567: tree.scrollPathToVisible(selectedPath);
568: tree.expandPath(selectedPath);
569: tree.setSelectionPath(selectedPath);
570: Rectangle rect = getCellRect(rowIdx, 0, true);
571: Container p = getParent();
572: if (p instanceof JViewport)
573: ((JViewport) p).setViewPosition(new Point(rect.x, Math.max(
574: 0, rect.y
575: - ((JScrollPane) p.getParent()).getHeight()
576: / 2)));
577: }
578:
579: /**
580: * Invoked to reload the children of a particular node. This will
581: * also restart the timer.
582: */
583: protected void reload(Object node) {
584: model.reloadChildren(node);
585: //reload();
586: }
587:
588: /**
589: * A TreeCellRenderer that displays a JTree.
590: */
591: public class TreeTableCellRenderer extends JTree implements
592: TableCellRenderer {
593: /** Last table/tree row asked to renderer. */
594: protected int visibleRow;
595:
596: public TreeTableCellRenderer(TreeModel model) {
597: super (model);
598: setCellRenderer(new TreeTableTreeCellRenderer());
599: }
600:
601: /**
602: * updateUI is overridden to set the colors of the Tree's renderer
603: * to match that of the table.
604: */
605: public void updateUI() {
606: super .updateUI();
607: // Make the tree's cell renderer use the table's cell selection
608: // colors.
609: TreeCellRenderer tcr = getCellRenderer();
610: if (tcr instanceof DefaultTreeCellRenderer) {
611: DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr);
612: // For 1.1 uncomment this, 1.2 has a bug that will cause an
613: // exception to be thrown if the border selection color is
614: // null.
615: // dtcr.setBorderSelectionColor(null);
616: dtcr.setTextSelectionColor(UIManager
617: .getColor("Table.selectionForeground"));
618: dtcr.setBackgroundSelectionColor(UIManager
619: .getColor("Table.selectionBackground"));
620: }
621: }
622:
623: public void setTreeIcons(ImageIcon leafIcon,
624: ImageIcon openIcon, ImageIcon closedIcon) {
625: TreeCellRenderer tcr = getCellRenderer();
626: if (tcr instanceof DefaultTreeCellRenderer) {
627: DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr);
628: dtcr.setLeafIcon(leafIcon);
629: dtcr.setClosedIcon(closedIcon);
630: dtcr.setOpenIcon(openIcon);
631: }
632: }
633:
634: /**
635: * Sets the row height of the tree, and forwards the row height to
636: * the table.
637: */
638: public void setRowHeight(int rowHeight) {
639: if (rowHeight > 0) {
640: super .setRowHeight(rowHeight);
641: if (XTreeTable.this != null
642: && XTreeTable.this .getRowHeight() != rowHeight) {
643: XTreeTable.this .setRowHeight(getRowHeight());
644: }
645: }
646: }
647:
648: /**
649: * This is overridden to set the height to match that of the JTable.
650: */
651: public void setBounds(int x, int y, int w, int h) {
652: super .setBounds(x, 0, w, XTreeTable.this .getHeight());
653: }
654:
655: /**
656: * Sublcassed to translate the graphics such that the last visible
657: * row will be drawn at 0,0.
658: */
659: public void paint(Graphics g) {
660: g.translate(0, -visibleRow * getRowHeight());
661: super .paint(g);
662: }
663:
664: /**
665: * TreeCellRenderer method. Overridden to update the visible row.
666: */
667: public Component getTableCellRendererComponent(JTable table,
668: Object value, boolean isSelected, boolean hasFocus,
669: int row, int column) {
670: percentage = 100;
671: if (!table.isEnabled())
672: percentage = 50;
673:
674: if (isSelected) {
675: setBackground(XuiUtilities.unsaturateColor(table
676: .getSelectionBackground(), percentage));
677: } else if (((row % 2) == 1)
678: && (altUnselectedBackground != null))
679: setBackground(XuiUtilities.unsaturateColor(
680: altUnselectedBackground, percentage));
681: else
682: setBackground(XuiUtilities.unsaturateColor(table
683: .getBackground(), percentage));
684:
685: if (isSelected)
686: setForeground(table.getSelectionForeground());
687: else if (((row % 2) == 1)
688: && (altUnselectedForeground != null))
689: setForeground(altUnselectedForeground);
690: else
691: setForeground(table.getForeground());
692:
693: visibleRow = row;
694: return this ;
695: }
696: }
697:
698: public class TreeTableTreeCellRenderer extends
699: DefaultTreeCellRenderer {
700: Color defaultBackgroundColor;
701: Color defaultTextColor;
702:
703: public TreeTableTreeCellRenderer() {
704: super ();
705: defaultBackgroundColor = getBackgroundNonSelectionColor();
706: defaultTextColor = getTextNonSelectionColor();
707: }
708:
709: /**
710: * TreeCellRenderer method. Overridden to update the visible row.
711: */
712: public Component getTreeCellRendererComponent(JTree tree,
713: Object value, boolean isSelected, boolean expanded,
714: boolean leaf, int row, boolean hasFocus) {
715: super .getTreeCellRendererComponent(tree, value, isSelected,
716: expanded, leaf, row, hasFocus);
717:
718: if (!isSelected) {
719: if (((row % 2) == 1)
720: && (altUnselectedBackground != null))
721: setBackgroundNonSelectionColor(XuiUtilities
722: .unsaturateColor(altUnselectedBackground,
723: percentage));
724: else
725: setBackgroundNonSelectionColor(XuiUtilities
726: .unsaturateColor(defaultBackgroundColor,
727: percentage));
728:
729: if (((row % 2) == 1)
730: && (altUnselectedForeground != null))
731: setTextNonSelectionColor(XuiUtilities
732: .unsaturateColor(altUnselectedForeground,
733: percentage));
734: else
735: setTextNonSelectionColor(XuiUtilities
736: .unsaturateColor(defaultTextColor,
737: percentage));
738: }
739:
740: return this ;
741: }
742: }
743:
744: /**
745: * TreeTableCellEditor implementation. Component returned is the
746: * JTree.
747: */
748: public class TreeTableCellEditor extends
749: com.xoetrope.swing.treetable.AbstractCellEditor implements
750: TableCellEditor {
751: public Component getTableCellEditorComponent(JTable table,
752: Object value, boolean isSelected, int r, int c) {
753: return tree;
754: }
755:
756: /**
757: * Overridden to return false, and if the event is a mouse event
758: * it is forwarded to the tree.<p>
759: * The behavior for this is debatable, and should really be offered
760: * as a property. By returning false, all keyboard actions are
761: * implemented in terms of the table. By returning true, the
762: * tree would get a chance to do something with the keyboard
763: * events. For the most part this is ok. But for certain keys,
764: * such as left/right, the tree will expand/collapse where as
765: * the table focus should really move to a different column. Page
766: * up/down should also be implemented in terms of the table.
767: * By returning false this also has the added benefit that clicking
768: * outside of the bounds of the tree node, but still in the tree
769: * column will select the row, whereas if this returned true
770: * that wouldn't be the case.
771: * <p>By returning false we are also enforcing the policy that
772: * the tree will never be editable (at least by a key sequence).
773: */
774: public boolean isCellEditable(EventObject e) {
775: if (e instanceof MouseEvent) {
776: for (int counter = getColumnCount() - 1; counter >= 0; counter--) {
777: if (getColumnClass(counter) == TreeTableModel.class) {
778: MouseEvent me = (MouseEvent) e;
779: MouseEvent newME = new MouseEvent(tree, me
780: .getID(), me.getWhen(), me
781: .getModifiers(), me.getX()
782: - getCellRect(0, counter, true).x, me
783: .getY(), me.getClickCount(), me
784: .isPopupTrigger());
785: tree.dispatchEvent(newME);
786: break;
787: }
788: }
789: }
790: return false;
791: }
792: }
793:
794: /**
795: * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
796: * to listen for changes in the ListSelectionModel it maintains. Once
797: * a change in the ListSelectionModel happens, the paths are updated
798: * in the DefaultTreeSelectionModel.
799: */
800: class ListToTreeSelectionModelWrapper extends
801: DefaultTreeSelectionModel {
802: /** Set to true when we are updating the ListSelectionModel. */
803: protected boolean updatingListSelectionModel;
804:
805: public ListToTreeSelectionModelWrapper() {
806: super ();
807: getListSelectionModel().addListSelectionListener(
808: createListSelectionListener());
809: }
810:
811: /**
812: * Returns the list selection model. ListToTreeSelectionModelWrapper
813: * listens for changes to this model and updates the selected paths
814: * accordingly.
815: */
816: ListSelectionModel getListSelectionModel() {
817: return listSelectionModel;
818: }
819:
820: /**
821: * This is overridden to set <code>updatingListSelectionModel</code>
822: * and message super. This is the only place DefaultTreeSelectionModel
823: * alters the ListSelectionModel.
824: */
825: public void resetRowSelection() {
826: if (!updatingListSelectionModel) {
827: updatingListSelectionModel = true;
828: try {
829: super .resetRowSelection();
830: } finally {
831: updatingListSelectionModel = false;
832: }
833: }
834: // Notice how we don't message super if
835: // updatingListSelectionModel is true. If
836: // updatingListSelectionModel is true, it implies the
837: // ListSelectionModel has already been updated and the
838: // paths are the only thing that needs to be updated.
839: }
840:
841: /**
842: * Creates and returns an instance of ListSelectionHandler.
843: */
844: protected ListSelectionListener createListSelectionListener() {
845: return new ListSelectionHandler();
846: }
847:
848: /**
849: * If <code>updatingListSelectionModel</code> is false, this will
850: * reset the selected paths from the selected rows in the list
851: * selection model.
852: */
853: protected void updateSelectedPathsFromSelectedRows() {
854: if (!updatingListSelectionModel) {
855: updatingListSelectionModel = true;
856: try {
857: // This is way expensive, ListSelectionModel needs an
858: // enumerator for iterating.
859: int min = listSelectionModel.getMinSelectionIndex();
860: int max = listSelectionModel.getMaxSelectionIndex();
861:
862: clearSelection();
863: if (min != -1 && max != -1) {
864: for (int counter = min; counter <= max; counter++) {
865: if (listSelectionModel
866: .isSelectedIndex(counter)) {
867: TreePath selPath = tree
868: .getPathForRow(counter);
869: if (selPath != null)
870: addSelectionPath(selPath);
871: }
872: }
873: }
874: } finally {
875: updatingListSelectionModel = false;
876: }
877: }
878: }
879:
880: /**
881: * Class responsible for calling updateSelectedPathsFromSelectedRows
882: * when the selection of the list changse.
883: */
884: class ListSelectionHandler implements ListSelectionListener {
885: public void valueChanged(ListSelectionEvent e) {
886: updateSelectedPathsFromSelectedRows();
887: }
888: }
889: }
890: }
|