| <!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 VI—Implementing the API</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 VI—Implementing our API and making implementations of it available from our custom project type"/> |
| <!-- Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. --> |
| <!-- Use is subject to license terms.--> |
| |
| </head> |
| |
| <body> |
| |
| <h1>Writing POV-Ray Support for NetBeans VI—Implementing the API</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>, and <a href="nbm-povray-5.html">fifth</a> |
| parts of this tutorial, you may want to start there.</p> |
| |
| <h2 class="tutorial"><a name="setup"></a>Implementing MainFileProvider</h2> |
| |
| <p>The first class we will implement is <code>MainFileProvider</code>. This |
| is the class that our <code>Node</code>s for POV-Ray files will look up, |
| to see if they represent the main scene file of the project (if so, they will |
| display their name in boldface text, and later this will be used to provide |
| actions to set which file is the main file).</p> |
| |
| <div class="indent"> |
| |
| <ol> |
| <li><p>In the Povray Project module, add the key for the main file to the top of the <tt>PovrayProject</tt> |
| class:</p> |
| <pre class="examplecode">public static final String KEY_MAINFILE = "main.file";</pre> |
| </li> |
| |
| <li><p>In the Povray Project module, create a new Java class in the package |
| <code>org.netbeans.examples.modules.povproject</code>, and call it |
| "MainFileProviderImpl".</p></li> |
| |
| <li>Implement it as follows: |
| <pre class="examplecode">class MainFileProviderImpl extends MainFileProvider { |
| |
| private final PovrayProject proj; |
| |
| private FileObject mainFile = null; |
| |
| private boolean checked = false; |
| |
| MainFileProviderImpl(PovrayProject proj) { |
| this.proj = proj; |
| } |
| |
| @Override |
| public FileObject getMainFile() { |
| //Try to look up the main file in the project properties |
| //the first time this is called; no need to look it up every |
| //time, either it's there or it's not and when the user sets it |
| //we'll save it when the project is closed |
| if (mainFile == null && !checked) { |
| checked = true; |
| Properties props = (Properties) proj.getLookup().lookup(Properties.class); |
| String path = props.getProperty(PovrayProject.KEY_MAINFILE); |
| if (path != null) { |
| FileObject projectDir = proj.getProjectDirectory(); |
| mainFile = projectDir.getFileObject(path); |
| } |
| } |
| if (mainFile != null && !mainFile.isValid()) { |
| return null; |
| } |
| return mainFile; |
| } |
| |
| @Override |
| public void setMainFile(FileObject file) { |
| String projPath = proj.getProjectDirectory().getPath(); |
| assert file == null || |
| file.getPath().startsWith(projPath) : |
| "Main file not under project"; |
| boolean change = ((mainFile == null) != (file == null)) || |
| (mainFile != null && !mainFile.equals(file)); |
| if (change) { |
| mainFile = file; |
| //Get the project properties (loaded from |
| //$PROJECT/pvproject/project.properties) |
| Properties props = (Properties) proj.getLookup().lookup( |
| Properties.class); |
| //Store the relative path from the project root as the main file |
| String relPath = file.getPath().substring(projPath.length()); |
| props.put(PovrayProject.KEY_MAINFILE, relPath); |
| } |
| } |
| |
| }</pre> |
| |
| <p class="notes">The code above is simple and quite straightforward—it will look for |
| a <code>Properties</code> object in the <code>Lookup</code> of the |
| project. The getter will look for the value of "main.file" |
| from the <code>Properties</code> object (which was loaded from |
| <code>$PROJECT/pvproject/project.properties</code>), which will be |
| a relative path to the main file. If there is a value for that key, try |
| to find the corresponding file and return it. The setter, in turn, will |
| write a new relative path to the <code>Properties</code> object. That |
| in turn, will cause the project to be marked as modified, so the system |
| will call <code>PovProjectFactory.saveProject()</code> if it is unloading |
| the project, causing the <code>Properties</code> to be written out to |
| disk in <code>$PROJECT/pvproject/project.properties</code>.</p> |
| |
| </li> |
| |
| <li><p>Add <code>new MainFileProviderImpl(this)</code> to the implementation |
| of <code>getLookup()</code> in <code>PovrayProject</code>, so that it |
| is included in the array of objects that make up the lookup contents:</p> |
| |
| <pre class="examplecode">@Override |
| public Lookup getLookup() { |
| if (lkp == null) { |
| lkp = Lookups.fixed(new Object[]{ |
| this, //handy to expose a project in its own lookup |
| state, //allow outside code to mark the project as needing saving |
| new ActionProviderImpl(), //Provides standard actions like Build and Clean |
| loadProperties(), //The project properties |
| new Info(), //Project information implementation |
| logicalView, //Logical view of project implementation |
| <b>new MainFileProviderImpl(this)</b> |
| }); |
| } |
| return lkp; |
| }</pre> |
| |
| </li> |
| </ol> |
| |
| </div> |
| |
| <h2 class="tutorial"><a name="setup"></a>Implementing RendererService—Providing Default Renderer Settings</h2> |
| |
| <p>The next class to implement is <code>RendererService</code>—this is the |
| service, belonging to the project, by which a project will be "compiled" |
| into an image (by executing the POV-Ray program and passing it arguments).</p> |
| |
| <p> |
| As we discussed <a href="nbm-povray-2.html">earlier</a>, we are not going to |
| try to implement a complicated dialog that makes every possible POV-Ray |
| setting adjustable via a GUI widget—this would add a lot of complexity |
| when many users would be satisfied with a reasonable set of defaults. So |
| we will have a set of different default combinations of settings that should |
| satisfy most users. Later we will add the ability to create completely |
| customized settings by editing the <code>project.properties</code> of a |
| POV-Ray project, to satisfy the needs of power users.</p> |
| |
| <p> |
| Right now, we will not worry about the execution part—<code>RendererService</code> |
| also provides for named sets of settings—combinations of line switches |
| which should be passed to POV-Ray to determine rendering quality, image size |
| and speed. Right now we will only implement that part of <code>RendererService</code>.</p> |
| |
| <p> |
| This is where we will begin dealing directly with the System |
| Filesystem—the registry of runtime data supplied by modules. What we |
| will do is create <code>.properties</code> files for each set of standard |
| settings we will supply. We will start by creating <code>.properties</code> |
| files for each set of settings in our module.</p> |
| |
| <div class="indent"> |
| |
| <ol> |
| |
| <li>Create a new Java Package in the Povray Project project, |
| <code>org.netbeans.examples.modules.povproject.defaults</code>. This |
| is where we will put our properties files. |
| </li> |
| |
| <li> |
| <p>Create six properties files with the following contents in that package:</p> |
| <ul> |
| <li>160x100.properties |
| <pre class="examplecode"> |
| W=160 |
| H=100 |
| Q=4 |
| FN=8 |
| A=0.0 |
| </pre> |
| </li> |
| <li>320x200.properties |
| <pre class="examplecode"> |
| W=320 |
| H=200 |
| Q=4 |
| FN=8 |
| A=0.0 |
| </pre> |
| </li> |
| <li>640x480.properties |
| <pre class="examplecode"> |
| W=640 |
| H=480 |
| Q=4 |
| FN=8 |
| A=0.0 |
| </pre> |
| </li> |
| <li>640x480hq.properties |
| <pre class="examplecode"> |
| W=640 |
| H=480 |
| Q=R |
| FN=8 |
| A=0.9 |
| </pre> |
| </li> |
| <li>1024x768.properties |
| <pre class="examplecode"> |
| W=1024 |
| H=768 |
| Q=4 |
| FN=9 |
| A=0.0 |
| </pre> |
| </li> |
| <li>1024x768hq.properties |
| <pre class="examplecode"> |
| W=1024 |
| H=768 |
| Q=R |
| FN=8 |
| A=0.9 |
| </pre> |
| </li> |
| </ul></li> |
| |
| <li><p>Next, we will want to actually add these to the |
| System Filesystem, so our module can find them at runtime, and more |
| importantly, so other modules can modify and save, or add additional, |
| sets of default settings by adding more properties files to the |
| same folder we put these files in, in the System Filesystem.</p> |
| <p>Right-click the Povray Project project and choose |
| New | Other | Module Development | XML Layer. Then click |
| Next and Finish. The IDE creates the <tt>layer.xml</tt> file |
| and registers it in the project's manifest. Open the newly created |
| <tt>layer.xml</tt> file in the code editor.</p> |
| </li> |
| |
| <li> |
| |
| <p>Replace the content of the <tt>layer.xml</tt> file with the content below.</p> |
| |
| <pre class="examplecode"><?xml version="1.0" encoding="UTF-8"?> |
| <!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.2//EN" "https://netbeans.org/dtds/filesystem-1_2.dtd"> |
| <filesystem> |
| |
| <folder name="Povray"> |
| |
| <folder name="RendererSettings"> |
| |
| <!-- Declare a file, with its content provided by the URL. This |
| will be the command line arguments for 1024x768 high quality |
| rendering --> |
| <file name="1024x768hq.properties" url="defaults/1024x768hq.properties"> |
| <attr name="SystemFileSystem.localizingBundle" |
| stringvalue="org.netbeans.examples.modules.povproject.defaults.Bundle"/> |
| </file> |
| <!-- This is an ordering attribute, it determines that the |
| DataFolder (but *not* the FileObject) for this folder will return |
| its child DataObjects (and thus also its Node's children) in |
| a specific order—in this case we are specifying that |
| 1024x768hq must come before 1024x768.properties. --> |
| <attr name="1024x768hq.properties/1024x768.properties" boolvalue="true"/> |
| |
| <file name="1024x768.properties" url="defaults/1024x768.properties"> |
| <attr name="SystemFileSystem.localizingBundle" |
| stringvalue="org.netbeans.examples.modules.povproject.defaults.Bundle"/> |
| </file> |
| |
| <attr name="1024x768.properties/640x480hq.properties" boolvalue="true"/> |
| <file name="640x480hq.properties" url="defaults/640x480hq.properties"> |
| <attr name="SystemFileSystem.localizingBundle" |
| stringvalue="org.netbeans.examples.modules.povproject.defaults.Bundle"/> |
| </file> |
| |
| <attr name="640x480hq.properties/640x480.properties" boolvalue="true"/> |
| <file name="640x480.properties" url="defaults/640x480.properties"> |
| <attr name="SystemFileSystem.localizingBundle" |
| stringvalue="org.netbeans.examples.modules.povproject.defaults.Bundle"/> |
| </file> |
| |
| <attr name="640x480.properties/320x200.properties" boolvalue="true"/> |
| <file name="320x200.properties" url="defaults/320x200.properties"> |
| <attr name="SystemFileSystem.localizingBundle" |
| stringvalue="org.netbeans.examples.modules.povproject.defaults.Bundle"/> |
| </file> |
| |
| <attr name="320x200.properties/160x100.properties" boolvalue="true"/> |
| <file name="160x100.properties" url="defaults/160x100.properties"> |
| <attr name="SystemFileSystem.localizingBundle" |
| stringvalue="org.netbeans.examples.modules.povproject.defaults.Bundle"/> |
| </file> |
| |
| </folder> |
| |
| </folder> |
| |
| </filesystem></pre> |
| |
| <p class="notes">What this XML does is map the properties files we just created into |
| the system filesystem in the folder <code>Povray/RendererSettings</code>, |
| which is where our code will look for them. Additionally, it specifies |
| <i>ordering attributes</i>, which are attributes we are adding to the |
| folder <code>RendererSettings/</code>, which will determine what order |
| the files will appear in when code asks for the array of children of |
| the <code>DataFolder</code> (<code>DataObject</code> subclass for |
| folders) or its Node for this folder.</p> |
| |
| </li> |
| |
| <li> |
| <p>You may have noticed the attribute <code>SystemFilesystem.localizingBundle</code> |
| which we added to the <code>RendererSettings</code> folder. NetBeans |
| <code>FileObject</code>s (which is what the "files" in the |
| System Filesystem are) can have ad-hoc key-value pairs associated |
| with them. <code>SystemFilesystem.localizingBundle</code> is a magic |
| attribute which the system will use to localize the names of files—all |
| you have to do is get the <code>DataObject</code> for a file in |
| the system filesystem, get the <code>Node</code> for that <code>DataObject</code>, |
| and the return value of <code>Node.getDisplayName()</code> for that |
| <code>Node</code> will look up its localized display name in the |
| requested resource bundle—this is how file names for things declared |
| in the System Filesystem are localized.</p> |
| <p> |
| So we need one <i>more</i> properties file in |
| <code>org.netbeans.examples.modules.povproject.defaults</code>—create |
| one called "Bundle". This one won't contain renderer |
| defaults, it will contain mappings from the file names of the files |
| we declared above, to their localized, human friendly names.</p> |
| </li> |
| |
| <li> |
| <p>Add the following contents to <code>Bundle.properties</code>:</p> |
| <p><pre class="examplecode">Povray/RendererSettings/1024x768.properties=1024 x 768 |
| Povray/RendererSettings/1024x768hq.properties=1024 x 768 High Quality |
| Povray/RendererSettings/640x480hq.properties=640 x 480 High Quality |
| Povray/RendererSettings/640x480.properties=640 x 480 |
| Povray/RendererSettings/320x200.properties=320 x 200 |
| Povray/RendererSettings/160x100.properties=160 x 100</pre></p> |
| </li> |
| |
| <li><p>Make sure that you have the new files in the correct places, reflecting |
| the structure in the image below:</p> |
| <p><img alt="" src="../../images/tutorials/povray/71/ch6/pic1.png"/></p> |
| </li> |
| |
| </ol> |
| |
| </div> |
| |
| <h2 class="tutorial"><a name="rendererservice1"></a>Implementing RendererService—Basic Implementation</h2> |
| |
| <p>Now we have a set of default settings to show, so we can implement the methods of |
| <code>RendererService</code> that will expose them.</p> |
| |
| <div class="indent"> |
| |
| <ol> |
| |
| <li>Create a new class, <code>RendererServiceImpl</code>, in |
| <code>org.netbeans.examples.modules.povproject</code>.</li> |
| |
| <li>Modify the class declaration to say that it extends <code>RendererService</code> |
| and press Ctrl-Shift-I to fix imports and to generate stub implementations |
| of the abstract methods. The result should be as follows: |
| |
| <pre class="examplecode">package org.netbeans.examples.modules.povproject; |
| |
| import java.util.Properties; |
| import org.netbeans.examples.api.povray.RendererService; |
| import org.openide.filesystems.FileObject; |
| |
| public class RendererServiceImpl extends RendererService { |
| |
| @Override |
| public FileObject render(FileObject scene, String propertiesName) { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| @Override |
| public FileObject render(FileObject scene, Properties renderSettings) { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| @Override |
| public FileObject render(FileObject scene) { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| @Override |
| public FileObject render() { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| @Override |
| public String[] getAvailableRendererSettingsNames() { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| @Override |
| public Properties getRendererSettings(String name) { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| @Override |
| public String getPreferredRendererSettingsNames() { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| @Override |
| public String getDisplayName(String settingsName) { |
| throw new UnsupportedOperationException("Not supported yet."); |
| } |
| |
| }</pre> |
| |
| </li> |
| |
| <li>The first things we will do are implement the constructor and |
| leave the render methods stubbed out—we will implement these later: |
| <pre class="examplecode">private PovrayProject proj; |
| |
| public RendererServiceImpl(PovrayProject proj) { |
| this.proj = proj; |
| } |
| |
| PovrayProject getProject() { |
| return proj; |
| }</pre> |
| </li> |
| |
| <li><p>Next, we will implement some private utility methods that the other |
| methods will do. This should help to give some of the flavor of working |
| with things in the system filesystem.</p> |
| |
| <pre class="examplecode"> private FileObject getRendererSettingsFolder() { |
| String folderName = "Povray/RendererSettings"; |
| FileObject result = FileUtil.getConfigFile(folderName); |
| if (result == null && !logged) { |
| //Corrupted userdir or something is very very wrong. |
| //Log it and move on. |
| Exceptions.printStackTrace(new IllegalStateException("Renderer settings dir missing!")); |
| logged = true; |
| } |
| return result; |
| } |
| |
| private static boolean logged = false; |
| |
| private FileObject fileFor (String settingsName) { |
| FileObject settingsFolder = getRendererSettingsFolder(); |
| FileObject result; |
| if (settingsFolder != null) { //should never be null |
| result = settingsFolder.getFileObject(settingsName); |
| } else { |
| result = null; |
| } |
| return result; |
| } |
| |
| private void setPreferredRendererSettingsName(String val) { |
| getPreferences().put(KEY_PREFERRED_SETTINGS, val); |
| } |
| |
| private static final String KEY_PREFERRED_SETTINGS = "preferredSettings"; |
| |
| static Preferences getPreferences() { |
| return Preferences.userNodeForPackage(RendererServiceImpl.class); |
| }</pre> |
| |
| <p>The first thing we have is a utility method that finds the folder we |
| declared in our XML layer, in the System Filesystem—that is what |
| <code>getRendererSettingsFolder()</code> does. You'll note that there |
| is a null check. This folder *should* not be null, since we are declaring |
| it in our layer. But it conceivably could be (a corrupted settings |
| directory or a module that for some reason hides the settings directory - |
| it should not happen, but it is theoretically possible), so we log an |
| exception if so, rather than throwing exceptions every time something goes |
| and looks for a display name for a menu item or similar.</p> |
| |
| <p>The next method is a utility method that just fetches the file corresponding |
| to a file name—we are returning the names of all files in the settings |
| folder, so this will allow us to find a corresponding properties file.</p> |
| |
| <p>The last two methods are simply for saving the last-used set of renderer |
| settings, and simply use the standard Java Preferences API.</p> |
| </li> |
| |
| <li> |
| <p>The next method we want to implement is |
| <code>getAvailableRendererSettingsNames()</code>. This method will |
| return an array of <code>String</code>s—the localized, human-friendly |
| names of all of the files which we declared above:</p> |
| |
| <pre class="examplecode"> @Override |
| public String[] getAvailableRendererSettingsNames() { |
| FileObject settingsFolder = getRendererSettingsFolder(); |
| String[] result; |
| if (settingsFolder != null) { |
| //Use a DataFolder here, so our ordering attributes in the layer |
| //file are applied, and our returned String array will be in the |
| //order we want |
| DataFolder fld = DataFolder.findFolder(settingsFolder); |
| DataObject[] kids = fld.getChildren(); |
| result = new String[ kids.length ]; |
| for (int i = 0; i < kids.length; i++) { |
| result[i] = kids[i].getPrimaryFile().getNameExt(); |
| } |
| } else { |
| result = new String[0]; |
| } |
| return result; |
| }</pre> |
| |
| <p>This is quite straightforward—we just iterate all of the files in the |
| folder, and return an array of <code>String</code>s with their names. The |
| one twist to it is that we don't iterate the <i><code>FileObject</code></i>'s |
| children, but rather we get a <code>DataFolder</code> (the <code>DataObject</code> |
| type for filesystem folders), and iterate its children. The reason we do |
| it this way is that the order of children of <code>FileObjects</code> is |
| undefined—we might get the files we declared in any order. The |
| <code>DataFolder</code>, however, understands <i>ordering attributes</i> - |
| attributes we can declare in the XML of our layer file, which will determine |
| what order a folder's children are returned in. So this enables us to sort |
| our settings files in an intuitive way—yet other modules could still insert |
| additional settings, with their own ordering attributes, and they would be |
| included in the sort (for more info on how and why this works, see the |
| javadoc for |
| <a href="https://netbeans.org/download/dev/javadoc/org-openide-util/org/openide/util/Utilities.html#topologicalSort(java.util.Collection,%20java.util.Map)">Utilities.topologicalSort()</a>).</p> |
| |
| </li> |
| |
| <li><p>Next we will implement <code>getRendererSettings(name)</code>—this method |
| will actually get a <code>Properties</code> object with the contents of |
| whichever file name was passed to it:</p> |
| |
| <pre class="examplecode"> @Override |
| public Properties getRendererSettings(String name) { |
| Properties result = new Properties(); |
| FileObject settingsFile = fileFor (name); |
| if (settingsFile != null) { |
| try { |
| result.load(new BufferedInputStream(settingsFile.getInputStream())); |
| } catch (FileNotFoundException ex) { |
| Exceptions.printStackTrace(ex); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } else { |
| Exceptions.printStackTrace( |
| new NullPointerException("Requested non-existent settings " + |
| "file " + name)); |
| } |
| return result; |
| }</pre> |
| |
| <p>The code here is also quite straightforward—it simply tries to load a |
| <code>Properties</code> object from the input stream of the file in question.</p> |
| |
| </li> |
| |
| <li> |
| <p>Next we will implement the method that fetches the name of the preferred |
| set of settings—this will be the most recently used settings, fetched from |
| the Preferences API, with a fallback if none has yet been chosen:</p> |
| |
| <pre class="examplecode"> @Override |
| public String getPreferredRendererSettingsName() { |
| String result = getPreferences().get(KEY_PREFERRED_SETTINGS, null); |
| if (result == null) { |
| result = "640x480.properties"; |
| } |
| return result; |
| }</pre> |
| |
| </li> |
| |
| <li> |
| |
| <p>The last method we will implement takes a settings <i>file name</i> and |
| converts it to a localized, human-readable name:</p> |
| |
| <pre class="examplecode"> @Override |
| public String getDisplayName(String settingsName) { |
| FileObject file = fileFor (settingsName); |
| String result; |
| if (file != null) { |
| DataObject dob; |
| try { |
| dob = DataObject.find(file); |
| result = dob.getNodeDelegate().getDisplayName(); |
| } catch (DataObjectNotFoundException ex) { |
| Exceptions.printStackTrace(ex); |
| result = "[error]"; |
| } |
| } else { |
| result = ""; |
| } |
| return result; |
| }</pre> |
| |
| <p>Human-readable display names are provided by <code>Nodes</code>—a |
| <code>FileObject</code> is simply a file on disk (or similar storage such |
| as the System Filesystem via our <code>layer.xml</code> file)—it has |
| no notion of human readability. So if we want the <i>localized</i> name |
| for a <code>FileObject</code>, we need to get the <code>Node</code> for it. |
| In this case, the <code>Node</code> will use the hint we provided |
| in the <code>layer.xml</code> file:</p> |
| |
| <pre class="examplecode"><attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.modules.povproject.defaults.Bundle"/></pre> |
| |
| <p>and look up its localized name in |
| <code>org.netbeans.examples.modules.povproject.defaults.Bundle.properties</code>.</p> |
| |
| </li> |
| |
| <li><p>As we did earlier with the <tt>MainFileProviderImpl</tt>, we now need to |
| expose our implementation of <tt>RendererService</tt> via the |
| project's lookup. Modify <tt>PovrayProject.getLookup()</tt> as follows: </p> |
| |
| <pre class="examplecode">public Lookup getLookup() { |
| if (lkp == null) { |
| lkp = Lookups.fixed(new Object[] { |
| this, //handy to expose a project in its own lookup |
| state, //allow outside code to mark the project as needing saving |
| new ActionProviderImpl(), //Provides standard actions like Build and Clean |
| loadProperties(), //The project properties |
| new Info(), //Project information implementation |
| logicalView, //Logical view of project implementation |
| new MainFileProviderImpl(this), //So things can set the main file |
| <b>new RendererServiceImpl(this), //Renderer Service Implementation</b> |
| }); |
| } |
| return lkp; |
| }</pre> |
| |
| </li> |
| |
| |
| </ol> |
| |
| </div> |
| |
| <h2 class="tutorial"><a name="setup"></a>Providing Render Actions on POV-Ray Files</h2> |
| |
| <p>Now we have an implementation of some of our API, the next step is to use it. |
| As <a href="nbm-povray-2.html">discussed earlier</a>, we want a user to be |
| able to right-click and choose to render any file, not just the main file |
| of the project. So there should be some menu items available from our |
| <code>PovrayDataNode</code>s which will allow the user to render the file |
| with one of our sets of settings.</p> |
| |
| <div class="indent"> |
| |
| <ol> |
| |
| <li>Open <code>PovrayDataNode</code>, from the Povray File Support project, |
| in the code editor</li> |
| |
| <li><p>Press Ctrl-I (Command-I on Macintosh) and override the |
| <code>getActions(boolean)</code> method. Implement it as follows:</p> |
| |
| <pre class="examplecode">@Override |
| public Action[] getActions (boolean popup) { |
| Action[] actions = super.getActions(popup); |
| RendererService renderer = |
| (RendererService)getFromProject (RendererService.class); |
| Action[] result; |
| if (renderer != null && actions.length > 0) { //should always be > 0 |
| Action rendererAction = new RendererAction (renderer, this); |
| result = new Action[ actions.length + 2 ]; |
| result[0] = actions[0]; |
| result[1] = new SetMainFileAction(); |
| result[2] = rendererAction; |
| } else { |
| //Isolated file in the favorites window or something |
| result = actions; |
| } |
| return result; |
| }</pre> |
| |
| <p>This method will add two (yet to be implemented) actions into the array |
| of actions, if a renderer service for this file can be found. It positions |
| them as the second and third elements in the array, since the first element is |
| what will be invoked when you double click the file, and we want that |
| to remain opening the file (we could also override |
| <code>getPreferredAction()</code> to determine what happens when |
| the node is doubled clicked).</p> |
| |
| </li> |
| |
| <li><p>Now we need to implement RendererAction. Right-click the |
| <code>org.netbeans.examples.modules.povfile</code> package, and create |
| a new Java Class called <code>RendererAction</code>. Define it |
| as follows: |
| <pre class="examplecode">public class RendererAction extends AbstractAction implements Presenter.Popup {</pre></p> |
| |
| <p>Implementing <code>Presenter.Popup</code> is an important step—this |
| is a way in which an action can actually provide whatever component it |
| wants to insert into the popup menu. It is a one-method interface, with |
| the method <code>getPopupPresenter</code> which returns a JMenuItem |
| (remember that in Swing, JMenu is a subclass of JMenuItem, so it's |
| legal to return whole submenu here). |
| In our case, we want a submenu:</p> |
| |
| <ul> |
| <li>Render |
| <ul> |
| <li>1024 x 768 High Quality</li> |
| <li>1024 x 768</li> |
| <li>640 x 480 High Quality</li> |
| <li>640 x 480 High Quality</li> |
| <li>320 x 200</li> |
| <li>160 x 120</li> |
| </ul> |
| </li> |
| <li>Standard file menu items...</li> |
| </ul> |
| |
| </li> |
| |
| <li><p>Now we will provide the body of <code>RendererAction</code>:</p> |
| |
| <pre class="examplecode">package org.netbeans.examples.modules.povfile; |
| |
| import java.awt.event.ActionEvent; |
| import javax.swing.AbstractAction; |
| import javax.swing.JCheckBoxMenuItem; |
| import javax.swing.JMenu; |
| import javax.swing.JMenuItem; |
| import org.netbeans.examples.api.povray.RendererService; |
| import org.openide.util.NbBundle; |
| import org.openide.util.actions.Presenter; |
| |
| public class RendererAction extends AbstractAction implements Presenter.Popup { |
| |
| private final RendererService renderer; |
| private final PovrayDataNode node; |
| |
| public RendererAction(RendererService renderer, PovrayDataNode node) { |
| this.renderer = renderer; |
| this.node = node; |
| } |
| |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| assert false; |
| } |
| |
| @NbBundle.Messages("LBL_Render=Render") |
| @Override |
| public JMenuItem getPopupPresenter() { |
| |
| JMenu result = new JMenu(); |
| |
| //Set the menu's label |
| result.setText(Bundle.LBL_Render()); |
| |
| //Get the names of all available settings sets: |
| String[] availableSettings = |
| renderer.getAvailableRendererSettingsNames(); |
| |
| //Get the name of the most recently used setting set: |
| String preferred = renderer.getPreferredRendererSettingsNames(); |
| |
| for (int i = 0; i < availableSettings.length; i++) { |
| |
| String currName = availableSettings[i]; |
| |
| RenderWithSettingsAction action = |
| new RenderWithSettingsAction(currName); |
| |
| JCheckBoxMenuItem itemForSettings = new JCheckBoxMenuItem(action); |
| |
| //Show our menu item checked if it is the most recently used set |
| //of settings: |
| itemForSettings.setSelected(preferred != null |
| && preferred.equals(currName)); |
| |
| result.add(itemForSettings); |
| |
| } |
| |
| return result; |
| |
| } |
| |
| }</pre> |
| |
| </li> |
| |
| <li><p>The one thing missing here, of course, is the individual actions |
| that will run the renderer with different sets of settings. Create an |
| inner class of <code>RendererAction</code> called |
| <code>RenderWithSettingsAction</code>, and implement it as follows:</p> |
| |
| <pre class="examplecode">private class RenderWithSettingsAction extends AbstractAction implements Runnable { |
| |
| private final String name; |
| |
| public RenderWithSettingsAction(String name) { |
| this.name = name; |
| putValue(NAME, renderer.getDisplayName(name)); |
| } |
| |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| RequestProcessor.getDefault().post(this); |
| } |
| |
| @Override |
| public void run() { |
| DataObject ob = node.getDataObject(); |
| FileObject toRender = ob.getPrimaryFile(); |
| FileObject image = renderer.render(toRender, name); |
| if (image != null) { |
| try { |
| //Try to open the file: |
| DataObject dob = DataObject.find(image); |
| Node n = dob.getNodeDelegate(); |
| OpenCookie ck = (OpenCookie) n.getLookup().lookup(OpenCookie.class); |
| if (ck != null) { |
| ck.open(); |
| } |
| } catch (DataObjectNotFoundException e) { |
| //Should never happen |
| Exceptions.printStackTrace(e); |
| } |
| } |
| } |
| |
| }</pre> |
| |
| <p>This is relatively straightforward as well. We are using the |
| <code>renderer</code> field of the outer class, and only storing the |
| name of which specific properties file should be used to provide |
| settings for this class, which we will pass to <code>renderer.render()</code>.</p> |
| |
| <p>The two interesting areas are how we find the file, and how we actually |
| perform the rendering. We have the instance of <code>PovRayDataNode</code> |
| that we are operating against. It is a subclass of <code>DataNode</code>, |
| so we can call its <code>getDataObject()</code> method (another way |
| would be to call <code>node.getLookup().lookup(DataObject.class)</code>, |
| but since we know its type, calling <code>getDataObject()</code> is |
| more efficient). From that we may call <code>getPrimaryFile()</code> to |
| actually get the <code>FileObject</code> that should be rendered into |
| an image by POV-Ray.</p> |
| |
| <p>The other item of interest is how we do our rendering. Notice |
| that we implement Runnable. Our action will be, by default, called |
| from the event dispatch thread when the user clicks it in a menu. |
| It would not be good at all if running the action blocked the UI from |
| repainting or anything else until the external POV-Ray process was |
| completed. So instead, we use a handy thread pool NetBeans provides |
| for us, and simply post the work to be done on another thread off of |
| the event queue.</p> |
| |
| </li> |
| |
| </ol> |
| |
| </div> |
| |
| <h2 class="tutorial"><a name="pov-exe"></a>Implementing SetMainFileAction</h2> |
| |
| <p>The other action we added to the array of actions on the popup menu for |
| POV-Ray files will set the main file of the project to be whatever file |
| was clicked.</p> |
| |
| <div class="indent"> |
| |
| <ol> |
| |
| <li>We will simply implement this as an inner class of <code>PovrayDataNode</code>. |
| Open <code>PovrayDataNode</code> in the code editor.</li> |
| |
| <li>Implement it as follows. The only twist is that if our <code>Node</code> |
| becomes the main file, it needs to tell the former main file that it is |
| not the main file anymore—more specifically, it needs to force it to |
| fire a property change in its display name so that it gets redrawn as |
| non-bold: |
| <pre class="examplecode">@NbBundle.Messages("CTL_SetMainFile=Set Main File") |
| private final class SetMainFileAction extends AbstractAction { |
| |
| public SetMainFileAction() { |
| putValue(NAME, Bundle.CTL_SetMainFile()); |
| } |
| |
| @Override |
| public void actionPerformed(ActionEvent ae) { |
| MainFileProvider provider = (MainFileProvider) getFromProject(MainFileProvider.class); |
| FileObject oldMain = provider.getMainFile(); |
| provider.setMainFile(getFile()); |
| fireDisplayNameChange(getDisplayName(), getHtmlDisplayName()); |
| if (oldMain != null) { |
| try { |
| Node oldMainFilesNode = DataObject.find(oldMain).getNodeDelegate(); |
| if (oldMainFilesNode instanceof PovrayDataNode) { |
| ((PovrayDataNode) oldMainFilesNode).fireDisplayNameChange(null, oldMainFilesNode.getDisplayName()); |
| } |
| } catch (DataObjectNotFoundException donfe) { //Should never happen |
| Exceptions.printStackTrace(donfe); |
| } |
| } |
| } |
| |
| @Override |
| public boolean isEnabled() { |
| return !isMainFile() && getFromProject(MainFileProvider.class) != null; |
| } |
| |
| }</pre> |
| </li> |
| |
| <li><p>Run the application, create or open a POV-Ray project, |
| right-click on a .pov file, and you should see your |
| new Actions on the Node:</p> |
| <p><img alt="" src="../../images/tutorials/povray/71/ch6/pic2.png"/></p> |
| <p class="notes"><b>Note:</b> Though the Set Main File |
| action should work correctly, the rendering Actions |
| do not work yet because we have not implemented them |
| yet. That will be done later in this tutorial.</p> |
| </li> |
| |
| </ol> |
| |
| </div> |
| |
| <h2 class="tutorial"><a name="pov-exe"></a>Locating the POV-Ray Executable</h2> |
| |
| <p>The next step is to write the code that will actually run POV-Ray and send |
| its text output to the output window, and eventually open a rendered image. |
| Since this involves some complicated code, we will create a separate |
| utility class that will do the actual rendering.</p> |
| |
| <div class="indent"> |
| |
| <ol> |
| |
| <li>Create a new Java class in the Povray Project project, in |
| <code>org.netbeans.examples.modules.povproject</code>, called |
| "Povray".</li> |
| |
| <li> |
| <p>First we need to implement support for finding the POV-Ray |
| executable, so that we have something to run. This will simply |
| be a matter of popping up a <code>JFileChooser</code> to let the |
| user locate the POV-Ray executable and the directory with the standard |
| POV-Ray include files—once this has been done once, we will store |
| the result so we do not have to ask again unless it is deleted.</p> |
| |
| <p>Since we may need a file chooser twice, once to locate the |
| executable, and once to locate the standard include file directory |
| (which contains files that define standard colors, shapes, etc. that |
| can be used in POV-Ray files), we should provide one method that shows |
| a file chooser for both cases. Add the following method to <code>Povray</code>: |
| <pre class="examplecode">private static File locate(String key) { |
| JFileChooser jfc = new JFileChooser(); |
| jfc.setDialogTitle(NbBundle.getMessage(Povray.class, key)); |
| jfc.setFileSelectionMode (JFileChooser.FILES_ONLY); |
| jfc.showOpenDialog(WindowManager.getDefault().getMainWindow()); |
| File result = jfc.getSelectedFile(); |
| return result; |
| }</pre> |
| <p class="notes">At this point we need to add another dependency, because we are calling |
| <code>WindowManager</code> above. That is part of the Window System API. |
| We could pass null here, but then there is the risk that on some window |
| managers, our file chooser would pop up <i>behind</i> the main window. |
| This makes sure it stays on top. Add a dependency on the Window System API to Povray Project, by |
| right-clicking the project's Libraries node and choosing |
| Add Module Dependency.</p> |
| </li> |
| |
| <li><p>As you can see in the above code, we will be fetching a localized |
| string from a resource bundle—a different one depending on whether |
| we're looking for the executable or include directory. So let's add |
| those strings to the resource bundle for this package via Bundle |
| annotations. We will also |
| add one warning message we will need later.</p> |
| |
| <p>Next, we will add the two methods for fetching the POV-Ray executable |
| and the include directory, which will automatically ask the user if they |
| are unknown or unavailable. Add the following two methods, and their |
| associated fields to |
| <code>Povray</code>:</p> |
| |
| <pre class="examplecode">private static File povray = null; |
| private static File include = null; |
| |
| /** |
| * Preferences key for the povray executable |
| */ |
| private static final String KEY_POVRAY_EXEC = "povray"; |
| |
| /** |
| * Preferences key for the povray standard includes dir |
| */ |
| private static final String KEY_POVRAY_INCLUDES = "include"; |
| |
| @NbBundle.Messages({"TTL_FindPovray=Locate POV-Ray Executable", |
| "MSG_WindowsWarning=" |
| + "<html>POV-Ray for Windows always displays its graphical" |
| + "user interface when it runs. You can get a command-line " |
| + "version of POV-Ray at <a href=\"http://www.imagico.de/files/povcyg_350c.zip\">" |
| + "http://www.imagico.de/files/povcyg_350c.zip</a></html>" |
| }) |
| private static File getPovray() { |
| if (povray == null || !povray.exists()) { |
| Preferences prefs = RendererServiceImpl.getPreferences(); |
| String loc = prefs.get(KEY_POVRAY_EXEC, null); |
| if (loc != null) { |
| povray = new File(loc); |
| } |
| if (povray == null || !povray.exists()) { |
| File maybePov = locate(Bundle.TTL_FindPovray()); |
| if (maybePov.getPath().endsWith("pvengine.exe")) { |
| //Warn the user to get a command line build: |
| NotifyDescriptor msg = new NotifyDescriptor.Confirmation( |
| NbBundle.getMessage(RendererServiceImpl.class, |
| Bundle.MSG_WindowsWarning()), |
| NotifyDescriptor.WARNING_MESSAGE); |
| Object result = DialogDisplayer.getDefault().notify(msg); |
| if (result == NotifyDescriptor.CANCEL_OPTION) { |
| return null; |
| } |
| } |
| povray = maybePov; |
| if (povray != null) { |
| prefs.put(KEY_POVRAY_EXEC, povray.getPath()); |
| } |
| } |
| } |
| return povray; |
| } |
| |
| @NbBundle.Messages("TTL_FindIncludeDir=Find POV-Ray Standard Include File Dir") |
| private static File getStandardIncludeDir(File povray) { |
| if (include != null) { |
| return include; |
| } |
| Preferences prefs = RendererServiceImpl.getPreferences(); |
| String loc = prefs.get(KEY_POVRAY_INCLUDES, null); |
| if (loc != null) { |
| include = new File(loc); |
| if (!include.exists()) { |
| include = null; |
| } |
| } |
| if (include == null) { |
| include = new File(povray.getParentFile().getParent() |
| + File.separator + "include"); |
| if (!include.exists()) { |
| include = locate(Bundle.TTL_FindIncludeDir()); |
| if (include != null) { |
| prefs.put(KEY_POVRAY_INCLUDES, include.getPath()); |
| } else { |
| include = null; |
| } |
| } |
| } |
| return include; |
| }</pre> |
| |
| </li> |
| |
| </ol> |
| |
| </div> |
| |
| <h2 class="tutorial"><a name="executing"></a>Next Steps</h2> |
| |
| <p>The <a href="nbm-povray-7.html">next section</a> will cover actually |
| executing POV-Ray and piping its output to the Output window of our application.</p> |
| |
| </body> |
| |
| </html> |