001    package nl.cwi.sen1.gui;
002    
003    import java.awt.BorderLayout;
004    import java.awt.Dimension;
005    import java.awt.Frame;
006    import java.awt.event.ActionEvent;
007    import java.awt.event.ActionListener;
008    import java.awt.event.WindowAdapter;
009    import java.awt.event.WindowEvent;
010    import java.io.IOException;
011    import java.io.ObjectInputStream;
012    import java.io.ObjectOutputStream;
013    import java.util.HashMap;
014    import java.util.LinkedList;
015    import java.util.List;
016    import java.util.Map;
017    
018    import javax.swing.AbstractAction;
019    import javax.swing.Action;
020    import javax.swing.ButtonGroup;
021    import javax.swing.Icon;
022    import javax.swing.JComponent;
023    import javax.swing.JFrame;
024    import javax.swing.JLabel;
025    import javax.swing.JMenu;
026    import javax.swing.JMenuBar;
027    import javax.swing.JMenuItem;
028    import javax.swing.JProgressBar;
029    import javax.swing.JRadioButtonMenuItem;
030    import javax.swing.SwingUtilities;
031    import javax.swing.WindowConstants;
032    
033    import net.infonode.docking.DockingWindow;
034    import net.infonode.docking.DockingWindowAdapter;
035    import net.infonode.docking.OperationAbortedException;
036    import net.infonode.docking.RootWindow;
037    import net.infonode.docking.View;
038    import net.infonode.docking.ViewSerializer;
039    import net.infonode.docking.properties.RootWindowProperties;
040    import net.infonode.docking.theme.BlueHighlightDockingTheme;
041    import net.infonode.docking.theme.ClassicDockingTheme;
042    import net.infonode.docking.theme.DefaultDockingTheme;
043    import net.infonode.docking.theme.DockingWindowsTheme;
044    import net.infonode.docking.theme.GradientDockingTheme;
045    import net.infonode.docking.theme.ShapedGradientDockingTheme;
046    import net.infonode.docking.theme.SlimFlatDockingTheme;
047    import net.infonode.docking.theme.SoftBlueIceDockingTheme;
048    import net.infonode.docking.util.DockingUtil;
049    import net.infonode.docking.util.ViewMap;
050    import net.infonode.tabbedpanel.titledtab.TitledTabStateProperties;
051    import net.infonode.util.Direction;
052    import nl.cwi.sen1.configapi.Factory;
053    import nl.cwi.sen1.configapi.types.ActionDescriptionList;
054    import nl.cwi.sen1.configapi.types.Event;
055    import nl.cwi.sen1.gui.component.NameChangedListener;
056    import nl.cwi.sen1.gui.component.StudioComponent;
057    import nl.cwi.sen1.gui.component.TooltipChangedListener;
058    import nl.cwi.sen1.gui.plugin.PluginLoader;
059    import nl.cwi.sen1.gui.plugin.StudioPlugin;
060    import nl.cwi.sen1.gui.plugin.StudioPluginListener;
061    import nl.cwi.sen1.util.StudioMenuBar;
062    import nl.cwi.sen1.util.StudioStatusBar;
063    import nl.cwi.sen1.util.StudioStatusBarConstraints;
064    import toolbus.AbstractTool;
065    import aterm.ATerm;
066    import aterm.ATermFactory;
067    import aterm.ATermList;
068    import aterm.pure.PureFactory;
069    
070    /**
071     * Toplevel window of The Meta-Environment. This class wraps two
072     * functionalities. First it builds up a toplevel window with tabs and menu's.
073     * Second it dynamically loads jars provided by the ToolBus. The loaded jars
074     * will add tabs to the GUI, and register menus using the Studio interface.
075     */
076    public class StudioImpl implements Studio, GuiTif {
077            private static int nextUniqueComponentID = 0;
078    
079            private PureFactory factory;
080    
081            private Factory configFactory;
082    
083            private GuiBridge bridge;
084    
085            private ViewMap viewsById;
086    
087            private Map<StudioComponent, Integer> idsByComponent;
088    
089            protected Map<View, StudioComponent> componentsByView;
090    
091            private Map<StudioComponent, List<JMenuItem>> componentMenus;
092    
093            protected RootWindow rootWindow;
094    
095            private RootWindowProperties properties;
096    
097            static private JFrame frame;
098    
099            private JLabel systemLabel;
100    
101            JProgressBar progressBar;
102    
103            private ActionDescriptionList menuList;
104    
105            private DockingWindowsTheme currentTheme;
106    
107            private View activeView;
108    
109            private StudioStatusBar statusBar;
110    
111            private boolean menuBarUpdatePending;
112    
113            private List<StudioPlugin> plugins;
114    
115            private List<String> jobQueue;
116    
117            public static void main(String args[]) throws Exception {
118                    new StudioImpl(args);
119            }
120    
121            static public Frame getFrame() {
122                    return frame;
123            }
124    
125            public StudioImpl(String[] args) {
126                    initialize();
127    
128                    rootWindow = createRootWindow();
129    
130                    statusBar = createStatusBar();
131    
132                    startFrameThread();
133    
134                    startBridgeThread(args);
135            }
136    
137            private void startBridgeThread(String[] args) {
138                    bridge = new GuiBridge(factory, this);
139                    try {
140                            bridge.init(args);
141                            bridge.connect();
142                            bridge.setLockObject(this);
143                    } catch (IOException e) {
144                            System.err.println("Could not establish connection to ToolBus: "
145                                            + e);
146                            System.exit(1);
147                    }
148    
149                    Thread thread = new Thread(new Runnable() {
150                            public void run() {
151                                    bridge.run();
152                            }
153                    });
154                    thread.setName("BasicStudio");
155                    thread.start();
156            }
157    
158            private void startFrameThread() {
159                    // Is it right that createFrame is not called with invokeLater? It does
160                    // solve the problem that the tile can be set, before the frame is
161                    // initialized.
162                    // SwingUtilities.invokeLater(new Runnable() {
163                    // public void run() {
164                    createFrame();
165                    // }
166                    // });
167            }
168    
169            private void initialize() {
170                    factory = new PureFactory();
171                    configFactory = Factory.getInstance(factory);
172                    menuList = configFactory.makeActionDescriptionList();
173                    properties = new RootWindowProperties();
174                    currentTheme = new ShapedGradientDockingTheme();
175                    idsByComponent = new HashMap<StudioComponent, Integer>();
176                    componentsByView = new HashMap<View, StudioComponent>();
177                    componentMenus = new HashMap<StudioComponent, List<JMenuItem>>();
178                    viewsById = new ViewMap();
179                    plugins = new LinkedList<StudioPlugin>();
180                    progressBar = new JProgressBar();
181                    systemLabel = new JLabel();
182                    jobQueue = new LinkedList<String>();
183            }
184    
185            synchronized private static int nextComponentID() {
186                    return nextUniqueComponentID++;
187            }
188    
189            protected RootWindow createRootWindow() {
190                    ViewSerializer viewSerializer = new ViewSerializer() {
191                            public void writeView(View arg0, ObjectOutputStream arg1) {
192                            }
193    
194                            public View readView(ObjectInputStream arg0) {
195                                    return null;
196                            }
197                    };
198    
199                    RootWindow root = new RootWindow(viewSerializer);
200    
201                    // Set gradient theme. The theme properties object is the super object
202                    // of our properties object, which
203                    // means our property value settings will override the theme values
204                    properties.addSuperObject(currentTheme.getRootWindowProperties());
205    
206                    // Our properties object is the super object of the root window
207                    // properties object, so all property values of the
208                    // theme and in our property object will be used by the root window
209                    root.getRootWindowProperties().addSuperObject(properties);
210                    // root.getRootWindowProperties().getTabWindowProperties().getTabbedPanelProperties().getTabAreaProperties().setTabAreaVisiblePolicy(TabAreaVisiblePolicy.MORE_THAN_ONE_TAB);
211    
212                    root.getWindowBar(Direction.DOWN).setEnabled(true);
213    
214                    root.addListener(new DockingWindowAdapter() {
215                            public void viewFocusChanged(View oldView, View newView) {
216                                    if (newView != null) {
217                                            activeView = newView;
218                                            updateMenuBar();
219                                            updateStatusBar();
220                                            StudioComponent component = getComponent(activeView);
221                                            if (component != null) {
222                                                    component.receiveFocus();
223                                            } else {
224                                                    System.err
225                                                                    .println("Internal error: viewFocusChanged no active component found");
226                                                    Thread.dumpStack();
227                                            }
228                                    }
229                            }
230    
231                            public void windowClosing(DockingWindow window)
232                                            throws OperationAbortedException {
233                                    if (window instanceof View) {
234                                            StudioComponent component = componentsByView.get(window);
235    
236                                            try {
237                                                    component.requestClose();
238                                            } catch (CloseAbortedException e) {
239                                                    throw new OperationAbortedException(e.getMessage(), e
240                                                                    .getCause());
241                                            }
242                                    }
243                            }
244    
245                            public void windowClosed(DockingWindow window) {
246                                    if (window instanceof View) {
247                                            StudioComponent component = componentsByView.get(window);
248    
249                                            componentsByView.remove(window);
250                                            componentMenus.remove(component);
251    
252                                            component.close();
253                                    }
254                            }
255                    });
256    
257                    return root;
258            }
259    
260            private void updateStatusBar() {
261                    if (activeView != null) {
262                            StudioComponent active = getComponent(activeView);
263    
264                            if (active != null) {
265                                    JComponent[] components = active.getStatusBarComponents();
266    
267                                    for (int i = statusBar.getComponentCount(); i > 5; i--) {
268                                            statusBar.remove(i - 1);
269                                    }
270    
271                                    if (components != null) {
272                                            for (JComponent cur : components) {
273                                                    statusBar.addSeparator();
274                                                    statusBar.add(cur);
275                                            }
276                                    }
277                                    statusBar.repaint();
278                            } else {
279                                    System.err
280                                                    .println("Internal error: updateStatusBar no active component found");
281                                    Thread.dumpStack();
282                            }
283                    }
284            }
285    
286            private void updateMenuBar() {
287                    if (!menuBarUpdatePending) {
288                            menuBarUpdatePending = true;
289                            SwingUtilities.invokeLater(new Runnable() {
290                                    public void run() {
291                                            if (frame != null) {
292                                                    frame.setJMenuBar(createMenuBar());
293                                            }
294                                            if (rootWindow != null) {
295                                                    rootWindow.revalidate();
296                                            }
297                                            menuBarUpdatePending = false;
298                                    }
299                            });
300                    }
301            }
302    
303            public ATermFactory getATermFactory() {
304                    return factory;
305            }
306    
307            protected View createView(final StudioComponent component, int id) {
308                    final String name = component.getName();
309                    Icon icon = component.getIcon();
310                    JComponent viewComponent = component.getViewComponent();
311                    View view = new View(name, icon, viewComponent);
312                    componentsByView.put(view, component);
313                    viewsById.addView(id, view);
314    
315                    return view;
316            }
317    
318            protected int registerComponent(final StudioComponent component) {
319                    int id = nextComponentID();
320                    idsByComponent.put(component, new Integer(id));
321                    return id;
322            }
323    
324            public void addComponent(StudioComponent component) {
325                    int id = registerComponent(component);
326                    final View view = createView(component, id);
327                    SwingUtilities.invokeLater(new Runnable() {
328                            public void run() {
329                                    showView(view);
330                            }
331                    });
332                    addStudioComponentNameChangedListener(component);
333                    addStudioComponentTooltipChangedListener(component);
334            }
335    
336            public void removeComponent(StudioComponent component) {
337                    if (component != null) {
338                            int id = getComponentId(component);
339    
340                            if (id != -1) {
341                                    final View view = deleteView(id);
342                                    SwingUtilities.invokeLater(new Runnable() {
343                                            public void run() {
344                                                    view.close();
345                                            }
346                                    });
347                                    idsByComponent.remove(component);
348                            } else {
349                                    System.err.println("Can not remove non-registered component: "
350                                                    + component.getName());
351                            }
352                    } else {
353                            System.err.println("Can not remove non-registered component");
354                    }
355            }
356    
357            private int getComponentId(StudioComponent component) {
358                    Integer componentId = idsByComponent.get(component);
359    
360                    if (componentId != null) {
361                            return componentId.intValue();
362                    }
363                    return -1;
364            }
365    
366            private View deleteView(int id) {
367                    View view = viewsById.getView(id);
368                    viewsById.removeView(id);
369                    return view;
370            }
371    
372            private void createFrame() {
373                    frame = new JFrame();
374                    frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
375                    // frame.getContentPane().add(createToolBar(), BorderLayout.NORTH);
376                    frame.getContentPane().add(statusBar, BorderLayout.SOUTH);
377                    frame.getContentPane().add(rootWindow, BorderLayout.CENTER);
378                    frame.addWindowListener(new WindowAdapter() {
379                            public void windowClosing(WindowEvent e) {
380                                    bridge.postEvent(factory.make("window-closing-event"));
381                            }
382                    });
383                    frame.pack();
384                    frame.setExtendedState(Frame.MAXIMIZED_BOTH);
385                    frame.setVisible(true);
386            }
387    
388            private StudioStatusBar createStatusBar() {
389                    StudioStatusBar statusPanel = new StudioStatusBar();
390    
391                    systemLabel.setPreferredSize(new Dimension(300, 18));
392                    statusPanel.add(systemLabel);
393                    statusPanel.addSeparator();
394    
395                    statusPanel.add(progressBar);
396                    statusPanel.addSeparator();
397    
398                    JLabel filler = new JLabel();
399                    statusPanel.add(filler, new StudioStatusBarConstraints(1.0));
400    
401                    // JButton cancel = new JButton("Cancel");
402                    // statusPanel.add(cancel);
403    
404                    return statusPanel;
405            }
406    
407            private JMenuBar createMenuBar() {
408                    StudioMenuBar menuBar = new StudioMenuBar(factory, bridge);
409                    menuBar.add(createEmptyFileMenu());
410                    menuBar.add(createViewsMenu());
411    
412                    ActionDescriptionList menus = menuList;
413                    menuBar.add(menus);
414    
415                    if (activeView != null && activeView.getRootWindow() != null) {
416                            StudioComponent activeComponent = getComponent(activeView);
417                            List<JMenuItem> list = getComponentMenus(activeComponent);
418                            if (list != null) {
419                                    for (Object menu : list) {
420                                            if (menu instanceof MenuItem) {
421                                                    MenuItem menuItem = (MenuItem) menu;
422                                                    menuBar.add(menuItem.getEvent(), menuItem.getAction());
423                                            } else {
424                                                    menuBar.add((JMenu) menu);
425                                            }
426                                    }
427                            }
428                    }
429    
430                    return menuBar.getMenuBar();
431            }
432    
433            private JMenu createViewsMenu() {
434                    JMenu menu = new JMenu("Views");
435                    int count = viewsById.getViewCount();
436    
437                    for (--count; count >= 0; count--) {
438                            final View view = viewsById.getViewAtIndex(count);
439                            JMenuItem item = new JMenuItem(view.getTitle());
440                            item.setEnabled(view.getRootWindow() == null);
441                            item.addActionListener(new AbstractAction() {
442                                    public void actionPerformed(ActionEvent e) {
443                                            showView(view);
444                                            view.restoreFocus();
445                                    }
446                            });
447    
448                            menu.add(item);
449                    }
450    
451                    return menu;
452            }
453    
454            private List<JMenuItem> getComponentMenus(StudioComponent component) {
455                    if (componentMenus == null) {
456                            return null;
457                    }
458    
459                    return componentMenus.get(component);
460            }
461    
462            private JMenu createEmptyFileMenu() {
463                    JMenu fileMenu = new JMenu("File");
464                    fileMenu.add(createThemesMenu());
465    
466                    return fileMenu;
467            }
468    
469            protected void showView(View view) {
470                    if (rootWindow == null) {
471                            rootWindow = createRootWindow();
472                            frame.getContentPane().add(rootWindow, BorderLayout.CENTER);
473                    }
474                    DockingUtil.addWindow(view, rootWindow);
475                    rootWindow.revalidate();
476            }
477    
478            private JMenu createThemesMenu() {
479                    JMenu themesMenu = new JMenu("Themes");
480    
481                    DockingWindowsTheme[] themes = { new DefaultDockingTheme(),
482                                    new BlueHighlightDockingTheme(), new SlimFlatDockingTheme(),
483                                    new GradientDockingTheme(), new ShapedGradientDockingTheme(),
484                                    new SoftBlueIceDockingTheme(), new ClassicDockingTheme() };
485    
486                    ButtonGroup group = new ButtonGroup();
487    
488                    for (final DockingWindowsTheme theme : themes) {
489                            JMenuItem item = new JRadioButtonMenuItem(theme.getName());
490                            item.addActionListener(new ActionListener() {
491                                    public void actionPerformed(ActionEvent e) {
492                                            // Clear the modified properties values
493                                            properties.getMap().clear(true);
494                                            setTheme(theme);
495                                    }
496                            });
497                            if (theme.getName() == currentTheme.getName()) {
498                                    item.setSelected(true);
499                            }
500                            group.add(item);
501                            themesMenu.add(item);
502                    }
503    
504                    return themesMenu;
505            }
506    
507            private void setTheme(DockingWindowsTheme theme) {
508                    properties.replaceSuperObject(currentTheme.getRootWindowProperties(),
509                                    theme.getRootWindowProperties());
510                    currentTheme = theme;
511            }
512    
513            public void setTitle(String title) {
514                    if (frame != null) {
515                            frame.setTitle(title);
516                    }
517            }
518    
519            public void recAckEvent(ATerm t0) {
520            }
521    
522            public void recTerminate(ATerm t) {
523                    frame.dispose();
524            }
525    
526            public void connect(String toolName, final AbstractTool tool) {
527                    try {
528                            tool.connect(toolName, bridge.getAddress(), bridge.getPort());
529                            tool.setLockObject(this);
530                    } catch (IOException e) {
531                            System.err.println("Failed to connect '" + toolName
532                                            + "' to ToolBus");
533                            e.printStackTrace();
534                    }
535                    Thread thread = new Thread(new Runnable() {
536                            public void run() {
537                                    tool.run();
538                            }
539                    });
540                    thread.setName(toolName + "-runner");
541                    thread.start();
542            }
543    
544            private StudioComponent getComponent(View view) {
545                    return componentsByView.get(view);
546            }
547    
548            private void startPlugin(PluginLoader loader) {
549              final StudioPlugin plugin = loader.instantiatePlugin();
550              if (plugin != null) {
551                registerPlugin(plugin);
552                Thread thread = new Thread(new Runnable() {
553                  public void run() {
554                    plugin.initStudioPlugin(StudioImpl.this);
555                  }
556                });
557                thread.setName(plugin.getName() + "-starter");
558                thread.start();
559              }
560            }
561    
562            private void registerPlugin(StudioPlugin plugin) {
563                    plugins.add(plugin);
564                    plugin.addStudioPluginListener(new StudioPluginListener() {
565                            public void studioPluginClosed(StudioPlugin plugin) {
566                                    plugins.remove(plugin);
567                            }
568                    });
569            }
570    
571            public void loadJar(String pluginJar) {
572              System.err.println("loadJar1" + pluginJar);
573                    startPlugin(new PluginLoader(pluginJar));
574            }
575    
576            public void loadJarUrls(String pluginJar, ATerm classPath) {
577              System.err.println("loadJar2" + pluginJar);
578                    startPlugin(new PluginLoader(pluginJar, (ATermList) classPath));
579            }
580    
581            public void loadJarClasspath(String pluginJar, String classPath) {
582              System.err.println("loadJar3" + pluginJar);
583                    startPlugin(new PluginLoader(pluginJar, classPath));
584            }
585    
586            public void addMenuEvents(ATerm menus) {
587                    ActionDescriptionList more = configFactory
588                                    .ActionDescriptionListFromTerm(menus);
589                    menuList = menuList.concat(more);
590                    updateMenuBar();
591            }
592    
593            public void addComponentMenu(StudioComponent component, JMenu menu) {
594                    if (componentMenus == null) {
595                            componentMenus = new HashMap<StudioComponent, List<JMenuItem>>();
596                    }
597    
598                    List<JMenuItem> menus = componentMenus.get(component);
599                    if (menus == null) {
600                            menus = new LinkedList<JMenuItem>();
601                    }
602                    menus.add(menu);
603    
604                    componentMenus.put(component, menus);
605                    updateMenuBar();
606            }
607    
608            public void addComponentMenu(StudioComponent component, Event menuPath,
609                            Action action) {
610                    MenuItem item = new MenuItem(menuPath, action);
611                    List<JMenuItem> menus = getMenus(component);
612                    menus.add(item);
613    
614                    componentMenus.put(component, menus);
615                    updateMenuBar();
616            }
617    
618            private List<JMenuItem> getMenus(StudioComponent component) {
619                    List<JMenuItem> menus = componentMenus.get(component);
620                    if (menus == null) {
621                            menus = new LinkedList<JMenuItem>();
622                    }
623                    return menus;
624            }
625    
626            public void requestFocus(StudioComponent component) {
627                    Integer id = idsByComponent.get(component);
628    
629                    if (id != null) {
630                            View view = viewsById.getView(id.intValue());
631    
632                            if (view != null) {
633                                    view.restoreFocus();
634                            } else {
635                                    System.err.println("No view found for: " + component.getName());
636                            }
637                    } else {
638                            System.err.println("This component does not have a view: "
639                                            + component.getName());
640                    }
641            }
642    
643            public void makeVisible(StudioComponent component) {
644                    Integer id = idsByComponent.get(component);
645    
646                    if (id != null) {
647                            View view = viewsById.getView(id.intValue());
648    
649                            if (view != null) {
650                                    view.makeVisible();
651                            } else {
652                                    System.err.println("No view found for: " + component.getName());
653                            }
654                    } else {
655                            System.err.println("This component does not have a view: "
656                                            + component.getName());
657                    }
658            }
659    
660            public void setStatus(String message) {
661                    systemLabel.setText(message);
662            }
663    
664            public void jobDone(String message) {
665                    jobQueue.remove(message);
666                    if (jobQueue.isEmpty()) {
667                            progressBar.setIndeterminate(false);
668                            setStatus("Idle");
669                    } else {
670                            setStatus(jobQueue.get(0));
671                    }
672            }
673    
674            public void addJob(String message) {
675                    jobQueue.add(message);
676                    setStatus(message);
677                    progressBar.setIndeterminate(true);
678            }
679    
680            protected void addStudioComponentNameChangedListener(
681                            final StudioComponent component) {
682                    component.addNameChangedListener(new NameChangedListener() {
683                            public void componentNameChanged() {
684                                    int id = getComponentId(component);
685                                    if (id != -1) {
686                                            View view = viewsById.getView(id);
687                                            if (view != null) {
688                                                    view.getViewProperties().setTitle(component.getName());
689                                            }
690                                    }
691                            }
692                    });
693            }
694    
695            protected void addStudioComponentTooltipChangedListener(
696                            final StudioComponent component) {
697                    component.addTooltipChangedListener(new TooltipChangedListener() {
698                            public void componentTooltipChanged() {
699                                    int id = getComponentId(component);
700                                    if (id != -1) {
701                                            View view = viewsById.getView(id);
702                                            if (view != null) {
703                                                    TitledTabStateProperties properties = view.getWindowProperties().getTabProperties()
704                                                                    .getTitledTabProperties().getNormalProperties();
705                                                    if (properties != null) {
706                                                                    properties.setToolTipText(component.getTooltip());
707                                                    }
708                                            }
709                                    }
710                            }
711                    });
712            }
713    }