Archive

Posts Tagged ‘swing’

JSplitPainInTheAss: A less abominable fix for setDividerLocation

June 12th, 2011 9 comments

Update: Some discussion of this idea and code updates are in the gist here. Hubris.

If you use Swing much there will come a time when you’d like to create a splitter, aka JSplitPane. Overall, it works fine, but it has one lame, lame, lame feature; if you want to set the divider’s initial position proportionally, you’re screwed. I say feature, because the documentation for JSplitPane.setDividerLocation clearly states that it won’t work if the component hasn’t been “displayed” yet.

I bet that 95% of the time the programmer using this function is calling it at start-up before anything is displayed. Way to cover the 5% case there.

Anyway, you’ll go to Google and try to find a solution to this problem and you’ll find a lot of discussion and many variations on the same workaround. But they inevitably rely on sub-classing (isn’t that the solution to everything in Swing?) and hacking in some logic to fix things right before the splitter gets painted the first time. Nasty.

I’ve been thinking about this in terms of Seesaw, a Clojure/Swing API I’ve been working on. I want setDividerLocation to cover the 95% case and I don’t want to sub-class anything if I can help it. It’s not composable. So I thought about it a bit and came up with a much more elegant solution that works on a vanilla JSplitPane. Since I think this will be much more interesting to the Java Swing developers toiling away out there, I’ve coded it in pure Java. Here it is, a simple function:

If you can’t see any code, look here.

By the way, this code is insanely shorter in Clojure. Just sayin’.

The basic idea is to keep putting off the call to setDividerLocation using invokeLater until the splitter is realized on the screen. No fuss, no muss. … strike that. The invokeLater approach did indeed work, but under certain circumstances, a split pane that wasn’t displayed very soon would result in an infinite cascade of invokeLater events. The main problem with this is that the processor never gets to sleep, etc, etc. So I came up with an alternate approach which takes advantage of a HierarchyChange event to detect when the split pane is displayed (learn something new every day) optionally followed by a resize listener because the hierarchy event may arrive before the split pane’s been laid out by its parent. Swing’s exhausting.

Cheers!

Categories: java, swing Tags: , ,

Painting Widgets with Seesaw

June 10th, 2011 1 comment

The functionality describe here will be in the 1.0.7 release of Seesaw in the next week or so.

Say for some reason, you want a label with a red X on it. A bad, bad label. If you’re working with Swing in Java, you’ll create a new class, extend JLabel, override paintComponent, make some drawing calls, etc. Not terrible, but not that fun either. Something like this:

public class BadLabel extends JLabel {
  public BadLabel(String text) {
    super(text);
  }
  @Override
  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    final int w = getWidth();
    final int h = getHeight();
    final int d = 5;
    g.setColor(Color.RED);
    g.drawLine(d, d, w - d, h - d);
    g.drawLine(w-d, d, d, h - d); 
  }
}

That’s pretty minimal if we ignore imports and stuff. Enabling anti-aliasing would be nice. It’d also be nice to set the width of the lines. And if you wanted to draw the X on a button or something else, you could pull all the drawing code into a function, but you couldn’t avoid a new, possibly anonymous, class for each type of component you want to use it on.

In Seesaw, we use the paintable macro to do all the dirty work for us. Give it a function to handle the painting and a description of the widget you want, and you’re done. It handles anti-aliasing and there’s functions that make styling the lines a little less tedious.

Below is the equivalent Clojure code using Seesaw. Note that here we can easily apply the red X to a label or a button. Of course this also puts the widgets in a frame and displays them too. I’ll spare you that part in Java :)

The code for this example is also here.

(ns seesaw.examples.paintable
  (:use [seesaw core graphics]))

(defn draw-a-red-x 
  "Draw a red X on a widget with the given graphics context"
  [c g]
  (let [w          (width c)
        h          (height c)
        line-style (style :foreground "#FF0000" :stroke 3 :cap :round)
        d 5]
    (draw g
      (line d d (- w d) (- h d)) line-style
      (line (- w d) d d (- h d)) line-style)))

(defn content []
  (flow-panel 
    :border 5
    :items [
      (label           :text "I'm a good label!" :font "ARIAL-BOLD-40" :foreground "#00AA00")
      ; *** Make a label and button with a red X on them ***
      (paintable label :text "I'm a bad label!"  :font "ARIAL-BOLD-40" 
                              :paint draw-a-red-x)
      (paintable button :text "I'm a bad button!"  :font "ARIAL-BOLD-40"
                                :paint draw-a-red-x)]))

(defn -main [& args]
  (invoke-later
    (->
      (frame :title "Seesaw (paintable) example"
             :content (content))
      pack!
      show!)))

(paintable) is still a macro so it comes with all the limitations associated with them. Unfortunately with Swing you have to sub-class to paint on a widget and in Clojure you can’t (as far as I know) sub-class at run-time.

Categories: clojure, seesaw, swing Tags: , ,

A Brief Note on Seesaw Selectors

June 8th, 2011 No comments

Just wanted to note that I finally got around to a full implementation of CSS-style selectors in Seesaw, the Clojure UI library I’ve been working on. The updated doc for the (seesaw.core/select) function has most of the details, but here are a couple examples:

      ; All JLabels, no sub-classes allowed
      (select root [:<javax.swing.JLabel!>])

      ; All JSliders that are descendants of a JPanel with id foo
      (select root [:JPanel#foo :JSlider])

      ; All JSliders (and sub-classes) that are immediate children of a
      ; JPanel with id foo
      (select root [:JPanel#foo :> :<javax.swing.JSlider>])

To implement the selectors, I hacked up the zipper-based selector code in the very cool Enlive project. It was surprisingly straightforward:

  • Modify zipper construction to create a zipper over a Swing widget hierarchy instead of an HTML doc
  • Hack up the parser a little bit to allow my Java-class-matching extensions
  • Update the predicate sets to work on widgets instead of DOM nodes

Hats off to Christophe Grand for writing such a well-factored piece of Clojure code that I could get this working in an evening.

I’ll write more (like what this could be useful for) when it’s not the middle of the night.

p.s. this will be in the 1.0.6 release on Clojars sometime this week after I beat on it a bit more.

Categories: clojure, seesaw, swing Tags: , ,

Nice Weather for Some Clojure (UIs)

May 24th, 2011 9 comments

Summary: I tried build something slightly non-trivial with Seesaw and learned some things.


And so it came to pass that it seemed like I needed to start dog-fooding Seesaw a bit. I’ve been steadily adding features and building tiny toy apps to demonstrate them. Others (and others) have apparently found it useful, but I haven’t tried anything myself.

So, I decided to write Gaidica, a little app that displays weather info for user-selected cities. The whole process wasn’t terrible, but shows that there’s work to do. I’d guess about 4 hours of work including fixing Seesaw bugs as I hit them and at least an hour puzzling over Clojure XML stuff. Here’s a screenshot of the forecasts:

The code is all available on github.

Intro

Just to make sure we’re on the same page, Seesaw is an API/DSL for building user interfaces in Clojure. It happens to be built on Swing as you’ll see below, but please don’t judge it too harshly. I’m working hard to make it so you never need to know that Swing’s even there.

The Data

I decided to use Weather Underground’s XML API for the data. It provides the data in a simple format and as a bonus, includes icons for weather conditions, which saved me some work. Additionally, I got to do some XML wrangling in Clojure. Let’s just say that that deserves a blog post of its own.

In any case, I just point Clojure’s (xml/parse) function at the URL and get back some results. All this is done in a background thread using (future) to keep the UI responsive.

The Good

I mostly want to focus on the bad in this post, but despite any difficulties I had, building this was more enjoyable in Clojure+Seesaw than the equivalent Clojure+Swing or raw Java code. The lack of (proxy) and (reify) usage is testament to that.

Seesaw’s table support, seen in the webcam tab below, really paid off. Compared to the equivalent JTable-wrangling you’d have to do, it was downright dreamy.

Here’s the code to create that table:

(defn make-webcam-table [] 
  (table 
    :id :webcam-table 
    :model 
      [:columns 
        [{:key :handle :text "Name" } 
         :lat :lon 
         {:key :updated :text "Last Updated"}
         {:key :image :text "Image"}]]))

The Bad

I learned a lot during this exercise. One thing I noticed is that Swing is a pain in the ass. I think when you’re in the depths of a Java-based Swing app, you don’t even notice it because you’re already so mired in Java verbosity. It’s a form of Stockholm Syndrome I guess.

Most of the pain centered around, you guessed it, layout. I wanted to do it with the vanilla Swing layouts provided by Seesaw (border-panel, vertical-panel, etc), but after fiddling with component alignments and lame resize problems, I eventually surrendered and ran back to MigLayout. It’s powerful and pretty easy to use, but it’s yet another little language to learn. Luckily Seesaw already has Mig support.

Otherwise, most of the other issues I had were just missing functionality. Swing methods not yet wrapped by Seesaw. The nice thing is that since Seesaw’s a fairly thin wrapper, it’s easy to drop down to raw Swing when needed. Some random functionality that I’m going to address as a result:

  • Manipulation of the widget hierarchy needs to be seamless. This has historically been a problem in Swing (invalidate(); revalidate(); repaint()!!)
  • Widget styling. Sprinkling fonts and colors throughout the code is a hassle. The main question is whether I just add convenience options for this, or go whole hog and try for a CSS-style styling.
  • Saner defaults for “vanilla” layouts. I think I can tune some of the defaults and add some dials to make it less frustrating. It still really bugs me that I had to use Mig for something so simple.

Conclusion

From the perspective of a potential Seesaw user, already suspicious of Swing, all of this is a problem. I’ll take it as a challenge to continue making Seesaw a buffer between them and the occasional craziness of Swing. Feedback welcome!

Categories: clojure, seesaw, swing Tags: , ,

20 Minute File Explorer with Seesaw

April 30th, 2011 3 comments

Seesaw, my Clojure/Swing experiment has been making a lot of progress. Earlier today I added basic support for JTree which is kind of the last major Swing component that had no support in Seesaw. As an test, I thought I’d try making a file explorer with the directory tree on the left and file list on the right. It took about 20 minutes and was way less tedious than the equivalent raw Swing code you’d write in Java or Clojure.

The full code for the example can be found on github.

Here’s what it ended up looking like:

Code Highlights

There are three code highlights.

Set up a model for the tree using the same pattern as (cloure.core/tree-seq):

; Make a model for the directory tree
(def tree-model
  (simple-tree-model
    #(.isDirectory %)
    (fn [f] (filter #(.isDirectory %) (.listFiles f)))
    (File. ".")))

Set up the structure of the frame. I think this reads pretty nicely:

  (frame :title "File Explorer" :width 500 :height 500 :pack? false :content
    (border-panel :border 5 :hgap 5 :vgap 5
      :north (label :id :current-dir :text "Location")

      :center (left-right-split
                (scrollable (tree :id :tree :model tree-model :renderer render-file-item))
                (scrollable (listbox :id :list :renderer render-file-item)))

      :south (label :id :status :text "Ready")))

Set up selection event handling:

  (listen (select [:#tree]) :selection
    (fn [e]
      (if-let [dir (-> (selection e) first last)]
        (let [files (.listFiles dir)]
          (config (select [:#current-dir]) :text (.getAbsolutePath dir))
          (config (select [:#status]) :text (format "Ready (%d items)" (count files)))
          (config (select [:#list]) :model files))))))

Here, the widgets are being retrieved by id with the (select) function. Alternatively, I could have just bound them to variables and used them directly. I’m still thinking about how this should work.

Conclusion

This is obviously still a toy (no sorting, doesn’t update, etc, etc), but I think shows promise. I like the separation of the structure and behavior, but the selector stuff needs more thought. Probably the biggest gain over raw Swing is that Seesaw takes care of all the yucky reify and proxy stuff for you. In this example alone, a raw implementation would need two (reify)s and a (proxy) call. Building up the widget structure is also pretty tedious with raw Swing.

I’d love to to hear feedback and criticism of the API since this is my first attempt at real Clojure code. Thanks!

Use enum to define JTable columns

November 26th, 2008 No comments

Last week while tediously defining another Swing TableModel, I had a little epiphany. Typically, I’d define column headers, types, etc with a list of integer constants, and some arrays:

public class MyTableModel extends AbstractTableModel
{
    private final int NAME_COLUMN = 0;
    private final int VALUE_COLUMN = 1;
 
    private final String NAMES[] = { "Name", "Value" };
    private final Class CLASSES[] = { String.class, Double.class };

    . . .

   public String getColumnName(int columnIndex)
   {
      return NAMES[columnIndex];
   }

   . . .
}

This code is pretty tedious to maintain. In particular, switching column order involves a bunch of changes that are easy to get wrong. How about this instead… use an enum to define the columns!!

public class MyTableModel extends AbstractTableModel
{
    private static enum Columns
    {
        Name(String.class), Value(Double.class);

        final Class klass;

        Columns(Class klass)
        {
             this.klass = klass;
        }
    }

    . . .

   public int getColumnCount() { return Columns.values().length; }

   public Class getColumnClass(int columnIndex)
   {
      return Columns.values()[columnIndex].klass;
   }

   public String getColumnName(int columnIndex)
   {
      return Columns.values()[columnIndex].toString();
   }

   . . .
}

Now rearranging column order just works. Furthermore, you can add whatever column-specific functionality you like as methods on the enum. I think this approach can be generified with an interface for the enum to implement and a new abstract table model base class that can handle all the boilerplate above (getColumnCount(), getColumnName(), etc). When I get around to trying it out, I’ll post an update.

Late breaking: Of course, a quick search reveals I’m not the first person to think of this. Typical.

Categories: java, swing Tags: ,