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 }