View Javadoc

1   /*
2    * @(#)$Id: MainController.java 820 2010-05-26 21:19:20Z bsigner $
3    *
4    * Author       :   Ueli Kurmann, igesture@uelikurmann.ch
5    *                                   
6    *                                   
7    * Purpose      :   The main controller
8    *
9    * -----------------------------------------------------------------------
10   *
11   * Revision Information:
12   *
13   * Date             Who         Reason
14   *
15   * 23.03.2008       ukurmann    Initial Release
16   * 29.10.2008       bsigner     Cleanup
17   *
18   * -----------------------------------------------------------------------
19   *
20   * Copyright 1999-2009 ETH Zurich. All Rights Reserved.
21   *
22   * This software is the proprietary information of ETH Zurich.
23   * Use is subject to license terms.
24   * 
25   */
26  
27  
28  package org.ximtec.igesture.tool.view;
29  
30  import java.awt.Cursor;
31  import java.awt.Point;
32  import java.beans.IndexedPropertyChangeEvent;
33  import java.beans.PropertyChangeEvent;
34  import java.io.File;
35  import java.io.FileInputStream;
36  import java.io.FileOutputStream;
37  import java.lang.reflect.Constructor;
38  import java.util.Date;
39  import java.util.Properties;
40  import java.util.logging.Level;
41  import java.util.logging.Logger;
42  
43  import javax.swing.JFileChooser;
44  import javax.swing.JOptionPane;
45  import javax.swing.SwingUtilities;
46  
47  import org.sigtec.util.Constant;
48  import org.ximtec.igesture.core.DataObject;
49  import org.ximtec.igesture.core.DataObjectWrapper;
50  import org.ximtec.igesture.io.AbstractGestureDevice;
51  import org.ximtec.igesture.io.GestureDevice;
52  import org.ximtec.igesture.storage.StorageEngineConverter;
53  import org.ximtec.igesture.storage.StorageManager;
54  import org.ximtec.igesture.storage.StorageManager.StorageEngineType;
55  import org.ximtec.igesture.tool.GestureConstants;
56  import org.ximtec.igesture.tool.core.Controller;
57  import org.ximtec.igesture.tool.core.DefaultController;
58  import org.ximtec.igesture.tool.core.EdtProxy;
59  import org.ximtec.igesture.tool.core.ExecCmd;
60  import org.ximtec.igesture.tool.core.GenericLocateableAction;
61  import org.ximtec.igesture.tool.core.TabbedView;
62  import org.ximtec.igesture.tool.locator.Locator;
63  import org.ximtec.igesture.tool.locator.Service;
64  import org.ximtec.igesture.tool.service.DeviceManagerService;
65  import org.ximtec.igesture.tool.service.GuiBundleService;
66  import org.ximtec.igesture.tool.service.SwingMouseReaderService;
67  import org.ximtec.igesture.tool.util.ComponentFactory;
68  import org.ximtec.igesture.tool.util.ExtensionFileFilter;
69  import org.ximtec.igesture.tool.util.FileType;
70  import org.ximtec.igesture.tool.view.admin.AdminController;
71  import org.ximtec.igesture.tool.view.batch.BatchController;
72  import org.ximtec.igesture.tool.view.composite.CompositeController;
73  import org.ximtec.igesture.tool.view.devicemanager.DeviceManagerController;
74  import org.ximtec.igesture.tool.view.testbench.TestbenchController;
75  import org.ximtec.igesture.tool.view.testset.TestSetController;
76  import org.ximtec.igesture.tool.view.welcome.WelcomeController;
77  
78  
79  /**
80   * The main controller class.
81   * 
82   * @version 1.0, Mar 2008
83   * @author Ueli Kurmann, igesture@uelikurmann.ch
84   * @author Beat Signer, bsigner@vub.ac.be
85   */
86  public class MainController extends DefaultController implements Service {
87  
88     private static final Logger LOGGER = Logger.getLogger(MainController.class
89           .getName());
90  
91     // Command Strings
92     public static final String CMD_LOAD = "load";
93     public static final String CMD_EXIT = "close";
94     public static final String CMD_SAVE = "save";
95     public static final String CMD_SAVE_AS = "saveAs";
96     public static final String CMD_START_WAITING = "startWaiting";
97     public static final String CMD_STOP_WAITING = "stopWaiting";
98     public static final String CMD_SHOW_ABOUT_DIALOG = "showAboutDialog";
99     public static final String CMD_CLOSE_WS = "closeWorkspace";
100    public static final String CMD_CHANGE_TAB = "changeTab";
101    public static final String CMD_SHOW_DEVICE_MANAGER = "showDeviceManager";
102 
103    // List of controllers (a project is active)
104    private static Class< ? >[] activeControllers = new Class< ? >[] {
105          AdminController.class, TestbenchController.class,
106          BatchController.class, TestSetController.class,
107          CompositeController.class };
108 
109    // List of controllers (no project active)
110    private static Class< ? >[] passiveControllers = new Class< ? >[] { WelcomeController.class };
111 
112    public static final String IDENTIFIER = "mainController";
113 
114    // Services
115    private MainModel mainModel;
116    private GuiBundleService guiBundle;
117    private SwingMouseReaderService deviceClient;
118    private StorageEngineType storageEngineType;
119    private DeviceManagerService deviceManager;
120 
121    // Main View
122    private IMainView mainView;
123 
124    // Properties
125    private Properties properties;
126 
127    /**
128     * Flag indicates, if the project is modified False: Project was not modified,
129     * no save needed.
130     */
131    private boolean modelIsModified;
132 
133 
134    /**
135     * Default Constructor. Initialises the application.
136     * <ul>
137     * <li>Init Services</li>
138     * <li>Init Main View</li>
139     * <li>Init Sub-Controller and Sub-Views</li>
140     * </ul>
141     */
142    public MainController() {
143       super(null);
144       initServices();
145       initMainView();
146       initSubControllersAndViews(passiveControllers);
147       getAction(CMD_CLOSE_WS).setEnabled(false);
148       getAction(CMD_SAVE).setEnabled(false);
149       getAction(CMD_SAVE_AS).setEnabled(false);
150       this.modelIsModified = false;
151    }
152 
153 
154    /**
155     * Instantiates a controller using reflection. If the controller has a
156     * Constructor taking a parent controller as argument, this constructor is
157     * used. If no such constructor exists, the default constructor is used.
158     * 
159     * @param controllerClass the type of the controller
160     * @return the created controller
161     * @throws Exception
162     */
163    private Controller createController(Class< ? > controllerClass)
164          throws Exception {
165 
166       boolean instantiated = false;
167 
168       Controller controller = null;
169       try {
170          if (!instantiated
171                && (controllerClass.getConstructor(Controller.class) != null)) {
172             Constructor< ? > constructor = controllerClass
173                   .getConstructor(Controller.class);
174             controller = (Controller)constructor
175                   .newInstance(MainController.this);
176             instantiated = true;
177          }
178       }
179       catch (Exception e) {
180          e.printStackTrace();
181       }
182       if (!instantiated) {
183          try {
184             controller = (Controller)controllerClass.newInstance();
185          }
186          catch (Exception e) {
187             e.printStackTrace();
188          }
189       }
190 
191       if (controller == null)
192          throw new Exception();
193       return controller;
194    }
195 
196 
197    /**
198     * Command is executed after changing a tab.
199     */
200    @ExecCmd(name = CMD_CHANGE_TAB)
201    protected void execChangeTab() {
202       LOGGER.info("Change Tab");
203       GestureDevice< ? , ? > gestureDevice = getLocator().getService(
204             SwingMouseReaderService.IDENTIFIER, GestureDevice.class);
205       if (gestureDevice != null) {
206          gestureDevice.clear();
207       }
208 
209       /*
210        * Only listeners for the current active tab are allowed. This way the
211        * performed gestures are only drawn on the current tab. On change of tab
212        * remove all listeners.
213        */
214       for (AbstractGestureDevice< ? , ? > device : getLocator().getService(
215             DeviceManagerService.IDENTIFIER, DeviceManagerController.class)
216             .getDevices()) {
217          device.removeAllGestureHandler();
218       }
219    }
220 
221 
222    /**
223     * Command to close a project.
224     */
225    @ExecCmd(name = CMD_CLOSE_WS)
226    protected void execCloseWsCommand() {
227       LOGGER.info("Command Close Workspace");
228       if (modelIsModified
229             && mainModel.isActive()
230             && JOptionPane.YES_OPTION == showYesNoDialog(GestureConstants.MAIN_CONTROLLER_DIALOG_SAVE)) {
231          mainModel.getStorageManager().commit();
232       }
233       mainView.removeAllTabs();
234       mainModel.stop();
235       mainModel.setStorageEngine(null);
236       initSubControllersAndViews(passiveControllers);
237 
238       getAction(CMD_CLOSE_WS).setEnabled(false);
239       getAction(CMD_SAVE).setEnabled(false);
240       getAction(CMD_SAVE_AS).setEnabled(false);
241       getAction(CMD_LOAD).setEnabled(true);
242 
243       mainView.setTitlePostfix(null);
244 
245    } // execLoadCommand
246 
247 
248    /**
249     * Command to exit the application.
250     */
251    @ExecCmd(name = CMD_EXIT)
252    protected void execExitCommand() {
253       LOGGER.info("Command Exit");
254 
255       if (modelIsModified) {
256          switch (showYesNoDialog(GestureConstants.MAIN_CONTROLLER_DIALOG_EXIT)) {
257             case JOptionPane.YES_OPTION:
258                mainModel.getStorageManager().commit();
259                shutdownApplication();
260                break;
261 
262             case JOptionPane.NO_OPTION:
263                shutdownApplication();
264                break;
265 
266             case JOptionPane.CANCEL_OPTION:
267                LOGGER.info("Exit cancelled.");
268                break;
269          }
270       }
271       else {
272          shutdownApplication();
273       }
274    } // execExitCommand
275 
276 
277    /**
278     * Terminates the application. All services are stopped, the properties are
279     * stored. afterwards the application is terminated with an exit statement.
280     */
281    private void shutdownApplication() {
282       getLocator().stopAll();
283       storeProperties();
284       System.exit(0);
285    } // shutdownAPplication
286 
287 
288    /**
289     * Persist the properties. This method should be called before shutting down
290     * the application.
291     */
292    private void storeProperties() {
293       try {
294          mainModel.getProperties().storeToXML(
295                new FileOutputStream(GestureConstants.PROPERTIES),
296                "iGesture: " + new Date());
297       }
298       catch (Exception e) {
299          LOGGER.log(Level.WARNING, "Failed to store properties.", e);
300       }
301    }
302 
303 
304    /**
305     * Command to load a project.
306     */
307    @ExecCmd(name = CMD_LOAD)
308    protected void execLoadCommand() {
309       LOGGER.info("Command Load");
310       File dataBase = getDatabase(false);
311 
312       if (dataBase != null) {
313          mainView.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
314          mainView.removeAllTabs();
315          mainModel.stop();
316          loadAndInitProject(dataBase);
317          mainView.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
318       }
319 
320    } // execLoadCommand
321 
322 
323    private void loadAndInitProject(File dataBase) {
324       storageEngineType = StorageManager.getEngineType(dataBase);
325       mainModel.setStorageEngine(StorageManager.createStorageEngine(dataBase));
326       mainModel.start();
327       initSubControllersAndViews(activeControllers);
328 
329       // activate actions
330       getAction(CMD_CLOSE_WS).setEnabled(true);
331       getAction(CMD_SAVE).setEnabled(true);
332       getAction(CMD_LOAD).setEnabled(false);
333       getAction(CMD_SAVE_AS).setEnabled(true);
334 
335       this.modelIsModified = false;
336       mainModel.setProjectName(dataBase.getName());
337       mainView.setTitlePostfix(dataBase);
338    }
339 
340 
341    /**
342     * Command to save as a project.
343     */
344    @ExecCmd(name = CMD_SAVE_AS)
345    protected void execSaveAsCommand() {
346       LOGGER.info("Command Save AS");
347       File dataBase = getDatabase(true);
348 
349       StorageEngineType targetFileType = StorageManager.getEngineType(dataBase);
350 
351       if (dataBase != null) {
352          if (storageEngineType == targetFileType) {
353             mainModel.getStorageManager().copyTo(dataBase);
354             this.modelIsModified = false;
355             mainView.setTitlePostfix(dataBase);
356          }
357          else {
358 
359             File workingCopy = new File(dataBase.getParentFile(), Long
360                   .toString(System.currentTimeMillis())
361                   + Constant.DOT + storageEngineType.name());
362             mainModel.getStorageManager().copyTo(workingCopy);
363             mainView.removeAllTabs();
364             mainModel.stop();
365             mainModel.setStorageEngine(null);
366 
367             StorageEngineConverter converter = new StorageEngineConverter();
368             converter.convert(workingCopy, dataBase);
369             loadAndInitProject(dataBase);
370             workingCopy.delete();
371 
372             storageEngineType = targetFileType;
373             mainView.setTitlePostfix(dataBase);
374 
375          }
376       }
377 
378    } // execSaveCommand
379 
380 
381    /**
382     * Command to save the project
383     */
384    @ExecCmd(name = CMD_SAVE)
385    protected void execSaveCommand() {
386       LOGGER.info("Command Save");
387       mainModel.getStorageManager().commit();
388       this.modelIsModified = false;
389    } // execSaveCommand
390 
391 
392    /**
393     * Command to show the about dialog
394     */
395    @ExecCmd(name = CMD_SHOW_ABOUT_DIALOG)
396    protected void execShowAboutDialog() {
397       LOGGER.info("Show About Dialog.");
398       AboutDialog dialog = new AboutDialog(GestureConstants.ABOUT, getLocator()
399             .getService(GuiBundleService.IDENTIFIER, GuiBundleService.class));
400       Point point = mainView.getLocation();
401       point.translate(100, 60);
402       dialog.setLocation(point);
403       dialog.setVisible(true);
404    } // execShowAboutDialog
405 
406 
407    /**
408     * Command to show the device manager.
409     */
410    @ExecCmd(name = CMD_SHOW_DEVICE_MANAGER)
411    protected void execShowDeviceManager() {
412       LOGGER.info("Show Device Manager.");
413       Point point = mainView.getLocation();
414       point.translate(100, 60);
415       deviceManager.showView(point);
416    }
417 
418 
419    /**
420     * Returns the component factory referenced in the locator.
421     * 
422     * @return the component factory referenced in the locator.
423     */
424    private ComponentFactory getComponentFactory() {
425       return getLocator().getService(ComponentFactory.class.getName(),
426             ComponentFactory.class);
427    }
428 
429 
430    /**
431     * Returns a file handle to the database to be opened.
432     * 
433     * @return file handle to the database to be opened.
434     */
435    private File getDatabase(boolean isSaveDialog) {
436       File file = null;
437       JFileChooser chooser = new JFileChooser();
438       chooser.addChoosableFileFilter(FileType.db4oWorkbench.getFilter());
439       chooser.addChoosableFileFilter(FileType.xstreamWorkbench.getFilter());
440       chooser.setFileFilter(FileType.compressedWorkbench.getFilter());
441       chooser.setCurrentDirectory(new File(properties
442             .getProperty(Property.WORKING_DIRECTORY)));
443       int result = 0;
444 
445       if (isSaveDialog) {
446          result = chooser.showSaveDialog(null);
447       }
448       else {
449          result = chooser.showOpenDialog(null);
450       }
451 
452       if (result == JFileChooser.APPROVE_OPTION) {
453          file = chooser.getSelectedFile();
454 
455          if (file != null) {
456             try {
457                ExtensionFileFilter fileFilter = (ExtensionFileFilter)chooser
458                      .getFileFilter();
459                if (!fileFilter.accept(file)) {
460                   file = new File(file.getAbsolutePath() + Constant.DOT
461                         + fileFilter.getExtension());
462                }
463             }
464             catch (Exception e) {
465                e.printStackTrace();
466             }
467 
468             properties.setProperty(Property.WORKING_DIRECTORY, file.getParent());
469          }
470 
471       }
472 
473       return file;
474    } // getDatabase
475 
476 
477    /*
478     * (non-Javadoc)
479     * 
480     * @see org.ximtec.igesture.tool.locator.Service#getIdentifier()
481     */
482    @Override
483    public String getIdentifier() {
484       return IDENTIFIER;
485    } // getIdentifier
486 
487 
488    @Override
489    public TabbedView getView() {
490       return null;
491    } // getView
492 
493 
494    /**
495     * Initialises the controllers and sub views. This method has to be called in
496     * the EDT.
497     * 
498     * @param controllers
499     */
500    private void initControllers(Class< ? >[] controllers) {
501       if (!SwingUtilities.isEventDispatchThread()) {
502          throw new RuntimeException("Must not be executed in the EDT.");
503       }
504 
505       for (Class< ? > clazz : controllers) {
506          try {
507             Controller controller = createController(clazz);
508             addController(controller);
509             mainView.addTab(controller.getView());
510          }
511          catch (Exception e) {
512             LOGGER.log(Level.SEVERE, "Could not initialize view. "
513                   + clazz.getName(), e);
514          }
515       }
516    }
517 
518 
519    /**
520     * Initialises the main view.
521     * <ul>
522     * <li>Add Actions</li>
523     * </ul>
524     */
525    private void initMainView() {
526       if (mainView == null) {
527 
528          addAction(CMD_LOAD, new GenericLocateableAction(this,
529                GestureConstants.OPEN_PROJECT, CMD_LOAD));
530          addAction(CMD_SAVE, new GenericLocateableAction(this,
531                GestureConstants.SAVE, CMD_SAVE));
532          addAction(CMD_EXIT, new GenericLocateableAction(this,
533                GestureConstants.EXIT, CMD_EXIT));
534          addAction(CMD_SHOW_ABOUT_DIALOG, new GenericLocateableAction(this,
535                GestureConstants.ABOUT, CMD_SHOW_ABOUT_DIALOG));
536          addAction(CMD_CLOSE_WS, new GenericLocateableAction(this,
537                GestureConstants.CLOSE_PROJECT, CMD_CLOSE_WS));
538          addAction(CMD_SAVE_AS, new GenericLocateableAction(this,
539                GestureConstants.SAVE_AS, CMD_SAVE_AS));
540          addAction(CMD_SHOW_DEVICE_MANAGER, new GenericLocateableAction(this,
541                GestureConstants.DEVICE_MANAGER, CMD_SHOW_DEVICE_MANAGER));
542 
543          mainView = EdtProxy.newInstance(new MainView(this), IMainView.class);
544          mainView.addWindowListener(new MainWindowAdapter(this));
545       }
546    }
547 
548 
549    /**
550     * Initialises the different services. These are the
551     * <ul>
552     * <li>MainModel</li>
553     * <li>Gui Bundle</li>
554     * <li>Gesture Device Client</li>
555     * <li>Component Factory</li>
556     * </ul>
557     * 
558     * After creating the services, they are started.
559     */
560    private void initServices() {
561       guiBundle = new GuiBundleService(GestureConstants.RESOURCE_BUNDLE);
562 
563       properties = new Properties();
564 
565       try {
566          properties
567                .loadFromXML(new FileInputStream(GestureConstants.PROPERTIES));
568       }
569       catch (Exception e) {
570          // if no properties are available, set default values
571          properties.setProperty(Property.WORKING_DIRECTORY, System
572                .getProperty(GestureConstants.USER_DIR));
573          LOGGER.log(Level.WARNING, "Failed to load properties.");
574       }
575 
576       mainModel = new MainModel(null, this, properties);
577       deviceClient = new SwingMouseReaderService();
578 
579       /**
580        * Register the services
581        */
582 
583       setLocator(Locator.getDefault());
584       getLocator().addService(mainModel);
585       getLocator().addService(guiBundle);
586       getLocator().addService(deviceClient);
587 
588       // init device manager service, ! guiBundle service needs to be
589       // installed first
590       deviceManager = new DeviceManagerService(this,
591             GestureConstants.DEVICE_MANAGER, guiBundle);
592       getLocator().addService(deviceManager);
593 
594       getLocator().addService(new ComponentFactory(guiBundle));
595       getLocator().addService(this);
596       getLocator().startAll();
597    } // initServices
598 
599 
600    /**
601     * Initialises controllers an views connected to the main controller. All
602     * controllers are initialised in the EDT.
603     * 
604     * @param controllers An array of controllers. These controllers are
605     *           initialised.
606     */
607    private void initSubControllersAndViews(final Class< ? >[] controllers) {
608 
609       if (SwingUtilities.isEventDispatchThread()) {
610          initControllers(controllers);
611       }
612       else {
613          try {
614             SwingUtilities.invokeAndWait(new Runnable() {
615 
616                @Override
617                public void run() {
618                   initControllers(controllers);
619                }
620             });
621 
622          }
623          catch (Exception e) {
624             e.printStackTrace();
625             LOGGER.log(Level.SEVERE, "View Initialization failed. ");
626          }
627       }
628    }
629 
630 
631    /**
632     * Handles property change events and persists the changed data model.
633     * 
634     * @param event
635     */
636    private void persist(IndexedPropertyChangeEvent event) {
637       LOGGER
638             .info("Store, Delete, Update: indexed property " + event.getSource());
639 
640       if (event.getOldValue() == null) {
641          mainModel.getStorageManager().store((DataObject)event.getNewValue());
642       }
643       else if (event.getNewValue() == null
644             && event.getOldValue() instanceof DataObject) {
645          mainModel.getStorageManager().remove((DataObject)event.getOldValue());
646       }
647 
648       mainModel.getStorageManager().update((DataObject)event.getSource());
649    } // persist
650 
651 
652    /**
653     * Handles property change events and persists the changed data model.
654     * 
655     * @param event
656     */
657    private void persist(PropertyChangeEvent event) {
658       LOGGER.info("Update: property " + event.getSource());
659       mainModel.getStorageManager().update((DataObject)event.getSource());
660    } // persist
661 
662 
663    /*
664     * (non-Javadoc)
665     * 
666     * @see
667     * org.ximtec.igesture.tool.core.DefaultController#propertyChange(java.beans
668     * .PropertyChangeEvent)
669     */
670    @Override
671    public void propertyChange(PropertyChangeEvent event) {
672       LOGGER.info("PropertyChange");
673       super.propertyChange(event);
674 
675       this.modelIsModified = true;
676 
677       // Dispatch DataObjects
678       if (event.getSource() instanceof DataObject) {
679 
680          if (event instanceof IndexedPropertyChangeEvent) {
681             persist((IndexedPropertyChangeEvent)event);
682          }
683          else {
684             persist(event);
685          }
686 
687       }
688       else if (event.getSource() instanceof DataObjectWrapper) {
689          LOGGER.info("DataObjectWrapper: "
690                + event.getSource().getClass().getName());
691 
692          if (event.getOldValue() instanceof DataObject
693                && event.getNewValue() == null) {
694             mainModel.getStorageManager()
695                   .remove((DataObject)event.getOldValue());
696          }
697          else if (event.getNewValue() instanceof DataObject
698                && event.getOldValue() == null) {
699             mainModel.getStorageManager().store((DataObject)event.getNewValue());
700          }
701          else if (event.getNewValue() instanceof DataObject
702                && event.getOldValue() != null) {
703             mainModel.getStorageManager()
704                   .update((DataObject)event.getNewValue());
705          }
706       }
707    } // propertyChange
708 
709 
710    /**
711     * Shows a Yes/No Dialog
712     * 
713     * @param key the key used in the Gui Bundle
714     * @return the return value of the Yes/No Dialog
715     */
716    private int showYesNoDialog(String key) {
717       String title = getComponentFactory().getGuiBundle().getName(
718             GestureConstants.APPLICATION_ROOT);
719       String text = Constant.SINGLE_QUOTE + mainModel.getProjectName()
720             + Constant.SINGLE_QUOTE_BLANK
721             + getComponentFactory().getGuiBundle().getShortDescription(key);
722       return JOptionPane.showConfirmDialog(null, text, title,
723             JOptionPane.YES_NO_CANCEL_OPTION);
724    }
725 
726 }