blob: dcfb539a924abf7036df5b2ac74171b6d84b5d76 [file] [log] [blame]
<!doctype html>
<html class="no-js" lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Writing POV-Ray Support for NetBeans IX—Build Support</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Writing POV-Ray Support for NetBeans IX—Build Support - Apache NetBeans">
<meta name="author" content="Apache NetBeans">
<meta name="description" content="Writing POV-Ray Support for NetBeans IX—Build Support - Apache NetBeans">
<meta name="keywords" content="Apache NetBeans Platform, Platform Tutorials, Writing POV-Ray Support for NetBeans IX—Build Support">
<meta name="generator" content="Apache NetBeans">
<link rel="stylesheet" href="../../../../_/css/font-awesome.min.css">
<link rel="alternate" type="application/atom+xml" title="Apache NetBeans Blog" href="https://netbeans.apache.org/blogs/atom" />
<link rel="stylesheet" href="../../../../_/css/highlightjs/default.min.css">
<link rel="stylesheet" href="../../../../_/css/netbeans.css">
<link rel="apple-touch-icon" sizes="180x180" href="../../../../_/images/fav/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="../../../../_/images/fav/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="../../../../_/images/fav/favicon-16x16.png">
<link rel="manifest" href="../../../../_/images/fav/site.webmanifest">
<link rel="mask-icon" href="../../../../_/images/fav/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#ffc40d">
<meta name="theme-color" content="#ffffff">
<link href="../../../../_/css/font-open-sans.css" rel="stylesheet">
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
</head>
<body>
<div class="title-bar" data-responsive-toggle="responsive-menu" data-hide-for="medium">
<button type="button" data-toggle="responsive-menu"><i style='font-size: 32px; color: #fff; padding: 8px' class='fa fa-bars'></i></button>
<div class="title-bar-title">Apache NetBeans</div>
</div>
<div class="top-bar" id="responsive-menu">
<div class='top-bar-left'>
<a class='title' href="../../../../index.html"><img src='../../../../_/images/apache-netbeans.svg' style='padding: 8px; height: 48px;'> Apache NetBeans</a>
</div>
<div class="top-bar-right">
<ul class="vertical medium-horizontal menu" data-responsive-menu="drilldown medium-dropdown">
<li> <input id="search-input" type="text" placeholder="Search the docs"> </li>
<li> <a href="../../../../front/main/community">Community</a> </li>
<li> <a href="../../../../front/main/participate">Participate</a> </li>
<li> <a href="../../../../front/main/blogs">Blog</a></li>
<li> <a href="../../../../front/main/help">Get Help</a> </li>
<li> <a href="https://plugins.netbeans.apache.org/">Plugins</a> </li>
<li> <a href="../../../../front/main/download">Download</a> </li>
</ul>
</div>
</div>
<!-- src/templates/news -->
<section class="hero news alternate">
<div class='grid-container'>
<div class='cell'>
<div class="annotation">Latest release</div>
<h1>Apache NetBeans 28</h1>
<p><a class="button success" href="../../../../front/main/download/nb28">Download</a></p>
</div>
</div>
</section>
<div class='grid-container main-content tutorial'>
<h1 class="sect0">Writing POV-Ray Support for NetBeans IX—Build Support</h1>
<div class="sectionbody">
<div class="admonitionblock note">
<table>
<tbody><tr>
<td class="icon"><i class="fa icon-note" title="Note"></i></td>
<td class="content">This tutorial needs a review.
You can <a href="https://github.com/apache/netbeans-antora-tutorials/edit/main/modules/ROOT/pages/tutorials/nbm-povray-9.adoc" title="Edit this tutorial in github">edit it in GitHub </a>
following these <a href="../../../../tutorial/main/kb/docs/contributing">contribution guidelines.</a></td>
</tr></tbody>
</table>
</div>
</div>
<div id="toc" class="toc">
<div id="toctitle"></div>
<ul class="sectlevel1">
<li><a href="#_project_action_support">Project Action Support</a></li>
<li><a href="#_ensuring_there_is_a_main_file">Ensuring There Is A Main File</a></li>
<li><a href="#_extending_the_project_popup_menu">Extending the Project Popup Menu</a></li>
<li><a href="#_next_steps">Next Steps</a></li>
</ul>
</div>
<div id="preamble">
<div class="sectionbody">
<div class="paragraph">
<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/" class="xref page">first</a>, <a href="../nbm-povray-2/" class="xref page">second</a>, <a href="../nbm-povray-3/" class="xref page">third</a>, <a href="../nbm-povray-4/" class="xref page">fourth</a>, <a href="../nbm-povray-5/" class="xref page">fifth</a> <a href="../nbm-povray-6/" class="xref page">sixth</a>, <a href="../nbm-povray-7/" class="xref page">seventh</a>, and <a href="../nbm-povray-8/" class="xref page">eighth</a> parts of this tutorial, you may want to start there.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_project_action_support"><a class="anchor" href="#_project_action_support"></a>Project Action Support</h2>
<div class="sectionbody">
<div class="paragraph">
<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>
</div>
<div class="paragraph">
<p>Open <code>PovrayProject</code> in the code editor, and implement the methods of <code>ActionProviderImpl</code> as follows:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">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
&amp;amp;&amp;amp; getImagesFolder(false).getChildren().length &gt; 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;
}
}</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_ensuring_there_is_a_main_file"><a class="anchor" href="#_ensuring_there_is_a_main_file"></a>Ensuring There Is A Main File</h2>
<div class="sectionbody">
<div class="paragraph">
<p>We now have handling implemented for all of the standard project actions that make sense for a POV-Ray project. Now, if you&#8217;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>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">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()));
}</code></pre>
</div>
</div>
<div class="paragraph">
<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 &amp; Property Sheet API, together with the Dialogs APIs!</p>
</div>
<div class="paragraph">
<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>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>On the Povray Project module, following the procedure outlined in previous sections of this tutorial, add a module dependency on the Explorer &amp; Property Sheet API.</p>
</li>
</ol>
</div>
<div class="olist arabic">
<ol class="arabic" start="2">
<li>
<p>Right-click the <code>org.netbeans.examples.modules.povproject</code> package and chose New &gt; Other &gt; Swing GUI Forms &gt; JPanel Form. Click Next. Name The JPanel "MainFileChooser" and click Finish. The GUI Designer (Matisse) will open.</p>
</li>
</ol>
</div>
<div class="olist arabic">
<ol class="arabic" start="3">
<li>
<p>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.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>1.
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>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/povray_71_ch9_pic1.png" alt="povray 71 ch9 pic1">
</div>
</div>
<div class="olist arabic">
<ol class="arabic" start="5">
<li>
<p>Make sure the Properties window (Ctrl-Shift-7) is open. Select the <code>JLabel</code> and and then click the [&#8230;&#8203;] button for its <code>text</code> property in the Properties window. A custom editor will open:</p>
</li>
</ol>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/povray_71_ch9_pic2.png" alt="povray 71 ch9 pic2">
</div>
</div>
<div class="olist arabic">
<ol class="arabic" start="6">
<li>
<p>Change the drop-down at the top of the dialog to "Custom code", as shown below, and then type <code>Bundle.LBL_ChooseMainFile()</code> :</p>
</li>
</ol>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/povray_71_ch9_pic3.png" alt="povray 71 ch9 pic3">
</div>
</div>
<div class="paragraph">
<p>Click OK. Now add this annotation above the class signature:</p>
</div>
<div class="paragraph">
<p><strong>@NbBundle.Messages("LBL_ChooseMainFile=Select Main File")</strong></p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">public class MainFileChooser extends javax.swing.JPanel {</code></pre>
</div>
</div>
<div class="paragraph">
<p>Save the file. Now the annotation is converted to a String constant in a <code>Bundle</code> class and the reference to this class in the <code>initComponents</code> block should not show an error anymore.</p>
</div>
<div class="olist arabic">
<ol class="arabic" start="7">
<li>
<p>Edit the signature of the class so that it implements the interface <code>ExplorerManager.Provider</code>:</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">public class MainFileChooser extends javax.swing.JPanel implements ExplorerManager.Provider {</code></pre>
</div>
</div>
<div class="olist arabic">
<ol class="arabic" start="8">
<li>
<p>Add the following code to implement <code>ExplorerManager.Provider</code>:</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">private final ExplorerManager mgr = new ExplorerManager();
public ExplorerManager getExplorerManager() {
return mgr;
}</code></pre>
</div>
</div>
<div class="olist arabic">
<ol class="arabic" start="9">
<li>
<p>Modify the constructor so it reads as follows:</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">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);
}</code></pre>
</div>
</div>
<div class="paragraph">
<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>
</div>
<div class="paragraph">
<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&#8217;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>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<i class="fa icon-note" title="Note"></i>
</td>
<td class="content">
An error mark will remain for <code>ProjectFilterNode</code> because we have not yet written it.
</td>
</tr>
</table>
</div>
<div class="olist arabic">
<ol class="arabic" start="10">
<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>
</li>
</ol>
</div>
<div class="paragraph">
<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>
</div>
<div class="paragraph">
<p>We don&#8217;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&#8217;t want. Implement this as a nested class inside <code>MainFileChooser</code>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">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];
}
}</code></pre>
</div>
</div>
<div class="olist arabic">
<ol class="arabic" start="11">
<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>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">@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;
}
}</code></pre>
</div>
</div>
<div class="olist arabic">
<ol class="arabic" start="12">
<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>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">@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 &gt; 1, explorer is broken—we set
//single selection mode
assert n.length &lt;= 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);
}
}</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_extending_the_project_popup_menu"><a class="anchor" href="#_extending_the_project_popup_menu"></a>Extending the Project Popup Menu</h2>
<div class="sectionbody">
<div class="paragraph">
<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>
</div>
<div class="olist arabic">
<ol class="arabic" start="1">
<li>
<p>Open <code>PovrayLogicalView</code> in the editor, and find the inner <code>ScenesNode</code> class.</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>1.
Override <code>getActions()</code> as follows:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">@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;
}</code></pre>
</div>
</div>
<div class="paragraph">
<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>
</div>
<div class="olist arabic">
<ol class="arabic" start="3">
<li>
<p>Implement <code>ProjectAction</code> as follows:</p>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">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);
}
}</code></pre>
</div>
</div>
<div class="olist arabic">
<ol class="arabic" start="4">
<li>
<p>Then implement the brilliantly named <code>OtherProjectAction</code> this way, also as a nested class inside PovrayLogicalView. What we&#8217;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>
</li>
</ol>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">@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);
}
}
}</code></pre>
</div>
</div>
<div class="olist arabic">
<ol class="arabic" start="5">
<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>
</li>
</ol>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/povray_71_ch9_pic4.png" alt="povray 71 ch9 pic4">
</div>
</div>
<div class="paragraph">
<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>
</div>
<div class="imageblock">
<div class="content">
<img src="../../_images/tutorials/povray_71_ch9_pic5.png" alt="povray 71 ch9 pic5">
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_next_steps"><a class="anchor" href="#_next_steps"></a>Next Steps</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Congratulations! You have completed the POV-Ray tutorial. The <a href="../nbm-povray-10/" class="xref page">next step</a> is to review the concepts you&#8217;ve learned and then continue your journey on the NetBeans Platform.</p>
</div>
</div>
</div>
<section class='tools'>
<ul class="menu align-center">
<li><a title="Facebook" href="https://www.facebook.com/NetBeans"><i class="fa fa-md fa-facebook"></i></a></li>
<li><a title="Twitter" href="https://twitter.com/netbeans"><i class="fa fa-md fa-twitter"></i></a></li>
<li><a title="Github" href="https://github.com/apache/netbeans"><i class="fa fa-md fa-github"></i></a></li>
<li><a title="YouTube" href="https://www.youtube.com/user/netbeansvideos"><i class="fa fa-md fa-youtube"></i></a></li>
<li><a title="Atom Feed" href="https://netbeans.apache.org/blogs/atom"><i class="fa fa-mf fa-rss"></i></a></li>
<li><a title="Slack" href="https://tinyurl.com/netbeans-slack-signup/"><i class="fa fa-md fa-slack"></i></a></li>
<li><a title="Issues" href="https://github.com/apache/netbeans/issues"><i class="fa fa-mf fa-bug"></i></a></li>
</ul>
<ul class="menu align-center">
<li><a href="https://github.com/apache/netbeans-antora-tutorials/edit/main/modules/ROOT/pages/tutorials/nbm-povray-9.adoc" title="See this page in github"><i class="fa fa-md fa-edit"></i> See this page in GitHub.</a></li>
</ul>
</section>
</div>
<div class='grid-container incubator-area' style='margin-top: 64px'>
<div class='grid-x grid-padding-x'>
<div class='large-auto cell text-center'>
<a href="https://www.apache.org/">
<img style="height: 60px" title="Apache Software Foundation" src="../../../../_/images/asf_logo_wide.svg" />
</a>
</div>
<div class='large-auto cell text-center'>
<a href="https://www.apache.org/events/current-event.html">
<img style="width:234px; height: 60px;" title="Apache Software Foundation current event" src="https://www.apache.org/events/current-event-234x60.png"/>
</a>
</div>
</div>
</div>
<footer>
<div class="grid-container">
<div class="grid-x grid-padding-x">
<div class="large-auto cell">
<h1><a href="../../../../front/main/about">About</a></h1>
<ul>
<li><a href="../../../../front/main/community/who">Who's Who</a></li>
<li><a href="https://www.apache.org/foundation/thanks.html">Thanks</a></li>
<li><a href="https://www.apache.org/foundation/sponsorship.html">Sponsorship</a></li>
<li><a href="https://www.apache.org/security/">Security</a></li>
</ul>
</div>
<div class="large-auto cell">
<h1><a href="../../../../front/main/community">Community</a></h1>
<ul>
<li><a href="../../../../front/main/community/mailing-lists">Mailing lists</a></li>
<li><a href="../../../../front/main/community/committer">Becoming a committer</a></li>
<li><a href="../../../../front/main/community/events">NetBeans Events</a></li>
<li><a href="https://www.apache.org/events/current-event.html">Apache Events</a></li>
</ul>
</div>
<div class="large-auto cell">
<h1><a href="../../../../front/main/participate">Participate</a></h1>
<ul>
<li><a href="../../../../front/main/participate/submit-pr">Submitting Pull Requests</a></li>
<li><a href="../../../../front/main/participate/report-issue">Reporting Issues</a></li>
<li><a href="../../../../front/main/participate/#documentation">Improving the documentation</a></li>
</ul>
</div>
<div class="large-auto cell">
<h1><a href="../../../../front/main/help">Get Help</a></h1>
<ul>
<li><a href="../../../../front/main/help/#documentation">Documentation</a></li>
<li><a href="../../../../wiki/main/wiki">Wiki</a></li>
<li><a href="../../../../front/main/help/#support">Community Support</a></li>
<li><a href="../../../../front/main/help/commercial-support">Commercial Support</a></li>
</ul>
</div>
<div class="large-auto cell">
<h1><a href="../../../../front/main/download">Download</a></h1>
<ul>
<li><a href="../../../../front/main/download">Releases</a></li>
<li><a href="https://plugins.netbeans.apache.org/">Plugins</a></li>
<li><a href="../../../../front/main/download/#_daily_builds_and_building_from_source">Building from source</a></li>
<li><a href="../../../../front/main/download/#_older_releases">Previous releases</a></li>
</ul>
</div>
</div>
</div>
</footer>
<div class='footer-disclaimer'>
<div class="footer-disclaimer-content">
<p>Copyright &copy; 2017-2025 <a href="https://www.apache.org">The Apache Software Foundation</a>.</p>
<p>Licensed under the Apache <a href="https://www.apache.org/licenses/">license</a>, version 2.0</p>
<div style='max-width: 40em; margin: 0 auto'>
<p>Apache, Apache NetBeans, NetBeans, the Apache feather logo and the Apache NetBeans logo are trademarks of <a href="https://www.apache.org">The Apache Software Foundation</a>.</p>
<p>Oracle and Java are registered trademarks of Oracle and/or its affiliates.</p>
<p>The Apache NetBeans website conforms to the <a href="https://privacy.apache.org/policies/privacy-policy-public.html">Apache Software Foundation Privacy Policy</a></p>
</div>
</div>
</div>
<script src="../../../../_/js/vendor/lunr.js"></script>
<script src="../../../../_/js/search-ui.js" id="search-ui-script" data-site-root-path="../../../.." data-snippet-length="100" data-stylesheet="../../../../_/css/search.css"></script>
<script async src="../../../../search-index.js"></script>
<script src="../../../../_/js/vendor/jquery.min.js"></script>
<script src="../../../../_/js/vendor/what-input.min.js"></script>
<script src="../../../../_/js/vendor/foundation.min.js"></script>
<script src="../../../../_/js/vendor/jquery.colorbox-min.js"></script>
<script src="../../../../_/js/netbeans.js"></script>
<script>
$(function(){ $(document).foundation(); });
</script>
<script src="../../../../_/js/vendor/highlight.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', (event) => {
document.querySelectorAll('pre code').forEach((el) => {
hljs.highlightElement(el);
});
});
</script>
</body>
</html>