Source code: org/apache/batik/util/gui/resource/MenuFactory.java
1 /*
2
3 Copyright 2000-2001,2003 The Apache Software Foundation
4
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16
17 */
18 package org.apache.batik.util.gui.resource;
19
20 import java.awt.Event;
21 import java.awt.event.KeyEvent;
22 import java.net.URL;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.MissingResourceException;
26 import java.util.ResourceBundle;
27
28 import javax.swing.AbstractButton;
29 import javax.swing.Action;
30 import javax.swing.ButtonGroup;
31 import javax.swing.ImageIcon;
32 import javax.swing.JCheckBoxMenuItem;
33 import javax.swing.JComponent;
34 import javax.swing.JMenu;
35 import javax.swing.JMenuBar;
36 import javax.swing.JMenuItem;
37 import javax.swing.JRadioButtonMenuItem;
38 import javax.swing.JSeparator;
39 import javax.swing.KeyStroke;
40
41 /**
42 * This class represents a menu factory which builds
43 * menubars and menus from the content of a resource file. <br>
44 *
45 * The resource entries format is (for a menubar named 'MenuBar'):<br>
46 * <pre>
47 * MenuBar = Menu1 Menu2 ...
48 *
49 * Menu1.type = RADIO | CHECK | MENU | ITEM
50 * Menu1 = Item1 Item2 - Item3 ...
51 * Menu1.text = text
52 * Menu1.icon = icon_name
53 * Menu1.mnemonic = mnemonic
54 * Menu1.accelerator = accelerator
55 * Menu1.action = action_name
56 * Menu1.selected = true | false
57 * Menu1.enabled = true | false
58 * ...
59 * mnemonic is a single character
60 * accelerator is of the form: mod+mod+...+X
61 * where mod is Shift, Meta, Alt or Ctrl
62 * '-' represents a separator
63 * </pre>
64 * All entries are optional except the '.type' entry
65 * Consecutive RADIO items are put in a ButtonGroup
66 *
67 * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
68 * @version $Id: MenuFactory.java,v 1.8 2005/03/27 08:58:37 cam Exp $
69 */
70 public class MenuFactory extends ResourceManager {
71 // Constants
72 //
73 private final static String TYPE_MENU = "MENU";
74 private final static String TYPE_ITEM = "ITEM";
75 private final static String TYPE_RADIO = "RADIO";
76 private final static String TYPE_CHECK = "CHECK";
77 private final static String SEPARATOR = "-";
78
79 private final static String TYPE_SUFFIX = ".type";
80 private final static String TEXT_SUFFIX = ".text";
81 private final static String MNEMONIC_SUFFIX = ".mnemonic";
82 private final static String ACCELERATOR_SUFFIX = ".accelerator";
83 private final static String ACTION_SUFFIX = ".action";
84 private final static String SELECTED_SUFFIX = ".selected";
85 private final static String ENABLED_SUFFIX = ".enabled";
86 private final static String ICON_SUFFIX = ".icon";
87
88 /**
89 * The table which contains the actions
90 */
91 private ActionMap actions;
92
93 /**
94 * The current radio group
95 */
96 private ButtonGroup buttonGroup;
97
98 /**
99 * Creates a new menu factory
100 * @param rb the resource bundle that contains the menu bar
101 * description.
102 * @param am the actions to add to menu items
103 */
104 public MenuFactory(ResourceBundle rb, ActionMap am) {
105 super(rb);
106 actions = am;
107 buttonGroup = null;
108 }
109
110 /**
111 * Creates and returns a swing menu bar
112 * @param name the name of the menu bar in the resource bundle
113 * @throws MissingResourceException if one of the keys that compose the
114 * menu is missing.
115 * It is not thrown if the mnemonic, the accelerator and the
116 * action keys are missing
117 * @throws ResourceFormatException if the mnemonic is not a single
118 * character and if the accelerator is malformed
119 * @throws MissingListenerException if an item action is not found in the
120 * action map
121 */
122 public JMenuBar createJMenuBar(String name)
123 throws MissingResourceException,
124 ResourceFormatException,
125 MissingListenerException {
126 JMenuBar result = new JMenuBar();
127 List menus = getStringList(name);
128 Iterator it = menus.iterator();
129
130 while (it.hasNext()) {
131 result.add(createJMenuComponent((String)it.next()));
132 }
133 return result;
134 }
135
136 /**
137 * Creates and returns a menu item or a separator
138 * @param name the name of the menu item or "-" to create a separator
139 * @throws MissingResourceException if key is not the name of a menu item.
140 * It is not thrown if the mnemonic, the accelerator and the
141 * action keys are missing
142 * @throws ResourceFormatException in case of malformed entry
143 * @throws MissingListenerException if an item action is not found in the
144 * action map
145 */
146 protected JComponent createJMenuComponent(String name)
147 throws MissingResourceException,
148 ResourceFormatException,
149 MissingListenerException {
150 if (name.equals(SEPARATOR)) {
151 buttonGroup = null;
152 return new JSeparator();
153 }
154 String type = getString(name+TYPE_SUFFIX);
155 JComponent item = null;
156
157 if (type.equals(TYPE_RADIO)) {
158 if (buttonGroup == null) {
159 buttonGroup = new ButtonGroup();
160 }
161 } else {
162 buttonGroup = null;
163 }
164
165 if (type.equals(TYPE_MENU)) {
166 item = createJMenu(name);
167 } else if (type.equals(TYPE_ITEM)) {
168 item = createJMenuItem(name);
169 } else if (type.equals(TYPE_RADIO)) {
170 item = createJRadioButtonMenuItem(name);
171 buttonGroup.add((AbstractButton)item);
172 } else if (type.equals(TYPE_CHECK)) {
173 item = createJCheckBoxMenuItem(name);
174 } else {
175 throw new ResourceFormatException("Malformed resource",
176 bundle.getClass().getName(),
177 name+TYPE_SUFFIX);
178 }
179
180 return item;
181 }
182
183 /**
184 * Creates and returns a new swing menu
185 * @param name the name of the menu bar in the resource bundle
186 * @throws MissingResourceException if one of the keys that compose the
187 * menu is missing.
188 * It is not thrown if the mnemonic, the accelerator and the
189 * action keys are missing
190 * @throws ResourceFormatException if the mnemonic is not a single
191 * character.
192 * @throws MissingListenerException if a item action is not found in the
193 * action map.
194 */
195 public JMenu createJMenu(String name)
196 throws MissingResourceException,
197 ResourceFormatException,
198 MissingListenerException {
199 JMenu result = new JMenu(getString(name+TEXT_SUFFIX));
200 initializeJMenuItem(result, name);
201
202 List items = getStringList(name);
203 Iterator it = items.iterator();
204
205 while (it.hasNext()) {
206 result.add(createJMenuComponent((String)it.next()));
207 }
208 return result;
209 }
210
211 /**
212 * Creates and returns a new swing menu item
213 * @param name the name of the menu item
214 * @throws MissingResourceException if one of the keys that compose the
215 * menu item is missing.
216 * It is not thrown if the mnemonic, the accelerator and the
217 * action keys are missing
218 * @throws ResourceFormatException if the mnemonic is not a single
219 * character.
220 * @throws MissingListenerException if then item action is not found in
221 * the action map.
222 */
223 public JMenuItem createJMenuItem(String name)
224 throws MissingResourceException,
225 ResourceFormatException,
226 MissingListenerException {
227 JMenuItem result = new JMenuItem(getString(name+TEXT_SUFFIX));
228 initializeJMenuItem(result, name);
229 return result;
230 }
231
232 /**
233 * Creates and returns a new swing radio button menu item
234 * @param name the name of the menu item
235 * @throws MissingResourceException if one of the keys that compose the
236 * menu item is missing.
237 * It is not thrown if the mnemonic, the accelerator and the
238 * action keys are missing
239 * @throws ResourceFormatException if the mnemonic is not a single
240 * character.
241 * @throws MissingListenerException if then item action is not found in
242 * the action map.
243 */
244 public JRadioButtonMenuItem createJRadioButtonMenuItem(String name)
245 throws MissingResourceException,
246 ResourceFormatException,
247 MissingListenerException {
248 JRadioButtonMenuItem result;
249 result = new JRadioButtonMenuItem(getString(name+TEXT_SUFFIX));
250 initializeJMenuItem(result, name);
251
252 // is the item selected?
253 try {
254 result.setSelected(getBoolean(name+SELECTED_SUFFIX));
255 } catch (MissingResourceException e) {
256 }
257
258 return result;
259 }
260
261 /**
262 * Creates and returns a new swing check box menu item
263 * @param name the name of the menu item
264 * @throws MissingResourceException if one of the keys that compose the
265 * menu item is missing.
266 * It is not thrown if the mnemonic, the accelerator and the
267 * action keys are missing
268 * @throws ResourceFormatException if the mnemonic is not a single
269 * character.
270 * @throws MissingListenerException if then item action is not found in
271 * the action map.
272 */
273 public JCheckBoxMenuItem createJCheckBoxMenuItem(String name)
274 throws MissingResourceException,
275 ResourceFormatException,
276 MissingListenerException {
277 JCheckBoxMenuItem result;
278 result = new JCheckBoxMenuItem(getString(name+TEXT_SUFFIX));
279 initializeJMenuItem(result, name);
280
281 // is the item selected?
282 try {
283 result.setSelected(getBoolean(name+SELECTED_SUFFIX));
284 } catch (MissingResourceException e) {
285 }
286
287 return result;
288 }
289
290 /**
291 * Initializes a swing menu item
292 * @param item the menu item to initialize
293 * @param name the name of the menu item
294 * @throws ResourceFormatException if the mnemonic is not a single
295 * character.
296 * @throws MissingListenerException if then item action is not found in
297 * the action map.
298 */
299 protected void initializeJMenuItem(JMenuItem item, String name)
300 throws ResourceFormatException,
301 MissingListenerException {
302 // Action
303 try {
304 Action a = actions.getAction(getString(name+ACTION_SUFFIX));
305 if (a == null) {
306 throw new MissingListenerException("", "Action",
307 name+ACTION_SUFFIX);
308 }
309 item.setAction(a);
310 item.setText(getString(name+TEXT_SUFFIX));
311 if (a instanceof JComponentModifier) {
312 ((JComponentModifier)a).addJComponent(item);
313 }
314 } catch (MissingResourceException e) {
315 }
316
317 // Icon
318 try {
319 String s = getString(name+ICON_SUFFIX);
320 URL url = actions.getClass().getResource(s);
321 if (url != null) {
322 item.setIcon(new ImageIcon(url));
323 }
324 } catch (MissingResourceException e) {
325 }
326
327 // Mnemonic
328 try {
329 String str = getString(name+MNEMONIC_SUFFIX);
330 if (str.length() == 1) {
331 item.setMnemonic(str.charAt(0));
332 } else {
333 throw new ResourceFormatException("Malformed mnemonic",
334 bundle.getClass().getName(),
335 name+MNEMONIC_SUFFIX);
336 }
337 } catch (MissingResourceException e) {
338 }
339
340 // Accelerator
341 try {
342 if (!(item instanceof JMenu)) {
343 String str = getString(name+ACCELERATOR_SUFFIX);
344 KeyStroke ks = toKeyStroke(str);
345 if (ks != null) {
346 item.setAccelerator(ks);
347 } else {
348 throw new ResourceFormatException
349 ("Malformed accelerator",
350 bundle.getClass().getName(),
351 name+ACCELERATOR_SUFFIX);
352 }
353 }
354 } catch (MissingResourceException e) {
355 }
356
357 // is the item enabled?
358 try {
359 item.setEnabled(getBoolean(name+ENABLED_SUFFIX));
360 } catch (MissingResourceException e) {
361 }
362 }
363
364 /**
365 * Translate a string into a key stroke.
366 * See the class comment for details
367 * @param str a string
368 */
369 protected KeyStroke toKeyStroke(String str) {
370 int state = 0;
371 int code = 0;
372 int modif = 0;
373 int i = 0;
374
375 while (state != 100 && i < str.length()) {
376 char curr = Character.toUpperCase(str.charAt(i));
377
378 switch (state) {
379 case 0 :
380 code = curr;
381 switch (curr) {
382 case 'C': state = 1; break;
383 case 'A': state = 5; break;
384 case 'M': state = 8; break;
385 case 'S': state = 12; break;
386 default:
387 state = 100;
388 }
389 break;
390
391 case 1 : state = (curr == 'T') ? 2 : 100; break;
392 case 2 : state = (curr == 'R') ? 3 : 100; break;
393 case 3 : state = (curr == 'L') ? 4 : 100; break;
394 case 4 : state = (curr == '+') ? 0 : 100;
395 if (state == 0) {
396 modif |= Event.CTRL_MASK;
397 }
398 break;
399 case 5 : state = (curr == 'L') ? 6 : 100; break;
400 case 6 : state = (curr == 'T') ? 7 : 100; break;
401 case 7 : state = (curr == '+') ? 0 : 100;
402 if (state == 0) {
403 modif |= Event.ALT_MASK;
404 }
405 break;
406 case 8 : state = (curr == 'E') ? 9 : 100; break;
407 case 9 : state = (curr == 'T') ? 10: 100; break;
408 case 10: state = (curr == 'A') ? 11: 100; break;
409 case 11: state = (curr == '+') ? 0 : 100;
410 if (state == 0) {
411 modif |= Event.META_MASK;
412 }
413 break;
414 case 12: state = (curr == 'H') ? 13: 100; break;
415 case 13: state = (curr == 'I') ? 14: 100; break;
416 case 14: state = (curr == 'F') ? 15: 100; break;
417 case 15: state = (curr == 'T') ? 16: 100; break;
418 case 16: state = (curr == '+') ? 0 : 100;
419 if (state == 0) {
420 modif |= Event.SHIFT_MASK;
421 }
422 break;
423 }
424 i++;
425 }
426 if (code > 0 && modif > 0) {
427 if (i < str.length()) {
428 char curr = Character.toUpperCase(str.charAt(i));
429 switch (code) {
430 case 'U':
431 if (str.length() - i != 1 || curr != 'P') {
432 break;
433 }
434 code = KeyEvent.VK_UP;
435 break;
436 case 'L':
437 if (str.length() - i != 3 ||
438 curr != 'E' ||
439 Character.toUpperCase(str.charAt(i + 1)) != 'F' ||
440 Character.toUpperCase(str.charAt(i + 2)) != 'T') {
441 break;
442 }
443 code = KeyEvent.VK_LEFT;
444 break;
445 case 'D':
446 if (str.length() - i != 3 ||
447 curr != 'O' ||
448 Character.toUpperCase(str.charAt(i + 1)) != 'W' ||
449 Character.toUpperCase(str.charAt(i + 2)) != 'N') {
450 break;
451 }
452 code = KeyEvent.VK_DOWN;
453 break;
454 case 'R':
455 if (str.length() - i != 4 ||
456 curr != 'I' ||
457 Character.toUpperCase(str.charAt(i + 1)) != 'G' ||
458 Character.toUpperCase(str.charAt(i + 2)) != 'H' ||
459 Character.toUpperCase(str.charAt(i + 3)) != 'T') {
460 break;
461 }
462 code = KeyEvent.VK_RIGHT;
463 }
464 }
465 return KeyStroke.getKeyStroke(code, modif);
466 }
467 return null;
468 }
469 }