JDK-5018951 : WHILE EDITING IN A JTREE, CLICKING IN JTREE SHOULD STOP EDITING AS OPPOSED TO CA
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 1.4.1
  • Priority: P3
  • Status: Closed
  • Resolution: Not an Issue
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2004-03-23
  • Updated: 2005-02-18
  • Resolved: 2005-02-18
Related Reports
Relates :  
Description
tic final DataFlavor objectsFlavor = new DataFlavor( DraggedObjects.class, "Objects" );

      public Object getTransferData( DataFlavor flavor )
      {
         if (flavor.equals( objectsFlavor ))
            return this;
         else if (flavor.equals( DataFlavor.stringFlavor ) )
            return this.toString();
            
         return null;
      }

      public DataFlavor[] getTransferDataFlavors()
      {
         return new DataFlavor[] { objectsFlavor, DataFlavor.stringFlavor };
      }

      public boolean isDataFlavorSupported(DataFlavor flavor)
      {
         return (flavor.equals( objectsFlavor ) || flavor.equals( DataFlavor.stringFlavor ));
      }
   }
}

Just select a node and click on it again.  You should see an edit box displayed.  Then edit the name of one of the nodes.  If you press enter, the name change is committed.  If you press escape, the rename is aborted. If you click the mouse on a different node, the rename is aborted.  That's the problem.  Clicking on another node should commit the change not abort the change.

In addition, when the tree starts a name edit, the text in the edit box should be selected and it is not.

======================================================================


Name: rv122619			Date: 03/23/2004

In a JTree, start editing a node.  When the user clicks outside of the edit area on another node, the edit should be stopped which commits the changes as opposed to cancelled.  In addition, the user should be able to click anywhere in the JTree not just on another node.

/**
 * Title:        MainFrame.java
 * Description:  
 * Copyright:    Copyright (c) 2002
 */

import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.WindowEvent;
import java.util.ArrayList;

import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

/*
 * 
 * 
 * 
 * 
 */
public class MainFrame extends JFrame
{
   private AbstractAction m_actFile;
   private AbstractAction m_actExit;

   private JTree            m_tree;
   private DefaultTreeModel m_mdl;
   
   private DragSourceListener m_lsnrDragSource;
   private DragSource         m_dragSource;
   
   
   public MainFrame()
   {
      setDefaultCloseOperation( EXIT_ON_CLOSE );
      initialize();
      setLocation( new Point( 200, 200 ) );
      setSize( new Dimension( 550, 250 ) );
   }

   private void initialize()
   {
      setTitle( "Drag And Drop Test" );

      createActions();
      createMainMenu();
      createContents();
      createListeners();
   }
   
   private void createActions()
   {
      m_actFile = new cFileAction();
      m_actExit = new cExitAction();
   }

   private void createListeners()
   {
      m_dragSource = new DragSource();
      m_dragSource.createDefaultDragGestureRecognizer( m_tree, DnDConstants.ACTION_COPY_OR_MOVE, new cDragGestureListener() );
      
      m_lsnrDragSource = new cDragSourceListener();

      new DropTarget( m_tree, DnDConstants.ACTION_COPY_OR_MOVE, new cDropTargetListener() );
   }
      
   private void createMainMenu()
   {
      JMenuBar menuMain = new JMenuBar();
      
      JMenu menuFile = new JMenu( m_actFile );
      menuFile.add( m_actExit );
      
      menuMain.add( menuFile );
      
      setJMenuBar( menuMain );
   }

   private void createContents()
   {
      m_tree = new JTree();
      m_mdl  = (DefaultTreeModel) m_tree.getModel();
      expand( m_tree, (TreeNode) m_mdl.getRoot() );
      
      m_tree.getSelectionModel().setSelectionMode( TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION );
      m_tree.setAutoscrolls( true );
   
      JScrollPane scrTree = new JScrollPane( m_tree );
      JPanel pnlContents = (JPanel) getContentPane();
      pnlContents.setLayout( new GridBagLayout() );
      pnlContents.add( scrTree, new GridBagConstraints( 0, 0, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets( 0, 0, 0, 0 ), 0, 0 ) );
   }
   
   private void expand( JTree tree, TreeNode node )
   {
      if (node.isLeaf())
         return;
      
      TreeNode[] aPathNodes = ((DefaultTreeModel) m_tree.getModel()).getPathToRoot( node );
      tree.expandPath( new TreePath( aPathNodes ) );

      for ( int iChild=0; iChild<node.getChildCount(); iChild++ )
         expand( tree, node.getChildAt( iChild ) );
   }

   public static void main( String[] saArgs )
   {
      // set the UI to the system UI
      try
      {
         UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
      }
      catch (ClassNotFoundException          e) {}
      catch (IllegalAccessException          e) {}
      catch (InstantiationException          e) {}
      catch (UnsupportedLookAndFeelException e) {}

      // create the main frame and show it
      MainFrame f = new MainFrame();
      f.setVisible( true );
   }

   //---------------------------------------------------------------------------
   // Actions      
   //---------------------------------------------------------------------------
   protected class cFileAction extends AbstractAction
   {
      public cFileAction()
      {
         putValue( AbstractAction.NAME,              "File"             );
         putValue( AbstractAction.SHORT_DESCRIPTION, "Files"            );
         putValue( AbstractAction.MNEMONIC_KEY,      new Integer( 'F' ) );
         putValue( AbstractAction.SMALL_ICON,        null               );
      }
      
      public void actionPerformed( ActionEvent e )
      {
      }
   } // cFileAction
   
   protected class cExitAction extends AbstractAction
   {
      public cExitAction()
      {
         putValue( AbstractAction.NAME,              "Exit"             );
         putValue( AbstractAction.SHORT_DESCRIPTION, "Exits"            );
         putValue( AbstractAction.MNEMONIC_KEY,      new Integer( 'x' ) );
         putValue( AbstractAction.SMALL_ICON,        null               );
      }
      
      public void actionPerformed( ActionEvent e )
      {
         // there should be a better way to do this but there is no close
         // and this seems to be what the system exit menu does, so ...
         MainFrame.this.dispatchEvent( new WindowEvent( MainFrame.this,  WindowEvent.WINDOW_CLOSING ) );
      }
   } // cExitAction
   
   //---------------------------------------------------------------------------
   //---------------------------------------------------------------------------
   protected class cDragGestureListener implements DragGestureListener
   {
      public void dragGestureRecognized(DragGestureEvent event)
      {
         TreePath[] aSelectedPaths = m_tree.getSelectionPaths();
         if (aSelectedPaths.length != 0)
         {
            DraggedObjects draggedObjects = new DraggedObjects();
            
            for ( int iSelectedPath=0; iSelectedPath < aSelectedPaths.length; iSelectedPath++ )
            {
               TreeNode node = (TreeNode) aSelectedPaths[ iSelectedPath ].getLastPathComponent();
               if (!node.isLeaf())
                  return;
               System.out.println( "Dragging " + node );
               draggedObjects.add( node );
            }

            m_dragSource.startDrag( event,
                                    DragSource.DefaultCopyDrop,
//                                    image, new Point( 0, 0 ),
                                    draggedObjects, m_lsnrDragSource );
         }
      }
   } // cDragGestureListener


   protected class cDragSourceListener implements DragSourceListener
   {
      /**
       * Handles the drag operation termination event.  This event occurs for
       * all types of termination (accepted drops, rejected drops, and
       * terminations that occur outside of any drop target).  This event is
       * handled by changing the dragging flag back to false.
       *
       * @param e the drag event for the drag source
       */
      public void dragDropEnd( DragSourceDropEvent e )
      {
//       System.out.println( "drag source end" );
      }

      /**
       * Handles the drag entering a drop target event.  This event is handled
       * by doing nothing.
       *
       * @param e the drag event for the drag source
       */
      public void dragEnter( DragSourceDragEvent e )
      {
//       System.out.println( "drag source enter" );
      }

      /**
       * Handles the drag exiting a drop target event.  This event is handled by
       * doing nothing.
       *
       * @param e the event for the drag source
       */
      public void dragExit( DragSourceEvent event )
      {
//       System.out.println( "drag source exit" );
      }

      /**
       * Handles the drag over the drop target event.  This event is handled by
       * doing nothing.
       *
       * @param e the drag event for the drag source
       */
      public void dragOver( DragSourceDragEvent e )
      {
//       System.out.println( "drag source over" );
      }

      /**
       * Handles the drop action being changed by the user (by changing the drag
       * gesture).  This event is handled by doing nothing.
       *
       * @param e the drag event for the drag source
       */
      public void dropActionChanged( DragSourceDragEvent e )
      {
//       System.out.println( "drag source action changed" );
      }
   }

   //---------------------------------------------------------------------------
   // cDropTargetListener - handles drop target events by checking to make sure
   //                       the drag operation is over a node that can be a drop
   //                       target.
   //---------------------------------------------------------------------------
   protected class cDropTargetListener implements DropTargetListener
   {
      protected boolean    m_bFirstDragOver;           // true = next DragOver is first DragOver
                                                       // for drag cursor kludge
      protected TreePath[] m_aOriginalSelectedPaths;   // the originally selected paths

      /**
       * Handles the drag operation entering the drop target event.  The event
       * is handled by selecting the node (if any) over which the drag occurred.
       * The drag is accepted if the drag is over a node that can be a drop
       * target.
       *
       * @param e the drag entering the drop target event.
       */
      public void dragEnter( DropTargetDragEvent e )
      {
//       System.out.println( "drop target enter" );

         // save the originally selected path
         m_aOriginalSelectedPaths = m_tree.getSelectionPaths();

         // get and select the node that drag event is over
         Point    pt   = e.getLocation();
         TreePath path = m_tree.getPathForLocation( pt.x, pt.y );
         m_tree.setSelectionPath( path );

         // if not over any node, reject
         if (path == null)
            e.rejectDrag();

         // otherwise, ...
         else
         {
            // if the node is not a drop target, reject
            // otherwise, accept
            TreeNode node = (TreeNode) path.getLastPathComponent();
            if (node.isLeaf() || m_mdl.getRoot() == node)
            {
               System.out.println( "drag rejected in enter" );
               e.rejectDrag();
            }
            else
            {
               System.out.println( "drag accepted in enter" );
               e.acceptDrag( e.getSourceActions() );
            }
         }
      }

      /**
       * Handles the event that the drag operation has exited the drop target.
       * The event is handled by reselecting the originally selected paths.
       *
       * @param e the drop target event
       */
      public void dragExit( DropTargetEvent e )
      {
//       System.out.println( "drop target exit" );
         m_tree.setSelectionPaths( m_aOriginalSelectedPaths );
      }

      /**
       * Handles the drag over the drop target event.  The event is handled by
       * by selecting the node (if any) over which the drag occurred.  The drag
       * is accepted if the drag is over a node that can be a drop target.
       *
       * @param e the drag over the drop target event.
       */
      public void dragOver( DropTargetDragEvent e )
      {
//       System.out.println( "drop target drag over" );

         // get and select the node that drag event is over
         Point    pt   = e.getLocation();
         TreePath path = m_tree.getPathForLocation( pt.x, pt.y );
         if (!m_tree.isPathSelected( path ))
            m_tree.setSelectionPath( path );

         // if not over any node, reject
         if (path == null)
            e.rejectDrag();

         // otherwise, ...
         else
         {
            // if the node is not a drop target, reject
            // otherwise, accept
            TreeNode node = (TreeNode) path.getLastPathComponent();
            if (node.isLeaf() || m_mdl.getRoot() == node)
            {
               System.out.println( "drag rejected in over" );
               e.rejectDrag();
            }
            else
            {
               System.out.println( "drag accepted in over" );
               e.acceptDrag( e.getSourceActions() );
            }
         }
      }

      /**
       * Handles the event when the drag operation has terminated with a drop on
       * the drop target.  The event is handled by by selecting the node (if
       * any) on which the drop occurred.  The drop is accepted if the drop is
       * on a node that can be a drop target.  The node is then notified of the
       * drop.
       *
       * @param e the drop event on the drop target
       */
      public void drop( DropTargetDropEvent e )
      {
//       System.out.println( "drop target drop" );

         // get and select the node that drag event is over
         Point    pt   = e.getLocation();
         TreePath path = m_tree.getPathForLocation( pt.x, pt.y );
         if (!m_tree.isPathSelected( path ))
            m_tree.setSelectionPath( path );

         // if not over any node, reject
         if (path == null)
            e.rejectDrop();

         // otherwise, ...
         else
         {
            // if the node is a drop target, accept and notify the node of the drop
            // otherwise, reject
            // if the node is not a drop target, reject
            // otherwise, accept
            TreeNode node = (TreeNode) path.getLastPathComponent();
            if (node.isLeaf() || m_mdl.getRoot() == node)
            {
               System.out.println( "Drop rejected" );
               e.rejectDrop();
            }
            else
            {
               e.acceptDrop( e.getSourceActions() );
               System.out.println( "Dropped on " + node );
               e.dropComplete( true );
            }
         }
      }

      /**
       * Handles the event when the user changes the current drop gesture.  The
       * event is handled by doing nothing.
       *
       * @param e the new drop target drag event
       */
      public void dropActionChanged( DropTargetDragEvent e )
      {
      }

   } // cDropTargetListener

   protected static class DraggedObjects extends ArrayList implements Transferable
   {
      public sta

Comments
EVALUATION I see the problem. We have similar issues with JTable. ###@###.### 2004-03-23 When using javax.swing.JTree class, the users are provided with the option of either 'cancel' or 'commit' the changes during any interruption while editing the cell. The API to use is JTree.setInvokesStopCellEditing(boolean) The default is false, which means the cancel operation will be initiated when such interruption happens. Hence, the user code requires change accordingly. ###@###.### 2004-12-22 12:48:50 GMT So there are three issues here: 1) JTree cancels editing when selecting another node. ###@###.###'s evaluation is correct (thank you) - submitter should be calling JTree.setInvokesStopCellEditing(true). 2) Submitter wants clicking anywhere in JTree to stop editing. 3) Submitter wants text to automatically be selected when start editing. Keeping this open for 2 and 3. ###@###.### 2004-12-22 16:59:43 GMT Issue two will actually happen if you have invoked setInvokesStopCellEditing(true). It should really happen regardless of the value of the 'invokesStopCellEditing' property. I've contacted submitter to make sure they're aware of this though as it sounds like they want to set this to true. The last issue is really a separate issue and I'm going to spin up a separate bug for this shortly. ###@###.### 2005-1-21 16:07:47 GMT Bug 6231029 has been filed for select all logic. As the other issues in this bug are not bugs I'm closing this one out. ###@###.### 2005-2-18 17:40:22 GMT
21-01-2005

WORK AROUND Name: rv122619 Date: 03/23/2004 We have fixed both of these problems by overriding the JTree's UI and extending its mouse listener. ======================================================================
25-09-2004