The Source for Java Technology Collaboration
User: Password:



Scott Violet's Blog

Community: JavaDesktop Archives


On Look and Feels

Posted by zixle on January 25, 2007 at 03:05 PM | Permalink | Comments (60)

For the past couple of releases we've been focusing significantly on the system look and feels (Windows XP/Vista and GTK). Sure, we updated the Java look and feel in 1.5, but that was more of redocorating the bathroom rather then remodeling the house. Our focus on system look and feels was in response to the cries of customers and the community screaming that the system look and feel matters, and is in fact is critical! After all, who wants an app that doesn't fit in with the desktop?

Yes, who does? Well, I suspect many folks actually don't care! That's right, I'm questioning whether the system look and feels matter as much as we've been lead to believe. But I'm getting ahead of myself.

Here's the set of apps I run day in and day out on my Windows box: Firefox, Thunderbird, NetBeans, Gaim, Total Commander, Yahoo! Widgets and Light Room. What's interesting about this set of apps is that I suspect Total Commander is the only one that directly uses the system toolkit. That's just a suspicion though, I don't confidently know what Total Commander is running, all I can say about Total Commander is that it doesn't look very much like an XP app. Total Commander is a better fit for DOS;) I use Total Commander as it extremely customizable and you can do everything from the keyboard; a power users dream for file management. That it isn't the prettiest app doesn't bother me (well, ok, if given the choice between two functionally equivalent versions of the app, I'll choose the prettier).

Adobe's Light Room is a wonderful app, but it looks nothing like the rest of the desktop. Does that bother me? No! I use Light Room for a particular task, and that it doesn't look much like the desktop in no way bothers me.

We also have a Mac at home. Surely the Mac must be a paragon of consistency, right? Well, take a look at any of Apple's pro-apps, such as Aperture or Final Cut Pro. These apps definitely have an Apple'ish look to them, but they don't have the exact same look as the rest of the apps on the desktop.

And lets not forget the web apps that are out there. Gmail is great for occasional email, but it too doesn't look anything like a desktop app. The same is true of most AJAX and Flash apps.

So, what gives? Does the system look and feel matter? Could it be that the cries we were hearing were primarily the result of our dated Java look and feel? And the knee jerk reaction from the community was to demand the system look and feel? I have to wonder that if we had kept the Java look and feel modern and fresh (not Ocean), would we have heard such a demand for the system look and feel?

I suppose you could take my argument one step further, that the look of the app doesn't really matter that much to the end user. What does matter is whether they are able to complete the task they need to get done with it in a timely manner. Anything on top of that is just icing.

While I'm not sold on apps needing to look consistent on the desktop, having a consistent feel, in terms of keyboard accessibility, is critical to me. Can you imagine not being able to use control-c (on windows) for copy? Or what about something other than alt-F4 for closing windows. That would drive me batty in a heart beat.

Now for the controversial (yes, it gets better). Is it time we take some of the energy we've been putting in the system look and feels and channel it into a stellar cross platform look and feel? Even if it came at the expense of fixing more off by one pixel problems in the system look and feels for a release?

Please don't take this train of thought as an indication that we're decommitting from the system look and feels. That couldn't be further from the truth; the same number of folks continue to fix bugs for both XP and GTK as has for the last release (well, except for occasional help from an unlikely source)! I'm posting this blog to stir up a discussion on the merits of the system look and feels as compared to cross platform look and feels.

    -Scott

Extreme GUI Makeover: 2006

Posted by zixle on November 21, 2006 at 07:42 AM | Permalink | Comments (25)

During one of many brainstorming sessions for the 2005 JavaOne conference the Swing team latched on to the idea of doing a makeover talk. Borrowing the idea from the popular TV show, the idea was to makeover an ugly ducking of a Swing app, turning it into a beautiful swan. Shannon, Romain and myself had a great time putting the app together and doing the session; as a bonus, the session was one of the most popular talks. WOW! Because we had so much fun, and because of the popularity, we decided to do a similar session for 2006.

For the 2006 session we decided to make over a mail client. A good chunk of my day goes into reading email, so I had a plethora of ideas in this area. This was true of Romain and Shannon as well. We didn't have the time to craft a real mail client, or the time to come up to speed on one of the many open source mail clients out there. As such, we put together a shell of a mail app. It has just the amount of functionality we needed for the demo, that's it. We tuned for demos, with just the windows look and feel. Romain says it works on OS X, which is surprising. There is plenty of stuff that doesn't work, and you can get stuck in areas where you can't get out. It's most defintely a DEMO! Let me say that one more time, THIS IS A DEMO! None-the-less we've gotten a slew of email requests for the code, some friendly, some not so friendly. So, in all it's glory, here's the code.

Here's a screen shot of the before:

And the after, with a traditional table:

A screen shot with a rich list:

The screen shots don't do the transformation justice; they can't show transitions, animation, custom drag and drop effects... For that, you need to run the app. You can run the app by downloanding this zip file, extracting it, and invoking java -jar mailman.jar on the jar file.

All 103 slides from the session can be found here.

A big part of the presentation was showing the ugly, then transitioning to the improved. To provide a similar experience for those trying the app now, I scripted the whole app with Robot. If you don't move the mouse, the demo will drive itself. It'll take you through each of the stages showing the before and after. I'll go over how this was done in more detail later. For those that want to know now, look in the appscripter package.

Wouldn't it be cool if I did voice overs? Or music? Or provided links to the code while running it? Or fixed some bugs in the message handling? Or added comments? Yes, yes and YES! The list of things I would like to do to this app could keep me employed for years. But, I know folks want the source, so we're cutting the cord. Have at it! I do promise Shannon, Romain and myself will go over aspects of the session in more detail over time.

Enjoy!

     -Scott

PS For those thinking a webstart link would be ideal. I agree. For JavaOne I threw a 5MB mailbox at the original parsing code, and it took minutes. I rewrote it using NIO, which means it won't work from WebStart. Hans pointed this out months ago. Not having time myself, I dropped the gauntlet and challenged Hans to give me the code that would suck down the mailboxes so that it would work from WebStart. Months later that code isn't in my mail box. Perhaps the public humiliation will be enough to get Hans to give me the code and I'll update the app later on;)

Cut, Copy and Paste

Posted by zixle on August 07, 2006 at 09:27 PM | Permalink | Comments (6)

After a long hiatus I'm returning to a series of blogs on architecting applications. This time around I'm covering a simple way to provide rich cut, copy and paste behavior in an application.

As usual, for those wishing to cut to the chase, here's the app:

Many Swing components provide cut, copy and paste support. For example, JTextField supports cut, copy and paste out the box. This is hardly revolutionary; todays developers expect such behavior out of the box.

Swing's cut, copy and paste support is provided by javax.swing.TransferHandler. TransferHandler provides Actions for cut, copy and paste. Unfortunately the enabled state of the actions is not updated based on context, and the actions target the source of the event. In other words, the actions provided by TransferHandler are not appropriate for use on menus or toolbars.

While the Actions provided by TransferHandler have limited usage, the remaining portions of TransferHandler are imminently more useful (even more so in 1.6). Rather than reinvent the wheel, I'll provide new Actions that target TransferHandler.

Here's the list of requirements for cut, copy and paste actions:

  • The actions should track the permanent focus owner. For example, you would not want the copy action to target a menu when it has focus.
  • The paste action should only be enabled if the clipboard has a data flavor supported by the component.
  • The cut and copy actions need to update based on the selection of the component. For example, if a text field has focus, the cut and copy actions should only be enabled if the selection is not empty. *
Ideally it would be easy to enable this support in an existing application with minimal fuss. To that end a helper class, CutCopyPasteHelper, with all static methods is used. The static methods operate on any JComponent and do not look for specific interface in determining cut, copy and paste support.

Targetting the focused component is easily done with the KeyboardFocusManager. The KeyboardFocusManager maintains a bound property for the permanent focus owner. To listen for changes one need only install a PropertyChangeListener. The following code illustrates this:

  PropertyChangeListener focusListener = new PropertyChangeListener {
    public void propertyChange(PropertyChangeEvent e) {
      if (e.getPropertyName() == "permanentFocusOwner") {
        // The permanent focus owner has changed.
        newPermanentFocusOwner((Component)e.getNewValue());
      }
    }
  };
  KeyboardFocusManager.getCurrentKeyboardFocusManager().
      addPropertyChangeListener(focusListener);
The paste action is the trickiest. The paste action is enabled if the paste action is enabled on the focus component, and a data flavor on the clipboard matches the one supplied for the focused component. Tracking the set of data flavors on the clipboard is easily done using a FlavorListener. The following code illustrates this:
  FlavorListener flavorListener = new FlavorListener {
    public void flavorsChanged(FlavorEvent e) {
      flavorsChanged();
    }
  };
  Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
  clipboard.addFlavorListener(flavorListener);
The set of data flavors a component supports for paste must be provided by the developer. This is done with the following method:
  CutCopyPasteHelper.registerDataFlavors(component,  DataFlavor...dataFlavors);
In addition to setting the set of flavors, you must explicitly enable the ability for a component to paste. This is done with the setPasteEnabled method. Seperating the set of flavors from the enable state makes it easier to operate on each independently. For example, once you've registered the flavors, if you subsequently need to change the paste enabled state you need only invoke setPasteEnabled.

Once you've enabled paste on the component and registered the set of flavors, CutCopyPasteHelper has all the information needed to update the paste action appropriately for you.

The cut and copy actions are simpler. These are updated based on the focused component, and whether or not you've enabled cut and copy. For example, a text field with an empty selection would disable the cut and copy actions. On the other hand, a text field with a non-empty selection would enable the cut and copy actions. CutCopyPasteHelper provides the setCutEnabled and setCopyEnabled methods for this.

Many Swing components provide keyboard bindings that target the actions provided by TransferHandler. CutCopyPasteHelper provides replacements for these actions. For a component to use CutCopyPasteHelper actions you must change the bindings registered on each component to target CutCopyPasteHelper. This is done using the ActionMap and InputMap. CutCopyPasteHelper provides the registerCutCopyPasteBindings that takes care of this.

Here's a cheat sheet for using these actions with a component:

  1. Provide a TransferHandler for the component.
  2. Replace the bindings provided by Swing using the registerCutCopyPasteBindings method.
  3. Register the set of data flavors supported during paste by way of the registerDataFlavors method.
  4. Install a listener on the component that updates the cut and copy state based on the component's selection. The cut and copy state is changed using the setCutEnabled and setCopyEnabled methods.
Here's the relevant portions of the code for the password store app that enable cut, copy and paste on entryList (a JList):
  // Step 1
  // Install the TransferHandler.
  entryList.setTransferHandler(new ListTransferHandler());

  // Step 2
  // Register key bindings that target the cut, copy, and paste actions
  // provided by CutCopyPasteHelper.
  CutCopyPasteHelper.registerCutCopyPasteBindings(entryList, true);

  // Step 3
  // Register the data flavors on the list:
  CutCopyPasteHelper.registerDataFlavors(entryList,
                PASSWORD_ENTRY_DATA_FLAVOR);
  // Enable paste
  CutCopyPasteHelper.setPasteEnabled(entryList, true);

  // Step 4
  // Install listener to update cut/copy state based on selection
  ListSelectionListener selectionListener = new ListSelectionListener {
    public void valueChanged(ListSelectionEvent e) {
      if (!e.getValueIsAdjusting()) {
        boolean hasSelection = (entryList.getMinSelectionIndex() != -1);
        CutCopyPasteHelper.setCopyEnabled(entryList, hasSelection);
        CutCopyPasteHelper.setCutEnabled(entryList, hasSelection);
      }
    }
  };
  entryList.addListSelectionListener(new ListSelectionHandler());
To use the actions provided by CutCopyPasteHelper in a menu is no different than any other action, invoke setAction or use a constructor that takes an Action. Please note that the name, accelerator, mnemonic, and icon are not provided. It is expected you'll set these directly on the menu or button using the action.

* Selection

You'll notice these actions target the focused component. Targetting the focused component is problematic if you need another component to get focus while firing the action. For example, if a button wired to the cut action gets focus, the cut action targets the button; this is not what you want. This is trivially fixed by invoking setFocusable(false) on the button, but that's a stop-gap. The larger issue is how to determine the target of these actions. In nearly all cases targetting the permanent focus owner is what you want, but there are exceptions. This is a larger issue, to which many folks have proposed big solutions. It's something that needs more study, and has been bothering both Hans and myself...

The Sandbox

For security reasons a sandboxed app can't get at the system clipboard. This is most definitely a major pain! I've primarily concerned myself with apps that have full access to the clipboard. I suspect there are some things that could be done to make support of these actions better when in the sandbox. That's for another day though.

Early on I mentioned I wanted it to be trivial to wire these actions up to existing components. I've done the work for the text components. As this blog is now gargantuan in size, I'll save that for a later blog. In the mean time, here's the latest source for the password store app, and source for fabric. For those that look at the source, you'll see a bunch of stuff I haven't gotten to. That's for a later day as well... Oh, and yes, CutCopyPasteHelper will eventually makes it's way into Swing in some form or another.

    -Scott

BeanShell + 2D = Instant Graphics

Posted by zixle on July 11, 2006 at 03:45 PM | Permalink | Comments (17)

Over the years I've worked on a number of projects that involved various 2D rendering operations. The usual cycle for such work is to tweak rendering code, compile, run, examine the results using a magnifier, and repeat until I'm happy with it. This certainly works, but takes a bit longer than is ideal. When I needed to do a lot of graphics tweaking for this years 'Extreme GUI Makeover' talk, I figured it was time to see if I could streamline this process. This blog shows what I've concocted.

I was looking for something that would allow me to get rid of the usual compile and run cycle; I want instant feedback! More often than not I need to zoom in on the results. As such, a magnifier would have to be included. In the end I want Java code, so alternative scripting languages aren't an option.

Thankfully I stumbled upon Bean Shell. BeanShell fit my needs perfectly! With BeanShell you can evaluate blocks of Java code; just what I was looking for. Evaluating raw Java code, for my purposes, is hardly interesting with out the ability to supply state to the interpeter. BeanShell allows you to supply state to the java code it executes. In this way I can pass a graphics object to the interpeter that is available to the java code that is executed. Putting all the pieces together enables me to execute arbitrary Java code, rendering the results to an Image and showing that Image using Swing components. Just what I wanted.

Here's the resulting application. For reasons I did not investigate BeanShell won't run in the sandbox. As such, the application is signed and needs to live outside the sandbox. Also note the application stores the script in the file ~/.igeScript.

How does it work?

As you type in the text area a timer is started. When the timer fires (~1 second) the script is evaluated in a background thread using BeanShell. The intepreter is passed three variables: the width of the image, the height of the image, and a graphics obtained from an image of the specified size. If the interpeter successfully evaluates the script, the image is rendered in two places. One shows the results at actual size, the other scaled.

One of the first things you'll notice is the text area does no completion for you. This is most definitely annoying! Ideally the editor would behave just like that of NetBeans. As such, a better fit would be to turn this into a NetBeans module. That way NetBeans would provide all the completion, syntax highlighting, and error reporting for free. Nice! If I have time, I'll do this; but don't hold your breath;)

In my first take at the app I evaluated the script nearly immediately, and on the EDT. This worked perfectly, until I typed in something like:

  while (x < y) {
  }
As I was evaluating the script almost immediately, on the EDT, this caused an infinite loop and I was screwed. This very rarely comes in every day coding as you don't compile and run while typing. Not the case here.

I was hoping BeanShell would allow me to stop the intepreter, but it doesn't. To solve this problem I moved evaluating the script to a separate thread. If the evaluation takes more than than 3 seconds, an option pane is shown asking if the background thread should be killed. To kill the background thread I use Thread.stop. This is obviously bad, and can cause problems, but is better than causing the app to be wedged...

Running the app

Try changing a couple of the variables at the top of the file to see the results. For example, try changing TEXT_COLOR to Color.RED and notice the effects render immediately. Trying changing the KERNEL_SIZE and BLUR_FACTOR to see the results on the drop shadow.

You can of course delete all the code and start a new.

Random other things

The app uses Hans's MultiSplitPane to house the components. This makes it easy to resize the app to fit your needs.

I'm also using an early prototype of beans binding to keep the model in sync with the width, height, scale and text area. I'm still working on the legal issues of releasing this code. I'll do a blog once it's all straightened out.

I'm using a variant of the application framework I had been developing in my blog series on architecting applications. I will return to the application framework series, but will stop development of the pieces that overlap with Hans's JSR 296: Swing appliction framework.

Full source can be found here.

    -Scott

Modern Heap View

Posted by zixle on June 19, 2006 at 06:25 AM | Permalink | Comments (16)

As part of brain storming on future ideas one of the visual designers did a mockup of NetBeans that included a more modern looking heap view. I was rather taken by it and decided to do an implementation, hence this blog. I had to cut a few corners, and features, to avoid the view consuming too much memory. As usual, for those that don't have the stomach to wade through a blog the following shows the heap view in action.

Source can be found here.

For those using NetBeans 5.0 I've created a module with this heap view in it. The module is here. To install this in NetBeans click on Tools -> Module Manager, then click on the 'Update' button, choose the 'Install Manually Downloaded Modules' and follow the instructions. Here's the source for the module.

Dissecting the View

The view consists of the following:
Small Border
A background gradient.
A grid with each cell having a slight gradient.
Vertical lines representing the amount of the heap being used at a particular time. The ticks are also drawn with a gradient.
A grid on top of the ticks.
Textual description showing amount of heap being used, with a drop shadow.
Yes, gradients galore;)

You can toggle a couple of options by right clicking.

Gradients

The code for rendering a gradient is pretty simple, pick the end points, colors, invoke setPaint, and fill in the region:
  Paint gradient = new GradientPaint(0, 0, color1, width, height, color2);
  g2.setPaint(gradient);
  g2.fillRect(0, 0, width, height);
It's worth mentioning a couple of new gradients have recently been added to 1.6. I wanted this code to work with 1.4.2, so I restricted myself to GradientPaint. For details on the new gradient classes look to Chris's blog.

The vitues of caching

A good heap view shouldn't accumulate much garbage on any given paint, otherwise when the heap view is running you'll be able to watch the amount of garbage continually grow and shrink even when you're not doing anything. Believe me, my first implementations did this and it wasn't pretty;) To avoid the garbage I ended up creating a bunch of images and doing image copies from the images. Turns out this generates very little garbage and is much faster; all around goodness.

If you look at the source you'll see a number of images. There is one for the border, background gradient and tiles, another for the tick gradient, and two for the text (I'll get to that in second).

The drop shadow

If you look closely at the text you can see a slight drop shadow. To create a drop shadow requires a bit of the more obscure 2D operations. The general algorithm is:
  1. Render what ever it is you want to create the drop shadow of to an image.
  2. Render that image to another image using a BufferedImageOp.
  3. Rerender step 1 to the second image.
Here's what this looks like in terms of creating a drop shadow for text.
  BufferedImage textImage = ... ;
  BufferedImage dropShadowImage = ...;

  // Step 1:
  Graphics2D textImageG = textImage.createGraphics();
  textImageG.drawString(...);
  textImageG.dispose();

  // Step 2:
  Graphics2D dropShadowG = dropShadowImage.createGraphics();
  dropShadowG.drawImage(textImage, bufferedImageOp, shiftX, shiftY);

  // Step 3:
  drawShadowG.drawString(...);
  drawShadowG.dispose();
So, what is bufferedImageOp? For a drop shadow effect it's a ConvolveOp. Here's the code:
  int kw = KERNEL_SIZE, kh = KERNEL_SIZE;
  float blurFactor = BLUR_FACTOR;
  float[] kernelData = new float[kw * kh];
  for (int i = 0; i < kernelData.length; i++) {
    kernelData[i] = blurFactor;
  }
  bufferedImageOp = new ConvolveOp(new Kernel(kw, kh, kernelData));
As I said, a bit obscure. You can play with the KERNEL_SIZE and BLUR_FACTORY to get different effects, I've used 3 and .1 respectively.

As part of this years Extreme GUI talk I wrote an app that lets you interactively experiment with these values and see the effects. I'll blog on that later. I know, I know, what about the architecture series? I will return to it! And yes, this years extreme GUI code will be released. And yes, I've been a bad BAD boy! Bad Scott, bad Scott!

Sorry, back to this blog.

Variations

I experimented with a handful of variations until I arrived at this one. You can try a couple of them by right clicking on the heap view and choosing one of the options. And just as with the heap view in NetBeans, a single click triggers a GC (I didn't add the flashing effect, sorry).

I suspect I could have spent weeks fine tuning (I'm aware of at least one other optimization I could have done), adding more features, refining the look, more effects (yes, there is one effect buried in the implementation and I played with more) ... But, I figured it best to push out what's there and move on to the next thing.

So, that's it. What do you think?

    -Scott



Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds