September 8, 2009

NetBeans Platform: Implement Perforce client - part II

Module preferences for PerforceNB.

Usually options for NetBeans modules are handled via option panel. For that we can use a specialized wizard. You can start the wizard from the contextual menu of the project, where we fire the New - Other - Module Development - Options Panel. In this way we can create primary or secondary panels for options.

This is not our case. That is because for versioning system options, NetBeans has created a panel already. You can find that panel from the main menu by Tools - Options - Miscellaneous - Versioning. Take a look on the dialog box and see what comes next. Our job is to insert another item in the list of versioning systems, handling preferences for Perforce.

We achieve that by extending org.netbeans.spi.options.AdvancedOption. This class represents an advanced interface element for options dialog. We create a new class which extends AdvancedOption.

After that we must configure the layout.xml to inject in the interface this element. The programmatic way is to modify by hand the layout.xml file. This file will look like below:


<filesystem>
    <folder name="VersioningOptionsDialog">
        <file name="PerforceOptions.instance">
            <attr name="instanceClass" stringvalue="org.padreati.perforcenb.ui.PerforceOptions"/>
        </file>
    </folder>
</filesystem>


Another option to generate this content is to find in the IDE the layer GUI editor. Use <this layer in context> representation. Find VersioningOptionsDialog folder. There use context menu to create a new file called PerforceOptions.instance and so on.


Now we injected our UI element in NetBeans IDE. We implement the class to provide appropriate values. I implemented AdvancedOption in class PerforceOptions.


public class PerforceOptions extends AdvancedOption {
    @Override
    public String getDisplayName() {
        return NbBundle.getMessage(PerforceVS.class, "CTL_Perforce_DisplayName");
    }

    @Override
    public String getTooltip() {
        return NbBundle.getMessage(PerforceVS.class, "CTL_Perforce_OptionsTooltip");
    }

    @Override
    public OptionsPanelController create() {
        return new PerforceOptionsPanelController();
    }
}


The listing is clear, we provide a display name and a tooltip for the UI element. I used resources for that. The main method here is create() which returns an instance of PerforceOptionsPanelController.An OptionPanelController is the controller behind UI for managing options (MVC sounds familiar? that's one reason why I love NetBeans and Swing). The controller handles operations of managing data. Here is the listing:

public class PerforceOptionsPanelController extends OptionsPanelController {
    private PerforceOptionsPanel panel;

    public PerforceOptionsPanelController() {
        panel = new PerforceOptionsPanel();
    }

    @Override
    public void update() {
        PerforceModuleConfig config = PerforceModuleConfig.getInstance();
        config.reload();
        panel.setPath(config.getP4Path());
        panel.setDefaultPort(config.getP4DefaultPort());
        panel.setDefaultWorkspace(config.getP4DefaultWorkspace());
    }

    @Override
    public void applyChanges() {
        PerforceModuleConfig config = PerforceModuleConfig.getInstance();
        config.setP4Path(panel.getPath());
        config.setP4DefaultPort(panel.getDefaultPort());
        config.setP4DefaultWorkspace(panel.getDefaultWorkspace());
        config.store();
    }

    @Override
    public void cancel() {
        PerforceModuleConfig.getInstance().reload();
    }

    @Override
    public boolean isValid() {
        return true;
    }

    @Override
    public boolean isChanged() {
        return panel.isDirty();
    }

    @Override
    public JComponent getComponent(Lookup masterLookup) {
        return panel;
    }

    @Override
    public HelpCtx getHelpCtx() {
        return new HelpCtx(PerforceOptionsPanel.class);
    }

    @Override
    public void addPropertyChangeListener(PropertyChangeListener l) {
    }

    @Override
    public void removePropertyChangeListener(PropertyChangeListener l) {
    }

}

There are only some methods which are really relevant. The update method is called when IDE loads configuration data from storage, I update the interface at that moment. The applyChanges is called when a save action is fired from interface. Use isChanged to tell IDE if the configuration data was updated in UI. The UI is specified in getComponent method. This method returns an instance of PerforceOptionsPanel, a class which extends JPanel.

This is the interface. Here is how it looks:


For managing the preferences of the module I created a class called PerforceModuleConfig. This is a singleton which calls NbPreferences to persist it's properties. This class I will use in our module to see which are the options for PerforceNB module. Here is a listing:

public final class PerforceModuleConfig {
    private static PerforceModuleConfig instance;

    private final static String P4_PATH_KEY = "P4_PATH_KEY";
    private final static String P4_DEFAULT_PORT_KEY = "P4_DEFAULT_PORT_KEY";
    private final static String P4_DEFAULT_WORKSPACE_KEY = "P4_DEFAULT_WORKSPACE_KEY";

    private String p4Path;
    private String p4DefaultPort;
    private String p4DefaultWorkspace;

    private PerforceModuleConfig() {
    }

    public static PerforceModuleConfig getInstance() {
        if(instance==null) {
            instance = new PerforceModuleConfig();
        }
        return instance;
    }

    public String getP4Path() {
        return p4Path;
    }

    public void setP4Path(String p4Path) {
        this.p4Path = p4Path;
    }

    public String getP4DefaultPort() {
        return p4DefaultPort;
    }

    public void setP4DefaultPort(String p4DefaultPort) {
        this.p4DefaultPort = p4DefaultPort;
    }

    public String getP4DefaultWorkspace() {
        return p4DefaultWorkspace;
    }

    public void setP4DefaultWorkspace(String p4DefaultWorkspace) {
        this.p4DefaultWorkspace = p4DefaultWorkspace;
    }

    public void reload() {
        Preferences pref = NbPreferences.forModule(PerforceModuleConfig.class);
        String p4PathDefault = "p4";
        if(System.getProperty("os.name").startsWith("Windows")) {
            p4PathDefault = "p4.exe";
        }
        setP4Path(pref.get(P4_PATH_KEY, p4PathDefault));
        setP4DefaultPort(pref.get(P4_DEFAULT_PORT_KEY, ""));
        setP4DefaultWorkspace(pref.get(P4_DEFAULT_WORKSPACE_KEY, ""));
    }

    public void store() {
        Preferences pref = NbPreferences.forModule(PerforceModuleConfig.class);
        pref.put(P4_PATH_KEY, getP4Path());
        pref.put(P4_DEFAULT_PORT_KEY, getP4DefaultPort());
        pref.put(P4_DEFAULT_WORKSPACE_KEY, getP4DefaultWorkspace());
    }
}

The content is obvious. Only notice that I have used NbPreferences, a utility class from NetBeans Platform for persisting preferences. Very useful one. Here is the result of my work from this episode.


As usual, you can see the whole project and code from the kenai project page at http://kenai.com/projects/perforcenb. See you on the next episode.

No comments:

Post a Comment