Specifically, I've got a set of forms in an instance of JTabbedPane. I know one of the tabs holds a field that defines my preferred download directory, which I happen to have open in my file browser. Drag from the file browser onto the JTabbedPane, and -- oops, I'm not on the right pane. It'd be nice to have the tabs change as I drag across them so that I can find the right destination.
Since this is system-wide behavior, I'd like to enable the behavior once at application startup and forget about it, rather than having to add listeners or subclass every instance of JTabbedPane and JTree that's going to accept a drop.
In order to make this happen, we install a global listener to the default DragSource, which lets us respond to any drags initiated within the same VM. Since that provides us with the current drag location, we can scan through the currently available components using
SwingUtilities.getDeepestComponentAt()to see if what's under the cursor needs to be navigated. If we do make a modification, a
Runnablewhich will undo the modification is added to a queue so that we can restore the component to its original state if the drop ends up going somewhere else or being canceled.
To handle drags originating from outside the VM requires a little hacking, since there isn't any sort of global drop target listener. During a native drag, you won't see any
MouseEvents. As of 1.4+, there is a
SunDropTargetEvent) that gets processed by
Component.dispatchEventImpl(), but it gets swallowed before the component passes events off to
AWTEventListeners. You can't override
dispatchEvent, and wouldn't want to anyway because you'd have to do it on every component. Instead, we can install a custom
EventQueuewhich can watch for the drop target events and forward them to our drop target listener.
The navigator class has built-in support for JTabbedPanes and JTrees. Custom classes that wish to support navigation can register themselves with a simple interface that performs custom navigation and rollback. This way you can enable auto navigation on whatever tree table implementation you happen to be using.
In order to avoid unintended navigation when the mouse is dragged rapidly across a component, I've added a configurable delay to when the navigation starts. You need to hover over the same spot for roughly half a second to activate the navigation.