/**
* Copyright (C) 2015 Valkyrie RCP
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.valkyriercp.command.support;
import com.jgoodies.forms.layout.ColumnSpec;
import com.jgoodies.forms.layout.RowSpec;
import com.jgoodies.forms.layout.Size;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.valkyriercp.command.CommandGroupListener;
import org.valkyriercp.command.CommandRegistry;
import org.valkyriercp.command.GroupContainerPopulator;
import org.valkyriercp.command.config.CommandButtonConfigurer;
import org.valkyriercp.command.config.CommandFaceDescriptor;
import org.valkyriercp.factory.ButtonFactory;
import org.valkyriercp.factory.MenuFactory;
import org.valkyriercp.util.GuiStandardUtils;
import org.valkyriercp.util.UIConstants;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.EventListenerList;
import java.awt.*;
import java.util.Collections;
import java.util.Iterator;
/**
* Implementation of an {@link AbstractCommand} that groups a collection of
* commands. This can be used to construct a menu with all kinds of sub menus.
*
* @author Keith Donald
*/
public class CommandGroup extends AbstractCommand {
private EventListenerList listenerList;
private GroupMemberList memberList = new GroupMemberList();
private CommandRegistry commandRegistry;
/**
* @see AbstractCommand#AbstractCommand()
*/
public CommandGroup() {
super();
}
/**
* @see AbstractCommand#AbstractCommand(String)
*/
public CommandGroup(String groupId) {
super(groupId);
}
/**
* @see AbstractCommand#AbstractCommand(String, CommandFaceDescriptor)
*/
public CommandGroup(String groupId, CommandFaceDescriptor face) {
super(groupId, face);
}
/**
* Constructor with id for configuration and the {@link CommandRegistry} to use.
*
* @see AbstractCommand#AbstractCommand(String)
*/
public CommandGroup(String groupId, CommandRegistry commandRegistry) {
super(groupId);
setCommandRegistry(commandRegistry);
}
/**
* @see AbstractCommand#AbstractCommand(String, String)
*/
public CommandGroup(String id, String encodedLabel) {
super(id, encodedLabel);
}
/**
* @see AbstractCommand#AbstractCommand(String, String, javax.swing.Icon, String)
*/
public CommandGroup(String id, String encodedLabel, Icon icon, String caption) {
super(id, encodedLabel, icon, caption);
}
protected void addInternal(AbstractCommand command) {
this.memberList.add(new SimpleGroupMember(this, command));
}
protected void addLazyInternal(String commandId) {
this.memberList.add(new LazyGroupMember(this, commandId));
}
protected void addSeparatorInternal() {
this.memberList.add(new SeparatorGroupMember());
}
protected void addGlueInternal() {
this.memberList.add(new GlueGroupMember());
}
protected void addComponentInternal(Component component) {
this.memberList.add(new ComponentGroupMember(component));
}
public final void setCommandRegistry(CommandRegistry registry) {
if (!ObjectUtils.nullSafeEquals(this.commandRegistry, registry)) {
// @TODO should groups listen to command registration events if
// they've
// got lazy members that haven't been instantiated? Or are
// targetable
// commands lightweight enough?
if (logger.isDebugEnabled()) {
logger.debug("Setting registry " + registry + " for command group '" + getId() + "'");
}
this.commandRegistry = registry;
}
}
/**
* Enable/disable the entire group.
*
* @param enabled enable/disable this group.
*/
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
for (Iterator members = getMemberList().iterator(); members.hasNext();)
{
GroupMember groupMember = (GroupMember)members.next();
groupMember.setEnabled(enabled);
}
}
public void setVisible(boolean visible) {
super.setVisible(visible);
getMemberList().setContainersVisible(visible);
}
protected CommandRegistry getCommandRegistry() {
return commandRegistry;
}
public void add(AbstractCommand command) {
add(command, true);
}
public void add(AbstractCommand command, boolean rebuild) {
if (command == null) {
return;
}
if (contains(command)) {
return;
}
getMemberList().append(new SimpleGroupMember(this, command));
rebuildIfNecessary(rebuild);
}
public void add(String groupMemberPath, AbstractCommand command) {
AbstractCommand c = find(groupMemberPath);
assertIsGroup(groupMemberPath, c);
((CommandGroup) c).add(command);
}
private void assertIsGroup(String groupMemberPath, AbstractCommand c) {
Assert.notNull(c, "Command at path '" + groupMemberPath + "' does not exist.");
Assert.isTrue((c instanceof CommandGroup), "Command at path '" + groupMemberPath + "' is not a group.");
}
public void add(String groupMemberPath, AbstractCommand command, boolean rebuild) {
AbstractCommand c = find(groupMemberPath);
assertIsGroup(groupMemberPath, c);
((CommandGroup) c).add(command, rebuild);
}
public void remove(AbstractCommand command) {
remove(command, true);
}
public void remove(AbstractCommand command, boolean rebuild) {
if (command == null) {
return;
}
ExpansionPointGroupMember expansionPoint = getMemberList().getExpansionPoint();
GroupMember member = expansionPoint.getMemberFor(command.getId());
if (member != null) {
expansionPoint.remove(member);
rebuildIfNecessary(rebuild);
}
}
public void remove(String groupMemberPath, AbstractCommand command) {
AbstractCommand c = find(groupMemberPath);
assertIsGroup(groupMemberPath, c);
((CommandGroup) c).remove(command);
}
public void remove(String groupMemberPath, AbstractCommand command, boolean rebuild) {
AbstractCommand c = find(groupMemberPath);
assertIsGroup(groupMemberPath, c);
((CommandGroup) c).remove(command, rebuild);
}
public void addSeparator() {
addSeparator(true);
}
public void addSeparator(boolean rebuild) {
getMemberList().append(new SeparatorGroupMember());
rebuildIfNecessary(rebuild);
}
public void addGlue() {
addGlue(true);
}
public void addGlue(boolean rebuild) {
getMemberList().append(new GlueGroupMember());
rebuildIfNecessary(rebuild);
}
private void rebuildIfNecessary(boolean rebuild) {
if (rebuild) {
rebuildAllControls();
fireMembersChanged();
}
}
protected void rebuildAllControls() {
if (logger.isDebugEnabled()) {
logger.debug("Rebuilding all GUI controls for command group '" + getId() + "'");
}
getMemberList().rebuildControls();
}
protected GroupMemberList getMemberList() {
return memberList;
}
protected Iterator memberIterator() {
return getMemberList().iterator();
}
public int size() {
return getMemberCount();
}
public boolean isAllowedMember(AbstractCommand proposedMember) {
return true;
}
/**
* Search for and return the command contained by this group with the
* specified path. Nested paths should be deliniated by the "/" character;
* for example, "fileGroup/newGroup/simpleFileCommand". The returned command
* may be a group or an action command.
*
* @param memberPath the path of the command, with nested levels deliniated
* by the "/" path separator.
* @return the command at the specified member path, or <code>null</code>
* if no was command found.
*/
public AbstractCommand find(String memberPath) {
if (logger.isDebugEnabled()) {
logger.debug("Searching for command with nested path '" + memberPath + "'");
}
String[] paths = StringUtils.delimitedListToStringArray(memberPath, "/");
CommandGroup currentGroup = this;
// fileGroup/newGroup/newJavaProject
for (int i = 0; i < paths.length; i++) {
String memberId = paths[i];
if (i < paths.length - 1) {
// must be a nested group
currentGroup = currentGroup.findCommandGroupMember(memberId);
}
else {
// is last path element; can be a group or action
return currentGroup.findCommandMember(memberId);
}
}
return null;
}
private CommandGroup findCommandGroupMember(String groupId) {
AbstractCommand c = findCommandMember(groupId);
Assert.isTrue((c instanceof CommandGroup), "Command with id '" + groupId + "' is not a group.");
return (CommandGroup) c;
}
private AbstractCommand findCommandMember(String commandId) {
Iterator it = memberList.iterator();
while (it.hasNext()) {
GroupMember member = (GroupMember) it.next();
if (member.managesCommand(commandId)) {
return member.getCommand();
}
}
logger.warn("No command with id '" + commandId + "' is nested within this group (" + getId()
+ "); returning null");
return null;
}
/**
* Executes all the members of this group.
*/
public void execute() {
Iterator it = memberList.iterator();
while (it.hasNext()) {
GroupMember member = (GroupMember) it.next();
member.getCommand().execute();
}
}
public int getMemberCount() {
return getMemberList().size();
}
public boolean contains(AbstractCommand command) {
return getMemberList().contains(command);
}
public void reset() {
ExpansionPointGroupMember expansionPoint = getMemberList().getExpansionPoint();
if (!expansionPoint.isEmpty()) {
expansionPoint.clear();
rebuildIfNecessary(true);
}
}
public AbstractButton createButton(String faceDescriptorId, ButtonFactory buttonFactory,
CommandButtonConfigurer buttonConfigurer) {
return createButton(getDefaultFaceDescriptorId(), buttonFactory, getMenuFactory(), buttonConfigurer);
}
public AbstractButton createButton(ButtonFactory buttonFactory, MenuFactory menuFactory) {
return createButton(getDefaultFaceDescriptorId(), buttonFactory, menuFactory, getPullDownMenuButtonConfigurer());
}
public AbstractButton createButton(String faceDescriptorId, ButtonFactory buttonFactory, MenuFactory menuFactory) {
return createButton(faceDescriptorId, buttonFactory, menuFactory, getPullDownMenuButtonConfigurer());
}
public AbstractButton createButton(ButtonFactory buttonFactory, MenuFactory menuFactory,
CommandButtonConfigurer buttonConfigurer) {
return createButton(getDefaultFaceDescriptorId(), buttonFactory, menuFactory, buttonConfigurer);
}
public AbstractButton createButton(String faceDescriptorId, ButtonFactory buttonFactory, MenuFactory menuFactory,
CommandButtonConfigurer buttonConfigurer) {
AbstractButton button = buttonFactory.createToggleButton();
attach(button, buttonConfigurer);
JPopupMenu popup = menuFactory.createPopupMenu();
bindMembers(button, popup, menuFactory, getMenuItemButtonConfigurer());
ToggleButtonPopupListener.bind(button, popup);
return button;
}
protected CommandButtonConfigurer getPullDownMenuButtonConfigurer() {
return getCommandServices().getPullDownMenuButtonConfigurer();
}
public JMenuItem createMenuItem(String faceDescriptorId, MenuFactory factory,
CommandButtonConfigurer buttonConfigurer) {
JMenu menu = factory.createMenu();
attach(menu);
bindMembers(menu, menu, factory, buttonConfigurer);
return menu;
}
public JPopupMenu createPopupMenu() {
return createPopupMenu(getMenuFactory());
}
public JPopupMenu createPopupMenu(MenuFactory factory) {
JPopupMenu popup = factory.createPopupMenu();
bindMembers(popup, popup, factory, getMenuItemButtonConfigurer());
return popup;
}
public JComponent createToolBar() {
return createToolBar(getToolBarButtonFactory());
}
public JComponent createToolBar(ButtonFactory buttonFactory) {
JComponent toolbar = getComponentFactory().createToolBar();
toolbar.setName(getText());
bindMembers(toolbar, toolbar, buttonFactory, getToolBarButtonConfigurer());
toolbar.setEnabled(false);
toolbar.setVisible(true);
return toolbar;
}
public JMenuBar createMenuBar() {
return createMenuBar(getMenuFactory());
}
public JMenuBar createMenuBar(MenuFactory factory) {
JMenuBar menubar = factory.createMenuBar();
bindMembers(menubar, menubar, factory, getMenuItemButtonConfigurer());
return menubar;
}
/**
* Create a button bar with buttons for all the commands in this group.
*
* @return never null
*/
public JComponent createButtonBar() {
return createButtonBar(null);
}
/**
* Create a button bar with buttons for all the commands in this group. Adds
* a border top and bottom of 2 spaces.
*
* @param minimumButtonSize if null, then there is no minimum size
*
* @return never null
*/
public JComponent createButtonBar(Size minimumButtonSize) {
return createButtonBar(minimumButtonSize, GuiStandardUtils.createTopAndBottomBorder(UIConstants.TWO_SPACES));
}
/**
* Create a button bar with buttons for all the commands in this.
*
* @param columnSpec Custom columnSpec for each column containing a button,
* can be <code>null</code>.
* @param rowSpec Custom rowspec for the buttonbar, can be <code>null</code>.
* @return never null
*/
public JComponent createButtonBar(final ColumnSpec columnSpec, final RowSpec rowSpec) {
return createButtonBar(columnSpec, rowSpec, null);
}
/**
* Create a button bar with buttons for all the commands in this.
*
* @param minimumButtonSize if null, then there is no minimum size
* @param border if null, then don't use a border
*
* @return never null
*/
public JComponent createButtonBar(final Size minimumButtonSize, final Border border) {
return createButtonBar(minimumButtonSize == null ? null : new ColumnSpec(minimumButtonSize), null, border);
}
/**
* Create a button bar with buttons for all the commands in this.
*
* @param columnSpec Custom columnSpec for each column containing a button,
* can be <code>null</code>.
* @param rowSpec Custom rowspec for the buttonbar, can be <code>null</code>.
* @param border if null, then don't use a border
*
* @return never null
*/
public JComponent createButtonBar(final ColumnSpec columnSpec, final RowSpec rowSpec, final Border border) {
final ButtonBarGroupContainerPopulator container = new ButtonBarGroupContainerPopulator();
container.setColumnSpec(columnSpec);
container.setRowSpec(rowSpec);
addCommandsToGroupContainer(container);
return GuiStandardUtils.attachBorder(container.getButtonBar(), border);
}
/**
* Create a button stack with buttons for all the commands.
*
* @return never null
*/
public JComponent createButtonStack() {
return createButtonStack(null);
}
/**
* Create a button stack with buttons for all the commands. Adds a border
* left and right of 2 spaces.
*
* @param minimumButtonSize Minimum size of the buttons (can be null)
* @return never null
*/
public JComponent createButtonStack(final Size minimumButtonSize) {
return createButtonStack(minimumButtonSize, GuiStandardUtils.createLeftAndRightBorder(UIConstants.TWO_SPACES));
}
/**
* Create a button stack with buttons for all the commands.
*
* @param minimumButtonSize Minimum size of the buttons (can be
* <code>null</code>)
* @param border Border to set around the stack.
* @return never null
*/
public JComponent createButtonStack(final Size minimumButtonSize, final Border border) {
return createButtonStack(minimumButtonSize == null ? null : new ColumnSpec(minimumButtonSize), null, border);
}
/**
* Create a button stack with buttons for all the commands.
*
* @param columnSpec Custom columnSpec for the stack, can be
* <code>null</code>.
* @param rowSpec Custom rowspec for each row containing a button can be
* <code>null</code>.
* @return never null
*/
public JComponent createButtonStack(final ColumnSpec columnSpec, final RowSpec rowSpec) {
return createButtonStack(columnSpec, rowSpec, null);
}
/**
* Create a button stack with buttons for all the commands.
*
* @param columnSpec Custom columnSpec for the stack, can be
* <code>null</code>.
* @param rowSpec Custom rowspec for each row containing a button can be
* <code>null</code>.
* @param border Border to set around the stack.
* @return never null
*/
public JComponent createButtonStack(final ColumnSpec columnSpec, final RowSpec rowSpec, final Border border) {
final ButtonStackGroupContainerPopulator container = new ButtonStackGroupContainerPopulator();
container.setColumnSpec(columnSpec);
container.setRowSpec(rowSpec);
addCommandsToGroupContainer(container);
return GuiStandardUtils.attachBorder(container.getButtonStack(), border);
}
/**
* Create a container with the given GroupContainerPopulator which will hold
* the members of this group.
*
* @param groupContainerPopulator
*/
protected void addCommandsToGroupContainer(final GroupContainerPopulator groupContainerPopulator) {
final Iterator members = getMemberList().iterator();
while (members.hasNext()) {
final GroupMember member = (GroupMember) members.next();
if (member.getCommand() instanceof CommandGroup) {
member.fill(groupContainerPopulator, getButtonFactory(), getPullDownMenuButtonConfigurer(),
Collections.EMPTY_LIST);
}
else {
member.fill(groupContainerPopulator, getButtonFactory(), getDefaultButtonConfigurer(),
Collections.EMPTY_LIST);
}
}
groupContainerPopulator.onPopulated();
}
private void bindMembers(Object owner, Container memberContainer, Object controlFactory,
CommandButtonConfigurer configurer) {
getMemberList().bindMembers(owner, new SimpleGroupContainerPopulator(memberContainer), controlFactory,
configurer);
}
public void addGroupListener(CommandGroupListener l) {
if (listenerList == null) {
listenerList = new EventListenerList();
}
listenerList.add(CommandGroupListener.class, l);
}
public void removeGroupListener(CommandGroupListener l) {
Assert.notNull(listenerList, "Listener list has not yet been instantiated!");
listenerList.remove(CommandGroupListener.class, l);
}
protected void fireMembersChanged() {
if (listenerList == null) {
return;
}
CommandGroupEvent event = null;
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == CommandGroupListener.class) {
if (event == null) {
event = new CommandGroupEvent(this);
}
((CommandGroupListener) listeners[i + 1]).membersChanged(event);
}
}
}
}