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