001 package nl.cwi.sen1.gui.plugin; 002 003 import java.awt.Dimension; 004 import java.awt.event.ActionEvent; 005 import java.awt.event.MouseEvent; 006 import java.io.IOException; 007 import java.util.HashMap; 008 import java.util.Iterator; 009 import java.util.Map; 010 011 import javax.swing.AbstractAction; 012 import javax.swing.JComponent; 013 import javax.swing.JFileChooser; 014 import javax.swing.JLabel; 015 import javax.swing.JOptionPane; 016 import javax.swing.event.MouseInputAdapter; 017 018 import nl.cwi.sen1.configapi.Factory; 019 import nl.cwi.sen1.configapi.types.ActionDescription; 020 import nl.cwi.sen1.configapi.types.ActionDescriptionList; 021 import nl.cwi.sen1.configapi.types.Event; 022 import nl.cwi.sen1.configapi.types.ItemLabels; 023 import nl.cwi.sen1.configapi.types.PropertyList; 024 import nl.cwi.sen1.configapi.types.shortcut.Shortcut; 025 import nl.cwi.sen1.gui.CloseAbortedException; 026 import nl.cwi.sen1.gui.Studio; 027 import nl.cwi.sen1.gui.StudioImpl; 028 import nl.cwi.sen1.gui.StudioImplWithPredefinedLayout; 029 import nl.cwi.sen1.gui.StudioWithPredefinedLayout; 030 import nl.cwi.sen1.gui.component.StudioComponent; 031 import nl.cwi.sen1.gui.component.StudioComponentAdapter; 032 import nl.cwi.sen1.gui.component.StudioComponentImpl; 033 import nl.cwi.sen1.gui.plugin.editor.FileToBigException; 034 import nl.cwi.sen1.gui.plugin.editor.SwingEditor; 035 import nl.cwi.sen1.util.DefaultPopupImpl; 036 import nl.cwi.sen1.util.MenuAction; 037 import nl.cwi.sen1.util.MouseAdapter; 038 import aterm.ATerm; 039 import aterm.ATermFactory; 040 import aterm.ATermList; 041 import aterm.pure.PureFactory; 042 043 public class EditorPlugin extends DefaultStudioPlugin implements 044 EditorPluginTif { 045 private static final String TOOL_NAME = "editor-plugin"; 046 047 private Factory configFactory; 048 049 private Map<String, Editor> editors; 050 051 private Map<String, StudioComponent> componentsById; 052 053 private Map<String, Map<String, JComponent>> statusbarsById; 054 055 private Studio studio; 056 057 private EditorPluginBridge bridge; 058 059 private DefaultPopupImpl popup; 060 061 public EditorPlugin() { 062 editors = new HashMap<String, Editor>(); 063 componentsById = new HashMap<String, StudioComponent>(); 064 statusbarsById = new HashMap<String, Map<String, JComponent>>(); 065 } 066 067 public void isModified(ATerm editorId) { 068 Editor panel = getEditorPanel(editorId); 069 String modified; 070 071 if (panel != null && panel.isModified()) { 072 modified = "true"; 073 } else { 074 modified = "false"; 075 } 076 077 ATerm term = studio.getATermFactory().parse(modified); 078 ATerm event = studio.getATermFactory().make( 079 "is-modified(<term>,<term>)", editorId, term); 080 bridge.postEvent(event); 081 } 082 083 public void getContents(ATerm editorId) { 084 Editor panel = getEditorPanel(editorId); 085 086 if (panel != null) { 087 String contents = panel.getContents(); 088 ATerm event = studio.getATermFactory().make( 089 "contents(<term>,<str>)", editorId, contents); 090 bridge.postEvent(event); 091 } 092 } 093 094 public void setContents(ATerm editorId, String contents) { 095 Editor panel = getEditorPanel(editorId); 096 097 if (panel != null) { 098 panel.setContents(contents); 099 } 100 } 101 102 public void writeContents(ATerm editorId) { 103 Editor panel = getEditorPanel(editorId); 104 105 if (panel != null && panel.isEditable()) { 106 try { 107 panel.writeContents(panel.getFilename()); 108 panel.setModified(false); 109 ATerm event = studio.getATermFactory().make( 110 "contents-written(<term>)", editorId); 111 bridge.postEvent(event); 112 } catch (IOException e) { 113 try { 114 showErrorDialog(panel, JOptionPane.OK_OPTION, 115 "\n\nError saving changes."); 116 } catch (CloseAbortedException e1) { 117 } 118 } 119 } 120 } 121 122 public void setFocus(ATerm editorId, ATerm focus) { 123 errorapi.Factory factory = errorapi.Factory 124 .getInstance((PureFactory) studio.getATermFactory()); 125 126 Editor panel = getEditorPanel(editorId); 127 128 if (panel != null) { 129 panel.setFocus(factory.AreaFromTerm(focus)); 130 } 131 } 132 133 public void setSelection(ATerm editorId, ATerm selection) { 134 errorapi.Factory factory = errorapi.Factory 135 .getInstance((PureFactory) studio.getATermFactory()); 136 137 Editor panel = getEditorPanel(editorId); 138 139 if (panel != null) { 140 panel.setSelection(factory.AreaFromTerm(selection)); 141 } 142 } 143 144 private Editor getEditorPanel(ATerm editorId) { 145 return editors.get(editorId.toString()); 146 } 147 148 public void clearFocus(ATerm editorId) { 149 } 150 151 public void registerTextCategories(ATerm editorId, ATerm categories) { 152 Editor panel = getEditorPanel(editorId); 153 154 if (panel != null) { 155 PropertyList properties = Factory.getInstance( 156 (PureFactory) categories.getFactory()) 157 .PropertyListFromTerm(categories); 158 panel.registerCategories(properties); 159 } 160 } 161 162 public void addActions(final ATerm editorId, ATerm menuList) { 163 StudioComponent comp = componentsById.get(editorId.toString()); 164 final Editor panel = getEditorPanel(editorId); 165 166 if (comp != null) { 167 addEditorActions(editorId, comp, (ATermList) menuList, panel); 168 createFileMenu(editorId, comp); 169 createEditMenu(getEditorPanel(editorId), comp); 170 panel.addMouseListener(new MouseInputAdapter() { 171 public void mousePressed(MouseEvent e) { 172 int offset = panel.getMouseOffset(e.getX(), e.getY()); 173 if (!e.isPopupTrigger()) { 174 bridge 175 .postEvent(editorId.getFactory().make( 176 "offset-event(<term>,<int>)", editorId, 177 offset)); 178 } 179 } 180 }); 181 } 182 } 183 184 private void addEditorActions(final ATerm editorId, StudioComponent comp, 185 ATermList actionList, final Editor panel) { 186 while (!actionList.isEmpty()) { 187 ActionDescription desc = configFactory 188 .ActionDescriptionFromTerm(actionList.getFirst()); 189 Event action = desc.getEvent(); 190 191 if (action.isMenu() || action.isMenuShortcut()) { 192 studio.addComponentMenu(comp, action, new MenuAction(editorId, 193 bridge, action)); 194 } else if (action.isClick() || action.isPopup()) { 195 panel.addMouseListener(new MouseAdapter(editorId, bridge, 196 action)); 197 } 198 actionList = actionList.getNext(); 199 } 200 } 201 202 private void createFileMenu(final ATerm editorId, final StudioComponent comp) { 203 Factory factory = Factory.getInstance((PureFactory) studio 204 .getATermFactory()); 205 206 ItemLabels items = factory.makeItemLabels(factory 207 .makeItem_Label("File"), factory.makeItem_Label("Save")); 208 209 Shortcut shortcut = factory.makeShortCut_Shortcut( 210 factory.makeKeyModifierList(factory 211 .makeKeyModifier_M_CTRL()), factory 212 .makeVirtualKey_VK_S()); 213 Event event = factory.makeEvent_MenuShortcut(items, shortcut, 214 "Save file"); 215 216 studio.addComponentMenu(comp, event, new AbstractAction() { 217 public void actionPerformed(ActionEvent e) { 218 Editor editor = getEditorPanel(editorId); 219 if (editor != null && editor.isEditable()) { 220 try { 221 editor.writeContents(editor.getFilename()); 222 ATerm event = studio.getATermFactory().make( 223 "contents-saved(<term>)", editorId); 224 bridge.postEvent(event); 225 if (comp.getName().endsWith("*")) { 226 comp.setName(comp.getName().substring(0, 227 comp.getName().length() - 1)); 228 } 229 } catch (IOException e1) { 230 try { 231 showErrorDialog(editor, JOptionPane.OK_OPTION, 232 "\n\nError saving changes."); 233 } catch (CloseAbortedException e2) { 234 } 235 } 236 } 237 } 238 }); 239 240 items = factory.makeItemLabels(factory.makeItem_Label("File"), factory 241 .makeItem_Label("Save a copy...")); 242 243 event = factory.makeEvent_Menu(items, 244 "Copy the contents of this file to another"); 245 246 studio.addComponentMenu(comp, event, new AbstractAction() { 247 Editor editor = getEditorPanel(editorId); 248 249 public void actionPerformed(ActionEvent e) { 250 JFileChooser chooser = new JFileChooser(); 251 chooser 252 .setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); 253 if (chooser.showDialog(StudioImpl.getFrame(), "Save Copy") == JFileChooser.APPROVE_OPTION) { 254 String path = chooser.getSelectedFile().getAbsolutePath(); 255 if (!path.equals(editor.getFilename()) 256 || editor.isEditable()) { 257 try { 258 editor.writeContents(path); 259 } catch (IOException e1) { 260 try { 261 showErrorDialog(editor, JOptionPane.OK_OPTION, 262 "\n\nError saving copy."); 263 } catch (CloseAbortedException e2) { 264 } 265 } 266 } 267 } 268 } 269 }); 270 271 items = factory.makeItemLabels(factory.makeItem_Label("File"), factory 272 .makeItem_Label("Save All")); 273 274 shortcut = factory.makeShortCut_Shortcut(factory.makeKeyModifierList( 275 factory.makeKeyModifier_M_CTRL(), factory 276 .makeKeyModifier_M_SHIFT()), factory 277 .makeVirtualKey_VK_A()); 278 279 event = factory.makeEvent_MenuShortcut(items, shortcut, 280 "Save all files"); 281 282 studio.addComponentMenu(comp, event, new AbstractAction() { 283 public void actionPerformed(ActionEvent e) { 284 for (String id : editors.keySet()) { 285 Editor editor = editors.get(id); 286 ATerm term = studio.getATermFactory().parse(id); 287 StudioComponent comp = componentsById.get(id); 288 289 if (editor.isEditable()) { 290 try { 291 editor.writeContents(editor.getFilename()); 292 ATerm event = studio.getATermFactory().make( 293 "contents-saved(<term>)", term); 294 bridge.postEvent(event); 295 if (comp.getName().endsWith("*")) { 296 comp.setName(comp.getName().substring(0, 297 comp.getName().length() - 1)); 298 } 299 } catch (IOException e1) { 300 try { 301 showErrorDialog(editor, JOptionPane.OK_OPTION, 302 "\n\nError saving changes."); 303 } catch (CloseAbortedException e2) { 304 } 305 } 306 } 307 } 308 } 309 }); 310 311 items = factory.makeItemLabels(factory.makeItem_Label("File"), factory 312 .makeItem_Label("Refresh")); 313 314 event = factory.makeEvent_Menu(items, 315 "Read in the contents of the file again"); 316 317 studio.addComponentMenu(comp, event, new AbstractAction() { 318 Editor editor = getEditorPanel(editorId); 319 320 public void actionPerformed(ActionEvent e) { 321 editor.rereadContents(); 322 editor.setModified(false); 323 ATerm event = studio.getATermFactory().make( 324 "contents-saved(<term>)", editorId); 325 bridge.postEvent(event); 326 if (comp.getName().endsWith("*")) { 327 comp.setName(comp.getName().substring(0, 328 comp.getName().length() - 1)); 329 } 330 } 331 }); 332 333 items = factory.makeItemLabels(factory.makeItem_Label("File"), factory 334 .makeItem_Label("Close")); 335 336 shortcut = factory.makeShortCut_Shortcut( 337 factory.makeKeyModifierList(factory 338 .makeKeyModifier_M_CTRL()), factory 339 .makeVirtualKey_VK_W()); 340 event = factory.makeEvent_MenuShortcut(items, shortcut, 341 "Close this file"); 342 343 studio.addComponentMenu(comp, event, new AbstractAction() { 344 public void actionPerformed(ActionEvent e) { 345 closeEditor(comp, editorId.toString(), 346 JOptionPane.YES_NO_CANCEL_OPTION); 347 } 348 }); 349 350 items = factory.makeItemLabels(factory.makeItem_Label("File"), factory 351 .makeItem_Label("Close All")); 352 353 shortcut = factory.makeShortCut_Shortcut(factory.makeKeyModifierList( 354 factory.makeKeyModifier_M_CTRL(), factory 355 .makeKeyModifier_M_SHIFT()), factory 356 .makeVirtualKey_VK_W()); 357 event = factory.makeEvent_MenuShortcut(items, shortcut, 358 "Close all files"); 359 360 studio.addComponentMenu(comp, event, new AbstractAction() { 361 public void actionPerformed(ActionEvent e) { 362 Map<String, StudioComponent> editors = new HashMap<String, StudioComponent>( 363 componentsById); 364 for (String id : editors.keySet()) { 365 StudioComponent component = componentsById.get(id); 366 closeEditor(component, id, JOptionPane.YES_NO_CANCEL_OPTION); 367 } 368 } 369 }); 370 } 371 372 private void createEditMenu(Editor editor, StudioComponent comp) { 373 studio.addComponentMenu(comp, editor.getEditMenu()); 374 } 375 376 public void displayMessage(ATerm editorId, String message) { 377 Map<String, JComponent> statusBarComponents = statusbarsById 378 .get(editorId.toString()); 379 380 JLabel status = (JLabel) statusBarComponents.get("Status"); 381 status.setText(message); 382 // comp.setStatusMessage(message); 383 } 384 385 public void setEditable(ATerm editorId, ATerm editable) { 386 ATermFactory factory = studio.getATermFactory(); 387 boolean edit = true; 388 389 if (editable.equals(factory.make("false"))) { 390 edit = false; 391 } 392 393 Editor panel = getEditorPanel(editorId); 394 if (panel != null) { 395 panel.setEditable(edit); 396 } 397 } 398 399 public void killEditor(ATerm editorId) { 400 StudioComponent comp = componentsById.get(editorId.toString()); 401 402 if (comp != null) { 403 closeEditor(comp, editorId.toString(), JOptionPane.YES_NO_OPTION); 404 } 405 } 406 407 private void closeEditor(StudioComponent comp, String id, int optionType) { 408 Editor panel = editors.get(id); 409 410 if (panel != null && panel.isModified()) { 411 studio.makeVisible(comp); 412 try { 413 showSaveConfirmDialog(panel, optionType); 414 } catch (CloseAbortedException e) { 415 return; 416 } 417 } 418 studio.removeComponent(comp); 419 cleanupEditor(comp, id); 420 } 421 422 private void cleanupEditor(StudioComponent comp, String id) { 423 componentsById.remove(id); 424 statusbarsById.remove(id); 425 editors.remove(id); 426 } 427 428 public void setCursorAtOffset(ATerm editorId, int offset) { 429 Editor panel = getEditorPanel(editorId); 430 431 if (panel != null) { 432 panel.setCursorAtOffset(offset); 433 } 434 } 435 436 public void editFile(ATerm editorId, String filename) { 437 try { 438 createPanel(editorId, filename); 439 } catch (IOException e) { 440 JOptionPane.showMessageDialog(StudioImpl.getFrame(), filename 441 + " could not be opened:" + e); 442 editorDisconnected(editorId); 443 } catch (FileToBigException e) { 444 JOptionPane.showMessageDialog(StudioImpl.getFrame(), filename 445 + " is too big to handle (>1Mbyte)"); 446 editorDisconnected(editorId); 447 } 448 } 449 450 public void setTooltip(ATerm editorId, String tooltip) { 451 StudioComponent comp = componentsById.get(editorId.toString()); 452 if (comp != null) { 453 comp.setTooltip(tooltip); 454 } 455 } 456 457 public void setInfo(ATerm editorId, String info) { 458 Map<String, JComponent> statusBar = statusbarsById.get(editorId 459 .toString()); 460 JLabel label = (JLabel) statusBar.get("Module"); 461 if (info.equals("")) { 462 label.setText(" "); 463 } else { 464 label.setText(info); 465 } 466 } 467 468 public void highlightSlices(ATerm editorId, ATerm slices) { 469 Editor panel = getEditorPanel(editorId); 470 if (panel != null && !panel.isModified()) { 471 panel.registerSlices(slices); 472 } 473 } 474 475 public void editorToFront(ATerm editorId) { 476 StudioComponent comp = componentsById.get(editorId.toString()); 477 478 if (comp != null) { 479 studio.requestFocus(comp); 480 } 481 } 482 483 public void rereadContents(ATerm editorId) { 484 Editor panel = getEditorPanel(editorId); 485 if (panel != null) { 486 panel.rereadContents(); 487 ATerm event = studio.getATermFactory().make( 488 "contents-changed(<term>)", editorId); 489 bridge.postEvent(event); 490 } 491 } 492 493 public String getName() { 494 return TOOL_NAME; 495 } 496 497 public void initStudioPlugin(Studio studio) { 498 this.studio = studio; 499 configFactory = Factory.getInstance((PureFactory) studio 500 .getATermFactory()); 501 bridge = new EditorPluginBridge(studio.getATermFactory(), this); 502 bridge.setLockObject(this); 503 popup = new DefaultPopupImpl(bridge); 504 studio.connect(getName(), bridge); 505 } 506 507 private Editor createPanel(final ATerm editorId, String filename) 508 throws IOException, FileToBigException { 509 final String id = editorId.toString(); 510 Editor editorPanel = editors.get(id); 511 512 if (editorPanel == null) { 513 final SwingEditor panel = new SwingEditor(id, filename); 514 editorPanel = panel; 515 516 addEditorModifiedListener(editorId, panel); 517 518 final JLabel status = new JLabel(" "); 519 status.setPreferredSize(new Dimension(250, 18)); 520 521 final JLabel module = new JLabel(" "); 522 module.setPreferredSize(new Dimension(250, 18)); 523 524 Map<String, JComponent> statusBarComponents = new HashMap<String, JComponent>(); 525 statusBarComponents.put("Status", status); 526 statusBarComponents.put("Module", module); 527 528 int beginIndex = filename.lastIndexOf("/") + 1; 529 String componentName = filename.substring(beginIndex, filename 530 .length()); 531 StudioComponent comp = new StudioComponentImpl(componentName, panel) { 532 public JComponent[] getStatusBarComponents() { 533 return new JComponent[] { status, module }; 534 } 535 }; 536 addStudioComponentListener(editorId, panel, comp); 537 538 editors.put(id, panel); 539 componentsById.put(id, comp); 540 statusbarsById.put(id, statusBarComponents); 541 ((StudioWithPredefinedLayout) studio).addComponent(comp, 542 StudioImplWithPredefinedLayout.TOP_RIGHT); 543 // studio.addComponentStatusBar(comp, statusBarComponents); 544 545 } 546 547 return editorPanel; 548 } 549 550 private void addEditorModifiedListener(final ATerm editorId, 551 final Editor panel) { 552 panel.addEditorModifiedListener(new EditorModifiedListener() { 553 public void editorModified(EditorModifiedEvent e) { 554 StudioComponent comp = componentsById.get(editorId.toString()); 555 if (comp != null) { 556 if (!comp.getName().endsWith("*")) { 557 comp.setName(comp.getName() + "*"); 558 } 559 } 560 panel.clearSelections(); 561 ATerm event = studio.getATermFactory().make( 562 "contents-changed(<term>)", editorId); 563 bridge.postEvent(event); 564 } 565 }); 566 } 567 568 private void editorDisconnected(final ATerm editorId) { 569 StudioComponent comp = componentsById.get(editorId.toString()); 570 cleanupEditor(comp, editorId.toString()); 571 ATerm event = studio.getATermFactory().make( 572 "editor-disconnected(<term>)", editorId); 573 bridge.postEvent(event); 574 575 // Is this supposed to be here? Shouldn't meta-studio handle this after closing a component? 576 studio.removeComponent(comp); 577 } 578 579 private void addStudioComponentListener(final ATerm editorId, 580 final SwingEditor panel, StudioComponent comp) { 581 comp.addStudioComponentListener(new StudioComponentAdapter() { 582 public void componentRequestClose() throws CloseAbortedException { 583 if (panel.isModified()) { 584 showSaveConfirmDialog(panel, 585 JOptionPane.YES_NO_CANCEL_OPTION); 586 } 587 } 588 589 public void componentClose() { 590 editorDisconnected(editorId); 591 } 592 593 public void componentFocusReceived() { 594 panel.requestFocus(); 595 } 596 }); 597 } 598 599 private void showSaveConfirmDialog(final Editor panel, int optionType) 600 throws CloseAbortedException { 601 switch (JOptionPane 602 .showConfirmDialog( 603 StudioImpl.getFrame(), 604 panel.getFilename() 605 + "\n\nThe editor for this file has unsaved changes.\nDo you want to save your changes?", 606 panel.getFilename(), optionType)) { 607 case JOptionPane.YES_OPTION: 608 try { 609 panel.writeContents(panel.getFilename()); 610 } catch (IOException e) { 611 showErrorDialog( 612 panel, 613 JOptionPane.OK_CANCEL_OPTION, 614 "\n\nError saving changes.\nClosing this editor will result in loosing your changes."); 615 } 616 break; 617 case JOptionPane.CANCEL_OPTION: 618 throw new CloseAbortedException(); 619 } 620 } 621 622 public void recAckEvent(ATerm t0) { 623 } 624 625 public void recTerminate(ATerm t0) { 626 Iterator<Editor> iter = editors.values().iterator(); 627 while (iter.hasNext()) { 628 Editor panel = iter.next(); 629 if (panel.isModified()) { 630 String id = panel.getId(); 631 StudioComponent comp = componentsById.get(id); 632 studio.makeVisible(comp); 633 try { 634 showSaveConfirmDialog(panel, JOptionPane.YES_NO_OPTION); 635 } catch (CloseAbortedException e) { 636 // this should never happen (no CANCEL button) 637 e.printStackTrace(); 638 } 639 } 640 } 641 fireStudioPluginClosed(); 642 } 643 644 public void showErrorDialog(final Editor panel, int optionType, String error) 645 throws CloseAbortedException { 646 int option; 647 if (optionType == JOptionPane.OK_OPTION) { 648 JOptionPane.showMessageDialog(StudioImpl.getFrame(), panel 649 .getFilename() 650 + error, panel.getFilename(), JOptionPane.ERROR_MESSAGE); 651 } else { 652 option = JOptionPane.showConfirmDialog(StudioImpl.getFrame(), panel 653 .getFilename() 654 + error, panel.getFilename(), optionType, 655 JOptionPane.ERROR_MESSAGE); 656 switch (option) { 657 case JOptionPane.OK_OPTION: 658 break; 659 case JOptionPane.CANCEL_OPTION: 660 throw new CloseAbortedException(); 661 } 662 } 663 } 664 665 public void showPopup(final ATerm editorId, ATerm menuList) { 666 ActionDescriptionList l = configFactory 667 .ActionDescriptionListFromTerm(menuList); 668 popup.showPopup(editorId, l); 669 } 670 }