package org.freehep.swing.treetable.test;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.NumberFormat;
import javax.swing.ButtonGroup;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.border.BevelBorder;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.tree.TreePath;
import org.freehep.swing.treetable.JTreeTable;
/**
* 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.getTree().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.getTree().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.getTree().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.getTree().getRowForPath(reloadPath);
}
}
}
/**
* A renderer that will give an indicator when a cell is being reloaded.
*/
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");
}
}
}
}