View Javadoc

1   /*
2    * Copyright (c) 2003-2008 by Cosylab d. d.
3    *
4    * This file is part of CosyBeans-Common.
5    *
6    * CosyBeans-Common is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU General Public License as published by
8    * the Free Software Foundation, either version 3 of the License, or
9    * (at your option) any later version.
10   *
11   * CosyBeans-Common is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU General Public License for more details.
15   *
16   * You should have received a copy of the GNU General Public License
17   * along with CosyBeans-Common.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  
20  package com.cosylab.gui.components.util;
21  
22  import java.awt.Component;
23  import java.awt.event.ActionEvent;
24  import java.awt.event.ActionListener;
25  import java.beans.PropertyChangeEvent;
26  import java.beans.PropertyChangeListener;
27  
28  import javax.swing.AbstractButton;
29  import javax.swing.Action;
30  import javax.swing.ButtonGroup;
31  import javax.swing.Icon;
32  import javax.swing.JButton;
33  import javax.swing.JCheckBoxMenuItem;
34  import javax.swing.JComponent;
35  import javax.swing.JMenu;
36  import javax.swing.JMenuBar;
37  import javax.swing.JMenuItem;
38  import javax.swing.JPopupMenu;
39  import javax.swing.JRadioButtonMenuItem;
40  import javax.swing.JSeparator;
41  import javax.swing.JToggleButton;
42  import javax.swing.SwingConstants;
43  
44  
45  /**
46   * Convenience class with set of utilities to help work with Action instances.
47   * Supports enhencments to Action framework by Abeans.
48   * 
49   * <p>
50   * If ActionList contains null action, than separator menu item is used.
51   * </p>
52   *
53   * @author <a href="mailto:miha.kadunc@cosylab.com">Miha Kadunc</a>
54   * @author <a href="mailto:igor.kriznar@cosylab.com">Igor Kriznar</a>
55   * @version $id$
56   */
57  public final class Actions
58  {
59  	/** Action value key for large icon image. */
60  	public static final String LARGE_ICON = "LargeIcon";
61  
62  	/** Action, which is used as placeholder for separator menu item. */
63  	public static final Action SEPARATOR = new Action() {
64  			public boolean isEnabled()
65  			{
66  				return false;
67  			}
68  
69  			public void setEnabled(boolean b)
70  			{
71  			}
72  
73  			public void addPropertyChangeListener(
74  			    PropertyChangeListener listener)
75  			{
76  			}
77  
78  			public void removePropertyChangeListener(
79  			    PropertyChangeListener listener)
80  			{
81  			}
82  
83  			public Object getValue(String key)
84  			{
85  				if (ActionList.SEPARATOR.equals(key)) {
86  					return Boolean.TRUE;
87  				}
88  
89  				return null;
90  			}
91  
92  			public void putValue(String key, Object value)
93  			{
94  			}
95  
96  			public void actionPerformed(ActionEvent e)
97  			{
98  			}
99  		};
100 
101 	/**
102 	 * Creates a button using the provided action.
103 	 *
104 	 * @param action 
105 	 *
106 	 * @return new button with assigned action
107 	 */
108 	public static AbstractButton createButton(final Action action)
109 	{
110 		if (action instanceof ToggleAction) {
111 			final JToggleButton btn = new JToggleButton(action);
112 			btn.setSelected(((ToggleAction)action).isSelected());
113 
114 			if (action.getValue(ToggleAction.SMALL_SELECTED_ICON) != null) {
115 				btn.setSelectedIcon((Icon)action.getValue(
116 				        ToggleAction.SMALL_SELECTED_ICON));
117 			}
118 
119 			action.addPropertyChangeListener(new PropertyChangeListener() {
120 					public void propertyChange(PropertyChangeEvent evt)
121 					{
122 						if ("selected".equals(evt.getPropertyName())) {
123 							btn.setSelected(((Boolean)evt.getNewValue())
124 							    .booleanValue());
125 						} else if ("visible".equals(evt.getPropertyName())) {
126 							btn.setVisible(((Boolean)evt.getNewValue())
127 							    .booleanValue());
128 						}
129 					}
130 				});
131             btn.addActionListener(new ActionListener() {
132                 public void actionPerformed(ActionEvent e) {
133                     boolean actSel=((ToggleAction)action).isSelected();
134                     if (actSel!=btn.isSelected()) {
135                         btn.setSelected(actSel);
136                     }
137                 }
138             });
139 
140 			return btn;
141 		} else if (action instanceof ActionList) {
142 			JButton butt = createButton((ActionList)action);
143 			butt.setAction(action);
144 
145 			return butt;
146 		} else {
147 			return new JButton(action);
148 		}
149 	}
150 
151 	/**
152 	 * Constructs a button from the provided ActionList.
153 	 *
154 	 * @param info
155 	 *
156 	 * @return button with assigned action
157 	 */
158 	public static JButton createButton(ActionList info)
159 	{
160 		JButton actionButton = new JButton();
161 
162 		if (info.getValue(Action.NAME) != null) {
163 			actionButton.setText((String)info.getValue(Action.NAME));
164 		}
165 
166 		if (info.getValue(Action.SHORT_DESCRIPTION) != null) {
167 			actionButton.setToolTipText((String)info.getValue(
168 			        Action.SHORT_DESCRIPTION));
169 		}
170 
171 		if (info.getValue(Action.SMALL_ICON) != null) {
172 			actionButton.setIcon((Icon)info.getValue(Action.SMALL_ICON));
173 		}
174 
175 		Object actions = info.getValue(ActionList.ACTIONS);
176 
177 		if (actions instanceof Action[]) {
178 			actionButton.addActionListener(new ListenerForMenu(
179 			        (Action[])actions));
180 		}
181 
182 		return actionButton;
183 	}
184 
185 	/**
186 	 * Creates new <code>JMenuItem</code>. If action is <code>null</code> or
187 	 * has <code>SEPARATOR</code>value <code>true</code>, <code>null</code> is
188 	 * returned.
189 	 *
190 	 * @param action action for which menu item should be created
191 	 *
192 	 * @return <code>JMenuItem</code> or <code>null</code> if action is
193 	 *         <code>null</code> or has <code>SEPARATOR</code>value
194 	 *         <code>true</code>
195 	 */
196 	public static JMenuItem createMenuItem(Action action)
197 	{
198 		return createMenuItem(action, false);
199 	}
200 
201 	/**
202 	 * Creates new <code>JMenuItem</code> or <code>JSeparator</code>, depending
203 	 * on provided action. If action is <code>null</code> or has
204 	 * <code>SEPARATOR</code>value <code>true</code>, then is reckognized as
205 	 * separator and instance of <code>JSeparator</code> with provided
206 	 * orientation is returned.
207 	 * 
208 	 * <p>
209 	 * Same as <code>createMenuComponent(Action action, boolean radio, int
210 	 * orientation)</code> with <code>false</code> for radio and
211 	 * <code>SwingConstants.HORIZONTAL</code> for orientation.
212 	 * </p>
213 	 *
214 	 * @param action action for which menu item should be created
215 	 *
216 	 * @return <code>JMenuItem</code> or <code>JSeparator</code> if action is
217 	 *         <code>null</code> or has <code>SEPARATOR</code>value
218 	 *         <code>true</code>
219 	 */
220 	public static JComponent createMenuComponent(Action action)
221 	{
222 		return createMenuComponent(action, false, SwingConstants.HORIZONTAL);
223 	}
224 
225 	/**
226 	 * Creates new <code>JMenuItem</code> or <code>JSeparator</code>, depending
227 	 * on provided action. If action is <code>null</code> or has
228 	 * <code>SEPARATOR</code>value <code>true</code>, then is reckognized as
229 	 * separator and instance of <code>JSeparator</code> with provided
230 	 * orientation is returned.
231 	 *
232 	 * @param action action for which menu item should be created
233 	 * @param radio if <code>true</code> then <code>JRadioButtonMenuItem</code>
234 	 *        is created
235 	 * @param orientation orientation used to
236 	 *
237 	 * @return <code>JMenuItem</code> or <code>JSeparator</code> if action is
238 	 *         <code>null</code> or has <code>SEPARATOR</code>value
239 	 *         <code>true</code>
240 	 */
241 	public static JComponent createMenuComponent(Action action, boolean radio,
242 	    int orientation)
243 	{
244 		JComponent comp = createMenuItem(action, radio);
245 
246 		if (comp == null) {
247 			comp = new JSeparator(orientation);
248 		}
249 
250 		return comp;
251 	}
252 
253 	/**
254 	 * Creates new <code>JMenuItem</code>. If action is <code>null</code> or
255 	 * has <code>SEPARATOR</code>value <code>true</code>, <code>null</code> is
256 	 * returned.
257 	 *
258 	 * @param action action for which menu item should be created
259 	 * @param radio if <code>true</code> then <code>JRadioButtonMenuItem</code>
260 	 *        is created
261 	 *
262 	 * @return <code>JMenuItem</code> or <code>null</code> if action is
263 	 *         <code>null</code> or has <code>SEPARATOR</code>value
264 	 *         <code>true</code>
265 	 */
266 	public static JMenuItem createMenuItem(Action action, boolean radio)
267 	{
268 		if (isSeparator(action)) {
269 			return null;
270 		}
271 
272 		if (action instanceof ToggleAction) {
273 			if (radio) {
274 				final JRadioButtonMenuItem itm = new JRadioButtonMenuItem(action);
275 				itm.setIcon(null);
276 				itm.setSelected(((ToggleAction)action).isSelected());
277 				action.addPropertyChangeListener(new PropertyChangeListener() {
278 						public void propertyChange(PropertyChangeEvent evt)
279 						{
280 							if ("selected".equals(evt.getPropertyName())) {
281 								itm.setSelected(((Boolean)evt.getNewValue())
282 								    .booleanValue());
283 							}
284 						}
285 					});
286 
287 				return itm;
288 			}
289 
290 			final JCheckBoxMenuItem itm = new JCheckBoxMenuItem(action);
291 			itm.setIcon(null);
292 			itm.setSelected(((ToggleAction)action).isSelected());
293 			action.addPropertyChangeListener(new PropertyChangeListener() {
294 					public void propertyChange(PropertyChangeEvent evt)
295 					{
296 						if ("selected".equals(evt.getPropertyName())) {
297 							itm.setSelected(((Boolean)evt.getNewValue())
298 							    .booleanValue());
299 						}
300 					}
301 				});
302 
303 			return itm;
304 		} else if (action instanceof ActionList) {
305 			return createMenuItem((ActionList)action);
306 		} else {
307 			if (radio) {
308 				JRadioButtonMenuItem itm = new JRadioButtonMenuItem(action);
309 
310 				return itm;
311 			}
312 
313 			JMenuItem itm = new JMenuItem(action);
314 
315 			return itm;
316 		}
317 	}
318 
319 	/**
320 	 * Returns <code>true</code> if action should be mapped to separator menu
321 	 * item.
322 	 *
323 	 * @param action
324 	 *
325 	 * @return <code>true</code> if action should be mapped to separator menu
326 	 *         item
327 	 */
328 	public static boolean isSeparator(Action action)
329 	{
330 		return action == null
331 		|| Boolean.TRUE.equals(action.getValue(ActionList.SEPARATOR));
332 	}
333 
334 	/**
335 	 * Creates a popup menu with the given actions.
336 	 *
337 	 * @param info actions to be included in the popup
338 	 *
339 	 * @return popup menu with assigned actions
340 	 */
341 	public static JPopupMenu createPopupMenu(ActionList info)
342 	{
343 		JPopupMenu theMenu = new JPopupMenu();
344 
345 		if (info.getValue(Action.SHORT_DESCRIPTION) != null) {
346 			theMenu.setToolTipText((String)info.getValue(
347 			        Action.SHORT_DESCRIPTION));
348 		}
349 
350 		if (info.getValue(Action.SHORT_DESCRIPTION) != null) {
351 			theMenu.setToolTipText((String)info.getValue(
352 			        Action.SHORT_DESCRIPTION));
353 		}
354 
355 		Action[] act = info.toArray();
356 		JComponent currentItem = null;
357 		boolean isThereIcon = false;
358 
359 		for (int i = 0; i < act.length; i++) {
360 			if (isSeparator(act[i])) {
361 				theMenu.add(new JPopupMenu.Separator());
362 			} else {
363 				currentItem = createMenuItem(act[i],
364 					    info.getValue(ActionList.MENU_CONTEXT) == ActionList.MENU_CONTEXT_RADIO);
365 				theMenu.add(currentItem);
366 
367 				if (!isThereIcon && currentItem instanceof JMenuItem
368 				    && ((JMenuItem)currentItem).getIcon() != null) {
369 					isThereIcon = true;
370 				}
371 			}
372 		}
373 
374 		// Workaround for GUI bug
375 		if (isThereIcon
376 		    && info.getValue(ActionList.MENU_CONTEXT) != ActionList.MENU_CONTEXT_RADIO) {
377 			Component[] c = theMenu.getComponents();
378 
379 			for (int i = 0; i < c.length; i++) {
380 				if (c[i] instanceof JMenuItem) {
381 					currentItem = (JMenuItem)c[i];
382 
383 					if (currentItem instanceof JMenuItem
384 					    && ((JMenuItem)currentItem).getIcon() == null) {
385 						((JMenuItem)currentItem).setIcon(IconHelper.createIcon(
386 						        "icons/general/Blank16.gif"));
387 					}
388 				}
389 			}
390 		}
391 
392 		if (info.getValue(ActionList.MENU_CONTEXT) == ActionList.MENU_CONTEXT_RADIO) {
393 			Component[] c = theMenu.getComponents();
394 			ButtonGroup bg = new ButtonGroup();
395 
396 			for (int i = 0; i < c.length; i++) {
397 				bg.add((AbstractButton)c[i]);
398 			}
399 
400 			info.putValue("buttonGroup", bg);
401 		}
402 
403 		return theMenu;
404 	}
405 
406 	/**
407 	 * Creates a menu item with the given action.
408 	 *
409 	 * @param actions 
410 	 *
411 	 * @return menu item with assigned action
412 	 */
413 	public static JMenuItem createMenuItem(ActionList actions)
414 	{
415 		return createMenuItem(actions,
416 		    actions.getValue(ActionList.MENU_CONTEXT) == ActionList.MENU_CONTEXT_RADIO);
417 	}
418 
419 	/**
420 	 * Creates a menu item with the given action.
421 	 *
422 	 * @param actions 
423 	 * @param radio if true the menu item will be a radio button
424 	 *
425 	 * @return menu item with assigned action
426 	 */
427 	public static JMenuItem createMenuItem(ActionList actions, boolean radio)
428 	{
429 		return addToMenu(new JMenu(), actions, radio, SwingConstants.HORIZONTAL);
430 	}
431 
432 	/**
433 	 * Constructs a menu with the given actions as menu items.
434 	 *
435 	 * @param actions menu items actions
436 	 *
437 	 * @return menu with assigned actions
438 	 */
439 	public static JMenu createMenu(ActionList actions)
440 	{
441 		return addToMenu(new JMenu(), actions, false, SwingConstants.HORIZONTAL);
442 	}
443 
444 	/**
445 	 * Constructs a menu with given actions as menu items.
446 	 *
447 	 * @param actions menu items actions
448 	 * @param orientation 
449 	 *
450 	 * @return menu with assigned actions
451 	 */
452 	public static JMenu createMenu(ActionList actions, int orientation)
453 	{
454 		return addToMenu(new JMenu(), actions, false, orientation);
455 	}
456 
457 	/**
458 	 * Constructs a menu bar with the give actions.
459 	 *
460 	 * @param actions 
461 	 *
462 	 * @return menu bar with assigned action
463 	 */
464 	public static JMenuBar createMenuBar(ActionList actions)
465 	{
466 		final JMenuBar bar = new JMenuBar();
467 		actions.addPropertyChangeListener(new PropertyChangeListener() {
468 				public void propertyChange(PropertyChangeEvent evt)
469 				{
470 					if (evt.getPropertyName() == ActionList.ACTIONS) {
471 						updateMenuBar((Action[])evt.getNewValue(), bar);
472 					}
473 				}
474 			});
475 
476 		return bar;
477 	}
478 
479 	private static void updateMenuBar(Action[] a, JMenuBar bar)
480 	{
481 		bar.removeAll();
482 
483 		for (int i = 0; i < a.length; i++) {
484 			if (a[i] instanceof ActionList) {
485 				bar.add(createMenu((ActionList)a[i], SwingConstants.VERTICAL));
486 			} else {
487 				bar.add(createMenuComponent(a[i], false, SwingConstants.VERTICAL));
488 			}
489 		}
490 	}
491 
492 	private static JMenu addToMenu(JMenu theMenu, ActionList info,
493 	    boolean radio, int orientation)
494 	{
495 		// No text in toolbars
496 		if (info.getValue(Action.NAME) != null) {
497 			theMenu.setText((String)info.getValue(Action.NAME));
498 		}
499 
500 		if (info.getValue(Action.SHORT_DESCRIPTION) != null) {
501 			theMenu.setToolTipText((String)info.getValue(
502 			        Action.SHORT_DESCRIPTION));
503 		}
504 
505 		if (info.getValue(Action.SMALL_ICON) != null) {
506 			theMenu.setIcon((Icon)info.getValue(Action.SMALL_ICON));
507 		}
508 
509 		Action[] act = info.toArray();
510 
511 		for (int i = 0; i < act.length; i++) {
512 			theMenu.add(createMenuComponent(act[i], radio, orientation));
513 		}
514 
515 		if (radio) {
516 			Component[] c = theMenu.getComponents();
517 			ButtonGroup bg = new ButtonGroup();
518 
519 			for (int i = 0; i < c.length; i++) {
520 				bg.add((AbstractButton)c[i]);
521 			}
522 
523 			info.putValue("buttonGroup", bg);
524 		}
525 
526 		return theMenu;
527 	}
528 
529 	/**
530 	 * DOCUMENT ME!
531 	 *
532 	 * @author $Author: jbobnar $
533 	 * @version $Revision: 1.13 $
534 	 */
535 	public static class ListenerForMenu implements ActionListener
536 	{
537 		private Action[] actions = null;
538 		private JPopupMenu popup = null;
539 
540 		/**
541 		 * Creates a new ListenerForMenu object.
542 		 *
543 		 * @param subActions DOCUMENT ME!
544 		 */
545 		public ListenerForMenu(Action[] subActions)
546 		{
547 			actions = subActions;
548 		}
549 
550 		/**
551 		 * DOCUMENT ME!
552 		 *
553 		 * @return DOCUMENT ME!
554 		 */
555 		public JPopupMenu getPopup()
556 		{
557 			if (popup == null) {
558 				popup = new JPopupMenu();
559 
560 				for (int i = 0; i < actions.length; i++) {
561 					if (isSeparator(actions[i])) {
562 						popup.add(new JPopupMenu.Separator());
563 					} else {
564 						popup.add(createMenuItem(actions[i]));
565 					}
566 				}
567 			}
568 
569 			return popup;
570 		}
571 
572 		/**
573 		 * DOCUMENT ME!
574 		 *
575 		 * @param e DOCUMENT ME!
576 		 */
577 		public void actionPerformed(ActionEvent e)
578 		{
579 			Component invoker = (Component)e.getSource();
580 			getPopup().show(invoker, 0, invoker.getHeight());
581 		}
582 	}
583 }
584 
585 /* __oOo__ */