Friday, March 26, 2004

Extending the DnD in Swing = reinventing the wheel


The Swing's DnD functionality looks very extendable at a first glance, but as more as I dig deeper into this part of the SDK I understand that it is not 100% true. Adding new DnD functionality to a component, forces the developer to reinvent the wheel every time. Why do the developer have to redo already implemented functionality to extend a components DnD actions?




Many of the JComponents in the swing package supports drag and drop actions. Those components already have a nice feature rich implementation that handles the dragging and dropping of objects to/from the component. The problem arises when the developer wants to add a new flavor to the components DnD functionality, ie extend so the component can import new types of objects. Each JComponent can only have one TransferHandler, and this is the one used during a DnD action.




Once again I'm in the middle of adding DnD to the email client (see my previous blog). This time I would like to add drag and drop of files into a new email, ie. add them as attachments. Other native email clients lets the user drag files onto the text area and the attachment bar. When the user drops the files in either of the target components, they are added to the emails attachment list. Now this was no problem adding the DnD of files to a attachment component but when I wanted to add this feature to the editor panes DnD support, I stumbled upon this problem. The editor pane already has a TransferHandler that supports strings/texts/Unicode data flavors. I would only like to add the fileList data flavor so it would also accept files.




The problem starts with that the TransferHandlers that are used by the JEditorPane components is package private, and therefore I cant extend them to get the functionality I want. I don't want to re-implement all those features in my own version, since that would be a waste of time and bloat the code. Then I thought of using the chain of responsibility design pattern to merge the two transfer handlers into one. Ie use one as the primary transfer handler, and then if the primary doesn't support a certain flavor it will try the other handler(s) if they can import a certain flavor. This to me looked like a fine idea, and I developed a nice class to work as one TransferHandler, but actually handling several. Everything went fine until the user tries to drag and drop from a component that has this object.




During that drag and drop action, it wants to create a transferable object through the protected method createTransferable(). BUT the method is called by at javax.swing.TransferHandler$DragHandler.dragGestureRecognized(TransferHandler.java:710) method, (outside the TransferHandler class) and it uses the fact that they are in the same package, ie they can break the protected access modifier. Now I have no clue on how to create the real transferable object, since in my implementation (see below) tries to do this on the primary transfer handler, but its createTransferable() method is protected and therefore I cant reach it, I'm not in the same package so I can "cheat" as the DragHandler does.




Once again, there is a problem in the design of the Swings DnD, but at least for this problem I have a workaround. I get around the problem by calling the needed protected methods using the reflection API, and making them public. But this to me is not the correct way of doing it, I should be able to call those methods in TransferHandler without relying on using reflection. There is already a feature request at Suns bug report system, and all I can do is hope that they change it in coming releases, but for now I at least got a solution. If you would like to use it, it is part of the Frappucino module in the Columba project. Frappucino is a collection of widgets that eases the development of Swing UIs. Check the link section for the class.


Monday, March 22, 2004

Shortcomings of Sun's DnD model!?


I've tried to implement a drag and drop action of attachments in an email client, and found that the Sun's implementation of DnD makes it impossible to mimic native applications drag and drop actions. The DnD framework does not support creating files lazily. This is a shortcoming that makes it very hard to create feature-rich java clients that can compete with native clients. This is not only true for email clients, its for most clients that work with large files over the network, ex. webbrowsers, FTPs, and others.
The problem comes from the fact that the files must exists locally before the DnD of files can begin.
Note that this is about DnD a file outside the java application!




In the email client the attachments lies within the email(s), and needs to be extracted before they can be copied to the correct location. For performance reasons I dont want to extract all attachments to the disk when the user opens an email with attachments. Neither do I want to extract the attachment during the user's DnD operation. I would like to make the copy/extraction when the DnD has been completed. This is also true for FTP clients as well, for instance if the UI displays the files on the remote server and the user wants to download it to the desktop, preferably he/she should drag the file from the file list to the dekstop.




The only way of copying (or moving) a File through DnD is to create a Transferable object that supports the DataFlavor.javaFileListFlavor. This transferable should return a List of files that are to be copied/moved to the target location. When the DnD action is completed the actual copying/moving of the file is handled deep down within the Suns own code, and I havent found the place to see how its done. But the problem is that the Files must exists locally before the DnD starts, ie before the getTransferData() or the exportDone() methods are called. This I cannot do, since I dont want to extract the attachment until the DnD is complete. (performance issues) There is no way of finding out the target file from the current DnD action., ie it is impossible to make the copying yourself.



My first idea was to create an empty dummy file on the disk, and use this as the source File to be dragged around. Then when the DnD is completed, I would extract the attachment from the email and make the copy manually. Doing it this way, Im sure that
  • theres no unecessary extraction,
  • the DnD action is not hindered by sharing CPU with the extraction of the file itself
  • the user doesnt have to wait until he has choosen where to put the file
  • the extraction is done according to the API for the email client.

There are four methods that need to be overloaded in order to get a TransferHandler to work:
  • getSourceAction() - return which actions the transfer handler supports, ie copy/move
  • createTransferable() - returns a Transferable object that is sent around to the other components that supports DnD actions.
  • canImport() - if the current component can import a certain type of data flavors
  • exportDone() - is called when the DnD action is finished, remove those objects that has been moved

The Transferable interface has these methods.
  • getTransferDataFlavors() - return which data flavors that this supports, in my case the javaFileListFlavor flavor
  • isDataFlavorSupported() - return if the transferable understands one type of data flavor
  • getTransferData() - return the actual data that is to be DnD'ed, in my case the list of Files

Ive found out that the actual Copying of the files is done before the exportDone() method and after the getTransferData(), which is not suprising since the actual copying is done by the DnD framework. But the exportDone() method has no way of telling what the target was, and therefore makes it impossible to make the copy yourself, or start the download to the location.




So, as far as I can see, there is no solution or workaround for this problem, and that it will be virtually impossible to create applications that can compete with native programs. If someone haves a solution or workaround for this problem, it would be greatly appreciated to get that.

But for now, I think the default implementation of Sun's DnD of Files is nice, but it lacks this important feature that hinders the usability.




SUN bug report - http://developer.java.sun.com/developer/bugParade/bugs/4808793.html - Bug # 4808793