001: /*
002:
003: ============================================================================
004: The Apache Software License, Version 1.1
005: ============================================================================
006:
007: Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
008:
009: Redistribution and use in source and binary forms, with or without modifica-
010: tion, are permitted provided that the following conditions are met:
011:
012: 1. Redistributions of source code must retain the above copyright notice,
013: this list of conditions and the following disclaimer.
014:
015: 2. Redistributions in binary form must reproduce the above copyright notice,
016: this list of conditions and the following disclaimer in the documentation
017: and/or other materials provided with the distribution.
018:
019: 3. The end-user documentation included with the redistribution, if any, must
020: include the following acknowledgment: "This product includes software
021: developed by the Apache Software Foundation (http://www.apache.org/)."
022: Alternately, this acknowledgment may appear in the software itself, if
023: and wherever such third-party acknowledgments normally appear.
024:
025: 4. The names "Batik" and "Apache Software Foundation" must not be
026: used to endorse or promote products derived from this software without
027: prior written permission. For written permission, please contact
028: apache@apache.org.
029:
030: 5. Products derived from this software may not be called "Apache", nor may
031: "Apache" appear in their name, without prior written permission of the
032: Apache Software Foundation.
033:
034: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
035: INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
036: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
037: APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
038: INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
039: DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
040: OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
041: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
042: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
043: THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
044:
045: This software consists of voluntary contributions made by many individuals
046: on behalf of the Apache Software Foundation. For more information on the
047: Apache Software Foundation, please see <http://www.apache.org/>.
048:
049: */
050:
051: package org.apache.batik.util.gui.resource;
052:
053: import java.awt.Event;
054: import java.awt.Point;
055: import java.awt.event.KeyEvent;
056: import java.awt.event.MouseEvent;
057: import java.net.URL;
058: import java.util.Iterator;
059: import java.util.List;
060: import java.util.MissingResourceException;
061: import java.util.ResourceBundle;
062:
063: import javax.swing.AbstractButton;
064: import javax.swing.Action;
065: import javax.swing.Box;
066: import javax.swing.ButtonGroup;
067: import javax.swing.ImageIcon;
068: import javax.swing.JCheckBoxMenuItem;
069: import javax.swing.JComponent;
070: import javax.swing.JMenu;
071: import javax.swing.JMenuBar;
072: import javax.swing.JMenuItem;
073: import javax.swing.JPopupMenu;
074: import javax.swing.JRadioButtonMenuItem;
075: import javax.swing.JSeparator;
076: import javax.swing.JToolTip;
077: import javax.swing.KeyStroke;
078:
079: import com.projity.menu.ExtButtonFactory;
080: import com.projity.menu.HyperLinkToolTip;
081: import com.projity.util.ClassLoaderUtils;
082:
083: /**
084: * This class represents a menu factory which builds
085: * menubars and menus from the content of a resource file.<br>
086: *
087: * The resource entries format is (for a menubar named 'MenuBar'):<br>
088: * <pre>
089: * MenuBar = Menu1 Menu2 ...
090: *
091: * Menu1.type = RADIO | CHECK | MENU | ITEM
092: * Menu1 = Item1 Item2 - Item3 ...
093: * Menu1.text = text
094: * Menu1.icon = icon_name
095: * Menu1.mnemonic = mnemonic
096: * Menu1.accelerator = accelerator
097: * Menu1.action = action_name
098: * Menu1.selected = true | false
099: * Menu1.enabled = true | false
100: * ...
101: * mnemonic is a single character
102: * accelerator is of the form: mod+mod+...+X
103: * where mod is Shift, Meta, Alt or Ctrl
104: * '-' represents a separator
105: * </pre>
106: * All entries are optional except the '.type' entry
107: * Consecutive RADIO items are put in a ButtonGroup
108: *
109: * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
110: * @version $Id: MenuFactory.java,v 1.1 2007/08/15 23:28:28 suricate Exp $
111: */
112: public class MenuFactory extends ResourceManager {
113: // Constants
114: //
115: private final static String TYPE_MENU = "MENU";
116: private final static String TYPE_ITEM = "ITEM";
117: private final static String TYPE_RADIO = "RADIO";
118: private final static String TYPE_CHECK = "CHECK";
119: private final static String SEPARATOR = "-";
120: private final static String GLUE = ">";
121:
122: private final static String TYPE_SUFFIX = ".type";
123: protected final static String TEXT_SUFFIX = ".text";
124: private final static String MNEMONIC_SUFFIX = ".mnemonic";
125: private final static String ACCELERATOR_SUFFIX = ".accelerator";
126: private final static String ACTION_SUFFIX = ".action";
127: private final static String SELECTED_SUFFIX = ".selected";
128: private final static String ENABLED_SUFFIX = ".enabled";
129: private final static String ICON_SUFFIX = ".icon";
130:
131: /**
132: * The table which contains the actions
133: */
134: private ActionMap actions;
135: protected ClassLoader classLoader; //LC MODIF
136:
137: /**
138: * The current radio group
139: */
140: private ButtonGroup buttonGroup;
141:
142: /**
143: * Creates a new menu factory
144: * @param rb the resource bundle that contains the menu bar
145: * description.
146: * @param am the actions to add to menu items
147: */
148: public MenuFactory(ResourceBundle rb, ActionMap am) {
149: super (rb);
150: actions = am;
151: buttonGroup = null;
152: classLoader = ClassLoaderUtils.getLocalClassLoader(); //LC MODIF
153: }
154:
155: /**
156: * Creates and returns a swing menu bar
157: * @param name the name of the menu bar in the resource bundle
158: * @throws MissingResourceException if one of the keys that compose the
159: * menu is missing.
160: * It is not thrown if the mnemonic, the accelerator and the
161: * action keys are missing
162: * @throws ResourceFormatException if the mnemonic is not a single
163: * character and if the accelerator is malformed
164: * @throws MissingListenerException if an item action is not found in the
165: * action map
166: */
167: public JMenuBar createJMenuBar(String name)
168: throws MissingResourceException, ResourceFormatException,
169: MissingListenerException {
170: JMenuBar result = new JMenuBar();
171: List menus = getStringList(name);
172: Iterator it = menus.iterator();
173: while (it.hasNext()) {
174: String x = (String) it.next();
175: result.add(createJMenuComponent(x));
176: }
177: return result;
178: }
179:
180: public JPopupMenu createJPopupMenuBar(String name)
181: throws MissingResourceException, ResourceFormatException,
182: MissingListenerException {
183: JPopupMenu result = new JPopupMenu();
184: List menus = getStringList(name);
185: Iterator it = menus.iterator();
186:
187: while (it.hasNext()) {
188: String x = (String) it.next();
189: result.add(createJMenuComponent(x));
190: }
191: return result;
192: }
193:
194: /**
195: * Creates and returns a menu item or a separator
196: * @param name the name of the menu item or "-" to create a separator
197: * @throws MissingResourceException if key is not the name of a menu item.
198: * It is not thrown if the mnemonic, the accelerator and the
199: * action keys are missing
200: * @throws ResourceFormatException in case of malformed entry
201: * @throws MissingListenerException if an item action is not found in the
202: * action map
203: */
204: protected JComponent createJMenuComponent(String name)
205: throws MissingResourceException, ResourceFormatException,
206: MissingListenerException {
207: if (name.equals(GLUE)) {
208: buttonGroup = null;
209: return (JComponent) Box.createHorizontalGlue();
210: }
211: if (name.equals(SEPARATOR)) {
212: buttonGroup = null;
213: return new JSeparator();
214: }
215: String type = getString(name + TYPE_SUFFIX);
216: JComponent item = null;
217:
218: if (type.equals(TYPE_RADIO)) {
219: if (buttonGroup == null) {
220: buttonGroup = new ButtonGroup();
221: }
222: } else {
223: buttonGroup = null;
224: }
225:
226: if (type.equals(TYPE_MENU)) {
227: item = createJMenu(name);
228: } else if (type.equals(TYPE_ITEM)) {
229: item = createJMenuItem(name);
230: } else if (type.equals(TYPE_RADIO)) {
231: item = createJRadioButtonMenuItem(name);
232: buttonGroup.add((AbstractButton) item);
233: } else if (type.equals(TYPE_CHECK)) {
234: item = createJCheckBoxMenuItem(name);
235: } else {
236: throw new ResourceFormatException("Malformed resource",
237: bundle.getClass().getName(), name + TYPE_SUFFIX);
238: }
239:
240: return item;
241: }
242:
243: /**
244: * Creates and returns a new swing menu
245: * @param name the name of the menu bar in the resource bundle
246: * @throws MissingResourceException if one of the keys that compose the
247: * menu is missing.
248: * It is not thrown if the mnemonic, the accelerator and the
249: * action keys are missing
250: * @throws ResourceFormatException if the mnemonic is not a single
251: * character.
252: * @throws MissingListenerException if a item action is not found in the
253: * action map.
254: */
255: public JMenu createJMenu(String name)
256: throws MissingResourceException, ResourceFormatException,
257: MissingListenerException {
258: JMenu result = new JMenu(getString(name + TEXT_SUFFIX)) {
259: public Point getToolTipLocation(MouseEvent event) { // the tip MUST be touching the button if html because you can click on links
260: if (getToolTipText().startsWith("<html>"))
261: return new Point(0, getHeight() - 2);
262: else
263: return super .getToolTipLocation(event);
264: }
265:
266: public JToolTip createToolTip() {
267: if (getToolTipText().startsWith("<html>")) {
268: JToolTip tip = new HyperLinkToolTip();
269: tip.setComponent(this );
270: return tip;
271: } else {
272: return super .createToolTip();
273: }
274: }
275: };
276: initializeJMenuItem(result, name);
277:
278: List items = getStringList(name);
279: Iterator it = items.iterator();
280:
281: while (it.hasNext()) {
282: String itemName = (String) it.next();
283:
284: JComponent item = createJMenuComponent(itemName);
285: // I added the possibility of having an invisible menu item. This is needed to handle radio button groups that have a "none" option. Aside from
286: // its initial state, a button group must always have a selected item. By creating an invisible item, I can essentially select "none" by selecting
287: // the invisible one
288: boolean visible = true;
289: try {
290: visible = getBoolean(itemName
291: + ExtButtonFactory.VISIBLE_SUFFIX);
292: } catch (MissingResourceException e) {
293: }
294: if (visible)
295: result.add(item);
296: }
297: return result;
298: }
299:
300: /**
301: * Creates and returns a new swing menu item
302: * @param name the name of the menu item
303: * @throws MissingResourceException if one of the keys that compose the
304: * menu item is missing.
305: * It is not thrown if the mnemonic, the accelerator and the
306: * action keys are missing
307: * @throws ResourceFormatException if the mnemonic is not a single
308: * character.
309: * @throws MissingListenerException if then item action is not found in
310: * the action map.
311: */
312: public JMenuItem createJMenuItem(String name)
313: throws MissingResourceException, ResourceFormatException,
314: MissingListenerException {
315: JMenuItem result = new JMenuItem(getString(name + TEXT_SUFFIX));
316: initializeJMenuItem(result, name);
317: return result;
318: }
319:
320: /**
321: * Creates and returns a new swing radio button menu item
322: * @param name the name of the menu item
323: * @throws MissingResourceException if one of the keys that compose the
324: * menu item is missing.
325: * It is not thrown if the mnemonic, the accelerator and the
326: * action keys are missing
327: * @throws ResourceFormatException if the mnemonic is not a single
328: * character.
329: * @throws MissingListenerException if then item action is not found in
330: * the action map.
331: */
332: public JRadioButtonMenuItem createJRadioButtonMenuItem(String name)
333: throws MissingResourceException, ResourceFormatException,
334: MissingListenerException {
335: JRadioButtonMenuItem result;
336: result = new JRadioButtonMenuItem(getString(name + TEXT_SUFFIX));
337: initializeJMenuItem(result, name);
338:
339: // is the item selected?
340: try {
341: result.setSelected(getBoolean(name + SELECTED_SUFFIX));
342: } catch (MissingResourceException e) {
343: }
344:
345: return result;
346: }
347:
348: /**
349: * Creates and returns a new swing check box menu item
350: * @param name the name of the menu item
351: * @throws MissingResourceException if one of the keys that compose the
352: * menu item is missing.
353: * It is not thrown if the mnemonic, the accelerator and the
354: * action keys are missing
355: * @throws ResourceFormatException if the mnemonic is not a single
356: * character.
357: * @throws MissingListenerException if then item action is not found in
358: * the action map.
359: */
360: public JCheckBoxMenuItem createJCheckBoxMenuItem(String name)
361: throws MissingResourceException, ResourceFormatException,
362: MissingListenerException {
363: JCheckBoxMenuItem result;
364: result = new JCheckBoxMenuItem(getString(name + TEXT_SUFFIX));
365: initializeJMenuItem(result, name);
366:
367: // is the item selected?
368: try {
369: result.setSelected(getBoolean(name + SELECTED_SUFFIX));
370: } catch (MissingResourceException e) {
371: }
372:
373: return result;
374: }
375:
376: /**
377: * Initializes a swing menu item
378: * @param item the menu item to initialize
379: * @param name the name of the menu item
380: * @throws ResourceFormatException if the mnemonic is not a single
381: * character.
382: * @throws MissingListenerException if then item action is not found in
383: * the action map.
384: */
385: protected void initializeJMenuItem(JMenuItem item, String name)
386: throws ResourceFormatException, MissingListenerException {
387: // Action
388: try {
389: Action a = actions
390: .getAction(getString(name + ACTION_SUFFIX));
391: if (a == null) {
392: throw new MissingListenerException("", "Action", name
393: + ACTION_SUFFIX);
394: }
395: item.setAction(a);
396: item.setText(getString(name + TEXT_SUFFIX));
397: if (a instanceof JComponentModifier) {
398: ((JComponentModifier) a).addJComponent(item);
399: }
400: } catch (MissingResourceException e) {
401: }
402:
403: // Icon
404: try {
405: String s = getString(name + ICON_SUFFIX);
406: URL url = classLoader.getResource(s);
407: // URL url = actions.getClass().getResource(s);
408: if (url != null) {
409: item.setIcon(new ImageIcon(url));
410: }
411: } catch (MissingResourceException e) {
412: }
413:
414: // Mnemonic
415: try {
416: String str = getString(name + MNEMONIC_SUFFIX);
417: if (str.length() == 1) {
418: item.setMnemonic(str.charAt(0));
419: } else {
420: throw new ResourceFormatException("Malformed mnemonic",
421: bundle.getClass().getName(), name
422: + MNEMONIC_SUFFIX);
423: }
424: } catch (MissingResourceException e) {
425: }
426:
427: // Accelerator
428: try {
429: if (!(item instanceof JMenu)) {
430: String str = getString(name + ACCELERATOR_SUFFIX);
431: KeyStroke ks = toKeyStroke(str);
432: if (ks != null) {
433: item.setAccelerator(ks);
434: } else {
435: throw new ResourceFormatException(
436: "Malformed accelerator", bundle.getClass()
437: .getName(), name
438: + ACCELERATOR_SUFFIX);
439: }
440: }
441: } catch (MissingResourceException e) {
442: }
443:
444: // is the item enabled?
445: try {
446: item.setEnabled(getBoolean(name + ENABLED_SUFFIX));
447: } catch (MissingResourceException e) {
448: }
449: }
450:
451: /**
452: * Translate a string into a key stroke.
453: * See the class comment for details
454: * @param str a string
455: */
456: protected KeyStroke toKeyStroke(String str) {
457: int state = 0;
458: int code = 0;
459: int modif = 0;
460: int i = 0;
461:
462: while (state != 100 && i < str.length()) {
463: char curr = Character.toUpperCase(str.charAt(i));
464:
465: switch (state) {
466: case 0:
467: code = curr;
468: switch (curr) {
469: case 'C':
470: state = 1;
471: break;
472: case 'A':
473: state = 5;
474: break;
475: case 'M':
476: state = 8;
477: break;
478: case 'S':
479: state = 12;
480: break;
481: default:
482: state = 100;
483: }
484: break;
485:
486: case 1:
487: state = (curr == 'T') ? 2 : 100;
488: break;
489: case 2:
490: state = (curr == 'R') ? 3 : 100;
491: break;
492: case 3:
493: state = (curr == 'L') ? 4 : 100;
494: break;
495: case 4:
496: state = (curr == '+') ? 0 : 100;
497: if (state == 0) {
498: modif |= Event.CTRL_MASK;
499: }
500: break;
501: case 5:
502: state = (curr == 'L') ? 6 : 100;
503: break;
504: case 6:
505: state = (curr == 'T') ? 7 : 100;
506: break;
507: case 7:
508: state = (curr == '+') ? 0 : 100;
509: if (state == 0) {
510: modif |= Event.ALT_MASK;
511: }
512: break;
513: case 8:
514: state = (curr == 'E') ? 9 : 100;
515: break;
516: case 9:
517: state = (curr == 'T') ? 10 : 100;
518: break;
519: case 10:
520: state = (curr == 'A') ? 11 : 100;
521: break;
522: case 11:
523: state = (curr == '+') ? 0 : 100;
524: if (state == 0) {
525: modif |= Event.META_MASK;
526: }
527: break;
528: case 12:
529: state = (curr == 'H') ? 13 : 100;
530: break;
531: case 13:
532: state = (curr == 'I') ? 14 : 100;
533: break;
534: case 14:
535: state = (curr == 'F') ? 15 : 100;
536: break;
537: case 15:
538: state = (curr == 'T') ? 16 : 100;
539: break;
540: case 16:
541: state = (curr == '+') ? 0 : 100;
542: if (state == 0) {
543: modif |= Event.SHIFT_MASK;
544: }
545: break;
546: }
547: i++;
548: }
549: if (code > 0 && modif > 0) {
550:
551: String rest = str.substring(i - 1);
552: if (rest.equals("F1"))
553: code = KeyEvent.VK_F1;
554: else if (rest.equals("F2"))
555: code = KeyEvent.VK_F2;
556: else if (rest.equals("F3"))
557: code = KeyEvent.VK_F3;
558: else if (rest.equals("F4"))
559: code = KeyEvent.VK_F4;
560: else if (rest.equals("F5"))
561: code = KeyEvent.VK_F5;
562: else if (rest.equals("F6"))
563: code = KeyEvent.VK_F6;
564: else if (rest.equals("F7"))
565: code = KeyEvent.VK_F7;
566: else if (rest.equals("F8"))
567: code = KeyEvent.VK_F8;
568: else if (rest.equals("F9"))
569: code = KeyEvent.VK_F9;
570: else if (rest.equals("F10"))
571: code = KeyEvent.VK_F10;
572: else if (rest.equals("F11"))
573: code = KeyEvent.VK_F11;
574: else if (rest.equals("F12"))
575: code = KeyEvent.VK_F12;
576: else {
577: if (i < str.length()) {
578: char curr = Character.toUpperCase(str.charAt(i));
579: switch (code) {
580: case 'U':
581: if (str.length() - i != 1 || curr != 'P') {
582: break;
583: }
584: code = KeyEvent.VK_UP;
585: break;
586: case 'L':
587: if (str.length() - i != 3
588: || curr != 'E'
589: || Character.toUpperCase(str
590: .charAt(i + 1)) != 'F'
591: || Character.toUpperCase(str
592: .charAt(i + 2)) != 'T') {
593: break;
594: }
595: code = KeyEvent.VK_LEFT;
596: break;
597: case 'D':
598: if (str.length() - i != 3
599: || curr != 'O'
600: || Character.toUpperCase(str
601: .charAt(i + 1)) != 'W'
602: || Character.toUpperCase(str
603: .charAt(i + 2)) != 'N') {
604: break;
605: }
606: code = KeyEvent.VK_DOWN;
607: break;
608: case 'R':
609: if (str.length() - i != 4
610: || curr != 'I'
611: || Character.toUpperCase(str
612: .charAt(i + 1)) != 'G'
613: || Character.toUpperCase(str
614: .charAt(i + 2)) != 'H'
615: || Character.toUpperCase(str
616: .charAt(i + 3)) != 'T') {
617: break;
618: }
619: code = KeyEvent.VK_RIGHT;
620: }
621: }
622: }
623: return KeyStroke.getKeyStroke(code, modif);
624: }
625: return null;
626: }
627: }
|