Welcome to my site

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ed ut perspiciatis unde omnis iste.

" Small is the number of people who see with their own eyes, think with their own minds and feel with their own hearts " (Albert Hermann Einstein)

= contact me at ndre@engineer.com or click on my facebook badge =

Multiple event queues (java.awt.EventQueue)

In order to make my UIRobot more acceptable to the marketplace, I needed a way in which I could unobtrusively hook myself into the AWT event system. I wanted a hotkey that users of the UIRobot could employ from any window to open the UIRobot dialog. The user could then "record" a script, "play" a script or "edit" a script, much like the macro recorder in MS Office (TM), using text on the components to locate them again. The only thing I expect the client code to do is to call
Class.forName("com.maxoft.ui.robot.UIRobot");
in order to give the UIRobot class a chance to register itself.
Once that happens, I want the hotkey to be available from any frame, dialog, component, focused or not, etc. It should be a global hotkey that you can press to activate the UIRobot dialog. I had a look at the EventQueue for my first newsletter and noticed that it followed, roughly, the Chain of Responsibility design pattern. You can register a new event queue and make it responsible for handling any new events that arrive.
I saw this pattern quite quickly, but it took me 3 hours (!) to finally get it working. Had I been more careful, it should have taken not more than 5 minutes, but sometimes I am a bit slow on the uptake. One of the disadvantages of having written too much generic Java code is that I don't recognise "private" methods as an obstacle, because I can just invoke them anyway. Designers of frameworks are not necessarily very skilled at guessing how I want to extend their framework so they often make a method private instead of protected. I even went so far as to decompile SUN's implementation of java.awt.Toolkit, namely sun.awt.SunToolkit, to try and find some way of hooking into the event queue. In the end the correct and most simple way of doing this was to write a subclass of EventQueue, called MyEventQueue, and to register it as the now reigning king of the Democratic Republic of Events with the command:
Toolkit.getDefaultToolkit().getSystemEventQueue().push(
  new MyEventQueue());
The reason it took me 3 hours to figure these couple of lines out was because I overrode the postEvent() method, instead of the dispatchEvent() method, duh! An example of MyEventQueue could look like this:
//: MyEventQueue.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MyEventQueue extends EventQueue {
  protected void dispatchEvent(AWTEvent event) {
    // the only functionality I add is that I print out all the events
    System.out.println(event);
    super.dispatchEvent(event);
  }
}
So, what type of functionality can you achieve with this code? You can write a global hotkey manager, you can write a recorder that generates scripts for the UIRobot or you can disable all user actions while the GUI is busy with something else. Those of you who've tried to disable GUI input have probably used the GlassPane of the JFrame which can catch all mouse events, but not keyboard shortcuts. Thanks to David Geary for that idea in his classic book on Swing!
I mentioned to one of our system engineers the possibility of using this event queue mechanism as a global hotkey manager. He got very excited and called Herman away from the company month-end barbacue to come talk to me. We had been struggling to get application-wide global hotkeys working for 3.5 years in our application and one mayor customer site was holding back on a purchase because of that problem. Herman and I sat down and we came up with the GlobalHotkeyManager, where you can register any input / action combination and it matches the two for you on a global scale. Note that you need JDK 1.3 to use ActionMap and InputMap. The other parts work in JDK 1.2.
//: GlobalHotkeyManager.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class GlobalHotkeyManager extends EventQueue {
  private static final boolean DEBUG = true; // BUG? what's that? ;-))
  private static final GlobalHotkeyManager instance =
    new GlobalHotkeyManager();
  private final InputMap keyStrokes = new InputMap();
  private final ActionMap actions = new ActionMap();
  static {
    // here we register ourselves as a new link in the chain of
    // responsibility
    Toolkit.getDefaultToolkit().getSystemEventQueue().push(instance);
  }
  private GlobalHotkeyManager() {} // One is enough - singleton
  public static GlobalHotkeyManager getInstance() {
    return instance;
  }
  public InputMap getInputMap() {
    return keyStrokes;
  }
  public ActionMap getActionMap() {
    return actions;
  }
  protected void dispatchEvent(AWTEvent event) {
    if (event instanceof KeyEvent) {
      // KeyStroke.getKeyStrokeForEvent converts an ordinary KeyEvent
      // to a keystroke, as stored in the InputMap.  Keep in mind that
      // Numpad keystrokes are different to ordinary keys, i.e. if you
      // are listening to
      KeyStroke ks = KeyStroke.getKeyStrokeForEvent((KeyEvent)event);
      if (DEBUG) System.out.println("KeyStroke=" + ks);
      String actionKey = (String)keyStrokes.get(ks);
      if (actionKey != null) {
        if (DEBUG) System.out.println("ActionKey=" + actionKey);
        Action action = actions.get(actionKey);
        if (action != null && action.isEnabled()) {
          // I'm not sure about the parameters
          action.actionPerformed(
            new ActionEvent(event.getSource(), event.getID(),
              actionKey, ((KeyEvent)event).getModifiers()));
          return; // consume event
        }
      }
    }
    super.dispatchEvent(event); // let the next in chain handle event
  }
}
Together with the GlobalHotkeyManager.java we have a test program:
//: GlobalHotkeyManagerTest.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class GlobalHotkeyManagerTest extends JFrame {
  private final static String UIROBOT_KEY = "UIRobot";
  private final KeyStroke uirobotHotkey = KeyStroke.getKeyStroke(
    KeyEvent.VK_R, KeyEvent.CTRL_MASK + KeyEvent.ALT_MASK, false);
  private final Action uirobot = new AbstractAction() {
    public void actionPerformed(ActionEvent e) {
      setEnabled(false); // stop any other events from interfering
      JOptionPane.showMessageDialog(GlobalHotkeyManagerTest.this,
        "UIRobot Hotkey was pressed");
      setEnabled(true);
    }
  };
  public GlobalHotkeyManagerTest() {
    super("Global Hotkey Manager Test");
    setSize(500,400);
    getContentPane().setLayout(new FlowLayout());
    getContentPane().add(new JButton("Button 1"));
    getContentPane().add(new JTextField(20));
    getContentPane().add(new JButton("Button 2"));
    GlobalHotkeyManager hotkeyManager = GlobalHotkeyManager.getInstance();
    hotkeyManager.getInputMap().put(uirobotHotkey, UIROBOT_KEY);
    hotkeyManager.getActionMap().put(UIROBOT_KEY, uirobot);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // JDK 1.3
    setVisible(true);
  }
  public static void main(String[] args) {
    new GlobalHotkeyManagerTest();
  }
}
We basically match a KeyStroke (CTRL+ALT+R) with an Action. Since the action can be invoked from anywhere, we must remember to switch it off while we are handling it, otherwise it could be invoked again by mistake. Try out what happens when you don't disable the action and press the hotkey twice.
This is one of the most interesting things I've discovered in Swing and is so extremely useful that I do not understand why it is not more widely publicised. Please let me know if you've done something similar in your coding. It seems that SUN are quite good at adding new features or improving code without bothering to tell anyone, or at least not me! ;-) At more than 500'000 lines of code in the JDK 1.3, it becomes tiresome to read through it all each time a new release comes out.
By now, you have hopefully seen the value of understanding OO Design Patterns if you want to become good at Java. I have found that Java lends itself to good OO design, certainly more than C++.
I want to thank all of you who took the time to read and respond to my newsletters, your feedback is what really makes this worthwhile.

created by Dr. Heinz M. Kabutz

EDT Lockup Detection (in Java)

It was long-long ago in February of 2001 when Dr. Heinz M. Kabutz discovered and described in Issue 007 how an alternative EventQueue could be installed in an application. In his application he used this technique to install a global hotkey manager. Today I would like to share another use for overriding EventQueue:
All of us have heard the winged phrase "Swing is slow!" at least once. In my personal opinion it isn't. The problem of many applications is that all of the processing is triggered by the user interface and performed by the Events Dispatching Thread (EDT). The more complex processing becomes, the more time it takes to complete and the lower overall GUI responsiveness becomes. It's clear that the longer your code will block EDT, the longer your GUI will be in a seemingly frozen state.
Low GUI responsiveness is one part of the problem, the other is complete GUI lockups. Sometimes EDT gets completely deadlocked and is unable to process application events crowding in the queue. At this point you can call your application "dead". The background tasks can still be running normally, but the users will never see the results of their work. As might seem clear by now, I wish to share my own method of detecting excessive use and lockups of EDT. In the product I participate as developer (BlogBridge - news feeds reader) we have chosen to use EDT over-usage reporting (during development) to improve GUI performance. Also we use complete GUI lockup detection to prevent data losses by saving application state, reporting the accident to the team and doing preventive application termination. We understand that it's not very good to force application termination, but it's definitely better that forcing users to do it by means of operation system and lose valuable data.

So how we will do the trick?

We need to be able to define:

  • Maximum time the EDT can take to dispatch of single event

  • Action that should be performed when time is exceeded

  • Time-action pairs to have several handlers

  • This should interfere as little as possible with normal dispatching flow. At first, I was thinking about having a separate timer which could be used to trigger actions if not periodically reset. The resetting code should be presented as an event of special flavour and dispatched in scope of EDT. If the EDT gets stuck (or deadlocked) the event isn't dispatched in time and timer triggers notification action. I will show the queue on the picture schematically.
    Event Queue
    WindowEvent
    KeyEvent
    ...
    WatchdogResetEvent
    ...
    WatchdogResetEvent
    The idea is good in general, but has several significant problems:
    1. We load EDT heavily if we don't do pauses between the ends of dispatching our events and additions of them back to the queue.
    2. We can meet the situation when there will be a lot of "short" events and overall time of their dispatching will get over the threshold. Then we will incorrectly detect the overload.
    3. There are many other situations similar to #2 where we will give incorrect results.
    After some thinking I came to conclusion that it is necessary to measure time of single event dispatching and trigger actions when it gets bigger than corresponding maximums. But there was still question how to make that work without affecting normal dispatching process significantly. And then I saw the alternative events queue installation technique described in Issue 007.
    The alternative queue records time of every single event dispatching start and clears it in the end. The setting / resetting of variable with long type is that minimum effect I could only dream. Separate timer thread is populated with tasks for checking the dispatching duration against their own periods and trigger actions associated with them if the dispatching take longer than one period. The idea is very elegant and clean. Here's the implementation:
    import java.awt.*;
    import java.awt.event.*;
    import java.util.*;
    
    /**
     * Alternative events dispatching queue. The benefit over the
     * default Event Dispatch queue is that you can add as many
     * watchdog timers as you need and they will trigger arbitrary
     * actions when processing of single event will take longer than
     * one timer period.
     * <p/>
     * Timers can be of two types:
     * <ul>
     * <li><b>Repetitive</b> - action can be triggered multiple times
     * for the same "lengthy" event dispatching.
     * </li>
     * <li><b>Non-repetitive</b> - action can be triggered only once
     * per event dispatching.</li>
     * </ul>
     * <p/>
     * The queue records time of the event dispatching start.  This
     * time is used by the timers to check if dispatching takes
     * longer than their periods. If so the timers trigger associated
     * actions.
     * <p/>
     * In order to use this queue application should call
     * <code>install()</code> method. This method will create,
     * initialize and register the alternative queue as appropriate.
     * It also will return the instance of the queue for further
     * interactions. Here's an example of how it can be done:
     * <p/>
     * <pre>
     * <p/>
     *  EventQueueWithWD queue = EventQueueWithWD.install();
     *  Action edtOverloadReport = ...;
     * <p/>
     *  // install single-shot wg to report EDT overload after
     *  // 10-seconds timeout
     *  queue.addWatchdog(10000, edtOverloadReport, false);
     * <p/>
     * </pre>
     */
    public class EventQueueWithWD extends EventQueue {
      // Main timer
      private final java.util.Timer timer = new java.util.Timer(true);
    
      // Group of informational fields for describing the event
      private final Object eventChangeLock = new Object();
      private volatile long eventDispatchingStart = -1;
      private volatile AWTEvent event = null;
    
      /**
       * Hidden utility constructor.
       */
      private EventQueueWithWD() { }
    
      /**
       * Install alternative queue.
       *
       * @return instance of queue installed.
       */
      public static EventQueueWithWD install() {
        EventQueue eventQueue =
            Toolkit.getDefaultToolkit().getSystemEventQueue();
        EventQueueWithWD newEventQueue = new EventQueueWithWD();
        eventQueue.push(newEventQueue);
        return newEventQueue;
      }
    
      /**
       * Record the event and continue with usual dispatching.
       *
       * @param anEvent event to dispatch.
       */
      protected void dispatchEvent(AWTEvent anEvent) {
        setEventDispatchingStart(anEvent, System.currentTimeMillis());
        super.dispatchEvent(anEvent);
        setEventDispatchingStart(null, -1);
      }
    
      /**
       * Register event and dispatching start time.
       *
       * @param anEvent   event.
       * @param timestamp dispatching start time.
       */
      private void setEventDispatchingStart(AWTEvent anEvent,
                                            long timestamp) {
        synchronized (eventChangeLock) {
          event = anEvent;
          eventDispatchingStart = timestamp;
        }
      }
    
      /**
       * Add watchdog timer. Timer will trigger <code>listener</code>
       * if the queue dispatching event longer than specified
       * <code>maxProcessingTime</code>. If the timer is
       * <code>repetitive</code> then it will trigger additional
       * events if the processing 2x, 3x and further longer than
       * <code>maxProcessingTime</code>.
       *
       * @param maxProcessingTime maximum processing time.
       * @param listener          listener for events. The listener
       *                          will receive <code>AWTEvent</code>
       *                          as source of event.
       * @param repetitive        TRUE to trigger consequent events
       *                          for 2x, 3x and further periods.
       */
      public void addWatchdog(long maxProcessingTime,
                              ActionListener listener,
                              boolean repetitive) {
        Watchdog checker = new Watchdog(maxProcessingTime, listener,
            repetitive);
        timer.schedule(checker, maxProcessingTime,
            maxProcessingTime);
      }
    
      /**
       * Checks if the processing of the event is longer than the
       * specified <code>maxProcessingTime</code>. If so then
       * listener is notified.
       */
      private class Watchdog extends TimerTask {
        // Settings
        private final long maxProcessingTime;
        private final ActionListener listener;
        private final boolean repetitive;
    
        // Event reported as "lengthy" for the last time. Used to
        // prevent repetitive behaviour in non-repeatitive timers.
        private AWTEvent lastReportedEvent = null;
    
        /**
         * Creates timer.
         *
         * @param maxProcessingTime maximum event processing time
         *                           before listener is notified.
         * @param listener          listener to notify.
         * @param repetitive       TRUE to allow consequent
         *                           notifications for the same event
         */
        private Watchdog(long maxProcessingTime,
                        ActionListener listener,
                        boolean repetitive) {
          if (listener == null)
            throw new IllegalArgumentException(
                "Listener cannot be null.");
          if (maxProcessingTime < 0)
            throw new IllegalArgumentException(
              "Max locking period should be greater than zero");
          this.maxProcessingTime = maxProcessingTime;
          this.listener = listener;
          this.repetitive = repetitive;
        }
    
        public void run() {
          long time;
          AWTEvent currentEvent;
    
          // Get current event requisites
          synchronized (eventChangeLock) {
            time = eventDispatchingStart;
            currentEvent = event;
          }
    
          long currentTime = System.currentTimeMillis();
    
          // Check if event is being processed longer than allowed
          if (time != -1 && (currentTime - time > maxProcessingTime) &&
              (repetitive || currentEvent != lastReportedEvent)) {
            listener.actionPerformed(
                new ActionEvent(currentEvent, -1, null));
            lastReportedEvent = currentEvent;
          }
        }
      }
    }
      
    As you could see the code is also straight-forward and elegant. You install the queue and register any number of timers you need and they will do the excellent job for you. Below an example code showing how the queue can be used to detect and report overloads. One great thing about timer task implementation is that it returns the event which is being dispatched for too long in the source field of ActionEvent passed to triggered action. The handler can use it to make a good report about problem.
    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.ActionEvent;
    import java.util.Date;
    
    /**
     * Sample usage of <code>EventQueueWithWD</code> class.
     */
    public class SampleEQUsage extends JFrame {
      public SampleEQUsage() {
        super("Sample EQ Usage");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    
        getContentPane().add(new JButton(new AbstractAction("Go") {
          public void actionPerformed(ActionEvent e) {
            System.out.println();
            System.out.println(new Date());
            try {
              // Sleep for 10 seconds
              Thread.sleep(10000);
            } catch (InterruptedException e1) {
            }
          }
        }));
    
        setSize(100, 100);
      }
    
      public static void main(String[] args) {
        initQueue();
    
        SampleEQUsage sequ = new SampleEQUsage();
        sequ.setVisible(true);
      }
    
      // Install and init the alternative queue
      private static void initQueue() {
        EventQueueWithWD queue = EventQueueWithWD.install();
    
        // Install 3-seconds single-shot watchdog timer
        queue.addWatchdog(3000, new AbstractAction() {
          public void actionPerformed(ActionEvent e) {
            System.out.println(new Date() + " 3 seconds - single-shot");
          }
        }, false);
    
        // Install 3-seconds multi-shot watchdog timer
        queue.addWatchdog(3000, new AbstractAction() {
          public void actionPerformed(ActionEvent e) {
            System.out.println(new Date() + " 3 seconds - multi-shot");
          }
        }, true);
    
        // Install 11-seconds multi-shot watchdog timer
        queue.addWatchdog(11000, new AbstractAction() {
          public void actionPerformed(ActionEvent e) {
            System.out.println(new Date() + " 11 seconds - multi-shot");
          }
        }, true);
      }
    }
      
    This code creates a single frame with Go button. By pressing it you freeze the GUI for 10 seconds. There are 3 watchdog timers installed by initQueue() method. The messages printed explain their nature pretty well. Pressing the button 3 times produces the following output for me (your output will be different):
    Tue Feb 08 16:09:57 CAT 2005
        Tue Feb 08 16:10:01 CAT 2005 3 seconds - multi-shot
        Tue Feb 08 16:10:01 CAT 2005 3 seconds - single-shot
        Tue Feb 08 16:10:04 CAT 2005 3 seconds - multi-shot
      
    I think that I don't need to explain why we can have only two 3-second multi-shots (as in second and third packages). ;-)
    So, now you can use this new opportunities to catch your junior developers doing their math in application EDT. :)
    See you soon!

    created by Aleksey Gureev

    Multi-line cells in JTable in JDK 1.4+

    Three years ago, I played an April Fools' joke on my readers, as is customary in South Africa. However, with 109 countries on the subscription list, I have stopped playing the fool, since this is can cause confusion in cultures that do not celebrate the 1st of April. In a follow-up newsletter, I explained that the unsubscription fees were nothing but a joke.
    Coincidentally, almost three years ago to the day, I wrote about Multi-line cells in the JTable. I discussed how to wrap text inside cells of a JTable. My solution only worked pre-JDK 1.4 , so I asked readers to send me a solution if they found it.
    Since I wrote that letter, several readers have sent solutions that worked for JDK 1.4 and JDK 5.0: Scott Sauyet, Dave Combs, Hitesh Patel, Debbie Utley, Wolf Siberski, Dirk Hillbrecht, Andrew Cole and Sami. Thank you very much!
    Most of the solutions consisted of adding one line to my code:
    setSize(columnModel.getColumn(column).getWidth(), 100000);
      
    This magically made the renderer work under JDK 1.4 and 1.5. However, it did not address the other issues that I mentioned in the original newsletter:
    1. It worked when only one column contained the TextAreaRenderer.
    2. I did not implement a TextAreaEditor.
    Something else which bothered me with the proposed solutions was that the cells with our renderers looked slightly different to the cells with default renderers.
    Here is a renderer followed by an editor that address all these issues:
    import javax.swing.*;
    import javax.swing.table.*;
    import java.awt.*;
    import java.util.*;
    
    public class TextAreaRenderer extends JTextArea
        implements TableCellRenderer {
      private final DefaultTableCellRenderer adaptee =
          new DefaultTableCellRenderer();
      /** map from table to map of rows to map of column heights */
      private final Map cellSizes = new HashMap();
    
      public TextAreaRenderer() {
        setLineWrap(true);
        setWrapStyleWord(true);
      }
    
      public Component getTableCellRendererComponent(//
          JTable table, Object obj, boolean isSelected,
          boolean hasFocus, int row, int column) {
        // set the colours, etc. using the standard for that platform
        adaptee.getTableCellRendererComponent(table, obj,
            isSelected, hasFocus, row, column);
        setForeground(adaptee.getForeground());
        setBackground(adaptee.getBackground());
        setBorder(adaptee.getBorder());
        setFont(adaptee.getFont());
        setText(adaptee.getText());
    
        // This line was very important to get it working with JDK1.4
        TableColumnModel columnModel = table.getColumnModel();
        setSize(columnModel.getColumn(column).getWidth(), 100000);
        int height_wanted = (int) getPreferredSize().getHeight();
        addSize(table, row, column, height_wanted);
        height_wanted = findTotalMaximumRowSize(table, row);
        if (height_wanted != table.getRowHeight(row)) {
          table.setRowHeight(row, height_wanted);
        }
        return this;
      }
    
      private void addSize(JTable table, int row, int column,
                           int height) {
        Map rows = (Map) cellSizes.get(table);
        if (rows == null) {
          cellSizes.put(table, rows = new HashMap());
        }
        Map rowheights = (Map) rows.get(new Integer(row));
        if (rowheights == null) {
          rows.put(new Integer(row), rowheights = new HashMap());
        }
        rowheights.put(new Integer(column), new Integer(height));
      }
    
      /**
       * Look through all columns and get the renderer.  If it is
       * also a TextAreaRenderer, we look at the maximum height in
       * its hash table for this row.
       */
      private int findTotalMaximumRowSize(JTable table, int row) {
        int maximum_height = 0;
        Enumeration columns = table.getColumnModel().getColumns();
        while (columns.hasMoreElements()) {
          TableColumn tc = (TableColumn) columns.nextElement();
          TableCellRenderer cellRenderer = tc.getCellRenderer();
          if (cellRenderer instanceof TextAreaRenderer) {
            TextAreaRenderer tar = (TextAreaRenderer) cellRenderer;
            maximum_height = Math.max(maximum_height,
                tar.findMaximumRowSize(table, row));
          }
        }
        return maximum_height;
      }
    
      private int findMaximumRowSize(JTable table, int row) {
        Map rows = (Map) cellSizes.get(table);
        if (rows == null) return 0;
        Map rowheights = (Map) rows.get(new Integer(row));
        if (rowheights == null) return 0;
        int maximum_height = 0;
        for (Iterator it = rowheights.entrySet().iterator();
             it.hasNext();) {
          Map.Entry entry = (Map.Entry) it.next();
          int cellHeight = ((Integer) entry.getValue()).intValue();
          maximum_height = Math.max(maximum_height, cellHeight);
        }
        return maximum_height;
      }
    }
      
    I know that the mechanism for handling the cell renderer for several columns is rather complex and inefficient. However, it caters for the general case where one instance of the cell renderer is used for several columns, or even for several tables.
    You will notice that I am borrowing the formatting of the DefaultTableCellRenderer, so that the Text Area looks the same as the other cells, for the particular platform and JVM version.
    As predicted in my newsletter three years ago, it was a "Kinderspiel" (child's play) to write the TextAreaEditor. A few points to note: I subclass the DefaultCellEditor, which only caters for JTextField, JComboBox and JCheckBox as editors. I therefore replace the editorComponent with my own, and also set my own delegate. In order to make it possible to edit more than the current cell allows, I put the TextArea for editing into a JScrollPane, and I set its border to null, to prevent it from showing the scrollbar unnecessarily.
    import javax.swing.*;
    
    public class TextAreaEditor extends DefaultCellEditor {
      public TextAreaEditor() {
        super(new JTextField());
        final JTextArea textArea = new JTextArea();
        textArea.setWrapStyleWord(true);
        textArea.setLineWrap(true);
        JScrollPane scrollPane = new JScrollPane(textArea);
        scrollPane.setBorder(null);
        editorComponent = scrollPane;
    
        delegate = new DefaultCellEditor.EditorDelegate() {
          public void setValue(Object value) {
            textArea.setText((value != null) ? value.toString() : "");
          }
          public Object getCellEditorValue() {
            return textArea.getText();
          }
        };
      }
    }
      
    I tested this on JDK 1.3.1 right up to JDK 1.5.0, under the Windows, Motif and Metal Look and Feels, and it appears the same as a normal JTable cell. Here is some test code:
    import javax.swing.*;
    import javax.swing.table.*;
    
    public class TextAreaRendererTest extends JFrame {
      private final JTable table = new JTable(10, 4);
    
      public TextAreaRendererTest() {
        super(System.getProperty("java.vm.version"));
    
        // We use our cell renderer for columns 1, 2, 3
        TableColumnModel cmodel = table.getColumnModel();
        TextAreaRenderer textAreaRenderer = new TextAreaRenderer();
    
        cmodel.getColumn(1).setCellRenderer(textAreaRenderer);
        cmodel.getColumn(2).setCellRenderer(new TextAreaRenderer());
        // I am demonstrating that you can have several renderers in
        // one table, and they communicate with one another in
        // deciding the row height.
        cmodel.getColumn(3).setCellRenderer(textAreaRenderer);
        TextAreaEditor textEditor = new TextAreaEditor();
        cmodel.getColumn(1).setCellEditor(textEditor);
        cmodel.getColumn(2).setCellEditor(textEditor);
        cmodel.getColumn(3).setCellEditor(textEditor);
    
        String test = "The lazy dog jumps over the quick brown fox";
    
        for (int column = 0; column < 4; column++) {
          table.setValueAt(test, 0, column);
          table.setValueAt(test, 4, column);
        }
        test = test + test + test + test + test + test + test + test;
        table.setValueAt(test, 4, 2);
    
        getContentPane().add(new JScrollPane(table));
        setSize(600, 600);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
      }
      public static void main(String[] args) {
        new TextAreaRendererTest();
      }
    }
      
    Well, it is late, and I need to hit the sack so that I can be fresh tomorrow for the Design Patterns Course at Alcatel Austria :)
    Don't forget to let me know if you are currently in Austria!

    created by Dr. Heinz M. Kabutz

    Maths Tutor in GWT

    Whenever you go to a Java conference nowadays, there will be at least one session dedicated to the Google Web Toolkit (GWT). You will be blown away by incredible AJAX feats, all coded in Java. Then you will hear that you do not need to worry about cross-browser JavaScript code, because GWT takes care of this for you. On Google's webpage we see this paragraph: "GWT shields you from worrying too much about cross-browser incompatibilities. If you stick to built-in widgets and composites, your applications will work similarly on the most recent versions of Internet Explorer, Firefox, and Safari. (Opera, too, most of the time.) DHTML user interfaces are remarkably quirky, though, so make sure to test your applications thoroughly on every browser."
    I decided to spend a few days investigating GWT to gauge its applicability for my website. It became clear quite early that mixing GWT with an existing multi-page website would be a challenge. Also, I found that a lot of the ideas I tried worked perfectly in Firefox but failed dismally in Internet Explorer. You do have to test everywhere. However, the effects are stunning.
    A few years ago, I wrote a simple Maths (that's what we call Math in South Africa) tutor for my son Maxi. We called it "Maxi Maths". It was simply a command line Java program, that would feed him questions and then measure the time it took him to answer. Functionally it was perfect in that it did exactly what we needed, and no more. By practicing his arithmetic, he regularly finished in the top four of his class.
    After some time, we decided to "upgrade" to a Swing application, which worked quite well. From there we went over to an Applet and downgraded it to AWT. We had the usual Applet issues, and eventually did a JSP page. However, the JSP page had the typical page reload issues. We wanted to have a richer client experience. So last week, we decided to try this in GWT, mainly to satisfy our curiosity, but also to learn more about GWT.
    Here is "Maxi Maths" in GWT and the older JSP version. When you try them out, note the page refreshing that happens with the JSP page whenever an answer is submitted. (Clue: Sometimes when you press an incorrect key, unexpected things happen.)
    Before I show you the code, you need to prepare your system a bit. First off, download the GWT and install it. Now run the applicationCreator batch file shipped with the GWT, as follows:
    applicationCreator net.kabutz.maxi.gwt.maths.client.Maths
      
    This will prepare a nice directory structure for you, with two batch files: Maths-compile and Maths-shell. Call them both and you will see a nice scaffolding application that gets you going on using GWT.

    Maths Game Foundation

    Next we create the maths game. A very simple maths tutor that asks you a bunch of questions and then gives you a score. Each question extends the MathsQuestion abstract class. We have catered for plus, minus, multiply and divide, but it could be extended for other operators like remainder.
    Note that GWT is limited in which classes can be compiled to JavaScript. For example, I could call the Math.random() method, but could not instantiate java.util.Random. Also, you cannot use any reflection code in the client part of GWT. If you add a server component, you can do anything that Java would normally allow under Servlets.
    package net.kabutz.maxi.gwt.maths.client.game;
    
    public abstract class MathsQuestion {
      private int answer;
      private String question;
    
      protected int random(int max) {
        return (int) (Math.random() * max) + 1;
      }
    
      protected void setQuestion(int first, int second,
                                 char operator, int answer) {
        this.question = first + " " + operator + " " + second + " = ";
        this.answer = answer;
      }
    
      public String getQuestion() {
        return question;
      }
    
      public int getAnswer() {
        return answer;
      }
    }
      
    We then have four concrete maths questions. I must admit that initially I used a switch statement for the questions. However, a better Design Pattern is to use the Strategy pattern, on which this design is based. It would allow us to add new types of questions without modifying the rest of our code.
    package net.kabutz.maxi.gwt.maths.client.game;
    
    public class PlusQuestion extends MathsQuestion {
      public PlusQuestion(int upto) {
        int first = random(upto - (upto / 10));
        int second = random(upto - first - 1);
        setQuestion(first, second, '+', first + second);
      }
    }
    
    package net.kabutz.maxi.gwt.maths.client.game;
    
    public class MinusQuestion extends MathsQuestion {
      public MinusQuestion(int upto) {
        int first = random(upto);
        int second = random(first - 1);
        setQuestion(first, second, '-', first - second);
      }
    }
    
    package net.kabutz.maxi.gwt.maths.client.game;
    
    public class TimesQuestion extends MathsQuestion {
      public TimesQuestion(int upto) {
        int first = random((int) Math.sqrt(upto));
        int num = random(upto);
        int second = num / first;
        setQuestion(first, second, '×', first * second);
      }
    }
    
    package net.kabutz.maxi.gwt.maths.client.game;
    
    public class DivideQuestion extends MathsQuestion {
      public DivideQuestion(int upto) {
        int top = random(upto);
        int second = random((int) Math.sqrt(top));
        int first = top / second * second;
        setQuestion(first, second, '÷', first / second);
      }
    }
      
    We put these together in a MathsBean and MathsGameBean. Each MathsBean has a pointer to a MathsQuestion, which we could see as a strategy instance. We could also generate these questions on the server and return them as XML to the browser. I am not showing it here, but it would be really easy to do. You need to just let the classes implement com.google.gwt.user.client.rpc.IsSerializable. The GWT assistance in IntelliJ IDEA 6 is excellent and will help you get all the pieces fitting together (demo).
    package net.kabutz.maxi.gwt.maths.client.game;
    
    public class MathsBean {
      private final MathsQuestion strategy;
      private boolean correct = false;
    
      public MathsBean(int upto) {
        strategy = makeRandomQuestion(upto);
      }
      private MathsQuestion makeRandomQuestion(int upto) {
        switch ((int) (Math.random() * 4)) {
          default:
          case 0: return new PlusQuestion(upto);
          case 1: return new MinusQuestion(upto);
          case 2: return new TimesQuestion(upto);
          case 3: return new DivideQuestion(upto);
        }
      }
      public String getQuestion() {
        return strategy.getQuestion();
      }
      public boolean checkAnswer(int answer) {
        return correct = strategy.getAnswer() == answer;
      }
      public int getAnswer() {
        return strategy.getAnswer();
      }
      public boolean isCorrect() {
        return correct;
      }
    }
      
    The MathsGameBean holds a number of MathsBeans and remembers how long we have been answering the questions for. Each MathsBean knows whether it was answered correctly or not, so we do not keep the information inside the MathsGameBean, but look it up on request.
    package net.kabutz.maxi.gwt.maths.client.game;
    
    public class MathsGameBean {
      private int currentQuestion = 0;
      private long start = System.currentTimeMillis();
      private final MathsBean[] questions;
    
      public MathsGameBean(int questions, int upto) {
        this.questions = new MathsBean[questions];
        for (int i = 0; i < questions; i++) {
          this.questions[i] = new MathsBean(upto);
        }
      }
      public boolean hasNext() {
        return currentQuestion < questions.length;
      }
      public MathsBean next() {
        return questions[currentQuestion++];
      }
      public int getScore() {
        int correct = 0;
        for (int i = 0; i < questions.length; i++) {
          correct += questions[i].isCorrect() ? 1 : 0;
        }
        return correct;
      }
      public int getTotalQuestions() {
        return questions.length;
      }
      public int getSeconds() {
        return (int) ((System.currentTimeMillis() - start + 500) / 1000);
      }
      public int getRate() {
        return getTotalQuestions() * 60 / getSeconds();
      }
    }
      
    We use these classes to write a simple Maths Tutor.

    Binding Into GWT

    The main entry class that we will use is Maths. It has two panels, MathsGameSetupPanel and MathsGamePlayPanel, which contain components used to either setup the game, or play it.
    Here is the magic of GWT. All of the Java code you have seen up to now, and the Java code that we will show next, is turned into JavaScript code. This is supposed to be portable between browsers, but is not always. You still need to test it on all browser / OS combinations!
    Since we are swapping between the "setup" and "play" pages, we implemented a very basic state pattern for managing the state transitions. There are many ways of implementing the state pattern. In this approach, we pass a pointer to Maths into the state, to allow it to switch to the next page. We do that with the methods setupGame() and playGame().
    package net.kabutz.maxi.gwt.maths.client;
    
    import com.google.gwt.core.client.EntryPoint;
    import com.google.gwt.user.client.ui.*;
    
    public class Maths implements EntryPoint {
      private Panel currentPanel;
    
      public void onModuleLoad() {
        setupGame();
      }
    
      public void setupGame() {
        setPanel(new MathsGameSetupPanel(this));
      }
    
      public void playGame(int questions, int upto) {
        setPanel(new MathsGamePlayPanel(this, questions, upto));
      }
    
      private void setPanel(Panel panel) {
        if (currentPanel != null) {
          RootPanel.get().remove(currentPanel);
        }
        currentPanel = panel;
        RootPanel.get().add(currentPanel);
      }
    }
      
    We create the setup panel with two text boxes and a submit button. The CSS style used is "game-style", described further down in the newsletter. The default values for the number of questions is "10" and the default maximum number is "50". It looks like a typical Swing application, but generates JavaScript when you compile it with GWT.
    package net.kabutz.maxi.gwt.maths.client;
    
    import com.google.gwt.user.client.ui.*;
    
    public class MathsGameSetupPanel extends VerticalPanel {
      private TextBox questions = new TextBox();
      private TextBox upToNumber = new TextBox();
    
      public MathsGameSetupPanel(final Maths owner) {
        setStyleName("game-style");
    
        questions.setText("10");
        upToNumber.setText("50");
    
        add(makeInput("Number of questions:", questions));
        add(makeInput("Up To Number:", upToNumber));
        add(new Button("Play Game!", new ClickListener() {
          public void onClick(Widget widget) {
            int numQues = Integer.parseInt(questions.getText());
            int upto = Integer.parseInt(upToNumber.getText());
            owner.playGame(numQues, upto);
          }
        }));
      }
    
      private Panel makeInput(String label, TextBox inputText) {
        HorizontalPanel inputPanel = new HorizontalPanel();
        inputPanel.add(new Label(label));
        inputPanel.add(inputText);
        return inputPanel;
      }
    }
      
    The actual game play panel shows the question and a textbox where we can enter the answer. We register a keyboard listener on the answer textbox. When we press enter, we check the answer, showing the correct answer if necessary. (Clue: We can add listeners to all sorts of characters with the KeyboardListener.) We apply a different style to the remark label, depending on whether it was correct or not.
    Part of the checkAnswer() method involves checking whether we have further questions. If we do, we display them, otherwise we show the final score and display a button to "Play again!".
    package net.kabutz.maxi.gwt.maths.client;
    
    import com.google.gwt.user.client.ui.*;
    import net.kabutz.maxi.gwt.maths.client.game.*;
    
    public class MathsGamePlayPanel extends VerticalPanel {
      private MathsGameBean mathsGame;
      private MathsBean maths;
      private TextBox answerBox = new TextBox();
      private Label questionLabel = new Label();
      private Label remark = new Label();
      private Maths owner;
    
      public MathsGamePlayPanel(Maths owner, int questions, int upto) {
        setStyleName("game-style");
        this.owner = owner;
        mathsGame = new MathsGameBean(questions, upto);
        maths = mathsGame.next();
    
        questionLabel.setText(maths.getQuestion());
    
        answerBox.setMaxLength(10);
        answerBox.setWidth("60px");
        answerBox.addKeyboardListener(new KeyboardListenerAdapter() {
          public void onKeyPress(Widget widget, char c, int i) {
            if (c == 13) {
              checkAnswer();
            }
          }
        });
    
        Panel questionPanel = new HorizontalPanel();
        questionPanel.add(questionLabel);
        questionPanel.add(answerBox);
    
        add(questionPanel);
        add(remark);
      }
    
      private void checkAnswer() {
        int answer = Integer.parseInt(answerBox.getText());
        if (maths.checkAnswer(answer)) {
          remark.setText("Well Done!");
          remark.setStyleName("correct-answer");
        } else {
          remark.setText("Nope! " + maths.getQuestion() +
              maths.getAnswer() + " not " + answer);
          remark.setStyleName("wrong-answer");
        }
        answerBox.setText("");
    
        if (mathsGame.hasNext()) {
          maths = mathsGame.next();
          questionLabel.setText(maths.getQuestion());
        } else {
          add(new Label(mathsGame.getScore() + " / " +
              mathsGame.getTotalQuestions()));
          add(new Label(mathsGame.getRate() + " answers per minute"));
          add(new Button("Play again!", new ClickListener() {
            public void onClick(Widget widget) {
              owner.setupGame();
            }
          }));
          answerBox.setEnabled(false);
        }
      }
    }
      
    The HTML page would be rather basic. We could name the element that we add our panels to, but in our case all we want to do is to add and remove panels to the bottom of the page. Here is the Maths.html file, positioned in the "public" directory constructed by the GWT applicationCreator.
    <html>
      <head>
        <title>Welcome to Maxi Maths</title>
        <link rel=stylesheet href="Maths.css">
        <meta name='gwt:module' content='net.kabutz.maxi.gwt.maths.Maths'>
      </head>
      <body>
        <script language="javascript" src="gwt.js"></script>
        <h1>Welcome to Maxi Maths</h1>
      </body>
    </html>
      
    There is quite a lot we can do with CSS. The styles are applied from within the GWT code with setStyleName("name"). Here we see the Maths.css file, which should be in the same directory as the Maths.html file:
    body {
      font-family: sans-serif;
      font-size: small;
    }
    
    .game-style {
      background-color: #00FF00;
      border: 3px solid orange;
    }
    
    .game-style * {
      padding: 6px;
      font-family: cursive;
      font-size: large;
    }
    
    .correct-answer {
      font-style: italic;
      color: blue;
    }
    
    .wrong-answer {
      font-style: oblique;
      color: red;
    }
      
    If you put all this together, you get a nice little maths tutor to amuse your kids with.

    Further Thoughts on GWT

    GWT seems a more comfortable approach for a Java programmer than scratching around in JavaScript. A lot of the coding approaches with Swing can be applied to GWT. However, you do need to control the browsers that your clients will use to connect to your webpage. If someone tries to connect with IE 5, they might just see a blank page.

    created by Dr. Heinz M. Kabutz and Maximilian F. Kabutz

    TristateCheckBox Revisited (JAVA)

    Have you ever written a piece of software and not received bug reports for it? If so, then make sure that your clients are actually running the code!
    Three and a half years ago, I published a TristateCheckBox that you can use in Swing applications and applets. Unlike a normal check box it had three states, SELECTED, DESELECTED and INDETERMINATE.
    Even though it is just a small little GUI class, I have had requests from no less than eight institutes, including a Fortune 500 company and a major university, asking whether they could use the class. In addition, several readers have sent corrections and additions to the class.
    The most recent was by Mark Zellers from Adaptive Planning, informing me that there was a problem with the TristateCheckBox. His description of the bug struck me as odd, so I ran my old code again. It looks like the behaviour changed in the move over to Java 5. The check box did not retain its INDETERMINATE state when it lost focus.
    Unfortunately, none of the proposed fixes covered all the issues that needed to be addressed. Even after spending the entire day reading through related emails and coming up with a better solution, I am convinced that there are still snafus that I have not thought of yet.
    The best solution was sent by David Wright, who is working on the Superficial framework for GUIs. In his last email to me, David wrote: Trying to produce a really robust TristateCheckBox has turned out to be quite a challenge and I'm still not sure I've covered all the bases. However, I would be delighted if you wanted to publish this or a further improved version.
    Several readers also sent me minor corrections to do with using enums for the states, so let us start with the states. Here, we have a very simple state machine that indicates what the next state would be. This works in a chain of Selected -> Indeterminate -> Deselected -> Selected.
    package eu.javaspecialists.tjsn.gui;
    
    public enum TristateState {
      SELECTED {
        public TristateState next() {
          return INDETERMINATE;
        }
      },
      INDETERMINATE {
        public TristateState next() {
          return DESELECTED;
        }
      },
      DESELECTED {
        public TristateState next() {
          return SELECTED;
        }
      };
    
      public abstract TristateState next();
    }
      
    In David Wright's approach, he subclassed the TristateButtonModel from ToggleButtonModel, rather than try to decorate the model. This allows us to reuse the model for other components, for example, a TristateRadioButton (not shown here).
    A point worth mentioning is that the itemChanged event is typically only fired with the checkbox is either selected or deselected (not during the intermediate states). We have to manually fire off the events in the setState() method.
    package eu.javaspecialists.tjsn.gui;
    
    import javax.swing.JToggleButton.ToggleButtonModel;
    import java.awt.event.ItemEvent;
    
    public class TristateButtonModel extends ToggleButtonModel {
      private TristateState state = TristateState.DESELECTED;
    
      public TristateButtonModel(TristateState state) {
        setState(state);
      }
    
      public TristateButtonModel() {
        this(TristateState.DESELECTED);
      }
    
      public void setIndeterminate() {
        setState(TristateState.INDETERMINATE);
      }
    
      public boolean isIndeterminate() {
        return state == TristateState.INDETERMINATE;
      }
    
      // Overrides of superclass methods
      public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        // Restore state display
        displayState();
      }
    
      public void setSelected(boolean selected) {
        setState(selected ?
            TristateState.SELECTED : TristateState.DESELECTED);
      }
    
      // Empty overrides of superclass methods
      public void setArmed(boolean b) {
      }
    
      public void setPressed(boolean b) {
      }
    
      void iterateState() {
        setState(state.next());
      }
    
      private void setState(TristateState state) {
        //Set internal state
        this.state = state;
        displayState();
        if (state == TristateState.INDETERMINATE && isEnabled()) {
          // force the events to fire
    
          // Send ChangeEvent
          fireStateChanged();
    
          // Send ItemEvent
          int indeterminate = 3;
          fireItemStateChanged(new ItemEvent(
              this, ItemEvent.ITEM_STATE_CHANGED, this,
              indeterminate));
        }
      }
    
      private void displayState() {
        super.setSelected(state != TristateState.DESELECTED);
        super.setArmed(state == TristateState.INDETERMINATE);
        super.setPressed(state == TristateState.INDETERMINATE);
    
      }
    
      public TristateState getState() {
        return state;
      }
    }
      
    We reference this model from within the TristateCheckbox class. Users can either figure out the state using the isSelected() and isIndeterminate() methods or by calling the getState() method.
    package eu.javaspecialists.tjsn.gui;
    
    import javax.swing.*;
    import javax.swing.event.*;
    import javax.swing.plaf.ActionMapUIResource;
    import java.awt.*;
    import java.awt.event.*;
    
    public final class TristateCheckBox extends JCheckBox {
      // Listener on model changes to maintain correct focusability
      private final ChangeListener enableListener =
          new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
              TristateCheckBox.this.setFocusable(
                  getModel().isEnabled());
            }
          };
    
      public TristateCheckBox(String text) {
        this(text, null, TristateState.DESELECTED);
      }
    
      public TristateCheckBox(String text, Icon icon,
                              TristateState initial) {
        super(text, icon);
    
        //Set default single model
        setModel(new TristateButtonModel(initial));
    
        // override action behaviour
        super.addMouseListener(new MouseAdapter() {
          public void mousePressed(MouseEvent e) {
            TristateCheckBox.this.iterateState();
          }
        });
        ActionMap actions = new ActionMapUIResource();
        actions.put("pressed", new AbstractAction() {
          public void actionPerformed(ActionEvent e) {
            TristateCheckBox.this.iterateState();
          }
        });
        actions.put("released", null);
        SwingUtilities.replaceUIActionMap(this, actions);
      }
    
      // Next two methods implement new API by delegation to model
      public void setIndeterminate() {
        getTristateModel().setIndeterminate();
      }
    
      public boolean isIndeterminate() {
        return getTristateModel().isIndeterminate();
      }
    
      public TristateState getState() {
        return getTristateModel().getState();
      }
    
      //Overrides superclass method
      public void setModel(ButtonModel newModel) {
        super.setModel(newModel);
    
        //Listen for enable changes
        if (model instanceof TristateButtonModel)
          model.addChangeListener(enableListener);
      }
    
      //Empty override of superclass method
      public void addMouseListener(MouseListener l) {
      }
    
      // Mostly delegates to model
      private void iterateState() {
        //Maybe do nothing at all?
        if (!getModel().isEnabled()) return;
    
        grabFocus();
    
        // Iterate state
        getTristateModel().iterateState();
    
        // Fire ActionEvent
        int modifiers = 0;
        AWTEvent currentEvent = EventQueue.getCurrentEvent();
        if (currentEvent instanceof InputEvent) {
          modifiers = ((InputEvent) currentEvent).getModifiers();
        } else if (currentEvent instanceof ActionEvent) {
          modifiers = ((ActionEvent) currentEvent).getModifiers();
        }
        fireActionPerformed(new ActionEvent(this,
            ActionEvent.ACTION_PERFORMED, getText(),
            System.currentTimeMillis(), modifiers));
      }
    
      //Convenience cast
      public TristateButtonModel getTristateModel() {
        return (TristateButtonModel) super.getModel();
      }
    }
      
    We also have a test case for this class, using the various look and feels installed on your system. Note that this approach does not work well on the Motif L&F.
    package eu.javaspecialists.tjsn.gui;
    
    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;
    
    public class TristateCheckBoxTest {
      public static void main(String args[]) throws Exception {
        JFrame frame = new JFrame("TristateCheckBoxTest");
        frame.setLayout(new GridLayout(0, 1, 15, 15));
        UIManager.LookAndFeelInfo[] lfs =
            UIManager.getInstalledLookAndFeels();
        for (UIManager.LookAndFeelInfo lf : lfs) {
          System.out.println("Look&Feel " + lf.getName());
          UIManager.setLookAndFeel(lf.getClassName());
          frame.add(makePanel(lf));
        }
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
      }
    
      private static JPanel makePanel(UIManager.LookAndFeelInfo lf) {
        final TristateCheckBox tristateBox = new TristateCheckBox(
            "Tristate checkbox");
        tristateBox.addItemListener(new ItemListener() {
          public void itemStateChanged(ItemEvent e) {
            switch(tristateBox.getState()) {
              case SELECTED:
                System.out.println("Selected"); break;
              case DESELECTED:
                System.out.println("Not Selected"); break;
              case INDETERMINATE:
                System.out.println("Tristate Selected"); break;
            }
          }
        });
        tristateBox.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            System.out.println(e);
          }
        });
        final JCheckBox normalBox = new JCheckBox("Normal checkbox");
        normalBox.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            System.out.println(e);
          }
        });
    
        final JCheckBox enabledBox = new JCheckBox("Enable", true);
        enabledBox.addItemListener(new ItemListener() {
          public void itemStateChanged(ItemEvent e) {
            tristateBox.setEnabled(enabledBox.isSelected());
            normalBox.setEnabled(enabledBox.isSelected());
          }
        });
    
        JPanel panel = new JPanel(new GridLayout(0, 1, 5, 5));
        panel.add(new JLabel(UIManager.getLookAndFeel().getName()));
        panel.add(tristateBox);
        panel.add(normalBox);
        panel.add(enabledBox);
        return panel;
      }
    }
      
    Please try it out and let me know if it causes any problems or unexpected behaviour on your system.

    created by Dr. Heinz M. Kabutz

    Snappy JSliders in Swing

    First, try this warm-up exercise: Load any Java application which includes a JSlider where the thumb "snaps" to the ticks/labels (if you don't have one to hand, run the test code at the end of the newsletter with the comment intact). Now open a native program with a snapping slider and experiment (e.g. the display resolution slider in Windows' "Display Properties"). Notice anything? The native slider thumb jumps between ticks as it's dragged, but in Java the thumb follows the mouse exactly and only snaps to a valid point when the thumb is released. I created the code presented below to adapt Swing's behaviour to imitate native applications exactly.
    The easiest solution would be to subclass JSlider and hack it to pieces, but this is very inflexible (every JSlider instance would need to be changed, and what if existing subclasses were already in use?) and we can do better. I wanted a solution that would mean minimal effort for the developer.
    Swing has the ability to plug different themes (Look and Feels - LAFs - in Swing lingo) into it which control a component's appearance and operation. Upon construction every standard Swing component queries a central registry managed by the UIManager class to obtain an object instance that implements its user interface (UI).
    Since the current half-baked snapping algorithm is built into the LAF code, then this makes the perfect entry point to add any new behaviour. Now to the code:
    import javax.swing.*;
    import javax.swing.event.MouseInputAdapter;
    import javax.swing.plaf.ComponentUI;
    import javax.swing.plaf.basic.BasicSliderUI;
    import java.awt.*;
    import java.awt.event.*;
    import java.beans.*;
    import java.lang.reflect.*;
    
    public class SliderSnap extends BasicSliderUI {
      /**
       * The UI class implements the current slider Look and Feel.
       */
      private static Class sliderClass;
      private static Method xForVal, yForVal;
      private static ReinitListener reinitListener =
          new ReinitListener();
    
      public SliderSnap() {
        super(null);
      }
    
      /**
       * Returns the UI as normal, but intercepts the call, so a
       * listener can be attached.
       */
      public static ComponentUI createUI(JComponent c) {
        if (c == null || sliderClass == null)
          return null;
        UIDefaults defaults = UIManager.getLookAndFeelDefaults();
        try {
          Method m = (Method) defaults.get(sliderClass);
          if (m == null) {
            m = sliderClass.getMethod("createUI",
              new Class[] {JComponent.class});
            defaults.put(sliderClass, m);
          }
          ComponentUI uiObject = (ComponentUI) m.invoke(null,
            new Object[] {c});
          if (uiObject instanceof BasicSliderUI)
            c.addHierarchyListener(new MouseAttacher());
          return uiObject;
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
    
      public static void init() {
        //check we don't initialise twice
        if (sliderClass != null)
          return;
        Init init = new Init();
        if (EventQueue.isDispatchThread()) {
          init.run();
        } else {
          // This code must run on the EDT for data visibility
          try {
            EventQueue.invokeAndWait(init);
          } catch (Exception e) {
            throw new RuntimeException(e);
          }
        }
      }
    
      /**
       * Listeners for when the JSlider becomes visible then
       * attaches the mouse listeners, then removes itself.
       */
      private static class MouseAttacher implements HierarchyListener {
        public void hierarchyChanged(HierarchyEvent evt) {
          long flags = evt.getChangeFlags();
          if ((flags & HierarchyEvent.DISPLAYABILITY_CHANGED) > 0
              && evt.getComponent() instanceof JSlider) {
            JSlider c = (JSlider) evt.getComponent();
            c.removeHierarchyListener(this);
            attachTo(c);
          }
        }
      }
    
      /**
       * Listens for Look and Feel changes and re-initialises the
       * class.
       */
      private static class ReinitListener implements
          PropertyChangeListener {
        public void propertyChange(PropertyChangeEvent evt) {
          if ("lookAndFeel".equals(evt.getPropertyName())) {
            // The look and feel was changed so we need to re-insert
            // Our hook into the new UIDefaults map
            sliderClass = null;
            xForVal = yForVal = null;
            UIManager.removePropertyChangeListener(reinitListener);
            init();
          }
        }
      }
    
      /**
       * Initialises the reflective methods and adjusts the current
       * Look and Feel.
       */
      private static class Init implements Runnable {
        public void run() {
          try {
            UIDefaults defaults = UIManager.getLookAndFeelDefaults();
            sliderClass = defaults.getUIClass("SliderUI");
            // Set up two reflective method calls
            xForVal = BasicSliderUI.class.getDeclaredMethod(
                "xPositionForValue",
                new Class[] {int.class});
            yForVal = BasicSliderUI.class.getDeclaredMethod(
                "yPositionForValue",
                new Class[] {int.class});
            // Allow us access to the methods
            xForVal.setAccessible(true);
            yForVal.setAccessible(true);
            // Replace UI class with ourselves
            defaults.put("SliderUI", SliderSnap.class.getName());
            UIManager.addPropertyChangeListener(reinitListener);
          } catch (Exception e) {
            sliderClass = null;
            xForVal = yForVal = null;
          }
        }
      }
    
      /**
       * Called to attach mouse listeners to the JSlider.
       */
      private static void attachTo(JSlider c) {
        MouseMotionListener[] listeners = c.getMouseMotionListeners();
        for (int i = 0; i < listeners.length; i++) {
          MouseMotionListener m = listeners[i];
          if (m instanceof TrackListener) {
            c.removeMouseMotionListener(m); //remove original
            SnapListener listen = new SnapListener(m,
                (BasicSliderUI) c.getUI(), c);
            c.addMouseMotionListener(listen);
            c.addMouseListener(listen);
            c.addPropertyChangeListener("UI", listen);
          }
        }
      }
    
      private static class SnapListener extends MouseInputAdapter
          implements PropertyChangeListener {
        private MouseMotionListener delegate;
        /**
         * Original Look and Feel implementation
         */
        private BasicSliderUI ui;
        /**
         * Our slider
         */
        private JSlider slider;
        /**
         * Offset of mouse click from centre of slider thumb
         */
        private int offset;
    
        public SnapListener(MouseMotionListener delegate,
                            BasicSliderUI ui, JSlider slider) {
          this.delegate = delegate;
          this.ui = ui;
          this.slider = slider;
        }
    
        /**
         * UI can change at any point, so we need to listen for these
         * events.
         */
        public void propertyChange(PropertyChangeEvent evt) {
          if ("UI".equals(evt.getPropertyName())) {
            // Remove old listeners and create new ones
            slider.removeMouseMotionListener(this);
            slider.removeMouseListener(this);
            slider.removePropertyChangeListener("UI", this);
            attachTo(slider);
          }
        }
    
        /**
         * Implements the actual "snap while dragging" behaviour.  If
         * snap to ticks is enabled on this slider, then the location
         * for the nearest tick/label is calculated and the click
         * location is translated before being passed to the
         * delegate.
         */
        public void mouseDragged(MouseEvent evt) {
          if (slider.getSnapToTicks()) { // if we are set to snap
            int pos = getLocationForValue(getSnappedValue(evt));
            // if above call fails and returns -1, take no action
            if (pos > -1) {
              if (slider.getOrientation() == JSlider.HORIZONTAL)
                evt.translatePoint(pos - evt.getX() + offset, 0);
              else
                evt.translatePoint(0, pos - evt.getY() + offset);
            }
          }
          delegate.mouseDragged(evt);
        }
    
        /**
         * When the slider is clicked we need to record the offset
         * from thumb center.
         */
        public void mousePressed(MouseEvent evt) {
          int pos = (slider.getOrientation() == JSlider.HORIZONTAL) ?
              evt.getX() : evt.getY();
          int loc = getLocationForValue(getSnappedValue(evt));
          this.offset = (loc < 0) ? 0 : pos - loc;
        }
    
        /* Pass straight to delegate. */
        public void mouseMoved(MouseEvent evt) {
          delegate.mouseMoved(evt);
        }
    
        /**
         * Calculates the nearest snapable value given a MouseEvent.
         * Code adapted from BasicSliderUI.
         */
        public int getSnappedValue(MouseEvent evt) {
          int value = slider.getOrientation() == JSlider.HORIZONTAL
              ? ui.valueForXPosition(evt.getX())
              : ui.valueForYPosition(evt.getY());
          // Now calculate if we should adjust the value
          int snappedValue = value;
          int tickSpacing = 0;
          int majorTickSpacing = slider.getMajorTickSpacing();
          int minorTickSpacing = slider.getMinorTickSpacing();
          if (minorTickSpacing > 0)
            tickSpacing = minorTickSpacing;
          else if (majorTickSpacing > 0)
            tickSpacing = majorTickSpacing;
          // If it's not on a tick, change the value
          if (tickSpacing != 0) {
            if ((value - slider.getMinimum()) % tickSpacing != 0) {
              float temp = (float) (value - slider.getMinimum())
                  / (float) tickSpacing;
              snappedValue = slider.getMinimum() +
                  (Math.round(temp) * tickSpacing);
            }
          }
          return snappedValue;
        }
    
        /**
         * Provides the x or y co-ordinate for a slider value,
         * depending on orientation.
         */
        public int getLocationForValue(int value) {
          try {
            // Reflectively call slider ui code
            Method m = slider.getOrientation() == JSlider.HORIZONTAL
                ? xForVal : yForVal;
            Integer result = (Integer) m.invoke(
                ui, new Object[]{new Integer(value)});
            return result.intValue();
          } catch (InvocationTargetException e) {
            return -1;
          } catch (IllegalAccessException e) {
            return -1;
          }
        }
      }
    }
    The following test class shows the new snapping behaviour in action. The first JSlider was created before the SliderSnap.init() was called, it thus has the old behaviour. We then create one JSlider for each of your installed Look and Feels.
    import javax.swing.*;
    import java.awt.*;
    
    public class SliderTest {
      public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
          public void run() {
            JFrame frame = new JFrame();
            frame.getContentPane().setLayout(new GridLayout(0, 1));
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(makeSlider("Without Snapping"));
            SliderSnap.init();
            UIManager.LookAndFeelInfo[] infos =
                UIManager.getInstalledLookAndFeels();
            for (int i = 0; i < infos.length; i++) {
              UIManager.LookAndFeelInfo info = infos[i];
              try {
                UIManager.setLookAndFeel(info.getClassName());
                JComponent slider = makeSlider(info.getClassName());
                frame.getContentPane().add(slider);
              } catch (Exception e) {
                e.printStackTrace();
              }
            }
            frame.pack();
            frame.setVisible(true);
          }
        });
      }
    
      private static JComponent makeSlider(String title) {
        JPanel panel = new JPanel();
        JSlider slider = new JSlider(-50, 50, 0);
        slider.setPaintLabels(true);
        slider.setMajorTickSpacing(20);
        slider.setSnapToTicks(true);
        panel.add(slider);
        panel.setBorder(BorderFactory.createTitledBorder(title));
        return panel;
      }
    }
    At the start of an application the line SliderSnap.init(); is all that is required. The init() method invokes the run() method in the SliderSnap.Init class to replace the slider UI class of the LAF. When a JSlider requests its UI object, createUI() is called which just returns the original UI object but attaches a HierarchyListener. The code must wait until the slider is displayed before switching the mouse handlers due to a data race that can occur if the component isn't constructed on the event thread (all too common) and we try to switch listeners immediately.
    SnapListener implements the snapping logic by calculating the nearest "tick" to the mouse and re-targeting the MouseEvent so it appears to the delegate that the mouse itself is hopping between ticks. The two propertyChange() methods are there to handle LAF changes that occur mid-application and ensure the code hooks into the new LAF and to de-register listeners when the UI changes on a slider.
    The interesting part is how the LAF is used to adapt a component's behaviour, not the actual change (the snap calculation borrows heavily from BasicSliderUI). The same approach could be used for several changes in Swing. The code works unaltered with just about any application by including the initialisation line as shown and has been tested successfully with many third-party Look and Feels. It is backwards compatible to at least Java 1.4, though should work with Java 1.3 as well (untested). What's more is that it should remain compatible with future versions, including the upcoming Nimbus Look and Feel that is slated to become Swing's default UI.

    created by Michael Kneebone

     
    This Theme Modified by Kapten Andre based on Structure Theme from MIT-style License by Jason J. Jaeger