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