package javax.swing.treetable.example;
/*
* 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 javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.*;
import java.text.NumberFormat;
import org.mypsycho.swing.treetable.*;
/**
* 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
*/
public class TreeTableExample2 {
/** Number of instances of TreeTableExample2. */
protected static int ttCount;
/** 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;
public TreeTableExample2(String path) {
this.path = path;
ttCount++;
frame = createFrame();
Container cPane = frame.getContentPane();
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.setVisible(true);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
reload(model.getRoot());
}
});
}
/**
* Creates and return a JLabel that is used to indicate the status
* of loading.
*/
protected JLabel createStatusLabel() {
JLabel retLabel = new JLabel(" ");
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.
*/
protected JTreeTable createTreeTable() {
JTreeTable treeTable = new JTreeTable(model);
treeTable.getColumnModel().getColumn(1).setCellRenderer
(new IndicatorRenderer());
Reloader rl = new Reloader();
timer = new Timer(700, rl);
timer.setRepeats(true);
treeTable.getTreeRenderer().addTreeExpansionListener(rl);
return treeTable;
}
/**
* Creates the FileSystemModel2 that will be used.
*/
protected FileSystemModel2 createModel(String path) {
return new FileSystemModel2(path);
}
/**
* Creates the JFrame that will contain everything.
*/
protected JFrame createFrame() {
JFrame retFrame = new JFrame("TreeTable II");
retFrame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent we) {
if (--ttCount == 0) {
System.exit(0);
}
}
});
return retFrame;
}
/**
* Creates a menu bar.
*/
protected JMenuBar createMenuBar() {
JMenu fileMenu = new JMenu("File");
JMenuItem menuItem;
menuItem = new JMenuItem("Open");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
JFileChooser fc = new JFileChooser(path);
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
int result = fc.showOpenDialog(frame);
if (result == JFileChooser.APPROVE_OPTION) {
String newPath = fc.getSelectedFile().getPath();
new TreeTableExample2(newPath);
}
}
});
fileMenu.add(menuItem);
fileMenu.addSeparator();
menuItem = new JMenuItem("Reload");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
TreePath path = treeTable.getTreeRenderer().getSelectionPath();
if (path != null) {
model.stopLoading();
reload(path.getLastPathComponent());
}
}
});
fileMenu.add(menuItem);
menuItem = new JMenuItem("Stop");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
model.stopLoading();
}
});
fileMenu.add(menuItem);
fileMenu.addSeparator();
menuItem = new JMenuItem("Exit");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
System.exit(0);
}
});
fileMenu.add(menuItem);
// Create a menu bar
JMenuBar menuBar = new JMenuBar();
menuBar.add(fileMenu);
// Menu for the look and feels (lafs).
UIManager.LookAndFeelInfo[] lafs = UIManager.
getInstalledLookAndFeels();
ButtonGroup lafGroup = new ButtonGroup();
JMenu optionsMenu = new JMenu("Options");
menuBar.add(optionsMenu);
for(int i = 0; i < lafs.length; i++) {
JRadioButtonMenuItem rb = new JRadioButtonMenuItem(lafs[i].
getName());
optionsMenu.add(rb);
rb.setSelected(UIManager.getLookAndFeel().getName().equals
(lafs[i].getName()));
rb.putClientProperty("UIKey", lafs[i]);
rb.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent ae) {
JRadioButtonMenuItem rb2 = (JRadioButtonMenuItem)ae.
getSource();
if(rb2.isSelected()) {
UIManager.LookAndFeelInfo info =
(UIManager.LookAndFeelInfo)
rb2.getClientProperty("UIKey");
try {
UIManager.setLookAndFeel(info.getClassName());
SwingUtilities.updateComponentTreeUI(frame);
}
catch (Exception e) {
System.err.println("unable to set UI " +
e.getMessage());
}
}
}
});
lafGroup.add(rb);
}
return menuBar;
}
/**
* Invoked to reload the children of a particular node. This will
* also restart the timer.
*/
protected void reload(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("Reloading: " + model.getPath
(reloadPath.getLastPathComponent()));
if ((reloadCounter % 4) < 2) {
statusLabel.setForeground(Color.red);
}
else {
statusLabel.setForeground(Color.blue);
}
}
else if (!model.isReloading()) {
statusLabel.setText("Total Size: " + NumberFormat.getInstance().
format(model.getTotalSize(model.getRoot())));
statusLabel.setForeground(Color.black);
}
}
/**
* 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.
*/
class Reloader implements ActionListener, TreeExpansionListener {
public void actionPerformed(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.
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.
int newRow = treeTable.getTreeRenderer().getRowForPath
(newPath);
if (newPath.equals(reloadPath)) {
reloadCounter = (reloadCounter + 1) % 8;
if (newRow != reloadRow) {
int lastRow = reloadRow;
reloadRow = newRow;
generateChangeEvent(lastRow);
}
generateChangeEvent(reloadRow);
}
else {
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.
*/
protected void generateChangeEvent(int row) {
if (row != -1) {
AbstractTableModel tModel = (AbstractTableModel)treeTable.
getModel();
tModel.fireTableChanged(new TableModelEvent
(tModel, row, row, 1));
}
}
//
// TreeExpansionListener
//
/**
* Invoked when the tree has expanded.
*/
public void treeExpanded(TreeExpansionEvent te) {
updateRow();
}
/**
* Invoked when the tree has collapsed.
*/
public void treeCollapsed(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.getTreeRenderer().getRowForPath(reloadPath);
}
}
}
/**
* A renderer that will give an indicator when a cell is being reloaded.
*/
@SuppressWarnings("serial")
class IndicatorRenderer extends DefaultTableCellRenderer {
/** Makes sure the number of displayed in an internationalized
* manner. */
protected NumberFormat formatter;
/** Row that is currently being painted. */
protected int lastRow;
IndicatorRenderer() {
setHorizontalAlignment(JLabel.RIGHT);
formatter = NumberFormat.getInstance();
}
/**
* Invoked as part of DefaultTableCellRenderers implemention. Sets
* the text of the label.
*/
public void setValue(Object value) {
setText((value == null) ? "---" : formatter.format(value));
}
/**
* Returns this.
*/
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus,
int row, 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.
*/
public void paint(Graphics g) {
if (lastRow == reloadRow) {
int width = getWidth();
int height = getHeight();
g.setColor(getBackground());
g.fillRect(0, 0, width, height);
g.setColor(getForeground());
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);
}
}
}
public static void main(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");
if (path != null) {
new TreeTableExample2(path);
}
}
catch (SecurityException se) {
path = null;
}
if (path == null) {
System.out.println("Could not determine home directory");
}
}
}
}