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 }