diff options
Diffstat (limited to 'libjava/classpath/javax/swing/tree')
8 files changed, 880 insertions, 590 deletions
diff --git a/libjava/classpath/javax/swing/tree/AbstractLayoutCache.java b/libjava/classpath/javax/swing/tree/AbstractLayoutCache.java index 155343f5bcc..4a6899fbeae 100644 --- a/libjava/classpath/javax/swing/tree/AbstractLayoutCache.java +++ b/libjava/classpath/javax/swing/tree/AbstractLayoutCache.java @@ -149,9 +149,11 @@ public abstract class AbstractLayoutCache protected Rectangle getNodeDimensions(Object value, int row, int depth, boolean expanded, Rectangle bounds) { - if (nodeDimensions == null) - throw new InternalError("The NodeDimensions are not set"); - return nodeDimensions.getNodeDimensions(value, row, depth, expanded, bounds); + Rectangle d = null; + if (nodeDimensions != null) + d = nodeDimensions.getNodeDimensions(value, row, depth, expanded, + bounds); + return d; } /** @@ -224,7 +226,12 @@ public abstract class AbstractLayoutCache */ public void setSelectionModel(TreeSelectionModel model) { + if (treeSelectionModel != null) + treeSelectionModel.setRowMapper(null); treeSelectionModel = model; + if (treeSelectionModel != null) + treeSelectionModel.setRowMapper(this); + } /** @@ -337,7 +344,7 @@ public abstract class AbstractLayoutCache * * @return Enumeration */ - public abstract Enumeration getVisiblePathsFrom(TreePath path); + public abstract Enumeration<TreePath> getVisiblePathsFrom(TreePath path); /** * getVisibleChildCount @@ -425,9 +432,13 @@ public abstract class AbstractLayoutCache */ public int[] getRowsForPaths(TreePath[] paths) { - int[] rows = new int[paths.length]; - for (int i = 0; i < rows.length; i++) - rows[i] = getRowForPath(paths[i]); + int[] rows = null; + if (paths != null) + { + rows = new int[paths.length]; + for (int i = 0; i < rows.length; i++) + rows[i] = getRowForPath(paths[i]); + } return rows; } @@ -440,6 +451,6 @@ public abstract class AbstractLayoutCache */ protected boolean isFixedRowHeight() { - return false; + return rowHeight > 0; } } diff --git a/libjava/classpath/javax/swing/tree/DefaultMutableTreeNode.java b/libjava/classpath/javax/swing/tree/DefaultMutableTreeNode.java index 6951b960005..9f587946fc2 100644 --- a/libjava/classpath/javax/swing/tree/DefaultMutableTreeNode.java +++ b/libjava/classpath/javax/swing/tree/DefaultMutableTreeNode.java @@ -67,7 +67,7 @@ public class DefaultMutableTreeNode * An empty enumeration, returned by {@link #children()} if a node has no * children. */ - public static final Enumeration EMPTY_ENUMERATION = + public static final Enumeration<TreeNode> EMPTY_ENUMERATION = EmptyEnumeration.getInstance(); /** @@ -78,7 +78,7 @@ public class DefaultMutableTreeNode /** * The child nodes for this node (may be empty). */ - protected Vector children = new Vector(); + protected Vector<MutableTreeNode> children = new Vector<MutableTreeNode>(); /** * userObject @@ -480,7 +480,7 @@ public class DefaultMutableTreeNode public TreeNode getSharedAncestor(DefaultMutableTreeNode node) { TreeNode current = this; - ArrayList list = new ArrayList(); + ArrayList<TreeNode> list = new ArrayList<TreeNode>(); while (current != null) { @@ -527,7 +527,7 @@ public class DefaultMutableTreeNode || children.size() == 0) return 0; - Stack stack = new Stack(); + Stack<Integer> stack = new Stack<Integer>(); stack.push(new Integer(0)); TreeNode node = getChildAt(0); int depth = 0; @@ -765,7 +765,7 @@ public class DefaultMutableTreeNode throw new IllegalArgumentException(); TreeNode parent = this; - Vector nodes = new Vector(); + Vector<TreeNode> nodes = new Vector<TreeNode>(); nodes.add(this); while (parent != node && parent != null) @@ -1148,7 +1148,7 @@ public class DefaultMutableTreeNode static class PostorderEnumeration implements Enumeration { - Stack nodes = new Stack(); + Stack<TreeNode> nodes = new Stack<TreeNode>(); Stack childrenEnums = new Stack(); PostorderEnumeration(TreeNode node) diff --git a/libjava/classpath/javax/swing/tree/DefaultTreeCellEditor.java b/libjava/classpath/javax/swing/tree/DefaultTreeCellEditor.java index b0a4d8db823..4c10bfe1af2 100644 --- a/libjava/classpath/javax/swing/tree/DefaultTreeCellEditor.java +++ b/libjava/classpath/javax/swing/tree/DefaultTreeCellEditor.java @@ -43,7 +43,6 @@ import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; -import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.event.ActionEvent; @@ -59,10 +58,10 @@ import javax.swing.Icon; import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.SwingUtilities; +import javax.swing.Timer; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.event.CellEditorListener; -import javax.swing.event.ChangeEvent; import javax.swing.event.EventListenerList; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; @@ -77,12 +76,6 @@ public class DefaultTreeCellEditor implements ActionListener, TreeCellEditor, TreeSelectionListener { /** - * The number of the fast mouse clicks, required to start the editing - * session. - */ - static int CLICK_COUNT_TO_START = 3; - - /** * This container that appears on the tree during editing session. * It contains the editing component displays various other editor - * specific parts like editing icon. @@ -99,7 +92,7 @@ public class DefaultTreeCellEditor */ public EditorContainer() { - // Do nothing here. + setLayout(null); } /** @@ -111,12 +104,6 @@ public class DefaultTreeCellEditor // Do nothing here. } - public void setBounds(Rectangle bounds) - { - super.setBounds(bounds); - doLayout(); - } - /** * Overrides Container.paint to paint the node's icon and use the selection * color for the background. @@ -126,11 +113,20 @@ public class DefaultTreeCellEditor */ public void paint(Graphics g) { + // Paint editing icon. if (editingIcon != null) { // From the previous version, the left margin is taken as half // of the icon width. - editingIcon.paintIcon(this, g, 0, 0); + int y = Math.max(0, (getHeight() - editingIcon.getIconHeight()) / 2); + editingIcon.paintIcon(this, g, 0, y); + } + // Paint border. + Color c = getBorderSelectionColor(); + if (c != null) + { + g.setColor(c); + g.drawRect(0, 0, getWidth() - 1, getHeight() - 1); } super.paint(g); } @@ -141,27 +137,33 @@ public class DefaultTreeCellEditor */ public void doLayout() { - // The offset of the editing component. - int eOffset; + if (editingComponent != null) + { + editingComponent.getPreferredSize(); + editingComponent.setBounds(offset, 0, getWidth() - offset, + getHeight()); + } + } - // Move the component to the left, leaving room for the editing icon: - if (editingIcon != null) - eOffset = editingIcon.getIconWidth(); + public Dimension getPreferredSize() + { + Dimension dim; + if (editingComponent != null) + { + dim = editingComponent.getPreferredSize(); + dim.width += offset + 5; + if (renderer != null) + { + Dimension r = renderer.getPreferredSize(); + dim.height = Math.max(dim.height, r.height); + } + if (editingIcon != null) + dim.height = Math.max(dim.height, editingIcon.getIconHeight()); + dim.width = Math.max(100, dim.width); + } else - eOffset = 0; - - Rectangle bounds = getBounds(); - Component c = getComponent(0); - c.setLocation(eOffset, 0); - - // Span the editing component near over all window width. - c.setSize(bounds.width - eOffset, bounds.height); - /* - * @specnote the Sun sets some more narrow editing component width (it is - * not documented how does it is calculated). However as our text field is - * still not able to auto - scroll horizontally, replicating such strategy - * would prevent adding extra characters to the text being edited. - */ + dim = new Dimension(0, 0); + return dim; } } @@ -227,46 +229,15 @@ public class DefaultTreeCellEditor */ public Dimension getPreferredSize() { - String s = getText(); - - Font f = getFont(); - - if (f != null) + Dimension size = super.getPreferredSize(); + if (renderer != null && DefaultTreeCellEditor.this.getFont() == null) { - FontMetrics fm = getToolkit().getFontMetrics(f); - - return new Dimension(SwingUtilities.computeStringWidth(fm, s), - fm.getHeight()); + size.height = renderer.getPreferredSize().height; } return renderer.getPreferredSize(); } } - /** - * Listens for the events from the realEditor. - */ - class RealEditorListener implements CellEditorListener - { - /** - * The method is called when the editing has been cancelled. - * @param event unused - */ - public void editingCanceled(ChangeEvent event) - { - cancelCellEditing(); - } - - /** - * The method is called after completing the editing session. - * - * @param event unused - */ - public void editingStopped(ChangeEvent event) - { - stopCellEditing(); - } - } - private EventListenerList listenerList = new EventListenerList(); /** @@ -367,21 +338,14 @@ public class DefaultTreeCellEditor public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer, TreeCellEditor editor) { - setTree(tree); this.renderer = renderer; - - if (editor == null) - editor = createTreeCellEditor(); - else - editor.addCellEditorListener(new RealEditorListener()); - realEditor = editor; - - lastPath = tree.getLeadSelectionPath(); - tree.addTreeSelectionListener(this); + if (realEditor == null) + realEditor = createTreeCellEditor(); editingContainer = createContainer(); - setFont(UIManager.getFont("Tree.font")); - setBorderSelectionColor(UIManager.getColor("Tree.selectionBorderColor")); + setTree(tree); + Color c = UIManager.getColor("Tree.editorBorderSelectionColor"); + setBorderSelectionColor(c); } /** @@ -505,19 +469,36 @@ public class DefaultTreeCellEditor * @return the component for editing */ public Component getTreeCellEditorComponent(JTree tree, Object value, - boolean isSelected, boolean expanded, + boolean isSelected, + boolean expanded, boolean leaf, int row) { - if (realEditor == null) - realEditor = createTreeCellEditor(); - - return realEditor.getTreeCellEditorComponent(tree, value, isSelected, - expanded, leaf, row); + setTree(tree); + lastRow = row; + determineOffset(tree, value, isSelected, expanded, leaf, row); + if (editingComponent != null) + editingContainer.remove(editingComponent); + + editingComponent = realEditor.getTreeCellEditorComponent(tree, value, + isSelected, + expanded, leaf, + row); + Font f = getFont(); + if (f == null) + { + if (renderer != null) + f = renderer.getFont(); + if (f == null) + f = tree.getFont(); + } + editingContainer.setFont(f); + prepareForEditing(); + return editingContainer; } /** * Returns the value currently being edited (requests it from the - * {@link realEditor}. + * {@link #realEditor}. * * @return the value currently being edited */ @@ -535,16 +516,48 @@ public class DefaultTreeCellEditor * @return true if editing can be started */ public boolean isCellEditable(EventObject event) - { - if (editingComponent == null) - configureEditingComponent(tree, renderer, realEditor); - - if (editingComponent != null && realEditor.isCellEditable(event)) + { + boolean ret = false; + boolean ed = false; + if (event != null) { - prepareForEditing(); - return true; + if (event.getSource() instanceof JTree) + { + setTree((JTree) event.getSource()); + if (event instanceof MouseEvent) + { + MouseEvent me = (MouseEvent) event; + TreePath path = tree.getPathForLocation(me.getX(), me.getY()); + ed = lastPath != null && path != null && lastPath.equals(path); + if (path != null) + { + lastRow = tree.getRowForPath(path); + Object val = path.getLastPathComponent(); + boolean isSelected = tree.isRowSelected(lastRow); + boolean isExpanded = tree.isExpanded(path); + TreeModel m = tree.getModel(); + boolean isLeaf = m.isLeaf(val); + determineOffset(tree, val, isSelected, isExpanded, isLeaf, + lastRow); + } + } + } } - return false; + if (! realEditor.isCellEditable(event)) + ret = false; + else + { + if (canEditImmediately(event)) + ret = true; + else if (ed && shouldStartEditingTimer(event)) + startEditingTimer(); + else if (timer != null && timer.isRunning()) + timer.stop(); + } + if (ret) + prepareForEditing(); + return ret; + } /** @@ -567,14 +580,13 @@ public class DefaultTreeCellEditor */ public boolean stopCellEditing() { - if (editingComponent != null) + boolean ret = false; + if (realEditor.stopCellEditing()) { - stopEditingTimer(); - tree.stopEditing(); - editingComponent = null; - return true; + finish(); + ret = true; } - return false; + return ret; } /** @@ -583,21 +595,15 @@ public class DefaultTreeCellEditor */ public void cancelCellEditing() { - if (editingComponent != null) - { - tree.cancelEditing(); - editingComponent = null; - } - stopEditingTimer(); + realEditor.cancelCellEditing(); + finish(); } - - /** - * Stop the editing timer, if it is installed and running. - */ - private void stopEditingTimer() + + private void finish() { - if (timer != null && timer.isRunning()) - timer.stop(); + if (editingComponent != null) + editingContainer.remove(editingComponent); + editingComponent = null; } /** @@ -640,10 +646,18 @@ public class DefaultTreeCellEditor */ public void valueChanged(TreeSelectionEvent e) { - tPath = lastPath; - lastPath = e.getNewLeadSelectionPath(); - lastRow = tree.getRowForPath(lastPath); - stopCellEditing(); + if (tree != null) + { + if (tree.getSelectionCount() == 1) + lastPath = tree.getSelectionPath(); + else + lastPath = null; + } + // TODO: We really should do the following here, but can't due + // to buggy DefaultTreeSelectionModel. This selection model + // should only fire if the selection actually changes. +// if (timer != null) +// timer.stop(); } /** @@ -653,6 +667,8 @@ public class DefaultTreeCellEditor */ public void actionPerformed(ActionEvent e) { + if (tree != null && lastPath != null) + tree.startEditingAtPath(lastPath); } /** @@ -664,7 +680,17 @@ public class DefaultTreeCellEditor */ protected void setTree(JTree newTree) { - tree = newTree; + if (tree != newTree) + { + if (tree != null) + tree.removeTreeSelectionListener(this); + tree = newTree; + if (tree != null) + tree.addTreeSelectionListener(this); + + if (timer != null) + timer.stop(); + } } /** @@ -675,10 +701,14 @@ public class DefaultTreeCellEditor */ protected boolean shouldStartEditingTimer(EventObject event) { - if ((event instanceof MouseEvent) && - ((MouseEvent) event).getClickCount() == 1) - return true; - return false; + boolean ret = false; + if (event instanceof MouseEvent) + { + MouseEvent me = (MouseEvent) event; + ret = SwingUtilities.isLeftMouseButton(me) && me.getClickCount() == 1 + && inHitRegion(me.getX(), me.getY()); + } + return ret; } /** @@ -686,8 +716,12 @@ public class DefaultTreeCellEditor */ protected void startEditingTimer() { - if (timer != null) - timer.start(); + if (timer == null) + { + timer = new Timer(1200, this); + timer.setRepeats(false); + } + timer.start(); } /** @@ -723,7 +757,6 @@ public class DefaultTreeCellEditor protected boolean inHitRegion(int x, int y) { Rectangle bounds = tree.getPathBounds(lastPath); - return bounds.contains(x, y); } @@ -739,13 +772,24 @@ public class DefaultTreeCellEditor protected void determineOffset(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) { - renderer.getTreeCellRendererComponent(tree, value, isSelected, expanded, - leaf, row, true); - Icon c = renderer.getIcon(); - if (c != null) - offset = renderer.getIconTextGap() + c.getIconWidth(); + if (renderer != null) + { + if (leaf) + editingIcon = renderer.getLeafIcon(); + else if (expanded) + editingIcon = renderer.getOpenIcon(); + else + editingIcon = renderer.getClosedIcon(); + if (editingIcon != null) + offset = renderer.getIconTextGap() + editingIcon.getIconWidth(); + else + offset = renderer.getIconTextGap(); + } else - offset = 0; + { + editingIcon = null; + offset = 0; + } } /** @@ -754,8 +798,8 @@ public class DefaultTreeCellEditor */ protected void prepareForEditing() { - editingContainer.removeAll(); - editingContainer.add(editingComponent); + if (editingComponent != null) + editingContainer.add(editingComponent); } /** @@ -776,10 +820,10 @@ public class DefaultTreeCellEditor */ protected TreeCellEditor createTreeCellEditor() { - DefaultCellEditor editor = new DefaultCellEditor(new DefaultTreeCellEditor.DefaultTextField( - UIManager.getBorder("Tree.selectionBorder"))); - editor.addCellEditorListener(new RealEditorListener()); - editor.setClickCountToStart(CLICK_COUNT_TO_START); + Border border = UIManager.getBorder("Tree.editorBorder"); + JTextField tf = new DefaultTreeCellEditor.DefaultTextField(border); + DefaultCellEditor editor = new DefaultCellEditor(tf); + editor.setClickCountToStart(1); realEditor = editor; return editor; } diff --git a/libjava/classpath/javax/swing/tree/DefaultTreeCellRenderer.java b/libjava/classpath/javax/swing/tree/DefaultTreeCellRenderer.java index e120b71c167..3766485abdb 100644 --- a/libjava/classpath/javax/swing/tree/DefaultTreeCellRenderer.java +++ b/libjava/classpath/javax/swing/tree/DefaultTreeCellRenderer.java @@ -77,7 +77,7 @@ public class DefaultTreeCellRenderer protected boolean hasFocus; /** - * drawsFocusBorderAroundIcon // FIXME: is this used? + * Indicates if the focus border is also drawn around the icon. */ private boolean drawsFocusBorderAroundIcon; @@ -152,6 +152,8 @@ public class DefaultTreeCellRenderer setBackgroundNonSelectionColor(UIManager.getColor("Tree.textBackground")); setBackgroundSelectionColor(UIManager.getColor("Tree.selectionBackground")); setBorderSelectionColor(UIManager.getColor("Tree.selectionBorderColor")); + Object val = UIManager.get("Tree.drawsFocusBorderAroundIcon"); + drawsFocusBorderAroundIcon = val != null && ((Boolean) val).booleanValue(); } /** @@ -499,67 +501,75 @@ public class DefaultTreeCellRenderer */ public void paint(Graphics g) { - // paint background - Rectangle vr = new Rectangle(); - Rectangle ir = new Rectangle(); - Rectangle tr = new Rectangle(); - - Insets insets = new Insets(0, 0, 0, 0); - Border border = UIManager.getBorder("Tree.selectionBorder"); - if (border != null) - insets = border.getBorderInsets(this); - - FontMetrics fm = getToolkit().getFontMetrics(getFont()); - SwingUtilities.layoutCompoundLabel((JLabel) this, fm, getText(), - getIcon(), getVerticalAlignment(), - getHorizontalAlignment(), - getVerticalTextPosition(), - getHorizontalTextPosition(), vr, ir, tr, - getIconTextGap()); - - // Reusing one rectangle. - Rectangle bounds = getBounds(ir); - - bounds.x = tr.x - insets.left; - bounds.width = tr.width + insets.left + insets.right; - - g.setColor(super.getBackground()); - g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); + // Determine background color. + Color bgColor; + if (selected) + bgColor = getBackgroundSelectionColor(); + else + { + bgColor = getBackgroundNonSelectionColor(); + if (bgColor == null) + bgColor = getBackground(); + } + // Paint background. + int xOffset = -1; + if (bgColor != null) + { + Icon i = getIcon(); + xOffset = getXOffset(); + g.setColor(bgColor); + g.fillRect(xOffset, 0, getWidth() - xOffset, getHeight()); + } - super.paint(g); - - // Paint the border of the focused element only (lead selection) if (hasFocus) { - Color b = getBorderSelectionColor(); - if (b != null) - { - g.setColor(b); - g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height - 1); - } + if (drawsFocusBorderAroundIcon) + xOffset = 0; + else if (xOffset == -1) + xOffset = getXOffset(); + paintFocus(g, xOffset, 0, getWidth() - xOffset, getHeight()); + } + super.paint(g); + } + + /** + * Paints the focus indicator. + */ + private void paintFocus(Graphics g, int x, int y, int w, int h) + { + Color col = getBorderSelectionColor(); + if (col != null) + { + g.setColor(col); + g.drawRect(x, y, w - 1, h - 1); } } /** + * Determines the X offset of the label that is caused by + * the icon. + * + * @return the X offset of the label + */ + private int getXOffset() + { + Icon i = getIcon(); + int offs = 0; + if (i != null && getText() != null) + offs = i.getIconWidth() + Math.max(0, getIconTextGap() - 1); + return offs; + } + + /** * Returns the preferred size of the cell. * * @return The preferred size of the cell. */ public Dimension getPreferredSize() { - Rectangle vr = new Rectangle(); - Rectangle ir = new Rectangle(); - Rectangle tr = new Rectangle(); - - FontMetrics fm = getToolkit().getFontMetrics(getFont()); - SwingUtilities.layoutCompoundLabel((JLabel) this, fm, getText(), - getIcon(), getVerticalAlignment(), - getHorizontalAlignment(), - getVerticalTextPosition(), - getHorizontalTextPosition(), vr, ir, tr, - getIconTextGap()); - Rectangle cr = ir.union(tr); - return new Dimension(cr.width, cr.height); + Dimension size = super.getPreferredSize(); + size.width += 3; + return size; } /** diff --git a/libjava/classpath/javax/swing/tree/DefaultTreeModel.java b/libjava/classpath/javax/swing/tree/DefaultTreeModel.java index 5819d15b627..afee7ea22fa 100644 --- a/libjava/classpath/javax/swing/tree/DefaultTreeModel.java +++ b/libjava/classpath/javax/swing/tree/DefaultTreeModel.java @@ -210,17 +210,32 @@ public class DefaultTreeModel } /** - * isLeaf + * Returns if the specified node is a leaf or not. When + * {@link #asksAllowsChildren} is true, then this checks if the TreeNode + * allows children, otherwise it returns the TreeNode's <code>leaf</code> + * property. * - * @param node TODO - * @return boolean + * @param node the node to check + * + * @return boolean <code>true</code> if the node is a leaf node, + * <code>false</code> otherwise + * + * @throws ClassCastException if the specified node is not a + * <code>TreeNode</code> instance + * + * @see TreeNode#getAllowsChildren() + * @see TreeNode#isLeaf() */ public boolean isLeaf(Object node) { - if (node instanceof TreeNode) - return ((TreeNode) node).isLeaf(); + // The RI throws a ClassCastException when node isn't a TreeNode, so do we. + TreeNode treeNode = (TreeNode) node; + boolean leaf; + if (asksAllowsChildren) + leaf = ! treeNode.getAllowsChildren(); else - return true; + leaf = treeNode.isLeaf(); + return leaf; } /** @@ -600,7 +615,7 @@ public class DefaultTreeModel * * @since 1.3 */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { return listenerList.getListeners(listenerType); } diff --git a/libjava/classpath/javax/swing/tree/DefaultTreeSelectionModel.java b/libjava/classpath/javax/swing/tree/DefaultTreeSelectionModel.java index 0684ef76659..3d9c67728bd 100644 --- a/libjava/classpath/javax/swing/tree/DefaultTreeSelectionModel.java +++ b/libjava/classpath/javax/swing/tree/DefaultTreeSelectionModel.java @@ -44,6 +44,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Arrays; +import java.util.BitSet; import java.util.EventListener; import java.util.HashSet; import java.util.Iterator; @@ -67,7 +68,39 @@ import javax.swing.event.TreeSelectionListener; public class DefaultTreeSelectionModel implements Cloneable, Serializable, TreeSelectionModel { - + + /** + * According to the API docs, the method + * {@link DefaultTreeSelectionModel#notifyPathChange} should + * expect instances of a class PathPlaceHolder in the Vector parameter. + * This seems to be a non-public class, so I can only make guesses about the + * use of it. + */ + private static class PathPlaceHolder + { + /** + * The path that we wrap. + */ + TreePath path; + + /** + * Indicates if the path is new or already in the selection. + */ + boolean isNew; + + /** + * Creates a new instance. + * + * @param p the path to wrap + * @param n if the path is new or already in the selection + */ + PathPlaceHolder(TreePath p, boolean n) + { + path = p; + isNew = n; + } + } + /** * Use serialVersionUID for interoperability. */ @@ -124,12 +157,36 @@ public class DefaultTreeSelectionModel protected int leadRow = -1; /** + * A supporting datastructure that is used in addSelectionPaths() and + * removeSelectionPaths(). It contains currently selected paths. + * + * @see #addSelectionPaths(TreePath[]) + * @see #removeSelectionPaths(TreePath[]) + * @see #setSelectionPaths(TreePath[]) + */ + private transient HashSet selectedPaths; + + /** + * A supporting datastructure that is used in addSelectionPaths() and + * removeSelectionPaths(). It contains the paths that are added or removed. + * + * @see #addSelectionPaths(TreePath[]) + * @see #removeSelectionPaths(TreePath[]) + * @see #setSelectionPaths(TreePath[]) + */ + private transient HashSet tmpPaths; + + /** * Constructs a new DefaultTreeSelectionModel. */ public DefaultTreeSelectionModel() { setSelectionMode(DISCONTIGUOUS_TREE_SELECTION); + listSelectionModel = new DefaultListSelectionModel(); listenerList = new EventListenerList(); + leadIndex = -1; + tmpPaths = new HashSet(); + selectedPaths = new HashSet(); } /** @@ -144,12 +201,14 @@ public class DefaultTreeSelectionModel { DefaultTreeSelectionModel cloned = (DefaultTreeSelectionModel) super.clone(); - - // Clone the selection and the list selection model. + cloned.changeSupport = null; cloned.selection = (TreePath[]) selection.clone(); - if (listSelectionModel != null) - cloned.listSelectionModel - = (DefaultListSelectionModel) listSelectionModel.clone(); + cloned.listenerList = new EventListenerList(); + cloned.listSelectionModel = + (DefaultListSelectionModel) listSelectionModel.clone(); + cloned.selectedPaths = new HashSet(); + cloned.tmpPaths = new HashSet(); + return cloned; } @@ -209,6 +268,7 @@ public class DefaultTreeSelectionModel public void setRowMapper(RowMapper mapper) { rowMapper = mapper; + resetRowSelection(); } /** @@ -236,8 +296,18 @@ public class DefaultTreeSelectionModel */ public void setSelectionMode(int mode) { + int oldMode = selectionMode; selectionMode = mode; - insureRowContinuity(); + // Make sure we have a valid selection mode. + if (selectionMode != SINGLE_TREE_SELECTION + && selectionMode != CONTIGUOUS_TREE_SELECTION + && selectionMode != DISCONTIGUOUS_TREE_SELECTION) + selectionMode = DISCONTIGUOUS_TREE_SELECTION; + + // Fire property change event. + if (oldMode != selectionMode && changeSupport != null) + changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY, oldMode, + selectionMode); } /** @@ -262,32 +332,10 @@ public class DefaultTreeSelectionModel */ public void setSelectionPath(TreePath path) { - // The most frequently only one cell in the tree is selected. - TreePath[] ose = selection; - selection = new TreePath[] { path }; - TreePath oldLead = leadPath; - leadIndex = 0; - leadRow = getRow(path); - leadPath = path; - - TreeSelectionEvent event; - - if (ose != null && ose.length > 0) - { - // The first item in the path list is the selected path. - // The remaining items are unselected pathes. - TreePath[] changed = new TreePath[ose.length + 1]; - boolean[] news = new boolean[changed.length]; - news[0] = true; - changed[0] = path; - System.arraycopy(ose, 0, changed, 1, ose.length); - event = new TreeSelectionEvent(this, changed, news, oldLead, path); - } - else - { - event = new TreeSelectionEvent(this, path, true, oldLead, path); - } - fireValueChanged(event); + TreePath[] paths = null; + if (path != null) + paths = new TreePath[]{ path }; + setSelectionPaths(paths); } /** @@ -307,7 +355,7 @@ public class DefaultTreeSelectionModel AbstractLayoutCache ama = (AbstractLayoutCache) mapper; return ama.getRowForPath(path); } - else + else if (mapper != null) { // Generic non optimized implementation. int[] rows = mapper.getRowsForPaths(new TreePath[] { path }); @@ -316,6 +364,7 @@ public class DefaultTreeSelectionModel else return rows[0]; } + return -1; } /** @@ -327,10 +376,90 @@ public class DefaultTreeSelectionModel */ public void setSelectionPaths(TreePath[] paths) { - // Must be called, as defined in JDK API 1.4. - insureUniqueness(); - clearSelection(); - addSelectionPaths(paths); + int oldLength = 0; + if (selection != null) + oldLength = selection.length; + int newLength = 0; + if (paths != null) + newLength = paths.length; + if (newLength > 0 || oldLength > 0) + { + // For SINGLE_TREE_SELECTION and for CONTIGUOUS_TREE_SELECTION with + // a non-contiguous path, we only allow the first path element. + if ((selectionMode == SINGLE_TREE_SELECTION && newLength > 1) + || (selectionMode == CONTIGUOUS_TREE_SELECTION && newLength > 0 + && ! arePathsContiguous(paths))) + { + paths = new TreePath[] { paths[0] }; + newLength = 1; + } + // Find new paths. + Vector changedPaths = null; + tmpPaths.clear(); + int validPaths = 0; + TreePath oldLeadPath = leadPath; + for (int i = 0; i < newLength; i++) + { + if (paths[i] != null && ! tmpPaths.contains(paths[i])) + { + validPaths++; + tmpPaths.add(paths[i]); + if (! selectedPaths.contains(paths[i])) + { + if (changedPaths == null) + changedPaths = new Vector(); + changedPaths.add(new PathPlaceHolder(paths[i], true)); + } + leadPath = paths[i]; + } + } + // Put together the new selection. + TreePath[] newSelection = null; + if (validPaths != 0) + { + if (validPaths != newLength) + { + // Some of the paths are already selected, put together + // the new selection carefully. + newSelection = new TreePath[validPaths]; + Iterator newPaths = tmpPaths.iterator(); + validPaths = 0; + for (int i = 0; newPaths.hasNext(); i++) + newSelection[i] = (TreePath) newPaths.next(); + } + else + { + newSelection = new TreePath[paths.length]; + System.arraycopy(paths, 0, newSelection, 0, paths.length); + } + } + + // Find paths that have been selected, but are no more. + for (int i = 0; i < oldLength; i++) + { + if (selection[i] != null && ! tmpPaths.contains(selection[i])) + { + if (changedPaths == null) + changedPaths = new Vector(); + changedPaths.add(new PathPlaceHolder(selection[i], false)); + } + } + + // Perform changes and notification. + selection = newSelection; + HashSet tmp = selectedPaths; + selectedPaths = tmpPaths; + tmpPaths = tmp; + tmpPaths.clear(); + + // Not necessary, but required according to the specs and to tests. + if (selection != null) + insureUniqueness(); + updateLeadIndex(); + resetRowSelection(); + if (changedPaths != null && changedPaths.size() > 0) + notifyPathChange(changedPaths, oldLeadPath); + } } /** @@ -345,29 +474,10 @@ public class DefaultTreeSelectionModel */ public void addSelectionPath(TreePath path) { - if (! isPathSelected(path)) + if (path != null) { - if (selectionMode == SINGLE_TREE_SELECTION || isSelectionEmpty() - || ! canPathBeAdded(path)) - setSelectionPath(path); - else - { - TreePath[] temp = new TreePath[selection.length + 1]; - System.arraycopy(selection, 0, temp, 0, selection.length); - temp[temp.length - 1] = path; - selection = new TreePath[temp.length]; - System.arraycopy(temp, 0, selection, 0, temp.length); - } - } - - if (path != leadPath) - { - TreePath oldLead = leadPath; - leadPath = path; - leadRow = getRow(path); - leadIndex = selection.length - 1; - fireValueChanged(new TreeSelectionEvent(this, path, true, oldLead, - leadPath)); + TreePath[] add = new TreePath[]{ path }; + addSelectionPaths(add); } } @@ -380,37 +490,76 @@ public class DefaultTreeSelectionModel */ public void addSelectionPaths(TreePath[] paths) { - // Must be called, as defined in JDK API 1.4. - insureUniqueness(); - - if (paths != null) + int length = paths != null ? paths.length : 0; + if (length > 0) { - TreePath v0 = null; - for (int i = 0; i < paths.length; i++) + if (selectionMode == SINGLE_TREE_SELECTION) + setSelectionPaths(paths); + else if (selectionMode == CONTIGUOUS_TREE_SELECTION + && ! canPathsBeAdded(paths)) + { + if (arePathsContiguous(paths)) + setSelectionPaths(paths); + else + setSelectionPaths(new TreePath[] { paths[0] }); + } + else { - v0 = paths[i]; - if (! isPathSelected(v0)) + Vector changedPaths = null; + tmpPaths.clear(); + int validPaths = 0; + TreePath oldLeadPath = leadPath; + int oldPaths = 0; + if (selection != null) + oldPaths = selection.length; + int i; + for (i = 0; i < length; i++) { - if (isSelectionEmpty()) - setSelectionPath(v0); - else + if (paths[i] != null) { - TreePath[] temp = new TreePath[selection.length + 1]; - System.arraycopy(selection, 0, temp, 0, selection.length); - temp[temp.length - 1] = v0; - selection = new TreePath[temp.length]; - System.arraycopy(temp, 0, selection, 0, temp.length); + if (! selectedPaths.contains(paths[i])) + { + validPaths++; + if (changedPaths == null) + changedPaths = new Vector(); + changedPaths.add(new PathPlaceHolder(paths[i], true)); + selectedPaths.add(paths[i]); + tmpPaths.add(paths[i]); + } + leadPath = paths[i]; } - TreePath oldLead = leadPath; - leadPath = paths[paths.length - 1]; - leadRow = getRow(leadPath); - leadIndex = selection.length - 1; - - fireValueChanged(new TreeSelectionEvent(this, v0, true, - oldLead, leadPath)); } + if (validPaths > 0) + { + TreePath[] newSelection = new TreePath[oldPaths + validPaths]; + if (oldPaths > 0) + System.arraycopy(selection, 0, newSelection, 0, oldPaths); + if (validPaths != paths.length) + { + // Some of the paths are already selected, put together + // the new selection carefully. + Iterator newPaths = tmpPaths.iterator(); + i = oldPaths; + while (newPaths.hasNext()) + { + newSelection[i] = (TreePath) newPaths.next(); + i++; + } + } + else + System.arraycopy(paths, 0, newSelection, oldPaths, + validPaths); + selection = newSelection; + insureUniqueness(); + updateLeadIndex(); + resetRowSelection(); + if (changedPaths != null && changedPaths.size() > 0) + notifyPathChange(changedPaths, oldLeadPath); + } + else + leadPath = oldLeadPath; + tmpPaths.clear(); } - insureRowContinuity(); } } @@ -422,36 +571,8 @@ public class DefaultTreeSelectionModel */ public void removeSelectionPath(TreePath path) { - if (isSelectionEmpty()) - return; - - int index = - 1; - if (isPathSelected(path)) - { - for (int i = 0; i < selection.length; i++) - { - if (selection[i].equals(path)) - { - index = i; - break; - } - } - TreePath[] temp = new TreePath[selection.length - 1]; - System.arraycopy(selection, 0, temp, 0, index); - System.arraycopy(selection, index + 1, temp, index, selection.length - - index - 1); - selection = new TreePath[temp.length]; - System.arraycopy(temp, 0, selection, 0, temp.length); - - // If the removed path was the lead path, set the lead path to null. - TreePath oldLead = leadPath; - if (path != null && leadPath != null && path.equals(leadPath)) - leadPath = null; - - fireValueChanged(new TreeSelectionEvent(this, path, false, oldLead, - leadPath)); - insureRowContinuity(); - } + if (path != null) + removeSelectionPaths(new TreePath[]{ path }); } /** @@ -462,40 +583,54 @@ public class DefaultTreeSelectionModel */ public void removeSelectionPaths(TreePath[] paths) { - if (isSelectionEmpty()) - return; - if (paths != null) + if (paths != null && selection != null && paths.length > 0) { - int index = - 1; - TreePath v0 = null; - TreePath oldLead = leadPath; - for (int i = 0; i < paths.length; i++) + if (! canPathsBeRemoved(paths)) + clearSelection(); + else { - v0 = paths[i]; - if (isPathSelected(v0)) + Vector pathsToRemove = null; + for (int i = paths.length - 1; i >= 0; i--) { - for (int x = 0; x < selection.length; x++) + if (paths[i] != null && selectedPaths.contains(paths[i])) { - if (selection[i].equals(v0)) - { - index = x; - break; - } - if (leadPath != null && leadPath.equals(v0)) + if (pathsToRemove == null) + pathsToRemove = new Vector(); + selectedPaths.remove(paths[i]); + pathsToRemove.add(new PathPlaceHolder(paths[i], + false)); + } + } + if (pathsToRemove != null) + { + int numRemove = pathsToRemove.size(); + TreePath oldLead = leadPath; + if (numRemove == selection.length) + selection = null; + else + { + selection = new TreePath[selection.length - numRemove]; + Iterator keep = selectedPaths.iterator(); + for (int valid = 0; keep.hasNext(); valid++) + selection[valid] = (TreePath) keep.next(); + } + // Update lead path. + if (leadPath != null && ! selectedPaths.contains(leadPath)) + { + if (selection != null) + leadPath = selection[selection.length - 1]; + else leadPath = null; } - TreePath[] temp = new TreePath[selection.length - 1]; - System.arraycopy(selection, 0, temp, 0, index); - System.arraycopy(selection, index + 1, temp, index, - selection.length - index - 1); - selection = new TreePath[temp.length]; - System.arraycopy(temp, 0, selection, 0, temp.length); - - fireValueChanged(new TreeSelectionEvent(this, v0, false, - oldLead, leadPath)); + else if (selection != null) + leadPath = selection[selection.length - 1]; + else + leadPath = null; + updateLeadIndex(); + resetRowSelection(); + notifyPathChange(pathsToRemove, oldLead); } } - insureRowContinuity(); } } @@ -572,19 +707,22 @@ public class DefaultTreeSelectionModel */ public void clearSelection() { - if (! isSelectionEmpty()) + if (selection != null) { - TreeSelectionEvent event = new TreeSelectionEvent( - this, selection, new boolean[selection.length], leadPath, null); + int selectionLength = selection.length; + boolean[] news = new boolean[selectionLength]; + Arrays.fill(news, false); + TreeSelectionEvent event = new TreeSelectionEvent(this, selection, + news, leadPath, + null); leadPath = null; + leadIndex = 0; + leadRow = 0; + selectedPaths.clear(); selection = null; + resetRowSelection(); fireValueChanged(event); } - else - { - leadPath = null; - selection = null; - } } /** @@ -638,7 +776,7 @@ public class DefaultTreeSelectionModel * @return an array of listeners * @since 1.3 */ - public EventListener[] getListeners(Class listenerType) + public <T extends EventListener> T[] getListeners(Class<T> listenerType) { return listenerList.getListeners(listenerType); } @@ -650,10 +788,43 @@ public class DefaultTreeSelectionModel */ public int[] getSelectionRows() { - if (rowMapper == null) - return null; - else - return rowMapper.getRowsForPaths(selection); + int[] rows = null; + if (rowMapper != null && selection != null) + { + rows = rowMapper.getRowsForPaths(selection); + if (rows != null) + { + // Find invisible rows. + int invisible = 0; + for (int i = rows.length - 1; i >= 0; i--) + { + if (rows[i] == -1) + invisible++; + + } + // Clean up invisible rows. + if (invisible > 0) + { + if (invisible == rows.length) + rows = null; + else + { + int[] newRows = new int[rows.length - invisible]; + int visCount = 0; + for (int i = rows.length - 1; i >= 0; i--) + { + if (rows[i] != -1) + { + newRows[visCount] = rows[i]; + visCount++; + } + } + rows = newRows; + } + } + } + } + return rows; } /** @@ -663,16 +834,7 @@ public class DefaultTreeSelectionModel */ public int getMinSelectionRow() { - if ((rowMapper == null) || (selection == null) || (selection.length == 0)) - return - 1; - else - { - int[] rows = rowMapper.getRowsForPaths(selection); - int minRow = Integer.MAX_VALUE; - for (int index = 0; index < rows.length; index++) - minRow = Math.min(minRow, rows[index]); - return minRow; - } + return listSelectionModel.getMinSelectionIndex(); } /** @@ -682,16 +844,7 @@ public class DefaultTreeSelectionModel */ public int getMaxSelectionRow() { - if ((rowMapper == null) || (selection == null) || (selection.length == 0)) - return - 1; - else - { - int[] rows = rowMapper.getRowsForPaths(selection); - int maxRow = - 1; - for (int index = 0; index < rows.length; index++) - maxRow = Math.max(maxRow, rows[index]); - return maxRow; - } + return listSelectionModel.getMaxSelectionIndex(); } /** @@ -706,29 +859,7 @@ public class DefaultTreeSelectionModel */ public boolean isRowSelected(int row) { - // Return false if nothing is selected. - if (isSelectionEmpty()) - return false; - - RowMapper mapper = getRowMapper(); - - if (mapper instanceof AbstractLayoutCache) - { - // The absolute majority of cases, unless the TreeUI is very - // seriously rewritten - AbstractLayoutCache ama = (AbstractLayoutCache) mapper; - TreePath path = ama.getPathForRow(row); - return isPathSelected(path); - } - else - { - // Generic non optimized implementation. - int[] rows = mapper.getRowsForPaths(selection); - for (int i = 0; i < rows.length; i++) - if (rows[i] == row) - return true; - return false; - } + return listSelectionModel.isSelectedIndex(row); } /** @@ -736,7 +867,32 @@ public class DefaultTreeSelectionModel */ public void resetRowSelection() { - // Nothing to do here. + listSelectionModel.clearSelection(); + if (selection != null && rowMapper != null) + { + int[] rows = rowMapper.getRowsForPaths(selection); + // Update list selection model. + for (int i = 0; i < rows.length; i++) + { + int row = rows[i]; + if (row != -1) + listSelectionModel.addSelectionInterval(row, row); + } + // Update lead selection. + if (leadIndex != -1 && rows != null) + leadRow = rows[leadIndex]; + else if (leadPath != null) + { + TreePath[] tmp = new TreePath[]{ leadPath }; + rows = rowMapper.getRowsForPaths(tmp); + leadRow = rows != null ? rows[0] : -1; + } + else + leadRow = -1; + insureRowContinuity(); + } + else + leadRow = -1; } /** @@ -766,6 +922,8 @@ public class DefaultTreeSelectionModel */ public void addPropertyChangeListener(PropertyChangeListener listener) { + if (changeSupport == null) + changeSupport = new SwingPropertyChangeSupport(this); changeSupport.addPropertyChangeListener(listener); } @@ -776,7 +934,8 @@ public class DefaultTreeSelectionModel */ public void removePropertyChangeListener(PropertyChangeListener listener) { - changeSupport.removePropertyChangeListener(listener); + if (changeSupport != null) + changeSupport.removePropertyChangeListener(listener); } /** @@ -787,7 +946,12 @@ public class DefaultTreeSelectionModel */ public PropertyChangeListener[] getPropertyChangeListeners() { - return changeSupport.getPropertyChangeListeners(); + PropertyChangeListener[] listeners = null; + if (changeSupport != null) + listeners = changeSupport.getPropertyChangeListeners(); + else + listeners = new PropertyChangeListener[0]; + return listeners; } /** @@ -801,67 +965,41 @@ public class DefaultTreeSelectionModel */ protected void insureRowContinuity() { - if (selection == null || selection.length < 2) - return; - else if (selectionMode == CONTIGUOUS_TREE_SELECTION) + if (selectionMode == CONTIGUOUS_TREE_SELECTION && selection != null + && rowMapper != null) { - if (rowMapper == null) - // This is the best we can do without the row mapper: - selectOne(); - else + int min = listSelectionModel.getMinSelectionIndex(); + if (min != -1) { - int[] rows = rowMapper.getRowsForPaths(selection); - Arrays.sort(rows); - int i; - for (i = 1; i < rows.length; i++) - { - if (rows[i - 1] != rows[i] - 1) - // Break if no longer continuous. - break; - } - - if (i < rows.length) + int max = listSelectionModel.getMaxSelectionIndex(); + for (int i = min; i <= max; i++) { - TreePath[] ns = new TreePath[i]; - for (int j = 0; j < ns.length; j++) - ns[i] = getPath(j); - setSelectionPaths(ns); + if (! listSelectionModel.isSelectedIndex(i)) + { + if (i == min) + clearSelection(); + else + { + TreePath[] newSelection = new TreePath[i - min]; + int[] rows = rowMapper.getRowsForPaths(selection); + for (int j = 0; j < rows.length; j++) + { + if (rows[j] < i) + newSelection[rows[j] - min] = selection[j]; + } + setSelectionPaths(newSelection); + break; + } + } } } } - else if (selectionMode == SINGLE_TREE_SELECTION) - selectOne(); + else if (selectionMode == SINGLE_TREE_SELECTION && selection != null + && selection.length > 1) + setSelectionPath(selection[0]); } /** - * Keep only one (normally last or leading) path in the selection. - */ - private void selectOne() - { - if (leadIndex > 0 && leadIndex < selection.length) - setSelectionPath(selection[leadIndex]); - else - setSelectionPath(selection[selection.length - 1]); - } - - /** - * Get path for the given row that must be in the current selection. - */ - private TreePath getPath(int row) - { - if (rowMapper instanceof AbstractLayoutCache) - return ((AbstractLayoutCache) rowMapper).getPathForRow(row); - else - { - int[] rows = rowMapper.getRowsForPaths(selection); - for (int i = 0; i < rows.length; i++) - if (rows[i] == row) - return selection[i]; - } - throw new InternalError(row + " not in selection"); - } - - /** * Returns <code>true</code> if the paths are contiguous (take subsequent * rows in the diplayed tree view. The method returns <code>true</code> if * we have no RowMapper assigned. @@ -875,16 +1013,36 @@ public class DefaultTreeSelectionModel if (rowMapper == null || paths.length < 2) return true; - int[] rows = rowMapper.getRowsForPaths(paths); - - // The patches may not be sorted. - Arrays.sort(rows); - - for (int i = 1; i < rows.length; i++) + int length = paths.length; + TreePath[] tmp = new TreePath[1]; + tmp[0] = paths[0]; + int min = rowMapper.getRowsForPaths(tmp)[0]; + BitSet selected = new BitSet(); + int valid = 0; + for (int i = 0; i < length; i++) { - if (rows[i - 1] != rows[i] - 1) - return false; + if (paths[i] != null) + { + tmp[0] = paths[i]; + int[] rows = rowMapper.getRowsForPaths(tmp); + if (rows == null) + return false; // No row mapping yet, can't be selected. + int row = rows[0]; + if (row == -1 || row < (min - length) || row > (min + length)) + return false; // Not contiguous. + min = Math.min(min, row); + if (! selected.get(row)) + { + selected.set(row); + valid++; + } + + } } + int max = valid + min; + for (int i = min; i < max; i++) + if (! selected.get(i)) + return false; // Not contiguous. return true; } @@ -904,34 +1062,51 @@ public class DefaultTreeSelectionModel */ protected boolean canPathsBeAdded(TreePath[] paths) { - if (rowMapper == null || isSelectionEmpty() - || selectionMode == DISCONTIGUOUS_TREE_SELECTION) + if (paths == null || paths.length == 0 || rowMapper == null + || selection == null || selectionMode == DISCONTIGUOUS_TREE_SELECTION) return true; - - TreePath [] all = new TreePath[paths.length + selection.length]; - System.arraycopy(paths, 0, all, 0, paths.length); - System.arraycopy(selection, 0, all, paths.length, selection.length); - return arePathsContiguous(all); + BitSet selected = new BitSet(); + int min = listSelectionModel.getMinSelectionIndex(); + int max = listSelectionModel.getMaxSelectionIndex(); + TreePath[] tmp = new TreePath[1]; + if (min != -1) + { + // Set the bitmask of selected elements. + for (int i = min; i <= max; i++) + selected.set(i); + } + else + { + tmp[0] = paths[0]; + min = rowMapper.getRowsForPaths(tmp)[0]; + max = min; + } + // Mark new paths as selected. + for (int i = paths.length - 1; i >= 0; i--) + { + if (paths[i] != null) + { + tmp[0] = paths[i]; + int[] rows = rowMapper.getRowsForPaths(tmp); + if (rows == null) + return false; // Now row mapping yet, can't be selected. + int row = rows[0]; + if (row == -1) + return false; // Now row mapping yet, can't be selected. + min = Math.min(min, row); + max = Math.max(max, row); + selected.set(row); + } + } + // Now look if the new selection would be contiguous. + for (int i = min; i <= max; i++) + if (! selected.get(i)) + return false; + return true; } /** - * Checks if the single path can be added to selection. - */ - private boolean canPathBeAdded(TreePath path) - { - if (rowMapper == null || isSelectionEmpty() - || selectionMode == DISCONTIGUOUS_TREE_SELECTION) - return true; - - TreePath[] all = new TreePath[selection.length + 1]; - System.arraycopy(selection, 0, all, 0, selection.length); - all[all.length - 1] = path; - - return arePathsContiguous(all); - } - - /** * Checks if the paths can be removed without breaking the continuity of the * selection according to selectionMode. * @@ -966,20 +1141,23 @@ public class DefaultTreeSelectionModel * method will call listeners if invoked, but it is not called from the * implementation of this class. * - * @param vPathes the vector of the changed patches + * @param vPaths the vector of the changed patches * @param oldLeadSelection the old selection index */ - protected void notifyPathChange(Vector vPathes, TreePath oldLeadSelection) + protected void notifyPathChange(Vector vPaths, TreePath oldLeadSelection) { - TreePath[] pathes = new TreePath[vPathes.size()]; - for (int i = 0; i < pathes.length; i++) - pathes[i] = (TreePath) vPathes.get(i); - boolean[] news = new boolean[pathes.length]; - for (int i = 0; i < news.length; i++) - news[i] = isPathSelected(pathes[i]); + int numChangedPaths = vPaths.size(); + boolean[] news = new boolean[numChangedPaths]; + TreePath[] paths = new TreePath[numChangedPaths]; + for (int i = 0; i < numChangedPaths; i++) + { + PathPlaceHolder p = (PathPlaceHolder) vPaths.get(i); + news[i] = p.isNew; + paths[i] = p.path; + } - TreeSelectionEvent event = new TreeSelectionEvent(this, pathes, news, + TreeSelectionEvent event = new TreeSelectionEvent(this, paths, news, oldLeadSelection, leadPath); fireValueChanged(event); @@ -991,22 +1169,20 @@ public class DefaultTreeSelectionModel */ protected void updateLeadIndex() { - if (isSelectionEmpty()) + leadIndex = -1; + if (leadPath != null) { - leadRow = leadIndex = - 1; - } - else - { - leadRow = getRow(leadPath); - for (int i = 0; i < selection.length; i++) + leadRow = -1; + if (selection == null) + leadPath = null; + else { - if (selection[i].equals(leadPath)) + for (int i = selection.length - 1; i >= 0 && leadIndex == -1; i--) { - leadIndex = i; - break; + if (selection[i] == leadPath) + leadIndex = i; } } - leadIndex = leadRow; } } diff --git a/libjava/classpath/javax/swing/tree/FixedHeightLayoutCache.java b/libjava/classpath/javax/swing/tree/FixedHeightLayoutCache.java index a699a6c9f21..dff9298e8f5 100644 --- a/libjava/classpath/javax/swing/tree/FixedHeightLayoutCache.java +++ b/libjava/classpath/javax/swing/tree/FixedHeightLayoutCache.java @@ -480,7 +480,7 @@ public class FixedHeightLayoutCache * @param parentPath the parent path * @return the enumeration over pathes */ - public Enumeration getVisiblePathsFrom(TreePath parentPath) + public Enumeration<TreePath> getVisiblePathsFrom(TreePath parentPath) { if (dirty) update(); diff --git a/libjava/classpath/javax/swing/tree/VariableHeightLayoutCache.java b/libjava/classpath/javax/swing/tree/VariableHeightLayoutCache.java index 0a787f7ca8c..8c70c13afd2 100644 --- a/libjava/classpath/javax/swing/tree/VariableHeightLayoutCache.java +++ b/libjava/classpath/javax/swing/tree/VariableHeightLayoutCache.java @@ -40,6 +40,7 @@ package javax.swing.tree; import gnu.javax.swing.tree.GnuPath; import java.awt.Rectangle; +import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; @@ -60,8 +61,11 @@ import javax.swing.event.TreeModelEvent; * @author Audrius Meskauskas */ public class VariableHeightLayoutCache - extends AbstractLayoutCache + extends AbstractLayoutCache { + + private static final Rectangle RECT_CACHE = new Rectangle(); + /** * The cached node record. */ @@ -73,8 +77,8 @@ public class VariableHeightLayoutCache depth = aDepth; parent = aParent; node = aNode; - - isExpanded = expanded.contains(aNode); + isExpanded = expanded.contains(aNode); + bounds = new Rectangle(0, -1, 0, 0); } /** @@ -102,7 +106,7 @@ public class VariableHeightLayoutCache * Using this field saves one hashtable access operation. */ final boolean isExpanded; - + /** * The cached bounds of the tree row. */ @@ -160,11 +164,6 @@ public class VariableHeightLayoutCache */ Rectangle getBounds() { - // This method may be called in the context when the tree rectangle is - // not known. To work around this, it is assumed near infinitely large. - if (bounds == null) - bounds = getNodeDimensions(node, row, depth, isExpanded, - new Rectangle()); return bounds; } } @@ -182,7 +181,7 @@ public class VariableHeightLayoutCache /** * Maps row numbers to nodes. */ - Hashtable row2node = new Hashtable(); + ArrayList row2node = new ArrayList(); /** * If true, the row map must be recomputed before using. @@ -236,45 +235,54 @@ public class VariableHeightLayoutCache return; Object root = treeModel.getRoot(); - - if (rootVisible) - { - countRows(root, null, 0); - } - else - { - int sc = treeModel.getChildCount(root); - for (int i = 0; i < sc; i++) - { - Object child = treeModel.getChild(root, i); - countRows(child, root, 0); - } - } + countRows(root, null, 0, 0); dirty = false; } /** * Recursively counts all rows in the tree. */ - private final void countRows(Object node, Object parent, int depth) + private final int countRows(Object node, Object parent, int depth, int y) { - Integer n = new Integer(row2node.size()); - row2node.put(n, node); - - NodeRecord nr = new NodeRecord(n.intValue(), depth, node, parent); + boolean visible = node != treeModel.getRoot() || rootVisible; + int row = row2node.size(); + if (visible) + { + row2node.add(node); + } + NodeRecord nr = new NodeRecord(row, depth, node, parent); + NodeDimensions d = getNodeDimensions(); + Rectangle r = RECT_CACHE; + if (d != null) + r = d.getNodeDimensions(node, row, depth, nr.isExpanded, r); + else + r.setBounds(0, 0, 0, 0); + + if (! visible) + r.y = -1; + else + r.y = Math.max(0, y); + + if (isFixedRowHeight()) + r.height = getRowHeight(); + + nr.bounds.setBounds(r); nodes.put(node, nr); - - // For expanded nodes + + if (visible) + y += r.height; + + int sc = treeModel.getChildCount(node); + int deeper = depth + 1; if (expanded.contains(node)) { - int sc = treeModel.getChildCount(node); - int deeper = depth + 1; for (int i = 0; i < sc; i++) { Object child = treeModel.getChild(node, i); - countRows(child, node, deeper); + y = countRows(child, node, deeper, y); } } + return y; } /** @@ -309,10 +317,14 @@ public class VariableHeightLayoutCache public void setExpandedState(TreePath path, boolean isExpanded) { if (isExpanded) - expanded.add(path.getLastPathComponent()); + { + int length = path.getPathCount(); + for (int i = 0; i < length; i++) + expanded.add(path.getPathComponent(i)); + } else expanded.remove(path.getLastPathComponent()); - + dirty = true; } @@ -339,25 +351,21 @@ public class VariableHeightLayoutCache return null; if (dirty) update(); + Object last = path.getLastPathComponent(); + Rectangle result = null; NodeRecord r = (NodeRecord) nodes.get(last); - if (r == null) - // This node is not visible. - { - rect.x = rect.y = rect.width = rect.height = 0; - } - else + if (r != null) { - if (r.bounds == null) - { - Rectangle dim = getNodeDimensions(last, r.row, r.depth, - r.isExpanded, rect); - r.bounds = dim; - } - - rect.setRect(r.bounds); + // The RI allows null arguments for rect, in which case a new Rectangle + // is created. + result = rect; + if (result == null) + result = new Rectangle(r.bounds); + else + result.setBounds(r.bounds); } - return rect; + return result; } /** @@ -370,14 +378,17 @@ public class VariableHeightLayoutCache { if (dirty) update(); - Object last = row2node.get(new Integer(row)); - if (last == null) - return null; - else + + TreePath path = null; + // Search row in the nodes map. TODO: This is inefficient, optimize this. + Enumeration nodesEnum = nodes.elements(); + while (nodesEnum.hasMoreElements() && path == null) { - NodeRecord r = (NodeRecord) nodes.get(last); - return r.getPath(); + NodeRecord record = (NodeRecord) nodesEnum.nextElement(); + if (record.row == row) + path = record.getPath(); } + return path; } /** @@ -390,7 +401,9 @@ public class VariableHeightLayoutCache { if (path == null) return -1; - if (dirty) update(); + + if (dirty) + update(); NodeRecord r = (NodeRecord) nodes.get(path.getLastPathComponent()); if (r == null) @@ -451,8 +464,8 @@ public class VariableHeightLayoutCache { if (y < r.y) return r.y - y; - else if (y > r.y + r.height) - return y - (r.y + r.height); + else if (y > r.y + r.height - 1) + return y - (r.y + r.height - 1); else return 0; } @@ -468,7 +481,7 @@ public class VariableHeightLayoutCache */ public int getVisibleChildCount(TreePath path) { - if (isExpanded(path)) + if (! isExpanded(path) || treeModel == null) return 0; else return treeModel.getChildCount(path.getLastPathComponent()); @@ -481,7 +494,7 @@ public class VariableHeightLayoutCache * @param parentPath the parent path * @return the enumeration over pathes */ - public Enumeration getVisiblePathsFrom(TreePath parentPath) + public Enumeration<TreePath> getVisiblePathsFrom(TreePath parentPath) { if (dirty) update(); @@ -493,7 +506,7 @@ public class VariableHeightLayoutCache { node = parentPath.getPathComponent(i); nr = (NodeRecord) nodes.get(node); - if (nr.row >= 0) + if (nr != null && nr.row >= 0) p.add(node); } return p.elements(); @@ -558,15 +571,11 @@ public class VariableHeightLayoutCache public void setModel(TreeModel newModel) { treeModel = newModel; - // We need to clear the table and update the layout, - // so that we don't end up with wrong data in the tables. - expanded.clear(); - update(); + dirty = true; if (treeModel != null) { // The root node is expanded by default. expanded.add(treeModel.getRoot()); - dirty = true; } } @@ -590,15 +599,14 @@ public class VariableHeightLayoutCache { if (dirty) update(); - totalHeight = 0; - Enumeration en = nodes.elements(); - while (en.hasMoreElements()) + int height = 0; + int rowCount = getRowCount(); + if (rowCount > 0) { - NodeRecord nr = (NodeRecord) en.nextElement(); - Rectangle r = nr.getBounds(); - totalHeight += r.height; + NodeRecord last = (NodeRecord) nodes.get(row2node.get(rowCount - 1)); + height = last.bounds.y + last.bounds.height; } - return totalHeight; + return height; } /** @@ -614,10 +622,36 @@ public class VariableHeightLayoutCache while (en.hasMoreElements()) { NodeRecord nr = (NodeRecord) en.nextElement(); - Rectangle r = nr.getBounds(); - if (r.x + r.width > maximalWidth) - maximalWidth = r.x + r.width; + if (nr != null) + { + Rectangle r = nr.getBounds(); + int width = r.x + r.width; + if (width > maximalWidth) + maximalWidth = width; + } } return maximalWidth; } + + /** + * Sets the node dimensions and invalidates the cached layout. + * + * @param dim the dimensions to set + */ + public void setNodeDimensions(NodeDimensions dim) + { + super.setNodeDimensions(dim); + dirty = true; + } + + /** + * Sets the row height and marks the layout as invalid. + * + * @param height the row height to set + */ + public void setRowHeight(int height) + { + super.setRowHeight(height); + dirty = true; + } } |