1   /*
2    *  Copyright (c) 1998-2005, The University of Sheffield.
3    *
4    *  This file is part of GATE (see http://gate.ac.uk/), and is free
5    *  software, licenced under the GNU Library General Public License,
6    *  Version 2, June 1991 (in the distribution as file licence.html,
7    *  and also available at http://gate.ac.uk/gate/licence.html).
8    *
9    *  Valentin Tablan 06/03/2001
10   *
11   *  $Id: JTreeTable.java,v 1.10 2005/01/11 13:51:37 ian Exp $
12   *
13   */
14  package gate.swing;
15  
16  import java.awt.*;
17  import java.awt.event.MouseAdapter;
18  import java.awt.event.MouseEvent;
19  import java.beans.PropertyChangeEvent;
20  import java.beans.PropertyChangeListener;
21  
22  import javax.swing.*;
23  import javax.swing.event.*;
24  import javax.swing.table.*;
25  import javax.swing.tree.*;
26  
27  
28  /**
29   * A TreeTable component. That is a component that looks like a table apart
30   * from the first column that contains a tree.
31   */
32  public class JTreeTable extends XJTable {
33  
34    /**The tree used to render the first column*/
35    protected CustomJTree tree;
36  
37    /**The model for this component*/
38    protected TreeTableModel treeTableModel;
39  
40    /**
41     * Constructs a JTreeTable from a model
42     */
43    public JTreeTable(TreeTableModel model) {
44      super();
45      this.treeTableModel = model;
46  
47      initLocalData();
48      initGuiComponents();
49      initListeners();
50  
51      super.setSortable(false);
52    }
53  
54    protected void initLocalData(){
55    }
56  
57    protected void initGuiComponents(){
58      // Create the tree. It will be used by the table renderer to draw the cells
59      //in the first column
60      tree = new CustomJTree();
61      tree.setModel(treeTableModel);
62      tree.setEditable(false);
63  
64      // Install a tableModel representing the visible rows in the tree.
65      super.setModel(new TreeTableModelAdapter(treeTableModel));
66  
67      // Force the JTable and JTree to share their row selection models.
68      tree.setSelectionModel(new DefaultTreeSelectionModel() {
69        //extend the constructor
70        {
71          setSelectionModel(listSelectionModel);
72        }
73      });
74  
75      setAutoCreateColumnsFromModel(false);
76      //Install the renderer and editor
77      getColumnModel().getColumn(0).setCellRenderer(new TreeTableCellRenderer());
78      getColumnModel().getColumn(0).setCellEditor(new TreeTableCellEditor());
79  
80      setShowGrid(false);
81    }
82  
83    protected void initListeners(){
84      //install the mouse listener that will forward the mouse events to the tree
85      addMouseListener(new MouseHandler());
86  
87      getColumnModel().getColumn(0).addPropertyChangeListener(new PropertyChangeListener() {
88        public void propertyChange(PropertyChangeEvent e) {
89          if(e.getPropertyName().equals("width")){
90            int width = ((Number)e.getNewValue()).intValue();
91            int height = tree.getSize().height;
92            tree.setSize(width, height);
93          }
94        }
95      });
96    }
97  
98    /**
99     * Overrides the setSortable() method from {@link XJTable} so the table is NOT
100    * sortable. In a tree-table component the ordering for the rows is given by
101    * the structure of the tree and they cannot be reordered.
102    */
103   public void setSortable(boolean b){
104     throw new UnsupportedOperationException(
105           "A JTreeTable component cannot be sortable!\n" +
106           "The rows order is defined by the tree structure.");
107   }
108 
109   public JTree getTree(){
110     return tree;
111   }
112 
113   public void expandPath(TreePath path){
114     tree.expandPath(path);
115   }
116 
117   public void expandRow(int row){
118     tree.expandRow(row);
119   }
120 
121   /**
122    * The renderer used to display the table cells containing tree nodes.
123    * Will use an internal JTree object to paint the nodes.
124    */
125   public class TreeTableCellRenderer extends DefaultTableCellRenderer {
126     public Component getTableCellRendererComponent(JTable table,
127                      Object value,
128                      boolean isSelected,
129                      boolean hasFocus,
130                      int row, int column) {
131       tree.setVisibleRow(row);
132       return tree;
133     }
134   }//public class TreeTableCellRenderer extends DefaultTableCellRenderer
135 
136   /**
137    * The editor used to edit the nodes in the tree. It only forwards the
138    * requests to the tree's editor.
139    */
140   class TreeTableCellEditor extends DefaultCellEditor
141                             implements TableCellEditor {
142     TreeTableCellEditor(){
143       super(new JTextField());
144       //placeHolder = new PlaceHolder();
145       editor = tree.getCellEditor();
146       setClickCountToStart(0);
147     }
148 
149     public Component getTableCellEditorComponent(JTable table,
150                                                  Object value,
151                                                  boolean isSelected,
152                                                  int row,
153                                                  int column) {
154 
155       editor = tree.getCellEditor();
156 
157       editor.addCellEditorListener(new CellEditorListener() {
158         public void editingStopped(ChangeEvent e) {
159           fireEditingStopped();
160         }
161 
162         public void editingCanceled(ChangeEvent e) {
163           fireEditingCanceled();
164         }
165       });
166 
167       editorComponent = editor.getTreeCellEditorComponent(
168                     tree, tree.getPathForRow(row).getLastPathComponent(),
169                     isSelected, tree.isExpanded(row),
170                     tree.getModel().isLeaf(
171                       tree.getPathForRow(row).getLastPathComponent()
172                     ),
173                     row);
174       Box box = Box.createHorizontalBox();
175       box.add(Box.createHorizontalStrut(tree.getRowBounds(row).x));
176       box.add(editorComponent);
177       return box;
178 //      return editorComponent;
179     }
180 
181     public Object getCellEditorValue() {
182       return editor == null ? null : editor.getCellEditorValue();
183     }
184 
185     public boolean stopCellEditing(){
186       return editor == null ? true : editor.stopCellEditing();
187     }
188 
189     public void cancelCellEditing(){
190       if(editor != null) editor.cancelCellEditing();
191     }
192 
193     TreeCellEditor editor;
194     Component editorComponent;
195   }
196 
197   /**
198    * Class used to convert the mouse events from the JTreeTable component space
199    * into the JTree space. It is used to forward the mouse events to the tree
200    * if they occured in the space used by the tree.
201    */
202   class MouseHandler extends MouseAdapter {
203     public void mousePressed(MouseEvent e) {
204       if(columnAtPoint(e.getPoint()) == 0){
205         tree.dispatchEvent(convertEvent(e));
206       }
207     }
208 
209     public void mouseReleased(MouseEvent e) {
210       if(columnAtPoint(e.getPoint()) == 0){
211         tree.dispatchEvent(convertEvent(e));
212       }
213     }
214 
215     public void mouseClicked(MouseEvent e) {
216       if(columnAtPoint(e.getPoint()) == 0){
217         tree.dispatchEvent(convertEvent(e));
218       }
219     }
220 
221 
222     public void mouseEntered(MouseEvent e) {
223       if(columnAtPoint(e.getPoint()) == 0){
224         tree.dispatchEvent(convertEvent(e));
225       }
226     }
227 
228     public void mouseExited(MouseEvent e) {
229       if(columnAtPoint(e.getPoint()) == 0){
230         tree.dispatchEvent(convertEvent(e));
231       }
232     }
233 
234     protected MouseEvent convertEvent(MouseEvent e){
235       int column = 0;
236       int row = rowAtPoint(e.getPoint());
237 
238       //move the event from table to tree coordinates
239       Rectangle tableCellRect = getCellRect(row, column, false);
240       Rectangle treeCellRect = tree.getRowBounds(row);
241       int dx = 0;
242       if(tableCellRect != null) dx = -tableCellRect.x;
243       int dy = 0;
244       if(tableCellRect !=null && treeCellRect != null)
245         dy = treeCellRect.y -tableCellRect.y;
246       e.translatePoint(dx, dy);
247 
248 
249       return new MouseEvent(
250         tree, e.getID(), e.getWhen(), e.getModifiers(),
251         e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger()
252       );
253     }
254   }
255 
256   /**
257    * A wrapper that reads a TreeTableModel and behaves as a TableModel
258    */
259   class TreeTableModelAdapter extends AbstractTableModel{
260     public TreeTableModelAdapter(TreeTableModel treeTableModel) {
261       tree.addTreeExpansionListener(new TreeExpansionListener() {
262         // Don't use fireTableRowsInserted() here;
263         // the selection model would get  updated twice.
264         public void treeExpanded(TreeExpansionEvent event) {
265           fireTableDataChanged();
266         }
267         public void treeCollapsed(TreeExpansionEvent event) {
268           fireTableDataChanged();
269         }
270       });
271       tree.getModel().addTreeModelListener(new TreeModelListener() {
272         public void treeNodesChanged(TreeModelEvent e) {
273           fireTableDataChanged();
274         }
275         public void treeNodesInserted(TreeModelEvent e) {
276           fireTableDataChanged();
277         }
278         public void treeNodesRemoved(TreeModelEvent e) {
279           fireTableDataChanged();
280         }
281         public void treeStructureChanged(TreeModelEvent e) {
282           fireTableDataChanged();
283         }
284       });
285     }
286 
287 
288 
289     // Wrappers, implementing TableModel interface.
290     public int getColumnCount() {
291       return treeTableModel.getColumnCount();
292     }
293 
294     public String getColumnName(int column) {
295       return treeTableModel.getColumnName(column);
296     }
297 
298     public Class getColumnClass(int column) {
299       if(column == 0) return TreeTableModel.class;
300       else return treeTableModel.getColumnClass(column);
301     }
302 
303     public int getRowCount() {
304       return tree.getRowCount();
305     }
306 
307     protected Object nodeForRow(int row) {
308       TreePath treePath = tree.getPathForRow(row);
309       return treePath.getLastPathComponent();
310     }
311 
312     public Object getValueAt(int row, int column) {
313       if(column == 0) return treeTableModel;
314       else return treeTableModel.getValueAt(nodeForRow(row), column);
315     }
316 
317     public boolean isCellEditable(int row, int column) {
318       return treeTableModel.isCellEditable(nodeForRow(row), column);
319     }
320 
321     public void setValueAt(Object value, int row, int column) {
322       Object node = nodeForRow(row);
323       treeTableModel.setValueAt(value, node, column);
324     }
325   }//class TreeTableModelAdapter extends AbstractTableModel
326 
327   /**
328    * The JTree used for rendering the first column.
329    */
330   class CustomJTree extends JTree {
331 
332     public void updateUI(){
333       super.updateUI();
334       setRowHeight(0);
335     }
336 
337 
338     public void setVisibleRow(int row){
339       visibleRow = row;
340     }
341 
342     /**
343      * Paints only the current cell in the table
344      */
345     public void paint(Graphics g){
346       Rectangle rect = getRowBounds(visibleRow);
347       Rectangle bounds = g.getClipBounds();
348       g.translate(0, -rect.y);
349       g.setClip(bounds.x, rect.y, bounds.width, rect.height);
350       super.paint(g);
351     }
352 
353 
354     public Dimension getPreferredSize(){
355       return new Dimension(super.getPreferredSize().width,
356                            getRowBounds(visibleRow).height);
357     }
358 
359 
360     public void validate(){}
361     public void revalidate(){}
362     public void repaint(long tm, int x, int y, int width, int height){}
363     public void repaint(Rectangle r){}
364 
365     protected int visibleRow;
366   }
367 
368 /*
369   class SmartTreeCellRenderer implements TreeCellRenderer{
370 
371     SmartTreeCellRenderer(TreeCellRenderer renderer){
372       originalRenderer = renderer;
373     }
374 
375     public Component getTreeCellRendererComponent(JTree tree,
376                                               Object value,
377                                               boolean selected,
378                                               boolean expanded,
379                                               boolean leaf,
380                                               int row,
381                                               boolean hasFocus){
382       Component comp = originalRenderer.getTreeCellRendererComponent(
383                        tree, value, selected, expanded, leaf, row, hasFocus);
384       if(comp instanceof JComponent &&
385          comp.getPreferredSize().height < getRowHeight(row)){
386         ((JComponent)comp).setPreferredSize(
387             new Dimension(comp.getPreferredSize().width,
388             getRowHeight(row))
389         );
390       }
391       return comp;
392     }
393 
394     public TreeCellRenderer getOriginalRenderer(){
395       return originalRenderer;
396     }
397 
398     TreeCellRenderer originalRenderer;
399   }
400 */
401 }//public class JTreeTable extends XJTable
402