Tuesday, April 30, 2013

Workrave

I tend to spend too much time at the computer, particularly if I'm wrapped up in writing (or, more likely, debugging) a piece of code. My hardware setup is probably not going to win any ergonomic awards, although I do have an ergonomic keyboard and a trackball with a wrist rest, and I often wear a brace on my right (trackball-side) wrist. (I've had episodes of carpal tunnel syndrome in the past.) Frankly, though, it's not really repetitive strain injuries that worry me the most. It's a combination of computer vision syndrome (too much staring at the monitor) and lard-ass nerd syndrome (too much sitting).

So, much as I hate being nagged, I decided to look around for a break timer that (a) wasn't too intrusive or annoying, (b) would automatically reset itself (or could be easily reset) when I took an unscheduled break (such as getting myself a cup of coffee, or recycling a previously consumed cup of coffee) and (c) preferably came with some suggestions for good at-the-desk exercises. There are quite a few break timers out there (both standalone and browser plug-in), but after trying one or two that didn't thrill me, I stumbled upon Workrave.

Workrave is open-source, available for both Windows and Linux (both Gnome and KDE). I did not see a download option for Macs, and I don't know if the Linux edition will run on a Mac. For users of Ubuntu and related Linux distributions, Workrave is available via Synaptic from the Canonical repositories (subject to the usual problem of being one version behind the current one). There appear to be translations of Workrave for a number of languages.

Although I can't find any documentation for Workrave to speak of (they have a FAQ and a user mailing list), it is fairly intuitive to use. Once launched, it parks itself in the system tray, where a right-click brings up a menu allowing you to set preferences, change mode, suspend it (useful if I'm in the middle of some high-priority task where I cannot afford to be interrupted) and so forth. It provides three separately configurable timers. The shortest cycle timer periodically urges you to take a "micro break" (rest for just a few seconds). The medium cycle timer nags you to take an extended break (how extended is configurable), and optionally displays some exercises (with instructions and illustrations) that you can do at your desk, without causing coworkers to report you to the authorities. (In a small bit of irony, Workrave bugged me to take an exercise break just as I finished typing the previous sentence.) The longest cycle timer is for your work day: when that timer goes off, Workrave will tell you to pack it up and go home (or at least do some non-computer work for the rest of the day).

By default, Workrave monitors your keyboard (and mouse?) activity, so if you stop providing input for more than a few seconds Workrave will recognize that you're on a break and reset accordingly. There is a "reading" mode that you can select, which I assume runs the timers regardless of input. There's also a network configuration option, whose purpose eludes me. (I really don't want my network connection taking breaks.)

All told, Workrave is a very well done program with a very nice user interface. It strikes what I think is just the right balance between flexability and ease of use. If you have RSI issues, you definitely should take a look at it; but even if not, I recommend it to anyone who spends extended periods of time parked in front of a computer.

Sunday, April 21, 2013

The G Graphics Library

For a Java program I'm writing (which I think I've mentioned/groused about elsewhere), I need to draw a few layered network diagrams (essentially binary trees turned on their sides), with a bit of customization. Part of that customization is that I want edges to run horizontally or vertically only (with right angle bends as needed), and I want to control layout (spacing). The Swing graphical toolkit provides everything necessary to paint on the screen, but I'm reluctant to spend the necessary time learning the ins and outs of drawing/painting with Swing. So I did some searching for libraries that would sit atop Swing and either provide mechanisms specifically for drawing graphs and networks, or at least provide a "higher level" drawing interface.

I have used JUNG with good success in the past for other applications, but neither JUNG nor JGraph (a similar library) seemed to give me the layout control I wanted (or, if they did, I could not figure out how). Skipping over various other blind alleys, which had me wondering whether I would spend more time looking for a library than it would take to master Swing, I eventually found G, described by its authors as
a generic graphics library built on top of Java 2D in order to make scene graph oriented 2D graphics available to client applications in a high level, easy to use way.
Note the phrase "easy to use", which (along with layout control) was my Holy Grail.

G, which is released under the GNU Lesser General Public License (LGPL), is a bit sparse on documentation. There is no user manual that I can find, and the Javadoc documentation, while comprehensive, does not provide as much descriptive material as a beginner might want. There are, however, some two dozen demonstration programs, which allowed me to infer much (though not all) of what I needed to know. The rest came from trial and error.

Once the basics are mastered, G does indeed provide a fairly easy to use toolkit for building two-dimensional graphics. There are some design decisions which may or may not suit the user. As one example, G provides support for dynamically panning and zooming scenes (graphs), but zooming does not change font sizes; text remains at its original size while the surrounding drawing elements expand or contract.

Bundled with G are two other classes, again documented only by their Javadoc (and by invocations in the sample programs).  The Geometry class provides various utility methods for geometric calculations, including factory methods for generating arrows, circles, ellipses, rectangles, circular or elliptical sectors, and stars. The Matrix4x4 class provides support for various mathematical operations on images, including scaling, rotation and translation (shifting).

The current version of G was released in December of 2009, and I see no indication that development is continuing, nor did I see any links to support forums or bug trackers. I think that I did encounter a bug, but found my own workaround. The factory methods in the Geometry class typically have two or more variants, with at least one using "device' coordinates (directly corresponding to pixels on the output device) and at least one using "world view" coordinates (using an arbitrary set of coordinates established by the user for his or her virtual canvas). The convention in G is that integers are device coordinates and double precision values are world coordinates. I had no problem using world coordinates to generate rectangles and stars, but my attempt to generate circles using world coordinates resulted in clipped ellipses with at least a few lines passing through the center. The workaround was to take the center and one point on the perimeter of the circle (for convenience, chosen directly left, right, up or down from the center), transform them from world to device coordinates, get the radius in device coordinates by subtracting, and then use the device-coordinates version of the factory method to generate the circle.

All that said, I now feel fairly comfortable using G, and I appreciate the work done by its authors and their releasing it open-source.

Saturday, April 13, 2013

Reusing Context Menus in Swing (Java)

I just spent hours (hours I can ill afford at my age!) resolving a very frustrating and in my opinion obscure problem with a graphical user interface I'm building using Swing (Java 7). My interface has a bunch of lists scattered around, using a customized class (extending Swing's JList) that includes tool tips for individual items in the list, pop up context menus (JPopupMenu), and tool tips for the menu items in the context menus. In one such list, everything worked: item tool tips displayed, a right click popped up the context menu, and hovering on a context menu displayed its tool tip. Everywhere else, item tool tips displayed and context menus popped up (and their actions correctly fired if I clicked them), but the bleeping menu tool tips were nowhere to be seen.

I'll skip the long and painful history of blind alleys I went down. (Somewhere, some Google employee is desperately trying to cool down a server I overheated during my search.) I ultimately realized that the meaningful distinction between the one list that worked and the many that did not had nothing to do with the lists themselves, nor with their parent containers. The one list that worked properly had a context menu that was only used in that one place. All the other lists popped up one of a few menus that could be invoked from more than one place.

My understanding is that you cannot add the same Swing component to more than one container, but that's not the issue with instances of JPopupMenu. You don't actually add the context menu to more than one component; you just invoke it (set it visible and position it) from more than one place. The fact that the same menu correctly displayed in more than one place, and the menu item actions correctly fired from each place, seems to support that. So I'm baffled why menu item tool tips appear if there is only one location that listens for the pop-up action -- right-click on most (but not all?) systems -- but fail to appear if more than one location listens for the pop-up action. Is this an "undocumented feature" of Swing?

The solution was to change my customized JList class so that, rather than using the original JPopupMenu, it uses a deep copy of the menu. The only thing multiple copies of the menu share is the action method. In case it will help anyone, here is my code for making the clone menu.

  /**
   * Clone a popup menu (deep copy).
   * @param m the menu to clone
   * @return the clone
   */
  private static JPopupMenu cloneMenu(JPopupMenu m) {
    if (m == null) {
      return null;
    }
    JPopupMenu menu = new JPopupMenu();
    for (Component i : m.getComponents()) {
      if (i instanceof JMenuItem) {
        JMenuItem item = new JMenuItem();
        JMenuItem old = (JMenuItem) i;
        item.setText(old.getText());
        item.setToolTipText(old.getToolTipText());
        item.setMnemonic(old.getMnemonic());
        for (ActionListener a : old.getActionListeners()) {
          item.addActionListener(a);
        }
        menu.add(item);
      }
    }
    return menu;
  }

I should note that I only copied the bits I use (text, tool tip, mnemonic and action listener). If you use other bits (accelerators, for instance), you'll need to copy those as well.

There's one other little piece of the puzzle. Since clones of a given menu all use the same action listeners, you need to give the action listeners a way of knowing which clone invoked them. Here's my tweak to a prototype action listener:

private void someMenuItemActionPerformed(java.awt.event.ActionEvent evt) {                                                          
  JPopupMenu m = (JPopupMenu) ((Component) evt.getSource()).getParent();
  // m.getInvoker() is the component on which the context menu was invoked
  // TODO add your handling code here:
} 

In my case, m.getInvoker() will be an instance of my modified JList class.

Saturday, April 6, 2013

Watching Swing Text Fields for Changes

As I mentioned earlier, I'm currently beating my head against a wall (or several walls) writing a graphical user interface (GUI) for a Java program, using Swing. There are certain dialogs in which I want the user to fill in text fields. In some cases the field content should be a string, with no domain restriction. In other cases the content needs to be a positive integer between specified limits. Either way, I want to listen for changes as they happen.

Listening for changes seems to be a fairly common goal, for a variety of reasons. My motivation is that the inputs are optional (the user is specifying properties of a filter), and each text input is matched with a check box indicating whether or not that particular property should be included in the filter. Although my program is a desktop application, I've filled out a number of similar forms on the web, many of which had what I consider to be a desirable feature: as soon as you start typing something valid in the text field, the check box is automatically selected. I want to do that in my dialog.

I spent considerable time searching for solutions, finding more questions than answers, but I did eventually come up with something that works, which I'll share here. Let's start with listening for changes, which proved to be the stickier bit. The advice that I found for JTextField  consistent involved listening for changes to the value property of the field. Unfortunately, I got a bunch of events where value supposedly changed even though nothing had been typed into the field. The field has oldValue and newValue properties, and it seemed intuitive to me to check for newValue != oldValue, but most of the events I saw returned newValue == null. The problem has to do with when changes are "committed". Hitting the Enter key after typing in the field commits the change, but typing itself does not, nor does typing and then changing focus by tabbing or clicking elsewhere.

The first key is to use JFormattedTextField rather than JTextField. That also buys you the ability to validate inputs and force the user to type approved characters. Just switching to JFormattedTextField is not enough, though. The field requires a formatter factory, and the default factories apparently do not automatically commit changes as soon as they are validated. So I ended up creating my own factory methods for generating formatter factories that immediately commit valid changes. Here's my code:

import java.text.NumberFormat;
import javax.swing.text.DefaultFormatter;
import javax.swing.text.DefaultFormatterFactory;
import org.jdesktop.swingx.text.NumberFormatExt;
import org.jdesktop.swingx.text.StrictNumberFormatter;

/**
 * FieldFormatter provides a factory method to provide formatter factories for
 * formatted text fields with input limits. The fields allow blank/null entries,
 * and commit immediately upon valid changes.
 * @author Paul A. Rubin <rubin@msu.edu>
 */
public class FieldFormatter {
  
  /**
   * Factory method to generate a formatter factory for integer inputs.
   * @param digits the maximum number of digits to allow (minimum is 0)
   * @param min the minimum legal value
   * @param max the maximum legal value
   * @return a formatter factory
   */
  public static DefaultFormatterFactory integerFormatter(int digits,
                                                         int min, int max) {
    NumberFormatExt f = new NumberFormatExt(NumberFormat.getIntegerInstance());
    f.setParseIntegerOnly(true);
    f.setMaximumIntegerDigits(3);
    f.setMinimumIntegerDigits(0);
    StrictNumberFormatter fmt = new StrictNumberFormatter(f);
    fmt.setAllowsInvalid(true);
    fmt.setCommitsOnValidEdit(true);
    fmt.setMinimum(min);
    fmt.setMaximum(max);
    return new DefaultFormatterFactory(fmt);
  }
  
  /**
   * Factory method to generate a formatter factory for arbitrary string inputs.
   * @return a formatter factory
   */
  public static DefaultFormatterFactory stringFormatter() {
    DefaultFormatter fmt = new DefaultFormatter();
    fmt.setCommitsOnValidEdit(true);
    fmt.setAllowsInvalid(true);
    return new DefaultFormatterFactory(fmt);
  } 
}

A few notes about the code:
  • For the integer fields, I used the NumberFormatExt and StrictNumberFormatter classes from SwingX in order to implement domain restrictions (integer only, maximum and minimum number of digits, upper and lower domain limits). Since the string fields had no domain restrictions, I did not need any SwingX classes for them.
  • The setCommitsOnValidEdit method is the key to getting notifications as soon as the user types something valid in the field.
  • I want to allow the user to delete an entry in a field and leave it empty. That requires setAllowsInvalid(true); otherwise, if the user selects and deletes the field content and then exits the field, the deleted content is automatically restored, at least for the integer fields. (I'm not sure I need it for the string fields, but better safe than sorry.)
Now all you have to do is attach a property change listener to the JFormattedTextField that looks something like the following:
private void listen(ProperChangeEvent evt) {
  if (evt.getPropertyName().equals("value")) {
    // do something
  }
}

Friday, April 5, 2013

Impressions of Netvibes

A couple of weeks ago I wrote about my search for an alternative to Google Reader, which eventually led me to Netvibes. Having used it a fair bit, I think I'm ready to share my reactions.

Desktop


I'm very comfortable using Netvibes on my PC. My subscription is to the free service. There's a fee-based premium service that I think is targeted at commercial users, but the free version is fine for me. On the desktop, I use it in Firefox, but I tested it with Chrome and the interface unsurprisingly seems to be identical.

You can login the old fashioned way (email address and password) or via Facebook. I'd like to see a general OpenID login option, or at least buttons to log in through Google+ and/or Twitter, but that's a quibble. As with many browser-based applications, once logged in I stay logged in for extended periods, provided I load the page periodically. (I assume this is refreshing a cookie. Cookie-averse users may need to authenticate more regularly.) 

As I previously mentioned, import of my Google Reader feeds and folders was easy. I showed a couple of pictures of the interface in my previous post, so I won't repeat those. Here are some things I like about the desktop interface.
  • You can show all new posts, posts from uncategorized feeds, posts from all feeds in a particular category, or posts from just one feed.
  • You can show all posts (ones you've read using a "faded" font) or just new posts.
  • You can read the post (or at least the initial portion of it, depending on the feed) inside Netvibes with a single click, or open the source document in a new browser window/tab with a single click. (The former marks the post as read, but the latter does not.)
  • You can mark a single post or a subset of the displayed posts read by checking them off (one click per post) and then clicking a button (so n+1 clicks to mark n posts). You can also mark all displayed posts read with two clicks.
  • Similarly, you can mark one, some or all posts as unread, with the same number of clicks. This is not something I do often, but occasionally I do mark a forum post unread so that I will come back to it.

Tablet


I also use Netvibes on a 10" Android 4.0 (Ice Cream Sandwich) tablet. Netvibes currently does not have any native mobile applications, but for a tool designed to browse online posts, I'm quite happy to work within a web browser. On any mobile device, you point your browser at mobile.netvibes.com; the server detects your device type and apparently customizes the interface accordingly. The interface is almost the same as the desktop interface, so I'll just point out the differences.
  • In the "reader view" (which is what I use exclusively; I can't recall if the "widget view" is available on the tablet), a menu of categories appears on the left and posts for the category you have selected appear on the right. The right-hand pane has a border at the top identifying the category. On the mobile version, this sometimes does not update when you switch categories: you see the posts for the new category but the header for the previous one. This is not exactly a high priority bug.
  • As best I can tell, there is no option in the mobile interface to mark a post unread. This is problematic for me for two reasons. First, my hand-eye coordination being what it is, I sometimes tap the wrong subject line and read a post that I intended to leave for later. Second, when I'm screening posts from sources (weeding out what I consider to be the "chaff"), I may have to read a post to determine that it is in fact "wheat" (something I want to keep for later). Both the mobile and desktop versions provide a "Read later" category, and it is easy to add a post I've just read into that category. "Read later" is not the same as keeping the post unread in the original category, though. The original categorization is lost when the post goes into "Read later", and in any case "out of sight is out of mind".
  • The option to select a subset of the displayed posts and mark them read does not seem to exist in the mobile version (at least on Android). I can mark all displayed posts read (two taps total), or read them one at a time (two taps per post). Again, when I'm looking at a forum, there are entire threads that I want to skip. On the mobile platform, that's 2n taps to get rid of n posts.
  • There's a built-in menu to share a post. It has only three options: email, Facebook and Twitter. The desktop version has the same three options, but on the desktop I have no use for them, particularly as I never share anything to Facebook. On the desktop, it's one click to open the original source, then one click with the HootSuite hootlet to share to Twitter, one click of the Google+ bookmarklet to share to Google+, or two clicks to use the Firefox "email link" feature. Similarly, if I have Netvibes open in the default Android browser, it's one tap to load the original source document and then two taps to access the browser's sharing menu. That said, I have to give Netvibes a thumbs-up for the Twitter option on Android. Like other applications, if I select the Twitter option it gives me a choice of opening the tweet in any of the installed web browsers or in the HootSuite Android app (which I have installed). Unlike every other application I've used, where that last option silently fails, Netvibes really does open the tweet in HootSuite.

So, to summarize, I'm quite comfortable using Netvibes on the desktop and fairly comfortable using it on Android. I'd be quite happy on Android if they would add an unread option and a way to mark a selected subset of posts read.