NetBeans Forums

 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister   ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 
  

How to register actions programmatically with NetBeans Platform

 
Post new topic   Reply to topic    NetBeans Forums -> NetBeans Platform Users
View previous topic :: View next topic  
Author Message
winder



Joined: 10 Mar 2015
Posts: 16

PostPosted: Fri Mar 11, 2016 12:01 am    Post subject: How to register actions programmatically with NetBeans Platform Reply with quote

I need to register some actions with NetBeans Platform that will show up as Menu items and have configurable Keymap bindings.

Creating individual action classes with annotations works great, but many of my actions invoke simple calls to the same service providers using slightly different arguments. I would like to create them programmatically rather than by creating lots of wrapper classes with lots of annotations.

For example:
Code:

public class JogZPlus extends AbstractAction implements ContextAwareAction {
    JogService jogService;

    public JogZPlus() {
        // Lookup my service class.
        jogService = Lookup.getDefault().lookup(JogService.class);
    }
       
    @Override
    public void actionPerformed(ActionEvent e) {
        // Call the service... repeat 5 more times for +/- X/Y/Z motion...
        // And many more times if I want to add in diagonals.
        jogService.adjustManualLocation(0, 0, 1);
    }

    ...
}


Now, I've figured out how to dynamically add items to the menu (thanks to one of Geertjan's many fantastic tutorials). Then I looked up the Keymap.class object, but haven't figured out how to register the actions with a default keybinding.

Here is how I was able to dynamically create menu actions (this involved creating the menu location in a layer.xml file):
Code:

    private void initActions() {
        SwingUtilities.invokeLater(() -> {
            FileObject menuFolder = FileUtil.getConfigFile("Menu/Machine/Jog");
            try {
                // This way its very obvious what actions are being created.
                registerAction("JogService.xPlus" , menuFolder, 1, 0, 0);
                registerAction("JogService.xMinus", menuFolder,-1, 0, 0);
                registerAction("JogService.yPlus" , menuFolder, 0, 1, 0);
                registerAction("JogService.yMinus", menuFolder, 0,-1, 0);
                registerAction("JogService.zPlus" , menuFolder, 0, 0, 1);
                registerAction("JogService.zMinus", menuFolder, 0, 0,-1);
            } catch (IOException ex) {
                Exceptions.printStackTrace(ex);
            }
        });
    }

    private void registerAction(String nameId, FileObject in, int x, int y, int z) throws IOException {
        String name = NbBundle.getMessage(JogService.class, nameId);

        // Create if missing.
        FileObject menu = in.getFileObject(name, "instance");

        // With this approach I noticed that I only need to create the instance the first time.
        if (menu == null) {
            menu = in.createData(name, "instance");

            // Now... how to register this instance with the Keymap?
        }

        AbstractAction action = new JogAction(name, this, x, y, z);
        menu.setAttribute("instanceCreate", action);
        menu.setAttribute("instanceClass", action.getClass().getName());
    }

    protected class JogAction extends AbstractAction {
        JogService js;
        int x,y,z;

        public JogAction(String name, JogService service, int x, int y, int z) {
            super(name);
            js = service;
            this.x = x;
            this.y = y;
            this.z = z;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            js.adjustManualLocation(x, y, z);
        }

        @Override
        public boolean isEnabled() {
            return js.canJog();
        }
    }


Thanks!
Back to top
markiewb



Joined: 29 Aug 2012
Posts: 870

PostPosted: Fri Mar 11, 2016 6:18 pm    Post subject: How to register actions programmatically with NetBeans Platform Reply with quote

Hej Will,


you have to add entries for the shortcuts via code to the layer.xml too.

To see the target format follow my description.
  1. Create a new module
  2. Create a layer.xml in this module using the "New file"-wizard
  3. Create an action via the "New Action"-wizard. Make sure to input some keystrokes at global keyboard shortcuts
  4. Expand the layer.xml file in the project explorer. Choose "this context" layer. Select shortcuts, right click an "go to declaration". This way NB shows you the generated layer.xml-entries for your action (based on the annotations)


Now you know the target format - the folders, tags and attributes for a shortcut registration. Using this knowledge you can create a shortcut registration via code (FileUtil.getConfigFile(...) and .setAttribute()) yourself. 


More resources on the format http://wiki.netbeans.org/DevFaqKeybindings 



Once you created a working solution, it would be great, if you share the created helper method at http://wiki.netbeans.org/NetBeansDeveloperFAQ Thank you in advance!





With kind regards, markiewb



2016-03-11 1:01 GMT+01:00 winder <address-removed ([email]address-removed[/email])>:
Quote:
I need to register some actions with NetBeans Platform that will show up as Menu items and have configurable Keymap bindings.

Creating individual action classes with annotations works great, but many of my actions invoke simple calls to the same service providers using slightly different arguments. I would like to create them programmatically rather than by creating lots of wrapper classes with lots of annotations.

For example:

Code:

public class JogZPlus extends AbstractAction implements ContextAwareAction {
    JogService jogService;

    public JogZPlus() {
        // Lookup my service class.
        jogService = Lookup.getDefault().lookup(JogService.class);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // Call the service... repeat 5 more times for +/- X/Y/Z motion...
        // And many more times if I want to add in diagonals.
        jogService.adjustManualLocation(0, 0, 1);
    }

    ...
}




Now, I've figured out how to dynamically add items to the menu (thanks to one of Geertjan's many fantastic tutorials ([url=https://blogs.oracle.com/geertjan/entry/dynamically_creating_menu_items_part)]https://blogs.oracle.com/geertjan/entry/dynamically_creating_menu_items_part)[/url]). Then I looked up the Keymap.class object, but haven't figured out how to register the actions with a default keybinding.

Here is how I was able to dynamically create menu actions (this involved creating the menu location in a layer.xml file):

Code:

    private void initActions() {
        SwingUtilities.invokeLater(() -> {
            FileObject menuFolder = FileUtil.getConfigFile("Menu/Machine/Jog");
            try {
                // This way its very obvious what actions are being created.
                registerAction("JogService.xPlus" , menuFolder, 1, 0, 0);
                registerAction("JogService.xMinus", menuFolder,-1, 0, 0);
                registerAction("JogService.yPlus" , menuFolder, 0, 1, 0);
                registerAction("JogService.yMinus", menuFolder, 0,-1, 0);
                registerAction("JogService.zPlus" , menuFolder, 0, 0, 1);
                registerAction("JogService.zMinus", menuFolder, 0, 0,-1);
            } catch (IOException ex) {
                Exceptions.printStackTrace(ex);
            }
        });
    }

    private void registerAction(String nameId, FileObject in, int x, int y, int z) throws IOException {
        String name = NbBundle.getMessage(JogService.class, nameId);

        // Create if missing.
        FileObject menu = in.getFileObject(name, "instance");

        // With this approach I noticed that I only need to create the instance the first time.
        if (menu == null) {
            menu = in.createData(name, "instance");

            // Now... how to register this instance with the Keymap?
        }

        AbstractAction action = new JogAction(name, this, x, y, z);
        menu.setAttribute("instanceCreate", action);
        menu.setAttribute("instanceClass", action.getClass().getName());
    }

    protected class JogAction extends AbstractAction {
        JogService js;
        int x,y,z;

        public JogAction(String name, JogService service, int x, int y, int z) {
            super(name);
            js = service;
            this.x = x;
            this.y = y;
            this.z = z;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            js.adjustManualLocation(x, y, z);
        }

        @Override
        public boolean isEnabled() {
            return js.canJog();
        }
    }




Thanks!







2016-03-11_19h05_02.png
 Description:
 Filesize:  143.67 KB
 Viewed:  2986 Time(s)

2016-03-11_19h05_02.png



2016-03-11_19h02_59.png
 Description:
 Filesize:  27.73 KB
 Viewed:  2986 Time(s)

2016-03-11_19h02_59.png


Back to top
winder



Joined: 10 Mar 2015
Posts: 16

PostPosted: Fri Mar 11, 2016 11:49 pm    Post subject: Reply with quote

Thanks Mark, I think I'm on the right track.

I'm now trying to create the instance as an action (So that it shows up in the Keymap), then create a Menu shadow entry, finally the shortcut shadow entry with a default value.

The action seems to be created now but the shadow entries are not working (Including the menu which was working earlier).

This is what I have so far:
Code:

    private void registerAction(String name, String category, String shortcut, String menuPath, Action a) throws IOException {
        ///////////////////////
        // Add/Update Action //
        ///////////////////////
        String originalFile = "Actions/" + category + "/" + name + ".instance";
        FileObject in = getFolderAt("Actions/" + category);
        FileObject obj = in.getFileObject(name, "instance");
        if (obj == null) {
            obj = in.createData(name, "instance");
        }
        a.putValue(Action.NAME, name);
        obj.setAttribute("instanceCreate", a);
        obj.setAttribute("instanceClass", a.getClass().getName());

        /////////////////////
        // Add/Update Menu //
        /////////////////////
        in = getFolderAt(menuPath);
        obj = in.getFileObject(name, "shadow");
        // Create if missing.
        if (obj == null) {
            obj = in.createData(name, "shadow");
            obj.setAttribute("originalFile", originalFile);
        }

        /////////////////////////
        // Add/Update Shortcut //
        /////////////////////////
        in = getFolderAt("Shortcuts");
        obj = in.getFileObject(shortcut, "shadow");
        if (obj == null) {
            obj = in.createData(shortcut, "shadow");
            obj.setAttribute("originalFile", originalFile);
        }
    }

    // This helper seems to work fine
    private static FileObject getFolderAt(String inputPath) throws IOException {
        String parts[] = inputPath.split("/");
        FileObject existing = FileUtil.getConfigFile(inputPath);
        if (existing != null)
            return existing;

        FileObject base = FileUtil.getConfigFile(parts[0]);
        if (base == null) return null;

        for (int i = 1; i < parts.length; i++) {
            String path = Joiner.on('/').join(Arrays.copyOfRange(parts,0,i+1));
            FileObject next = FileUtil.getConfigFile(path);
            if (next == null) {
                next = base.createFolder(parts[i]);
            }
            base = next;
        }

        return FileUtil.getConfigFile(inputPath);
    }

Back to top
winder



Joined: 10 Mar 2015
Posts: 16

PostPosted: Sat Mar 12, 2016 3:32 am    Post subject: Reply with quote

Thanks again for the tips, everything is working great now. I wrapped it up in a service that can go on the wiki.


Code:

import com.google.common.base.Joiner;
import java.io.IOException;
import java.util.Arrays;
import javax.swing.Action;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.lookup.ServiceProvider;

/**
 *
 * @author wwinder
 */
@ServiceProvider(service=ActionRegistrationService.class)
public class ActionRegistrationService {
    /**
     * Registers an action with the platform along with optional shortcuts and
     * menu items.
     * @param name Display name of the action.
     * @param category Category in the Keymap tool.
     * @param shortcut Default shortcut, use an empty string or null for none.
     * @param menuPath Menu location starting with "Menu", like "Menu/File"
     * @param action an action object to attach to the action entry.
     * @throws IOException
     */
    public void registerAction(String name, String category, String shortcut, String menuPath, Action action) throws IOException {
        ///////////////////////
        // Add/Update Action //
        ///////////////////////
        String originalFile = "Actions/" + category + "/" + name + ".instance";
        FileObject in = getFolderAt("Actions/" + category);
        FileObject obj = in.getFileObject(name, "instance");
        if (obj == null) {
            obj = in.createData(name, "instance");
        }
        action.putValue(Action.NAME, name);
        obj.setAttribute("instanceCreate", action);
        obj.setAttribute("instanceClass", action.getClass().getName());

        /////////////////////
        // Add/Update Menu //
        /////////////////////
        in = getFolderAt(menuPath);
        obj = in.getFileObject(name, "shadow");
        // Create if missing.
        if (obj == null) {
            obj = in.createData(name, "shadow");
            obj.setAttribute("originalFile", originalFile);
        }

        /////////////////////////
        // Add/Update Shortcut //
        /////////////////////////
        in = getFolderAt("Shortcuts");
        obj = in.getFileObject(shortcut, "shadow");
        if (obj == null) {
            obj = in.createData(shortcut, "shadow");
            obj.setAttribute("originalFile", originalFile);
        }
    }

    private FileObject getFolderAt(String inputPath) throws IOException {
        String parts[] = inputPath.split("/");
        FileObject existing = FileUtil.getConfigFile(inputPath);
        if (existing != null)
            return existing;

        FileObject base = FileUtil.getConfigFile(parts[0]);
        if (base == null) return null;

        for (int i = 1; i < parts.length; i++) {
            String path = Joiner.on('/').join(Arrays.copyOfRange(parts,0,i+1));
            FileObject next = FileUtil.getConfigFile(path);
            if (next == null) {
                next = base.createFolder(parts[i]);
            }
            base = next;
        }

        return FileUtil.getConfigFile(inputPath);
    }
}


Now to register actions you just call the service:
Code:

    private void initActions() {
        ActionRegistrationService ars =  Lookup.getDefault().lookup(ActionRegistrationService.class);

        try {
            String menuPath = "Menu/Machine/Jog";
           
            ars.registerAction(getMessage(this.getClass(), "JogService.xPlus") ,
                    "Machine", "M-RIGHT" , menuPath, new JogAction(this, 1, 0, 0));
            ars.registerAction(getMessage(this.getClass(), "JogService.xMinus"),
                    "Machine", "M-LEFT"  , menuPath, new JogAction(this,-1, 0, 0));
            ars.registerAction(getMessage(this.getClass(), "JogService.yPlus") ,
                    "Machine", "M-UP"    , menuPath, new JogAction(this, 0, 1, 0));
            ars.registerAction(getMessage(this.getClass(), "JogService.yMinus"),
                    "Machine", "M-DOWN"  , menuPath, new JogAction(this, 0,-1, 0));
            ars.registerAction(getMessage(this.getClass(), "JogService.zPlus") ,
                    "Machine", "SM-UP"   , menuPath, new JogAction(this, 0, 0, 1));
            ars.registerAction(getMessage(this.getClass(), "JogService.zMinus"),
                    "Machine", "SM-DOWN" , menuPath, new JogAction(this, 0, 0,-1));
        } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
        }
    }
Back to top
RamiS



Joined: 09 Feb 2012
Posts: 48

PostPosted: Sat Mar 12, 2016 5:33 pm    Post subject: How to register actions programmatically with NetBeans Platform Reply with quote

You can remove all the null checks and getFolderAt() by:

String path = ...
FileObject root= FileUtil.getConfigRoot();
FileObject f = FileUtil.createData(root, path);

This creates the file including nonexistent parent folders if necessary
(FileUtil.createFolder() does the equivalent for folders).
Generally the FileUtil class contains a lot of useful stuff.

Regards,
Johannes

On 12.03.2016 04:32, winder wrote:
Quote:
Thanks again for the tips, everything is working great now. I wrapped it up in a service that can go on the wiki.



Code:

import com.google.common.base.Joiner;
import java.io.IOException;
import java.util.Arrays;
import javax.swing.Action;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.lookup.ServiceProvider;

/**
*
* @author wwinder
*/
@ServiceProvider(service=ActionRegistrationService.class)
public class ActionRegistrationService {
/**
* Registers an action with the platform along with optional shortcuts and
* menu items.
* @param name Display name of the action.
* @param category Category in the Keymap tool.
* @param shortcut Default shortcut, use an empty string or null for none.
* @param menuPath Menu location starting with "Menu", like "Menu/File"
* @param action an action object to attach to the action entry.
* @throws IOException
*/
public void registerAction(String name, String category, String shortcut, String menuPath, Action action) throws IOException {
///////////////////////
// Add/Update Action //
///////////////////////
String originalFile = "Actions/" + category + "/" + name + ".instance";
FileObject in = getFolderAt("Actions/" + category);
FileObject obj = in.getFileObject(name, "instance");
if (obj == null) {
obj = in.createData(name, "instance");
}
action.putValue(Action.NAME, name);
obj.setAttribute("instanceCreate", action);
obj.setAttribute("instanceClass", action.getClass().getName());

/////////////////////
// Add/Update Menu //
/////////////////////
in = getFolderAt(menuPath);
obj = in.getFileObject(name, "shadow");
// Create if missing.
if (obj == null) {
obj = in.createData(name, "shadow");
obj.setAttribute("originalFile", originalFile);
}

/////////////////////////
// Add/Update Shortcut //
/////////////////////////
in = getFolderAt("Shortcuts");
obj = in.getFileObject(shortcut, "shadow");
if (obj == null) {
obj = in.createData(shortcut, "shadow");
obj.setAttribute("originalFile", originalFile);
}
}

private FileObject getFolderAt(String inputPath) throws IOException {
String parts[] = inputPath.split("/");
FileObject existing = FileUtil.getConfigFile(inputPath);
if (existing != null)
return existing;

FileObject base = FileUtil.getConfigFile(parts[0]);
if (base == null) return null;

for (int i = 1; i < parts.length; i++) {
String path = Joiner.on('/').join(Arrays.copyOfRange(parts,0,i+1));
FileObject next = FileUtil.getConfigFile(path);
if (next == null) {
next = base.createFolder(parts[i]);
}
base = next;
}

return FileUtil.getConfigFile(inputPath);
}
}




Now to register actions you just call the service:

Code:

private void initActions() {
ActionRegistrationService ars = Lookup.getDefault().lookup(ActionRegistrationService.class);

try {
String menuPath = "Menu/Machine/Jog";

ars.registerAction(getMessage(this.getClass(), "JogService.xPlus") ,
"Machine", "M-RIGHT" , menuPath, new JogAction(this, 1, 0, 0));
ars.registerAction(getMessage(this.getClass(), "JogService.xMinus"),
"Machine", "M-LEFT" , menuPath, new JogAction(this,-1, 0, 0));
ars.registerAction(getMessage(this.getClass(), "JogService.yPlus") ,
"Machine", "M-UP" , menuPath, new JogAction(this, 0, 1, 0));
ars.registerAction(getMessage(this.getClass(), "JogService.yMinus"),
"Machine", "M-DOWN" , menuPath, new JogAction(this, 0,-1, 0));
ars.registerAction(getMessage(this.getClass(), "JogService.zPlus") ,
"Machine", "SM-UP" , menuPath, new JogAction(this, 0, 0, 1));
ars.registerAction(getMessage(this.getClass(), "JogService.zMinus"),
"Machine", "SM-DOWN" , menuPath, new JogAction(this, 0, 0,-1));
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}






Back to top
markiewb



Joined: 29 Aug 2012
Posts: 870

PostPosted: Sun Mar 13, 2016 2:05 pm    Post subject: Reply with quote

Added to http://wiki.netbeans.org/DevFaqActionsAddAtRuntime


2016-03-13_15h04_32.png
 Description:
 Filesize:  21.74 KB
 Viewed:  2921 Time(s)

2016-03-13_15h04_32.png


Back to top
Display posts from previous:   
Post new topic   Reply to topic    NetBeans Forums -> NetBeans Platform Users All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Powered by phpBB
By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2012, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo