View Javadoc

1   /*
2    * Copyright 2006-2016 The JGUIraffe Team.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.jguiraffe.gui.platform.swing.builder.action;
17  
18  import javax.swing.AbstractButton;
19  import javax.swing.Action;
20  import javax.swing.Icon;
21  import javax.swing.JButton;
22  import javax.swing.JCheckBoxMenuItem;
23  import javax.swing.JMenu;
24  import javax.swing.JMenuBar;
25  import javax.swing.JMenuItem;
26  import javax.swing.JPopupMenu;
27  import javax.swing.JToggleButton;
28  import javax.swing.JToolBar;
29  import javax.swing.KeyStroke;
30  import java.awt.Component;
31  import java.util.Set;
32  
33  import net.sf.jguiraffe.gui.builder.action.Accelerator;
34  import net.sf.jguiraffe.gui.builder.action.ActionBuilder;
35  import net.sf.jguiraffe.gui.builder.action.ActionData;
36  import net.sf.jguiraffe.gui.builder.action.ActionManager;
37  import net.sf.jguiraffe.gui.builder.action.FormAction;
38  import net.sf.jguiraffe.gui.builder.action.FormActionException;
39  import net.sf.jguiraffe.gui.builder.action.PopupMenuHandler;
40  import net.sf.jguiraffe.gui.builder.components.ComponentBuilderData;
41  import net.sf.jguiraffe.gui.builder.components.tags.TextIconData;
42  import net.sf.jguiraffe.gui.builder.event.Modifiers;
43  import net.sf.jguiraffe.gui.forms.ComponentHandler;
44  import net.sf.jguiraffe.gui.platform.swing.builder.components.SwingButtonHandler;
45  import net.sf.jguiraffe.gui.platform.swing.builder.event.SwingEventConstantMapper;
46  
47  /**
48   * <p>
49   * The Swing specific implementation of the {@code ActionManager}
50   * interface.
51   * </p>
52   * <p>
53   * This class implements the {@code ActionManager} methods in a way that
54   * correctly initialized Swing objects (like Actions and JMenus) are created.
55   * </p>
56   *
57   * @author Oliver Heger
58   * @version $Id: SwingActionManager.java 205 2012-01-29 18:29:57Z oheger $
59   */
60  public class SwingActionManager implements ActionManager
61  {
62      /**
63       * Creates an action object. The returned object will also implement Swing's
64       * {@code Action} interface.
65       *
66       * @param actionBuilder the action builder
67       * @param actionData the properties of the new action
68       * @return the newly created action
69       * @throws FormActionException if an error occurs
70       */
71      public FormAction createAction(ActionBuilder actionBuilder,
72              ActionData actionData) throws FormActionException
73      {
74          SwingFormAction action = new SwingFormAction(actionData.getName(),
75                  actionData.getTask());
76          initActionProperty(action, Action.NAME, actionData.getText());
77          initActionProperty(action, Action.SHORT_DESCRIPTION, actionData
78                  .getToolTip());
79          initActionProperty(action, Action.SMALL_ICON, actionData.getIcon());
80          if (actionData.getMnemonicKey() > 0)
81          {
82              initActionProperty(action, Action.MNEMONIC_KEY, Integer.valueOf(
83                      actionData.getMnemonicKey()));
84          }
85          initActionProperty(action, Action.ACCELERATOR_KEY,
86                  keyStrokeFromAccelerator(actionData.getAccelerator()));
87          return action;
88      }
89  
90      /**
91       * Creates a menu item based on the given action. Depending on the
92       * {@code checked} argument either a {@code JMenuItem} or a
93       * {@code JCheckBoxMenuItem} object will be returned.
94       *
95       * @param actionBuilder the action builder
96       * @param formAction the action to associate with the menu item; this object
97       * must implement Swing's {@code Action} interface
98       * @param checked the checked flag
99       * @param parent the parent menu; this must be an instance of
100      * {@code JMenu}
101      * @return the new menu item
102      * @throws FormActionException if an error occurs
103      */
104     public Object createMenuItem(ActionBuilder actionBuilder,
105             FormAction formAction, boolean checked, Object parent)
106             throws FormActionException
107     {
108         Action action = (Action) formAction;
109         JMenuItem item = checked ? new JCheckBoxMenuItem(action)
110                 : new JMenuItem(action);
111         if (!actionBuilder.isMenuIcon())
112         {
113             // explicitly set the icon to null
114             item.setIcon(null);
115         }
116 
117         addToMenu(parent, item);
118         return item;
119     }
120 
121     /**
122      * Creates a menu item based on the passed in data object. Depending on the
123      * {@code checked} argument either a {@code JMenuItem} or a
124      * {@code JCheckBoxMenuItem} object will be returned.
125      *
126      * @param actionBuilder the action builder
127      * @param actionData the action data object
128      * @param checked the checked flag
129      * @param parent the parent menu; this must be an instance of
130      * {@code JMenu}
131      * @return the new menu item
132      * @throws FormActionException if an error occurs
133      *
134      */
135     public ComponentHandler<?> createMenuItem(ActionBuilder actionBuilder,
136             ActionData actionData, boolean checked, Object parent)
137             throws FormActionException
138     {
139         JMenuItem item = checked ? new JCheckBoxMenuItem() : new JMenuItem();
140         initFromActionData(item, actionData);
141         item.setAccelerator(keyStrokeFromAccelerator(actionData.getAccelerator()));
142         addToMenu(parent, item);
143         return new SwingButtonHandler(item);
144     }
145 
146     /**
147      * Creates a menu bar. This implementation will return a new
148      * {@code JMenuBar} object.
149      *
150      * @param actionBuilder the action builder
151      * @return the new menu bar
152      * @throws FormActionException if an error occurs
153      */
154     public Object createMenuBar(ActionBuilder actionBuilder)
155             throws FormActionException
156     {
157         return new JMenuBar();
158     }
159 
160     /**
161      * Creates a menu based on the given data. The return value will be a
162      * {@code JMenu} object.
163      *
164      * @param actionBuilder the action builder
165      * @param menu the menu object (<b>null</b> in the creation phase, a not
166      * <b>null</b> {@code JMenu} instance in the initialization phase)
167      * @param data the data for the new menu
168      * @param parent the menu's parent (a menu bar or a menu)
169      * @return the new menu
170      * @throws FormActionException if an error occurs, i.e. if the parent is
171      * undefined
172      */
173     public Object createMenu(ActionBuilder actionBuilder, Object menu,
174             TextIconData data, Object parent) throws FormActionException
175     {
176         if (menu == null)
177         {
178             // Creation phase
179             return new JMenu();
180         }
181 
182         else
183         {
184             // Initialization phase
185             JMenu men = (JMenu) menu;
186             men.setText(data.getCaption());
187             men.setIcon((Icon) data.getIcon());
188             men.setMnemonic(data.getMnemonic());
189 
190             if (parent != null)
191             {
192                 if (parent instanceof JMenuBar)
193                 {
194                     ((JMenuBar) parent).add(men);
195                 }
196                 else
197                 {
198                     addToMenu(parent, men);
199                 }
200             }
201             else
202             {
203                 throw new FormActionException("A parent must be provided!");
204             }
205 
206             return men;
207         }
208     }
209 
210     /**
211      * Creates a tool bar object. This implementation returns a
212      * {@code JToolBar} object.
213      *
214      * @param actionBuilder the action builder
215      * @return the new tool bar
216      * @throws FormActionException if an error occurs
217      */
218     public Object createToolbar(ActionBuilder actionBuilder)
219             throws FormActionException
220     {
221         return new JToolBar();
222     }
223 
224     /**
225      * Creates a toolbar button based on the given action. Depending on the
226      * {@code checked} argument either a {@code JButton} or a
227      * {@code JToggleButton} object will be returned.
228      *
229      * @param actionBuilder the action builder
230      * @param formAction the action to associate with the menu item; this object
231      * must implement Swing's {@code Action} interface
232      * @param checked the checked flag
233      * @param parent the parent toolbar; this must be an instance of
234      * {@code JToolBar}
235      * @return the new tool button
236      * @throws FormActionException if an error occurs
237      */
238     public Object createToolbarButton(ActionBuilder actionBuilder,
239             FormAction formAction, boolean checked, Object parent)
240             throws FormActionException
241     {
242         Action action = (Action) formAction;
243         AbstractButton btn = checked ? new JToggleButton(
244                 action) : (AbstractButton) new JButton(action);
245         if (!actionBuilder.isToolbarText())
246         {
247             // explicitly set the text to null
248             btn.setText(null);
249         }
250 
251         addToToolbar(parent, btn);
252         return btn;
253     }
254 
255     /**
256      * Creates a toolbar button based on the passed in action data object and
257      * returns a component handler for it. Depending on the {@code checked}
258      * argument either a {@code JButton} or a {@code JToggleButton}
259      * object will be created.
260      *
261      * @param actionBuilder the action builder
262      * @param data the action data object with the properties for the tool
263      * button
264      * @param checked the checked flag
265      * @param parent the parent toolbar; this must be an instance of
266      * {@code JToolBar}
267      * @return a component handler for the new tool button
268      * @throws FormActionException if an error occurs
269      */
270     public ComponentHandler<?> createToolbarButton(ActionBuilder actionBuilder,
271             ActionData data, boolean checked, Object parent)
272             throws FormActionException
273     {
274         AbstractButton btn = checked ? new JToggleButton()
275                 : (AbstractButton) new JButton();
276         initFromActionData(btn, data);
277         addToToolbar(parent, btn);
278         return new SwingButtonHandler(btn);
279     }
280 
281     /**
282      * Adds a separator to the given menu. The passed in menu must be an
283      * instance of {@code JMenu}.
284      *
285      * @param actionBuilder the action builder
286      * @param menu the menu
287      * @throws FormActionException if an error occurs
288      */
289     public void addMenuSeparator(ActionBuilder actionBuilder, Object menu)
290             throws FormActionException
291     {
292         ((JMenu) menu).addSeparator();
293     }
294 
295     /**
296      * Adds a separator to the given tool bar. The passed in object must be an
297      * instance of {@code JToolBar}.
298      *
299      * @param actionBuilder the action builder
300      * @param toolBar the tool bar
301      * @throws FormActionException if an error occurs
302      */
303     public void addToolBarSeparator(ActionBuilder actionBuilder, Object toolBar)
304             throws FormActionException
305     {
306         ((JToolBar) toolBar).addSeparator();
307     }
308 
309     /**
310      * Associates a {@code PopupMenuHandler} with a UI component. This
311      * implementation expects that the passed in object is derived from
312      * {@code java.awt.Component}. It registers a special mouse listener at
313      * this component that is looking for gestures triggering a popup menu. When
314      * such a gesture is detected the {@code PopupMenuHandler} object is
315      * invoked.
316      *
317      * @param component the component
318      * @param handler the handler to register
319      * @param compData the component builder data object
320      * @throws FormActionException if an error occurs
321      * @throws IllegalArgumentException if a required parameter is missing
322      */
323     public void registerPopupMenuHandler(Object component,
324             PopupMenuHandler handler, ComponentBuilderData compData)
325             throws FormActionException
326     {
327         if (!(component instanceof Component))
328         {
329             throw new FormActionException(
330                     "Component object must be derived from java.awt.Component: "
331                             + component);
332         }
333 
334         ((Component) component).addMouseListener(new SwingPopupListener(
335                 handler, compData, this, fetchActionBuilder(compData)));
336     }
337 
338     /**
339      * Adds a menu item to a parent menu. The menu must be an instance of
340      * {@code JMenu} or {@code JPopupMenu}.
341      *
342      * @param parent the parent menu
343      * @param item the item to add
344      */
345     protected void addToMenu(Object parent, JMenuItem item)
346     {
347         if (parent instanceof JPopupMenu)
348         {
349             ((JPopupMenu) parent).add(item);
350         }
351         else
352         {
353             ((JMenu) parent).add(item);
354         }
355     }
356 
357     /**
358      * Adds a tool button to the parent bar. The bar must be an instance of
359      * {@code JToolBar}.
360      *
361      * @param parent the parent tool bar
362      * @param button the button to add
363      */
364     protected void addToToolbar(Object parent, AbstractButton button)
365     {
366         ((JToolBar) parent).add(button);
367     }
368 
369     /**
370      * Helper method for setting an action's property. This method sets the
371      * property only if its value is defined.
372      *
373      * @param action the action to initialize
374      * @param property the name of the property
375      * @param value the property's value
376      */
377     private static void initActionProperty(Action action, String property,
378             Object value)
379     {
380         if (value != null)
381         {
382             action.putValue(property, value);
383         }
384     }
385 
386     /**
387      * Helper method for setting a button's properties to the values provided in
388      * the action data object. This method can be used for menu items and
389      * toolbar buttons.
390      *
391      * @param c the button to initialize
392      * @param data the action data object
393      */
394     static void initFromActionData(AbstractButton c, ActionData data)
395     {
396         c.setText(data.getText());
397         c.setToolTipText(data.getToolTip());
398         c.setMnemonic(data.getMnemonicKey());
399         c.setIcon((Icon) data.getIcon());
400     }
401 
402     /**
403      * Transforms a generic accelerator definition into a Swing-specific key
404      * stroke.
405      *
406      * @param acc the accelerator
407      * @return the key stroke
408      */
409     static KeyStroke keyStrokeFromAccelerator(Accelerator acc)
410     {
411         if (acc == null)
412         {
413             return null;
414         }
415 
416         int modifiers = convertAcceleratorModifiers(acc);
417         if (acc.getKey() != null)
418         {
419             return KeyStroke.getKeyStroke(acc.getKey(), modifiers);
420         }
421         else
422         {
423             int keyCode = (acc.getSpecialKey() != null) ? SwingEventConstantMapper
424                     .convertStandardKey(acc.getSpecialKey())
425                     : acc.getKeyCode();
426             return KeyStroke.getKeyStroke(keyCode, modifiers);
427         }
428     }
429 
430     /**
431      * Converts the modifiers defined for the given accelerator into a
432      * corresponding bit map.
433      *
434      * @param acc the accelerator
435      * @return the corresponding bits
436      */
437     private static int convertAcceleratorModifiers(Accelerator acc)
438     {
439         Set<Modifiers> mods = acc.getModifiers();
440         return SwingEventConstantMapper.convertStandardModifiers(mods);
441     }
442 
443     /**
444      * Obtains the {@code ActionBuilder} object from the bean context of the
445      * passed in {@code ComponentBuilderData}.
446      *
447      * @param compData the {@code ComponentBuilderData}
448      * @return the current {@code ActionBuilder}
449      */
450     private static ActionBuilder fetchActionBuilder(
451             ComponentBuilderData compData)
452     {
453         return (ActionBuilder) compData.getBeanContext().getBean(
454                 ActionBuilder.KEY_ACTION_BUILDER);
455     }
456 }