| <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> |
| |
| <html xmlns="http://www.w3.org/1999/xhtml"> |
| |
| <head> |
| |
| <title>Writing POV-Ray Support for NetBeans IX—Build Support</title> |
| |
| <link rel="stylesheet" type="text/css" href="https://netbeans.org/netbeans.css"/> |
| <meta name="AUDIENCE" content="NBUSER"/> |
| <meta name="TYPE" content="ARTICLE"/> |
| <meta name="EXPIRES" content="N"/> |
| <meta name="developer" content="geertjan.wielenga@oracle.com"/> |
| <meta name="indexed" content="y"/> |
| <meta name="description" |
| content="NetBeans POV-Ray Support Tutorial Part IX—Adding Project Build Support and Some Finishing Touches"/> |
| <!-- Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. --> |
| <!-- Use is subject to license terms.--> |
| |
| </head> |
| |
| <body> |
| |
| <h1>Writing POV-Ray Support for NetBeans IX—Build Support</h1> |
| |
| <p>This is a continuation of the tutorial for building a POV-Ray rendering application on |
| the NetBeans Platform. If you have not read the <a href="nbm-povray-1.html">first</a>, |
| <a href="nbm-povray-2.html">second</a>, <a href="nbm-povray-3.html">third</a>, |
| <a href="nbm-povray-4.html">fourth</a>, <a href="nbm-povray-5.html">fifth</a> |
| <a href="nbm-povray-6.html">sixth</a>, <a href="nbm-povray-7.html">seventh</a>, |
| and <a href="nbm-povray-8.html">eighth</a> |
| parts of this tutorial, you may want to start there.</p> |
| |
| <h2 class="tutorial"><a name="spit-and-polish"></a>Project Action Support</h2> |
| |
| <p>You may recall that early on, when we were writing <code>PovrayProject</code>, |
| we stubbed out the implementation of <code>PovrayProject.ActionsProviderImpl</code>—so |
| in fact the build, clean and run actions are all disabled when a POV-Ray |
| project is active. We should quickly implement them.</p> |
| |
| <p>Open <code>PovrayProject</code> in the code editor, and implement |
| the methods of <code>ActionProviderImpl</code> as follows:</p> |
| |
| <pre class="examplecode">private final class ActionProviderImpl implements ActionProvider { |
| |
| @Override |
| public String[] getSupportedActions() { |
| return new String[]{ |
| ActionProvider.COMMAND_BUILD, |
| ActionProvider.COMMAND_CLEAN, |
| ActionProvider.COMMAND_COMPILE_SINGLE |
| }; |
| } |
| |
| @Override |
| public void invokeAction(String string, Lookup lookup) throws IllegalArgumentException { |
| int idx = Arrays.asList(getSupportedActions()).indexOf(string); |
| switch (idx) { |
| case 0: //build |
| final RendererService ren = (RendererService) getLookup().lookup(RendererService.class); |
| RequestProcessor.getDefault().post(new Runnable() { |
| @Override |
| public void run() { |
| FileObject image = ren.render(); |
| //If we succeeded, try to open the image |
| if (image != null) { |
| DataObject dob; |
| try { |
| dob = DataObject.find(image); |
| OpenCookie open = (OpenCookie) dob.getNodeDelegate().getLookup().lookup(OpenCookie.class); |
| if (open != null) { |
| open.open(); |
| } |
| } catch (DataObjectNotFoundException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| } |
| }); |
| break; |
| case 1: //clean |
| FileObject fob = getImagesFolder(false); |
| if (fob != null) { |
| DataFolder fld = DataFolder.findFolder(fob); |
| for (Enumeration en = fld.children(); en.hasMoreElements();) { |
| DataObject ob = (DataObject) en.nextElement(); |
| try { |
| ob.delete(); |
| } catch (IOException ioe) { |
| Exceptions.printStackTrace(ioe); |
| } |
| } |
| } |
| break; |
| case 2: //compile-single |
| final DataObject ob = (DataObject) lookup.lookup(DataObject.class); |
| if (ob != null) { |
| final RendererService ren1 = (RendererService) getLookup().lookup(RendererService.class); |
| RequestProcessor.getDefault().post(new Runnable() { |
| @Override |
| public void run() { |
| if (ob.isValid()) { //Could theoretically change before we run |
| ren1.render(ob.getPrimaryFile()); |
| } |
| } |
| }); |
| } |
| break; |
| default: |
| throw new IllegalArgumentException(string); |
| } |
| } |
| |
| @Override |
| public boolean isActionEnabled(String string, Lookup lookup) throws IllegalArgumentException { |
| int idx = Arrays.asList(getSupportedActions()).indexOf(string); |
| boolean result; |
| switch (idx) { |
| case 0: //build |
| result = true; |
| break; |
| case 1: //clean |
| result = getImagesFolder(false) != null |
| && getImagesFolder(false).getChildren().length > 0; |
| break; |
| case 2: //compile-single |
| DataObject ob = (DataObject) lookup.lookup(DataObject.class); |
| if (ob != null) { |
| FileObject file = ob.getPrimaryFile(); |
| result = "text/x-povray".equals(file.getMIMEType()); |
| } else { |
| result = false; |
| } |
| break; |
| default: |
| result = false; |
| } |
| return result; |
| } |
| |
| }</pre> |
| |
| |
| </div> |
| |
| <h2 class="tutorial"><a name="ensure-main-file"></a>Ensuring There Is A Main File</h2> |
| |
| <p>We now have handling implemented for all of the standard project actions that make |
| sense for a POV-Ray project. Now, if you've been following very carefully, |
| you may have noticed that there is one bug we need to fix: |
| <code>isActionEnabled()</code> will always return true for build. But we |
| implemented the following code in <code>Povray.getFileToRender()</code>:</p> |
| |
| <pre class="examplecode">render = provider.getMainFile(); |
| if (render == null) { |
| ProjectInformation info = (ProjectInformation) proj.getLookup().lookup(ProjectInformation.class); |
| //XXX let the user choose |
| throw new IOException(NbBundle.getMessage(Povray.class, "MSG_NoMainFile", info.getDisplayName())); |
| }</pre> |
| |
| <p>So if there is no main file set for a project, the build action will be |
| enabled, but if it is invoked, it will throw an exception! The simple |
| choice would be to test if there is a main file, and if not, disable the |
| build action—but this would be rather non-intuitive to the user who |
| might not be able to figure out what is wrong with the project. |
| And we would miss an opportunity to explore the Explorer & Property Sheet API, |
| together with the Dialogs APIs!</p> |
| |
| <p>So instead, we will post a dialog which will allow the user to choose |
| which file should be the main file, if none is set when build is called:</p> |
| |
| <div class="indent"> |
| |
| <ol> |
| |
| <li>On the Povray Project module, |
| following the procedure outlined in previous sections of this tutorial, |
| add a module dependency on the Explorer & |
| Property Sheet API.</li> |
| |
| <li>Right-click the <code>org.netbeans.examples.modules.povproject</code> |
| package and chose New > Other > Swing GUI Forms > JPanel Form. |
| Click Next. Name The JPanel "MainFileChooser" and click Finish. |
| The GUI Designer (Matisse) |
| will open.</li> |
| |
| <li>In the Palette (Ctrl-Shift-8), click the item for <code>JLabel</code> and drag |
| it to the top of the <code>JPanel</code>. |
| Drag the right-hand edge of the <code>JLabel</code> to the right edge of the |
| <code>JPanel</code> so that the <code>JLabel</code> will |
| resize automatically at runtime.</li> |
| |
| <li><p>In the Palette, click the item for <code>JScrollPane</code> and drag |
| it to the <code>JPanel</code> below the <code>JLabel</code>. |
| Drag the bottom right corner of the <code>JScrollPane</code> down |
| and to the right until the bottom and right edge alignment guidelines |
| appear. The result should look like this:</p> |
| <p><img alt="" src="../../images/tutorials/povray/71/ch9/pic1.png"/></p> |
| </li> |
| |
| <li><p>Make sure the Properties window (Ctrl-Shift-7) is open. |
| Select the <code>JLabel</code> and and then click the [...] button for its |
| <code>text</code> property in the Properties window. A custom editor |
| will open:</p> |
| <p><img alt="" src="../../images/tutorials/povray/71/ch9/pic2.png"/></p> |
| </li> |
| |
| <li><p>Change the drop-down at the top of the dialog to "Custom code", as |
| shown below, and then type <tt>Bundle.LBL_ChooseMainFile()</tt>:</p> |
| <p><img alt="" src="../../images/tutorials/povray/71/ch9/pic3.png"/></p> |
| <p>Click OK. Now add this annotation above the class signature:</p> |
| |
| <pre class="examplecode"><b>@NbBundle.Messages("LBL_ChooseMainFile=Select Main File")</b> |
| public class MainFileChooser extends javax.swing.JPanel {</pre> |
| |
| <p>Save the file. Now the annotation is converted to a String constant in a <tt>Bundle</tt> |
| class and the reference to this class in the <tt>initComponents</tt> block |
| should not show an error anymore.</p> |
| |
| </li> |
| |
| <li>Edit the signature of the class so that it implements the interface |
| <code>ExplorerManager.Provider</code>: |
| <pre class="examplecode">public class MainFileChooser extends javax.swing.JPanel implements ExplorerManager.Provider {</pre></li> |
| |
| <li>Add the following code to implement <code>ExplorerManager.Provider</code>: |
| <pre class="examplecode">private final ExplorerManager mgr = new ExplorerManager(); |
| |
| public ExplorerManager getExplorerManager() { |
| return mgr; |
| }</pre> |
| </li> |
| |
| <li> |
| <p>Modify the constructor so it reads as follows:</p> |
| |
| <pre class="examplecode">public MainFileChooser(PovrayProject proj) { |
| |
| initComponents(); |
| |
| setLayout(new BorderLayout()); |
| |
| LogicalViewProvider logicalView = (LogicalViewProvider) proj.getLookup().lookup(LogicalViewProvider.class); |
| |
| Node projectNode = logicalView.createLogicalView(); |
| |
| mgr.setRootContext(new FilterNode(projectNode, new ProjectFilterChildren(projectNode))); |
| |
| BeanTreeView btv = new BeanTreeView(); |
| |
| jScrollPane1.setViewportView(btv); |
| |
| btv.setPopupAllowed(false); |
| |
| btv.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); |
| |
| add(btv, BorderLayout.CENTER); |
| |
| }</pre> |
| <p>The <code>BeanTreeView</code> we are showing is a UI class from the |
| Explorer API—in fact, it is the very same component that you see in |
| the Projects, Files, Runtime and Favorites tabs in the NetBeans IDE.</p> |
| <p> |
| What it will do is, when it is added to a component, search through |
| the hierarchy of parent components until it finds one that implements |
| <code>ExplorerManager.Provider</code>. That component's <code>ExplorerManager</code> |
| will then become the place where the tree view gets its root node to |
| display, and will be what it notifies when the selection changes.</p> |
| |
| <p class="notes"><b>Note:</b> An error mark will remain |
| for <code>ProjectFilterNode</code> because we have not yet |
| written it.</p></li> |
| |
| <li><p>Now we need to implement <code>ProjectFilterNode</code>. The |
| Nodes API contains a class, <code>FilterNode</code>, which makes it |
| possible to take one <code>Node</code>, and create another <code>Node</code> |
| which "filters" the original <code>Node</code>—providing |
| different children, actions, properties or whatever it chooses to.</p> |
| |
| <p>In our case, we want a <code>FilterNode</code> that will filter out |
| any files that do not have the MIME type <code>text/x-povray</code>—so |
| that, if the user has a text file or an image file or such in their |
| project, they cannot set that to be the main file and try to pass it |
| to POV-Ray.</p> |
| |
| <p>We don't actually need to implement <code>FilterNode</code>, we |
| simply need to provide an alternate <code>Children</code> object |
| which filters out files we don't want. Implement this as a nested |
| class inside <code>MainFileChooser</code>:</p> |
| |
| <pre class="examplecode">private static final class ProjectFilterChildren extends FilterNode.Children { |
| |
| ProjectFilterChildren(Node projectNode) { |
| super(projectNode); |
| } |
| |
| @Override |
| protected Node[] createNodes(Node object) { |
| Node origChild = (Node) object; |
| DataObject dob = (DataObject) origChild.getLookup().lookup(DataObject.class); |
| if (dob != null) { |
| FileObject fob = dob.getPrimaryFile(); |
| if ("text/x-povray".equals(fob.getMIMEType())) { |
| return super.createNodes(object); |
| } else if (dob instanceof DataFolder) { |
| //Allow child folders of the scenes/ dir |
| return new Node[]{ |
| new FilterNode(origChild, |
| new ProjectFilterChildren(origChild)) |
| }; |
| } |
| } |
| //Don't create any nodes for non-povray files |
| return new Node[0]; |
| } |
| |
| }</pre> |
| </li> |
| |
| <li> |
| |
| <p>Now we just need some code to use this panel. That code will |
| go in <code>RenderServiceImpl</code>, before we call |
| <code>Povray.render()</code>. Reimplement the no-argument version |
| of the <code>render()</code> method as follows:</p> |
| |
| <pre class="examplecode">@Override |
| public FileObject render() { |
| MainFileProvider mfp = (MainFileProvider) proj.getLookup().lookup(MainFileProvider.class); |
| assert mfp != null; |
| if (mfp.getMainFile() == null) { |
| showChooseMainFileDlg(mfp); |
| } |
| if (mfp.getMainFile() != null) { |
| return render(null); |
| } else { |
| return null; |
| } |
| }</pre> |
| |
| </li> |
| |
| <li><p>Now we need to implement the method we are calling above, |
| <code>showChooseMainFileDlg()</code>. This is the method which |
| will ask the user to pick a main file. It will use the Dialogs API |
| to show a dialog containing an instance of <code>MainFileChooser</code>, |
| and enable the OK button once a file is selected. If the user |
| selects a POV-Ray file, it will be stored in <code>MainFileProvider</code>, |
| and so it will be non-null when we return to the <code>render()</code> |
| method, and so <code>render()</code> will proceed:</p> |
| |
| <pre class="examplecode">@NbBundle.Messages("TTL_ChooseMainFile=Choose Main File") |
| private void showChooseMainFileDlg(final MainFileProvider mfp) { |
| |
| final MainFileChooser chooser = new MainFileChooser(proj); |
| |
| String title = Bundle.TTL_ChooseMainFile(); |
| |
| //Create a simple dialog descriptor describing what kind of dialog |
| //we want and its title and contents |
| final DialogDescriptor desc = new DialogDescriptor(chooser, title); |
| |
| //The OK button should be disabled initially |
| desc.setValid(false); |
| |
| //Create a property change listener. It will listen on the selection |
| //in our MainFileChooser, and enable the OK button if an appropriate |
| //node is selected: |
| PropertyChangeListener pcl = new PropertyChangeListener() { |
| @Override |
| public void propertyChange(PropertyChangeEvent pce) { |
| String propName = pce.getPropertyName(); |
| if (ExplorerManager.PROP_SELECTED_NODES.equals(propName)) { |
| Node[] n = (Node[]) pce.getNewValue(); |
| boolean valid = n.length == 1; |
| if (valid) { |
| DataObject ob = (DataObject) n[0].getLookup().lookup(DataObject.class); |
| valid = ob != null; |
| if (valid) { |
| FileObject selectedFile = ob.getPrimaryFile(); |
| String mimeType = selectedFile.getMIMEType(); |
| valid = "text/x-povray".equals(mimeType); |
| } |
| } |
| desc.setValid(valid); |
| } |
| } |
| }; |
| chooser.getExplorerManager().addPropertyChangeListener(pcl); |
| |
| //Show the dialog—dialogResult will be OK or Cancel |
| Object dialogResult = DialogDisplayer.getDefault().notify(desc); |
| |
| //If the user clicked OK, try to set the main file |
| //from the selection |
| if (DialogDescriptor.OK_OPTION.equals(dialogResult)) { |
| |
| //Get the selected Node |
| Node[] n = chooser.getExplorerManager().getSelectedNodes(); |
| |
| //If it's > 1, explorer is broken—we set |
| //single selection mode |
| assert n.length <= 1; |
| DataObject ob = (DataObject) n[0].getLookup().lookup( |
| DataObject.class); |
| |
| //Get the file from the data object |
| FileObject selectedFile = ob.getPrimaryFile(); |
| |
| //And save it as the main file |
| mfp.setMainFile(selectedFile); |
| |
| } |
| |
| }</pre> |
| </li> |
| |
| </ol> |
| |
| </div> |
| |
| <h2 class="tutorial"><a name="project-popup"></a>Extending the Project Popup Menu</h2> |
| |
| <p>Right now, if you run the application and open a new POV-Ray project |
| and right-click on it, there are no render or clean actions. |
| The first thing we can do is improve the popup menu for POV-Ray |
| projects—we need to add a couple of menu items to those already returned.</p> |
| |
| <ol> |
| |
| <li>Open <code>PovrayLogicalView</code> in the editor, and find the |
| inner <code>ScenesNode</code> class.</li> |
| |
| <li><p>Override <code>getActions()</code> as follows:</p> |
| |
| <pre class="examplecode">@NbBundle.Messages({ |
| "LBL_Build=Render Project", |
| "LBL_Clean=Clean Project" |
| }) |
| @Override |
| public Action[] getActions(boolean popup) { |
| Action[] result = new Action[]{ |
| new ProjectAction(ActionProvider.COMMAND_BUILD, Bundle.LBL_Build(), project), |
| new ProjectAction(ActionProvider.COMMAND_CLEAN, Bundle.LBL_Clean(), project), |
| new OtherProjectAction(project, false), |
| SystemAction.get(NewTemplateAction.class), |
| SystemAction.get(FileSystemAction.class), |
| new OtherProjectAction(project, true), |
| }; |
| return result; |
| }</pre> |
| <p>This gives us two classes to implement—<code>ProjectAction</code> and |
| <code>OtherProjectAction</code>. |
| The former will simply be an action class which delegates to the action |
| provider of the project, and the other will use the <code>OpenProjects</code> |
| class from the Project UI API to close the project.</p> |
| |
| </li> |
| |
| <li><p>Implement <code>ProjectAction</code> as follows:</p> |
| |
| <pre class="examplecode">private static class ProjectAction extends AbstractAction { |
| |
| private final PovrayProject project; |
| private final String command; |
| |
| public ProjectAction(String cmd, String displayName, PovrayProject prj) { |
| this.project = prj; |
| putValue(NAME, displayName); |
| this.command = cmd; |
| } |
| |
| @Override |
| public void actionPerformed(ActionEvent actionEvent) { |
| ActionProvider prov = (ActionProvider) project.getLookup().lookup(ActionProvider.class); |
| prov.invokeAction(command, null); |
| } |
| |
| @Override |
| public boolean isEnabled() { |
| ActionProvider prov = (ActionProvider) project.getLookup().lookup(ActionProvider.class); |
| return prov.isActionEnabled(command, null); |
| } |
| |
| }</pre> |
| </li> |
| |
| <li><p>Then implement the brilliantly named <code>OtherProjectAction</code> |
| this way, also as a nested class inside PovrayLogicalView. What we're |
| doing here is saving the overhead of one more class to do something |
| simple, and writing one action that either closes the project or makes |
| it the main project, depending on a flag. While not beautiful, it is |
| short enough to be readable—and additional classes do come with a |
| memory penalty, so for trivial things, this approach is not necessarily |
| a bad idea—as long as the result is readable:</p> |
| |
| <pre class="examplecode">@NbBundle.Messages({ |
| "LBL_CloseProject=Close Project", |
| "LBL_SetMainProject=Set Main Project" |
| }) |
| private static class OtherProjectAction extends AbstractAction { |
| |
| private final PovrayProject project; |
| private final boolean isClose; |
| |
| OtherProjectAction(PovrayProject project, boolean isClose) { |
| putValue(NAME, isClose ? Bundle.LBL_CloseProject() : Bundle.LBL_SetMainProject()); |
| this.project = project; |
| this.isClose = isClose; |
| } |
| |
| @Override |
| public void actionPerformed(ActionEvent actionEvent) { |
| if (isClose) { |
| OpenProjects.getDefault().close(new Project[]{project}); |
| } else { |
| OpenProjects.getDefault().setMainProject(project); |
| } |
| } |
| |
| }</pre> |
| </li> |
| |
| |
| <li><p>Run the application and right-click on a POV-Ray project |
| node. Notice that we now have a much improved popup menu:</p> |
| <p><img alt="" src="../../images/tutorials/povray/71/ch9/pic4.png"/></p> |
| <p>If "Render Project" is chosen while no main file has been set, you will |
| see this dialog, where you can set a main file, which will |
| immediately result in the file being rendered:</p> |
| <p><img alt="" src="../../images/tutorials/povray/71/ch9/pic5.png"/></p> |
| </li> |
| |
| </ol> |
| |
| <h2 class="tutorial"><a name="next"></a>Next Steps</h2> |
| |
| <p>Congratulations! You have completed the POV-Ray tutorial. |
| The <a href="nbm-povray-10.html">next step</a> is to |
| review the concepts you've learned and then continue |
| your journey on the NetBeans Platform.</p> |
| |
| </body> |
| |
| </html> |