package iiuf.swing; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.awt.GridLayout; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.Component; import java.awt.Graphics; import java.awt.Color; import java.awt.Cursor; import java.awt.Point; import java.awt.Rectangle; import java.awt.dnd.DragGestureRecognizer; import java.awt.dnd.DragGestureListener; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetListener; import java.awt.dnd.DragSource; import java.awt.event.MouseListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.JPanel; import javax.swing.JList; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JScrollPane; import javax.swing.JLabel; import javax.swing.DefaultListCellRenderer; import javax.swing.ImageIcon; import javax.swing.UIManager; import javax.swing.JViewport; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import javax.swing.tree.TreeNode; import javax.swing.event.ChangeListener; import javax.swing.event.ChangeEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionEvent; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import iiuf.awt.Awt; import iiuf.util.ProgressListener; import iiuf.util.ProgressWatcher; import iiuf.util.EventListenerList; /** Spilt pane tree view implementation, looks like the NeXTSTEP browser. (c) 2000, 2001, IIUF, DIUF<p> @author $Author: ohitz $ @version $Name: $ $Revision: 1.1 $ */ public class SplitPaneTreeView extends JPanel implements ProgressListener, TreeView { final static ImageIcon ICON = Resource.RIGHTARROW; final static Border NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1); public static final int KEEP_PANE_COUNT = 0; public static final int KEEP_PANE_WIDTH = 1; private static final int MIN_PANE_WIDTH = 30; Object[] currentPath = new Object[0]; int lastPathComponentPane; JPanel browser = new JPanel(); ButtonTreePathView pathView = new ButtonTreePathView(null); int minPaneWidth = 100; int resizePolicy = KEEP_PANE_COUNT; int paneCount; ContextMenuManager ctxmgr; Controler controler; Object viewRoot; EventListenerList listeners = new EventListenerList(); public SplitPaneTreeView() { this(null, -1); } public SplitPaneTreeView(int paneCount) { this(null, paneCount); } public SplitPaneTreeView(TreeModel tree, int paneCount) { this(tree, null, paneCount); } public SplitPaneTreeView(TreeModel tree, TreePath rootPath, int paneCount) { setLayout(new BorderLayout()); JScrollPane pathSP = new JScrollPane(pathView); pathSP.setHorizontalScrollBarPolicy(pathSP.HORIZONTAL_SCROLLBAR_ALWAYS); pathSP.setVerticalScrollBarPolicy(pathSP.VERTICAL_SCROLLBAR_NEVER); add(pathSP, BorderLayout.NORTH); if(tree != null) setModel(tree, rootPath); setPaneCount(paneCount); add(browser, BorderLayout.CENTER); refresh(); } //------ public methods public SplitPaneTreeView setResizePolicy(int policy) { resizePolicy = policy; return this; } public void setMinPaneWidth(int width) { setResizePolicy(KEEP_PANE_WIDTH); minPaneWidth = width < MIN_PANE_WIDTH ? MIN_PANE_WIDTH : width; setPaneCount(getWidth() / minPaneWidth); } public void setPaneCount(int count) { int oldPaneCount = paneCount; if(count == paneCount) return; paneCount = count; if(paneCount < 2) paneCount = 2; oldPaneCount -= paneCount; if(oldPaneCount > 0) { // reduce number of panes // first we remove panes on the right hand side of the lastPathComponentPane // then we remove at the left end. for(; oldPaneCount > 0; oldPaneCount--) { if(lastPathComponentPane < paneCount) removePane(paneCount + oldPaneCount - 1); // remove on right hand side else { removePane(0); // remove on left hand side lastPathComponentPane--; } } } else { oldPaneCount = -oldPaneCount; // increase number of panes // first we insert on the lft hand side // then on the right hand side int maxPathLength = distance(currentPath, getViewRoot(), currentPath[currentPath.length - 1]); for(; oldPaneCount > 0; oldPaneCount--) { if(lastPathComponentPane < maxPathLength) { browser.add(getScrollPane(new NodeList(currentPath[currentPath.length - lastPathComponentPane - 2])), 0); lastPathComponentPane++; } else browser.add(getScrollPane(new NodeList(null))); } } browser.setLayout(new GridLayout(1, paneCount)); } //------ various overrides public void setSize(int w, int h) { super.setSize(w, h); handleResize(w, h); } public void setBounds(int x, int y, int w, int h) { super.setBounds(x, y, w, h); handleResize(w, h); } //------ private stuff private TreePath currentPathTo(Object node) { TreePath result = new TreePath(currentPath[0]); if(node == currentPath[0]) return result; for(int i = 1; i < currentPath.length; i++) { result = result.pathByAddingChild(currentPath[i]); if(node == currentPath[i]) return result; } throw new IllegalArgumentException("node " + node + " not in " + new TreePath(currentPath)); } private void removePane(int idx) { nodeListAt(idx).dispose(); browser.remove(idx); } private NodeList nodeListAt(int idx) { return (NodeList)((JScrollPane)browser.getComponent(idx)).getViewport().getView(); } private JScrollPane getScrollPane(NodeList list) { JScrollPane result = new JScrollPane(list); result.setHorizontalScrollBarPolicy(result.HORIZONTAL_SCROLLBAR_NEVER); result.setVerticalScrollBarPolicy(result.VERTICAL_SCROLLBAR_ALWAYS); result.getViewport().getView().setBackground(new Color(0xCCCCCC)); return result; } private void handleResize(int w, int h) { if(resizePolicy == KEEP_PANE_WIDTH) setPaneCount(w / minPaneWidth); } private void setView(TreePath path, int lastPathComponentPane_) { currentPath = path.getPath(); lastPathComponentPane = lastPathComponentPane_; // make sure index is visible if(lastPathComponentPane >= paneCount) lastPathComponentPane = paneCount - 1; // makes user index does not underrun if(lastPathComponentPane < 0) lastPathComponentPane = 0; // make sure we have enough path to display if(lastPathComponentPane >= distance(currentPath, getViewRoot(), currentPath[currentPath.length - 1])) lastPathComponentPane = distance(currentPath, getViewRoot(), currentPath[currentPath.length - 1]); refresh(); } private void refresh() { int start = currentPath.length - lastPathComponentPane - 1; for(int i = 0; i < paneCount; i++) nodeListAt(i).setNode(start + i < currentPath.length ? currentPath[start + i] : null); } private int distance(Object[] path, Object fromNode, Object toNode) { int start = 0; try { while(path[start] != fromNode) start++; } catch(ArrayIndexOutOfBoundsException e) { throw new IllegalArgumentException("fromNode " + fromNode + " not in path:" + new TreePath(path)); } int end = start; try{while(path[end] != toNode) end++;} catch(ArrayIndexOutOfBoundsException e) { throw new IllegalArgumentException("fromNode " + fromNode + " not in path:" + new TreePath(path)); } return end - start; } private TreePath getCommonAncestor(Object[] p1, Object[] p2) { TreePath result = new TreePath(p1[0]); if(p1[0] != p2[0]) throw new IllegalArgumentException("root not common:" + new TreePath(p1) + ", " + new TreePath(p2)); for(int i = 1; ; i++) { if(i >= p1.length || i >= p2.length) return result; if(p1[i] != p2[i]) return result; else result = result.pathByAddingChild(p1[i]); } } private int getPaneIndexForNode(Object node) { for(int i = 0; i < paneCount; i++) if(nodeListAt(i).node == node) return i; return -1; } private void setPath(TreePath path) { Object leaf = null; if(path.getLastPathComponent() != getViewRoot() && controler.tree.isLeaf(path.getLastPathComponent())) { leaf = path.getLastPathComponent(); path = path.getParentPath(); } TreePath cap = getCommonAncestor(currentPath, path.getPath()); int idx = getPaneIndexForNode(cap.getLastPathComponent()); if(idx >= 0) setView(path, idx + distance(path.getPath(), cap.getLastPathComponent(), path.getLastPathComponent())); else setView(path, path.getPathCount()); if(leaf != null) nodeListAt(getPaneIndexForNode(path.getLastPathComponent())).makeVisible(leaf); pathView.setPath(path); refresh(); } private void select(TreePath path) { Object[] p = path.getPath(); for(int j = 0; j < paneCount; j++) { NodeList nl = nodeListAt(j); for(int i = 0; i < p.length - 1; i++) { if(p[i] == nl.node) nl.select(p[i + 1]); } } } //------ TreeView implementation public Component getComponent() { return browser; } public void setContextMenuManager(ContextMenuManager manager) { ctxmgr = manager; } public Object locationToObject(Component component, Point location) { if(!(component instanceof JList)) return null; int idx = ((JList)component).locationToIndex(location); return idx == -1 ? null : ((JList)component).getModel().getElementAt(idx); } public void addRootNodeChangeListener(ChangeListener l) { listeners.add(ChangeListener.class, l); } public void addRootNodeChangeListener(ChangeListener l, boolean weak) { listeners.add(ChangeListener.class, l, weak); } public void removeRootNodeChangeListener(ChangeListener l) { listeners.remove(ChangeListener.class, l); } public Object getViewRoot() { return viewRoot; } public void clearSelection() { for(int i = 0; i < paneCount; i++) nodeListAt(i).clearSelection(); } public TreeModel getModel() { return controler == null ? null : controler.tree; } public TreePath getMostVisiblePath() { TreePath result = new TreePath(currentPath); Object node = result.getLastPathComponent(); if(controler.tree.isLeaf(node) || controler.tree.getChildCount(node) == 0) return result; else return result.pathByAddingChild(controler.tree.getChild(node, 0)); } public TreePath getSelectionPath() { TreePath[] tp = getSelectionPaths(); return tp.length > 0 ? tp[0] : null; } public TreePath[] getSelectionPaths() { HashSet selection = new HashSet(); for(int i = 0; i < paneCount; i++) { NodeList l = nodeListAt(i); TreePath p = l.getPath(); if(p == null) break; int[] idxs = l.getSelectedIndices(); for(int j = 0; j < idxs.length; j++) if(idxs[j] < l.getModel().getSize()) selection.add(p.pathByAddingChild(l.getModel().getElementAt(idxs[j]))); } TreePath[] tmp = (TreePath[])selection.toArray(new TreePath[selection.size()]); for(int i = 0; i < tmp.length; i++) if(selection.contains(tmp[i].getParentPath())) selection.remove(tmp[i].getParentPath()); return (TreePath[])selection.toArray(new TreePath[selection.size()]); } public boolean isVisible(TreePath path) { for(int j = 0; j < paneCount; j++) if(path.getLastPathComponent().equals(nodeListAt(j).node)) return true; return false; } public void makeVisible(TreePath path) { if(path == null) return; if(isVisible(path)) return; setPath(path); } public void setModel(TreeModel model) { setModel(model, null); } public void setModel(TreeModel model, TreePath viewRootPath) { for(int i = 0; i < paneCount; i++) nodeListAt(i).setNode(null); if(viewRootPath == null) viewRootPath = new TreePath(model.getRoot()); if(controler != null) controler.dispose(); controler = new Controler(model); viewRoot = viewRootPath.getLastPathComponent(); setView(viewRootPath, 0); pathView.setModel(model); pathView.setPath(viewRootPath, true); } public void setSelectionPath(TreePath path) { if(path == null) return; setSelectionPaths(new TreePath[] {path}); } public void setSelectionPaths(TreePath[] path) { if(path == null || path.length == 0) return; clearSelection(); TreePath maxPath = path[0]; for(int i = 0; i < path.length; i++) if(path[i].getPathCount() > maxPath.getPathCount()) maxPath = path[i]; makeVisible(maxPath); for(int i = 0; i < path.length; i++) select(path[i]); } public void operationStart(String desc) {} public void operationProgress(int amount, int of) { if(amount == 0) Awt.setCursor(this, Cursor.WAIT_CURSOR); } public void operationStop() { Awt.setCursor(this, Cursor.DEFAULT_CURSOR); } DragGestureListener srcListener; int srcActions; public void enableDrag(int sourceActions, DragGestureListener sourceListener) { disableDrag(); for(int i = 0; i < paneCount; i++) nodeListAt(i).enableDrag(sourceActions, sourceListener); srcListener = sourceListener; srcActions = sourceActions; } DropTargetListener trgtListener; int trgtActions; public void enableDrop(int targetActions, DropTargetListener targetListener) { disableDrop(); for(int i = 0; i < paneCount; i++) nodeListAt(i).enableDrop(targetActions, targetListener); trgtListener = targetListener; trgtActions = targetActions; } public void disableDrag() { for(int i = 0; i < paneCount; i++) nodeListAt(i).disableDrag(); srcListener = null; System.gc(); } public void disableDrop() { for(int i = 0; i < paneCount; i++) nodeListAt(i).disableDrop(); trgtListener = null; System.gc(); } //------ helper classes class Controler implements ChangeListener { ListTreeModel tree; Controler(TreeModel model) { tree = new ListTreeModel(model); pathView.addChangeListener(this); } void dispose() { pathView.removeChangeListener(this); } public void stateChanged(ChangeEvent e) { if(e.getSource() == pathView) setPath(pathView.getPath()); } } ListSelectionListener lsl = Swing.asyncWrapper(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { NodeList l = (NodeList)e.getSource(); if(!e.getValueIsAdjusting()) { int[] idxs = l.getSelectedIndices(); if(idxs.length == 1) { ProgressWatcher.watch(SplitPaneTreeView.this); if(!controler.tree.isLeaf(l.getModel().getElementAt(idxs[0]))) { SplitPaneTreeView.this.makeVisible(currentPathTo(l.node).pathByAddingChild(l.getModel().getElementAt(idxs[0]))); } } } } }); MouseListener ml = new MouseAdapter() { public void mouseReleased(MouseEvent e) { NodeList l = (NodeList)e.getSource(); int idx = l.locationToIndex(e.getPoint()); if(idx == -1) idx = 0; else idx = 1; for(int i = getPaneIndexForNode(l.node) + idx; i < SplitPaneTreeView.this.paneCount; i++) nodeListAt(i).clearSelection(); } }; class NodeList extends JList { int minSelected = Integer.MAX_VALUE; Rectangle r = new Rectangle(0, 0, 1, 1); Object node; NodeList(Object node) { setNode(node); setCellRenderer(new CellRenderer(this)); if(srcListener != null) enableDrag(srcActions, srcListener); if(trgtListener != null) enableDrop(trgtActions, trgtListener); addListSelectionListener(lsl); addMouseListener(ml); } void dispose() { disableDrag(); disableDrop(); removeListSelectionListener(lsl); removeMouseListener(ml); } void setNode(Object node_) { if(node_ == node) return; node = node_; ProgressWatcher.watch(SplitPaneTreeView.this); this.setModel(node == null ? new DefaultListModel() : controler.tree.getListModel(node, true)); } void makeVisible(Object child) { r.setLocation(indexToLocation(controler.tree.getIndexOfChild(node, child))); scrollRectToVisible(r); } void select(Object node) { int count = this.getModel().getSize(); for(int i = 0; i < count; i++) if(this.getModel().getElementAt(i).equals(node)) { getSelectionModel().addSelectionInterval(i, i); if(i < minSelected) { ensureIndexIsVisible(i); minSelected = i; } } } public void clearSelection() { minSelected = Integer.MAX_VALUE; super.clearSelection(); } public String toString() { return NodeList.this.getModel().toString(); } public TreePath getPath() { if(node == null) return null; TreePath result = new TreePath(currentPath[0]); if(currentPath[0] == node) return result; for(int i = 1; ; i++) { result = result.pathByAddingChild(currentPath[i]); if(currentPath[i] == node) return result; } } DragGestureRecognizer sourceRecognizer; void enableDrag(int sourceActions, DragGestureListener sourceListener) { sourceRecognizer = DragSource.getDefaultDragSource(). createDefaultDragGestureRecognizer(this, sourceActions, sourceListener); } DropTarget dropTarget; void enableDrop(int targetActions, DropTargetListener targetListener) { dropTarget = new DropTarget(this, targetActions, targetListener); } public void disableDrag() { if(sourceRecognizer != null) sourceRecognizer.removeDragGestureListener(srcListener); sourceRecognizer = null; } public void disableDrop() { if(dropTarget != null) dropTarget.removeDropTargetListener(trgtListener); dropTarget = null; } } class CellRenderer extends DefaultListCellRenderer { boolean isLeaf; NodeList list; public CellRenderer(NodeList list_) { super(); list = list_; setOpaque(true); setBorder(NO_FOCUS_BORDER); } public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { NodeList nl = (NodeList)list; isLeaf = nl.node == null ? true : controler.tree.isLeaf(controler.tree.getChild(nl.node, index)); return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); } public void paintComponent(Graphics g) { super.paintComponent(g); if(!isLeaf) ICON.paintIcon(this, g, ((JViewport)list.getParent()).getExtentSize().width - ICON.getIconWidth(), (getHeight() - ICON.getIconHeight()) / 2); } } } /* $Log: SplitPaneTreeView.java,v $ Revision 1.1 2002/07/11 12:09:52 ohitz Initial checkin Revision 1.10 2001/03/07 17:36:28 schubige soundium properties panel beta Revision 1.9 2001/01/14 13:21:13 schubige Win NT update Revision 1.8 2001/01/12 08:26:21 schubige TJGUI update and some TreeView bug fixes Revision 1.7 2001/01/04 16:28:39 schubige Header update for 2001 and DIUF Revision 1.6 2001/01/03 08:30:39 schubige graph stuff beta Revision 1.5 2000/11/09 07:48:44 schubige early checkin for DCJava Revision 1.4 2000/10/10 16:32:12 schubige Added subtree display to TreeView, fixed some bugs Revision 1.3 2000/10/03 08:39:39 schubige Added tree view and contect menu stuff Revision 1.2 2000/08/17 16:34:22 schubige Fixed SplitPaneTreeView icon bug Revision 1.1 2000/08/17 16:22:15 schubige Swing cleanup & TreeView added */