Showing posts with label Swing. Show all posts
Showing posts with label Swing. Show all posts

Monday, August 10, 2009

Autocompletion and Swing

I can remember being asked to implement cross-browser autocompletion in 2000 and telling my employers that it couldn't be done. We got a prototype working on Netscape (remember when that was the most advanced browser?) but it was buggy on Internet Explorer (remember when IE was the bane of every web developer's existence? Wait, some things never change...) and latency was way too high for most users. Anyway, I didn't last long in that gig, but I still think that for practical purposes at the time I was right. Of course, things are different now.

Type something into Google or Amazon's search box and you'll get a nice drop-down list of possible completions. For a biological example, check out NCBI's BLAST. Make sure the database chooser reads "Nucleotide collection" and start typing "Pyrococcus". Nice, huh?

Several ways to give poor abandoned Swing an autocompleting upgrade are documented in a java.net article. Sadly, they all seem to suffer from one deficiency or another.

The JIDE common layer an open source library that spun out of Jide's commercial offerings seems to be the most stable, but isn't nearly as convenient as the javascript versions. GlazedLists does a nice job, but it's currently (still) broken on OS X. Out of the solutions I found, GlazedLists seems most promising, especially if that bug gets fixed.

I also checkout out the Substance Java look & feel. It looks really sharp. The developer has done some really slick transitions -- highlights that fade in and out or components that expand like icons on OS X's dock. It's probably great on Windows, but unfortunately, it didn't seem very stable on the Mac.

There should be some good lessons to be learned from the failure of Swing. It seems apparent that several developers who are a lot smarter than me have tried to get Swing to cough up a decent UI. The results seem to be consistently limited. Not that some aren't impressive; they are. But the limits to the success of some very good developers speaks very loudly.

More attempts...

  • AutoCompleteCombo by Exterminator13 (haha) -
    Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: -1
     at java.util.ArrayList.get(ArrayList.java:323)
     at dzone.AutoCompleteCombo$Model.getElementAt(AutoCompleteCombo.java:476)
  • An autocomplete popup by Pierre Le Lannic, which seems to work as long as you don't care about upper case.
  • Java2sAutoTextField from Sun

Saturday, March 14, 2009

Bug in GlazedLists' AutoCompleteSupport?

I found what looks to me like a couple bugs in the AutoCompleteSupport of GlazedLists. I'm not going to talk about how cool GlazedLists is - it's very cool. I just want to document the bugs. Either that, or document that I'm a bonehead and I'm doing something bass-ackwards with a perfectly good library.

Note: I am doing something boneheaded. I'm documenting a known bug (bug 458). I spammed GlazedLists' mailing list. The first issue, selecting items from the popup, I reported as bug 469.

Here's my repro. I'm working on OS X with Java 10.5.0_16, which I suspect is a factor.

package org.cbare.testglazedlists;

import javax.swing.*;

import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.swing.AutoCompleteSupport;


public class SpeciesChooser {
 JFrame frame;
 AutoCompleteSupport autocomplete;
 private JComboBox chooser;

 public SpeciesChooser() {
  initGui();
 }

 private void initGui() {
  Box vbox, hbox;
  frame = new JFrame("Species Chooser");
  
  vbox = Box.createVerticalBox();
  vbox.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
  hbox = Box.createHorizontalBox();
  
  hbox.add(new JLabel("Select Species:"));
  chooser = new JComboBox();
  chooser.setPrototypeDisplayValue("Marmoset");
  hbox.add(chooser);
  SwingUtilities.invokeLater(new Runnable() {
   public void run() {
    autocomplete = AutoCompleteSupport.install(chooser, getSpecies());
   }
  });

  vbox.add(hbox);
  frame.add(vbox);
  frame.pack();
  frame.setVisible(true);
 }

 private EventList getSpecies() {
  EventList result = new BasicEventList();
  result.add("Marmoset");
  result.add("Monkey");
  result.add("Moose");
  result.add("Mouse");
  result.add("Spaaa");
  result.add("Spider");
  result.add("Spidooo");
  return result;
 }

 public static void main(String[] args) {
  SpeciesChooser s = new SpeciesChooser();
 }
}

I used Jing to make a little screencast of my flailing about. Instructions for the repro follow:

First, if I type a prefix which matches several item in the list, and I use the down-arrow to scroll through the matches and pick one, it fails to update the context of the text box. In my example, I type "mo" which matches, Monkey, Moose, Mouse. I press down-arrow twice to select Moose then press Enter. "Monkey" is still in the text box. If I repeat that a couple of times, the popup then shows all choices, not just those matching my prefix. Maybe that's intentional.

Second, if I type something that matches nothing in the list, say "moz" and then backspace over the "z" I get an exception. The stack trace is shown here:

Exception in thread "AWT-EventQueue-0" java.lang.IndexOutOfBoundsException: Index: 1, Size: 0
    at java.util.ArrayList.RangeCheck(ArrayList.java:546)
    at java.util.ArrayList.get(ArrayList.java:321)
    at ca.odell.glazedlists.impl.gui.ThreadProxyEventList.applyChangeToCache(ThreadProxyEventList.java:175)
    at ca.odell.glazedlists.impl.gui.ThreadProxyEventList.access$600(ThreadProxyEventList.java:68)
    at ca.odell.glazedlists.impl.gui.ThreadProxyEventList$UpdateRunner.listChanged(ThreadProxyEventList.java:237)
    at ca.odell.glazedlists.event.ListEventAssembler$ListEventFormat.fire(ListEventAssembler.java:412)
    at ca.odell.glazedlists.event.ListEventAssembler$ListEventFormat.fire(ListEventAssembler.java:409)
    at ca.odell.glazedlists.event.SequenceDependenciesEventPublisher$SubjectAndListener.firePendingEvent(SequenceDependenciesEventPublisher.java:445)
    at ca.odell.glazedlists.event.SequenceDependenciesEventPublisher.fireEvent(SequenceDependenciesEventPublisher.java:344)
    at ca.odell.glazedlists.event.ListEventAssembler.commitEvent(ListEventAssembler.java:316)
    at ca.odell.glazedlists.impl.gui.ThreadProxyEventList$UpdateRunner.run(ThreadProxyEventList.java:225)
    at ca.odell.glazedlists.impl.swing.SwingThreadProxyEventList.schedule(SwingThreadProxyEventList.java:33)
    at ca.odell.glazedlists.impl.gui.ThreadProxyEventList.listChanged(ThreadProxyEventList.java:118)
    at ca.odell.glazedlists.event.ListEventAssembler$ListEventFormat.fire(ListEventAssembler.java:412)
    at ca.odell.glazedlists.event.ListEventAssembler$ListEventFormat.fire(ListEventAssembler.java:409)
    at ca.odell.glazedlists.event.SequenceDependenciesEventPublisher$SubjectAndListener.firePendingEvent(SequenceDependenciesEventPublisher.java:445)
    at ca.odell.glazedlists.event.SequenceDependenciesEventPublisher.fireEvent(SequenceDependenciesEventPublisher.java:344)
    at ca.odell.glazedlists.event.ListEventAssembler.commitEvent(ListEventAssembler.java:316)
    at ca.odell.glazedlists.FilterList.constrained(FilterList.java:389)
    at ca.odell.glazedlists.FilterList.changeMatcher(FilterList.java:286)
    at ca.odell.glazedlists.FilterList.changeMatcherWithLocks(FilterList.java:269)
    at ca.odell.glazedlists.FilterList.access$100(FilterList.java:51)
    at ca.odell.glazedlists.FilterList$PrivateMatcherEditorListener.changedMatcher(FilterList.java:443)
    at ca.odell.glazedlists.matchers.AbstractMatcherEditor.fireChangedMatcher(AbstractMatcherEditor.java:115)
    at ca.odell.glazedlists.matchers.AbstractMatcherEditor.fireConstrained(AbstractMatcherEditor.java:73)
    at ca.odell.glazedlists.matchers.TextMatcherEditor.setTextMatcher(TextMatcherEditor.java:321)
    at ca.odell.glazedlists.matchers.TextMatcherEditor.setFilterText(TextMatcherEditor.java:292)
    at ca.odell.glazedlists.swing.AutoCompleteSupport.applyFilter(AutoCompleteSupport.java:1271)
    at ca.odell.glazedlists.swing.AutoCompleteSupport.access$2300(AutoCompleteSupport.java:209)
    at ca.odell.glazedlists.swing.AutoCompleteSupport$AutoCompleteFilter.postProcessDocumentChange(AutoCompleteSupport.java:1497)
    at ca.odell.glazedlists.swing.AutoCompleteSupport$AutoCompleteFilter.remove(AutoCompleteSupport.java:1450)
    at javax.swing.text.AbstractDocument.remove(AbstractDocument.java:572)
    at javax.swing.text.DefaultEditorKit$DeletePrevCharAction.actionPerformed(DefaultEditorKit.java:1030)
    at javax.swing.SwingUtilities.notifyAction(SwingUtilities.java:1576)
    at javax.swing.JComponent.processKeyBinding(JComponent.java:2772)
    ...

In spite of my whining, GlazedLists is very well thought out and a nice piece of work. For later reference, I also reported bug 472.

Friday, November 28, 2008

Better color chooser for Java Swing

I came across a color chooser that kicks the Swing JColorChooser's booty. It looks great and supports transparency. It's available at colorchooser.dev.java.net and on the developer's blog. It will soon be used in my genome browser. Thanks, Jeremy!

Wednesday, February 27, 2008

What a drag

Java's drag and drop functionality is painful. What I want is a list that the user can reorder by dragging. Here a couple of nice efforts with the caveat that they work only for single selections.

Sun's tutorial is here. There's a chapter in O'Reilly's Swing Hacks about it, but their code seems buggy to me.

Thursday, November 22, 2007

More Swing Misery

Making a ListCellRenderer containing a word-wrapping JTextArea is apparently asking for trouble. The root of the problem is that there isn't a specified protocol for the negotiation between container and contained gui components to establish size. What you want here is for the JList to compute its width independent of the JTextAreas in the cells. The JTextAreas get their width from the JList and wrap appropriately. Finally, the JTextAreas now know their heights, so the JList can compute its height.

To laugh at my suffering see these forum threads:

Wednesday, November 21, 2007

Cruft and loathing in Java/Swing

Why am I writing a Swing app in 2007? That's what I'm asking myself. It's a conspiracy, I tell you, involving the dreaded secret society the FuglyPLAF Posse. Anyway, onward to more pissing and moaning...
Take the humble MouseEvent (alias java.awt.event.MouseEvent). The integer constants on MouseEvent include:
  • BUTTON1
  • BUTTON1_MASK
  • BUTTON1_DOWN_MASK
BUTTON1 is for use with the getButton() method, which (you'd think) might return the button being pressed when the event was generated. It actually does that on the Apple Java 5 JVM. On the Sun JVMs, apparently, getButton() returns the button "whose state has changed". If you're holding down a mouse button and dragging, getButton() lamely returns zero. Thanks for nothing.
BUTTON1_MASK is for use with getModifiers(). According to the JavaDocs, it's now recommended to use BUTTON1_DOWN_MASK which goes with getModifiersEx().
And, before you can say "Type-safe Enumeration", stop to ponder how many constants are needed to represent mouse buttons. How about none? Wouldn't a plain integer do just fine? How about a boolean function: event.buttonDown(int button)? OK, maybe it's useful to expose the bitmasking of the modifiers to the user, but the resulting mess argues that it would have been more useful not to.
Three different constants for a concept with all the complexity of mouse buttons? I sincerely hope someone was taken out and given an over-the-head turbo-wedgie for that one.