JTreeTable.java
001 /*
002  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
003  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
004  *
005  *  This file is part of GATE (see http://gate.ac.uk/), and is free
006  *  software, licenced under the GNU Library General Public License,
007  *  Version 2, June 1991 (in the distribution as file licence.html,
008  *  and also available at http://gate.ac.uk/gate/licence.html).
009  *
010  *  Valentin Tablan 06/03/2001
011  *
012  *  $Id: JTreeTable.java 17612 2014-03-10 08:51:17Z markagreenwood $
013  *
014  */
015 package gate.swing;
016 
017 import java.awt.*;
018 import java.awt.event.MouseAdapter;
019 import java.awt.event.MouseEvent;
020 import java.beans.PropertyChangeEvent;
021 import java.beans.PropertyChangeListener;
022 
023 import javax.swing.*;
024 import javax.swing.event.*;
025 import javax.swing.table.*;
026 import javax.swing.tree.*;
027 
028 
029 /**
030  * A TreeTable component. That is a component that looks like a table apart
031  * from the first column that contains a tree.
032  */
033 @SuppressWarnings("serial")
034 public class JTreeTable extends XJTable {
035 
036   /**The tree used to render the first column*/
037   protected CustomJTree tree;
038 
039   /**The model for this component*/
040   protected TreeTableModel treeTableModel;
041   
042   /**
043    * The adapter used internally to convert a tree model into a table model. 
044    */
045   protected TreeTableModelAdapter modelAdapter;
046 
047   /**
048    * Constructs a JTreeTable from a model
049    */
050   public JTreeTable(TreeTableModel model) {
051     super();
052     this.treeTableModel = model;
053 
054     initLocalData();
055     initGuiComponents();
056     initListeners();
057 
058     super.setSortable(false);
059   }
060 
061   protected void initLocalData(){
062   }
063 
064   protected void initGuiComponents(){
065     // Create the tree. It will be used by the table renderer to draw the cells
066     //in the first column
067     tree = new CustomJTree();
068     tree.setModel(treeTableModel);
069     tree.setEditable(false);
070 
071     // Install a tableModel representing the visible rows in the tree.
072     modelAdapter = new TreeTableModelAdapter(treeTableModel);
073     super.setModel(modelAdapter);
074 
075     // Force the JTable and JTree to share their row selection models.
076     tree.setSelectionModel(new DefaultTreeSelectionModel() {
077       //extend the constructor
078       {
079         setSelectionModel(listSelectionModel);
080       }
081     });
082 
083     setAutoCreateColumnsFromModel(false);
084     //Install the renderer and editor
085     getColumnModel().getColumn(0).setCellRenderer(new TreeTableCellRenderer());
086     getColumnModel().getColumn(0).setCellEditor(new TreeTableCellEditor());
087 
088     setShowGrid(false);
089     
090     setRowMargin(0);
091   }
092 
093   protected void initListeners(){
094     //install the mouse listener that will forward the mouse events to the tree
095     addMouseListener(new MouseHandler());
096 
097     getColumnModel().getColumn(0).addPropertyChangeListener(new PropertyChangeListener() {
098       @Override
099       public void propertyChange(PropertyChangeEvent e) {
100         if(e.getPropertyName().equals("width")){
101           int width = ((Number)e.getNewValue()).intValue();
102           int height = tree.getSize().height;
103           tree.setSize(width, height);
104         }
105       }
106     });
107   }
108 
109   /**
110    * Overrides the setSortable() method from {@link XJTable} so the table is NOT
111    * sortable. In a tree-table component the ordering for the rows is given by
112    * the structure of the tree and they cannot be reordered.
113    */
114   @Override
115   public void setSortable(boolean b){
116     throw new UnsupportedOperationException(
117           "A JTreeTable component cannot be sortable!\n" +
118           "The rows order is defined by the tree structure.");
119   }
120 
121   public JTree getTree(){
122     return tree;
123   }
124 
125   public void expandPath(TreePath path){
126     tree.expandPath(path);
127   }
128 
129   public void expandRow(int row){
130     tree.expandRow(row);
131   }
132 
133   /**
134    * The renderer used to display the table cells containing tree nodes.
135    * Will use an internal JTree object to paint the nodes.
136    */
137   public class TreeTableCellRenderer extends DefaultTableCellRenderer {
138     @Override
139     public Component getTableCellRendererComponent(JTable table,
140                      Object value,
141                      boolean isSelected,
142                      boolean hasFocus,
143                      int row, int column) {
144       tree.setVisibleRow(row);
145       return tree;
146     }
147   }//public class TreeTableCellRenderer extends DefaultTableCellRenderer
148 
149   /**
150    * The editor used to edit the nodes in the tree. It only forwards the
151    * requests to the tree's editor.
152    */
153   class TreeTableCellEditor extends DefaultCellEditor
154                             implements TableCellEditor {
155     TreeTableCellEditor(){
156       super(new JTextField());
157       //placeHolder = new PlaceHolder();
158       editor = tree.getCellEditor();
159       setClickCountToStart(0);
160     }
161 
162     @Override
163     public Component getTableCellEditorComponent(JTable table,
164                                                  Object value,
165                                                  boolean isSelected,
166                                                  int row,
167                                                  int column) {
168 
169       editor = tree.getCellEditor();
170 
171       editor.addCellEditorListener(new CellEditorListener() {
172         @Override
173         public void editingStopped(ChangeEvent e) {
174           fireEditingStopped();
175         }
176 
177         @Override
178         public void editingCanceled(ChangeEvent e) {
179           fireEditingCanceled();
180         }
181       });
182 
183       editorComponent = editor.getTreeCellEditorComponent(
184                     tree, tree.getPathForRow(row).getLastPathComponent(),
185                     isSelected, tree.isExpanded(row),
186                     tree.getModel().isLeaf(
187                       tree.getPathForRow(row).getLastPathComponent()
188                     ),
189                     row);
190       Box box = Box.createHorizontalBox();
191       box.add(Box.createHorizontalStrut(tree.getRowBounds(row).x));
192       box.add(editorComponent);
193       return box;
194 //      return editorComponent;
195     }
196 
197     @Override
198     public Object getCellEditorValue() {
199       return editor == null null : editor.getCellEditorValue();
200     }
201 
202     @Override
203     public boolean stopCellEditing(){
204       return editor == null true : editor.stopCellEditing();
205     }
206 
207     @Override
208     public void cancelCellEditing(){
209       if(editor != nulleditor.cancelCellEditing();
210     }
211 
212     TreeCellEditor editor;
213     Component editorComponent;
214   }
215 
216   /**
217    * Class used to convert the mouse events from the JTreeTable component space
218    * into the JTree space. It is used to forward the mouse events to the tree
219    * if they occured in the space used by the tree.
220    */
221   class MouseHandler extends MouseAdapter {
222     @Override
223     public void mousePressed(MouseEvent e) {
224       if(columnAtPoint(e.getPoint()) == 0){
225         tree.dispatchEvent(convertEvent(e));
226       }
227     }
228 
229     @Override
230     public void mouseReleased(MouseEvent e) {
231       if(columnAtPoint(e.getPoint()) == 0){
232         tree.dispatchEvent(convertEvent(e));
233       }
234     }
235 
236     @Override
237     public void mouseClicked(MouseEvent e) {
238       if(columnAtPoint(e.getPoint()) == 0){
239         tree.dispatchEvent(convertEvent(e));
240       }
241     }
242 
243 
244     @Override
245     public void mouseEntered(MouseEvent e) {
246       if(columnAtPoint(e.getPoint()) == 0){
247         tree.dispatchEvent(convertEvent(e));
248       }
249     }
250 
251     @Override
252     public void mouseExited(MouseEvent e) {
253       if(columnAtPoint(e.getPoint()) == 0){
254         tree.dispatchEvent(convertEvent(e));
255       }
256     }
257 
258     protected MouseEvent convertEvent(MouseEvent e){
259       int column = 0;
260       int row = rowAtPoint(e.getPoint());
261 
262       //move the event from table to tree coordinates
263       Rectangle tableCellRect = getCellRect(row, column, false);
264       Rectangle treeCellRect = tree.getRowBounds(row);
265       int dx = 0;
266       if(tableCellRect != nulldx = -tableCellRect.x;
267       int dy = 0;
268       if(tableCellRect !=null && treeCellRect != null)
269         dy = treeCellRect.y -tableCellRect.y;
270       e.translatePoint(dx, dy);
271 
272 
273       return new MouseEvent(
274         tree, e.getID(), e.getWhen(), e.getModifiers(),
275         e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger()
276       );
277     }
278   }
279 
280   /**
281    * A wrapper that reads a TreeTableModel and behaves as a TableModel
282    */
283   class TreeTableModelAdapter extends AbstractTableModel{
284     public TreeTableModelAdapter(TreeTableModel treeTableModel) {
285       tree.addTreeExpansionListener(new TreeExpansionListener() {
286         // Don't use fireTableRowsInserted() here;
287         // the selection model would get  updated twice.
288         @Override
289         public void treeExpanded(TreeExpansionEvent event) {
290           fireTableDataChanged();
291         }
292         @Override
293         public void treeCollapsed(TreeExpansionEvent event) {
294           fireTableDataChanged();
295         }
296       });
297       tree.getModel().addTreeModelListener(new TreeModelListener() {
298         @Override
299         public void treeNodesChanged(TreeModelEvent e) {
300           fireTableDataChanged();
301         }
302         @Override
303         public void treeNodesInserted(TreeModelEvent e) {
304           fireTableDataChanged();
305         }
306         @Override
307         public void treeNodesRemoved(TreeModelEvent e) {
308           fireTableDataChanged();
309         }
310         @Override
311         public void treeStructureChanged(TreeModelEvent e) {
312           fireTableDataChanged();
313         }
314       });
315     }
316 
317     // Wrappers, implementing TableModel interface.
318     @Override
319     public int getColumnCount() {
320       return treeTableModel.getColumnCount();
321     }
322 
323     @Override
324     public String getColumnName(int column) {
325       return treeTableModel.getColumnName(column);
326     }
327 
328     @Override
329     public Class<?> getColumnClass(int column) {
330       if(column == 0return TreeTableModel.class;
331       else return treeTableModel.getColumnClass(column);
332     }
333 
334     @Override
335     public int getRowCount() {
336       return tree.getRowCount();
337     }
338 
339     protected Object nodeForRow(int row) {
340       TreePath treePath = tree.getPathForRow(row);
341       return treePath.getLastPathComponent();
342     }
343 
344     @Override
345     public Object getValueAt(int row, int column) {
346       if(column == 0return treeTableModel;
347       else return treeTableModel.getValueAt(nodeForRow(row), column);
348     }
349 
350     @Override
351     public boolean isCellEditable(int row, int column) {
352       return treeTableModel.isCellEditable(nodeForRow(row), column);
353     }
354 
355     @Override
356     public void setValueAt(Object value, int row, int column) {
357       Object node = nodeForRow(row);
358       treeTableModel.setValueAt(value, node, column);
359     }
360   }//class TreeTableModelAdapter extends AbstractTableModel
361 
362   /**
363    * The JTree used for rendering the first column.
364    */
365   class CustomJTree extends JTree {
366 
367     @Override
368     public void updateUI(){
369       super.updateUI();
370       setRowHeight(0);
371     }
372 
373 
374     public void setVisibleRow(int row){
375       visibleRow = row;
376     }
377 
378     /**
379      * Paints only the current cell in the table
380      */
381     @Override
382     public void paint(Graphics g){
383       Rectangle rowBounds = getRowBounds(visibleRow);
384       g.translate(0, -rowBounds.y);
385       Rectangle oldClip = g.getClipBounds();
386 //      Rectangle newClip = oldClip.intersection(
387 //              new Rectangle(oldClip.x, rowBounds.y, oldClip.width, 
388 //                      rowBounds.height));
389 //      g.setClip(newClip);
390       //re-implemented more efficiently below:
391       int newY = Math.max(oldClip.y, rowBounds.y);
392       int newHeight = Math.min(rowBounds.height - (rowBounds.y - newY)
393               oldClip.height);
394       g.setClip(oldClip.x, newY, oldClip.width, newHeight);
395       super.paint(g);
396     }
397 
398 
399     @Override
400     public Dimension getPreferredSize(){
401       return new Dimension(super.getPreferredSize().width,
402                            getRowBounds(visibleRow).height);
403     }
404 
405 
406     @Override
407     public void validate(){}
408     @Override
409     public void revalidate(){}
410     @Override
411     public void repaint(long tm, int x, int y, int width, int height){}
412     @Override
413     public void repaint(Rectangle r){}
414 
415     protected int visibleRow;
416 
417     /* (non-Javadoc)
418      * @see javax.swing.JTree#setRootVisible(boolean)
419      */
420     @Override
421     public void setRootVisible(boolean rootVisible) {
422       boolean oldValue = isRootVisible();
423       if(oldValue != rootVisible){
424         super.setRootVisible(rootVisible);
425         modelAdapter.fireTableDataChanged();
426       }
427     }
428   }
429 
430 /*
431   class SmartTreeCellRenderer implements TreeCellRenderer{
432 
433     SmartTreeCellRenderer(TreeCellRenderer renderer){
434       originalRenderer = renderer;
435     }
436 
437     public Component getTreeCellRendererComponent(JTree tree,
438                                               Object value,
439                                               boolean selected,
440                                               boolean expanded,
441                                               boolean leaf,
442                                               int row,
443                                               boolean hasFocus){
444       Component comp = originalRenderer.getTreeCellRendererComponent(
445                        tree, value, selected, expanded, leaf, row, hasFocus);
446       if(comp instanceof JComponent &&
447          comp.getPreferredSize().height < getRowHeight(row)){
448         ((JComponent)comp).setPreferredSize(
449             new Dimension(comp.getPreferredSize().width,
450             getRowHeight(row))
451         );
452       }
453       return comp;
454     }
455 
456     public TreeCellRenderer getOriginalRenderer(){
457       return originalRenderer;
458     }
459 
460     TreeCellRenderer originalRenderer;
461   }
462 */
463 }//public class JTreeTable extends XJTable