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 }