New File Dialog for Specific Template and Target Folder

Sometimes our project structure separates files by its extension (or, to be specific, its filetype), for example folder scenes in POV-Ray project here contains .inc and .pov files only. So it makes sense if our New File action on project root folder is restricted to several predefined filetypes, and these new files will be directly created in their respective folder.

projectstructure

That’s the structure of my project, where only those .cor files will be displayed under “Corak files”, and .lay files in “Layout files”. Actually those two nodes represent two directories on disk, so it’s possible for them to contain any file but my FilterNode will only display those files with .cor and .lay extension.

The first, default approach is by using CommonProjectActions.newFileAction() and put it on project’s root node. This way, we let user to select the filetype first, then our InstantiatingIterator.instantiate() will take care about the target folder.

But my client want a more to-the-point approach : the “Corak files” node, for example, should have an action to create new Corak file, that is, those with .cor extension. A little googling brings me to TemplateWizard class, which is part of Datasystems API. It has instantiate(DataObject template, DataFolder targetFolder) method which allows us to skip the first panel of new file wizard (assuming template is not null and a valid, registered template). This way, indeed we can skip the first panel, but the Back button will always be there and makes it possible for curious user to click it, select another filetype and finally ruin our plan.

Eventually I use a hardcoded approach, in which both template and target folder are specified on InstantiatingIterator subclasses. The only information I need from the action invoked is the Project that will own the file, so I create my action to be context-sensitive to Project instance. Below is the code snippet from the Action..

public final class NewLayoutWizardAction implements ActionListener {

    private Project context;

    public NewLayoutWizardAction(Project context) {
        this.context = context;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        WizardDescriptor wiz = new WizardDescriptor(new NewLayoutWizardIterator(context));
        // {0} will be replaced by WizardDesriptor.Panel.getComponent().getName()
        wiz.setTitleFormat(new MessageFormat("{0}"));
        wiz.setTitle(Bundle.CTL_NewLayoutFile());
        if (DialogDisplayer.getDefault().notify(wiz) == WizardDescriptor.FINISH_OPTION) {
            try {
                executeInstantiated(wiz);
            } catch (IOException ex) {
                Exceptions.printStackTrace(ex);
            }

        }
    }

    /**
     * The code to execute default action for the newly created dataobject.
     */
    private void executeInstantiated(WizardDescriptor wiz) throws IOException {
        for (Object o : wiz.getInstantiatedObjects()) {
            if (o instanceof FileObject) {
                DataObject obj = DataObject.find((FileObject) o);
                // run default action (hopefully should be here)
                final Node node = obj.getNodeDelegate();
                Action _a = node.getPreferredAction();
                if (_a instanceof ContextAwareAction) {
                    _a = ((ContextAwareAction) _a).createContextAwareInstance(node.getLookup());
                }
                final Action a = _a;
                if (a != null) {
                    SwingUtilities.invokeLater(() -> {
                        a.actionPerformed(new ActionEvent(node, ActionEvent.ACTION_PERFORMED, ""));
                    });
                }
            }
        }
    }
}

The executeInstantiated is just for ethical reason, since it’s confusing if a new file is created on disk but nothing happened on GUI. So we open it.

And below is the snippet from the Iterator..

public final class NewLayoutWizardIterator implements WizardDescriptor.InstantiatingIterator<WizardDescriptor> {

    private static final String LAYOUT_TEMPLATE_PATH = "Templates/JArsi/LayTemplate.lay"; //NO18N
    private Project project;
    ...
    public NewLayoutWizardIterator(Project context) {
        this.project = context;
    }

    private List<WizardDescriptor.Panel<WizardDescriptor>> getPanels() {
        if (panels == null) {
            panels = new ArrayList<>();
            panels.add(new NewLayoutWizardPanel1(project,wizard));
            String[] steps = new String[panels.size()]; //REMOVE THAT createSteps
            ...
        }
        return panels;
    }

    @Override
    public Set<?> instantiate() throws IOException {
        //Get the new file name:
        String targetName = (String) wizard.getProperty(NewLayoutWizardPanel1.WIZARD_KEY_TARGET_NAME);

        //Specify the target directory
        FileObject laysDirFO = LayoutFileUtil.getLayoutsFolder(project, false);
        DataFolder laysDirDF = DataFolder.findFolder(laysDirFO);

        //Specify the template defined somewhere in XML layer
        FileObject template = FileUtil.getConfigFile(LAYOUT_TEMPLATE_PATH);
        DataObject dTemplate = DataObject.find(template);

        //create Map for freemarker template engine
        Map hashMap = new HashMap();
        hashMap.put("width", wizard.getProperty(NewLayoutWizardPanel1.WIDTH));
        hashMap.put("height", wizard.getProperty(NewLayoutWizardPanel1.HEIGHT));
        hashMap.put("unit", wizard.getProperty(NewLayoutWizardPanel1.UNIT));

        //Create new DataObject
        DataObject dobj = dTemplate.createFromTemplate(laysDirDF, targetName, hashMap);

        //Obtain a FileObject:
        FileObject createdFile = dobj.getPrimaryFile();

        //Create the new file:
        return Collections.singleton(createdFile);
    }

    ...    

}

NB : A less restricted approach can be achieved using PrivilegedTemplates and RecommendedTemplates as described here.

This entry was posted in netbeans platform and tagged , . Bookmark the permalink.

Comments are closed.