001: /*
002: * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
003: * for visualizing and manipulating spatial features with geometry and attributes.
004: *
005: * Copyright (C) 2003 Vivid Solutions
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: *
021: * For more information, contact:
022: *
023: * Vivid Solutions
024: * Suite #1A
025: * 2328 Government Street
026: * Victoria BC V8T 5G5
027: * Canada
028: *
029: * (250)385-6040
030: * www.vividsolutions.com
031: */
032: package com.vividsolutions.jump.workbench.ui.plugin;
033:
034: import com.vividsolutions.jts.util.Assert;
035:
036: import com.vividsolutions.jump.util.CollectionUtil;
037: import com.vividsolutions.jump.util.StringUtil;
038: import com.vividsolutions.jump.workbench.WorkbenchContext;
039: import com.vividsolutions.jump.workbench.plugin.*;
040: import com.vividsolutions.jump.workbench.ui.MenuNames;
041: import com.vividsolutions.jump.workbench.ui.task.TaskMonitorManager;
042:
043: import java.awt.Component;
044: import java.awt.event.ActionEvent;
045: import java.awt.event.ActionListener;
046:
047: import java.util.*;
048: import java.util.Collection;
049: import java.util.HashMap;
050: import java.util.Iterator;
051: import java.util.Map;
052:
053: import javax.swing.*;
054: import javax.swing.event.MenuEvent;
055: import javax.swing.event.MenuListener;
056: import javax.swing.event.PopupMenuEvent;
057: import javax.swing.event.PopupMenuListener;
058:
059: /**
060: * Adds a plug-in to the JUMP Workbench as a menu item.
061: */
062: //TODO - Refactoring: Rename this class to PlugInMenuInstaller [Jon Aquino
063: // 10/22/2003]
064: public class FeatureInstaller {
065: private interface Menu {
066:
067: void insert(JMenuItem menuItem, int i);
068:
069: String getText();
070:
071: int getItemCount();
072:
073: void add(JMenuItem menuItem);
074:
075: }
076:
077: private WorkbenchContext workbenchContext;
078:
079: private TaskMonitorManager taskMonitorManager = new TaskMonitorManager();
080:
081: private EnableCheckFactory checkFactory;
082:
083: public FeatureInstaller(WorkbenchContext workbenchContext) {
084: this .workbenchContext = workbenchContext;
085: checkFactory = new EnableCheckFactory(workbenchContext);
086: }
087:
088: /** @deprecated Use the EnableCheckFactory methods instead */
089: public MultiEnableCheck createLayersSelectedCheck() {
090: return new MultiEnableCheck()
091: .add(
092: checkFactory
093: .createWindowWithLayerNamePanelMustBeActiveCheck())
094: .add(
095: checkFactory
096: .createAtLeastNLayersMustBeSelectedCheck(1));
097: }
098:
099: /** @deprecated Use the EnableCheckFactory methods instead */
100: public MultiEnableCheck createOneLayerSelectedCheck() {
101: return new MultiEnableCheck()
102: .add(
103: checkFactory
104: .createWindowWithLayerNamePanelMustBeActiveCheck())
105: .add(
106: checkFactory
107: .createExactlyNLayersMustBeSelectedCheck(1));
108: }
109:
110: /** @deprecated Use the EnableCheckFactory methods instead */
111: public MultiEnableCheck createVectorsExistCheck() {
112: return new MultiEnableCheck()
113: .add(
114: checkFactory
115: .createWindowWithLayerViewPanelMustBeActiveCheck())
116: .add(
117: checkFactory
118: .createAtLeastNVectorsMustBeDrawnCheck(1));
119: }
120:
121: /** @deprecated Use the EnableCheckFactory methods instead */
122: public MultiEnableCheck createFenceExistsCheck() {
123: return new MultiEnableCheck()
124: .add(
125: checkFactory
126: .createWindowWithLayerViewPanelMustBeActiveCheck())
127: .add(checkFactory.createFenceMustBeDrawnCheck());
128: }
129:
130: public void addMenuSeparator(String menu) {
131: addMenuSeparator(new String[] { menu });
132: }
133:
134: public void addMenuSeparator(String[] menuPath) {
135: JMenu mainMenu = menuBarMenu(menuPath[0]);
136: addMenuSeparator(createMenusIfNecessary(mainMenu,
137: behead(menuPath)));
138: }
139:
140: public void addMenuSeparator(JMenu menu) {
141: Component separator = null;
142: Component exitMenu = null;
143: if (menu.getText().equals(MenuNames.FILE)) {
144: //Ensure separator and Exit appear last
145: separator = menu.getMenuComponent(menu
146: .getMenuComponentCount() - 2);
147: exitMenu = menu.getMenuComponent(menu
148: .getMenuComponentCount() - 1);
149: menu.remove(separator);
150: menu.remove(exitMenu);
151: }
152: menu.addSeparator();
153: if (menu.getText().equals(MenuNames.FILE)) {
154: menu.add(separator);
155: menu.add(exitMenu);
156: }
157: }
158:
159: private void associate(JMenuItem menuItem, PlugIn plugIn) {
160: menuItem.addActionListener(AbstractPlugIn.toActionListener(
161: plugIn, workbenchContext, taskMonitorManager));
162: }
163:
164: public String[] behead(String[] a1) {
165: String[] a2 = new String[a1.length - 1];
166: System.arraycopy(a1, 1, a2, 0, a2.length);
167: return a2;
168: }
169:
170: public void addMainMenuItem(PlugIn executable, String menuName,
171: String menuItemName, Icon icon, EnableCheck enableCheck) {
172: addMainMenuItem(executable, new String[] { menuName },
173: menuItemName, false, icon, enableCheck);
174: }
175:
176: public void addLayerViewMenuItem(PlugIn executable,
177: String menuName, String menuItemName) {
178: addLayerViewMenuItem(executable, new String[] { menuName },
179: menuItemName);
180: }
181:
182: public void addLayerNameViewMenuItem(PlugIn executable,
183: String menuName, String menuItemName) {
184: addLayerNameViewMenuItem(executable, new String[] { menuName },
185: menuItemName);
186: }
187:
188: /**
189: * Add a menu item to the main menu that is enabled only if the active
190: * internal frame is a LayerViewPanelProxy.
191: */
192: public void addLayerViewMenuItem(PlugIn executable,
193: String[] menuPath, String menuItemName) {
194: addMainMenuItem(
195: executable,
196: menuPath,
197: menuItemName,
198: false,
199: null,
200: checkFactory
201: .createWindowWithLayerViewPanelMustBeActiveCheck());
202: }
203:
204: /**
205: * Add a menu item to the main menu that is enabled only if the active
206: * internal frame is a LayerViewPanelProxy and a LayerNamePanelProxy.
207: */
208: public void addLayerNameViewMenuItem(PlugIn executable,
209: String[] menuPath, String menuItemName) {
210: addMainMenuItem(
211: executable,
212: menuPath,
213: menuItemName,
214: false,
215: null,
216: new MultiEnableCheck()
217: .add(
218: checkFactory
219: .createWindowWithLayerViewPanelMustBeActiveCheck())
220: .add(
221: checkFactory
222: .createWindowWithLayerNamePanelMustBeActiveCheck()));
223: }
224:
225: /**
226: * @param menuPath
227: * separate items with slashes; items will be created if they do
228: * not already exist
229: * @param menuActionListener
230: * listener for the menu (not the menu item); useful for setting
231: * the menu item's enabled state; can be set to null
232: * @param checkBox
233: * whether to create a JCheckBoxMenuItem or a JMenuItem
234: * @return the created JMenuItem
235: * @see GUIUtil#toSmallIcon
236: */
237: public void addMainMenuItem(PlugIn executable, String[] menuPath,
238: String menuItemName, boolean checkBox, Icon icon,
239: EnableCheck enableCheck) {
240: Map properties = extractProperties(menuItemName);
241: menuItemName = removeProperties(menuItemName);
242: JMenu menu = menuBarMenu(menuPath[0]);
243: if (menu == null) {
244: menu = (JMenu) installMnemonic(new JMenu(menuPath[0]),
245: menuBar());
246: addToMenuBar(menu);
247: }
248: JMenu parent = createMenusIfNecessary(menu, behead(menuPath));
249: final JMenuItem menuItem = installMnemonic(
250: checkBox ? new JCheckBoxMenuItem(menuItemName)
251: : new JMenuItem(menuItemName), parent);
252: menuItem.setIcon(icon);
253: associate(menuItem, executable);
254: insert(menuItem, createMenu(parent), properties);
255: if (enableCheck != null) {
256: addMenuItemShownListener(menuItem,
257: toMenuItemShownListener(enableCheck));
258: }
259: }
260:
261: private Menu createMenu(final JMenu menu) {
262: return new Menu() {
263:
264: public void insert(JMenuItem menuItem, int i) {
265: menu.insert(menuItem, i);
266: }
267:
268: public String getText() {
269: return menu.getText();
270: }
271:
272: public int getItemCount() {
273: return menu.getItemCount();
274: }
275:
276: public void add(JMenuItem menuItem) {
277: menu.add(menuItem);
278: }
279: };
280: }
281:
282: private void insert(final JMenuItem menuItem, Menu parent,
283: Map properties) {
284: if (properties.get("pos") != null) {
285: parent.insert(menuItem, Integer
286: .parseInt((String) properties.get("pos")));
287: } else if (parent.getText().equals(MenuNames.FILE)) {
288: //If menu is File, insert menu item just before Exit and its
289: // separator [Jon Aquino]
290: parent.insert(menuItem, parent.getItemCount() - 2);
291: } else {
292: parent.add(menuItem);
293: }
294: }
295:
296: private Map extractProperties(String menuItemName) {
297: if (menuItemName.indexOf('{') == -1) {
298: return new HashMap();
299: }
300: Map properties = new HashMap();
301: String s = menuItemName.substring(
302: menuItemName.indexOf('{') + 1, menuItemName
303: .indexOf('}'));
304: for (Iterator i = StringUtil.fromCommaDelimitedString(s)
305: .iterator(); i.hasNext();) {
306: String property = (String) i.next();
307: properties.put(property.substring(0, property.indexOf(':'))
308: .trim(), property.substring(
309: property.indexOf(':') + 1, property.length())
310: .trim());
311: }
312: return properties;
313: }
314:
315: public static String removeProperties(String menuItemName) {
316: return menuItemName.indexOf('{') > -1 ? menuItemName.substring(
317: 0, menuItemName.indexOf('{')) : menuItemName;
318: }
319:
320: public static JMenuItem installMnemonic(JMenuItem menuItem,
321: MenuElement parent) {
322: String text = menuItem.getText();
323: StringUtil.replaceAll(text, "&&", "##");
324: int ampersandPosition = text.indexOf('&');
325: if (-1 < ampersandPosition
326: && ampersandPosition + 1 < text.length()) {
327: menuItem.setMnemonic(text.charAt(ampersandPosition + 1));
328: text = StringUtil.replace(text, "&", "", false);
329: } else {
330: installDefaultMnemonic(menuItem, parent);
331: }
332: //Double-ampersands get converted to single-ampersands. [Jon Aquino]
333: StringUtil.replaceAll(text, "##", "&");
334: menuItem.setText(text);
335: return menuItem;
336: }
337:
338: private static void installDefaultMnemonic(JMenuItem menuItem,
339: MenuElement parent) {
340: outer: for (int i = 0; i < menuItem.getText().length(); i++) {
341: //Swing stores mnemonics in upper case [Jon Aquino]
342: char candidate = Character.toUpperCase(menuItem.getText()
343: .charAt(i));
344: if (!Character.isLetter(candidate)) {
345: continue;
346: }
347: for (Iterator j = menuItems(parent).iterator(); j.hasNext();) {
348: JMenuItem other = (JMenuItem) j.next();
349: if (other.getMnemonic() == candidate) {
350: continue outer;
351: }
352: }
353: menuItem.setMnemonic(candidate);
354: return;
355: }
356: menuItem.setMnemonic(menuItem.getText().charAt(0));
357: }
358:
359: private static Collection menuItems(MenuElement element) {
360: ArrayList menuItems = new ArrayList();
361: if (element instanceof JMenuBar) {
362: for (int i = 0; i < ((JMenuBar) element).getMenuCount(); i++) {
363: CollectionUtil.addIfNotNull(((JMenuBar) element)
364: .getMenu(i), menuItems);
365: }
366: } else if (element instanceof JMenu) {
367: for (int i = 0; i < ((JMenu) element).getItemCount(); i++) {
368: CollectionUtil.addIfNotNull(((JMenu) element)
369: .getItem(i), menuItems);
370: }
371: } else if (element instanceof JPopupMenu) {
372: MenuElement[] children = ((JPopupMenu) element)
373: .getSubElements();
374: for (int i = 0; i < children.length; i++) {
375: if (children[i] instanceof JMenuItem) {
376: menuItems.add(children[i]);
377: }
378: }
379: } else {
380: Assert.shouldNeverReachHere(element.getClass().getName());
381: }
382: return menuItems;
383: }
384:
385: private MenuItemShownListener toMenuItemShownListener(
386: final EnableCheck enableCheck) {
387: return new MenuItemShownListener() {
388: public void menuItemShown(JMenuItem menuItem) {
389: String errorMessage = null;
390: try {
391: errorMessage = enableCheck.check(menuItem);
392: } catch (Exception e) {
393: workbenchContext.getWorkbench().getFrame().log(
394: menuItem.getText());
395: workbenchContext.getWorkbench().getFrame()
396: .handleThrowable(e);
397: }
398: if (errorMessage != null) {
399: menuItem.setEnabled(false);
400: //<<TODO:AESTHETICS>> Wrap lines of tooltip text using HTML
401: // [Jon Aquino]
402: menuItem.setToolTipText(errorMessage);
403: return;
404: }
405: menuItem.setEnabled(true);
406: //<<TODO:AESTHETICS>> Wrap lines of tooltip text using HTML
407: // [Jon Aquino]
408: menuItem.setToolTipText(null);
409: }
410: };
411: }
412:
413: /**
414: * @return the leaf
415: */
416: public JMenu createMenusIfNecessary(JMenu parent, String[] menuPath) {
417: if (menuPath.length == 0) {
418: return parent;
419: }
420: JMenu child = (JMenu) childMenuItem(menuPath[0], parent);
421: if (child == null) {
422: child = (JMenu) installMnemonic(new JMenu(menuPath[0]),
423: parent);
424: parent.add(child);
425: }
426: return createMenusIfNecessary(child, behead(menuPath));
427: }
428:
429: public void addMenuItemShownListener(final JMenuItem menuItem,
430: final MenuItemShownListener menuItemShownListener) {
431: JMenu menu = (JMenu) ((JPopupMenu) menuItem.getParent())
432: .getInvoker();
433: menu.addMenuListener(new MenuListener() {
434: public void menuSelected(MenuEvent e) {
435: menuItemShownListener.menuItemShown(menuItem);
436: }
437:
438: public void menuDeselected(MenuEvent e) {
439: }
440:
441: public void menuCanceled(MenuEvent e) {
442: }
443: });
444: }
445:
446: /**
447: * @param enableCheck
448: * null to leave unspecified
449: */
450: public void addPopupMenuItem(JPopupMenu popupMenu,
451: PlugIn executable, String menuItemName, boolean checkBox,
452: Icon icon, EnableCheck enableCheck) {
453: Map properties = extractProperties(menuItemName);
454: menuItemName = removeProperties(menuItemName);
455: JMenuItem menuItem = installMnemonic(
456: checkBox ? new JCheckBoxMenuItem(menuItemName)
457: : new JMenuItem(menuItemName), popupMenu);
458: menuItem.setIcon(icon);
459: addPopupMenuItem(popupMenu, executable, menuItem, properties,
460: enableCheck);
461: }
462:
463: private void addPopupMenuItem(JPopupMenu popupMenu,
464: final PlugIn executable, final JMenuItem menuItem,
465: Map properties, final EnableCheck enableCheck) {
466: associate(menuItem, executable);
467: insert(menuItem, createMenu(popupMenu), properties);
468: if (enableCheck != null) {
469: popupMenu.addPopupMenuListener(new PopupMenuListener() {
470: public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
471: toMenuItemShownListener(enableCheck).menuItemShown(
472: menuItem);
473: }
474:
475: public void popupMenuWillBecomeInvisible(
476: PopupMenuEvent e) {
477: }
478:
479: public void popupMenuCanceled(PopupMenuEvent e) {
480: }
481: });
482: }
483: }
484:
485: private Menu createMenu(final JPopupMenu popupMenu) {
486: return new Menu() {
487:
488: public void insert(JMenuItem menuItem, int i) {
489: popupMenu.insert(menuItem, i);
490: }
491:
492: public String getText() {
493: return "";
494: }
495:
496: public int getItemCount() {
497: return popupMenu.getComponentCount();
498: }
499:
500: public void add(JMenuItem menuItem) {
501: popupMenu.add(menuItem);
502: }
503: };
504: }
505:
506: public JMenuBar menuBar() {
507: return workbenchContext.getWorkbench().getFrame().getJMenuBar();
508: }
509:
510: /**
511: * @return the menu with the given name, or null if no such menu exists
512: */
513: public JMenu menuBarMenu(String childName) {
514: MenuElement[] subElements = menuBar().getSubElements();
515: for (int i = 0; i < subElements.length; i++) {
516: if (!(subElements[i] instanceof JMenuItem)) {
517: continue;
518: }
519: JMenuItem menuItem = (JMenuItem) subElements[i];
520: if (menuItem.getText().equals(childName)) {
521: return (JMenu) menuItem;
522: }
523: }
524: return null;
525: }
526:
527: private void addToMenuBar(JMenu menu) {
528: menuBar().add(menu);
529: //Ensure Window and Help are placed at the end. Remove #windowMenu and
530: // #helpMenu
531: //*after* adding #menu, because #menu might be the Window or Help menu!
532: // [Jon Aquino]
533: JMenu windowMenu = menuBarMenu(MenuNames.WINDOW);
534: JMenu helpMenu = menuBarMenu(MenuNames.HELP);
535: //Customized workbenches may not have Window or Help menus [Jon Aquino]
536: if (windowMenu != null) {
537: menuBar().remove(windowMenu);
538: }
539: if (helpMenu != null) {
540: menuBar().remove(helpMenu);
541: }
542: if (windowMenu != null) {
543: menuBar().add(windowMenu);
544: }
545: if (helpMenu != null) {
546: menuBar().add(helpMenu);
547: }
548: }
549:
550: public static JMenuItem childMenuItem(String childName,
551: MenuElement menu) {
552: if (menu instanceof JMenu) {
553: return childMenuItem(childName, ((JMenu) menu)
554: .getPopupMenu());
555: }
556: MenuElement[] childMenuItems = menu.getSubElements();
557: for (int i = 0; i < childMenuItems.length; i++) {
558: if (childMenuItems[i] instanceof JMenuItem
559: && ((JMenuItem) childMenuItems[i]).getText()
560: .equals(childName)) {
561: return ((JMenuItem) childMenuItems[i]);
562: }
563: }
564: return null;
565: }
566:
567: /**
568: * Workaround for Java Bug 4809393: "Menus disappear prematurely after
569: * displaying modal dialog" Evidently fixed in Java 1.5. The workaround is
570: * to wrap #actionPerformed with SwingUtilities#invokeLater.
571: */
572: public void addMainMenuItemWithJava14Fix(PlugIn executable,
573: String[] menuPath, String menuItemName, boolean checkBox,
574: Icon icon, EnableCheck enableCheck) {
575: addMainMenuItem(executable, menuPath, menuItemName, checkBox,
576: icon, enableCheck);
577: JMenuItem menuItem = FeatureInstaller.childMenuItem(
578: FeatureInstaller.removeProperties(menuItemName),
579: ((JMenu) createMenusIfNecessary(
580: menuBarMenu(menuPath[0]), behead(menuPath))));
581: final ActionListener listener = abstractPlugInActionListener(menuItem
582: .getActionListeners());
583: menuItem.removeActionListener(listener);
584: menuItem.addActionListener(new ActionListener() {
585: public void actionPerformed(final ActionEvent e) {
586: SwingUtilities.invokeLater(new Runnable() {
587: public void run() {
588: listener.actionPerformed(e);
589: }
590: });
591: }
592: });
593: }
594:
595: private ActionListener abstractPlugInActionListener(
596: ActionListener[] actionListeners) {
597: for (int i = 0; i < actionListeners.length; i++) {
598: if (actionListeners[i].getClass().getName().indexOf(
599: AbstractPlugIn.class.getName()) > -1) {
600: return actionListeners[i];
601: }
602: }
603: Assert.shouldNeverReachHere();
604: return null;
605: }
606: }
|