package org.openswing.swing.tree.client; import java.lang.reflect.*; import java.text.*; import java.util.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.table.*; import javax.swing.tree.*; import org.openswing.swing.message.receive.java.*; import org.openswing.swing.util.client.*; /** * <p>Title: OpenSwing Framework</p> * <p>Description: grid that contains a tree in the first column.</p> * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p> * * <p> This file is part of OpenSwing Framework. * This library is free software; you can redistribute it and/or * modify it under the terms of the (LGPL) Lesser General Public * License as published by the Free Software Foundation; * * GNU LESSER GENERAL PUBLIC LICENSE * Version 2.1, February 1999 * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * The author may be contacted at: * maurocarniel@tin.it</p> * * @author Mauro Carniel * @version 1.0 */ public class TreeGrid extends JTable { /** a subclass of JTree. */ protected TreeTableCellRenderer tree; private TreeGridNodeRenderer treeRenderer; /** grid columns alignments: collection of pairs <attribute name,alignment: Integer> */ private Hashtable gridColumnAlignments = new Hashtable(); /** attribute name that identifies the column containing the tree */ private String attributeName; /** attribute names to used to show grid columns */ private ArrayList gridColumns = new ArrayList(); /** optional attribute name that identifies the name of the image to show as tree node */ private String iconAttributeName; public TreeGrid(TreeTableModel treeTableModel,String attributeName,ArrayList gridColumns,ArrayList gridColumnSizes,Hashtable gridColumnAlignments,String folderIconName,String leavesImageName,Format formatter,boolean rootVisible) { super(); this.attributeName = attributeName; this.gridColumns = gridColumns; this.gridColumnAlignments = gridColumnAlignments; treeRenderer = new TreeGridNodeRenderer(folderIconName,leavesImageName,formatter); // Create the tree. It will be used as a renderer and editor. tree = new TreeTableCellRenderer(treeTableModel); tree.setRootVisible(rootVisible); tree.setCellRenderer(treeRenderer); // Install a tableModel representing the visible rows in the tree. super.setModel(new TreeTableModelAdapter(treeTableModel, tree)); // Force the JTable and JTree to share their row selection models. ListToTreeSelectionModelWrapper selectionWrapper = new ListToTreeSelectionModelWrapper(); tree.setSelectionModel(selectionWrapper); setSelectionModel(selectionWrapper.getListSelectionModel()); // Install the tree editor renderer and editor. setDefaultRenderer(TreeTableModel.class, tree); setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor()); // No grid. setShowGrid(false); // No intercell spacing setIntercellSpacing(new Dimension(0, 0)); setAutoResizeMode(JTable.AUTO_RESIZE_OFF); setSelectionMode(ListSelectionModel.SINGLE_SELECTION); for(int i=0;i<gridColumnSizes.size();i++) { getColumnModel().getColumn(i).setPreferredWidth( ( (Integer)gridColumnSizes.get(i)).intValue()); if (gridColumnAlignments.containsKey(gridColumns.get(i)) && !gridColumns.get(i).equals(attributeName)) { DefaultTableCellRenderer rend = new DefaultTableCellRenderer(); rend.setHorizontalAlignment( ((Integer)gridColumnAlignments.get(gridColumns.get(i))).intValue() ); getColumnModel().getColumn(i).setCellRenderer(rend); } } JTableHeader th = this.getTableHeader(); th.setPreferredSize(new Dimension(th.getPreferredSize().width,ClientSettings.HEADER_HEIGHT)); setRowHeight(ClientSettings.CELL_HEIGHT); setBackground(ClientSettings.GRID_CELL_BACKGROUND); setForeground(ClientSettings.GRID_CELL_FOREGROUND); setSelectionForeground(ClientSettings.GRID_SELECTION_FOREGROUND); setSelectionBackground(ClientSettings.GRID_SELECTION_BACKGROUND); } /** * Overridden to message super and forward the method to the tree. * Since the tree is not actually in the component hieachy it will * never receive this unless we forward it in this manner. */ public void updateUI() { super.updateUI(); if(tree != null) { tree.updateUI(); } // Use the tree's default foreground and background colors in the // table. LookAndFeel.installColorsAndFont(this, "Tree.background", "Tree.foreground", "Tree.font"); } /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to * paint the editor. The UI currently uses different techniques to * paint the renderers and editors and overriding setBounds() below * is not the right thing to do for an editor. Returning -1 for the * editing row in this case, ensures the editor is never painted. */ public int getEditingRow() { return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1 : editingRow; } /** * Overridden to pass the new rowHeight to the tree. */ public void setRowHeight(int rowHeight) { super.setRowHeight(rowHeight); if (tree != null && tree.getRowHeight() != rowHeight) { tree.setRowHeight(getRowHeight()); } } /** * Returns the tree that is being shared between the model. */ public JTree getTree() { return tree; } /** * @return optional attribute name that identifies the name of the image to show as tree node */ public final String getIconAttributeName() { return iconAttributeName; } /** * Optional attribute name that identifies the name of the image to show as tree node. */ public final void setIconAttributeName(String iconAttributeName) { this.iconAttributeName = iconAttributeName; } /** * A TreeCellRenderer that displays a JTree. */ public class TreeTableCellRenderer extends JTree implements TableCellRenderer { /** last table/tree row asked to renderer. */ protected int visibleRow; public TreeTableCellRenderer(TreeModel model) { super(model); } /** * updateUI is overridden to set the colors of the Tree's renderer * to match that of the table. */ public void updateUI() { super.updateUI(); // Make the tree's cell renderer use the table's cell selection // colors. TreeCellRenderer tcr = getCellRenderer(); if (tcr instanceof DefaultTreeCellRenderer) { DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer)tcr); // For 1.1 uncomment this, 1.2 has a bug that will cause an // exception to be thrown if the border selection color is // null. // dtcr.setBorderSelectionColor(null); dtcr.setTextSelectionColor(UIManager.getColor("Table.selectionForeground")); dtcr.setBackgroundSelectionColor(UIManager.getColor("Table.selectionBackground")); } } /** * Sets the row height of the tree, and forwards the row height to * the table. */ public void setRowHeight(int rowHeight) { if (rowHeight > 0) { super.setRowHeight(rowHeight); if (TreeGrid.this != null && TreeGrid.this.getRowHeight() != rowHeight) { TreeGrid.this.setRowHeight(getRowHeight()); } } } /** * This is overridden to set the height to match that of the JTable. */ public void setBounds(int x, int y, int w, int h) { super.setBounds(x, 0, w, TreeGrid.this.getHeight()); } /** * Sublcassed to translate the graphics such that the last visible * row will be drawn at 0,0. */ public void paint(Graphics g) { g.translate(0, -visibleRow * getRowHeight()); super.paint(g); } /** * TreeCellRenderer method. Overridden to update the visible row. */ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if(isSelected) setBackground(table.getSelectionBackground()); else setBackground(table.getBackground()); visibleRow = row; return this; } } /** * <p>Title: OpenSwing Framework</p> * <p>Description: Inner class used to render the tree cells.</p> * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p> * <p> </p> * @author Mauro Carniel * @version 1.0 */ public class TreeGridNodeRenderer extends DefaultTreeCellRenderer { ImageIcon folderIcon = null; ImageIcon leafIcon = null; TreePanel treePanel; /** formatter to use for this column (optional, may be null) */ private Format formatter; /** * Costructor. * @param tree node container */ public TreeGridNodeRenderer(String folderIconName,String leavesImageName,Format formatter) { try { this.treePanel = treePanel; this.formatter = formatter; folderIcon = new ImageIcon(ClientUtils.getImage(folderIconName)); leafIcon = new ImageIcon(ClientUtils.getImage(leavesImageName)); this.setOpaque(false); this.setBackgroundNonSelectionColor(new java.awt.Color(0,0,0,0)); } catch (Exception ex) { ex.printStackTrace(); } } public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { ValueObject vo = null; try { super.getTreeCellRendererComponent(tree, value, sel,expanded, leaf, row,hasFocus); DefaultMutableTreeNode node = (DefaultMutableTreeNode) value; vo = (ValueObject) node.getUserObject(); if (vo!=null && iconAttributeName!=null) { Method getter = vo.getClass().getMethod("get"+iconAttributeName.substring(0,1).toUpperCase()+iconAttributeName.substring(1),new Class[0]); value = getter.invoke(vo,new Object[0]); if (value!=null) { setIcon(new ImageIcon(ClientUtils.getImage(value.toString()))); } else { if (leaf) setIcon(leafIcon); else setIcon(folderIcon); } } else { if (leaf) setIcon(leafIcon); else setIcon(folderIcon); } } catch (Exception ex) { ex.printStackTrace(); } JLabel l = (JLabel)this; try { if (vo!=null) { Method getter = vo.getClass().getMethod("get"+attributeName.substring(0,1).toUpperCase()+attributeName.substring(1),new Class[0]); value = getter.invoke(vo,new Object[0]); if (value!=null) { if (formatter!=null) value = formatter.format(value); l.setText(value.toString()); if (gridColumnAlignments.get(attributeName)!=null) { ((JLabel)this).setHorizontalAlignment( ((Integer)gridColumnAlignments.get(attributeName)).intValue() ); } } } } catch (Throwable ex1) { ex1.printStackTrace(); } return l; } } // end inner-class /** * TreeTableCellEditor implementation. Component returned is the * JTree. */ public class TreeTableCellEditor extends AbstractCellEditor implements TableCellEditor { public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int r, int c) { return tree; } /** * Overridden to return false, and if the event is a mouse event * it is forwarded to the tree.<p> * The behavior for this is debatable, and should really be offered * as a property. By returning false, all keyboard actions are * implemented in terms of the table. By returning true, the * tree would get a chance to do something with the keyboard * events. For the most part this is ok. But for certain keys, * such as left/right, the tree will expand/collapse where as * the table focus should really move to a different column. Page * up/down should also be implemented in terms of the table. * By returning false this also has the added benefit that clicking * outside of the bounds of the tree node, but still in the tree * column will select the row, whereas if this returned true * that wouldn't be the case. * <p>By returning false we are also enforcing the policy that * the tree will never be editable (at least by a key sequence). */ public boolean isCellEditable(EventObject e) { if (e instanceof MouseEvent) { for (int counter = getColumnCount() - 1; counter >= 0; counter--) { if (getColumnClass(counter) == TreeTableModel.class) { MouseEvent me = (MouseEvent)e; MouseEvent newME = new MouseEvent(tree, me.getID(), me.getWhen(), me.getModifiers(), me.getX() - getCellRect(0, counter, true).x, me.getY(), me.getClickCount(), me.isPopupTrigger()); tree.dispatchEvent(newME); break; } } } return false; } } /** * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel * to listen for changes in the ListSelectionModel it maintains. Once * a change in the ListSelectionModel happens, the paths are updated * in the DefaultTreeSelectionModel. */ class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel { /** Set to true when we are updating the ListSelectionModel. */ protected boolean updatingListSelectionModel; public ListToTreeSelectionModelWrapper() { super(); getListSelectionModel().addListSelectionListener (createListSelectionListener()); } /** * Returns the list selection model. ListToTreeSelectionModelWrapper * listens for changes to this model and updates the selected paths * accordingly. */ ListSelectionModel getListSelectionModel() { return listSelectionModel; } /** * This is overridden to set <code>updatingListSelectionModel</code> * and message super. This is the only place DefaultTreeSelectionModel * alters the ListSelectionModel. */ public void resetRowSelection() { if(!updatingListSelectionModel) { updatingListSelectionModel = true; try { super.resetRowSelection(); } finally { updatingListSelectionModel = false; } } // Notice how we don't message super if // updatingListSelectionModel is true. If // updatingListSelectionModel is true, it implies the // ListSelectionModel has already been updated and the // paths are the only thing that needs to be updated. } /** * Creates and returns an instance of ListSelectionHandler. */ protected ListSelectionListener createListSelectionListener() { return new ListSelectionHandler(); } /** * If <code>updatingListSelectionModel</code> is false, this will * reset the selected paths from the selected rows in the list * selection model. */ protected void updateSelectedPathsFromSelectedRows() { if(!updatingListSelectionModel) { updatingListSelectionModel = true; try { // This is way expensive, ListSelectionModel needs an // enumerator for iterating. int min = listSelectionModel.getMinSelectionIndex(); int max = listSelectionModel.getMaxSelectionIndex(); clearSelection(); if(min != -1 && max != -1) { for(int counter = min; counter <= max; counter++) { if(listSelectionModel.isSelectedIndex(counter)) { TreePath selPath = tree.getPathForRow(counter); if(selPath != null) { addSelectionPath(selPath); } } } } } finally { updatingListSelectionModel = false; } } } /** * Class responsible for calling updateSelectedPathsFromSelectedRows * when the selection of the list changse. */ class ListSelectionHandler implements ListSelectionListener { public void valueChanged(ListSelectionEvent e) { updateSelectedPathsFromSelectedRows(); } } } }