Skip to main content
This revision made June 13, 2011 10:12, by magnum
« earlier revision revert to this later revision »

Overview

The first example is a simple application to view any image files that can be handled by the JRE (as of jpg, for example), and get some properties about these files. We will use a Plugin to open the files, and the main application to get the informations about them. Remember that although it is a fully working example, it intends to be overly simple, so surely it could not be useful in the real life.

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 menu item, and an Exit item. We also want a menu to be able to close the opened images.

In this example, we will learn to :

  • Create an Application and its associated menus
  • Load Plugins in the Applications
  • Create Plugins and register their menus in the Application
  • Use Actions
  • Add a Splash Screen to the Application

Creation of the Application

First of all, we must create our Application. We need :

  • a Message Area at the bottom of the Application Window
  • a Status Area under the message area
  • a Menu Bar with our File menu.

Lets look at the org.mdi.app package. Rather than coding from scratch from the Application interface, we will use the AbstractApplication implementation. We only need to initialize the Application correctly.

  import org.mdi.app.AbstractApplication;

  public class SimpleMDI extends AbstractApplication  {
    public SimpleMDI() {
        super("MDISimpleExample");
    }
            
    public static void main(String[] args) {
        SimpleMDI mdi = new SimpleMDI();
        mdi.setVisible(true);        
    }  
  }

For the moment, we will only see an empty panel like that : .

First we need to set the size of the Application window. OK, lets do it :

    public SimpleMDI() {
        super("MDISimpleExample");        
        this.setSize(500, 500);        
    }

Now it's better, but there still is nothing in the Window. We will need to :

  • Initialize the Application Configuration, such as for the events management.
  • Register the possible Plugins
  • Add a Menu Factory to create and manage the File menu, add the Message Area, the Status bar...

Adding a Menu Factory

Our Menu Factory will derive from the AbstractMenuFactory class. We only have to implement the two following abstract methods initMenus() to create the File menu, and createPopupMenu(JPopupMenu) to create the Popup menu to close the tabs.

  import org.mdi.app.AbstractApplication;

  public class SimpleMenuFactory extends AbstractMenuFactory {        
    private JMenu filemenu = new JMenu("File");
    
    public SimpleMenuFactory() {
        super();
    }
    
    /** Construct the application menus.
     */
    protected void initMenus() {
        JMenuItem exitItem = new JMenuItem(getDefaultExitAction("Exit"));        
        filemenu.add(exitItem);        
        Mbar.add(filemenu);           
    }
    
    protected void createPopupMenu(JPopupMenu menu) {
    }        
  }

See that we don't need to create an Exit action ourselves. We can use the default built-in getDefaultExitAction(String) method in the AbstractMenuFactory class. And now whe will have an exit menu. We need to reference the menu factory in the Application :

    public SimpleMDI() {
        super("MDISimpleExample");

        this.initConfiguration(); // initialize the Application without storing Preferences
        
        mfactory = new SimpleMenuFactory();       
        // create the Application Panels with 
        // preparePanels(int messageAreaSize, boolean hasStatusbar, boolean hasToolBar,
                         AbstractMenuFactory mfactory)
        // - a Message Area of 4 rows height
        // - a status bar
        // - no tool bar
        // - our Menu Factory
        super.preparePanels(4, true, false, mfactory);     
        this.setSize(500, 500);        
    }

We have initialized the Application without storing Preferences, and we have registered our Menu factory (see the fundamental method preparePanels). We still not do anything else than Exit, we can't open any File, and we don't register posible Plugins yet. Still, we now have a Status Bar, and a Message Area, but they are useless for now. Managing Plugins

Let's register possible Plugins in the Application. We will assume that Plugins are located in the same directory as the main Application jar itself. We need to define :

  • The directory where the Plugin jars are located
  • The name of the xml file giving the necessary informations about the Plugins

For the Application, this is very simple :

    public SimpleMDI() {
        super("MDISimpleExample");

        // set the plugins directory and xml file name
        pluginsDir = new File(System.getProperty("user.dir")); // plugins directory
        pluginsFileName = "pluginsSimple.xml"; // plugins xml file name
        
        this.initConfiguration();
        // register plugins in the plugins directory
        this.registerPlugins();
        
        mfactory = new SimpleMenuFactory();       
        super.preparePanels(4, true, false, mfactory);     
        this.setSize(500, 500);        
    }

Now if we have any plugins in the directory, and assuming that the xml file exists and reference the Plugins, they will be loaded. Speaking of it, we need to create this xml file (named pluginsSimple.xml in our example) :

  <?xml version="1.0" encoding="UTF-8"?>
  <!DOCTYPE Plugins PUBLIC "Plugins DTD 1.0" "Plugins.dtd">
  <Plugins>
  </Plugins>

For now, we don't have any Plugin to reference, we will do it in the next chapter.

We can now do two other useful things about Plugins in the main Application :

  • Create the Open Menu and a key Map for this menu to be able to add Plugin actions in it
  • Create a Help menu and a About Plugins to see what Plugins have been loaded

For that, we must add some code in our SimpleMenuFactory :

  public class SimpleMenuFactory extends AbstractMenuFactory {        
    private JMenu filemenu = new JMenu("File");
    private JMenu openmenu = new JMenu("Open");
    private JMenu helpmenu = new JMenu("Help");        
    
    public SimpleMenuFactory() {
        super();
    }
    
    protected void initMenus() {    
        JMenuItem exitItem = new JMenuItem(getDefaultExitAction("Exit"));        
        filemenu.add(exitItem); 
        filemenu.add(openmenu);         
        
        // create the about Plugins menu
        JMenuItem aboutPluginsItem = 
            new JMenuItem(getPluginsMenuFactory().getAboutPluginsAction());        
        helpmenu.add(aboutPluginsItem);                                

        // register key maps
        staticMenuKeyMap.put(PluginElementTypes.OPEN_ACTIONS, openmenu);  
        getPluginsMenuFactory().registerMenus(staticMenuKeyMap, null);         
        
        Mbar.add(filemenu);   
        Mbar.add(helpmenu); 
    }

As you see, the About Plugins is also built-in. As for the key maps, they map menus created by the main applications to String keys. Plugins will hook to these keys to be able to add sub-menus or menu items under the menus corresponding with these keys. The staticMenuKeyMap is the key map corresponding to static menus (menus that do not depend on the type of the selected tab).

Now to be able to see something interesting, we will have to create at least a Plugin.

Creating a Plugin

Our Plugin will be able to open an image file. A Plugin must implement the Plugin interface. To simplify our, we will rather derive from the AbstractPlugin class, which implements the less useful Plugin methods with an empty behavior

 package org.mdi.examples.simple.plugins;    
    
 import org.mdi.plugins.AbstractPlugin;

 public class OpenImagePlugin extends AbstractPlugin {
    public static final String OPEN_IMAGES = "OpenImages"; 
    public static final String OPEN_IMAGES_DESC = "Open Images Plugin";
    
    public OpenImagePlugin() {
    }
    
    public String getPluginName() {
        return OPEN_IMAGES;
    }    
    
    /** Return the properties of this plugin.
     */
    public Object getPluginProperty(String prop) {
        if (prop.equals(PluginElementTypes.PROPERTY_DATE)) return "undef";
        else if (prop.equals(PluginElementTypes.PROPERTY_DESC)) return OPEN_IMAGES_DESC;
        else if (prop.equals(PluginElementTypes.PROPERTY_VERSION)) return "0.1";
        else return null;
    }    
  }

Now we must add the reference to tis Plugin main class in the plugins xml declaration file :

  <?xml version="1.0" encoding="UTF-8"?>
  <!DOCTYPE Plugins PUBLIC "Plugins DTD 1.0" "Plugins.dtd">
  <Plugins>
     <plugin mainclass="org.mdi.examples.simple.plugins.OpenImagePlugin" desc="OpenImagePlugin" />
  </Plugins>

This Plugin does nothing but will now be loaded by the Application. So now have something to show in the Help menu :

Creating Actions

We now want our Plugin do do something useful, in our case being able to open image Files. For that, we will create a MDIAction, or rather a AbstractMDIAction, which is an MDIAction that is also an AbstractAction, so that it can be used directly in swing components.

 package org.mdi.examples.simple.plugins;    
    
 import org.mdi.plugins.AbstractPlugin;

 public class OpenImagePlugin extends AbstractPlugin {
    public static final String OPEN_IMAGES = "OpenImages"; 
    public static final String OPEN_IMAGES_DESC = "Open Images Plugin";    
    protected List importMenuActions; // open image menu actions    
    
    public OpenImagePlugin() {
    }
    
    public String getPluginName() {
        return OPEN_IMAGES;
    }   
 
    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;
    }    
    
    public Object getPluginProperty(String prop) {
        if (prop.equals(PluginElementTypes.PROPERTY_DATE)) return "undef";
        else if (prop.equals(PluginElementTypes.PROPERTY_DESC)) return OPEN_IMAGES_DESC;
        else if (prop.equals(PluginElementTypes.PROPERTY_VERSION)) return "0.1";        
        else return null;
    }    
    
    protected class ImportImageAction extends AbstractMDIAction {
        public ImportImageAction(String name) {
            super(app, name);
            this.setDescription("Open Image", "Open Image");
        }

        public void run() throws Exception {
        }        
    }    
  }

We have created an AbstractMDIAction and added it in the static menus for this Plugin. See that we registered it under the PluginElementTypes.OPEN_ACTIONS in the getStaticMenuElements(String) method. This is the key menu name we gave for the Open menu, so the menu item corresponding to this action for this Plugin will appear under the Application Open menu. Creating a Vector for that is a bit complex for such a simple thing, but it also would have been possible to :

  • directly registering the AbstractMDIAction without using a List (using a List is useful if you want to register more than one MDIAction at once
  • creating a JMenuItem with the AbstractMDIAction and registering it
  • or even not using an AbstractMDIAction, but creating a new MDIAction, and calling it in the actionPerformed(ActionEvent) method of a plain Java AbstractAction

The code under the run() method of the Action is specific of what we do and not related to the framework, lets look at it however to see somthing functional :

      public void run() throws Exception {
            JFileChooser chooser = new JFileChooser("Open Image");
            chooser.setDialogTitle("Open Image");        
            chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
            if (chooser.showOpenDialog(app.getApplicationWindow()) == JFileChooser.APPROVE_OPTION) {
                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());                
            }
        }

We don't have to bother about the Exception, it will be catched by the main Application, and a specific Exception panel will appear if there is any Exception encountered. The important part here is the app.addTab(JComponent, Object, String) after the creation of the image :

  • The new tab will be a JPanel to show our image (ImagePanel, nothing specific in it)
  • The Object is our Image
  • The name of the tab is the file name

The Application will create a new tab, and associate it with FileProperties with no associated MetaData (MetaData are useful when we want to handle more than one type of File / tabs, which is not the case here, every tab will contain a BufferedImage).

Remark : Unfortunately, the time of the Action begins to be counted before the effective opening of the File, and the time is calculated even if the FileChooser is closed without selecting a File. We will see how to improve this behavior in the next tutorial.

And as we now open something, lets be able to Close tabs after they have been opened. We will do it in the createPopupMenu(JPopupMenu) method of our SimpleMenuFactory :

  public class SimpleMenuFactory extends AbstractMenuFactory {        
    protected void createPopupMenu(JPopupMenu menu) {
        JMenuItem close = this.getDefaultCloseItem("Close");
        menu.add(close);                    
    }        
  }

Now we have a fully functional application : we can open image files and close them. The Message Area shows informations about what we have done (the Status bar also shows ongoing operations). Also the framework ensures that the user actions are performed one at the time, but you did not have to do anytnhing about it : it is handled by the framework. If an Exception is catch in the run() method, an error panel will appear.

We can now add the code to analyse an already opened image, but to change, we will do it in the main code of our Application (It would also be possible to do it in a Plugin).

  public class SimpleMenuFactory extends AbstractMenuFactory {        
    protected void initMenus() {    
        JMenuItem exitItem = new JMenuItem(getDefaultExitAction("Exit"));        
        filemenu.add(exitItem); 
        openmenu = new JMenu("Open");
        filemenu.add(openmenu);         
        
        // new analyseImage Item
        JMenuItem analyseImageItem = new JMenuItem(new AnalyseImageAction("Analyse"));                                        
        filemenu.add(analyseImageItem);                   
       
        JMenuItem aboutPluginsItem = 
            new JMenuItem(getPluginsMenuFactory().getAboutPluginsAction());        
        helpmenu.add(aboutPluginsItem);                                

        staticMenuKeyMap.put(PluginElementTypes.OPEN_ACTIONS, openmenu);  
        getPluginsMenuFactory().registerMenus(staticMenuKeyMap, null);         
        
        Mbar.add(filemenu);   
        Mbar.add(helpmenu); 
    }  
    
    public class AnalyseImageAction extends AbstractMDIAction {        
        public AnalyseImageAction(String name) {
            super(app, name);
            this.setDescription("Analyse", "Analyse Image");
        }
        
        public void run() throws Exception {
        }
    }    
  }

And now we can write the code that perform the Analysis :

        public void run() throws Exception {
            // will only analyse the image if there is one available property
            // for our case, we could also have used app.getTabCount() != 0
            if (app.getSelectedProperties() != null) {
                // specific code, could have been in an Action
                BufferedImage image = (BufferedImage)app.getSelectedProperties().getObject();

                // The application window is the mother component of the dialog
                // the name of the selected property is the name of the tab
                JDialog dialog = new JDialog(app.getApplicationWindow(), 
                        app.getSelectedProperties().getName(), false);

                // specific code
                Container pane = dialog.getContentPane();        
                pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
                pane.add(new JLabel("Width: " + image.getWidth()));
                pane.add(new JLabel("Height: " + image.getHeight()));            
                pane.add(new JLabel("Type: " + image.getType()));
                dialog.setSize(200, 100);
                dialog.setVisible(true);            
            }                    
        }

Here we gather informations about the currently selected tab, or rather the Object under this tab : (BufferedImage)app.getSelectedProperties().getObject() gets the BufferedImage that is associated with the selected tab.

Adding a SplashScreen

Adding a SplashScreen is easy. It involves the class SplashScreen. First we assume that we have an Image somewhere, for example in the same package as the SimpleMDI class. We need to create a SplashScreen with this image, and then we can use it where we want (at the end of initializtion we need to dispose of the SplashScreen) :

    public SimpleMDI() {
        super("MDISimpleExample");
        
        ImageIcon splash = new ImageIcon(this.getClass().getResource("splash.png"));
        SplashScreen splashdialog = new SplashScreen(splash, "0.1", "Build xxxx", true);                               
        ...        
        splashdialog.dispose();
        splashdialog = null;
    }    

For changing the state of the SplashScreen, we only have to call its setProgress method, for example :

    public SimpleMDI() {
        super("MDISimpleExample");        
        ImageIcon splash = new ImageIcon(this.getClass().getResource("splash.png"));
        SplashScreen splashdialog = new SplashScreen(splash, "0.1", "Build xxxx", true);                                       
        ...        
        // initialize configuration without Preferences
        this.initConfiguration();
        splashdialog.setProgress(splashdialog.getProgress() + 20, "Register Plugins");
        // register plugins in the plugins directory (same as main application jar directory)
        this.registerPlugins();
        ...
    }    

If we want to use it in the menu factory, we need to set the Splash to it after initialization :

    public SimpleMDI() {
        super("MDISimpleExample");        
        ImageIcon splash = new ImageIcon(this.getClass().getResource("splash.png"));
        SplashScreen splashdialog = new SplashScreen(splash, "0.1", "Build xxxx", true);                                       
        ...        
        mfactory = new SimpleMenuFactory();       
        mfactory.setProgressInterface(splashdialog);
        super.preparePanels(4, true, true, mfactory);     

Getting it all Together

In this tutorial, we learned to :

  • Create an Application and its associated menus
  • Load Plugins in the Applications
  • Create Plugins and register their menus in the Application
  • Use Actions
  • Add a Splash Screen to the Application

We haven't use the File properties yet. In the next tutorial, we will learn to add MetaDatas to tabs, and manage dynamic menus, depending on the type of the selected tab.

 
 
Close
loading
Please Confirm
Close