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