001 /*
002 * Copyright 2002-2004 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.springframework.richclient.command;
017
018 import com.jgoodies.forms.layout.ColumnSpec;
019 import com.jgoodies.forms.layout.RowSpec;
020 import com.jgoodies.forms.layout.Size;
021 import org.springframework.richclient.application.ApplicationServicesLocator;
022 import org.springframework.richclient.command.config.CommandButtonConfigurer;
023 import org.springframework.richclient.command.config.CommandConfigurer;
024 import org.springframework.richclient.command.config.CommandFaceDescriptor;
025 import org.springframework.richclient.command.support.ButtonBarGroupContainerPopulator;
026 import org.springframework.richclient.command.support.ButtonStackGroupContainerPopulator;
027 import org.springframework.richclient.command.support.SimpleGroupContainerPopulator;
028 import org.springframework.richclient.command.support.ToggleButtonPopupListener;
029 import org.springframework.richclient.core.UIConstants;
030 import org.springframework.richclient.factory.ButtonFactory;
031 import org.springframework.richclient.factory.MenuFactory;
032 import org.springframework.richclient.util.GuiStandardUtils;
033 import org.springframework.util.Assert;
034 import org.springframework.util.ObjectUtils;
035 import org.springframework.util.StringUtils;
036
037 import javax.swing.*;
038 import javax.swing.border.Border;
039 import javax.swing.event.EventListenerList;
040 import java.awt.*;
041 import java.util.Collections;
042 import java.util.Iterator;
043 import java.util.List;
044
045 /**
046 * Implementation of an {@link AbstractCommand} that groups a collection of
047 * commands. This can be used to construct a menu with all kinds of sub menus.
048 *
049 * @author Keith Donald
050 */
051 public class CommandGroup extends AbstractCommand {
052
053 private EventListenerList listenerList;
054
055 private GroupMemberList memberList = new GroupMemberList();
056
057 private CommandRegistry commandRegistry;
058
059 /**
060 * @see AbstractCommand#AbstractCommand()
061 */
062 public CommandGroup() {
063 super();
064 }
065
066 /**
067 * @see AbstractCommand#AbstractCommand(String)
068 */
069 public CommandGroup(String groupId) {
070 super(groupId);
071 }
072
073 /**
074 * @see AbstractCommand#AbstractCommand(String, CommandFaceDescriptor)
075 */
076 public CommandGroup(String groupId, CommandFaceDescriptor face) {
077 super(groupId, face);
078 }
079
080 /**
081 * Constructor with id for configuration and the {@link CommandRegistry} to use.
082 *
083 * @see AbstractCommand#AbstractCommand(String)
084 */
085 public CommandGroup(String groupId, CommandRegistry commandRegistry) {
086 super(groupId);
087 setCommandRegistry(commandRegistry);
088 }
089
090 /**
091 * @see AbstractCommand#AbstractCommand(String, String)
092 */
093 public CommandGroup(String id, String encodedLabel) {
094 super(id, encodedLabel);
095 }
096
097 /**
098 * @see AbstractCommand#AbstractCommand(String, String, Icon, String)
099 */
100 public CommandGroup(String id, String encodedLabel, Icon icon, String caption) {
101 super(id, encodedLabel, icon, caption);
102 }
103
104 /**
105 * Creates a command group with a single command member.
106 *
107 * @param member the command to put in the CommandGroup
108 * @return a {@link CommandGroup} which contains the given command.
109 */
110 public static CommandGroup createCommandGroup(AbstractCommand member) {
111 return createCommandGroup(null, new Object[] { member });
112 }
113
114 /**
115 * Create a command group which holds all the given members.
116 *
117 * @param members members to add to the group.
118 * @return a {@link CommandGroup} which contains all the members.
119 */
120 public static CommandGroup createCommandGroup(Object[] members) {
121 return createCommandGroup(null, members, false, null);
122 }
123
124 /**
125 * Create a command group which holds all the given members.
126 *
127 * @param members members to add to the group.
128 * @return a {@link CommandGroup} which contains all the members.
129 */
130 public static CommandGroup createCommandGroup(List<? extends AbstractCommand> members) {
131 return createCommandGroup(null, members.toArray(), false, null);
132 }
133
134 /**
135 * Create a command group which holds all the given members.
136 *
137 * @param groupId the id to configure the group.
138 * @param members members to add to the group.
139 * @return a {@link CommandGroup} which contains all the members.
140 */
141 public static CommandGroup createCommandGroup(String groupId, Object[] members) {
142 return createCommandGroup(groupId, members, false, null);
143 }
144
145 /**
146 * Create a command group which holds all the given members.
147 *
148 * @param groupId the id to configure the group.
149 * @param members members to add to the group.
150 * @return a {@link CommandGroup} which contains all the members.
151 */
152 public static CommandGroup createCommandGroup(String groupId, List<? extends AbstractCommand> members) {
153 return createCommandGroup(groupId, members.toArray(), false, null);
154 }
155
156 /**
157 * Create a command group which holds all the given members.
158 *
159 * @param groupId the id to configure the group.
160 * @param members members to add to the group.
161 * @param configurer the configurer to use.
162 * @return a {@link CommandGroup} which contains all the members.
163 */
164 public static CommandGroup createCommandGroup(String groupId, Object[] members, CommandConfigurer configurer) {
165 return createCommandGroup(groupId, members, false, configurer);
166 }
167
168 public static CommandGroup createExclusiveCommandGroup(Object[] members) {
169 return createCommandGroup(null, members, true, null);
170 }
171
172 public static CommandGroup createExclusiveCommandGroup(String groupId, Object[] members) {
173 return createCommandGroup(groupId, members, true, null);
174 }
175
176 public static CommandGroup createExclusiveCommandGroup(String groupId, Object[] members,
177 CommandConfigurer configurer) {
178 return createCommandGroup(groupId, members, true, configurer);
179 }
180
181 /**
182 * Creates a command group, configuring the group using the ObjectConfigurer
183 * service (pulling visual configuration properties from an external
184 * source). This method will also auto-configure contained Command members
185 * that have not yet been configured.
186 *
187 * @param groupId id to configure this commandGroup.
188 * @param members members to add to the group.
189 * @return a {@link CommandGroup} that holds the given members.
190 */
191 private static CommandGroup createCommandGroup(final String groupId, final Object[] members,
192 final boolean exclusive, final CommandConfigurer configurer) {
193 final CommandConfigurer theConfigurer = (configurer != null) ? configurer
194 : (CommandConfigurer) ApplicationServicesLocator.services().getService(CommandConfigurer.class);
195
196 final CommandGroupFactoryBean groupFactory = new CommandGroupFactoryBean(groupId, null, theConfigurer, members);
197 groupFactory.setExclusive(exclusive);
198 return groupFactory.getCommandGroup();
199 }
200
201 protected void addInternal(AbstractCommand command) {
202 this.memberList.add(new SimpleGroupMember(this, command));
203 }
204
205 protected void addLazyInternal(String commandId) {
206 this.memberList.add(new LazyGroupMember(this, commandId));
207 }
208
209 protected void addSeparatorInternal() {
210 this.memberList.add(new SeparatorGroupMember());
211 }
212
213 protected void addGlueInternal() {
214 this.memberList.add(new GlueGroupMember());
215 }
216
217 protected void addComponentInternal(Component component) {
218 this.memberList.add(new ComponentGroupMember(component));
219 }
220
221 public final void setCommandRegistry(CommandRegistry registry) {
222 if (!ObjectUtils.nullSafeEquals(this.commandRegistry, registry)) {
223
224 // @TODO should groups listen to command registration events if
225 // they've
226 // got lazy members that haven't been instantiated? Or are
227 // targetable
228 // commands lightweight enough?
229 if (logger.isDebugEnabled()) {
230 logger.debug("Setting registry " + registry + " for command group '" + getId() + "'");
231 }
232 this.commandRegistry = registry;
233 }
234 }
235
236 /**
237 * Enable/disable the entire group.
238 *
239 * @param enabled enable/disable this group.
240 */
241 public void setEnabled(boolean enabled) {
242 super.setEnabled(enabled);
243 for (Iterator members = getMemberList().iterator(); members.hasNext();)
244 {
245 GroupMember groupMember = (GroupMember)members.next();
246 groupMember.setEnabled(enabled);
247 }
248 }
249
250 public void setVisible(boolean visible) {
251 super.setVisible(visible);
252 getMemberList().setContainersVisible(visible);
253 }
254
255 protected CommandRegistry getCommandRegistry() {
256 return commandRegistry;
257 }
258
259 public void add(AbstractCommand command) {
260 add(command, true);
261 }
262
263 public void add(AbstractCommand command, boolean rebuild) {
264 if (command == null) {
265 return;
266 }
267 if (contains(command)) {
268 return;
269 }
270 getMemberList().append(new SimpleGroupMember(this, command));
271 rebuildIfNecessary(rebuild);
272 }
273
274 public void add(String groupMemberPath, AbstractCommand command) {
275 AbstractCommand c = find(groupMemberPath);
276 assertIsGroup(groupMemberPath, c);
277 ((CommandGroup) c).add(command);
278 }
279
280 private void assertIsGroup(String groupMemberPath, AbstractCommand c) {
281 Assert.notNull(c, "Command at path '" + groupMemberPath + "' does not exist.");
282 Assert.isTrue((c instanceof CommandGroup), "Command at path '" + groupMemberPath + "' is not a group.");
283 }
284
285 public void add(String groupMemberPath, AbstractCommand command, boolean rebuild) {
286 AbstractCommand c = find(groupMemberPath);
287 assertIsGroup(groupMemberPath, c);
288 ((CommandGroup) c).add(command, rebuild);
289 }
290
291 public void remove(AbstractCommand command) {
292 remove(command, true);
293 }
294
295 public void remove(AbstractCommand command, boolean rebuild) {
296 if (command == null) {
297 return;
298 }
299 ExpansionPointGroupMember expansionPoint = getMemberList().getExpansionPoint();
300 GroupMember member = expansionPoint.getMemberFor(command.getId());
301 if (member != null) {
302 expansionPoint.remove(member);
303 rebuildIfNecessary(rebuild);
304 }
305 }
306
307 public void remove(String groupMemberPath, AbstractCommand command) {
308 AbstractCommand c = find(groupMemberPath);
309 assertIsGroup(groupMemberPath, c);
310 ((CommandGroup) c).remove(command);
311 }
312
313 public void remove(String groupMemberPath, AbstractCommand command, boolean rebuild) {
314 AbstractCommand c = find(groupMemberPath);
315 assertIsGroup(groupMemberPath, c);
316 ((CommandGroup) c).remove(command, rebuild);
317 }
318
319 public void addSeparator() {
320 addSeparator(true);
321 }
322
323 public void addSeparator(boolean rebuild) {
324 getMemberList().append(new SeparatorGroupMember());
325 rebuildIfNecessary(rebuild);
326 }
327
328 public void addGlue() {
329 addGlue(true);
330 }
331
332 public void addGlue(boolean rebuild) {
333 getMemberList().append(new GlueGroupMember());
334 rebuildIfNecessary(rebuild);
335 }
336
337 private void rebuildIfNecessary(boolean rebuild) {
338 if (rebuild) {
339 rebuildAllControls();
340 fireMembersChanged();
341 }
342 }
343
344 protected void rebuildAllControls() {
345 if (logger.isDebugEnabled()) {
346 logger.debug("Rebuilding all GUI controls for command group '" + getId() + "'");
347 }
348 getMemberList().rebuildControls();
349 }
350
351 protected GroupMemberList getMemberList() {
352 return memberList;
353 }
354
355 protected Iterator memberIterator() {
356 return getMemberList().iterator();
357 }
358
359 public int size() {
360 return getMemberCount();
361 }
362
363 public boolean isAllowedMember(AbstractCommand proposedMember) {
364 return true;
365 }
366
367 /**
368 * Search for and return the command contained by this group with the
369 * specified path. Nested paths should be deliniated by the "/" character;
370 * for example, "fileGroup/newGroup/simpleFileCommand". The returned command
371 * may be a group or an action command.
372 *
373 * @param memberPath the path of the command, with nested levels deliniated
374 * by the "/" path separator.
375 * @return the command at the specified member path, or <code>null</code>
376 * if no was command found.
377 */
378 public AbstractCommand find(String memberPath) {
379 if (logger.isDebugEnabled()) {
380 logger.debug("Searching for command with nested path '" + memberPath + "'");
381 }
382 String[] paths = StringUtils.delimitedListToStringArray(memberPath, "/");
383 CommandGroup currentGroup = this;
384 // fileGroup/newGroup/newJavaProject
385 for (int i = 0; i < paths.length; i++) {
386 String memberId = paths[i];
387 if (i < paths.length - 1) {
388 // must be a nested group
389 currentGroup = currentGroup.findCommandGroupMember(memberId);
390 }
391 else {
392 // is last path element; can be a group or action
393 return currentGroup.findCommandMember(memberId);
394 }
395 }
396 return null;
397 }
398
399 private CommandGroup findCommandGroupMember(String groupId) {
400 AbstractCommand c = findCommandMember(groupId);
401 Assert.isTrue((c instanceof CommandGroup), "Command with id '" + groupId + "' is not a group.");
402 return (CommandGroup) c;
403 }
404
405 private AbstractCommand findCommandMember(String commandId) {
406 Iterator it = memberList.iterator();
407 while (it.hasNext()) {
408 GroupMember member = (GroupMember) it.next();
409 if (member.managesCommand(commandId)) {
410 return member.getCommand();
411 }
412 }
413 logger.warn("No command with id '" + commandId + "' is nested within this group (" + getId()
414 + "); returning null");
415 return null;
416 }
417
418 /**
419 * Executes all the members of this group.
420 */
421 public void execute() {
422 Iterator it = memberList.iterator();
423 while (it.hasNext()) {
424 GroupMember member = (GroupMember) it.next();
425 member.getCommand().execute();
426 }
427 }
428
429 public int getMemberCount() {
430 return getMemberList().size();
431 }
432
433 public boolean contains(AbstractCommand command) {
434 return getMemberList().contains(command);
435 }
436
437 public void reset() {
438 ExpansionPointGroupMember expansionPoint = getMemberList().getExpansionPoint();
439 if (!expansionPoint.isEmpty()) {
440 expansionPoint.clear();
441 rebuildIfNecessary(true);
442 }
443 }
444
445 public AbstractButton createButton(String faceDescriptorId, ButtonFactory buttonFactory,
446 CommandButtonConfigurer buttonConfigurer) {
447 return createButton(getDefaultFaceDescriptorId(), buttonFactory, getMenuFactory(), buttonConfigurer);
448 }
449
450 public AbstractButton createButton(ButtonFactory buttonFactory, MenuFactory menuFactory) {
451 return createButton(getDefaultFaceDescriptorId(), buttonFactory, menuFactory, getPullDownMenuButtonConfigurer());
452 }
453
454 public AbstractButton createButton(String faceDescriptorId, ButtonFactory buttonFactory, MenuFactory menuFactory) {
455 return createButton(faceDescriptorId, buttonFactory, menuFactory, getPullDownMenuButtonConfigurer());
456 }
457
458 public AbstractButton createButton(ButtonFactory buttonFactory, MenuFactory menuFactory,
459 CommandButtonConfigurer buttonConfigurer) {
460 return createButton(getDefaultFaceDescriptorId(), buttonFactory, menuFactory, buttonConfigurer);
461 }
462
463 public AbstractButton createButton(String faceDescriptorId, ButtonFactory buttonFactory, MenuFactory menuFactory,
464 CommandButtonConfigurer buttonConfigurer) {
465 AbstractButton button = buttonFactory.createToggleButton();
466 attach(button, buttonConfigurer);
467 JPopupMenu popup = menuFactory.createPopupMenu();
468 bindMembers(button, popup, menuFactory, getMenuItemButtonConfigurer());
469 ToggleButtonPopupListener.bind(button, popup);
470 return button;
471 }
472
473 protected CommandButtonConfigurer getPullDownMenuButtonConfigurer() {
474 return getCommandServices().getPullDownMenuButtonConfigurer();
475 }
476
477 public JMenuItem createMenuItem(String faceDescriptorId, MenuFactory factory,
478 CommandButtonConfigurer buttonConfigurer) {
479 JMenu menu = factory.createMenu();
480 attach(menu);
481 bindMembers(menu, menu, factory, buttonConfigurer);
482 return menu;
483 }
484
485 public JPopupMenu createPopupMenu() {
486 return createPopupMenu(getMenuFactory());
487 }
488
489 public JPopupMenu createPopupMenu(MenuFactory factory) {
490 JPopupMenu popup = factory.createPopupMenu();
491 bindMembers(popup, popup, factory, getMenuItemButtonConfigurer());
492 return popup;
493 }
494
495 public JComponent createToolBar() {
496 return createToolBar(getToolBarButtonFactory());
497 }
498
499 public JComponent createToolBar(ButtonFactory buttonFactory) {
500 JComponent toolbar = getComponentFactory().createToolBar();
501 toolbar.setName(getText());
502 bindMembers(toolbar, toolbar, buttonFactory, getToolBarButtonConfigurer());
503 toolbar.setEnabled(false);
504 toolbar.setVisible(true);
505 return toolbar;
506 }
507
508 public JMenuBar createMenuBar() {
509 return createMenuBar(getMenuFactory());
510 }
511
512 public JMenuBar createMenuBar(MenuFactory factory) {
513 JMenuBar menubar = factory.createMenuBar();
514 bindMembers(menubar, menubar, factory, getMenuItemButtonConfigurer());
515 return menubar;
516 }
517
518 /**
519 * Create a button bar with buttons for all the commands in this group.
520 *
521 * @return never null
522 */
523 public JComponent createButtonBar() {
524 return createButtonBar(null);
525 }
526
527 /**
528 * Create a button bar with buttons for all the commands in this group. Adds
529 * a border top and bottom of 2 spaces.
530 *
531 * @param minimumButtonSize if null, then there is no minimum size
532 *
533 * @return never null
534 */
535 public JComponent createButtonBar(Size minimumButtonSize) {
536 return createButtonBar(minimumButtonSize, GuiStandardUtils.createTopAndBottomBorder(UIConstants.TWO_SPACES));
537 }
538
539 /**
540 * Create a button bar with buttons for all the commands in this.
541 *
542 * @param columnSpec Custom columnSpec for each column containing a button,
543 * can be <code>null</code>.
544 * @param rowSpec Custom rowspec for the buttonbar, can be <code>null</code>.
545 * @return never null
546 */
547 public JComponent createButtonBar(final ColumnSpec columnSpec, final RowSpec rowSpec) {
548 return createButtonBar(columnSpec, rowSpec, null);
549 }
550
551 /**
552 * Create a button bar with buttons for all the commands in this.
553 *
554 * @param minimumButtonSize if null, then there is no minimum size
555 * @param border if null, then don't use a border
556 *
557 * @return never null
558 */
559 public JComponent createButtonBar(final Size minimumButtonSize, final Border border) {
560 return createButtonBar(minimumButtonSize == null ? null : new ColumnSpec(minimumButtonSize), null, border);
561 }
562
563 /**
564 * Create a button bar with buttons for all the commands in this.
565 *
566 * @param columnSpec Custom columnSpec for each column containing a button,
567 * can be <code>null</code>.
568 * @param rowSpec Custom rowspec for the buttonbar, can be <code>null</code>.
569 * @param border if null, then don't use a border
570 *
571 * @return never null
572 */
573 public JComponent createButtonBar(final ColumnSpec columnSpec, final RowSpec rowSpec, final Border border) {
574 final ButtonBarGroupContainerPopulator container = new ButtonBarGroupContainerPopulator();
575 container.setColumnSpec(columnSpec);
576 container.setRowSpec(rowSpec);
577 addCommandsToGroupContainer(container);
578 return GuiStandardUtils.attachBorder(container.getButtonBar(), border);
579 }
580
581 /**
582 * Create a button stack with buttons for all the commands.
583 *
584 * @return never null
585 */
586 public JComponent createButtonStack() {
587 return createButtonStack(null);
588 }
589
590 /**
591 * Create a button stack with buttons for all the commands. Adds a border
592 * left and right of 2 spaces.
593 *
594 * @param minimumButtonSize Minimum size of the buttons (can be null)
595 * @return never null
596 */
597 public JComponent createButtonStack(final Size minimumButtonSize) {
598 return createButtonStack(minimumButtonSize, GuiStandardUtils.createLeftAndRightBorder(UIConstants.TWO_SPACES));
599 }
600
601 /**
602 * Create a button stack with buttons for all the commands.
603 *
604 * @param minimumButtonSize Minimum size of the buttons (can be
605 * <code>null</code>)
606 * @param border Border to set around the stack.
607 * @return never null
608 */
609 public JComponent createButtonStack(final Size minimumButtonSize, final Border border) {
610 return createButtonStack(minimumButtonSize == null ? null : new ColumnSpec(minimumButtonSize), null, border);
611 }
612
613 /**
614 * Create a button stack with buttons for all the commands.
615 *
616 * @param columnSpec Custom columnSpec for the stack, can be
617 * <code>null</code>.
618 * @param rowSpec Custom rowspec for each row containing a button can be
619 * <code>null</code>.
620 * @return never null
621 */
622 public JComponent createButtonStack(final ColumnSpec columnSpec, final RowSpec rowSpec) {
623 return createButtonStack(columnSpec, rowSpec, null);
624 }
625
626 /**
627 * Create a button stack with buttons for all the commands.
628 *
629 * @param columnSpec Custom columnSpec for the stack, can be
630 * <code>null</code>.
631 * @param rowSpec Custom rowspec for each row containing a button can be
632 * <code>null</code>.
633 * @param border Border to set around the stack.
634 * @return never null
635 */
636 public JComponent createButtonStack(final ColumnSpec columnSpec, final RowSpec rowSpec, final Border border) {
637 final ButtonStackGroupContainerPopulator container = new ButtonStackGroupContainerPopulator();
638 container.setColumnSpec(columnSpec);
639 container.setRowSpec(rowSpec);
640 addCommandsToGroupContainer(container);
641 return GuiStandardUtils.attachBorder(container.getButtonStack(), border);
642 }
643
644 /**
645 * Create a container with the given GroupContainerPopulator which will hold
646 * the members of this group.
647 *
648 * @param groupContainerPopulator
649 */
650 protected void addCommandsToGroupContainer(final GroupContainerPopulator groupContainerPopulator) {
651 final Iterator members = getMemberList().iterator();
652
653 while (members.hasNext()) {
654 final GroupMember member = (GroupMember) members.next();
655 if (member.getCommand() instanceof CommandGroup) {
656 member.fill(groupContainerPopulator, getButtonFactory(), getPullDownMenuButtonConfigurer(),
657 Collections.EMPTY_LIST);
658 }
659 else {
660 member.fill(groupContainerPopulator, getButtonFactory(), getDefaultButtonConfigurer(),
661 Collections.EMPTY_LIST);
662 }
663 }
664 groupContainerPopulator.onPopulated();
665 }
666
667 private void bindMembers(Object owner, Container memberContainer, Object controlFactory,
668 CommandButtonConfigurer configurer) {
669 getMemberList().bindMembers(owner, new SimpleGroupContainerPopulator(memberContainer), controlFactory,
670 configurer);
671 }
672
673 public void addGroupListener(CommandGroupListener l) {
674 if (listenerList == null) {
675 listenerList = new EventListenerList();
676 }
677 listenerList.add(CommandGroupListener.class, l);
678 }
679
680 public void removeGroupListener(CommandGroupListener l) {
681 Assert.notNull(listenerList, "Listener list has not yet been instantiated!");
682 listenerList.remove(CommandGroupListener.class, l);
683 }
684
685 protected void fireMembersChanged() {
686 if (listenerList == null) {
687 return;
688 }
689 CommandGroupEvent event = null;
690 Object[] listeners = listenerList.getListenerList();
691 for (int i = listeners.length - 2; i >= 0; i -= 2) {
692 if (listeners[i] == CommandGroupListener.class) {
693 if (event == null) {
694 event = new CommandGroupEvent(this);
695 }
696 ((CommandGroupListener) listeners[i + 1]).membersChanged(event);
697 }
698 }
699 }
700 }