TreeWrapper updated: arrange your OPML documents!

After some short holidays, I have been doing some little improvements in TreeWrapper, an easy to use wrapper that allows for drag and drop on JTrees. Improvements include support for JDK 1.4, better handling of popup menus, better performance, support for disabled trees and some other minor things.

To verify that everything is working I built a simple to use OPML editor, that you can use to arrange your RSS feeds into categories.

JDK 1.4 support

As I explained earlier on the first post about TreeWrapper, JDK 5 includes support for examining what the contents of the object being transferred are during the drag operation. JDK 1.4 does not. Well, not directly. Kirill Grouchnikov found that JDK 1.4 includes a protected method exactly for that. He suggested using reflection to access that method and, well, there we go. The code looks like this:

507     private Transferable getTransferable( DropTargetDragEvent dtde )
508     {
509       try
510       {
511         DropTargetContext context = dtde.getDropTargetContext();
512         if ( getTransferableMethod == null )
513         {
514           getTransferableMethod = context.getClass().getDeclaredMethod( "getTransferable", EMPTY_CLASS_ARRAY );
515           getTransferableMethod.setAccessible( true );
516         }
517         return (Transferable) getTransferableMethod.invoke( context, EMPTY_OBJECT_ARRAY );
518       }
519       catch( Exception e )
520       {
521         e.printStackTrace( System.err );
522         return null;
523       }
524     }

Thanks, Kirill, for this. As a consequence TreeWrapper runs on JDK 1.4 now.

Flooded with events!!

The fact is that "dragOver( DropTargetEvent )" is invoked several times per second during a drag and drop operation, even if the user does not move the mouse. As a consequence, the previous version of TreeWrapper invoked the set of listeners a lot of times per second, even for the same node!!

This, of course, is a waste of CPU resources. And it slows things down a lot. The new version of TreeWrapper handles gracefully this behaviour by keeping a reference to the last "dragged over" node, and refusing to ask the listeners twice for the same node. As a consequence, the new version of TreeWrapper is much faster and consumes less CPU resources.

The code looks like this:

526     /** This node to avoid too many invocations to dragOver */
527     private TreeNode lastDragOverNode = null;
528     
529     public void dragOver(DropTargetDragEvent dtde)
530     {
531       if ( ! tree.isEnabled() )
532       {
533         dtde.rejectDrag();
534         return;
535       }
536       
537       // Is this a valid node for dropping?
538       TreePath dropPath = tree.getClosestPathForLocation( dtde.getLocation().x,
539         dtde.getLocation().y );
540       
541       TreeNode currentDropNode = (TreeNode) dropPath.getLastPathComponent();
542       
543       if ( dropPath == null || currentDropNode == null || currentDropNode.equals( lastDragOverNode ) )
544       {
545         return;
546       }
547       else
548       {
549         lastDragOverNode = currentDropNode;
550       }

Better popup menu handling

I'm running a recent install of Kubuntu 6.06. I like KDE and feel comfortable with it. I admit I didn't test the previous release of TreeWrapper on Windows (I just did some small tests on Windows), so I didn't notice that Windows and Linux handle popup menu triggers in different ways.

The fact is that a MouseEvent.isPopupTrigger() behaves differently in Windows and Linux. In Linux you detect popup events (usually right-clicks on the mouse) by overriding the "MouseListener.mouseReleased( MouseEvent me )" method. But Windows prefers the "MouseListener.mousePressed( MouseEvent me )" instead.

The bug is easy to solve. Basically you have a MouseAdapter and override both methods, and then for each one you verify that MouseEvent.isPopupTrigger(), like this:

139   class PopupChooserMouseListener
140     extends MouseAdapter
141   {
142     
143     // @Override
144     public void mousePressed(MouseEvent e)
145     {
146       verifyPopupTrigger( e );
147     }
148     
149     // @Override
150     public void mouseReleased(MouseEvent e)
151     {
152       verifyPopupTrigger( e );
153     }
154     
155     private void verifyPopupTrigger( MouseEvent e )
156     {
157       if ( customPopupHandler != null && e.isPopupTrigger() )
158       {
159         Point point = e.getPoint();
160         TreePath path = tree.getClosestPathForLocation( point.x, point.y );
161         if ( path != null )
162         {
163           tree.getSelectionModel().setSelectionPath( path );
164           TreeNode node = (TreeNode) path.getLastPathComponent();
165           JPopupMenu menu = customPopupHandler.getMenuAt( tree, node );
166           if ( menu != null )
167           {
168             menu.show( tree, point.x, point.y );
169           }
170         }
171       }
172     }
173   }

An OPML Editor

OPML Editor Screenshot

To verify that everything was working correctly I decided to build a simple OPML editor. You know, the Outline Processor Markup Language is a spec commonly used to export collections of RSS feeds. You can, for instance, import your list of feeds (as an OPML document) from your bloglines account. This OPML editor allows me to load/save/import and arrange my list of feeds and categories. This OPML editor is a step forward towards the construction of a more complete RSS feed reader.

You can run the OPML Editor using JavaWebStart 1.4+.

More info on the TreeWrapper can be found at the attic.

I would appreciate feedback about he TreeWrapper. If you find a bug or see an interesting new feature then please let me know.

Happy Swinging, Antonio

blog comments powered by Disqus