001: /*
002: * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. Redistribution and use in source and binary
003: * forms, with or without modification, are permitted provided that the following conditions are met: -
004: * Redistributions of source code must retain the above copyright notice, this list of conditions and the
005: * following disclaimer. - Redistribution in binary form must reproduce the above copyright notice, this list
006: * of conditions and the following disclaimer in the documentation and/or other materials provided with the
007: * distribution. Neither the name of Sun Microsystems, Inc. or the names of contributors may be used to
008: * endorse or promote products derived from this software without specific prior written permission. This
009: * software is provided "AS IS," without a warranty of any kind. ALL EXPRESS OR IMPLIED CONDITIONS,
010: * REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
011: * PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES
012: * OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS
013: * SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT
014: * OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED
015: * AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
016: * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. You acknowledge that this software is not
017: * designed, licensed or intended for use in the design, construction, operation or maintenance of any nuclear
018: * facility.
019: */
020:
021: package br.com.igor.beans.table;
022:
023: import java.awt.Color;
024: import java.awt.Component;
025: import java.awt.Graphics;
026: import java.awt.Rectangle;
027: import java.awt.event.InputEvent;
028: import java.awt.event.KeyAdapter;
029: import java.awt.event.KeyEvent;
030: import java.awt.event.MouseAdapter;
031: import java.awt.event.MouseEvent;
032: import java.util.EventObject;
033:
034: import javax.swing.DefaultCellEditor;
035: import javax.swing.Icon;
036: import javax.swing.JTable;
037: import javax.swing.JTextField;
038: import javax.swing.JTree;
039: import javax.swing.ListSelectionModel;
040: import javax.swing.UIManager;
041: import javax.swing.border.Border;
042: import javax.swing.event.ListSelectionEvent;
043: import javax.swing.event.ListSelectionListener;
044: import javax.swing.table.TableCellRenderer;
045: import javax.swing.tree.DefaultTreeCellRenderer;
046: import javax.swing.tree.DefaultTreeSelectionModel;
047: import javax.swing.tree.TreeCellRenderer;
048: import javax.swing.tree.TreeModel;
049: import javax.swing.tree.TreePath;
050:
051: /**
052: * This example shows how to create a simple JTreeTable component, by using a JTree as a renderer (and editor)
053: * for the cells in a particular column in the JTable.
054: *
055: * @version 1.2 10/27/98
056: * @author Philip Milne
057: * @author Scott Violet
058: */
059: public class JTreeTable extends JTable {
060: /** A subclass of JTree. */
061: protected TreeTableCellRenderer tree;
062:
063: /**
064: * @param treeTableModel
065: */
066: public JTreeTable(TreeTableModel treeTableModel) {
067: super ();
068:
069: setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
070:
071: // Creates the tree. It will be used as a renderer and editor.
072: tree = new TreeTableCellRenderer(treeTableModel);
073:
074: // Installs a tableModel representing the visible rows in the tree.
075: super .setModel(new TreeTableModelAdapter(treeTableModel, tree));
076:
077: // Forces the JTable and JTree to share their row selection models.
078: ListToTreeSelectionModelWrapper selectionWrapper = new ListToTreeSelectionModelWrapper();
079: tree.setSelectionModel(selectionWrapper);
080: setSelectionModel(selectionWrapper.getListSelectionModel());
081:
082: // Installs the tree editor renderer and editor.
083: setDefaultRenderer(TreeTableModel.class, tree);
084: setDefaultEditor(TreeTableModel.class,
085: new TreeTableCellEditor());
086:
087: addMouseListener(new MouseAdapter() {
088: @Override
089: public void mouseReleased(MouseEvent e) {
090: if (e.getClickCount() == 2) {
091: if (tree.getSelectionRows() != null
092: && tree
093: .isExpanded(tree.getSelectionRows()[0]))
094: tree.collapseRow(getSelectedRow());
095: else
096: tree.expandRow(getSelectedRow());
097: }
098: }
099: });
100:
101: addKeyListener(new KeyAdapter() {
102: @Override
103: public void keyPressed(KeyEvent e) {
104: tree.dispatchEvent(e);
105: }
106: });
107:
108: // And update the height of the trees row to match that of
109: // the table.
110: if (tree.getRowHeight() < 1) {
111: // Metal looks better like this.
112: setRowHeight(20);
113: }
114: }
115:
116: /**
117: * @param rootVisible
118: */
119: public void setRootVisible(boolean rootVisible) {
120: tree.setRootVisible(rootVisible);
121: }
122:
123: /**
124: * Overridden to message super and forward the method to the tree. Since the tree is not actually in the
125: * component hierarchy it will never receive this unless we forward it in this manner.
126: */
127: @Override
128: public void updateUI() {
129: super .updateUI();
130: if (tree != null) {
131: tree.updateUI();
132: // Do this so that the editor is referencing the current renderer
133: // from the tree. The renderer can potentially change each time
134: // laf changes.
135: setDefaultEditor(TreeTableModel.class,
136: new TreeTableCellEditor());
137: }
138: // Use the tree's default foreground and background colors in the
139: // table.
140: //LookAndFeel.installColorsAndFont(this, "Tree.background", "Tree.foreground", "Tree.font");
141: }
142:
143: /**
144: * Workaround for BasicTableUI anomaly. Make sure the UI never tries to resize the editor. The UI
145: * currently uses different techniques to paint the renderers and editors; overriding setBounds() below is
146: * not the right thing to do for an editor. Returning -1 for the editing row in this case, ensures the
147: * editor is never painted.
148: * @return Linha sendo editada
149: */
150: @Override
151: public int getEditingRow() {
152: return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1
153: : editingRow;
154: }
155:
156: /**
157: * Returns the actual row that is editing as <code>getEditingRow</code> will always return -1.
158: * @return Linha sendo editada
159: */
160: private int realEditingRow() {
161: return editingRow;
162: }
163:
164: /**
165: * This is overridden to invoke super's implementation, and then, if the receiver is editing a Tree
166: * column, the editor's bounds is reset. The reason we have to do this is because JTable doesn't think the
167: * table is being edited, as <code>getEditingRow</code> returns -1, and therefore doesn't automatically
168: * resize the editor for us.
169: * @param resizingColumn
170: */
171: @Override
172: public void sizeColumnsToFit(int resizingColumn) {
173: super .sizeColumnsToFit(resizingColumn);
174: if (getEditingColumn() != -1
175: && getColumnClass(editingColumn) == TreeTableModel.class) {
176: Rectangle cellRect = getCellRect(realEditingRow(),
177: getEditingColumn(), false);
178: Component component = getEditorComponent();
179: component.setBounds(cellRect);
180: component.validate();
181: }
182: }
183:
184: /**
185: * Overridden to pass the new rowHeight to the tree.
186: * @param rowHeight Altura de linha
187: */
188: @Override
189: public void setRowHeight(int rowHeight) {
190: super .setRowHeight(rowHeight);
191: if (tree != null && tree.getRowHeight() != rowHeight) {
192: tree.setRowHeight(getRowHeight());
193: }
194: }
195:
196: /**
197: * Returns the tree that is being shared between the model.
198: * @return O JTree usado por este componete
199: */
200: public JTree getTree() {
201: return tree;
202: }
203:
204: /**
205: * Overridden to invoke repaint for the particular location if the column contains the tree. This is done
206: * as the tree editor does not fill the bounds of the cell, we need the renderer to paint the tree in the
207: * background, and then draw the editor over it.
208: * @param row Linha
209: * @param column Coluna
210: * @param e evento
211: * @return True em caso de sucesso
212: */
213: @Override
214: public boolean editCellAt(int row, int column, EventObject e) {
215: boolean retValue = super .editCellAt(row, column, e);
216: if (retValue && getColumnClass(column) == TreeTableModel.class) {
217: repaint(getCellRect(row, column, false));
218: }
219: return retValue;
220: }
221:
222: /**
223: * A TreeCellRenderer that displays a JTree.
224: */
225: public class TreeTableCellRenderer extends JTree implements
226: TableCellRenderer {
227: /** Last table/tree row asked to renderer. */
228: protected int visibleRow;
229:
230: /**
231: * Border to draw around the tree, if this is non-null, it will be painted.
232: */
233: protected Border highlightBorder;
234:
235: /**
236: * Contrutor
237: * @param model Novo Model
238: */
239: public TreeTableCellRenderer(TreeModel model) {
240: super (model);
241: }
242:
243: /**
244: * updateUI is overridden to set the colors of the Tree's renderer to match that of the table.
245: */
246: @Override
247: public void updateUI() {
248: super .updateUI();
249:
250: // Make the tree's cell renderer use the table's cell selection
251: // colors.
252: TreeCellRenderer tcr = getCellRenderer();
253: if (tcr instanceof DefaultTreeCellRenderer) {
254: DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr);
255: // For 1.1 uncomment this, 1.2 has a bug that will cause an
256: // exception to be thrown if the border selection color is
257: // null.
258: // dtcr.setBorderSelectionColor(null);
259: //dtcr.setTextSelectionColor(UIManager.getColor("Table.selectionForeground"));
260: //dtcr.setBackgroundSelectionColor(UIManager.getColor("Table.selectionBackground"));
261: }
262: }
263:
264: /**
265: * Sets the row height of the tree, and forwards the row height to the table.
266: * @param rowHeight Altura da linha
267: */
268: @Override
269: public void setRowHeight(int rowHeight) {
270: if (rowHeight > 0) {
271: super .setRowHeight(rowHeight);
272: if (JTreeTable.this != null
273: && JTreeTable.this .getRowHeight() != rowHeight) {
274: JTreeTable.this .setRowHeight(getRowHeight());
275: }
276: }
277: }
278:
279: /**
280: * This is overridden to set the height to match that of the JTable.
281: * @param x Posição x
282: * @param y Posição y
283: * @param w Largura
284: * @param h Altura
285: */
286: @Override
287: public void setBounds(int x, int y, int w, int h) {
288: super .setBounds(x, 0, w, JTreeTable.this .getHeight());
289: }
290:
291: /**
292: * Sublcassed to translate the graphics such that the last visible row will be drawn at 0,0.
293: * @param g Contexto Gráfigo
294: */
295: @Override
296: public void paint(Graphics g) {
297: g.translate(0, -visibleRow * getRowHeight());
298: super .paint(g);
299: // Draw the Table border if we have focus.
300: if (highlightBorder != null) {
301: highlightBorder.paintBorder(this , g, 0, visibleRow
302: * getRowHeight(), getWidth(), getRowHeight());
303: }
304: }
305:
306: /**
307: * TreeCellRenderer method. Overridden to update the visible row.
308: * @param table JTable
309: * @param value Valor
310: * @param isSelected Está selecionado?
311: * @param hasFocus Tem foco?
312: * @param row Linha
313: * @param column Coluna
314: * @return O TableCellEditor
315: */
316: public Component getTableCellRendererComponent(JTable table,
317: Object value, boolean isSelected, boolean hasFocus,
318: int row, int column) {
319: Color background;
320: Color foreground;
321:
322: if (isSelected) {
323: background = table.getSelectionBackground();
324: foreground = table.getSelectionForeground();
325: } else {
326: background = table.getBackground();
327: foreground = table.getForeground();
328: }
329: highlightBorder = null;
330: if (realEditingRow() == row && getEditingColumn() == column) {
331: background = UIManager
332: .getColor("Table.focusCellBackground");
333: foreground = UIManager
334: .getColor("Table.focusCellForeground");
335: } else if (hasFocus) {
336: highlightBorder = UIManager
337: .getBorder("Table.focusCellHighlightBorder");
338: if (isCellEditable(row, column)) {
339: background = UIManager
340: .getColor("Table.focusCellBackground");
341: foreground = UIManager
342: .getColor("Table.focusCellForeground");
343: }
344: }
345:
346: visibleRow = row;
347: setBackground(background);
348:
349: TreeCellRenderer tcr = getCellRenderer();
350: if (tcr instanceof DefaultTreeCellRenderer) {
351: DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr);
352: if (isSelected) {
353: dtcr.setTextSelectionColor(foreground);
354: dtcr.setBackgroundSelectionColor(background);
355: } else {
356: dtcr.setTextNonSelectionColor(foreground);
357: dtcr.setBackgroundNonSelectionColor(background);
358: }
359: }
360: return this ;
361: }
362: }
363:
364: /**
365: * An editor that can be used to edit the tree column. This extends DefaultCellEditor and uses a
366: * JTextField (actually, TreeTableTextField) to perform the actual editing.
367: * <p>
368: * To support editing of the tree column we can not make the tree editable. The reason this doesn't work
369: * is that you can not use the same component for editing and renderering. The table may have the need to
370: * paint cells, while a cell is being edited. If the same component were used for the rendering and
371: * editing the component would be moved around, and the contents would change. When editing, this is
372: * undesirable, the contents of the text field must stay the same, including the caret blinking, and
373: * selections persisting. For this reason the editing is done via a TableCellEditor.
374: * <p>
375: * Another interesting thing to be aware of is how tree positions its render and editor. The render/editor
376: * is responsible for drawing the icon indicating the type of node (leaf, branch...). The tree is
377: * responsible for drawing any other indicators, perhaps an additional +/- sign, or lines connecting the
378: * various nodes. So, the renderer is positioned based on depth. On the other hand, table always makes its
379: * editor fill the contents of the cell. To get the allusion that the table cell editor is part of the
380: * tree, we don't want the table cell editor to fill the cell bounds. We want it to be placed in the same
381: * manner as tree places it editor, and have table message the tree to paint any decorations the tree
382: * wants. Then, we would only have to worry about the editing part. The approach taken here is to
383: * determine where tree would place the editor, and to override the <code>reshape</code> method in the
384: * JTextField component to nudge the textfield to the location tree would place it. Since JTreeTable will
385: * paint the tree behind the editor everything should just work. So, that is what we are doing here.
386: * Determining of the icon position will only work if the TreeCellRenderer is an instance of
387: * DefaultTreeCellRenderer. If you need custom TreeCellRenderers, that don't descend from
388: * DefaultTreeCellRenderer, and you want to support editing in JTreeTable, you will have to do something
389: * similiar.
390: */
391: public class TreeTableCellEditor extends DefaultCellEditor {
392: /**
393: * Construtor
394: */
395: public TreeTableCellEditor() {
396: super (new TreeTableTextField());
397: }
398:
399: /**
400: * Overridden to determine an offset that tree would place the editor at. The offset is determined
401: * from the <code>getRowBounds</code> JTree method, and additionally from the icon
402: * DefaultTreeCellRenderer will use.
403: * <p>
404: * The offset is then set on the TreeTableTextField component created in the constructor, and
405: * returned.
406: * @param table JTable
407: * @param value Valor
408: * @param isSelected Está selecionado?
409: * @param r Linha
410: * @param c Coluna
411: * @return O TableCellEditor
412: */
413: @Override
414: public Component getTableCellEditorComponent(JTable table,
415: Object value, boolean isSelected, int r, int c) {
416: Component component = super .getTableCellEditorComponent(
417: table, value, isSelected, r, c);
418: JTree t = getTree();
419: boolean rv = t.isRootVisible();
420: int offsetRow = rv ? r : r - 1;
421: Rectangle bounds = t.getRowBounds(offsetRow);
422: int offset = bounds.x;
423: TreeCellRenderer tcr = t.getCellRenderer();
424: if (tcr instanceof DefaultTreeCellRenderer) {
425: Object node = t.getPathForRow(offsetRow)
426: .getLastPathComponent();
427: Icon icon;
428: if (t.getModel().isLeaf(node))
429: icon = ((DefaultTreeCellRenderer) tcr)
430: .getLeafIcon();
431: else if (tree.isExpanded(offsetRow))
432: icon = ((DefaultTreeCellRenderer) tcr)
433: .getOpenIcon();
434: else
435: icon = ((DefaultTreeCellRenderer) tcr)
436: .getClosedIcon();
437: if (icon != null) {
438: offset += ((DefaultTreeCellRenderer) tcr)
439: .getIconTextGap()
440: + icon.getIconWidth();
441: }
442: }
443: ((TreeTableTextField) getComponent()).offset = offset;
444: return component;
445: }
446:
447: /**
448: * This is overridden to forward the event to the tree. This will return true if the click count >= 3,
449: * or the event is null.
450: * @param e Evento
451: * @return True se editavel
452: */
453: @Override
454: public boolean isCellEditable(EventObject e) {
455: if (e instanceof MouseEvent) {
456: MouseEvent me = (MouseEvent) e;
457: // If the modifiers are not 0 (or the left mouse button),
458: // tree may try and toggle the selection, and table
459: // will then try and toggle, resulting in the
460: // selection remaining the same. To avoid this, we
461: // only dispatch when the modifiers are 0 (or the left mouse
462: // button).
463: if (me.getModifiers() == 0
464: || me.getModifiers() == InputEvent.BUTTON1_MASK) {
465: for (int counter = getColumnCount() - 1; counter >= 0; counter--) {
466: if (getColumnClass(counter) == TreeTableModel.class) {
467: MouseEvent newME = new MouseEvent(
468: JTreeTable.this .tree, me.getID(),
469: me.getWhen(), me.getModifiers(), me
470: .getX()
471: - getCellRect(0, counter,
472: true).x, me.getY(),
473: me.getClickCount(), me
474: .isPopupTrigger());
475: JTreeTable.this .tree.dispatchEvent(newME);
476: break;
477: }
478: }
479: }
480: if (me.getClickCount() >= 3) {
481: return true;
482: }
483: return false;
484: }
485: if (e == null) {
486: return true;
487: }
488: return false;
489: }
490: }
491:
492: /**
493: * Component used by TreeTableCellEditor. The only thing this does is to override the <code>reshape</code>
494: * method, and to ALWAYS make the x location be <code>offset</code>.
495: */
496: static class TreeTableTextField extends JTextField {
497: /**
498: * Comment for <code>offset</code>
499: */
500: public int offset;
501:
502: /**
503: * @see javax.swing.JComponent#reshape(int, int, int, int)
504: */
505: @Override
506: public void reshape(int x, int y, int w, int h) {
507: int newX = Math.max(x, offset);
508: super .reshape(newX, y, w - (newX - x), h);
509: }
510: }
511:
512: /**
513: * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel to listen for changes in the
514: * ListSelectionModel it maintains. Once a change in the ListSelectionModel happens, the paths are updated
515: * in the DefaultTreeSelectionModel.
516: */
517: class ListToTreeSelectionModelWrapper extends
518: DefaultTreeSelectionModel {
519: /** Set to true when we are updating the ListSelectionModel. */
520: protected boolean updatingListSelectionModel;
521:
522: /**
523: * Contrutor
524: */
525: public ListToTreeSelectionModelWrapper() {
526: super ();
527: getListSelectionModel().addListSelectionListener(
528: createListSelectionListener());
529: }
530:
531: /**
532: * Returns the list selection model. ListToTreeSelectionModelWrapper listens for changes to this model
533: * and updates the selected paths accordingly.
534: * @return ListSelectionModel
535: */
536: ListSelectionModel getListSelectionModel() {
537: return listSelectionModel;
538: }
539:
540: /**
541: * This is overridden to set <code>updatingListSelectionModel</code> and message super. This is the
542: * only place DefaultTreeSelectionModel alters the ListSelectionModel.
543: */
544: @Override
545: public void resetRowSelection() {
546: if (!updatingListSelectionModel) {
547: updatingListSelectionModel = true;
548: try {
549: super .resetRowSelection();
550: } finally {
551: updatingListSelectionModel = false;
552: }
553: }
554: // Notice how we don't message super if
555: // updatingListSelectionModel is true. If
556: // updatingListSelectionModel is true, it implies the
557: // ListSelectionModel has already been updated and the
558: // paths are the only thing that needs to be updated.
559: }
560:
561: /**
562: * Creates and returns an instance of ListSelectionHandler.
563: * @return ListSelectionListener
564: */
565: protected ListSelectionListener createListSelectionListener() {
566: return new ListSelectionHandler();
567: }
568:
569: /**
570: * If <code>updatingListSelectionModel</code> is false, this will reset the selected paths from the
571: * selected rows in the list selection model.
572: */
573: protected void updateSelectedPathsFromSelectedRows() {
574: if (!updatingListSelectionModel) {
575: updatingListSelectionModel = true;
576: try {
577: // This is way expensive, ListSelectionModel needs an
578: // enumerator for iterating.
579: int min = listSelectionModel.getMinSelectionIndex();
580: int max = listSelectionModel.getMaxSelectionIndex();
581:
582: clearSelection();
583: if (min != -1 && max != -1) {
584: for (int counter = min; counter <= max; counter++) {
585: if (listSelectionModel
586: .isSelectedIndex(counter)) {
587: TreePath selPath = tree
588: .getPathForRow(counter);
589:
590: if (selPath != null) {
591: addSelectionPath(selPath);
592: }
593: }
594: }
595: }
596: } finally {
597: updatingListSelectionModel = false;
598: }
599: }
600: }
601:
602: /**
603: * Class responsible for calling updateSelectedPathsFromSelectedRows when the selection of the list
604: * changse.
605: */
606: class ListSelectionHandler implements ListSelectionListener {
607: /**
608: * @see javax.swing.event.ListSelectionListener#valueChanged(javax.swing.event.ListSelectionEvent)
609: */
610: public void valueChanged(ListSelectionEvent e) {
611: updateSelectedPathsFromSelectedRows();
612: }
613: }
614: }
615: }
|