org.jdesktop.swingx.action.ActionContainerFactory Maven / Gradle / Ivy
Show all versions of swingx-all Show documentation
/*
* $Id: ActionContainerFactory.java 3980 2011-03-28 20:24:46Z kschaefe $
*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jdesktop.swingx.action;
import java.awt.Insets;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
/**
* Creates user interface elements based on action ids and lists of action ids.
* All action ids must represent actions managed by the ActionManager.
*
*
Action Lists
* Use the createXXX(List) methods to construct containers of actions like menu
* bars, menus, popups and toolbars from actions represented as action ids in a
* java.util.List. Each element in the action-list can be one of 3 types:
*
* - action id: corresponds to an action managed by the ActionManager
*
- null: indicates a separator should be inserted.
*
- java.util.List: represents a submenu. See the note below which describes
* the configuration of menus.
*
* The order of elements in an action-list determines the arrangement of the ui
* components which are constructed from the action-list.
*
* For a menu or submenu, the first element in the action-list represents a menu
* and subsequent elements represent menu items or separators (if null).
*
* This class can be used as a general component factory which will construct
* components from Actions if the create<comp>(Action,...)
* methods are used.
*
* @see ActionManager
*/
public class ActionContainerFactory {
/**
* Standard margin for toolbar buttons to improve their look
*/
private static Insets TOOLBAR_BUTTON_MARGIN = new Insets(1, 1, 1, 1);
private ActionMap manager;
// Map between group id + component and the ButtonGroup
private Map groupMap;
/**
* Constructs an container factory which uses the default
* ActionManager.
*
*/
public ActionContainerFactory() {
}
/**
* Constructs an container factory which uses managed actions.
*
* @param manager use the actions managed with this manager for
* constructing ui componenents.
*/
public ActionContainerFactory(ActionMap manager) {
setActionManager(manager);
}
/**
* Gets the ActionManager instance. If the ActionManager has not been explicitly
* set then the default ActionManager instance will be used.
*
* @return the ActionManager used by the ActionContainerFactory.
* @see #setActionManager
*/
public ActionMap getActionManager() {
if (manager == null) {
manager = ActionManager.getInstance();
}
return manager;
}
/**
* Sets the ActionManager instance that will be used by this
* ActionContainerFactory
*/
public void setActionManager(ActionMap manager) {
this.manager = manager;
}
/**
* Constructs a toolbar from an action-list id. By convention,
* the identifier of the main toolbar should be "main-toolbar"
*
* @param list a list of action ids used to construct the toolbar.
* @return the toolbar or null
*/
public JToolBar createToolBar(Object[] list) {
return createToolBar(Arrays.asList(list));
}
/**
* Constructs a toolbar from an action-list id. By convention,
* the identifier of the main toolbar should be "main-toolbar"
*
* @param list a list of action ids used to construct the toolbar.
* @return the toolbar or null
*/
public JToolBar createToolBar(List> list) {
JToolBar toolbar = new JToolBar();
Iterator> iter = list.iterator();
while(iter.hasNext()) {
Object element = iter.next();
if (element == null) {
toolbar.addSeparator();
} else {
AbstractButton button = createButton(element, toolbar);
// toolbar buttons shouldn't steal focus
button.setFocusable(false);
/*
* TODO
* The next two lines improve the default look of the buttons.
* This code should be changed to retrieve the default look
* from some UIDefaults object.
*/
button.setMargin(TOOLBAR_BUTTON_MARGIN);
button.setBorderPainted(false);
toolbar.add(button);
}
}
return toolbar;
}
/**
* Constructs a popup menu from an array of action ids.
*
* @param list an array of action ids used to construct the popup.
* @return the popup or null
*/
public JPopupMenu createPopup(Object[] list) {
return createPopup(Arrays.asList(list));
}
/**
* Constructs a popup menu from a list of action ids.
*
* @param list a list of action ids used to construct the popup.
* @return the popup or null
*/
public JPopupMenu createPopup(List> list) {
JPopupMenu popup = new JPopupMenu();
Iterator> iter = list.iterator();
while(iter.hasNext()) {
Object element = iter.next();
if (element == null) {
popup.addSeparator();
} else if (element instanceof List>) {
JMenu newMenu= createMenu((List>)element);
if (newMenu!= null) {
popup.add(newMenu);
}
} else {
popup.add(createMenuItem(element, popup));
}
}
return popup;
}
/**
* Constructs a menu tree from a list of actions or lists of lists or actions.
*
* @param actionIds an array which represents the root item.
* @return a menu bar which represents the menu bar tree
*/
public JMenuBar createMenuBar(Object[] actionIds) {
return createMenuBar(Arrays.asList(actionIds));
}
/**
* Constructs a menu tree from a list of actions or lists of lists or actions.
*
* @param list a list which represents the root item.
* @return a menu bar which represents the menu bar tree
*/
public JMenuBar createMenuBar(List> list) {
final JMenuBar menubar = new JMenuBar();
for (Object element : list) {
if (element == null) {
continue;
}
JMenuItem menu;
if (element instanceof Object[]) {
menu = createMenu((Object[]) element);
} else if (element instanceof List>) {
menu = createMenu((List>) element);
} else {
menu = createMenuItem(element, menubar);
}
if (menu != null) {
menubar.add(menu);
}
}
return menubar;
}
/**
* Creates and returns a menu from a List which represents actions, separators
* and sub-menus. The menu
* constructed will have the attributes from the first action in the List.
* Subsequent actions in the list represent menu items.
*
* @param actionIds an array of action ids used to construct the menu and menu items.
* the first element represents the action used for the menu,
* @return the constructed JMenu or null
*/
public JMenu createMenu(Object[] actionIds) {
return createMenu(Arrays.asList(actionIds));
}
/**
* Creates and returns a menu from a List which represents actions, separators
* and sub-menus. The menu
* constructed will have the attributes from the first action in the List.
* Subsequent actions in the list represent menu items.
*
* @param list a list of action ids used to construct the menu and menu items.
* the first element represents the action used for the menu,
* @return the constructed JMenu or null
*/
public JMenu createMenu(List> list) {
// The first item will be the action for the JMenu
Action action = getAction(list.get(0));
if (action == null) {
return null;
}
JMenu menu = new JMenu(action);
// The rest of the items represent the menu items.
for (Object element : list.subList(1, list.size())) {
if (element == null) {
menu.addSeparator();
} else {
JMenuItem newMenu;
if (element instanceof Object[]) {
newMenu = createMenu((Object[]) element);
} else if (element instanceof List>) {
newMenu = createMenu((List>) element);
} else {
newMenu = createMenuItem(element, menu);
}
if (newMenu != null) {
menu.add(newMenu);
}
}
}
return menu;
}
/**
* Convenience method to get the action from an ActionManager.
*/
private Action getAction(Object id) {
return getActionManager().get(id);
}
/**
* Returns the button group corresponding to the groupid
*
* @param groupid the value of the groupid attribute for the action element
* @param container a container which will further identify the ButtonGroup
*/
private ButtonGroup getGroup(String groupid, JComponent container) {
if (groupMap == null) {
groupMap = new HashMap();
}
int intCode = groupid.hashCode();
if (container != null) {
intCode ^= container.hashCode();
}
Integer hashCode = new Integer(intCode);
ButtonGroup group = groupMap.get(hashCode);
if (group == null) {
group = new ButtonGroup();
groupMap.put(hashCode, group);
}
return group;
}
/**
* Creates a menu item based on the attributes of the action element.
* Will return a JMenuItem, JRadioButtonMenuItem or a JCheckBoxMenuItem
* depending on the context of the Action.
*
* @return a JMenuItem or subclass depending on type.
*/
private JMenuItem createMenuItem(Object id, JComponent container) {
return createMenuItem(getAction(id), container);
}
/**
* Creates a menu item based on the attributes of the action element.
* Will return a JMenuItem, JRadioButtonMenuItem or a JCheckBoxMenuItem
* depending on the context of the Action.
*
* @param action a managed Action
* @param container the parent container may be null for non-group actions.
* @return a JMenuItem or subclass depending on type.
*/
private JMenuItem createMenuItem(Action action, JComponent container) {
JMenuItem menuItem = null;
if (action instanceof AbstractActionExt) {
AbstractActionExt ta = (AbstractActionExt)action;
if (ta.isStateAction()) {
String groupid = (String)ta.getGroup();
if (groupid != null) {
// If this action has a groupid attribute then it's a
// GroupAction
menuItem = createRadioButtonMenuItem(getGroup(groupid, container),
(AbstractActionExt)action);
} else {
menuItem = createCheckBoxMenuItem((AbstractActionExt)action);
}
}
}
if (menuItem == null) {
menuItem= new JMenuItem(action);
configureMenuItemFromExtActionProperties(menuItem, action);
}
return menuItem;
}
/**
* Creates a menu item based on the attributes of the action.
* Will return a JMenuItem, JRadioButtonMenuItem or a JCheckBoxMenuItem
* depending on the context of the Action.
*
* @param action an action used to create the menu item
* @return a JMenuItem or subclass depending on type.
*/
public JMenuItem createMenuItem(Action action) {
return createMenuItem(action, null);
}
/**
* Creates, configures and returns an AbstractButton.
*
* The attributes of the action element
* registered with the ActionManger by the given id.
* Will return a JButton or a JToggleButton.
*
* @param id the identifier
* @param container the JComponent which parents the group, if any.
* @return an AbstractButton based on the
*/
public AbstractButton createButton(Object id, JComponent container) {
return createButton(getAction(id), container);
}
/**
* Creates a button based on the attributes of the action. If the container
* parameter is non-null then it will be used to uniquely identify
* the returned component within a ButtonGroup. If the action doesn't
* represent a grouped component then this value can be null.
*
* @param action an action used to create the button
* @param container the parent container to uniquely identify
* grouped components or null
* @return will return a JButton or a JToggleButton.
*/
public AbstractButton createButton(Action action, JComponent container) {
if (action == null) {
return null;
}
AbstractButton button = null;
if (action instanceof AbstractActionExt) {
// Check to see if we should create a toggle button
AbstractActionExt ta = (AbstractActionExt)action;
if (ta.isStateAction()) {
// If this action has a groupid attribute then it's a
// GroupAction
String groupid = (String)ta.getGroup();
if (groupid == null) {
button = createToggleButton(ta);
} else {
button = createToggleButton(ta, getGroup(groupid, container));
}
}
}
if (button == null) {
// Create a regular button
button = new JButton(action);
configureButtonFromExtActionProperties(button, action);
}
return button;
}
/**
* Creates a button based on the attributes of the action.
*
* @param action an action used to create the button
* @return will return a JButton or a JToggleButton.
*/
public AbstractButton createButton(Action action) {
return createButton(action, null);
}
/**
* Adds and configures a toggle button.
* @param a an abstraction of a toggle action.
*/
private JToggleButton createToggleButton(AbstractActionExt a) {
return createToggleButton(a, null);
}
/**
* Adds and configures a toggle button.
* @param a an abstraction of a toggle action.
* @param group the group to add the toggle button or null
*/
private JToggleButton createToggleButton(AbstractActionExt a, ButtonGroup group) {
JToggleButton button = new JToggleButton();
configureButton(button, a, group);
return button;
}
/**
*
* @param button
* @param a
* @param group
*/
public void configureButton(JToggleButton button, AbstractActionExt a, ButtonGroup group) {
configureSelectableButton(button, a, group);
configureButtonFromExtActionProperties(button, a);
}
/**
* method to configure a "selectable" button from the given AbstractActionExt.
* As there is some un-/wiring involved to support synch of the selected property between
* the action and the button, all config and unconfig (== setting a null action!)
* should be passed through this method.
*
* It's up to the client to only pass in button's where selected and/or the
* group property makes sense.
*
* PENDING: the group properties are yet untested.
* PENDING: think about automated unconfig.
*
* @param button where selected makes sense
* @param a
* @param group the button should be added to.
* @throws IllegalArgumentException if the given action doesn't have the state flag set.
*
*/
public void configureSelectableButton(AbstractButton button, AbstractActionExt a, ButtonGroup group){
if ((a != null) && !a.isStateAction()) throw
new IllegalArgumentException("the Action must be a stateAction");
// we assume that all button configuration is done exclusively through this method!!
if (button.getAction() == a) return;
// unconfigure if the old Action is a state AbstractActionExt
// PENDING JW: automate unconfigure via a PCL that is listening to
// the button's action property? Think about memory leak implications!
Action oldAction = button.getAction();
if (oldAction instanceof AbstractActionExt) {
AbstractActionExt actionExt = (AbstractActionExt) oldAction;
// remove as itemListener
button.removeItemListener(actionExt);
// remove the button related PCL from the old actionExt
PropertyChangeListener[] l = actionExt.getPropertyChangeListeners();
for (int i = l.length - 1; i >= 0; i--) {
if (l[i] instanceof ToggleActionPropertyChangeListener) {
ToggleActionPropertyChangeListener togglePCL = (ToggleActionPropertyChangeListener) l[i];
if (togglePCL.isToggling(button)) {
actionExt.removePropertyChangeListener(togglePCL);
}
}
}
}
button.setAction(a);
if (group != null) {
group.add(button);
}
if (a != null) {
button.addItemListener(a);
// JW: move the initial config into the PCL??
button.setSelected(a.isSelected());
new ToggleActionPropertyChangeListener(a, button);
// new ToggleActionPCL(button, a);
}
}
/**
* This method will be called after buttons created from an action. Override
* for custom configuration.
*
* @param button the button to be configured
* @param action the action used to construct the menu item.
*/
protected void configureButtonFromExtActionProperties(AbstractButton button, Action action) {
if (action.getValue(Action.SHORT_DESCRIPTION) == null) {
button.setToolTipText((String)action.getValue(Action.NAME));
}
// Use the large icon for toolbar buttons.
if (action.getValue(AbstractActionExt.LARGE_ICON) != null) {
button.setIcon((Icon)action.getValue(AbstractActionExt.LARGE_ICON));
}
// Don't show the text under the toolbar buttons if they have an icon
if (button.getIcon() != null) {
button.setText("");
}
}
/**
* This method will be called after menu items are created.
* Override for custom configuration.
*
* @param menuItem the menu item to be configured
* @param action the action used to construct the menu item.
*/
protected void configureMenuItemFromExtActionProperties(JMenuItem menuItem, Action action) {
}
/**
* Helper method to add a checkbox menu item.
*/
private JCheckBoxMenuItem createCheckBoxMenuItem(AbstractActionExt a) {
JCheckBoxMenuItem mi = new JCheckBoxMenuItem();
configureSelectableButton(mi, a, null);
configureMenuItemFromExtActionProperties(mi, a);
return mi;
}
/**
* Helper method to add a radio button menu item.
*/
private JRadioButtonMenuItem createRadioButtonMenuItem(ButtonGroup group,
AbstractActionExt a) {
JRadioButtonMenuItem mi = new JRadioButtonMenuItem();
configureSelectableButton(mi, a, group);
configureMenuItemFromExtActionProperties(mi, a);
return mi;
}
}