It was discovered that JTree multiple element selection is currently not working as expected. This path fixes the tree ability to select multiple nodes, if permitted.

2006-04-23  Audrius Meskauskas  <[EMAIL PROTECTED]>

   * examples/gnu/classpath/examples/swing/TreeDemo.java:
   (createContent): Added check box to swith between single and
   multiple selection.
   * javax/swing/JTree.java (leadSelectionPath): Removed.
   (addSelectionInterval): Explained. (getLeadSelectionPath):
   Request the path from model. (getPathsBetweenRows): Explained.
   (setLeadSelectionPath): Set the path in model.
   * javax/swing/plaf/basic/BasicTreeUI.java
   (TreeIncrementAction.actionPerformed, isMultiSelectionEvent,
   isToggleSelectionEvent, selectPath, selectPathForEvent): Rewritten.
   (MouseHandler.mousePressed): Call selectPathForEvent.
Index: examples/gnu/classpath/examples/swing/TreeDemo.java
===================================================================
RCS file: /sources/classpath/classpath/examples/gnu/classpath/examples/swing/TreeDemo.java,v
retrieving revision 1.1
diff -u -r1.1 TreeDemo.java
--- examples/gnu/classpath/examples/swing/TreeDemo.java	15 Mar 2006 16:41:02 -0000	1.1
+++ examples/gnu/classpath/examples/swing/TreeDemo.java	23 Apr 2006 18:08:00 -0000
@@ -39,10 +39,13 @@
 package gnu.classpath.examples.swing;
 
 import java.awt.BorderLayout;
+import java.awt.JobAttributes.DefaultSelectionType;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 
+import javax.swing.DebugGraphics;
 import javax.swing.JButton;
+import javax.swing.JCheckBox;
 import javax.swing.JComponent;
 import javax.swing.JFrame;
 import javax.swing.JPanel;
@@ -52,6 +55,7 @@
 import javax.swing.tree.DefaultMutableTreeNode;
 import javax.swing.tree.DefaultTreeSelectionModel;
 import javax.swing.tree.TreePath;
+import javax.swing.tree.TreeSelectionModel;
 
 public class TreeDemo
   extends JPanel
@@ -153,12 +157,30 @@
            }
         }
       });
-
+    
+    final JCheckBox cbSingle = new JCheckBox("single selection");
+    cbSingle.addActionListener(new ActionListener()
+      {
+        public void actionPerformed(ActionEvent e)
+      {
+        TreeSelectionModel model = tree.getSelectionModel();
+        if (cbSingle.isSelected())
+          model.setSelectionMode(
+            DefaultTreeSelectionModel.SINGLE_TREE_SELECTION);
+        else
+          model.setSelectionMode(
+            DefaultTreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
+      }
+      });
 
     setLayout(new BorderLayout());
     
     JPanel p2 = new JPanel(); 
     p2.add(add);
+    p2.add(cbSingle);
+    
+    tree.getSelectionModel().
+      setSelectionMode(DefaultTreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
 
     add(p2, BorderLayout.NORTH);
     add(new JScrollPane(tree), BorderLayout.CENTER);
Index: javax/swing/JTree.java
===================================================================
RCS file: /sources/classpath/classpath/javax/swing/JTree.java,v
retrieving revision 1.62
diff -u -r1.62 JTree.java
--- javax/swing/JTree.java	22 Apr 2006 17:07:45 -0000	1.62
+++ javax/swing/JTree.java	23 Apr 2006 18:08:29 -0000
@@ -1402,8 +1402,6 @@
 
   private TreePath anchorSelectionPath;
 
-  private TreePath leadSelectionPath;
-
   /**
    * This contains the state of all nodes in the tree. Al/ entries map the
    * TreePath of a note to to its state. Valid states are EXPANDED and
@@ -1568,7 +1566,15 @@
     TreeUI ui = getUI();
     return ui != null ? ui.getPathForRow(this, row) : null;
   }
-
+  
+  /**
+   * Get the pathes that are displayes between the two given rows.
+   * 
+   * @param index0 the starting row, inclusive
+   * @param index1 the ending row, inclusive
+   * 
+   * @return the array of the tree pathes
+   */
   protected TreePath[] getPathBetweenRows(int index0, int index1)
   {
     TreeUI ui = getUI();
@@ -2212,7 +2218,15 @@
 
     addSelectionPaths(paths);
   }
-
+  
+  /**
+   * Select all rows between the two given indexes, inclusive. The method
+   * will not select the inner leaves and braches of the currently collapsed
+   * nodes in this interval.
+   * 
+   * @param index0 the starting row, inclusive
+   * @param index1 the ending row, inclusive
+   */
   public void addSelectionInterval(int index0, int index1)
   {
     TreePath[] paths = getPathBetweenRows(index0, index1);
@@ -2268,7 +2282,10 @@
 
   public TreePath getLeadSelectionPath()
   {
-    return leadSelectionPath;
+    if (selectionModel == null)
+      return null;
+    else
+      return selectionModel.getLeadSelectionPath();
   }
 
   /**
@@ -2276,12 +2293,15 @@
    */
   public void setLeadSelectionPath(TreePath path)
   {
-    if (leadSelectionPath == path)
-      return;
-    
-    TreePath oldValue = leadSelectionPath;
-    leadSelectionPath = path;
-    firePropertyChange(LEAD_SELECTION_PATH_PROPERTY, oldValue, path);
+    if (selectionModel != null)
+      {
+        TreePath oldValue = selectionModel.getLeadSelectionPath();
+        if (path.equals(oldValue))
+          return;
+
+        selectionModel.addSelectionPath(path);
+        firePropertyChange(LEAD_SELECTION_PATH_PROPERTY, oldValue, path);
+      }
   }
 
   /**
Index: javax/swing/plaf/basic/BasicTreeUI.java
===================================================================
RCS file: /sources/classpath/classpath/javax/swing/plaf/basic/BasicTreeUI.java,v
retrieving revision 1.127
diff -u -r1.127 BasicTreeUI.java
--- javax/swing/plaf/basic/BasicTreeUI.java	22 Apr 2006 17:07:46 -0000	1.127
+++ javax/swing/plaf/basic/BasicTreeUI.java	23 Apr 2006 18:08:39 -0000
@@ -57,6 +57,7 @@
 import java.awt.event.ComponentListener;
 import java.awt.event.FocusEvent;
 import java.awt.event.FocusListener;
+import java.awt.event.InputEvent;
 import java.awt.event.KeyAdapter;
 import java.awt.event.KeyEvent;
 import java.awt.event.KeyListener;
@@ -1716,7 +1717,10 @@
 
   /**
    * Returning true signifies a mouse event on the node should toggle the
-   * selection of only the row under the mouse.
+   * selection of only the row under the mouse. The BasisTreeUI treats the
+   * event as "toggle selection event" if the CTRL button was pressed while
+   * clicking. The event is not counted as toggle event if the associated
+   * tree does not support the multiple selection.
    * 
    * @param event is the MouseEvent performed on the row.
    * @return true signifies a mouse event on the node should toggle the
@@ -1724,12 +1728,18 @@
    */
   protected boolean isToggleSelectionEvent(MouseEvent event)
   {
-    return (tree.getSelectionModel().getSelectionMode() == TreeSelectionModel.SINGLE_TREE_SELECTION);
+    return 
+      (tree.getSelectionModel().getSelectionMode() != 
+        TreeSelectionModel.SINGLE_TREE_SELECTION) &&
+      ((event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0);  
   }
 
   /**
    * Returning true signifies a mouse event on the node should select from the
-   * anchor point.
+   * anchor point. The BasisTreeUI treats the event as "multiple selection
+   * event" if the SHIFT button was pressed while clicking. The event is not
+   * counted as multiple selection event if the associated tree does not support
+   * the multiple selection.
    * 
    * @param event is the MouseEvent performed on the node.
    * @return true signifies a mouse event on the node should select from the
@@ -1737,7 +1747,10 @@
    */
   protected boolean isMultiSelectEvent(MouseEvent event)
   {
-    return (tree.getSelectionModel().getSelectionMode() == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
+    return 
+      (tree.getSelectionModel().getSelectionMode() != 
+        TreeSelectionModel.SINGLE_TREE_SELECTION) &&
+      ((event.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0);  
   }
 
   /**
@@ -1759,15 +1772,19 @@
    * row. If the even is a toggle selection event, the row is either selected,
    * or deselected. If the event identifies a multi selection event, the
    * selection is updated from the anchor point. Otherwise, the row is selected,
-   * and if the even specified a toggle event the row is expanded/collapsed.
+   * and the previous selection is cleared.</p>
    * 
    * @param path is the path selected for an event
    * @param event is the MouseEvent performed on the path.
+   * 
+   * @see #isToggleSelectionEvent(MouseEvent)
+   * @see #isMultiSelectEvent(MouseEvent)
    */
   protected void selectPathForEvent(TreePath path, MouseEvent event)
   {
     if (isToggleSelectionEvent(event))
       {
+        // The event selects or unselects the clicked row.
         if (tree.isPathSelected(path))
           tree.removeSelectionPath(path);
         else
@@ -1778,6 +1795,7 @@
       }
     else if (isMultiSelectEvent(event))
       {
+        // The event extends selection form anchor till the clicked row.
         TreePath anchor = tree.getAnchorSelectionPath();
         if (anchor != null)
           {
@@ -1788,7 +1806,11 @@
           tree.addSelectionPath(path);
       }
     else
-      tree.addSelectionPath(path);
+      {
+        // This is an ordinary event that just selects the clicked row.
+        tree.setSelectionPath(path);
+        tree.setAnchorSelectionPath(path);
+      }
   }
 
   /**
@@ -2167,23 +2189,22 @@
                         startEditTimer.stop();
 
                       startEditTimer = new Timer(WAIT_TILL_EDITING,
-                                                 new ActionListener()
-                                                 {
-                                                   public void actionPerformed(
-                                                                               ActionEvent e)
-                                                   {
-                                                     startEditing(editPath,
-                                                                  EDIT);
-                                                   }
-                                                 });
+                        new ActionListener()
+                          {
+                            public void actionPerformed(ActionEvent e)
+                              {
+                                startEditing(editPath, EDIT);
+                              }
+                          });
                       startEditTimer.setRepeats(false);
                       startEditTimer.start();
                     }
                   else
                     {
-                      selectPath(tree, path);
                       if (e.getClickCount() == 2 && ! isLeaf(row))
                         toggleExpandState(path);
+                      else
+                        selectPathForEvent(path, e);
                     }
                 }
 
@@ -2648,8 +2669,9 @@
       if (command.equals("selectPreviousChangeLead") && hasPrev)
         {
           newPath = treeState.getPathForRow(prevRow);
-          selectPath(tree, newPath);
+          tree.setSelectionPath(newPath);
           tree.setLeadSelectionPath(newPath);
+          tree.setAnchorSelectionPath(newPath);
         }
       else if (command.equals("selectPreviousExtendSelection") && hasPrev)
         {
@@ -2660,12 +2682,12 @@
       else if (command.equals("selectPrevious") && hasPrev)
         {
           newPath = treeState.getPathForRow(prevRow);
-          selectPath(tree, newPath);
+          tree.setSelectionPath(newPath);
         }
       else if (command.equals("selectNext") && hasNext)
         {
           newPath = treeState.getPathForRow(nextRow);
-          selectPath(tree, newPath);
+          tree.setSelectionPath(newPath);
         }
       else if (command.equals("selectNextExtendSelection") && hasNext)
         {
@@ -2676,8 +2698,9 @@
       else if (command.equals("selectNextChangeLead") && hasNext)
         {
           newPath = treeState.getPathForRow(nextRow);
-          selectPath(tree, newPath);
+          tree.setSelectionPath(newPath);
           tree.setLeadSelectionPath(newPath);
+          tree.setAnchorSelectionPath(newPath);          
         }
     }
 
@@ -3029,17 +3052,8 @@
   {
     if (path != null)
       {
-        if (tree.getSelectionModel().getSelectionMode() == 
-          TreeSelectionModel.SINGLE_TREE_SELECTION)
-          {
-            tree.setSelectionPath(path);
-            tree.setLeadSelectionPath(path);
-          }
-        else
-          {
-            tree.addSelectionPath(path);
-            tree.setLeadSelectionPath(path);
-          }
+        tree.setSelectionPath(path);
+        tree.setLeadSelectionPath(path);
         tree.makeVisible(path);
         tree.scrollPathToVisible(path);
       }

Reply via email to