/*************************************************** * * cismet GmbH, Saarbruecken, Germany * * ... and it just works. * ****************************************************/ package de.cismet.tools.gui.treetable; /* * TreeTableExample2.java * * Copyright (c) 1998 Sun Microsystems, Inc. All Rights Reserved. * * This software is the confidential and proprietary information of Sun * Microsystems, Inc. ("Confidential Information"). You shall not * disclose such Confidential Information and shall use it only in * accordance with the terms of the license agreement you entered into * with Sun. * * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING * THIS SOFTWARE OR ITS DERIVATIVES. * */ import java.awt.*; import java.awt.event.*; import java.text.NumberFormat; import javax.swing.*; import javax.swing.border.*; import javax.swing.event.*; import javax.swing.table.*; import javax.swing.tree.*; /** * Assembles the UI. The UI consists of a JTreeTable and a status label. As nodes are loaded by the FileSystemModel2, in * a background thread, the status label updates as well as the renderer to draw the node that is being loaded * differently. * * @author Scott Violet * @author Philip Milne * @version $Revision$, $Date$ */ public class TreeTableExample2 { //~ Static fields/initializers --------------------------------------------- /** Number of instances of TreeTableExample2. */ protected static int ttCount; //~ Instance fields -------------------------------------------------------- /** Model for the JTreeTable. */ protected FileSystemModel2 model; /** Used to represent the model. */ protected JTreeTable treeTable; /** Row the is being reloaded. */ protected int reloadRow; /** TreePath being reloaded. */ protected TreePath reloadPath; /** A counter increment as the Timer fies and the same path is being reloaded. */ protected int reloadCounter; /** Timer used to update reload state. */ protected Timer timer; /** Used to indicate status. */ protected JLabel statusLabel; /** Frame containing everything. */ protected JFrame frame; /** Path created with. */ protected String path; //~ Constructors ----------------------------------------------------------- /** * Creates a new TreeTableExample2 object. * * @param path DOCUMENT ME! */ public TreeTableExample2(final String path) { this.path = path; ttCount++; frame = createFrame(); final Container cPane = frame.getContentPane(); final JMenuBar mb = createMenuBar(); model = createModel(path); treeTable = createTreeTable(); statusLabel = createStatusLabel(); cPane.add(new JScrollPane(treeTable)); cPane.add(statusLabel, BorderLayout.SOUTH); reloadRow = -1; frame.setJMenuBar(mb); frame.pack(); frame.show(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { reload(model.getRoot()); } }); } //~ Methods ---------------------------------------------------------------- /** * Creates and return a JLabel that is used to indicate the status of loading. * * @return DOCUMENT ME! */ protected JLabel createStatusLabel() { final JLabel retLabel = new JLabel(" "); // NOI18N retLabel.setHorizontalAlignment(JLabel.RIGHT); retLabel.setBorder(new BevelBorder(BevelBorder.LOWERED)); return retLabel; } /** * Creates and returns the instanceof JTreeTable that will be used. This also creates, but does not start, the Timer * that is used to update the display as files are loaded. * * @return DOCUMENT ME! */ protected JTreeTable createTreeTable() { final JTreeTable treeTable = new JTreeTable(model); treeTable.getColumnModel().getColumn(1).setCellRenderer(new IndicatorRenderer()); final Reloader rl = new Reloader(); timer = new Timer(700, rl); timer.setRepeats(true); treeTable.getTree().addTreeExpansionListener(rl); return treeTable; } /** * Creates the FileSystemModel2 that will be used. * * @param path DOCUMENT ME! * * @return DOCUMENT ME! */ protected FileSystemModel2 createModel(final String path) { return new FileSystemModel2(path); } /** * Creates the JFrame that will contain everything. * * @return DOCUMENT ME! */ protected JFrame createFrame() { final JFrame retFrame = new JFrame(org.openide.util.NbBundle.getMessage( TreeTableExample2.class, "TreeTableExample2.createFrame().retFrame.title")); // NOI18N retFrame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(final WindowEvent we) { if (--ttCount == 0) { System.exit(0); } } }); return retFrame; } /** * Creates a menu bar. * * @return DOCUMENT ME! */ protected JMenuBar createMenuBar() { final JMenu fileMenu = new JMenu(org.openide.util.NbBundle.getMessage( TreeTableExample2.class, "TreeTableExample2.createMenuBar().fileMenu.title")); // NOI18N JMenuItem menuItem; menuItem = new JMenuItem(org.openide.util.NbBundle.getMessage( TreeTableExample2.class, "TreeTableExample2.createMenuBar().menuItem.text.open")); // NOI18N menuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent ae) { final JFileChooser fc = new JFileChooser(path); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); final int result = fc.showOpenDialog(frame); if (result == JFileChooser.APPROVE_OPTION) { final String newPath = fc.getSelectedFile().getPath(); new TreeTableExample2(newPath); } } }); fileMenu.add(menuItem); fileMenu.addSeparator(); menuItem = new JMenuItem(org.openide.util.NbBundle.getMessage( TreeTableExample2.class, "TreeTableExample2.createMenuBar().menuItem.text.reload")); // NOI18N menuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent ae) { final TreePath path = treeTable.getTree().getSelectionPath(); if (path != null) { model.stopLoading(); reload(path.getLastPathComponent()); } } }); fileMenu.add(menuItem); menuItem = new JMenuItem(org.openide.util.NbBundle.getMessage( TreeTableExample2.class, "TreeTableExample2.createMenuBar().menuItem.text.stop")); // NOI18N menuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent ae) { model.stopLoading(); } }); fileMenu.add(menuItem); fileMenu.addSeparator(); menuItem = new JMenuItem(org.openide.util.NbBundle.getMessage( TreeTableExample2.class, "TreeTableExample2.createMenuBar().menuItem.text.exit")); // NOI18N menuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent ae) { System.exit(0); } }); fileMenu.add(menuItem); // Create a menu bar final JMenuBar menuBar = new JMenuBar(); menuBar.add(fileMenu); // Menu for the look and feels (lafs). final UIManager.LookAndFeelInfo[] lafs = UIManager.getInstalledLookAndFeels(); final ButtonGroup lafGroup = new ButtonGroup(); final JMenu optionsMenu = new JMenu(org.openide.util.NbBundle.getMessage( TreeTableExample2.class, "TreeTableExample2.createMenuBar().optionsMenu.title")); // NOI18N menuBar.add(optionsMenu); for (int i = 0; i < lafs.length; i++) { final JRadioButtonMenuItem rb = new JRadioButtonMenuItem(lafs[i].getName()); optionsMenu.add(rb); rb.setSelected(UIManager.getLookAndFeel().getName().equals(lafs[i].getName())); rb.putClientProperty("UIKey", lafs[i]); // NOI18N rb.addItemListener(new ItemListener() { @Override public void itemStateChanged(final ItemEvent ae) { final JRadioButtonMenuItem rb2 = (JRadioButtonMenuItem)ae.getSource(); if (rb2.isSelected()) { final UIManager.LookAndFeelInfo info = (UIManager.LookAndFeelInfo)rb2.getClientProperty( "UIKey"); // NOI18N try { UIManager.setLookAndFeel(info.getClassName()); SwingUtilities.updateComponentTreeUI(frame); } catch (Exception e) { System.err.println("unable to set UI " // NOI18N + e.getMessage()); } } } }); lafGroup.add(rb); } return menuBar; } /** * Invoked to reload the children of a particular node. This will also restart the timer. * * @param node DOCUMENT ME! */ protected void reload(final Object node) { model.reloadChildren(node); if (!timer.isRunning()) { timer.start(); } } /** * Updates the status label based on reloadRow. */ protected void updateStatusLabel() { if (reloadPath != null) { statusLabel.setText(org.openide.util.NbBundle.getMessage( TreeTableExample2.class, "TreeTableExample2.statusLabel.text.reload", model.getPath(reloadPath.getLastPathComponent()))); if ((reloadCounter % 4) < 2) { statusLabel.setForeground(Color.red); } else { statusLabel.setForeground(Color.blue); } } else if (!model.isReloading()) { statusLabel.setText(org.openide.util.NbBundle.getMessage( TreeTableExample2.class, "TreeTableExample2.statusLabel.text.default", NumberFormat.getInstance().format(model.getTotalSize(model.getRoot())))); statusLabel.setForeground(Color.black); } } /** * DOCUMENT ME! * * @param args DOCUMENT ME! */ public static void main(final String[] args) { if (args.length > 0) { for (int counter = args.length - 1; counter >= 0; counter--) { new TreeTableExample2(args[counter]); } } else { String path; try { path = System.getProperty("user.home"); // NOI18N if (path != null) { new TreeTableExample2(path); } } catch (SecurityException se) { path = null; } if (path == null) { System.out.println("Could not determine home directory"); // NOI18N } } } //~ Inner Classes ---------------------------------------------------------- /** * Reloader is the ActionListener used in the Timer. In response to the timer updating it will reset the * reloadRow/reloadPath and generate the necessary event so that the display will update. It also implements the * TreeExpansionListener so that if the tree is altered while loading the reloadRow is updated accordingly. * * @version $Revision$, $Date$ */ class Reloader implements ActionListener, TreeExpansionListener { //~ Methods ------------------------------------------------------------ @Override public void actionPerformed(final ActionEvent ae) { if (!model.isReloading()) { // No longer loading. timer.stop(); if (reloadRow != -1) { generateChangeEvent(reloadRow); } reloadRow = -1; reloadPath = null; } else { // Still loading, see if paths changed. final TreePath newPath = model.getPathLoading(); if (newPath == null) { // Hmm... Will usually indicate the reload thread // completed between time we asked if reloading. if (reloadRow != -1) { generateChangeEvent(reloadRow); } reloadRow = -1; reloadPath = null; } else { // Ok, valid path, see if matches last path. final int newRow = treeTable.getTree().getRowForPath(newPath); if (newPath.equals(reloadPath)) { reloadCounter = (reloadCounter + 1) % 8; if (newRow != reloadRow) { final int lastRow = reloadRow; reloadRow = newRow; generateChangeEvent(lastRow); } generateChangeEvent(reloadRow); } else { final int lastRow = reloadRow; reloadCounter = 0; reloadRow = newRow; reloadPath = newPath; if (lastRow != reloadRow) { generateChangeEvent(lastRow); } generateChangeEvent(reloadRow); } } } updateStatusLabel(); } /** * Generates and update event for the specified row. FileSystemModel2 could do this, but it would not know when * the row has changed as a result of expanding/collapsing nodes in the tree. * * @param row DOCUMENT ME! */ protected void generateChangeEvent(final int row) { if (row != -1) { final AbstractTableModel tModel = (AbstractTableModel)treeTable.getModel(); tModel.fireTableChanged(new TableModelEvent(tModel, row, row, 1)); } } // // TreeExpansionListener // /** * Invoked when the tree has expanded. * * @param te DOCUMENT ME! */ @Override public void treeExpanded(final TreeExpansionEvent te) { updateRow(); } /** * Invoked when the tree has collapsed. * * @param te DOCUMENT ME! */ @Override public void treeCollapsed(final TreeExpansionEvent te) { updateRow(); } /** * Updates the reloadRow and path, this does not genernate a change event. */ protected void updateRow() { reloadPath = model.getPathLoading(); if (reloadPath != null) { reloadRow = treeTable.getTree().getRowForPath(reloadPath); } } } /** * A renderer that will give an indicator when a cell is being reloaded. * * @version $Revision$, $Date$ */ class IndicatorRenderer extends DefaultTableCellRenderer { //~ Instance fields ---------------------------------------------------- /** Makes sure the number of displayed in an internationalized manner. */ protected NumberFormat formatter; /** Row that is currently being painted. */ protected int lastRow; //~ Constructors ------------------------------------------------------- /** * Creates a new IndicatorRenderer object. */ IndicatorRenderer() { setHorizontalAlignment(JLabel.RIGHT); formatter = NumberFormat.getInstance(); } //~ Methods ------------------------------------------------------------ /** * Invoked as part of DefaultTableCellRenderers implemention. Sets the text of the label. * * @param value DOCUMENT ME! */ @Override public void setValue(final Object value) { setText((value == null) ? "---" : formatter.format(value)); // NOI18N } /** * Returns this. * * @param table DOCUMENT ME! * @param value DOCUMENT ME! * @param isSelected DOCUMENT ME! * @param hasFocus DOCUMENT ME! * @param row DOCUMENT ME! * @param column DOCUMENT ME! * * @return DOCUMENT ME! */ @Override public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) { super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); lastRow = row; return this; } /** * If the row being painted is also being reloaded this will draw a little indicator. * * @param g DOCUMENT ME! */ @Override public void paint(final Graphics g) { if (lastRow == reloadRow) { final int width = getWidth(); final int height = getHeight(); g.setColor(getBackground()); g.fillRect(0, 0, width, height); g.setColor(getForeground()); final int diameter = Math.min(width, height); if (reloadCounter < 5) { g.fillArc((width - diameter) / 2, (height - diameter) / 2, diameter, diameter, 90, -(reloadCounter * 90)); } else { g.fillArc((width - diameter) / 2, (height - diameter) / 2, diameter, diameter, 90, (4 - (reloadCounter % 4)) * 90); } } else { super.paint(g); } } } }