001    package jigcell.compare.ui;
002    
003    import java.awt.BorderLayout;
004    import java.awt.Component;
005    import java.awt.Container;
006    import java.awt.Cursor;
007    import java.awt.Dimension;
008    import java.awt.EventQueue;
009    import java.awt.Frame;
010    import java.awt.Toolkit;
011    import java.awt.Window;
012    import java.awt.event.ActionEvent;
013    import java.awt.event.ActionListener;
014    import java.awt.event.ComponentEvent;
015    import java.awt.event.ComponentListener;
016    import java.awt.event.MouseEvent;
017    import java.awt.event.MouseListener;
018    import java.awt.event.WindowEvent;
019    import java.awt.event.WindowListener;
020    import java.awt.event.WindowStateListener;
021    import java.beans.PropertyChangeEvent;
022    import java.lang.reflect.Field;
023    import java.util.ArrayList;
024    import java.util.Arrays;
025    import java.util.HashSet;
026    import java.util.Iterator;
027    import java.util.List;
028    import java.util.Map;
029    import javax.swing.BorderFactory;
030    import javax.swing.ImageIcon;
031    import javax.swing.JButton;
032    import javax.swing.JComponent;
033    import javax.swing.JDialog;
034    import javax.swing.JFrame;
035    import javax.swing.JLabel;
036    import javax.swing.JMenu;
037    import javax.swing.JMenuBar;
038    import javax.swing.JPanel;
039    import javax.swing.JScrollPane;
040    import javax.swing.JTabbedPane;
041    import javax.swing.JToolBar;
042    import javax.swing.JWindow;
043    import javax.swing.LookAndFeel;
044    import javax.swing.SwingUtilities;
045    import javax.swing.Timer;
046    import javax.swing.UIManager;
047    import javax.swing.event.ChangeEvent;
048    import javax.swing.event.ChangeListener;
049    import jigcell.compare.IConfigEditor;
050    import jigcell.compare.ITab;
051    import jigcell.compare.impl.Compare;
052    import jigcell.compare.impl.Config;
053    import jigcell.compare.impl.ProxyBuilder;
054    
055    /**
056     * The Comparator frontend.
057     *
058     * <p>
059     * This code is licensed under the DARPA BioCOMP Open Source License.  See LICENSE for more details.
060     * </p>
061     *
062     * @author Nicholas Allen
063     */
064    
065    public class CompareFrontEnd extends Compare implements ChangeListener, ComponentListener, ITab, WindowListener, WindowStateListener {
066    
067       /**
068        * Property key for look and feel class
069        */
070    
071       public final static String CONFIG_LOOKFEEL = "CompareFrontEnd.lookAndFeel";
072    
073       /**
074        * Property key for tab config names
075        */
076    
077       public final static String CONFIG_TABCONFIG = "CompareFrontEnd.tabs";
078    
079       /**
080        * Property key for window coordinates
081        */
082    
083       public final static String CONFIG_WINDOWCOORDINATES = "CompareFrontEnd.windowCoordinates";
084    
085       /**
086        * Property key for window maximized state
087        */
088    
089       public final static String CONFIG_WINDOWMAXIMIZED = "CompareFrontEnd.windowMaximized";
090    
091       /**
092        * Initial dimensions of the Comparator window
093        */
094    
095       protected final static Dimension DIMENSION_WINDOW = new Dimension (880, 660);
096    
097       /**
098        * Number of screen buffers to request
099        */
100    
101       protected final static int COUNT_FRAMEBUFFER = 2;
102    
103       /**
104        * Default menu bar name
105        */
106    
107       protected final static String MENUBAR_DEFAULT = "default";
108    
109       /**
110        * Method signature of tabs
111        */
112    
113       private final static Class SIGNATURE_TAB [] = new Class [] {Compare.class, String.class};
114    
115       /**
116        * Provides interface help for the view
117        */
118    
119       protected InterfaceBuilder manager;
120    
121       /**
122        * Main interface
123        */
124    
125       protected JTabbedPane pane;
126    
127       /**
128        * Toolbar
129        */
130    
131       protected JToolBar toolbar;
132    
133       /**
134        * Menu builder
135        */
136    
137       protected MenuBuilder menuManager;
138    
139       /**
140        * Component of the tab to add to pane
141        */
142    
143       private Component tabComponent;
144    
145       /**
146        * Currently selected tab in the display frame
147        */
148    
149       private ITab selectedTab;
150    
151       /**
152        * Tab to add to pane
153        */
154    
155       private ITab tabImplementation;
156    
157       /**
158        * Current tab accessory
159        */
160    
161       private ITabAccessory currentTabAccessory;
162    
163       /**
164        * Default tab accessory
165        */
166    
167       private ITabAccessory defaultTabAccessory;
168    
169       /**
170        * Display frame for frontend shell
171        */
172    
173       private JFrame displayFrame;
174    
175       /**
176        * Splash screen while loading
177        */
178    
179       private SplashScreen splash;
180    
181       /**
182        * Information about a Comparator tab.
183        */
184    
185       protected static class FrontEndTabInfo extends TabInfo {
186    
187          /**
188           * Configures the tab
189           */
190    
191          private final IConfigEditor config;
192    
193          /**
194           * Tab toolbar tools
195           */
196    
197          private final JButton tools [];
198    
199          /**
200           * About the tab
201           */
202    
203          private final JDialog about;
204    
205          /**
206           * Tab menus
207           */
208    
209          private final JMenu menus [];
210    
211          /**
212           * Tab accessory
213           */
214    
215          private ITabAccessory accessory;
216    
217          /**
218           * Creates a new tab description.
219           *
220           * @param tab Tab
221           * @param menus Tab menus
222           * @param tools Tab toolbar tools
223           * @param about About dialog for the tab
224           * @param config Configure dialog the tab
225           */
226    
227          public FrontEndTabInfo (ITab tab, JMenu menus [], JButton tools [], JDialog about, IConfigEditor config) {
228             super (tab);
229             this.menus = menus;
230             this.tools = tools;
231             this.about = about;
232             this.config = config;
233          }
234    
235          /**
236           * About the tab.
237           */
238    
239          public JDialog getAbout () {
240             return about;
241          }
242    
243          /**
244           * The tab accessory.
245           */
246    
247          public ITabAccessory getAccessory () {
248             return accessory;
249          }
250    
251          /**
252           * Configures the tab.
253           */
254    
255          public IConfigEditor getConfigEditor () {
256             return config;
257          }
258    
259          /**
260           * The tab menus.
261           */
262    
263          public JMenu [] getMenus () {
264             return menus;
265          }
266    
267          /**
268           * The tab toolbar tools.
269           */
270    
271          public JButton [] getTools () {
272             return tools;
273          }
274    
275          /**
276           * Sets the tab accessory.  If the accessory is null, the default accessory will be used.
277           *
278           * @param accessory Tab accessory
279           */
280    
281          public void setAccessory (ITabAccessory accessory) {
282             this.accessory = accessory;
283          }
284       }
285    
286       /**
287        * A splash screen to display while things are loading.
288        */
289    
290       protected class SplashScreen extends JWindow implements ActionListener, MouseListener, Runnable {
291    
292          /**
293           * Length to display splash screen after loading is done
294           */
295    
296          protected final static int TIMER_LENGTH = 1000;
297    
298          /**
299           * Lock for controlling the splash screen
300           */
301    
302          public final Object SPLASH_LOCK = new Object ();
303    
304          /**
305           * Minimum size splash to display
306           */
307    
308          protected Dimension SIZE_MINIMUM = new Dimension (400, 300);
309    
310          /**
311           * Status display
312           */
313    
314          protected JLabel messageLabel;
315    
316          /**
317           * Status text to update to
318           */
319    
320          protected String message;
321    
322          /**
323           * Delay timer
324           */
325    
326          protected Timer timer;
327    
328          /**
329           * Lock for controlling update manipulation
330           */
331    
332          private final Object UPDATE_LOCK = new Object ();
333    
334          public SplashScreen (JFrame compare, String message) {
335             super (compare);
336             SplashScreen.this.setCursor (Cursor.getPredefinedCursor (Cursor.WAIT_CURSOR));
337             timer = new Timer (TIMER_LENGTH, this);
338             Container pane = this.getContentPane ();
339             pane.add (new JLabel (UIManager.getIcon ("jigcell.compare.splashScreen")), BorderLayout.CENTER);
340             messageLabel = new JLabel (message);
341             pane.add (messageLabel, BorderLayout.SOUTH);
342             this.getRootPane ().setBorder (BorderFactory.createRaisedBevelBorder ());
343             this.pack ();
344             Dimension size = getSize ();
345             if (size.width < SIZE_MINIMUM.width)
346                size.width = SIZE_MINIMUM.width;
347             if (size.height < SIZE_MINIMUM.height)
348                size.height = SIZE_MINIMUM.height;
349             setSize (size);
350             this.setLocationRelativeTo (null);
351             this.addWindowListener (CompareFrontEnd.this);
352             this.addMouseListener (this);
353             this.setVisible (true);
354          }
355    
356          public void actionPerformed (ActionEvent e) {
357             this.setVisible (false);
358             this.dispose ();
359          }
360    
361          /**
362           * Destroys the splash screen.
363           */
364    
365          public void dispose () {
366             synchronized (SPLASH_LOCK) {
367                this.removeMouseListener (this);
368                if (timer != null) {
369                   timer.stop ();
370                   timer.removeActionListener (this);
371                   timer = null;
372                }
373                super.dispose ();
374             }
375          }
376    
377          public void mouseClicked (MouseEvent e) {}
378    
379          public void mouseEntered (MouseEvent e) {}
380    
381          public void mouseExited (MouseEvent e) {}
382    
383          public void mousePressed (MouseEvent e) {
384             this.setVisible (false);
385             this.dispose ();
386          }
387    
388          public void mouseReleased (MouseEvent e) {}
389    
390          public void run () {
391             messageLabel.setText (message);
392             message = null;
393          }
394    
395          /**
396           * Changes the status message displayed on the splash screen.
397           *
398           * @param message Message to display
399           */
400    
401          public void updateStatus (String message) {
402             updateStatus (message, false);
403          }
404    
405          /**
406           * Changes the status message displayed on the splash screen.
407           *
408           * @param message Message to display
409           * @param done Whether the load has finished
410           */
411    
412          public void updateStatus (String message, boolean done) {
413             synchronized (UPDATE_LOCK) {
414                if (done && timer != null)
415                   timer.start ();
416                if (EventQueue.isDispatchThread ())
417                   messageLabel.setText (message);
418                else
419                   try {
420                      this.message = message;
421                      message (message);
422                      EventQueue.invokeAndWait (this);
423                   } catch (Exception e) {
424                      assertion (getString ("CompareFrontEnd.splashUpdateError"), e);
425                   }
426             }
427          }
428       }
429    
430       static {
431          new IconLoader (CompareFrontEnd.class);
432       }
433    
434       /**
435        * Starts a new graphical Comparator.
436        *
437        * @param args Program arguments
438        */
439    
440       public static void main (String args []) {
441          new CompareFrontEnd (args == null || args.length == 0 ? DEFAULT_CONFIG : args [0]);
442       }
443    
444       /**
445        * Finds the class for a given look and feel name.
446        *
447        * @param name Name of look and feel
448        */
449    
450       protected static Class findLookAndFeelClass (String name) {
451          if (name == null)
452             return null;
453          UIManager.LookAndFeelInfo looks [] = UIManager.getInstalledLookAndFeels ();
454          for (int looksCount = 0, looksLength = looks.length; looksCount < looksLength; looksCount++) {
455             UIManager.LookAndFeelInfo look = looks [looksCount];
456             if (name.equals (look.getName ()))
457                try {
458                   return Class.forName (look.getClassName ());
459                } catch (Exception e) {
460                   assertion (getString ("CompareFrontEnd.lookFeelCreateError"), e);
461                   return null;
462                }
463          }
464          return null;
465       }
466    
467       /**
468        * Installs a new look and feel if it has not already been registered.
469        *
470        * @param name Name of new look and feel
471        * @param className Class for new look and feel
472        */
473    
474       protected static void installLookAndFeel (String name, String className) {
475          if (name == null || className == null)
476             throw new IllegalArgumentException ();
477          UIManager.LookAndFeelInfo looks [] = UIManager.getInstalledLookAndFeels ();
478          int looksLength = looks.length;
479          for (int looksCount = 0; looksCount < looksLength; looksCount++)
480             if (className.equals (looks [looksCount].getClassName ()))
481                return;
482          UIManager.LookAndFeelInfo newLooks [] = new UIManager.LookAndFeelInfo [looksLength + 1];
483          System.arraycopy (looks, 0, newLooks, 0, looksLength);
484          newLooks [looksLength] = new UIManager.LookAndFeelInfo (name, className);
485          UIManager.setInstalledLookAndFeels (newLooks);
486       }
487    
488       /**
489        * Creates a new graphical Comparator.
490        *
491        * @param configFile Name of configuration file
492        */
493    
494       public CompareFrontEnd (String configFile) {
495          super (configFile);
496          try {
497             Config config = getConfig ();
498             Config compareConfig = config.getConfig (Config.convertToClassMarker (this));
499             for (Iterator iterator = Config.convertToList (config.findValue (configMarkers, CONFIG_TABCONFIG, false, false, false)).iterator ();
500                iterator.hasNext (); )
501                try {
502                   String marker = Config.createInstanceMarker ();
503                   Config tabConfig = compareConfig.getConfig ((String) iterator.next ());
504                   String name = tabConfig.findValue (null, CONFIG_CLASSNAME, false, false, false);
505                   config.setConfig (marker, tabConfig);
506                   if (splash != null)
507                      synchronized (splash.SPLASH_LOCK) {
508                         splash.updateStatus (formatString ("CompareFrontEnd.tabLoad", name));
509                      }
510                   ITab tab = (ITab) Class.forName (name).getDeclaredConstructor (SIGNATURE_TAB).newInstance (new Object [] {this, marker});
511                   tab.readConfiguration (ITab.STATE_ENDINITIALIZE);
512                } catch (Exception e) {
513                   shellHandleException (MESSAGE_WARNING, getString ("CompareFrontEnd.tabLoadError"), e);
514                }
515          } catch (Exception e) {
516             assertion (getString ("CompareFrontEnd.tabListError"), e);
517          }
518          if (splash != null)
519             synchronized (splash.SPLASH_LOCK) {
520                splash.updateStatus (getString ("CompareFrontEnd.startupFinished"), true);
521                splash = null;
522             }
523       }
524    
525       /**
526        * Adds a tab to the main Comparator display.
527        *
528        * @param component Contents of the tab
529        */
530    
531       public TabInfo addTab (ITab component) {
532          return addTab (component, component instanceof Component, null, null);
533       }
534    
535       /**
536        * Adds a tab to the main Comparator display.
537        *
538        * @param component Contents of the tab
539        * @param wrapped Whether component should be wrapped in a scrollpane
540        * @param menus Menus to be displayed when tab is active
541        * @param tools Tools to place in toolbar
542        */
543    
544       public TabInfo addTab (ITab component, boolean wrapped, JMenu menus [], JButton tools []) {
545          TabInfo info = new FrontEndTabInfo (component, menus, tools, component.createAboutDialog (), component.createConfigEditor ());
546          synchronized (TAB_LOCK) {
547             updateResourceMap (RESOURCE_TABS, component, info);
548             if (!(component instanceof Component))
549                return info;
550             tabImplementation = component;
551             if (wrapped) {
552                JScrollPane pane = new JScrollPane ((Component) component, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
553                   JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
554                pane.setBorder (InterfaceBuilder.BORDER_NOTHING);
555                tabComponent = pane;
556             } else
557                tabComponent = (Component) component;
558             if (EventQueue.isDispatchThread ())
559                addTabToView ();
560             else
561                try {
562                   EventQueue.invokeAndWait (ProxyBuilder.proxyRunnable (this, "addTabToView"));
563                } catch (Exception e) {
564                   assertion (getString ("CompareFrontEnd.tabAddError"), e);
565                }
566          }
567          return info;
568       }
569    
570       public void componentHidden (ComponentEvent e) {}
571    
572       public void componentMoved (ComponentEvent e) {
573          saveWindowSettings ();
574       }
575    
576       public void componentResized (ComponentEvent e) {
577          saveWindowSettings ();
578       }
579    
580       public void componentShown (ComponentEvent e) {}
581    
582       /**
583        * Starts configuring a Comparator tab.
584        */
585    
586       public void configure (ITab tab) {
587          IConfigEditor configEditor = getTabInfo (tab).getConfigEditor ();
588          if (configEditor == null)
589             return;
590          JDialog configDialog = configEditor.createConfigDialog ();
591          configDialog.setLocationRelativeTo (null);
592          if (!configDialog.isVisible ())
593             configDialog.setVisible (true);
594          configDialog.requestFocus ();
595       }
596    
597       /**
598        * {@inheritDoc}
599        */
600    
601       public JDialog createAboutDialog () {
602          return PanelTab.createAboutDialogForTab (CompareFrontEnd.class, manager);
603       }
604    
605       /**
606        * {@inheritDoc}
607        */
608    
609       public IConfigEditor createConfigEditor () {
610          IConfigEditor editor = new ConfigEditor (this, this, configMarkers, getConfig ().getConfig (Config.convertToClassMarker (this)));
611          editor.addOption (CONFIG_DATASOURCE, getString ("CompareFrontEnd.optionData"), String [].class);
612          UIManager.LookAndFeelInfo looks [] = UIManager.getInstalledLookAndFeels ();
613          int looksLength = looks.length;
614          String lookNames [] = new String [looksLength];
615          for (int looksCount = 0; looksCount < looksLength; looksCount++)
616             lookNames [looksCount] = looks [looksCount].getName ();
617          lookNames = (String []) new HashSet (Arrays.asList (lookNames)).toArray (new String [0]);
618          Arrays.sort (lookNames);
619          editor.addOption (CONFIG_LOOKFEEL, getString ("CompareFrontEnd.optionLook"), lookNames, true, Class.class);
620          return editor;
621       }
622    
623       /**
624        * The frontend shell display frame.  Returns null if this is a headless Comparator.
625        */
626    
627       public JFrame getDisplayFrame () {
628          return displayFrame;
629       }
630    
631       /**
632        * {@inheritDoc}
633        */
634    
635       public String getName () {
636          return getString ("CompareFrontEnd.tabName");
637       }
638    
639       /**
640        * The accessory component for a tab.
641        */
642    
643       public ITabAccessory getTabAccessory (ITab tab) {
644          ITabAccessory accessory = getTabInfo (tab).getAccessory ();
645          return accessory == null ? defaultTabAccessory : accessory;
646       }
647    
648       /**
649        * The currently selected tab in the display frame.
650        */
651    
652       public ITab getSelectedTab () {
653          return selectedTab;
654       }
655    
656       protected void propertyChange (PropertyChangeEvent e) {
657          if (PROPERTY_CONFIG_EDIT.equals (e.getPropertyName ()))
658             readConfiguration (STATE_RUNNING);
659          super.propertyChange (e);
660       }
661    
662       /**
663        * {@inheritDoc}
664        */
665    
666       public void readConfiguration (String state) {
667          updateLookAndFeel (state);
668       }
669    
670       /**
671        * Selects one of the tabs for the display frame.
672        *
673        * @param tab Underlying tab component
674        */
675    
676       public void selectTab (ITab tab) {
677          if (selectedTab == tab)
678             return;
679          for (Iterator iterator = getTabMap ().keySet ().iterator (); iterator.hasNext (); ) {
680             Object component = iterator.next ();
681             if (component == tab || component instanceof JScrollPane && ((JScrollPane) component).getViewport ().getView () == tab) {
682                pane.setSelectedComponent ((Component) component);
683                return;
684             }
685          }
686       }
687    
688       /**
689        * Sets the accessory component displayed for a tab.  If the accessory is null, the default accessory will be displayed.
690        *
691        * @param tab Comparator tab
692        * @param accessory Accessory
693        */
694    
695       public void setTabAccessory (ITab tab, ITabAccessory accessory) {
696          FrontEndTabInfo info = getTabInfo (tab);
697          info.setAccessory (accessory);
698          if (tab == getSelectedTab ())
699             updateViewAccessory (info);
700          displayFrame.validate ();
701          displayFrame.repaint ();
702       }
703    
704       /**
705        * Handle an exception in a Comparator component.
706        *
707        * @param type Exception type
708        * @param message Display message
709        * @param t Throwable
710        */
711    
712       public void shellHandleException (String type, Object message, Throwable t) {
713          manager.showErrorDialog (type, message, t);
714          assertion (message instanceof String ? (String) message : null, t);
715       }
716    
717       /**
718        * Handle an informational message in a Comparator component.
719        *
720        * @param type Message type
721        * @param message Display message
722        */
723    
724       public void shellHandleMessage (String type, Object message) {
725          manager.showMessageDialog (type, message);
726          if (message instanceof String)
727             message ((String) message);
728       }
729    
730       public void stateChanged (ChangeEvent e) {
731          if (e.getSource () != pane)
732             return;
733          Map tabMap = getTabMap ();
734          Component component = pane.getSelectedComponent ();
735          FrontEndTabInfo info = (FrontEndTabInfo) tabMap.get (component);
736          if (info == null && component instanceof JScrollPane)
737             info = (FrontEndTabInfo) tabMap.get (((JScrollPane) component).getViewport ().getView ());
738          selectedTab = info.getTab ();
739          updateViewTools (info);
740          updateViewMenus (info);
741          updateViewAccessory (info);
742          displayFrame.validate ();
743          displayFrame.repaint ();
744       }
745    
746       public void windowActivated (WindowEvent e) {}
747    
748       public void windowClosed (WindowEvent e) {
749          if (e.getWindow () instanceof SplashScreen)
750             displayFrame.setVisible (true);
751       }
752    
753       public void windowClosing (WindowEvent e) {
754          if (e.getWindow () == displayFrame)
755             exit (0);
756       }
757    
758       public void windowDeactivated (WindowEvent e) {}
759    
760       public void windowDeiconified (WindowEvent e) {}
761    
762       public void windowIconified (WindowEvent e) {}
763    
764       public void windowOpened (WindowEvent e) {
765          if (e.getWindow () == displayFrame) {
766             try {
767                Thread.currentThread ().setContextClassLoader (Compare.class.getClassLoader ());
768             } catch (Exception _e) {
769                assertion (getString ("Compare.startupLoaderError"), _e);
770             }
771             if (splash != null)
772                synchronized (splash.SPLASH_LOCK) {
773                   Window children [] = displayFrame.getOwnedWindows ();
774                   for (int i = 0, l = children.length; i < l; i++) {
775                      Window child = children [i];
776                      if (child != null && child instanceof SplashScreen) {
777                         child.setVisible (false);
778                         child.dispose ();
779                      }
780                   }
781                }
782             displayFrame.createBufferStrategy (COUNT_FRAMEBUFFER);
783             displayFrame.toFront ();
784             displayFrame.requestFocusInWindow ();
785          }
786       }
787    
788       public void windowStateChanged (WindowEvent e) {
789          saveWindowSettings ();
790       }
791    
792       protected void saveWindowSettings () {
793          Config config = getConfig ().getConfig (Config.convertToClassMarker (this));
794          int state = displayFrame.getExtendedState ();
795          config.setValue (CONFIG_WINDOWMAXIMIZED, (state & JFrame.MAXIMIZED_BOTH) == JFrame.MAXIMIZED_BOTH);
796          if (state == JFrame.NORMAL)
797             config.setValue (CONFIG_WINDOWCOORDINATES, displayFrame.getBounds ());
798          firePropertyChange (Compare.PROPERTY_CONFIG_EDIT);
799       }
800    
801       /**
802        * Displays information about a Comparator tab.
803        */
804    
805       protected void about (ITab tab) {
806          JDialog aboutDialog = getTabInfo (tab).getAbout ();
807          if (aboutDialog == null)
808             return;
809          aboutDialog.setLocationRelativeTo (null);
810          if (!aboutDialog.isVisible ())
811             aboutDialog.setVisible (true);
812          aboutDialog.requestFocus ();
813       }
814    
815       /**
816        * Inserts a new tab into the interface.
817        */
818    
819       protected void addTabToView () {
820          pane.addTab (tabImplementation.getName (), tabComponent);
821          if (pane.getTabCount () == 1)
822             selectTab (tabImplementation);
823          tabImplementation = null;
824          tabComponent = null;
825       }
826    
827       /**
828        * Creates the menus that will appear for all tabs.
829        */
830    
831       protected JMenu [] createSharedMenus () {
832          return menuManager.createMenus (MENUBAR_DEFAULT);
833       }
834    
835       /**
836        * Exits from the Comparator.
837        *
838        * @param status Status code
839        */
840    
841       protected void exit (int status) {
842          if (manager.showChoiceDialog (getString ("CompareFrontEnd.exitTitle"), getString ("CompareFrontEnd.exitQuestion"),
843             new String [] {getString ("CompareFrontEnd.exitButtonYes"), getString ("CompareFrontEnd.exitButtonNo")}, 1) != 0)
844             return;
845          displayFrame.setVisible (false);
846          displayFrame.dispose ();
847          super.exit (status);
848       }
849    
850       /**
851        * The tab info for a tab.
852        *
853        * @param tab Tab
854        */
855    
856       protected FrontEndTabInfo getTabInfo (ITab tab) {
857          for (Iterator iterator = getTabMap ().entrySet ().iterator (); iterator.hasNext (); ) {
858             Map.Entry entry = (Map.Entry) iterator.next ();
859             Object component = entry.getKey ();
860             if (component == tab || component instanceof JScrollPane && ((JScrollPane) component).getViewport ().getView () == tab)
861                return (FrontEndTabInfo) entry.getValue ();
862          }
863          return null;
864       }
865    
866       /**
867        * {@inheritDoc}
868        */
869    
870       protected void shellStableHook () {
871          super.shellStableHook ();
872          updateLookAndFeel (STATE_INITIALIZE);
873          splash = new SplashScreen (displayFrame, getString ("CompareFrontEnd.splashStartup"));
874          displayFrame = new JFrame (getString ("CompareFrontEnd.title"));
875          displayFrame.setIconImage (((ImageIcon) UIManager.getIcon ("jigcell.compare.programIcon")).getImage ());
876          Config config = getConfig ().getConfig (Config.convertToClassMarker (this));
877          try {
878             displayFrame.setBounds (Config.convertToRectangle (config.findValue (configMarkers, CONFIG_WINDOWCOORDINATES, false, false, false)));
879          } catch (Exception e) {
880             Compare.warning ("Unable to restore window position.", e);
881             displayFrame.setSize (DIMENSION_WINDOW);
882             displayFrame.setLocationRelativeTo (null);
883          }
884          if (Config.convertToBoolean (config.findValue (configMarkers, CONFIG_WINDOWMAXIMIZED, false, false, false), false))
885             displayFrame.setExtendedState (JFrame.MAXIMIZED_BOTH);
886          displayFrame.getToolkit ().setDynamicLayout (true);
887          displayFrame.setDefaultCloseOperation (JFrame.DO_NOTHING_ON_CLOSE);
888          displayFrame.addComponentListener (this);
889          displayFrame.addWindowListener (this);
890          displayFrame.addWindowStateListener (this);
891          manager = new InterfaceBuilder (this, displayFrame);
892          defaultTabAccessory = new MemoryMonitorAccessory ();
893          JPanel contentPane = new JPanel (new BorderLayout ());
894          pane = new JTabbedPane (JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
895          contentPane.add (pane, BorderLayout.CENTER);
896          pane.addChangeListener (this);
897          toolbar = new JToolBar ();
898          contentPane.add (toolbar, BorderLayout.NORTH);
899          toolbar.setRollover (true);
900          displayFrame.setContentPane (contentPane);
901          menuManager = new MenuBuilder (this);
902          synchronized (TAB_LOCK) {
903             updateResourceMap (RESOURCE_TABS, this,
904                new FrontEndTabInfo (this, createSharedMenus (), null, createAboutDialog (), createConfigEditor ()));
905          }
906       }
907    
908       /**
909        * {@inheritDoc}
910        */
911    
912       protected void shellStartHook () {
913          super.shellStartHook ();
914          try {
915             class FrontEndThreadGroup extends ThreadGroup {
916                public FrontEndThreadGroup () {
917                   super ("compare");
918                }
919    
920                public void uncaughtException (Thread thread, Throwable t) {
921                   shellHandleException (t instanceof AssertionError ? getString ("CompareFrontEnd.assertionError") :
922                      getString ("CompareFrontEnd.uncaughtError"), t.getMessage (), t);
923                }
924             }
925    
926             Class unsafeClass = Class.forName ("sun.misc.Unsafe");
927             Field unsafeField = unsafeClass.getDeclaredField ("theUnsafe");
928             unsafeField.setAccessible (true);
929             Object unsafe = unsafeField.get (null);
930             Long offset = (Long) unsafeClass.getMethod ("objectFieldOffset", new Class [] {Field.class}).invoke (unsafe,
931                new Object [] {EventQueue.class.getDeclaredField ("threadGroup")});
932             unsafeClass.getMethod ("putObject", new Class [] {Object.class, Long.TYPE, Object.class}).invoke (unsafe,
933                new Object [] {Toolkit.getDefaultToolkit ().getSystemEventQueue (), offset, new FrontEndThreadGroup ()});
934          } catch (Exception e) {
935             assertion (getString ("CompareFrontEnd.startupGroupError"), e);
936          }
937       }
938    
939       /**
940        * Modifies the application look and feel to match the current configuration.
941        *
942        * @param state Application state
943        */
944    
945       protected void updateLookAndFeel (String state) {
946          String lookFeelName = getConfig ().findValue (configMarkers, CONFIG_LOOKFEEL, false, false, false);
947          if (lookFeelName == null)
948             return;
949          Class lookFeelClass = findLookAndFeelClass (lookFeelName);
950          if (lookFeelClass == null)
951             return;
952          LookAndFeel currentLook = UIManager.getLookAndFeel ();
953          if (currentLook != null && currentLook.getClass () == lookFeelClass)
954             return;
955          try {
956             UIManager.setLookAndFeel ((LookAndFeel) lookFeelClass.newInstance ());
957          } catch (Exception e) {
958             shellHandleException (MESSAGE_WARNING, getString ("CompareFrontEnd.lookFeelLoadError"), e);
959          }
960          if (state != STATE_RUNNING)
961             return;
962          List windows = new ArrayList ();
963          windows.addAll (Arrays.asList (Frame.getFrames ()));
964          while (!windows.isEmpty ()) {
965             Window window = (Window) windows.remove (0);
966             windows.addAll (Arrays.asList (window.getOwnedWindows ()));
967             SwingUtilities.updateComponentTreeUI (window);
968          }
969          updateViewTools (getTabInfo (getSelectedTab ()));
970       }
971    
972       /**
973        * Fills in the tab accessory using the provided tab info.
974        *
975        * @param info Tab info
976        */
977    
978       protected void updateViewAccessory (FrontEndTabInfo info) {
979          ITab tab = info.getTab ();
980          ITabAccessory accessory = getTabAccessory (tab);
981          Container contentPane = displayFrame.getContentPane ();
982          if (currentTabAccessory != null)
983             try {
984                contentPane.remove (currentTabAccessory.getDisplay ());
985                currentTabAccessory.uninstall ();
986             } finally {
987                currentTabAccessory = null;
988             }
989          if (accessory == null)
990             return;
991          JComponent display = accessory.getDisplay ();
992          SwingUtilities.updateComponentTreeUI (display);
993          contentPane.add (display, BorderLayout.SOUTH);
994          currentTabAccessory = accessory;
995          accessory.install (this, tab);
996       }
997    
998       /**
999        * Fills in the menu bar using the provided tab info.
1000        *
1001        * @param info Tab info
1002        */
1003    
1004       protected void updateViewMenus (FrontEndTabInfo info) {
1005          JMenuBar menuBar = new JMenuBar ();
1006          JMenu menus [] = getTabInfo (this).getMenus ();
1007          if (menus != null)
1008             for (int i = 0, l = menus.length; i < l; i++)
1009                menuBar.add (menus [i]);
1010          menus = info.getMenus ();
1011          if (menus != null)
1012             for (int i = 0, l = menus.length; i < l; i++)
1013                menuBar.add (menus [i]);
1014          SwingUtilities.updateComponentTreeUI (menuBar);
1015          displayFrame.setJMenuBar (menuBar);
1016       }
1017    
1018       /**
1019        * Fills in the toolbar using the provided tab info.
1020        *
1021        * @param info Tab info
1022        */
1023    
1024       protected void updateViewTools (FrontEndTabInfo info) {
1025          JButton tools [] = info.getTools ();
1026          toolbar.removeAll ();
1027          if (tools == null)
1028             return;
1029          for (int i = 0, l = tools.length; i < l; i++) {
1030             JButton tool = tools [i];
1031             if (tool == null)
1032                toolbar.addSeparator ();
1033             else {
1034                SwingUtilities.updateComponentTreeUI (tool);
1035                toolbar.add (tool);
1036             }
1037          }
1038       }
1039    }