X

Geertjan's Blog

  • June 4, 2006

System Options in NetBeans IDE

Geertjan Wielenga
Product Manager
System options in NetBeans IDE are traditionally provided by extending the NetBeans API's System Option class. However, this class is being rewritten. Or, in fact, the whole approach to system options is being reappraised. At the same time, though, users are creating modules that need options to be set. In some cases, this NetBeans API class is still being used even while it is being rewritten (planned for NetBeans 6.0, so still some time away). For example, Alex Lam's Napkin module makes use of this class. So does Chuk Lee Munn's module for integrating Star Office into NetBeans IDE. So did Sandip during his module development hands on lab at JavaOne. Personally, because I know that this class is going to be replaced, I'm not using it. But then, I never used that class in the first place.

Why I never used it in the first place is because my first encounter with providing a way to set options for NetBeans modules was via Masoud's Gmail Checker modules. And he didn't use the System Option class at all (because he didn't know about it). I don't know anything about the the System Option class, so I can't compare Masoud's approach to the "official" approach. However, Masoud's basic concept is to create an external file, write the settings to it, and then load the file when needed. Jens Trapp, when writing his module for integrating HTML Tidy (discussed here), uses a similar approach, one that is very clean and clear and ready to use.

So, his is the approach I've been following in my own modules, such as in the module that enables PDF files to be generated from files in NetBeans IDE, as discussed a few blog entries ago. A lot of options are settable via that module, as scrolling back to those blog entries will show you. So, here's an outline of how you can take this approach. What follows is a complete working sample that lets you choose a file from your file system and then displays your choice in a JOptionPane. Importantly, when you restart the IDE, the chosen file will still be displayed in the Options window. After all, that's a basic requirement of a system option—it must persist across the NetBeans IDE's (or any NetBeans Platform application's) restarts.

  1. Create project. Create a module project. Make it a standalone module. Call it MyOptions. Accept all of the defaults and click Finish.

  2. Generate a Skeleton for the Options Window Extension. In the Projects window, right-click the MyOptions project node. Choose New > File/Folder. Then choose Options Panel from the NetBeans Module Development category. (This assumes that you're either using a NetBeans IDE 5.5 build or that you have the Module Development Update 1 module installed in NetBeans IDE 5.0. If this is not the case, you will not see "Options Panel" in the NetBeans Modulde Development" category.)
    Click Next. Type "My Brilliant Options" in the Title text field. Same in the Tool Tip text field. (So, we're extending the existing "Miscellaneous panel", which is where the GUI Builder settings and Ant settings are found by default. If you wanted to create a complete new panel, on the level of "General" or "Miscellaneous", you'd click "Create Primary Panel" and fill that section out instead.)
    Click Next, accept the default settings, click Finish.

  3. Create the User Interface. Expand the MyOptions project node, expand Source Packages, and double-click on MyoptionsPanel.java. Drag and drop a JTextField, a JLabel and a JButton onto the Design view. Leave the default names jTextField1, jLabel1, and jButton1. Move things around and make it look like this:

  4. Create the Event Handler. Right-click on the Browse button, choose Events > Action > actionPerformed. Now stick this between the opening and closing lines of the method (thanks to Jens, as pointed out above):

    String filename = jTextField1.getText ();
    JFileChooser chooser = new JFileChooser (new File (filename));
    // Show open dialog; this method does not return until the dialog is closed
    int result = chooser.showOpenDialog (this);
    // Determine which button was clicked to close the dialog
    switch (result)
    {
    case JFileChooser.APPROVE_OPTION:
    File selFile = chooser.getSelectedFile ();
    jTextField1.setText (selFile.getAbsolutePath ());
    break;
    case JFileChooser.CANCEL_OPTION:
    // Cancel or the close-dialog icon was clicked
    break;
    case JFileChooser.ERROR_OPTION:
    // The selection process did not complete successfully
    break;
    }

    You'll see many lines of code underlined in red. So right-click in the Source Editor, choose Fix Imports, and choose Java.io.File from the Fix Imports dialog box. All the red underlinings should now disappear.

  5. Make the Option Accessible. First, add a getter and setter to MyoptionsPanel.java, as shown here:

    public String getFile() {
    return jTextField1.getText();
    }
    public void setFile(String executable) {
    jTextField1.setText(executable);
    }

    Replace the default Constructor with this one and fix imports:

    public MyoptionsPanel(String executable) {
    initComponents();
    jButton1.setText(NbBundle.getMessage(MyoptionsPanel.class, "MyOptionBrowseButton"));
    jLabel1.setText(NbBundle.getMessage(MyoptionsPanel.class, "MyOptionLabel"));
    jTextField1.setText(executable);
    }

    Now you'll get an error about the variable controller not being initialized. That's because it was initialized in the default constructor that you deleted. So delete the variable. We won't be using it.

    Next, make sure to define key-value pairs in Bundle.properties for MyOptionBrowseButton and MyOptionLabel:

    MyOptionBrowseButton=Browse...
    MyOptionLabel=File:

  6. Create the Background Infrastructure. Next, use the New File wizard to add a Java file called MyoptionsSettings.java. The purpose of this file is to create a folder and a properties file for storing the option. The folder will be found within the user directory (either of NetBeans IDE or on whatever application based on the NetBeans Platform that provides the option). Also, the file contains the store() and load() methods for saving and retrieving the option from the file. There's a Constructor for initializing the Options window extension. Finally, the file contains a getValue() method and a setValue() method for transferring the option from the IDE to the file. Here's the whole content of this file:

    package org.yourorghere.myoptions;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.PrintStream;
    import java.util.HashMap;
    import java.util.Properties;
    import org.openide.filesystems.FileLock;
    import org.openide.filesystems.FileObject;
    import org.openide.filesystems.Repository;
    public class MyoptionsSettings
    {
    private FileObject settingsFolder;
    private FileObject settingsFile;
    private FileLock lock;
    private Properties settings;
    /\*\* Configuration file base name\*/
    public static String PROPERTIES_BASE = "myoptions";
    /\*\* Configuration file extension\*/
    public static String PROPERTIES_EXTENSION = "properties";
    /\*\* Key under which the executable path is stored in the properties file\*/
    public static String KEY_EXECUTABLE="executable";
    /\*\* There can only be one!\*/
    private static MyoptionsSettings SINGLETON=null;
    private MyoptionsSettings ()
    {
    settings = new Properties ();
    setDefaultValues ();
    settingsFolder= Repository.getDefault ().getDefaultFileSystem ().getRoot ().getFileObject ("Settings");
    if (settingsFolder==null)
    {
    try
    {
    settingsFolder=Repository.getDefault ().getDefaultFileSystem ().getRoot ().createFolder ("Settings");
    store ();
    }
    catch (IOException ex)
    {
    ex.printStackTrace ();
    }
    }
    else
    {
    load ();
    }
    }
    public static MyoptionsSettings getSettings() {
    if (SINGLETON==null)
    SINGLETON=new MyoptionsSettings();
    return SINGLETON;
    }
    public void setDefaultValues ()
    {
    settings.clear ();
    settings.put (KEY_EXECUTABLE, "");
    }
    public void store ()
    {
    try
    {
    settingsFile = settingsFolder.getFileObject (PROPERTIES_BASE,PROPERTIES_EXTENSION);
    if (settingsFile==null)
    {
    settingsFile = settingsFolder.createData (PROPERTIES_BASE,PROPERTIES_EXTENSION);
    }
    lock = settingsFile.lock ();
    OutputStream out = settingsFile.getOutputStream (lock);
    settings.storeToXML (out,"Configuration File for My Options");
    out.close ();
    lock.releaseLock ();
    }
    catch (IOException ex)
    {
    // TODO file can not be created , do something about it
    ex.printStackTrace ();
    }
    }
    public void load ()
    {
    settingsFile = settingsFolder.getFileObject (PROPERTIES_BASE,PROPERTIES_EXTENSION);
    if (settingsFile! = null)
    {
    try
    {
    InputStream in = settingsFile.getInputStream ();
    settings.loadFromXML (in);
    in.close ();
    }
    catch (IOException ex)
    {
    ex.printStackTrace ();
    }
    }
    }
    public String getValue (String key)
    {
    return settings.getProperty (key);
    }
    public void setValue (String key, String value)
    {
    settings.setProperty (key, value.trim ());
    }
    }

    To fix the red underlinings, you need to declare a dependency on the File System API. (Right-click on the project node, choose Properties, click Libraries, click Add, and find the File System API.)

  7. Hook the Background Infrastructure to the User Interface. Right-click the project node and choose Build Project. Notice that the Output window shows an error hyperlink. Click the hyperlink. The MyoptionsOptionsPanelController.java file opens. This file is very important. It controls the Options Panel extension that you're creating. It listens for changes and provides opportunities for you to reacts to updates, changes, and so on.

    First, delete the getPanel method, which is where the first error is found. That method we won't need. Then another error is identified, this time in the getComponent() method. Here, instead of returning getPanel, return panel, which is declared at the top of the file by default. Declare the settings file that you created, at the top of the file:

    private MyoptionsSettings settings;

    Then add a Constructor for initializing the Options Window extension:

    public MyoptionsOptionsPanelController() {
    settings=MyoptionsSettings.getSettings();
    panel = new MyoptionsPanel(settings.getValue(settings.KEY_EXECUTABLE));
    }

    The methods update, applyChanges, isValid, and isChanged should be changed to the following:

    public void update() {
    panel.setFile(settings.getValue(settings.KEY_EXECUTABLE));
    }
    public void applyChanges() {
    settings.setValue(settings.KEY_EXECUTABLE, panel.getFile());
    settings.store();
    changed=false;
    }
    public boolean isValid() {
    return panel.valid();
    }
    public boolean isChanged() {
    return panel.getFile().trim().equals(settings.getValue(settings.KEY_EXECUTABLE));
    }

  8. Install and Test. Install the module. Go to the Options window. In the Miscellaneous category, browse to a file. Now, after clicking OK in the Options window... look inside the user directory's config folder and you'll see, firstly, a new settings folder and, secondly, the fact that this folder contains a file called myoptions.properties, which is an XML file with this content:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
    <properties>
    <comment>Configuration File for My Options</comment>
    <entry key="executable">C:\\myfile.png</entry>
    </properties>

    Restart the IDE and notice that the value you specified is still there when you look in the Options window. (This means the value persists across restarts, which is what you want when working with system options.)

  9. Incorporate. Now you need to use the value in your module. In other words, depending on the file selected, something should or should not happen, depending on what you want to do with your module. As a simple example, create a new Action class (use the New Action wizard), declare the settings file (private MyoptionsSettings settings; at the top of your Action class), and then put this in your performAction():

    settings=MyoptionsSettings.getSettings();
    JOptionPane.showMessageDialog(null,settings.getValue(settings.KEY_EXECUTABLE));

    Invoke the action from wherever you installed it in your menu bar (or wherever else) and you'll see a JOptionPane containing the currently set value for the option in the Options window.

As pointed out at the start of this blog entry, this approach isn't "official" in any way (and I'm half expecting irate people to comment on this fact). However, it is an approach that doesn't make use of the NetBeans APIs (apart from the APIs involved with the Options Window extension itself and a little bit with the File System API), which is a good thing when it comes to the System Option class, since it is being revised and relooked at and will more than likely be changed sometime in the middle future.

Join the discussion

Comments ( 4 )
  • Ram&oacute;n Sunday, June 4, 2006
    Now I find that the easiest way to handle options is to use the Preferences API, like the generated code for the options panel also suggests (among with the system option alternative):

    void load() {

    // TODO read settings and initialize GUI

    // Example:
    //someCheckBox.setSelected(Preferences.userNodeForPackage(Temporary_modulePanel.class).getBoolean("someFlag",
    false));

    }


    void store() {

    // TODO store modified settings

    // Example:

    //Preferences.userNodeForPackage(Temporary_modulePanel.class).putBoolean("someFlag",
    someCheckBox.isSelected());
    }

    It is almost no work at all ;)

    Ramón


  • Geertjan Sunday, June 4, 2006
    I've seen that code Ramon, in the generate code. But I haven't seen the Preferences API. How do I declare a dependency on it? And does it produce an external file where I can see the values I specified?
  • Ram&oacute;n Sunday, June 4, 2006
    As I understand it, it writes to the windows registry in windows and to a .java directory or the like in linux.

    I think Tom Ball had writen something about it in a mailing list.

    Under windows you can easily find the value with regedit (like I did).

    The Preferences API is inside the JDK (java.util.prefs.Preferences).

    I also had read that the new way of storing prefs in NetBeans will be based on this API.
  • David Strupl Sunday, June 4, 2006
    Ramon is right - using Preferences API is the simplest way and is also suggested by the API Support modules. It is part of JDK - you don't need to declare any dependency on it. Should be even more supported in the future. And even if it is not supported by NB API - it is still part of JDK and you can use it ...
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.