Modifying Selection Properties of a JTree, JList or JTable

I like to write programs for those times that I come up with something elegant. What I really mean by that is when I learn something I didn’t really know before. Here’s today’s challenge:

You have a very long JTree, and a need to select several things in that JTree (This could be a JTable, JList, etc.). The idea here is that if you make a mistake and click on something you will erase all your previous selections. So, you want to change the default behavior of click-select.

The why is simple: it’s a royal pain to remember to hold down the control key. Plus being as paranoid as I am about data entry I would constantly scroll up and down the list before clicking accept – just to make sure everything I needed was properly selected.

I kept telling myself how nice it would be to just click to toggle the selected status instead of having to control-click. To have toggling as the default behavior.

There are a few ways to do this, but the best way I could come up with was to handle the mouse click itself. JTree, JList, and JTable all have methods for determining which item is being clicked based on the getX() and getY() methods from the mouse event. So I declared a mouse click handler for my JTree (list, whatever). This is what it looks like:


private void specialClickSelectionHandler(java.awt.event.MouseEvent evt)
{
if((evt.getButton() == evt.BUTTON2) || evt.getButton() == evt.BUTTON3)
{
TreePath tmp = tagview.getClosestPathForLocation(evt.getX(), evt.getY());
if (tagview.isPathSelected(tmp))
{
tagview.removeSelectionPath(tmp);
}
else
{
tagview.addSelectionPath(tmp);
} // if it's currently selected
} // if it's a mouse2 or mouse3 event.

return;
}

Now, when the user right clicks, it will toggle the selection.

Digging around I found some similar code for tables and lists on Sun’s Swing Archive, but keep in mind the code there uses isPopupTrigger(), and therefore needs to be modified for cross-platform use (since isPopupTrigger() works differently on different systems – see javadoc).

You could also use this technique to change the default action of the left mouse button, which might be a better idea. I don’t want any chance of mistakenly pressing left click and accidentally removing selections off-screen where I won’t see them dissapear. Well, there’s one headache gone. Now I can relax a bit more while entering the thousands of Chinese words I need into my database 🙂

Kongzi can now import CC-CEDICT

Yes, Kongzi Beta-5 can now import data from CEDICT (or CC-CEDICT). It has no problem loading up each of the over 100,000 entries and making them editable, searchable, and quizzable. Yet, there is a problem. It turns out that there is so much non-chinese information (such as korean characters, just as an example) and many unsupported (ancient?) characters, and the definitions so unwieldly – so many mistakes and missing pieces of information – that I don’t feel using CC-CEDICT is a good idea.

Looks like I’ll have to just keep adding characters one by one. It will take a while but beginners would only be interested in the first few hundred anyways, so I have my work cut out for me.

I suspect things would be different if there was any sort of organizational data in CC-CEDICT to help draw only on what would be useful, but due to several of the project’s stated rules (such as not creating a separate entry for parts of speech – a huge mistake in and of itself) and not including any frequency data or grading data (another huge mistake where learners are concerned) CC-CEDICT is actually quite useless for learners compared to better organized dictionaries or lists of words (such as the BLI or CYY lists, or the ones used to make Far East’s 3000 Learner’s Dictionary). In that respect you probably wouldn’t want to use CC-CEDICT. I’ll leave the import feature in, however, since you never know who may wish to use it for whatever purpose.

In other news:

kongzi-b5-mnmquiz

As you can see I’ve recovered the Mix ‘n Match quiz feature. The Multiple choice quiz feature was also recovered. Some cosmetic changes will come later, of course, but all the logic is there. Some of the logic is even more advanced than previously. You will notice that at least one of the answers (if not more than one) is highly similar to the one being presented. For example, we include the character for seven, and the phonetics for eight and nine, when the proper answer is three. And so forth. In Mix ‘n Match, we include no, when the correct answer is yes. And so forth. It makes for a more difficult (and therefore more interesting/useful) quiz. Note also that you can’t use similar terms to help you narrow down an answer. The logic will also include head fakes (see the SI prefixes above or the numbers included with yes/no below). You really have to know the answer, no guessing allowed!

You’ll also notice in the above that pinyin tone numbers and tone marks have been mixed. This is because I haven’t finished entering the shortcuts in. That’s fine, the program always recognizes tone numbers (and will always recognize both interchangeably once I finish the shortcuts).

kongzi-b5-mcquiz

The program is stable enough that I’ve started adding characters again (in order to have fun using it, and beta test it while doing so, of course!) In it’s current state it is very useable, however there are a number of things I’d like to do before releasing it and there are still a few bugs and things I haven’t hammered out yet. I’m still annoyed that I lost the memory game quiz. That was fun and amazing. Although now, the problem of word size has been solved by the implication it will select only from words of the same size (as such are organized by tags, providing a convenient solution). When I do reimplement it, I will cause it to toss in characters with the same radical (or which differ only by a radical) to help the user learn to visualize and discern between characters. I have high hopes for that feature.

I’d like to have at least 1,000 characters in the dictionary before I start public beta testing. One important organization is the grading scheme (A, B, ss, or Jia, Yi, Bing, Ding, etc.) used by some government testing programs, HSK grading, and that sort of thing. Which brings me to frequency representation. What a headache! I could easily include three different items for frequency and have them all be different. Instead of causing needless headaches, or lying to the user that only one number is accurate, I think that the best way to use ‘frequency data’ will be to allow the user to import frequency order lists from a file. That way if the user decides to switch from one order to another it would be an easy thing to do. This way I also save myself from having to write special code to determine which particular frequency order they were using. Brilliant!

Now, back to adding characters to the dictionary…

Populating Trees and Lists in Java

I’d like to comment on what went into the new Font Chooser dialog for Kongzi Beta-5. The problem can be posed as follows:

Create a dialog box that takes, as input, existing data.
Complication: Selecting a value on some lists will change the contents of the others.

Obviously this sort of design pattern would have important usages; configuration boxes, persistant user profiles, and so forth. Or, for example, a specialized Font Chooser – which is the example I’ll discuss. Take a look at this screenshot from Kongzi Beta-4:

Kongzi Beta-4 Japanese Fonts

I remember how fiendishly difficult this was to code the first time around, so the second time around I was prepared. The problem would be that I had to follow a special order: populate the language box, select the language, then use that event to populate the font box. But then I had to interrupt the normal order of things and populate the font box before I selected the font from pre-existing data. I couldn’t think of a way to do that without cutting and pasting code or creating a very convoluted logic.

But worrying about that before I wrote any code would drive me insane. So I plunged in head first, fleshing out the dialog box in Netbeans, then adding several methods to find and select values in the list, such as “SelectLanguage(String)”. That way whenever I wanted to, I could populate the dialog box with pre-existing data, in this case, the language and font data that had been selected previously. This is important, of course, to provide a smooth user experience.

Then the nasty null pointer errors started. Basically because of the logic/flow problem I described above. The logic for populating from pre-existing data, and the logic to modify itself based on user input, was too difficult to reconcile. When I tried to populate language, the size box wouldn’t be populated yet, so I couldn’t preview sample text for the new language. If I tried to populate the size box first, this would also trigger a preview event which would crash. The workaround in Kongzi Beta-4 was to include a preview button as shown in the above screenshot. Then I was technically excused from having to do anything. But just because I put a button on it doesn’t mean it wasn’t a giant kludge. For Beta-5 I wanted something smoother. I wanted to use the same logic at all times, the same entry points, and I didn’t want a preview button. I wanted selection events to populate everything.

The key to my final solution was in realizing that when I sent commands to the lists, such as fontList.setSelectedIndex(i), I was going to trigger events which would send me back into the auto-population and auto-selection methods. This is how it had to be, to avoid cutting and pasting code, making a terrible mess. Yes, I wanted to follow “accepted practices” of reusing the same code which auto-populated the lists to accept events and propagate their own data to other lists. Doesn’t everyone?

The first idea I had was checking for null pointers and then skipping over those lines of code. I rejected this almost immediately because of the difficulty it would present to doing any sort of error checking. I would in essence be assuming that any time I encountered a null pointer, I knew why it was there; and that was simply untrue. To write proper code, I would then have to write a second check to determine if the null pointer was there because there was a bug, or simply because the dialog wasn’t fully populated yet.

Or, as an aside, populated properly. I saw the potential for sloppy logic to start creeping in, caused by my abuse of checking for null pointers. In horror, I imagined several cases where entire blocks of code would never execute because they would always be called at a time when null pointers were there. I imagined terrifying cases where a population function would be called four, five, or more times, each time getting sent back with a null pointer, until the dialog was finally ready to accept the input. I recoiled from checking for null pointers, and resolved to find a better way.”

After staring into the monitor for a while, I came up with the idea of using boolean flags to keep track of what procedure I was in. The solution worked like a charm. I created several class-wide variables such as “boolean populatingFontList”. Any time a method dialog was performing an operation on a list where the selection would change as an unintended consequence, I simply set “updatingFontList” or “updatingSelectLanguage” (or whatever) to true. Then, at the beginning of that function (and any other appropriate place) I would put in a clause which prevented anything bad from happening. Usually something like “if (updatingFonts) return;” at the beginning of a troublesome function.

populatingSelectLanguage = true;
selectLanguage.removeAllItems();
populatingSelectLanguage = false;

Then when the program fires off it’s little events as a result of you removing whatever item was selected, you wont get an error because the font list isn’t populated yet. And so forth.