The question of the day comes from Vadim, who asks on the NetBeans Platform mailing list: "Looking for example showing how to add Widget to Scene, e.g. by toolbar button click."
Well, the solution is very similar to this blog entry, where you see a solution provided by Jesse Glick for VisiTrend in Boston: https://blogs.oracle.com/geertjan/entry/zoom_capability
Other relevant articles to read are as follows:
- http://netbeans.dzone.com/news/which-netbeans-platform-action
- http://netbeans.dzone.com/how-to-make-context-sensitive-actions
Let's go through it step by step, with this result in the end, a solution involving 4 classes split (optionally, since a central feature of the NetBeans Platform is modularity) across multiple modules:
The Customer object has a "name" field, with getters, setters, and a constructor, while the Droppable capability has a method "doDrop" which takes a Customer object:
public interface Droppable { void doDrop(Customer c); }
In the TopComponent, we use "TopComponent.associateLookup" to publish an instance of "Droppable", which creates a new LabelWidget and adds it to the Scene in the TopComponent. Here's the TopComponent constructor:
public CustomerCanvasTopComponent() { initComponents(); setName(Bundle.CTL_CustomerCanvasTopComponent()); setToolTipText(Bundle.HINT_CustomerCanvasTopComponent()); final Scene scene = new Scene(); final LayerWidget layerWidget = new LayerWidget(scene); Droppable d = new Droppable(){ @Override public void doDrop(Customer c) { LabelWidget customerWidget = new LabelWidget(scene, c.getTitle()); customerWidget.getActions().addAction(ActionFactory.createMoveAction()); layerWidget.addChild(customerWidget); scene.validate(); } }; scene.addChild(layerWidget); jScrollPane1.setViewportView(scene.createView()); associateLookup(Lookups.singleton(d)); }
The Action is displayed in the toolbar and is enabled only if a Droppable is currently in the Lookup:
@ActionID( category = "Tools", id = "org.customer.controler.AddCustomerAction") @ActionRegistration( iconBase = "org/customer/controler/icon.png", displayName = "#AddCustomerAction") @ActionReferences({ @ActionReference(path = "Toolbars/File", position = 300) }) @NbBundle.Messages("AddCustomerAction=Add Customer") public final class AddCustomerAction implements ActionListener { private final Droppable context; public AddCustomerAction(Droppable droppable) { this.context = droppable; } @Override public void actionPerformed(ActionEvent ev) { NotifyDescriptor.InputLine inputLine = new NotifyDescriptor.InputLine("Name:", "Data Entry"); Object result = DialogDisplayer.getDefault().notify(inputLine); if (result == NotifyDescriptor.OK_OPTION) { Customer customer = new Customer(inputLine.getInputText()); context.doDrop(customer); } } }
Therefore, when the Properties window, for example, is selected, the Action will be disabled because the Properties window does not publish an instance of the Droppable capability. (See the Zoomable example referred to in the link above for another example of this.) As you can see above, when the Action is invoked, a Droppable must be available (otherwise the Action would not have been enabled). The Droppable is obtained in the Action and a new Customer object is passed to its "doDrop" method.
The above in pictures, take note of the enablement of the toolbar button with the red dot, on the extreme left of the toolbar in the screenshots below:
The above shows the JButton is only enabled if the relevant TopComponent is active and, when the Action is invoked, the user can enter a name, after which a new LabelWidget is created in the Scene.
Now imagine that we have a different TopComponent (or a Node or anything that implements Lookup.Provider) that also should let the user drop something on itself. All we need to do is publish a new instance of Droppable from that TopComponent... and then the Action defined above will automatically also be enabled when that TopComponent is selected, but this time the result will be different, depending on however we've implemented the Droppable. In other words, one single Action can now do multiple different things, depending on the Droppable currently available in the Lookup.
The source code of the above is here, note that I used a recent 7.3 development build, so you may need to change the dependencies after you download the code, otherwise compilation will fail because of incorrect dependency version numbers:
Note: Showing this as an MVC example is slightly misleading because, depending on which model object ("Customer" or "Droppable") you're looking at, the V and the C are different. From the point of view of "Customer", the TopComponent is the View, while the Action is the Controler, since it determines when the M is displayed. However, from the point of view of "Droppable", the TopComponent is the Controler, since it determines when the Action, i.e., which is in this case the View, displays the presence of the M.