Santhosh Kumar's Weblog

hey, catch me at [email protected]

All | General | Java | Developer | Swing | JAXP
20060207 Tuesday February 07, 2006

Make you application restart on its own

Sometimes it is required to make your application restart on its own. For example, IntelliJ Idea after downloading the new plugins requires to be restarted. But it can't restart itself, so asks the user to restart after its exit.

Let us see how to make you application restart on its own.

import<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> javax.swing.*; 
import<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> java.io.*; 
import<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> java.awt.event.*; 
import<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> java.awt.*; 
public<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> App{ 
    public<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> main(String args[]){ 
        Action restartAction = new<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> AbstractAction("Restart"<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">){ 
            public<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> actionPerformed(ActionEvent ae){ 
                try<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">{ 
                    new<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> File("restart"<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">).createNewFile(); 
                    System.exit(0); 
                }catch<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">(Exception ex){ 
                    ex.printStackTrace(); 
                } 
            } 
        }; 
        JFrame frame = new<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> JFrame("App"<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">); 
        JMenuBar menubar = new<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> JMenuBar(); 
        JMenu menu = new<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> JMenu("File"<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">); 
        menu.add(restartAction); 
        menubar.add(menu); 
        frame.setJMenuBar(menubar); 
        frame.setSize(100, 100); 
        frame.setVisible(true<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">); 
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 
    } 
}

and the launch script run.bat is as follows:

:restart
del restart
java App
if exist restart GOTO restart

The application is run by the script run.bat. When user clicks File>Restart from menubar, it creates a file named restart and exits. the script checks for restart file existance and if it exists, it reruns the entire script again, which results in launching the application again.

This can be changed not to use any file as follows. Change restartAction as follows:

Action restartAction = new<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> AbstractAction("Restart"<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">){ 
     public<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> actionPerformed(ActionEvent ae){ 
         try<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">{ 
             System.exit(100); 
         }catch<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">(Exception ex){ 
             ex.printStackTrace(); 
         } 
      } 
}; 

Here the program exits with value 100 indicating restart. and run.bat is changed to:

:restart
java App
if %errorlevel%==100 GOTO restart

These tricks are not just applicable to java applications. These tricks will be very useful to restart a sever which is running on a remote machine. implement restart() in server as explained and expose it to clients using RMI, JMX or by any other means.

Your comments are appreciated.

( Feb 07 2006, 12:25:36 PM EST ) Permalink Comments [13]

20051208 Thursday December 08, 2005

JToolBar with "more" Button

import<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> javax.swing.*; 
import<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> javax.swing.event.PopupMenuListener; 
import<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> javax.swing.event.PopupMenuEvent; 
import<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> java.awt.event.ActionEvent; 
import<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> java.awt.event.ActionListener; 
import<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> java.awt.event.ComponentAdapter; 
import<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> java.awt.event.ComponentEvent; 
import<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> java.awt.*; 
/** 
 * MySwing: Advanced Swing Utilites 
 * Copyright (C) 2005  Santhosh Kumar T 
 * <p/> 
 * This library is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version. 
 * <p/> 
 * This library is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 * Lesser General Public License for more details. 
 */ 
/** 
 * to use this feature replace: 
 *   frame.getContentPane().add(toolbar, BorderLayout.NORTH); 
 * with 
 *   frame.getContentPane().add(MoreButton.wrapToolBar(toolBar), BorderLayout.NORTH); 
 * 
 * @author<../../b__/font__font_style_.css"font-family:monospaced;" color="#808080"> Santhosh Kumar T 
 * @email<../../b__/font__font_style_.css"font-family:monospaced;" color="#808080">  [email protected] 
 */ 
public<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> MoreButton extends<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> JToggleButton implements<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> ActionListener{ 
    JToolBar toolbar; 
    private<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> MoreButton(final<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> JToolBar toolbar){ 
        super<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">(new<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> ImageIcon(MoreButton.class<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">.getResource("more.gif"<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">))); 
        this<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">.toolbar = toolbar; 
        addActionListener(this<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">); 
        setFocusPainted(false<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">); 
        // hide & seek 
        toolbar.addComponentListener(new<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> ComponentAdapter(){ 
            public<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> componentResized(ComponentEvent e){ 
                setVisible(!isVisible(toolbar.getComponent(toolbar.getComponentCount()-1), null<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">)); 
            } 
        }); 
    } 
    // check visibility 
    // partially visible is treated as not visible 
    private<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> boolean<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> isVisible(Component comp, Rectangle rect){ 
        if<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">(rect==null<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">) 
            rect = toolbar.getVisibleRect(); 
        return<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> comp.getLocation().x+comp.getWidth()<=rect.getWidth(); 
    } 
    public<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> actionPerformed(ActionEvent e){ 
        Component[] comp = toolbar.getComponents(); 
        Rectangle visibleRect = toolbar.getVisibleRect(); 
        for<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">(int<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> i = 0; i<comp.length; i++){ 
            if<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">(!isVisible(comp[i], visibleRect)){ 
                JPopupMenu popup = new<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> JPopupMenu(); 
                for<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">(; i<comp.length; i++){ 
                    if<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">(comp[i] instanceof<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> AbstractButton){ 
                        AbstractButton button = (AbstractButton)comp[i]; 
                        if<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">(button.getAction()!=null<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">) 
                            popup.add(button.getAction()); 
                    } else<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> if<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">(comp[i] instanceof<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> JSeparator) 
                        popup.addSeparator(); 
                } 
                //on popup close make more-button unselected 
                popup.addPopupMenuListener(new<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> PopupMenuListener(){ 
                    public<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> popupMenuWillBecomeInvisible(PopupMenuEvent e){ 
                        setSelected(false<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">); 
                    } 
                    public<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> popupMenuCanceled(PopupMenuEvent e){} 
                    public<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> popupMenuWillBecomeVisible(PopupMenuEvent e){} 
                }); 
                popup.show(this<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">, 0, getHeight()); 
            } 
        } 
    } 
    public<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> Component wrapToolBar(JToolBar toolbar){ 
        JToolBar moreToolbar = new<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> JToolBar(); 
        moreToolbar.setRollover(true<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">); 
        moreToolbar.setFloatable(false<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000">); 
        moreToolbar.add(new<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> MoreButton(toolbar)); 
        JPanel panel = new<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> JPanel(new<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> BorderLayout()); 
        panel.add(toolbar, BorderLayout.CENTER); 
        panel.add(moreToolbar, BorderLayout.EAST); 
        return<../../b__/font__font_style_.css"font-family:monospaced;" color="#000000"> panel; 
    } 
} 

Your comments are appreciated.

( Dec 08 2005, 03:12:14 PM EST ) Permalink Comments [10]

20051109 Wednesday November 09, 2005

Show progress in Modal Dialog

You can find all my postings in categorized tree here.
The title of my page "Santhosh Kumar's Weblog" now has hyperlink to this URL. You can also find this URL in sidebar with name "MyBlog" under "My Bookmarks" Category.

Let us first create a model for progress of task.

/** 
 * MySwing: Advanced Swing Utilites 
 * Copyright (C) 2005  Santhosh Kumar T 
 * <p/> 
 * This library is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version. 
 * <p/> 
 * This library is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 * Lesser General Public License for more details. 
 */ 
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ProgressMonitor{ 
    int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> total, current=-1; 
    boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> indeterminate; 
    int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> milliSecondsToWait = 500; // half second 
    String status; 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ProgressMonitor(int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> total, boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> indeterminate, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> milliSecondsToWait){ 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.total = total; 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.indeterminate = indeterminate; 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.milliSecondsToWait = milliSecondsToWait; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ProgressMonitor(int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> total, boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> indeterminate){ 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.total = total; 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.indeterminate = indeterminate; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> getTotal(){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> total; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> start(String status){ 
        if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(current!=-1) 
            throw<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> IllegalStateException("not started yet"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.status = status; 
        current = 0; 
        fireChangeEvent(); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> getMilliSecondsToWait(){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> milliSecondsToWait; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> getCurrent(){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> current; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> String getStatus(){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> status; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> isIndeterminate(){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> indeterminate; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> setCurrent(String status, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> current){ 
        if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(current==-1) 
            throw<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> IllegalStateException("not started yet"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.current = current; 
        if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(status!=null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">) 
            this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.status = status; 
        fireChangeEvent(); 
    } 
    /*--------------------------------[ ListenerSupport ]--------------------------------*/ 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Vector listeners = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Vector(); 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ChangeEvent ce = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ChangeEvent(this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> addChangeListener(ChangeListener listener){ 
        listeners.add(listener); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> removeChangeListener(ChangeListener listener){ 
        listeners.remove(listener); 
    } 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> fireChangeEvent(){ 
        ChangeListener listener[] = 
                (ChangeListener[])listeners.toArray(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ChangeListener[listeners.size()]); 
        for<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> i=0; i<listener.length; i++) 
            listener[i].stateChanged(ce); 
    } 
}

progress always starts from zero. user specified the total units and whether it is indeterminate or not. The arguments milliSecondsToWait tells, after how much time after the task started, the progress dialog should be shown. This value defaults to half second. Because if GUI doesn't respond within half second, user will clearly fees that GUI is slow. If the task completes before the milliSecondsToWait is elapsed, then progress dialog is not shown.

ProgressMonitor fires ChangeEvent which can be listened by some other class to show progress dialog. Thus GUI is decoupled from model.

Now create a ProgressDialog class:

/** 
 * MySwing: Advanced Swing Utilites 
 * Copyright (C) 2005  Santhosh Kumar T 
 * <p/> 
 * This library is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version. 
 * <p/> 
 * This library is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 * Lesser General Public License for more details. 
 */ 
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ProgressDialog extends<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JDialog implements<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ChangeListener{ 
    JLabel statusLabel = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JLabel(); 
    JProgressBar progressBar; 
    ProgressMonitor monitor; 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ProgressDialog(Frame owner, ProgressMonitor monitor) throws<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> HeadlessException{ 
        super<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(owner, "Progress"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, true<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        init(monitor); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ProgressDialog(Dialog owner, ProgressMonitor monitor) throws<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> HeadlessException{ 
        super<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(owner); 
        init(monitor); 
    } 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> init(ProgressMonitor monitor){ 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.monitor = monitor; 
        progressBar = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JProgressBar(0, monitor.getTotal()); 
        if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(monitor.isIndeterminate()) 
            progressBar.setIndeterminate(true<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        else<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> 
            progressBar.setValue(monitor.getCurrent()); 
        statusLabel.setText(monitor.getStatus()); 
        JPanel contents = (JPanel)getContentPane(); 
        contents.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 
        contents.add(statusLabel, BorderLayout.NORTH); 
        contents.add(progressBar); 
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); 
        monitor.addChangeListener(this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> stateChanged(final<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ChangeEvent ce){ 
        // to ensure EDT thread 
        if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(!SwingUtilities.isEventDispatchThread()){ 
            SwingUtilities.invokeLater(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Runnable(){ 
                public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> run(){ 
                    stateChanged(ce); 
                } 
            }); 
            return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
        } 
        if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(monitor.getCurrent()!=monitor.getTotal()){ 
            statusLabel.setText(monitor.getStatus()); 
            if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(!monitor.isIndeterminate()) 
                progressBar.setValue(monitor.getCurrent()); 
        }else<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> 
            dispose(); 
    } 
} 

ProgressDialog has a JLabel which shows the status message and JProgressBar to show amount of progress done. It listens to ChangeEvents from ProgressMonitor and updates the JLabel and JProgressBar. When ProgressMonitor's value hits its total value, dialog is disposed. It ensures that GUI is updated in EDT thread, because ChangeEvents from ProgressMonitor are always fired in Non-EDT thread (see stateChanged method).

ProgressMonitor can be used to show a modal progress dialog or event show the progress in status bar of the application, because we decoupled how progress is shown from its model. Now let us see how to create a ProgressMonitor which shows modal progress dialog:

/** 
 * MySwing: Advanced Swing Utilites 
 * Copyright (C) 2005  Santhosh Kumar T 
 * <p/> 
 * This library is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version. 
 * <p/> 
 * This library is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 * Lesser General Public License for more details. 
 */ 
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ProgressUtil{ 
    static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> MonitorListener implements<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ChangeListener, ActionListener{ 
        ProgressMonitor monitor; 
        Window owner; 
        Timer timer; 
        public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> MonitorListener(Window owner, ProgressMonitor monitor){ 
            this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.owner = owner; 
            this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.monitor = monitor; 
        } 
        public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> stateChanged(ChangeEvent ce){ 
            ProgressMonitor monitor = (ProgressMonitor)ce.getSource(); 
            if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(monitor.getCurrent()!=monitor.getTotal()){ 
                if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(timer==null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">){ 
                    timer = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Timer(monitor.getMilliSecondsToWait(), this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
                    timer.setRepeats(false<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
                    timer.start(); 
                } 
            }else<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">{ 
                if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(timer!=null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> && timer.isRunning()) 
                    timer.stop(); 
                monitor.removeChangeListener(this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
            } 
        } 
        public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> actionPerformed(ActionEvent e){ 
            monitor.removeChangeListener(this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
            ProgressDialog dlg = owner instanceof<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Frame 
                    ? new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ProgressDialog((Frame)owner, monitor) 
                    : new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ProgressDialog((Dialog)owner, monitor); 
            dlg.pack(); 
            dlg.setLocationRelativeTo(null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
            dlg.setVisible(true<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        } 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ProgressMonitor createModalProgressMonitor(Component owner, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> total, boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> indeterminate, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> milliSecondsToWait){ 
        ProgressMonitor monitor = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ProgressMonitor(total, indeterminate, milliSecondsToWait); 
        Window window = owner instanceof<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Window 
                ? (Window)owner 
                : SwingUtilities.getWindowAncestor(owner); 
        monitor.addChangeListener(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> MonitorListener(window, monitor)); 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> monitor; 
    } 
}

User creates a ProgressMonitor using createModalProgressMonitor(...) method. The first argument owner is used to find the owner window for ProgressDialog. We create a MonitorListener and register with ProgressMonitor. When ProgressMonitor.start(...) is called, MonitorListener starts a timer with milliSecondsToWait time. When timer hits its finish time, we show ProgressDialog, We also ensure that MonitorListener is unregistered from ProgressMonitor when its job is complete.

Following is the sample application to show the usage of ProgressMonitor:

public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ModalProgressDemo{ 
    static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JFrame frame; 
    static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Runnable heavyRunnable = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Runnable(){ 
        public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> run(){ 
            ProgressMonitor monitor = ProgressUtil.createModalProgressMonitor(frame, 100, false<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, 1000); 
            monitor.start("Fetching 1 of 10 records from database..."<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
            try<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">{ 
                for<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> i=0; i<10; i+=1){ 
                    fetchRecord(i); 
                    monitor.setCurrent("Fetching "<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">+(i+1)+" of 10 records from database"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, (i+1)*10); 
                } 
            } finally<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">{ 
                // to ensure that progress dlg is closed in case of any exception 
                if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(monitor.getCurrent()!=monitor.getTotal()) 
                    monitor.setCurrent(null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, monitor.getTotal()); 
            } 
            heavyAction.setEnabled(true<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        } 
        private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> fetchRecord(int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> index){ 
            try<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">{ 
                Thread.sleep(1000); 
            } catch<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(InterruptedException e){ 
                e.printStackTrace(); 
            } 
        } 
    }; 
    static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Action heavyAction = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> AbstractAction("Databse Query"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">){ 
        public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> actionPerformed(ActionEvent e){ 
            setEnabled(false<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
            new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Thread(heavyRunnable).start(); 
        } 
    }; 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> main(String args[]){ 
        try<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">{ 
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 
        } catch<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(Exception e){ 
            e.printStackTrace(); 
        } 
        frame = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JFrame("Modal Progress Dialog - [email protected]"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        frame.getContentPane().setLayout(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> FlowLayout()); 
        frame.getContentPane().add(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JButton(heavyAction)); 
        frame.setSize(300, 200); 
        frame.setLocationRelativeTo(null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        frame.setVisible(true<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
    } 
} 

You must ensure that, ProgressMonitor's value always hits its total value, otherwise ProgressDialog will never get disposed. We ensure this using try...finally block.

Your comments are appreciated.

( Nov 09 2005, 02:37:22 PM EST ) Permalink Comments [7]

20051103 Thursday November 03, 2005

Add [...] button to any TableCellEditor

JTable allows editing with TableCellEditor. Sometimes the table might contain some complex objects, whose editing may not fit it the small area allocated to TableCellEditor. In such case it is good idea to add custom-editor button to TableCellEditor. CustomEditor is a button with icon [...]. On clicking this button, a more sofisticated UI in a dialog will popup. This is very common in PropertySheets.

Today I tried doing this. Instead of throwing already implemented TableCellEditors, what I did is, reuse them by adding custom-editor button. Here is the class which allows such reusability:

/** 
 * MySwing: Advanced Swing Utilites 
 * Copyright (C) 2005  Santhosh Kumar T 
 * <p/> 
 * This library is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version. 
 * <p/> 
 * This library is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 * Lesser General Public License for more details. 
 */ 
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> abstract<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ActionTableCellEditor implements<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> TableCellEditor, ActionListener{ 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> final<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Icon DOTDOTDOT_ICON = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ImageIcon(getClass().getResource("dotdotdot.gif"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">)); 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> TableCellEditor editor; 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JButton customEditorButton = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JButton(DOTDOTDOT_ICON); 
    protected<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JTable table; 
    protected<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> row, column; 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ActionTableCellEditor(TableCellEditor editor){ 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.editor = editor; 
        customEditorButton.addActionListener(this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        // ui-tweaking 
        customEditorButton.setFocusable(false<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        customEditorButton.setFocusPainted(false<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        customEditorButton.setMargin(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Insets(0, 0, 0, 0)); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Component getTableCellEditorComponent(JTable table, Object value, boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> isSelected, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> row, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> column){ 
        JPanel panel = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JPanel(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> BorderLayout()); 
        panel.add(editor.getTableCellEditorComponent(table, value, isSelected, row, column)); 
        panel.add(customEditorButton, BorderLayout.EAST); 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.table = table; 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.row = row; 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.column = column; 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> panel; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Object getCellEditorValue(){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> editor.getCellEditorValue(); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> isCellEditable(EventObject anEvent){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> editor.isCellEditable(anEvent); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> shouldSelectCell(EventObject anEvent){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> editor.shouldSelectCell(anEvent); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> stopCellEditing(){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> editor.stopCellEditing(); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> cancelCellEditing(){ 
        editor.cancelCellEditing(); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> addCellEditorListener(CellEditorListener l){ 
        editor.addCellEditorListener(l); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> removeCellEditorListener(CellEditorListener l){ 
        editor.removeCellEditorListener(l); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> final<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> actionPerformed(ActionEvent e){ 
        editor.cancelCellEditing(); 
        editCell(table, row, column); 
    } 
    protected<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> abstract<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> editCell(JTable table, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> row, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> column); 
}

ActionTableCellEditor wraps a given TableCellEditor. This class itself implements TableCellEditor and delegates all methods to the given TableCellEditor.

in getTableCellEditorComponent method, we add custom editor button to the existing TableCellEditor component and return. it. When custom editor button is clicked, we cancel the cell editing and call the method editCell which is abstract. Subclasses override editCell method to show a dialog and call JTable.setValueAt(...) when [OK] button of dialog is pressed.

Let us write simple subclass of ActionCellEditor called StringActionTableCellEditor. StringActionTableCellEditor is used to edit string values (possible lengthy or multi-line strings)

/** 
 * MySwing: Advanced Swing Utilites 
 * Copyright (C) 2005  Santhosh Kumar T 
 * <p/> 
 * This library is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version. 
 * <p/> 
 * This library is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 * Lesser General Public License for more details. 
 */ 
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> StringActionTableCellEditor extends<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ActionTableCellEditor{ 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> StringActionTableCellEditor(TableCellEditor editor){ 
        super<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(editor); 
    } 
    protected<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> editCell(JTable table, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> row, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> column){ 
        JTextArea textArea = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JTextArea(10, 50); 
        Object value = table.getValueAt(row, column); 
        if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(value!=null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">){ 
            textArea.setText((String)value); 
            textArea.setCaretPosition(0); 
        } 
        int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> result = JOptionPane.showOptionDialog(table 
                , new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JScrollPane(textArea), (String)table.getColumnName(column) 
                , JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(result==JOptionPane.OK_OPTION) 
            table.setValueAt(textArea.getText(), row, column); 
    } 
} 

In this class we simple use JTextArea inside JOptionPane. We initialize textarea value from cell value and when JOptionPane's [OK] is pressed, we commit the new value into table.

I created a simple webstart demo to show it in action.

The first column uses DefaultTableCellEditor. Second column contains description of book which can be lengthy or multiline. So I used the same DefaultTableCellEditor instance wrapped inside StringActionTableCellEditor. On clicking [...] button following custom editor is shown:

You can view the source of webstart demo's main-class here.

ActionTableCellEditor is very easy to implement as you don't need to discard your already implemented TableCellEditors. One can create HashtableActionTableCellEditor, PropertiesActionTableCellEditor etc in the same way as StringActionTableCellEditor.

Your comments are appreciated

( Nov 03 2005, 01:17:00 PM EST ) Permalink Comments [7]

20051101 Tuesday November 01, 2005

Scrolling Swing component during DND

Swing component like JList, JTree and JTable doesn't scroll automatically, when drag-cursor goes beyond the component during drag and drop. This happens even if these components are placed in JScrollPane.

When a swing component is placed in JScrollPane, javax.swing.Scrollable interface tells how to scroll that component. All the above mentioned three component this interface. More over, we can place any swing component in JScrollPane, to make it scroll. The component need not implement javax.swing.Scrollable interface. Implementing javax.swing.Scrollable interface we can give more details such as how much scroll/page in each direction.

But during drag-n-drop the same components doesn't scroll if the drag cursor is moved to the border of the component. Later I found an interface in AWT called java.awt.dnd.Autoscroll. This interface dictates when/how to scroll a component during drag-n-drop. Bad luck is the above mentioned three components doesn't implement this interface. Let us see how to implement this.

First we will write a helper class which can be used in implementing this interface for any component:

public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> AutoscrollSupport implements<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Autoscroll{ 
    // component for which autoscroll to be enabled 
    Component comp; 
    // The insets where autoscrolling is active */ 
    Insets insets; 
    // no of units to be scrolled in each direction 
    Insets scrollUnits; 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> AutoscrollSupport(Component comp, Insets insets){ 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(comp, insets, insets); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> AutoscrollSupport(Component comp, Insets insets, Insets scrollUnits){ 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.comp = comp; 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.insets = insets; 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.scrollUnits = scrollUnits; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> autoscroll(Point cursorLoc){ 
        JViewport viewport = getViewport(); 
        if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(viewport==null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">) 
            return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
        Point viewPos = viewport.getViewPosition(); 
        int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> viewHeight = viewport.getExtentSize().height; 
        int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> viewWidth = viewport.getExtentSize().width; 
        // perform scrolling 
        if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">((cursorLoc.y-viewPos.y)<insets.top){ // scroll up 
            viewport.setViewPosition( 
                    new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Point(viewPos.x, 
                            Math.max(viewPos.y-scrollUnits.top, 0))); 
        } else<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">((viewPos.y+viewHeight-cursorLoc.y)<insets.bottom){ // scroll down 
            viewport.setViewPosition( 
                    new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Point(viewPos.x, 
                            Math.min(viewPos.y+scrollUnits.bottom, 
                                    comp.getHeight()-viewHeight))); 
        } else<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">((cursorLoc.x-viewPos.x)<insets.left){ // scroll left 
            viewport.setViewPosition( 
                    new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Point(Math.max(viewPos.x-scrollUnits.left, 0), 
                            viewPos.y)); 
        } else<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">((viewPos.x+viewWidth-cursorLoc.x)<insets.right){ // scroll right 
            viewport.setViewPosition( 
                    new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Point(Math.min(viewPos.x+scrollUnits.right, comp.getWidth()-viewWidth), 
                            viewPos.y)); 
        } 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Insets getAutoscrollInsets(){ 
        int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> height = comp.getHeight(); 
        int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> width = comp.getWidth(); 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Insets(height, width, height, width); 
    } 
    JViewport getViewport(){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> (JViewport)SwingUtilities.getAncestorOfClass(JViewport.class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, comp); 
    } 
} 

The constructor takes the autoscrolling Insets and scrollingInsets.

Autoscrolling Insets, tells that when mouse cursor is moved inside this insets of visiblerect of component, then autoscroll should start.

Scrolling Insets, tells how much to scroll in each direction.

Rest of the code is self-explanatory. Now let us see how to add autoscrolling support to JList using the above class:

class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> MyList extends<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JList implements<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Autoscroll{ 
    AutoscrollSupport scrollSupport = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> AutoscrollSupport(this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Insets(10, 10, 10, 10)); 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> MyList(ListModel model){ 
        super<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(model); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Insets getAutoscrollInsets(){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> scrollSupport.getAutoscrollInsets(); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> autoscroll(Point cursorLocn){ 
        scrollSupport.autoscroll(cursorLocn); 
    } 
}

You can see this in action with the following webstart demo. Drag an item from "DragSource" list to "Standard JList" and "MyList". Here "MyList" implements Autoscroll interface.

Your comments are appreciated.

 

( Nov 01 2005, 08:30:54 AM EST ) Permalink Comments [2]

20050915 Thursday September 15, 2005

How to show JPopupMenu on Empty JTable ?

It looks like as simple as writing a MouseListener implementation and register with JTable where the MouseListener implementation shows JPopupMenu.

But this doesn't works if there are no rows in JTable. Why so? The height of  the JTable is zero when there are no rows, Thus the area where user clicks is not JTable, it is JViewPort.

This can be solved by overriding the method getScrollableTracksViewportHeight() of Scrollable interface. JTable already implements Scrollable interface. So we just override that method and make the table height same as that of view-port if its preferred size is smaller than view-port size

 JTable table = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JTable(0, 5){ 
     public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> getScrollableTracksViewportHeight() { 
         return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> getPreferredSize().height < getParent().getHeight(); 
     } 
 };

This is how JTextComponent handles this. You can checkout JTextComponent.java and see getScrollableTracksViewportHeight() implementation.

Any JComponent can be scrollable by adding to JScrollable. Then what is Scrollable interface is for ?
Scrollable interface which gives some hints to perform better scrolling, such as how many pixels to move per mouse-click or page up/down.

The demo contains two tables. and both tables have the same MouseListener registered. But only the bottom table shows JPopupMenu on right mouse click. The bottom table overrides getScrollableTracksViewportHeight() as explained above.

Your comments are appreciated.

( Sep 15 2005, 06:51:17 PM EDT ) Permalink Comments [5]

CheckTree with/without Selection Digging

Now it is easy to add check-boxes to any JTree very easily using MySwing.

It also comes with two options whether to dig the selection or not.

Your comments are welcome.

( Sep 15 2005, 02:01:16 PM EDT ) Permalink Comments [3]

20050817 Wednesday August 17, 2005

Debugging Swing Models and Listeners

JDK 1.3 introduced the concept of Dynamic Proxys. Proxy is a class which implements a given set of interfaces and delegates the actual work to an object. A Dynmaic Proxy is one such proxy class which gets defined or created at runtime.

I frequently use Dynamic Proxy for debugging swing models and listeners.

 public<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> Object newProxyInstance(ClassLoader loader, 
                    Class[] interfaces, InvocationHandler h) 

Let us we have an object say myComponentListener which implements ComponentListener interface, and myInvocationHandler is an Object implementing InvocationHandler, then we can do:

 ComponentListener compListener = (ComponentListener)Proxy.newProxyInstance(
                                   myComponentListener.getClass().getClassLoader()
                                   , new Class[]{ ComponentListener.class }
                                   , myInvocationHandler);

Here is my InvocationHandler implementation which is used for debugging purposes:

import<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> java.lang.reflect.InvocationHandler; 
import<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> java.lang.reflect.InvocationTargetException; 
import<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> java.lang.reflect.Method; 
import<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> java.util.ArrayList; 
import<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> java.util.Arrays; 
// @author Santhosh Kumar T - [email protected] 
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> DebugProxy implements<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> InvocationHandler{ 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Object obj; 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Class[] getAllInterfaces(Class clazz){ 
        ArrayList<Class> list = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ArrayList<Class>(); 
        while<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(clazz!=null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">){ 
            list.addAll(Arrays.asList(clazz.getInterfaces())); 
            clazz = clazz.getSuperclass(); 
        } 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> list.toArray(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Class[0]); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Object newInstance(Object obj, StringConvertor stringConvertor) { 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> java.lang.reflect.Proxy.newProxyInstance( 
                obj.getClass().getClassLoader(), 
                getAllInterfaces(obj.getClass()), 
                new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> DebugProxy(obj, stringConvertor) 
        ); 
    } 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> StringConvertor stringConvertor; 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> DebugProxy(Object obj, StringConvertor stringConvertor) { 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.obj = obj; 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.stringConvertor = stringConvertor; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Object invoke(Object proxy, Method m, Object[] args) throws<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Throwable{ 
        Object result; 
        try<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> { 
            System.out.print(m.getName()+"("<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
            if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(args!=null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">){ 
                for<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> i = 0; i<args.length; i++){ 
                    if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(i>0) 
                        System.out.print(", "<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
                    System.out.print(toString(args[i])); 
                } 
            } 
            System.out.print(")"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
            result = m.invoke(obj, args); 
            if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(m.getReturnType()!=Void.class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">) 
                System.out.println(" returned "<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">+toString(result)); 
            else<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> 
                System.out.println(); 
            System.out.println("----------------------------------------------------------"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        } catch<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> (InvocationTargetException e) { 
            throw<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> e.getTargetException(); 
        } finally<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> { 
            System.out.flush(); 
        } 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> result; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> String toString(Object obj){ 
        if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(obj!=null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> && obj.getClass().isArray() && !obj.getClass().getComponentType().isPrimitive()) 
            return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Arrays.asList(obj).toString(); 
        else<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(obj!=null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> && stringConvertor!=null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">) 
            return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> stringConvertor.toString(obj); 
        else<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> 
            return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> String.valueOf(obj); 
    } 
}

It prints method name & argument values and delegates the method call. After result is obtained it prints the result and returns it.

In the above class, you can specify a class which can do task of converting objects to String, because all objects doesn't override toString();

// @author Santhosh Kumar T - [email protected] 
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> interface<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> StringConvertor{ 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> String toString(Object obj); 
}

How to use this:

 JTree tree = createTree(); 
 ComponentListener compListener = createComponentListener(); 
 tree.addComponentListener(compListener);

is changed to

 JTree tree = createTree(); 
 ComponentListener compListener = createComponentListener(); 
 compListener = (ComponentListener)DebugProxy.newInstance(compListener, null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
 tree.addComponentListener(compListener);

or some time to understand how listeners are called, you could simply say:

 JTree tree = createTree(); 
 tree.addComponentListener((ComponentListener)DebugProxy.newInstance(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ComponentAdapter(){}, null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">));

Even though this can be used at any place (not just swing), I found it very useful in swing programming, as all models and listeners in swing are interfaces.

Your comments are appreciated.

( Aug 17 2005, 01:18:42 PM EDT ) Permalink Comments [3]

20050812 Friday August 12, 2005

Tweaking JTable Editing

There are two client properties supported by JTable which are related to editing.

JTable.autoStartsEdit:
        The value is of type Java.lang.Boolean. This property tells whether JTable should start edit when a key is types. If the value of this property is null, it defaults to Boolean.TRUE. So this feature is enabled by default in JTable.

terminateEditOnFocusLost:
        The value is of type Java.lang.Boolean. This property tells whether what to do when focus goes outside of JTable. It first tries to commit the changes in editor, if can't be committed then the changes are cancelled. If the value of this property is null, it default to Boolean.FALSE. So this feature is disabled by default in JTable. You can enable this feature as follows:

table.putClientProperty("terminateEditOnFocusLost"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, Boolean.TRUE);

I suggest to make this feature to be enabled for JTable.

Remove Borders of TableCellEditors:

   --->  

JTextField tf = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JTextField(); 
tf.setBorder(BorderFactory.createEmptyBorder()); 
table.setDefaultEditor(Object.class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> DefaultCellEditor(tf));

   ----> 

JComboBox combo = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JComboBox(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> String[]{"item1"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, "item2"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, "item3"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">}); 
combo.setBorder(BorderFactory.createEmptyBorder()); 
table.getColumn("B"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">).setCellEditor(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> DefaultCellEditor(combo));

Make JComboBox popup open when user invokes editing with F2 key:

JComboBox combo = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JComboBox(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> String[]{"item1"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, "item2"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, "item3"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">}){ 
     public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> processFocusEvent(FocusEvent fe) { 
           super<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.processFocusEvent(fe); 
           Component focusOwner = KeyboardFocusManager. 
               getCurrentKeyboardFocusManager().getFocusOwner(); 
          if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> (isDisplayable() && fe.getID()==FocusEvent.FOCUS_GAINED 
                   && focusOwner==this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> && !isPopupVisible()) { 
                       showPopup(); 
           } 
     } 
}; 
combo.setBorder(BorderFactory.createEmptyBorder()); 
table.getColumn("B"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">).setCellEditor(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> DefaultCellEditor(combo));

Add rows on tabbing to a cell beyond the last cell:

JTable by defaults selects the next cell when TAB is pressed. When the current selection is last cell, pressing tab selects the first cell. But here we want a new row to be inserted and selected the next newly created cell.

JTable table = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JTable(2, 2){ 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> final<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> KeyStroke tabKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0); 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> changeSelection(int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> rowIndex, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> columnIndex, boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> toggle, boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> extend) { 
        AWTEvent currentEvent = EventQueue.getCurrentEvent(); 
        if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(currentEvent instanceof<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> KeyEvent){ 
            KeyEvent ke = (KeyEvent)currentEvent; 
            if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(ke.getSource()!=this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">) 
                return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
            // focus change with keyboard 
            if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(rowIndex==0 && columnIndex==0 
                    && KeyStroke.getKeyStrokeForEvent(ke).equals(tabKeyStroke)){ 
                ((DefaultTableModel)getModel()).addRow(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Object[getColumnCount()]); 
                rowIndex = getRowCount()-1; 
            } 
        } 
        super<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.changeSelection(rowIndex, columnIndex, toggle, extend); 
    } 
};

See how do we find out whether the selection change is because of tab key press or not.

This trick may be useful in only occasional scenarios. I found such feature during HTML table editing of Microsoft Frontpage.

I created a webstart demo to show this tricks live:

The first tab contains JTable without any of these tricks, and the second tab contains JTable with all these tricks implemented.

Your comments are appreciated.
 

( Aug 12 2005, 02:15:39 PM EDT ) Permalink Comments [1]

20050808 Monday August 08, 2005

Synchronize Scrolling Views

 

When you move the vertical scrollbar of first textarea, the vertical scrollbar of second text area also moves and vice versa.

  JScrollPane scroll1 = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JScrollPane(textArea1); 
  JScrollPane scroll2 = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JScrollPane(textArea2); 
  scroll1.getVerticalScrollBar().setModel(scroll2.getVerticalScrollBar().getModel());

Assumption:
     the maximum value of both vertical scrollbars are same.

This might even work if we use the maximum value of both vertical scrollbars and use the same for both.

Your comments are appreciated.

( Aug 08 2005, 06:45:16 PM EDT ) Permalink Comments [1]

Component Titled Border

The Swing Components that support renderers are:
         JTable, JList and JTree

How do CellRenderers work ?

All of know, that component we see in JTable is not the actual renderer component but is just it's rubber stamp. Let us see how JTable creates these rubber stamps.

 System.out.println(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JTable().getComponent(0));

When the above snippet is executed, you will see the following output:

 javax.swing.CellRendererPane[,0,0,0x0,invalid,hidden]

CellRendererPane is a class which is used by JTable to create rubber stamps. A CellRendererPane is added to JTable in BasicTableUI.installUI(). When renderer has to be rubber-stamped, it is added to CellRendererPane and its paint(...) method is called with JTable's Graphics Object.

Why do we need CellRendererPane? Can't we say cellRenderer.getTableCellRendererComponent(...).paint(tableGraphics) ?

In Swing, A Component is painted if and only if it is added to a showing component. So cellRenderer must be added to some currently visible component in order to paint its rubber-stamp. CellRendererPane fills this need. CellRenderer doesn't do any thing in its paint(...) and update(..) methods.

SwingUtilities class has two utility methods to create rubber stamps of component:

public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> paintComponent(Graphics g, Component c, Container p, Rectangle r)
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> paintComponent(Graphics g, Component c, Container p, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> x, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> y, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> w, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> h)

The above two methods internally use CellRendererPane;

Let us create some small application using rubber stamps:

   

We are going to create a ComponentTitledBorder:
    a border which can allow to place any component in it. (NOTE: border is not a visual component so we can't add a component)

1    /** 
2     * MySwing: Advanced Swing Utilites 
3     * Copyright (C) 2005  Santhosh Kumar T 
4     * <p/> 
5     * This library is free software; you can redistribute it and/or 
6     * modify it under the terms of the GNU Lesser General Public 
7     * License as published by the Free Software Foundation; either 
8     * version 2.1 of the License, or (at your option) any later version. 
9     * <p/> 
10    * This library is distributed in the hope that it will be useful, 
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of 
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
13    * Lesser General Public License for more details. 
14    */ 
15    
24   public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ComponentTitledBorder implements<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Border, MouseListener, SwingConstants{ 
25       int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> offset = 5; 
26    
27       Component comp; 
28       JComponent container; 
29       Rectangle rect; 
30       Border border; 
31    
32       public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ComponentTitledBorder(Component comp, JComponent container, Border border){ 
33           this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.comp = comp; 
34           this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.container = container; 
35           this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.border = border; 
36           container.addMouseListener(this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
37       } 
38    
39       public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> isBorderOpaque(){ 
40           return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> true<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
41       } 
42    
43       public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> paintBorder(Component c, Graphics g, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> x, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> y, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> width, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> height){ 
44           Insets borderInsets = border.getBorderInsets(c); 
45           Insets insets = getBorderInsets(c); 
46           int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> temp = (insets.top-borderInsets.top)/2; 
47           border.paintBorder(c, g, x, y+temp, width, height-temp); 
48           Dimension size = comp.getPreferredSize(); 
49           rect = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Rectangle(offset, 0, size.width, size.height); 
50           SwingUtilities.paintComponent(g, comp, (Container)c, rect); 
51       } 
52    
53       public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Insets getBorderInsets(Component c){ 
54           Dimension size = comp.getPreferredSize(); 
55           Insets insets = border.getBorderInsets(c); 
56           insets.top = Math.max(insets.top, size.height); 
57           return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> insets; 
58       } 
59    
60       private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> dispatchEvent(MouseEvent me){ 
61           if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(rect!=null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> && rect.contains(me.getX(), me.getY())){ 
62               Point pt = me.getPoint(); 
63               pt.translate(-offset, 0); 
64               comp.setBounds(rect); 
65               comp.dispatchEvent(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> MouseEvent(comp, me.getID() 
66                       , me.getWhen(), me.getModifiers() 
67                       , pt.x, pt.y, me.getClickCount() 
68                       , me.isPopupTrigger(), me.getButton())); 
69               if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(!comp.isValid()) 
70                   container.repaint(); 
71           } 
72       } 
73    
74       public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> mouseClicked(MouseEvent me){ 
75           dispatchEvent(me); 
76       } 
77    
78       public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> mouseEntered(MouseEvent me){ 
79           dispatchEvent(me); 
80       } 
81    
82       public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> mouseExited(MouseEvent me){ 
83           dispatchEvent(me); 
84       } 
85    
86       public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> mousePressed(MouseEvent me){ 
87           dispatchEvent(me); 
88       } 
89    
90       public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> mouseReleased(MouseEvent me){ 
91           dispatchEvent(me); 
92       } 
93   }

32-35
     First argument is the component to be displayed in the border. Second argument is the container for which this border is set and the third argument is the border which has to be titled with component. If you see javax.swing.border.TitledBorder, it allows to place title on any given border. Similarly we make the component to be placed on any given border.

36-37
      we add a mouse listener to the component (I will refer to this as checkbox to make explanation more understandable). We want the checkbox selectable/unselectable by user. That is checkbox should respond to mouse events. But remember that Border is not a component, so we can't add the checkbox to border.

44-47
      we paint the specified border with proper insets.

48-50
      the rubber stamp of the checkbox is painted at some offset say 5 pixels. We have used SwingUtilities.paintComponent(...) here which in turn used CellRendererPane.

53-58
     we compute the union of the specified border insets and the checkbox height and return as insets of this border

74-92
     in the constructor we added this border as mouselistener to checkbox. now we re-dispatch any mouse events in the checkbox's rubberstamp to the actual checkbox so that, user feels that checkbox actually responds to the mouse clicks.

61
    we dispatch only mouse events within the rubber stamp.

62-63
    the mouse coordinates must be translated to the actual checkbox which is painted at some offset (5 pixels here).

64
    we must set the size of checkbox to the size of current rubberstamp, because after the rubberstamp has been painted, CellRendererPane sets the size of checkbox to [0,0]

65-68
    we create the new mouse event with translated coordinates and dispatch it to checkbox.

69-70
    we must repaint the border, if there is any change in checkbox status such as selected/unselected.

The ComponentTitledBorder is not just for JCheckBox. You can use any component. For example we might want to show a title with icon which is not possible with Swing's TitleBorder.

NOTE:
       The checkbox doesn't respond to keyboard. So you can't bring focus to checkbox by TAB or select/unselect using SPACE. After all, it is a rubber stamp :)

Here is the source of demo application:

    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> main(String[] args){ 
        try<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">{ 
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 
        } catch<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(Exception e){ 
            e.printStackTrace(); 
        } 
        final<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JPanel proxyPanel = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JPanel(); 
        proxyPanel.add(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JLabel("Proxy Host: "<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">)); 
        proxyPanel.add(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JTextField("proxy.xyz.com"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">)); 
        proxyPanel.add(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JLabel("  Proxy Port"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">)); 
        proxyPanel.add(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JTextField("8080"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">)); 
        final<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JCheckBox checkBox = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JCheckBox("Use Proxy"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, true<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        checkBox.setFocusPainted(false<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        ComponentTitledBorder componentBorder = 
                new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ComponentTitledBorder(checkBox, proxyPanel 
                , BorderFactory.createEtchedBorder()); 
        checkBox.addActionListener(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ActionListener(){ 
            public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> actionPerformed(ActionEvent e){ 
                boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> enable = checkBox.isSelected(); 
                Component comp[] = proxyPanel.getComponents(); 
                for<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> i = 0; i<comp.length; i++){ 
                    comp[i].setEnabled(enable); 
                } 
            } 
        }); 
        proxyPanel.setBorder(componentBorder); 
        JFrame frame = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JFrame("ComponentTitledBorder - [email protected]"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        Container contents = frame.getContentPane(); 
        contents.setLayout(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> FlowLayout()); 
        contents.add(proxyPanel); 
        frame.pack(); 
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        frame.setVisible(true<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
    } 

Your comments are appreciated.

( Aug 08 2005, 05:23:54 PM EDT ) Permalink Comments [13]

20050804 Thursday August 04, 2005

MySwing: Incremental Search

checkout:
    http://www.javalobby.org/forums/thread.jspa?threadID=20958

( Aug 04 2005, 03:37:18 PM EDT ) Permalink Comments [1]

20050801 Monday August 01, 2005

Deferred Cut/Paste

How cut/copy/paste works?

When you copy something from a component, it creates a transferable object and places it in clipboard. When user tries to paste on another component, the target component fetches the transferable object from clipboard and gets the actual data from transferable and does its job. Thus there is a data transfer between the source and target component via clipboard.

Why not place the actual data in clipboard rather than transferable ?

Transferable is an interface which lets you define the data being transferred. The advantage of transferable is it supports dataflavors. Depending on the target component, the data getting transferred might be different. For example, let us say you copied some text from a web-browser, now if you try to paste in a notepad, it inserts only text, but if you try to paste in a html aware editor such as DreamWeaver or FrontPage, it inserts html snippet. i.e. the transferable created by the web-browser supports two dataflavors: simple text flavor and html flavor. Another advantage of dataflavors is delayed construction of data.

In this article, I am concentrating on cut/paste operation. There are two ways to implement cut/paste:

Immediate cut/paste:
      When you issue a cut command, the data immediately gets removed from the source component. For example text editor use this approach. When you cut some text from an editor, it immediately gets removed.

Deferred cut/paste:
     The actual data that is being cut won't be removed from source component, until you paste it somewhere. For example windows explorer use this approach. When you cut a file from windows explorer, the file won't be removed immediately, rather its icon is shown in dim color. When you paste it in some other folder, it is removed from original folder and added to the target folder, and the icon changes from normal to dim. One interesting thing to note here is highlighting the object being cut in dim color. When you cut two files one after another, only the recently cut file will be highlighted in dim color. Because only one transferable can be placed in clipboard.

In this article, I will be concentrating on how to implement Deferred cut/paste. because you can find implementation of Immediate cut/paste in swing text components.

First we will see how to create a dim image using Java2D:

// @author Santhosh Kumar T - [email protected]
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ImageUtil{ 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JLabel imgObserver = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JLabel(); 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Image createGhostImage(Image img){ 
        BufferedImage ghost = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> BufferedImage(img.getWidth(imgObserver) 
                , img.getHeight(imgObserver), BufferedImage.TYPE_INT_ARGB_PRE); 
        Graphics2D g2 = ghost.createGraphics(); 
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.5f)); 
        g2.drawImage(img, 0, 0, ghost.getWidth(), ghost.getHeight(), imgObserver); 
        g2.dispose(); 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ghost; 
    } 
}

In my example, I am using JList to make explanation more clear. Now let us jump into highlighting. When user cuts an item in JList, it places a transferable in clipboard and displays that item's icon in dim color (we need a special ListCellRender for this). This item has to be displayed in dim color as long as the transferable created by this JList is in clipboard. If user copies something let us say some text from notepad, JList should immediately display the item with normal icon. That is we need some sort of Listener to clipboard so that we know when our transferable object is removed from clipboard. We have such listener called ClipboardOwner.

A transferable object is placed in clipboard as follows:

Cliboard.setContents(Transferable contents, ClipboardOwner owner)

The second argument is the clipboard owner. when the transferable is removed from the clipboard, it calls lostOwnership(...) method in clipboard owner.

Thus the above interface is enough to handle the highlighting of the item being cut. Now let us see how to implement Deferred cut/paste.

When user cuts an item in JList, it just places a transferable in the clipboard but doesn't remove it from list. When user pastes it somewhere then the item has to be removed from JList. Now the problem, How does JList know when user tried to paste it somewhere. Let us see whether ClipboardOwner can help us to track this. Using ClipboardOwner, we can just know that. our transferable object is removed from the clipboard. But it doesn't tell whether the transferable is removed because of whether clipboard content is replaced by some other transferable, or because of some paste operation. Truly saying, Java lack an API for this.

At first I tried I could create a special transferable subclass:

// @author Santhosh Kumar T - [email protected]
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> interface<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> XTransferable extends<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Transferable{ 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> transferAccepted(); 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> transferRejected(); 
}

Now, JList can place a transferable object implementing XTransferable in clipboard, which removes the item from list in transferAccepted() method. When user pastes in some other component, the target component can check if the transferable is an instanceof XTransferable, if so, call transferAccepted() method after the data is pasted.

The above solution seems to be excellent for me and tried to implement this. But it didn't work. On debugging, I found that, the transferable fetched at target component is not the same transferable placed by the source component. You can confirm this, by executing the following snippet:

Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 
StringSelection transferable = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> StringSelection("data"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
System.out.println("transferable placed: "<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">+transferable); 
clipboard.setContents(transferable, null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
System.out.println("transferable fetched: "<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">+clipboard.getContents(null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">));

The output of the above snippet is as follows:

transferable placed: java.awt.datatransfer.StringSelection@87a5cc
transferable fetched: sun.awt.datatransfer.TransferableProxy@18fb1f7

The acutal transferable object is wrapped in sun.awt.datatransfer.TransferableProxy object, which is a proprietary class of Sun JVM.

So I started trying to find is there any way to implement this ? and finally I found one approach: using dataflavors

// @author Santhosh Kumar T - [email protected]
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> interface<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> TransferNotifier{ 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> DataFlavor NOTIFICATION_FLAVOR = 
            new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> DataFlavor(TransferNotifier.class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, TransferNotifier.class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.getName()); 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> transferAccepted(); 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> transferRejected(); 
} 

JList creates a transferable object which supports two flavors: the original dataflavor and notification flavor. In TransferNotifier implementation, we can take care of removing item from JList.

To make usage of TransferNotifier usage easier, a created a special delegating Transferable in which user can wrap his/her original transferable object very easily.

// @author Santhosh Kumar T - [email protected]
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> abstract<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> TransferNotifierProxy implements<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Transferable, TransferNotifier{ 
    Transferable delegate; 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> TransferNotifierProxy(Transferable delegate){ 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.delegate = delegate; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> DataFlavor[] getTransferDataFlavors(){ 
        DataFlavor delegateFlavors[] = delegate.getTransferDataFlavors(); 
        DataFlavor flavors[] = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> DataFlavor[delegateFlavors.length + 1]; 
        System.arraycopy(delegateFlavors, 0, flavors, 0, delegateFlavors.length); 
        flavors[flavors.length-1] = NOTIFICATION_FLAVOR; 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> flavors; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> isDataFlavorSupported(DataFlavor flavor){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> flavor.equals(NOTIFICATION_FLAVOR) 
                || delegate.isDataFlavorSupported(flavor); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Object getTransferData(DataFlavor flavor) throws<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> UnsupportedFlavorException, IOException{ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> flavor.equals(NOTIFICATION_FLAVOR) 
                ? this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> 
                : delegate.getTransferData(flavor); 
    } 
    protected<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> clearClipBoard(){ 
        // how to clear the clipboard contents ? 
        Toolkit.getDefaultToolkit().getSystemClipboard() 
                .setContents(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> StringSelection(""<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">), null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> transferAccepted(){ 
        clearClipBoard(); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> transferRejected(){ 
        clearClipBoard(); 
    } 
}

Now let us create a sample application using the above technique:

Follwing is the transferable placed in clipboard when an item is cut from JList:

// @author Santhosh Kumar T - [email protected]
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ListItemSelection extends<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> TransferNotifierProxy{ 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JList list; 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> index; 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ListItemSelection(JList list){ 
        super<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> StringSelection((String)list.getSelectedValue())); 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.list = list; 
        index = list.getSelectedIndex(); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> transferAccepted(){ 
        DefaultListModel model = (DefaultListModel)list.getModel(); 
        model.removeElementAt(index); 
        super<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.transferAccepted(); 
    } 
} 

Following is the ClipboardOwner implementation, which updates the JList item's display:

// @author Santhosh Kumar T - [email protected]
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ListClipboardOwner implements<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ClipboardOwner{ 
    // used as JList's client property 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> final<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> String CLIP_BOARD_OWNER = "ClipBoardOwner"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JList list; 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> index; 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ListClipboardOwner(JList list){ 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.list = list; 
        index = list.getSelectedIndex(); 
        list.putClientProperty(CLIP_BOARD_OWNER, this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        list.paintImmediately(list.getCellBounds(index, index)); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> getIndex(){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> index; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> lostOwnership(Clipboard clipboard, Transferable contents){ 
        if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(list.getClientProperty(CLIP_BOARD_OWNER)==this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">) 
            list.putClientProperty(CLIP_BOARD_OWNER, null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        list.paintImmediately(list.getCellBounds(index, index)); 
    } 
}

Here is the ListCellRenderer implementation which takes care of dim highlighting:

// @author Santhosh Kumar T - [email protected]
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> MyListCellRenderer extends<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> DefaultListCellRenderer{ 
    ImageIcon icon = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ImageIcon(getClass().getResource("user.gif"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">)); 
    Icon ghostIcon = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ImageIcon(ImageUtil.createGhostImage(icon.getImage())); 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Component getListCellRendererComponent(JList list, Object value 
                                                  , int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> index, boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> isSelected 
                                                  , boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> cellHasFocus){ 
        super<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 
        ListClipboardOwner owner = (ListClipboardOwner)list.getClientProperty(ListClipboardOwner.CLIP_BOARD_OWNER); 
        setIcon(owner!=null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> && owner.getIndex()==index ? ghostIcon : icon); 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
    } 
} 

Here is the Swing's TransferHandler, which tells you to copy data to and from JList:

// @author Santhosh Kumar T - [email protected]
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ListTransferHandler extends<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> TransferHandler{ 
    protected<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> Transferable createTransferable(JComponent c){ 
        JList list = (JList)c; 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ListItemSelection(list); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> exportToClipboard(JComponent comp, Clipboard clip, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> action) throws<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> IllegalStateException{ 
        int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> clipboardAction = getSourceActions(comp) & action; 
        if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(clipboardAction!=NONE){ 
            Transferable t = createTransferable(comp); 
            if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(t!=null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">){ 
                try<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">{ 
                    clip.setContents(t, new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ListClipboardOwner((JList)comp)); 
                    exportDone(comp, t, clipboardAction); 
                    return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
                } catch<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(IllegalStateException ise){ 
                    exportDone(comp, t, NONE); 
                    throw<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ise; 
                } 
            } 
        } 
        exportDone(comp, null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">, NONE); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> getSourceActions(JComponent c){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> MOVE; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> canImport(JComponent comp, DataFlavor[] transferFlavors){ 
        for<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> i = 0; i<transferFlavors.length; i++){ 
            if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(transferFlavors[i].equals(DataFlavor.stringFlavor)) 
                return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> true<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
        } 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> false<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> importData(JComponent comp, Transferable t){ 
        try<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">{ 
            String data = String.valueOf(t.getTransferData(DataFlavor.stringFlavor)); 
            JList list = (JList)comp; 
            ((DefaultListModel)list.getModel()).addElement(data); 
            acceptOrReject(t, true<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
            return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> true<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
        } catch<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(Exception e){ 
            acceptOrReject(t, false<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
            return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> false<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
        } 
    } 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> acceptOrReject(Transferable t, boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> accept){ 
        try<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">{ 
            if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(t.isDataFlavorSupported(TransferNotifier.NOTIFICATION_FLAVOR)){ 
                TransferNotifier notifier = (TransferNotifier)t.getTransferData(TransferNotifier.NOTIFICATION_FLAVOR); 
                if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(accept) 
                    notifier.transferAccepted(); 
                else<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> 
                    notifier.transferRejected(); 
            } 
        } catch<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(UnsupportedFlavorException e){ 
            e.printStackTrace(); // impossible 
        } catch<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(IOException e){ 
            e.printStackTrace(); 
        } 
    } 
}

Finally here is the main application's class:

// @author Santhosh Kumar T - [email protected]
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> CutPasteTest{ 
    private<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JScrollPane createList(int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> from, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> to){ 
        DefaultListModel model1 = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> DefaultListModel(); 
        for<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> i = from; i<=to; i++) 
            model1.addElement("User "<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">+i); 
        JList list = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JList(model1); 
        list.setTransferHandler(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> ListTransferHandler()); 
        list.setCellRenderer(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> MyListCellRenderer()); 
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 
        JScrollPane scroll = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JScrollPane(list); 
        scroll.setBorder(BorderFactory.createEmptyBorder()); 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> scroll; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> main(String[] args){ 
        JFrame frame = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JFrame("Deferred Cut/Paste - [email protected]"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        JSplitPane split = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> JSplitPane(JSplitPane.HORIZONTAL_SPLIT 
                , createList(1, 5) 
                , createList(6, 10)); 
        split.setResizeWeight(0.5d); 
        frame.getContentPane().add(split); 
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        frame.setSize(200, 200); 
        frame.setLocationRelativeTo(null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        frame.setVisible(true<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
    } 
} 

Try to cut/paste items from one list to another in demo.

Your Comments are appreciated.

( Aug 01 2005, 06:33:04 PM EDT ) Permalink Comments [5]

20050729 Friday July 29, 2005

Context Menu for TextComponents

In Windows, it is very common to show context menu (with cut, copy, paste etc) on textfields, textareas. I don't know about other operating systems. So any windows user using a swing application expects a popup menu on right click of text components.

When I am in doubt whether this application is swing or not, what I do is find any textfield and right click. if no popup appears then I conclude it as swing application.

How do I provide context menu for text components ?

Solution1: Create a subclass for each text component (JTextField, JPasswordField etc) and implement the logic inside these subclasses.
Solution2: Create a mouse listener which does the work of showing popup and add this to all text components of your applications.

Each of the above solutions have their own drawbacks:

I found a very clean solution for this problem: We install our implmentation of EventQueue.

// @author Santhosh Kumar T - [email protected] 
public<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> MyEventQueue extends<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> EventQueue{ 
    protected<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> dispatchEvent(AWTEvent event){ 
        super<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">.dispatchEvent(event); 
        // interested only in mouseevents 
        if<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">(!(event instanceof<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> MouseEvent)) 
            return<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">; 
        MouseEvent me = (MouseEvent)event; 
        // interested only in popuptriggers 
        if<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">(!me.isPopupTrigger()) 
            return<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">; 
        // me.getComponent(...) retunrs the heavy weight component on which event occured 
        Component comp = SwingUtilities.getDeepestComponentAt(me.getComponent(), me.getX(), me.getY()); 
        // interested only in textcomponents 
        if<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">(!(comp instanceof<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> JTextComponent)) 
            return<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">; 
        // no popup shown by user code 
        if<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">(MenuSelectionManager.defaultManager().getSelectedPath().length>0) 
            return<../../b__/font__font_style_.css"font-family: monospaced" color="#000000">; 
        // create popup menu and show 
        JTextComponent tc = (JTextComponent)comp; 
        JPopupMenu menu = new<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> JPopupMenu(); 
        menu.add(new<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> CutAction(tc)); 
        menu.add(new<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> CopyAction(tc)); 
        menu.add(new<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> PasteAction(tc)); 
        menu.add(new<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> DeleteAction(tc)); 
        menu.addSeparator(); 
        menu.add(new<../../b__/font__font_style_.css"font-family: monospaced" color="#000000"> SelectAllAction(tc)); 
        Point pt = SwingUtilities.convertPoint(me.getComponent(), me.getPoint(), tc);
        menu.show(tc, pt.x, pt.y);
    } 
} 

The above class is self-explanatory with comments.

The implementation of the actions is here:

// @author Santhosh Kumar T - [email protected] 
class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> CutAction extends<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> AbstractAction{ 
    JTextComponent comp; 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> CutAction(JTextComponent comp){ 
        super<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">("Cut"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.comp = comp; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> actionPerformed(ActionEvent e){ 
        comp.cut(); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> isEnabled(){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> comp.isEditable() 
                && comp.isEnabled() 
                && comp.getSelectedText()!=null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
    } 
} 
// @author Santhosh Kumar T - [email protected] 
class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> PasteAction extends<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> AbstractAction{ 
    JTextComponent comp; 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> PasteAction(JTextComponent comp){ 
        super<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">("Paste"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.comp = comp; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> actionPerformed(ActionEvent e){ 
        comp.paste(); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> isEnabled(){ 
        if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> (comp.isEditable() && comp.isEnabled()){ 
            Transferable contents = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
            return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> contents.isDataFlavorSupported(DataFlavor.stringFlavor); 
        }else<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> 
            return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> false<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
    } 
} 
// @author Santhosh Kumar T - [email protected] 
class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> DeleteAction extends<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> AbstractAction{ 
    JTextComponent comp; 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> DeleteAction(JTextComponent comp){ 
        super<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">("Delete"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.comp = comp; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> actionPerformed(ActionEvent e){ 
        comp.replaceSelection(null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> isEnabled(){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> comp.isEditable() 
                && comp.isEnabled() 
                && comp.getSelectedText()!=null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
    } 
} 
// @author Santhosh Kumar T - [email protected] 
class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> CopyAction extends<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> AbstractAction{ 
    JTextComponent comp; 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> CopyAction(JTextComponent comp){ 
        super<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">("Copy"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.comp = comp; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> actionPerformed(ActionEvent e){ 
        comp.copy(); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> isEnabled(){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> comp.isEnabled() 
                && comp.getSelectedText()!=null<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
    } 
} 
// @author Santhosh Kumar T - [email protected] 
class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> SelectAllAction extends<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> AbstractAction{ 
    JTextComponent comp; 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> SelectAllAction(JTextComponent comp){ 
        super<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">("Select All"<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        this<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">.comp = comp; 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> actionPerformed(ActionEvent e){ 
        comp.selectAll(); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> isEnabled(){ 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> comp.isEnabled() 
                && comp.getText().length()>0; 
    } 
} 

How to use it:

    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> main(String[] args){ 
        Toolkit.getDefaultToolkit().getSystemEventQueue().push(new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> MyEventQueue()); 
        .....
    } 

just a single line in the beginning of your application's main method. So simple.

I created a webstart demo to show it in action:

There are two textfields in this demo. Second textfield shows a popupmenu in one of its mouse listener

When click the button [JOptionPane], it shows a input message dialog which again contains a textfield, which is not created the demo.

Thus, this approach is very simple to use, and works perfectly, even if the application has its own popup menu on mouse click.

Your comments are appreciated.

( Jul 29 2005, 03:06:27 AM EDT ) Permalink Comments [17]

20050727 Wednesday July 27, 2005

Retaining JTree Expansion State

There are many use-cases in which retaining the expansion state of JTree improves the usability. I will introduce you to one such use case.

We have a JTree in which user can move the tree nodes from one place to another using drag-n-drop. When a non-leaf node is moved from one place to another, it looses its expansion state. It is very irritating from user point of view and becomes a usability issue. Today I will introduce a trick that can be used to store the expansion state and retain it later when required.

// @author santhosh kumar T - [email protected] 
public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> class<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> TreeUtil{ 
    // is path1 descendant of path2 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> boolean<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> isDescendant(TreePath path1, TreePath path2){ 
        int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> count1 = path1.getPathCount(); 
        int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> count2 = path2.getPathCount(); 
        if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(count1<=count2) 
            return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> false<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
        while<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(count1!=count2){ 
            path1 = path1.getParentPath(); 
            count1--; 
        } 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> path1.equals(path2); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> String getExpansionState(JTree tree, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> row){ 
        TreePath rowPath = tree.getPathForRow(row); 
        StringBuffer buf = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> StringBuffer(); 
        int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> rowCount = tree.getRowCount(); 
        for<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> i=row; i<rowCount; i++){ 
            TreePath path = tree.getPathForRow(i); 
            if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(i==row || isDescendant(path, rowPath)){ 
                if<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(tree.isExpanded(path)) 
                    buf.append(","<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">+String.valueOf(i-row)); 
            }else<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> 
                break<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">; 
        } 
        return<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> buf.toString(); 
    } 
    public<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> static<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> void<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> restoreExpanstionState(JTree tree, int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> row, String expansionState){ 
        StringTokenizer stok = new<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> StringTokenizer(expansionState, ","<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">); 
        while<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000">(stok.hasMoreTokens()){ 
            int<../../b__/font__font_style_.css"font-family: monospaced;" color="#000000"> token = row + Integer.parseInt(stok.nextToken()); 
            tree.expandRow(token); 
        } 
    } 
}

getExpanstionState(...) returns the current expansion state of the given row, and restoreExpansionState(....) restores the expansion state of a row from specified status.

ExpansionState of a row is the list of descendant rows that are expanded. Note that here is row is counted from the row for which expansion state is calculated.

For example in the above screen shot the expansion state of the selected node is 0, 2

during drag-n-drop, get the expansion state of the dragged node before moving and restore the expansion state after drop.

In the webstart demo, you can unselect the checkbox, to see how loosing expansion state irritates user.

Your comments are appreciated.

( Jul 27 2005, 06:33:01 PM EDT ) Permalink Comments [3]

Calendar

RSS Feeds

Search

Links

Navigation

Referers