Home
Categories
Dictionary
Download
Project Details
Changes Log
Tutorials
FAQ
License

Actions tutorial



Overview

For this tutorial, we will reuse what we have done for the basic tutorial.

The Application will be able to view:
  • Any image files that can be handled by the JRE, and get some properties about these files, and also save the opened files
  • Any text files that can be handled by the JRE, and save the opened files as plain text. We will also get some properties about the text files


We will use two different Plugins: one to open the image files and another for the text files. We will create only one File menu, with an Open sub-menu (we can imagine that there can be other Plugins that will be able to open other sorts of images), an Analyse sub-menu for images and text analysis, and an Exit item. We also want a menu to be able to close the opened images. This time, we will also create two tool bars:
  • one managed by the core application
  • and another by one of the plugins

Other ways to register Actions

As we wrote in the first tutorial, there are a lot of ways to register actions. In the basic tutorial, we created a List and put the AbstractMDIAction in it:
   public void register(Application app) {
     super.register(app);
     importMenuActions = new Vector(1);
     importMenuActions.add(new ImportImageAction("Image"));
   }

   public Object getStaticMenuElements(String menu) {
     if (menu.equals(PluginElementTypes.OPEN_ACTIONS)) return importMenuActions;
     else return null;
   }
Now we will return directly the AbstractMDIAction:
   public void register(MDIApplication app) {
     super.register(app);
     importImageAction = new ImportImageAction((GUIApplication) app, "Image");
   }

   public Object getStaticMenuElements(String menu) {
     if (menu.equals(PluginElementTypes.OPEN_ACTIONS)) return importImageAction
     else return null;
   }

More control on the Actions process

In the first tutorial, the time of the Action was counted before the effective opening of the File, and the time was calculated even if the FileChooser was closed without selecting a File. We can change this by using the three following methods:
 public void run() throws Exception {
    app.stopTime(this); // don't show the progress bar
    JFileChooser chooser = new JFileChooser("Open Image");
    chooser.setDialogTitle("Open Image");
    chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    if (chooser.showOpenDialog(app.getApplicationWindow()) == JFileChooser.APPROVE_OPTION) {
      app.startTime(this); // show the progress bar and start to count
      File file = chooser.getSelectedFile();
      BufferedImage image = ImageIO.read(file);
      if (image == null) throw new Exception("Bad File type");
      JScrollPane pane = new JScrollPane(new ImagePanel(image));
      app.addTab(pane, image, file.getName());
   } else {
      app.noWriteMessages();
   }
 }

Creating a new Plugin

Before creating new MetaDatas, we will create our second Plugin. It will be able to open text files. We will then create two types of tabs:
  • Those that will correspond to images (opened by our first Plugin)
  • Those that will correspond to texts (opened by the new Plugin)


First we crreate an action for this Plugin to import a Document:
   public class ImportDocumentAction extends AbstractMDIAction {
     public ImportDocumentAction(String name) {
       super(appli, name);
       this.setDescription("Open Document", "Open Document");
     }

     public void run() throws Exception {
     }
   }
And the Plugin itself:
   public class OpenDocumentPlugin extends AbstractPlugin {
      public static final String OPEN_DOCUMENTS = "OpenDocuments";
      public static final String OPEN_DOCUMENTS_DESC = "Open Documents Plugin";
      private AbstractAction importDocAction; // Plugin importMenuItem

      public OpenDocumentPlugin() {
      }

      public String getPluginName() {
        return OPEN_DOCUMENTS;
      }

      public Object getPluginProperty(String prop) {
        if (prop.equals(PluginElementTypes.PROPERTY_DATE)) return "undef";
        else if (prop.equals(PluginElementTypes.PROPERTY_DESC)) return OPEN_DOCUMENTS_DESC;
        else if (prop.equals(PluginElementTypes.PROPERTY_VERSION)) return "0.1";
        else return null;
      }

      public void register(MDIApplication appli) {
        super.register(appli);

        importDocAction = new ImportDocumentAction((GUIApplication)appli, "Document");

        JToolBar tbar = new JToolBar("OpenDocument");
        tbar.add(analyseDocAction);
        appli.getToolBarPanel().add(tbar);
      }

      public Object getStaticMenuElements(String menu) {
        if (menu.equals(PluginElementTypes.OPEN_ACTIONS)) return importDocAction;
        else return null;
      }

      public Object getDynamicMenuElements(String menuKey, FileProperties tab) {
        return null;
      }
   }
To be able to load this new Plugin in the Application, we must create another jar for the Plugin with this class in the "plugins" directory. Also we must create a Manifest for this Plugin like:
  MDIPluginClass: org.samples.plugins.OpenDocumentPlugin
We can now properly implement the run() method for our Action. There is nothing new here:
   public void run() throws Exception {
      app.stopTime(this);
      JFileChooser chooser = new JFileChooser("Open Document");
      chooser.setDialogTitle("Save Document");
      chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
      if (chooser.showOpenDialog(app.getApplicationWindow()) == JFileChooser.APPROVE_OPTION) {
        app.startTime(this);
        URL url = chooser.getSelectedFile().toURL();
        File file = chooser.getSelectedFile();
        JEditorPane pane = new JEditorPane();
        JScrollPane scroll = new JScrollPane(pane);
        pane.setEditable(true);
        pane.setPage(url);
        app.addTab(pane, pane.getDocument(), file.getName());
     } else {
        app.noWriteMessages();
     }
   }
Text files can now be opened and we have the following result:
tutorial2_1
But if we try to Analyse the corresponding tab, of course we have the following Exception:
analyseException
We must Analyse only image files: For that, we need to use MetaDatas.

Creating MetaDatas

MetaData allow to add (or modify) Meta-informations about an existing tab. In our case, we will use them to give a type to the tab (TEXT or IMAGE). We will only allow to Analyse IMAGE tabs.

For the moment, we don't have any documents types. Now lets create a new interface, just to store our two different String types.
  public interface ElementTypes {
    public String IMAGE = "image";
    public String TEXT = "text";
  }
Now we will add a MetaData, with the TEXT type, to our opened text files:
   protected class ImportDocumentAction extends AbstractMDIAction {

     public void run() throws Exception {
       ...
       pane.setEditable(true);
       pane.setPage(url);
       MetaData meta = new MetaData(2);
       meta.put(ElementTypes.TEXT, Boolean.TRUE);
       FileProperties prop =
       new FileProperties(file.getName(), scroll, pane.getDocument(), meta);
       app.addTab(pane, prop);
       ...
The FileProperties contains all the properties of the tab. Before now, it was created "under the hood" by the Application when we called the TabbedApplication.addTab(JComponent, Object, String) method. We will now handle this ourselves. The FileProperties is initialized with:
  • The name of the File, which will be the name of the tab
  • The JComponent to add to the tab (in our case the scroll pane)
  • The associated Object (in our case the Document)
  • And The MetaData Map for the tab, which in our case only contains one entry:
    • The key is the TEXT String
    • The value is the Boolean TRUE


With this MetaData, we are specifying that the tab is of the type "TEXT". We better do the same thing for our first Plugin:
   public class ImportImageAction extends AbstractMDIAction {
      public void run() throws Exception {
      ...
      JScrollPane pane = new JScrollPane(new ImagePanel(image));
      MetaData meta = new MetaData(2);
      meta.put(ElementTypes.IMAGE, Boolean.TRUE);

      FileProperties prop = new FileProperties(file.getName(), pane, image, meta);
      app.addTab(pane, prop);
      ...
      }
   }
We will now need to use this MetaDatas for our AnalyseAction, else we will still have the same Exception.

Using MetaDatas

By learning to use MetaDatas, we will also learn to use the concept of dynamic menus. Dynamic menus are menus which will be enabled (for menu items or buttons) or present only for certain types of tabs. In our case, we want the "Analyse" action to be enabled only if the selected tab is of the type "IMAGE".

To do this, we will first create a "MetaData template" that we will compare with the MetaData of the selected tab:
  public class SecondMenuFactory extends AbstractMDIMenuFactory {
     private static MetaData acceptAnalyseMeta = new MetaData();
     static {
       acceptAnalyseMeta.put(ElementTypes.IMAGE, Boolean.TRUE);
     }
     ...
  }
Now the Analyse action will not be a static element any more. Instead, it will be added to the dynamic menus, such that we will be able to compare our static Metadata template to the selected MetaData dynamically:
  public class SecondMenuFactory extends AbstractMDIMenuFactory {
    protected void initMenus() {
      ...
      analyseImageAction = new AnalyseImageAction(appli, "Analyse");
      this.addToDynamicMenuMap("AnalyseKey", analyseImageAction);
  ...
    }
  ...
    protected Object getDynamicMenuItems(FileProperties prop, String menuKey) {
      if (menuKey.equals(ElementTypes.ANALYSE_IMAGE_KEY)) {
        MetaData properties = prop.getMetaData();
        if (properties.isCompatibleWith(acceptAnalyseMeta, false)) return analyseImageAction;
        else return null;
      } else return null;
    }
  }
We now test the compatibility of the MetaData with our static "template" before trying to analyse it:
  • If we select a tab which contains some text, the Analyse item is disabled
  • If we select a tab which contains an image, the Analyse item is enabled


Now if we open a text file, the "Analyse" item will be disabled:
tutorial2_2

More with MetaDatas

For the moment, we have only used MetaDatas for the main Application, and only to enable or disable menu items. Of course, it is also possible to use them in Plugins, and for whole menus. Lets create a SaveAs Action for the two Plugins. First we create the SaveAs menu and bind it to the PluginElementTypes.SAVEAS_ACTIONS key:
   public class SecondMenuFactory extends AbstractMDIMenuFactory {
      ...
      private JMenu savemenu = new JMenu("SaveAs");
      ...
      protected void initMenus() {
      ...
      dynamicMenuKeyMap.put(PluginElementTypes.SAVEAS_ACTIONS, savemenu);
      ...
      filemenu.add(savemenu);
   }
We added the savemenu to the AbstractMDIMenuFactory.dynamicMenuKeyMap, mapped to the PluginElementTypes.SAVEAS_ACTIONS key. Now we will look for dynamic content under this menu, for the plugins and the main application.

First xwe create the saction to save the image:
   public class SaveImageAction extends AbstractMDIAction {
      public SaveImageAction(String name) {
        super(appli, name);
        this.setDescription("Save Image", "Save Image");
      }

      public void run() throws Exception {
        if (app.getSelectedProperties() != null) {
          app.stopTime(this);
          JFileChooser chooser = new JFileChooser("Save Image");
          chooser.setDialogTitle("Save Image");
          chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
          if (chooser.showSaveDialog(app.getApplicationWindow()) == JFileChooser.APPROVE_OPTION) {
            app.startTime(this);
            BufferedImage image = (BufferedImage)app.getSelectedProperties().getObject();
            File file = chooser.getSelectedFile();
            ImageIO.write(image, "jpg", file);
         } else {
            app.noWriteMessages();
          }
       } else {
          app.noWriteMessages();
       }
     }
   }
And we use your newly creatd action in the OpenImagePlugin:
   public class OpenImagePlugin extends AbstractMDIPlugin {
      public static final String OPEN_IMAGES = "OpenImages";
      public static final String OPEN_IMAGES_DESC = "Second Open Images Plugin";
      private ImportImageAction importImageAction;
      private SaveImageAction saveImageAction = new SaveImageAction("Image");  ;

      private static MetaData acceptSaveAsMeta = new MetaData();
      static {
       acceptSaveAsMeta.put(ElementTypes.IMAGE, Boolean.TRUE);
      }

      public OpenImagePlugin() {
      }
      ...
      public Object getDynamicMenuElements(String menuKey, FileProperties prop) {
        if (menuKey.equals(PluginElementTypes.SAVEAS_ACTIONS)) {
          MetaData properties = prop.getMetaData();
          if (properties.isCompatibleWith(acceptSaveAsMeta, false)) return saveImageAction;
          else return null;
        } else return null;
      }
   }
We return null if the properties are not compatible, in other words if the selected tab is not of the IMAGE type. But if the selected tab is of the IMAGE type, we return the saveImageAction Action. The SaveAs menu will be constructed dynamically, depending on the type of the selected tab.

We do the same for the OpenDocumentPlugin, first the action to save the document::
   public class SaveDocumentAction extends AbstractMDIAction {
      public SaveDocumentAction(String name) {
        super(appli, name);
        this.setDescription("Save Document", "Save Document As Plain text File");
      }

      public void run() throws Exception {
        if (app.getSelectedProperties() != null) {
          app.stopTime(this);
          JFileChooser chooser = new JFileChooser("Save Document");
          chooser.setDialogTitle("Save Document");
          chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
          if (chooser.showSaveDialog(app.getApplicationWindow()) == JFileChooser.APPROVE_OPTION) {
            app.startTime(this);
            File file = chooser.getSelectedFile();
            Document doc = (Document)app.getSelectedProperties().getObject();
            String text = doc.getText(0, doc.getLength());
            BufferedWriter writer = new BufferedWriter(new FileWriter(file));
            writer.write(text);
            writer.flush();
            writer.close();
          }
        } else app.noWriteMessages();
      }
   }
And the Plugin itself:
   public class OpenDocumentPlugin extends AbstractMDIPlugin {
     public static final String OPEN_DOCUMENTS = "OpenDocuments";
     public static final String OPEN_DOCUMENTS_DESC = "Open Documents Plugin";
     private AbstractAction importDocAction;
     private AbstractAction saveDocAction;

     private static MetaData acceptSaveAsMeta = new MetaData();
     static {
       acceptSaveAsMeta.put(ElementTypes.TEXT, Boolean.TRUE);
     }

     public OpenDocumentPlugin() {
     }

     @Override
     public void register(MDIApplication appli) throws Exception {
       super.register(appli);
       importDocAction = new ImportDocumentAction((GUIApplication) appli, "Document");
       saveDocAction = new SaveDocumentAction((GUIApplication) appli, "Document");
     }

     public Object getDynamicMenuElements(String menuKey, FileProperties tab) {
       if (menuKey.equals(PluginElementTypes.SAVEAS_ACTIONS)) {
         MetaData properties = tab.getMetaData();
         if (properties.isCompatibleWith(acceptSaveAsMeta, false)) return saveDocAction;
           else return null;
         } else return null;
       }
     }
   }
Now the SaveAs menu will depend on the selected tab.

For images:
saveasImage
For documents:
saveasDocument

Creating Tool bars

Adding Toolbars is easy. For example, lets add a Toolbar for the main Application, containing only the button Analyze. We will use the AbstractMDIMenuFactory.getToolBarPanel() method of the AbstractMDIMenuFactory to get the toolbar:
  public class SecondMenuFactory extends AbstractMDIMenuFactory {
    protected void initMenus() {
      ...
      analyseImageAction = new AnalyseImageAction(appli, "Analyse");

      // create toolbar
      JToolBar tbar = new JToolBar("Standard");
      tbar.add(analyseImageAction);
      this.getToolBarPanel().add(tbar);
      ...
    }
  }
This is done! Now we have a Toolbar, and it will react as the Analyse item:

We can do exactly the same thing for other actions. For example lets create an Analyze Action for TEXT tabs only in the OpenDocumentPlugin class:
   public class OpenDocumentPlugin extends AbstractPlugin {
     ...
     protected AbstractAction analyseDocAction;

     public void register(MDIApplication appli) {
       super.register(appli);

       importDocAction = new ImportDocumentAction((GUIApplication) appli, "Document");
       saveDocAction = new SaveDocumentAction((GUIApplication) appli, "Document");
       analyseDocAction = new AnalyseDocumentAction((GUIApplication) appli, "Document");
       appli.getMenuFactory().addToDynamicMenuMap("Analyse", analyseDocAction);

       JToolBar tbar = new JToolBar("OpenDocument");
       tbar.add(analyseDocAction);
       appli.getToolBarPanel().add(tbar);
     }

     public Object getDynamicMenuElements(String menuKey, FileProperties tab) {
       if (menuKey.equals("Analyse")) {
         MetaData properties = tab.getMetaData();
         if (properties.isCompatibleWith(acceptSaveAsMeta, false)) return analyseDocAction;
         else return null;
       } else if (menuKey.equals(PluginElementTypes.SAVEAS_ACTIONS)) {
         MetaData properties = tab.getMetaData();
         if (properties.isCompatibleWith(acceptSaveAsMeta, false)) return saveDocAction;
         else return null;
       } else return null;
     }
   }
As for other dynamic menus (and even if there it is only a button on a Toolbar), we have to register the associated Action under a specific key, and return it when asked by the Application in the AbstractMDIPlugin.getDynamicMenuElements(String, FileProperties) method.

toolbar

Getting it all Together

In this tutorial, we learned to:
  • Use MetaDatas and filter menus according to them
  • Use Other ways to register Actions
  • Associate toolbars with actions, both in the main Application and in Plugins

See also


Categories: swing | tutorials

Copyright 2006-2023 Herve Girod. All Rights Reserved. Documentation and source under the LGPL v2 and Apache 2.0 licences