Persistence for Small Visual Bean Editor

The next step in the life of my small visual bean editor is to think about persistence. Right now, I don't have any file type associated with my scene. It's simply a scene on which you can drag and drop items from the palette. However, when I restart the application, I see this:

In other words, the widgets are persisted across restarts. But how is this possible if there's no file type associated with the scene? Well, I have set the new @ConvertAsJavaBean annotation on the bean that defines the widget. (In other words, that bean is my domain object.) That enables the generation of a ".settings" file for each widget dropped into the scene. And, because I have a property change listener on the bean, whenever a new property change is fired on my bean, the ".settings" file for the changed widget is automatically updated!

Here's the ".settings" files (in build/testuserdir/config/EditorComponents) for the scene that you see above:

And, even if my scene were to be associated with a particular file type (which I may still add), I would STILL use the above approach so that the user would be able to see the last state of the widgets.

Here's the sources of this project, feel free to join and also please feel free to contribute your own code too:

http://kenai.com/projects/org-simple-editor

So, how to end up with the above scenario? Here is the general approach:

  1. Mark your domain object (i.e., the bean describing the widget) with @ConvertAsJavaBean. Make sure to also add property change listener support to the bean, with each setter firing a property change.

  2. Whenever a widget is dropped, you need to ensure that a ".settings" file is created. So, in the scene's AcceptProvider, within the "accept" method, I have this try/catch block that does the trick:
    try {
        FileObject editorComponentsFolder = FileUtil.getConfigFile("EditorComponents");
        if (editorComponentsFolder == null) {
            FileUtil.getConfigRoot().createFolder("EditorComponents");
        }
        InstanceDataObject.create(
                DataFolder.findFolder(
                FileUtil.getConfigFile("EditorComponents")),
                component.getType() + "-" + System.currentTimeMillis(), 
                component, null, true);
    } catch (IOException ex) {
        Exceptions.printStackTrace(ex);
    }

    What's "component" above? That's what I get from the transferable after the drag/drop from the palette is complete—an instance of the bean defining my widget.

    Note that I create a unique name for my ".settings" file, by adding a timestamp to the type of the widget. That, in turn, is determined by the palette item, which is built from the layer file.

  3. So, at this stage, the ".settings" file exists. How are these files loaded into the scene at restart? The TopComponent that holds my scene implements a LookupListener. This is what happens when the TopComponent opens:
    @Override
    public void componentOpened() {
        res = Lookups.forPath("EditorComponents").lookupResult(EditorComponent.class);
        res.addLookupListener(this);
        resultChanged(new LookupEvent(res));
    }

    So, we look in the System FileSystem, into the "EditorComponents" folder, while indicating an interest in the "EditorComponent" class (which is the bean that defines the widget). And this is what the "resultChanged" method looks like:

    @Override
    public void resultChanged(LookupEvent le) {
        scene = new EditorComponentGraphScene(res.allInstances());
        myView = scene.createView();
        widgetScrollPane.setViewportView(myView);
    }

    And then, in the scene, we process the "res.allInstances()" like this:

    for (EditorComponent component : coll) {
        Widget w = attachNodeWidget(new EditorComponentWrapper(component));
        Point point = new Point();
        point.setLocation(component.getX(), component.getY());
        w.setPreferredLocation(widget.convertLocalToScene(point));
        w.setPreferredSize(new Dimension(component.getWidth(), component.getHeight()));
    }

    Now we have a widget in the scene for each item we found in the "EditorComponents" folder.

  4. Next, whenever the user changes something (e.g., resizes or moves a widget), we need to let the NetBeans Platform recreate the ".settings" file. Here, for example, is the relevant part of the "movementFinished" method in my "MoveStrategyProvider" (which implements both "MoveStrategy" and "MoveProvider"):
    component.setX(widget.getPreferredLocation().x);
    component.setY(widget.getPreferredLocation().y);

    Hence, when the move is complete, we update the bean with the current X and current Y of the widget, which fires a property change in the bean, which in turn results in the ".settings" file being updated.

  5. The trickiest part was figuring out how to update the JComponent (which represents the JavaBean being edited) within the bean that defines the widget. For this, I created my own BeanNode, so that I could listen to the changes of the JComponent that is wrapped within the BeanNode:
    public class MyBeanNode extends BeanNode implements PropertyChangeListener {
    
        EditorComponent editorComponent;
    
        public MyBeanNode(EditorComponent editorComponent) throws Exception {
            super(editorComponent.getComponent(), Children.LEAF);
            this.editorComponent = editorComponent;
            editorComponent.getComponent().addPropertyChangeListener(this);
        }
    
        @Override
        public void propertyChange(final PropertyChangeEvent e) {
            editorComponent.fireChange(e.getPropertyName(), e.getOldValue(), e.getNewValue());
        }
    
    }

    The firing of the change on the bean that defines the widget is what is necessary for the ".settings" file to be updated with the current data of the JComponent wrapped by the BeanNode.

And that's all. It's relatively simple and now I can restore my scene across restarts, without needing to think about associating a file type with my scene.

Comments:

Geertjan, have you found any documentation about the "convertLocalToScene" and "convertSceneToLocal" ?

I assume that when using any of the widget.setPreferredXYZ we need to use Scene coordinates but how did you know you had to transfer the coordinates in the

Point point = new Point();
point.setLocation(component.getX(), component.getY());

? I have to admit it confuses me a little as it is not clearly defined for me when what is in "local" and when in "scene" coordinates.

Thanx - I ask since that mixture could be the reasons for my coordinates trouble with my MultiResizeProvider http://www.netbeans.org/issues/show_bug.cgi?id=173997 and http://forums.netbeans.org/topic18097.html

Thanx again - this is just great stuff and fun to work with.

Bernd

Posted by Bernd Ruehlicke on October 08, 2009 at 11:46 PM PDT #

Hi Bernd,

The view/scene/local coordinates are described at:

http://bits.netbeans.org/dev/javadoc/org-netbeans-api-visual/org/netbeans/api/visual/widget/doc-files/documentation.html#CoordinationSystem

Basically Widget is working with local-coords.

Scene-coords are used e.g. when you want to compare locations of two independent Widgets in a scene.

View-coords are related to the JComponent top-left origin. This is usually used when you cooperate with other Swing-components.

Most of the WidgetAction including MoveStrategy are working with local-coords of the related Widget.

Here is their relation:
view <-> scene <-> parent-parent-Widget-local - parent-Widget-local <-> Widget-local

Regards,
David

Posted by David Kaspar on October 09, 2009 at 01:07 AM PDT #

Thanx David. If you run the test app (added as Nb project "ResizeProblem" in http://www.netbeans.org/issues/show_bug.cgi?id=173997 you will see that the location coordinates of the widget get mixed up after a Resize using upper left control point. I am using a default Resize Strategy and Provider just overwriting them to print out coordinates. (see also attached video of how to trigger this)

I will try to debug into the Visual API to see what is going on - but have not had any success yet.

Still - wonderful library, I use it wherever I can.

Posted by Bernd Ruehlicke on October 11, 2009 at 10:46 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
12
13
14
24
25
26
27
28
29
30
   
       
Today