The Artima Developer Community
Sponsored Link

Software and Return on Investment
Java desktop drag and drop
by Gregg Wonderly
June 1, 2006
Summary
There are several different things about desktop drag and drop that are interesting. I've been using several things in my JiniDesktop class in my http://startnow.dev.java.net project. Here's an extracted example of all the pieces.

Advertisement

When creating a drag and drop desktop like you'd find on a desktop computing environment, there are several interesting things to tend to. I've been working through these issues in my JiniDesktop application. This application looks for all ServiceUI enabled Jini services, and then enumerates them into a tree structure for access.

You can drag the service names out of the tree structure and place them on a desktop which you can then arrange by dragging the icons around. I used a JLayeredPane to make the drag operations be layered on top of the static operations more easily.

My JDesktopItem class buried inside of the JiniDesktop class has much more too it then shown here, including some different class structure. I extracted the parts that I thought would be the most interesting and have included them below.

The DeskItem class

I used JLabel as the parent class so that I could also associate an Icon with the text of the service name. I haven't included support or use of Icons here to simplify the visible code.

I'm just going to include all of the code here for now, and my follow up with some point by point discussion if anyone is interested. There are two classes here, if you put them into the appropriately named files, adding the necessary imports, you should be able to compile them. Run the DeskExample class and you'll see how the draging on the desktop works including hover control and selection highlighting.

I used gradient drawing to make everything look fancy. You can, of course do some different things with the drawing.

public class DeskItem extends JLabel {
    /**
     * The original UI that we revert to when not selected, hovered or otherwise active.
     */
    private ComponentUI oldUI;
    /**
     * Is this DeskItem in a selected state?
     */
    protected boolean selected;
    /**
     * The JLayeredPane we are on, if any.
     */
    private JLayeredPane pane;
    /**
     * The MouseMotionListener created to manage our drag state.
     */
    private MyMouseMotionListener mml;
    
    /**
     * The ComponentUI used for normal rendering.
     */
    protected static MyUI myui = new MyUI();
    /**
     * The ComponentUI used for active rendering (hover).
     */
    protected static MyUI actui = new MyUI( new Color( 255, 205, 215) );
    /**
     * The ComponentUI used for selected state.
     */
    protected static MyUI selui = new MyUI( new Color( 230, 230, 255 ) );
    /**
     * The ComponentUI used for selected and active rendering (hover).
     */
    protected static MyUI selactui = new MyUI( new Color( 180, 180, 255 ) );
    /**
     * Should gradient rendering be down at all?
     */
    protected static boolean gradients = true;
    /**
     * The last DeskItem that was selected.
     */
    private static DeskItem lastItem;
    /**
     * The current, active DeskItem.
     */
    private static DeskItem curItem;
    /**
     * Logger for this class
     */
    private static Logger log = Logger.getLogger( DeskItem.class.getName() );

    /**
     * Activates all the mouse listening activities
     */
    private void mouseEntry() {
        addMouseListener( new MouseAdapter() {
            public void mouseEntered( MouseEvent ev ) {
                if( gradients ) {
                    setUI( selected ? selactui : actui );
                    setOpaque(true);
                }
                repaint();
            }
            public void mouseExited( MouseEvent ev ) {
                if( gradients ) {
                    setUI( selected ? selui : myui );
                    setOpaque(selected);
                }
                repaint();
            }
        });
        addMouseListener( new MyMouseAdapter() );
    }
    
    /**
     * Used to deselect the last selected DeskItem so that none are active.  This can
     * be called when the background of the desk is clicked on, or some other focus
     * change event.
     */
    public static synchronized void clearSelection() {
        if( lastItem != null ) {
            lastItem.exit();
        }
        lastItem = null;
    }

    /**
     * This is called when the item is double clicked on.  A subclass can provide an
     * action here.
     */
    public void invoke() {
    }
    
    /**
     * Called to report exception encountered during processing of events.
     * @param ex The Exception to report.
     */
    protected void reportException( Exception ex ) {
        log.log( Level.SEVERE, ex.toString(), ex );
    }
    /**
     * Make the selectable object bigger to surround the text 
     * with coloring.
     */
    protected void setBorder() {

        setBorder( BorderFactory.createEmptyBorder( 5,5,5,5));    
    }
    /**
     * Creates a DeskItem that is on a JLayeredPane with the passed String identifier.
     * @param pane The JLayeredPane that the item will be placed on.
     * @param label The Label string to use.
     */
    public DeskItem( JLayeredPane pane, String label ) {
        super(label);
        this.pane = pane;
        oldUI = getUI();
        if( gradients )
            setUI( myui );
        mouseEntry();
    }

    /**
     * Constructs a DeskItem that won't be placed on a JLayeredPane.
     * @param label The String label for this item
     */
    public DeskItem( String label ) {
        super(label);
        oldUI = getUI();
        if( gradients )
            setUI( myui );
        mouseEntry();
    }

    /**
     * Called when the mouse enters this DeskItem.
     */
    protected void enter() {
        selected = true;
        if( gradients ) {
            setUI( selui );
        }
        setOpaque( true );
        repaint();
        curItem = this;
    }

    /**
     * Called when the mouse exits this DeskItem.
     */
    protected void exit() {
        curItem = null;
        selected = false;
        setOpaque( false );
        repaint();
    }

    /**
     * Called when the user performs a context menu click using the popup trigger button/operation
     * associated with the component.
     * 

* Subclasses can override this method to provide an operation. * * @param ev The associated mouse event to get the context from. */ protected void popup( MouseEvent ev ) { } /** * The MouseAdapter that handles click events to establish the MouseMotionListener. */ private class MyMouseAdapter extends MouseAdapter { /** * The item this listener is associated with. */ DeskItem item; /** * Creates an instance. */ public MyMouseAdapter() { this.item = DeskItem.this; } /** * Called when the user clicks on the DeskItem. * @param ev The event associated with the click operation. */ public void mouseClicked( MouseEvent ev ) { if( ev.getClickCount() == 2 && ev.getButton() == 1 ) { log.finer("Invoking: "+DeskItem.this); try { // Invoke the item to process any action. item.invoke(); } catch( Exception ex ) { reportException(ex); } } } /** * Called when the mouse is pressed down. * @param ev The associated event. */ public void mousePressed( MouseEvent ev ) { // Check if popup if( ev.isPopupTrigger() ) { // switch selected to this item if( lastItem != DeskItem.this ) { lastItem.exit(); } lastItem = DeskItem.this; lastItem.enter(); // Show the menu if any popup(ev); return; } // Not popup, so clear last selected if( lastItem != null ) { lastItem.exit(); } // If not button 1, just ignore if( ev.getButton() != 1 ) return; // Activate motion listener and what for drag. createMotionListener(ev); // Add the mouse listener created, or already existing. DeskItem.this.addMouseMotionListener(mml); } /** * called to create the MouseMotionListener when the drag operation starts. * @param ev The associated mouse event. */ private void createMotionListener(MouseEvent ev) { lastItem = DeskItem.this; lastItem.enter(); Point p = lastItem.getLocation(); final int offx = ev.getX(); final int offy = ev.getY(); // Create new listener as needed if( mml == null ) { mml = new MyMouseMotionListener(); } // Set drag offsets into object mml.setOffsets( offx, offy ); } /** * Called when the mouse button is released. * @param ev The associated event. */ public void mouseReleased( MouseEvent ev ) { if( ev.isPopupTrigger() ) { // Make sure the correct last item is identified. if( lastItem != null && lastItem != DeskItem.this ) { lastItem.exit(); } lastItem = DeskItem.this; lastItem.enter(); popup(ev); // Stop listening to mouse motion events. if( mml != null ) DeskItem.this.removeMouseMotionListener(mml); return; } // Not popup, remove motion listener DeskItem.this.removeMouseMotionListener(mml); // When dropped, move back to the default layer. if( pane != null ) { pane.setLayer( DeskItem.this, JLayeredPane.DEFAULT_LAYER.intValue(), 0 ); } } } /** * The MouseMotionAdapter used to track the drag operation. */ private class MyMouseMotionListener extends MouseMotionAdapter { /** * The x offset of the initial mouse click from the left edge of the DeskItem. */ int offx; /** * The y offset of the mouse from the top of the DeskItem */ int offy; /** * updates the current offsets for each successive drag operation to the click point that the * mouse was out when the mouse was pressed. * @param x The X location of the initial mouse down event. * @param y The Y location of the initial mouse down event. */ public void setOffsets(int x, int y ) { offx = x; offy = y; } /** * Called when the mouse is moved without a button down. * @param ev The associated event for this operation. */ public void mouseMoved( MouseEvent ev ) { mouseDragged(ev); } /** * Called when the mouse is moved with button one down. * @param ev The associated mouse event. */ public void mouseDragged( MouseEvent ev ) { Point pt = getLocation(); Point p = new Point( ev.getX()+pt.x-offx, ev.getY()+pt.y-offy); // Positioning is every 5 pixels to make it easier to line things up. int xoff = p.x % 5; int yoff = p.y % 5; p = new Point( p.x-xoff+5, p.y-yoff+5); // On a JDesktopPane, change the layer so that // we pass over everything on the desktop if( pane != null ) { pane.setLayer( DeskItem.this, JLayeredPane.DRAG_LAYER.intValue() ); } setLocation( p.x, p.y ); } } /** * The UI used to control the drawing of the DeskItem without having to conditionalize * the paint operations. */ private static class MyUI extends com.sun.java.swing.plaf.windows.WindowsLabelUI { /** * The color of the background. */ Color col; /** * Constructs an instance with the default coloring using the default background color * associated with the look and feel. */ public MyUI() { col = new JLabel().getBackground(); } /** * Constructs and instance using the passed color for the background. * @param c The color to use for the background painting operations. */ public MyUI( Color c ) { col = c; } /** * Called to perform the paint operation on the passed component. * @param g The graphics context for the paint operation. * @param c The component to paint into the graphics environment. */ public void update(Graphics g, JComponent c) { if( !gradients ) { super.update( g, c ); return; } if (c.isOpaque()) { Graphics2D g2 = (Graphics2D)g; Paint pt = g2.getPaint(); g2.setPaint( new GradientPaint( c.getWidth(), 0, col.darker(), c.getWidth(), c.getHeight(), col ) ); g.fillRoundRect(0, 0, c.getWidth(),c.getHeight(), 5, 5 ); g2.setPaint( pt ); } super.paint(g, c); } } }

The DeskExample class

This is the main class which provides the example desktop with some DeskItems on it. Drag them around and see how it all works.

public class DeskExample  {
    Logger log = Logger.getLogger( getClass().getName() );
    public static void main( String args[] ) {
        new DeskExample();
    }
    
    /** Creates a new instance of DeskExample */
    public DeskExample() {
        JFrame f = new JFrame("Example desktop dragging");
        JDesktopPane pane = new JDesktopPane() {
            // Use the background color of a known component.
            Color col = new Color( 200, 235, 210 );

            public void paintComponent( Graphics g ) {
                Graphics2D g2 = (Graphics2D)g;
                Paint pt = g2.getPaint();
                int w = getSize().width;
                int h = getSize().height;
                g2.setPaint( new GradientPaint( w/4, h/2, col.brighter(),
                    w, h, col.darker() ) );
                g2.fillRect( 0, 0, w, h );
                g2.setPaint( pt );
            }
        };
        pane.setOpaque( true );
        Packer pk = new Packer( f.getContentPane() );
        pk.pack( pane ).fillboth();
        for( int i = 0; i < 10; ++i ) {
            DeskItem item = new DeskItem( pane, "Item #"+i );
            log.info("adding item: "+item);
            pane.add( item );
            pane.setLayer( item, JLayeredPane.DEFAULT_LAYER.intValue() );
            item.setLocation( 10, i*30 );
            item.setVisible(true);
            item.setSize( item.getPreferredSize() );
            item.setForeground( Color.black );
        }
        f.pack();
        f.setSize( 400, 300 );
        f.setLocationRelativeTo( null );
        f.addWindowListener( new WindowAdapter() {
            public void windowClosing( WindowEvent ev ) {
                System.exit(1);
            }
        });
        f.setVisible( true );
    }
}

Talk Back!

Have an opinion? Readers have already posted 2 comments about this weblog entry. Why not add yours?

RSS Feed

If you'd like to be notified whenever Gregg Wonderly adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Gregg Wonderly graduated from Oklahoma State University in 1988 with an MS in COMSCI. His areas of concentration include Operating Systems and Languages. His first job was at the AT&T Bell Labs facilities in Naperville IL working on software retrofit for the 5ESS switch. He designed a procedure control language in the era of the development of Java with similar motivations that the Oak and then Java language development was driven by. Language design is still at the top of his list, but his focus tends to be on application languges layered on top of programming languages such as Java. Some just consider this API design, but there really is more to it! Gregg now works for Cyte Technologies Inc., where he does software engineering and design related to distributed systems in highly available environments.

This weblog entry is Copyright © 2006 Gregg Wonderly. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use