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 }