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 }