Tuesday, September 23, 2008

JGrid : a folder-like view, enhanced with JXLayer

I have finish coding this since 3 weeks and still haven't found the time to write a post, so here we go : I have updated my "sexy" swing app with a component which is a mix of a JTable and a windows folder :
The size of the cells is dynamic, so for example if you move the slider at the bottom the content will grow or shrink :


It look best when you play with the zoom in real time, so download it and have a look! (see the links at the end of the post)

I was searching for a way to implement the selection by drag and drop with the mouse, and JXLayer come to the rescue. The API is very simple, but yet very powerful.
With help of JXLayer you can easily decorate your compound components and catch all Mouse, Keyboard and FocusEvent for all its subcomponents.
Basically, you wrap your component into a layer which allow you to draw above or under the component. The "getting started" example will learn you all you need to know to start using JXLayer (it's really very simple and quick to learn).
So I used a ListSelectionModel for the logic and JXLayer for the drawing and events management. The result look like this :

I have used one layer on the whole grid component, to draw the selection rectangle when the user drag and drop to select cell.
JXLayer keep it simple!!
I can see plenty of different usage for this library, like a lightbox or a heat map over the UI.

About the JGrid component :
  • it's working like a JList (both display a list of values ...)
  • you can set renderer to customize each cell
  • the code need to be improved a little :) but you can still get it from here (in the ilist.ui.generic.grid package). There still some little problem with selection too.
  • it's scrollable, so you can embed it in a JScrollPane
Wow, big update. As pointed out in the comments,  it's possible to have the same thing with a simple JList :


JList grid = new JList(model); grid.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
// layout as a grid :
grid.setLayoutOrientation(JList.HORIZONTAL_WRAP);
grid.setVisibleRowCount(-1);
// cell size :
grid.setFixedCellHeight(100);
grid.setFixedCellWidth(100);
grid.setCellRenderer(myRenderer);

And if I wrap it with a JXLayer, i can have the "drag and drop" effect :


import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;

import javax.swing.JList;
import javax.swing.SwingUtilities;

import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.AbstractLayerUI;


public class GridLayerUI extends AbstractLayerUI {

private Point startDrag, endDrag;


@Override
protected void processMouseEvent(MouseEvent e, JXLayer l) {
super.processMouseEvent(e, l);

if (e.getID() == MouseEvent.MOUSE_PRESSED)
startDrag = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), l);

else if (e.getID() == MouseEvent.MOUSE_RELEASED) {
endDrag = null;
startDrag = null;
setDirty(true);
}
}

@Override
protected void processMouseMotionEvent(MouseEvent e, JXLayer l) {
super.processMouseMotionEvent(e, l);

if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
endDrag = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), l);
if (startDrag != null) {
l.getView().getSelectionModel().clearSelection();
Rectangle dragZone = new Rectangle(Math.min(startDrag.x, endDrag.x), Math.min(startDrag.y, endDrag.y), Math.abs(endDrag.x - startDrag.x), Math.abs(endDrag.y - startDrag.y));
for (int i = 0; i < l.getView().getModel().getSize(); i++) { if (l.getView().getCellBounds(i, i).intersects(dragZone)) l.getView().getSelectionModel().addSelectionInterval(i, i); } } setDirty(true); } } @Override protected void paintLayer(Graphics2D g2, JXLayer l) {
super.paintLayer(g2, l);
if (endDrag != null && startDrag != null) {
// draw the selection rectangle when user drag & drop on the grid :
g2.setPaint(new Color(137, 177, 237, 128));
g2.fillRoundRect(Math.min(startDrag.x, endDrag.x), Math.min(startDrag.y, endDrag.y), Math.abs(endDrag.x - startDrag.x), Math.abs(endDrag.y - startDrag.y), 2, 2);
g2.setColor(new Color(19, 106, 197));
g2.drawRoundRect(Math.min(startDrag.x, endDrag.x), Math.min(startDrag.y, endDrag.y), Math.abs(endDrag.x - startDrag.x), Math.abs(endDrag.y - startDrag.y), 2, 2);
}
}
}


JXLayer gridLayer = new JXLayer(myList);
gridLayer.setUI(new GridLayerUI());
JScrollPane scrollPane = new JScrollPane(gridLayer);

The only problem is that by default JList select a cell in the list when you drag the mouse ... so you loose the selection done inside the layer :( The only solution I found is :


grid.putClientProperty("List.isFileList", Boolean.TRUE); // shame on me

which remove this feature. Yes it's a hack, and I'm currently searching for a best way to do this (may be by filtering the events with JXLayer ?).

About the "fake" application (see post #1 and post #2) :
  • the breadcrumb/search field/menu list/back and previous buttons work *cough*almost*cough*
  • the "new" button open a translucent panel, and also if you double click on a cell
  • there is a little bit of Vista, Mac OS and others things ... have to work on homogeneity :)
  • you can donwload it here (java -classpath ilist.jar;examples.jar;Filters.jar;jna.jar;plug-engine.jar;jxlayer.jar ilist.MainPlugin to start it, yes the jar is not runnable :( )
  • download sources here

13 comments:

Laird Nelson said...

Hello; love your blog.

Can you explain how your JGrid component is different from a JList set to horizontal style with a custom renderer?

Christophe said...

hello Laird,

you are right, it look like setLayoutOrientation with HORIZONTAL_WRAP ( http://java.sun.com/javase/6/docs/api/javax/swing/JList.html#setLayoutOrientation(int) ) do the same thing ... I didn't know it was possible :(
The only missing feature is the ability to select cell with drag and drop like in a folder. It should be possible to do this by wrapping a JList in a JXLayer ...

Laird Nelson said...

Thanks. (I too love JXLayer; it really is a tremendously useful tool.)

Please keep up the good work and good writing.

Cheers,
Laird

Christophe said...

thx for your support :)

btw, I'm currently trying to rewrite it with a JList wraped in a JXLayer.

Christophe said...

It's done !
I have updated the post/app/sources.

Ravi said...

That was some fantastic swingy work. Bravo.
I ran against a little glitch. I am on WinXP with Java 6. But the borders are not coming rounded. A little white is visible at the corners of the app. Do you know, why that may happen?

Christophe said...

Hello Ravi,

you're right, I have forgot to activate the translucency on the main frame.

(in the constructor of MainFrame, "WindowUtils.setWindowTransparent(this, true);" is in comment ...)

technomage said...

Here's an option for decorating drop targets on a given Swing component:

http://rabbit-hole.blogspot.com/2006/04/decoratingoverpainting-swing.html

It doesn't require any changes in your component hierarchy.

Source is available at furbelow.sf.net.

Anonymous said...

Is this source available via a BSD license? (I will be happy to give credit to Christophe in the About view)? If so, could you update the source file with the BSD license (available at http://en.wikipedia.org/wiki/BSD_license)

Christophe said...

hello,

I have updated the sources with a BSD licence (http://www.opensource.org/licenses/bsd-license.php ).
You can download it at http://sites.google.com/site/christophe/files-1/iList-sources.rar?attredirects=0

Anonymous said...

Thanks! I'll post a link to the GUI when it's done (about 1.5 months) :). Your design looks great!

Mario said...

Ok - 1.5 months took a bit longer :) Take a look at your contribution to http://www.rockyourphone.com (Independent iPhone App Store). Download the desktop client and enjoy :). Great work.

Christophe said...

Good job Mario !
Congratulation for you application !

Post a Comment