001 /* 002 * Copyright 2002-2004 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy of 006 * 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, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016 package org.springframework.richclient.factory; 017 018 import java.awt.Component; 019 import java.awt.LayoutManager; 020 import java.beans.PropertyChangeEvent; 021 import java.beans.PropertyChangeListener; 022 import java.util.ArrayList; 023 import java.util.Collection; 024 025 import javax.swing.AbstractButton; 026 import javax.swing.BorderFactory; 027 import javax.swing.Icon; 028 import javax.swing.JButton; 029 import javax.swing.JCheckBox; 030 import javax.swing.JComboBox; 031 import javax.swing.JComponent; 032 import javax.swing.JFormattedTextField; 033 import javax.swing.JLabel; 034 import javax.swing.JList; 035 import javax.swing.JMenuItem; 036 import javax.swing.JPanel; 037 import javax.swing.JPasswordField; 038 import javax.swing.JRadioButton; 039 import javax.swing.JScrollPane; 040 import javax.swing.JTabbedPane; 041 import javax.swing.JTable; 042 import javax.swing.JTextArea; 043 import javax.swing.JTextField; 044 import javax.swing.JToggleButton; 045 import javax.swing.JToolBar; 046 import javax.swing.JFormattedTextField.AbstractFormatterFactory; 047 import javax.swing.table.TableModel; 048 049 import org.apache.commons.logging.Log; 050 import org.apache.commons.logging.LogFactory; 051 import org.springframework.binding.value.ValueModel; 052 import org.springframework.context.MessageSource; 053 import org.springframework.context.MessageSourceAware; 054 import org.springframework.context.MessageSourceResolvable; 055 import org.springframework.context.NoSuchMessageException; 056 import org.springframework.context.support.MessageSourceAccessor; 057 import org.springframework.core.enums.LabeledEnum; 058 import org.springframework.core.enums.LabeledEnumResolver; 059 import org.springframework.richclient.application.ApplicationServicesLocator; 060 import org.springframework.richclient.command.config.CommandButtonLabelInfo; 061 import org.springframework.richclient.components.PatchedJFormattedTextField; 062 import org.springframework.richclient.core.LabelInfo; 063 import org.springframework.richclient.core.UIConstants; 064 import org.springframework.richclient.image.IconSource; 065 import org.springframework.richclient.list.ComboBoxListModel; 066 import org.springframework.richclient.list.LabeledEnumComboBoxEditor; 067 import org.springframework.richclient.list.LabeledEnumListRenderer; 068 import org.springframework.richclient.util.Alignment; 069 import org.springframework.richclient.util.GuiStandardUtils; 070 import org.springframework.util.comparator.ComparableComparator; 071 import org.springframework.util.comparator.CompoundComparator; 072 073 /** 074 * Default component factory implementation that delegates to JGoodies component 075 * factory. 076 * 077 * @author Keith Donald 078 */ 079 public class DefaultComponentFactory implements ComponentFactory, MessageSourceAware { 080 081 private final Log logger = LogFactory.getLog(getClass()); 082 083 private MessageSourceAccessor messages; 084 085 private IconSource iconSource; 086 087 private ButtonFactory buttonFactory; 088 089 private MenuFactory menuFactory; 090 091 private LabeledEnumResolver enumResolver; 092 093 private MessageSource messageSource; 094 095 private TableFactory tableFactory; 096 097 private int textFieldColumns = 25; 098 099 /** 100 * {@inheritDoc} 101 */ 102 public void setMessageSource(MessageSource messageSource) { 103 this.messageSource = messageSource; 104 this.messages = new MessageSourceAccessor(messageSource); 105 } 106 107 /** 108 * Set the source for retrieving icons. 109 */ 110 public void setIconSource(IconSource iconSource) { 111 this.iconSource = iconSource; 112 } 113 114 /** 115 * Set the button factory. 116 */ 117 public void setButtonFactory(ButtonFactory buttonFactory) { 118 this.buttonFactory = buttonFactory; 119 } 120 121 /** 122 * Set the menu factory. 123 */ 124 public void setMenuFactory(MenuFactory menuFactory) { 125 this.menuFactory = menuFactory; 126 } 127 128 /** 129 * Set the resolver used to create messages for enumerations. 130 * 131 * @see LabeledEnum 132 */ 133 public void setEnumResolver(LabeledEnumResolver enumResolver) { 134 this.enumResolver = enumResolver; 135 } 136 137 /** 138 * Returns the resolver used for enumerations. Uses the 139 * {@link ApplicationServicesLocator} to find one if no resolver is 140 * explicitly set. 141 */ 142 protected LabeledEnumResolver getEnumResolver() { 143 if (enumResolver == null) { 144 enumResolver = (LabeledEnumResolver) ApplicationServicesLocator.services().getService( 145 LabeledEnumResolver.class); 146 } 147 return enumResolver; 148 } 149 150 /** 151 * {@inheritDoc} 152 */ 153 public JLabel createLabel(String labelKey) { 154 JLabel label = createNewLabel(); 155 getLabelInfo(getRequiredMessage(labelKey)).configureLabel(label); 156 return label; 157 } 158 159 /** 160 * {@inheritDoc} 161 */ 162 public JLabel createLabel(String[] labelKeys) { 163 JLabel label = createNewLabel(); 164 getLabelInfo(getRequiredMessage(labelKeys)).configureLabel(label); 165 return label; 166 } 167 168 /** 169 * {@inheritDoc} 170 */ 171 public JLabel createLabel(String labelKey, Object[] arguments) { 172 JLabel label = createNewLabel(); 173 getLabelInfo(getRequiredMessage(labelKey, arguments)).configureLabel(label); 174 return label; 175 } 176 177 /** 178 * Parse the given label to create a {@link LabelInfo}. 179 * 180 * @param label The label to parse. 181 * @return a {@link LabelInfo} representing the label. 182 * @see LabelInfo#valueOf(String) 183 */ 184 protected LabelInfo getLabelInfo(String label) { 185 return LabelInfo.valueOf(label); 186 } 187 188 /** 189 * Get the message for the given key. Don't throw an exception if it's not 190 * found but return a default value. 191 * 192 * @param messageKey Key to lookup the message. 193 * @return the message found in the resources or a default message. 194 */ 195 protected String getRequiredMessage(String messageKey) { 196 return getRequiredMessage(messageKey, null); 197 } 198 199 /** 200 * Get the message for the given key. Don't throw an exception if it's not 201 * found but return a default value. 202 * 203 * @param messageKeys The keys to use when looking for the message. 204 * @return the message found in the resources or a default message. 205 */ 206 protected String getRequiredMessage(final String[] messageKeys) { 207 MessageSourceResolvable resolvable = new MessageSourceResolvable() { 208 209 public String[] getCodes() { 210 return messageKeys; 211 } 212 213 public Object[] getArguments() { 214 return null; 215 } 216 217 public String getDefaultMessage() { 218 if (messageKeys.length > 0) { 219 return messageKeys[0]; 220 } 221 return ""; 222 } 223 }; 224 return getMessages().getMessage(resolvable); 225 } 226 227 /** 228 * Returns the messageSourceAccessor. Uses the 229 * {@link ApplicationServicesLocator} to find one if no accessor is 230 * explicitly set. 231 */ 232 private MessageSourceAccessor getMessages() { 233 if (messages == null) { 234 messages = (MessageSourceAccessor) ApplicationServicesLocator.services().getService( 235 MessageSourceAccessor.class); 236 } 237 return messages; 238 } 239 240 /** 241 * {@inheritDoc} 242 */ 243 public JLabel createLabel(String labelKey, ValueModel[] argumentValueHolders) { 244 return new LabelTextRefresher(labelKey, argumentValueHolders).getLabel(); 245 } 246 247 private class LabelTextRefresher implements PropertyChangeListener { 248 249 private String labelKey; 250 251 private JLabel label; 252 253 private ValueModel[] argumentHolders; 254 255 public LabelTextRefresher(String labelKey, ValueModel[] argumentHolders) { 256 this.labelKey = labelKey; 257 this.argumentHolders = argumentHolders; 258 this.label = createNewLabel(); 259 subscribe(); 260 updateLabel(); 261 } 262 263 private void subscribe() { 264 for (int i = 0; i < argumentHolders.length; i++) { 265 ValueModel argHolder = argumentHolders[i]; 266 argHolder.addValueChangeListener(this); 267 } 268 } 269 270 public JLabel getLabel() { 271 return label; 272 } 273 274 public void propertyChange(PropertyChangeEvent evt) { 275 updateLabel(); 276 } 277 278 private void updateLabel() { 279 Object[] argValues = new Object[argumentHolders.length]; 280 for (int i = 0; i < argumentHolders.length; i++) { 281 ValueModel argHolder = argumentHolders[i]; 282 argValues[i] = argHolder.getValue(); 283 } 284 getLabelInfo(getRequiredMessage(labelKey, argValues)).configureLabel(label); 285 } 286 } 287 288 private String getRequiredMessage(String messageKey, Object[] args) { 289 try { 290 String message = getMessages().getMessage(messageKey, args); 291 return message; 292 } 293 catch (NoSuchMessageException e) { 294 return messageKey; 295 } 296 } 297 298 public JLabel createTitleLabel(String labelKey) { 299 return com.jgoodies.forms.factories.DefaultComponentFactory.getInstance().createTitle( 300 getRequiredMessage(labelKey)); 301 } 302 303 public JComponent createTitledBorderFor(String labelKey, JComponent component) { 304 component.setBorder(BorderFactory.createCompoundBorder(BorderFactory 305 .createTitledBorder(getRequiredMessage(labelKey)), GuiStandardUtils 306 .createEvenlySpacedBorder(UIConstants.ONE_SPACE))); 307 return component; 308 } 309 310 public JLabel createLabelFor(String labelKey, JComponent component) { 311 JLabel label = createNewLabel(); 312 getLabelInfo(getRequiredMessage(labelKey)).configureLabelFor(label, component); 313 return label; 314 } 315 316 public JLabel createLabelFor(String[] labelKeys, JComponent component) { 317 JLabel label = createNewLabel(); 318 getLabelInfo(getRequiredMessage(labelKeys)).configureLabelFor(label, component); 319 return label; 320 } 321 322 protected JLabel createNewLabel() { 323 return new JLabel(); 324 } 325 326 public JButton createButton(String labelKey) { 327 return (JButton) getButtonLabelInfo(getRequiredMessage(labelKey)).configure(getButtonFactory().createButton()); 328 } 329 330 protected CommandButtonLabelInfo getButtonLabelInfo(String label) { 331 return CommandButtonLabelInfo.valueOf(label); 332 } 333 334 protected ButtonFactory getButtonFactory() { 335 if (buttonFactory == null) { 336 buttonFactory = (ButtonFactory) ApplicationServicesLocator.services().getService(ButtonFactory.class); 337 } 338 return buttonFactory; 339 } 340 341 public JComponent createLabeledSeparator(String labelKey) { 342 return createLabeledSeparator(labelKey, Alignment.LEFT); 343 } 344 345 public JCheckBox createCheckBox(String labelKey) { 346 return (JCheckBox) getButtonLabelInfo(getRequiredMessage(labelKey)).configure(createNewCheckBox()); 347 } 348 349 public JCheckBox createCheckBox(String[] labelKeys) { 350 return (JCheckBox) getButtonLabelInfo(getRequiredMessage(labelKeys)).configure(createNewCheckBox()); 351 } 352 353 protected JCheckBox createNewCheckBox() { 354 return new JCheckBox(); 355 } 356 357 public JToggleButton createToggleButton(String labelKey) { 358 return (JToggleButton) getButtonLabelInfo(getRequiredMessage(labelKey)).configure(createNewToggleButton()); 359 } 360 361 public JToggleButton createToggleButton(String[] labelKeys) { 362 return (JToggleButton) getButtonLabelInfo(getRequiredMessage(labelKeys)).configure(createNewToggleButton()); 363 } 364 365 protected AbstractButton createNewToggleButton() { 366 return new JToggleButton(); 367 } 368 369 /* 370 * (non-Javadoc) 371 * @see org.springframework.richclient.factory.ComponentFactory#createRadioButton(java.lang.String) 372 */ 373 public JRadioButton createRadioButton(String labelKey) { 374 return (JRadioButton) getButtonLabelInfo(getRequiredMessage(labelKey)).configure(createNewRadioButton()); 375 } 376 377 protected JRadioButton createNewRadioButton() { 378 return new JRadioButton(); 379 } 380 381 public JRadioButton createRadioButton(String[] labelKeys) { 382 return (JRadioButton) getButtonLabelInfo(getRequiredMessage(labelKeys)).configure(createNewRadioButton()); 383 } 384 385 public JMenuItem createMenuItem(String labelKey) { 386 return (JMenuItem) getButtonLabelInfo(getRequiredMessage(labelKey)) 387 .configure(getMenuFactory().createMenuItem()); 388 } 389 390 protected MenuFactory getMenuFactory() { 391 if (menuFactory == null) { 392 menuFactory = (MenuFactory) ApplicationServicesLocator.services().getService(MenuFactory.class); 393 } 394 return menuFactory; 395 } 396 397 public JComponent createLabeledSeparator(String labelKey, Alignment alignment) { 398 return com.jgoodies.forms.factories.DefaultComponentFactory.getInstance().createSeparator( 399 getRequiredMessage(labelKey), ((Number) alignment.getCode()).intValue()); 400 } 401 402 public JList createList() { 403 return new JList(); 404 } 405 406 public JComboBox createComboBox() { 407 return new JComboBox(); 408 } 409 410 public JComboBox createComboBox(Class enumType) { 411 JComboBox comboBox = createComboBox(); 412 configureForEnum(comboBox, enumType); 413 return comboBox; 414 } 415 416 public JComboBox createListValueModelComboBox(ValueModel selectedItemValueModel, 417 ValueModel selectableItemsListHolder, String renderedPropertyPath) { 418 return null; 419 } 420 421 public void configureForEnum(JComboBox comboBox, Class enumType) { 422 Collection enumValues = getEnumResolver().getLabeledEnumSet(enumType); 423 if (logger.isDebugEnabled()) { 424 logger.debug("Populating combo box model with enums of type '" + enumType.getName() + "'; enums are [" 425 + enumValues + "]"); 426 } 427 CompoundComparator comparator = new CompoundComparator(); 428 comparator.addComparator(LabeledEnum.LABEL_ORDER); 429 comparator.addComparator(new ComparableComparator()); 430 comboBox.setModel(new ComboBoxListModel(new ArrayList(enumValues), comparator)); 431 comboBox.setRenderer(new LabeledEnumListRenderer(messageSource)); 432 comboBox.setEditor(new LabeledEnumComboBoxEditor(messageSource, comboBox.getEditor())); 433 } 434 435 /** 436 * Returns the default column count for new text fields (including formatted 437 * text and password fields) 438 * 439 * @return the default column count. Must not be lower than 0 440 * @see JTextField 441 */ 442 public int getTextFieldColumns() { 443 return textFieldColumns; 444 } 445 446 /** 447 * Defines the default column count for new text fields (including formatted 448 * text and password fields) 449 * 450 * @param the default column count. Must not be lower than 0 451 * @see JTextField 452 */ 453 public void setTextFieldColumns(int columns) { 454 if (columns < 0) 455 throw new IllegalArgumentException("text field columns must not be lower than 0. Value was: " + columns); 456 this.textFieldColumns = columns; 457 } 458 459 public JFormattedTextField createFormattedTextField(AbstractFormatterFactory formatterFactory) { 460 PatchedJFormattedTextField patchedJFormattedTextField = new PatchedJFormattedTextField(formatterFactory); 461 configureTextField(patchedJFormattedTextField); 462 return patchedJFormattedTextField; 463 } 464 465 public JTextField createTextField() { 466 JTextField textField = new JTextField(); 467 configureTextField(textField); 468 return textField; 469 } 470 471 /** 472 * Configures the text field. 473 * 474 * @param textField the field to configure. Must not be null 475 */ 476 protected void configureTextField(JTextField textField) { 477 textField.setColumns(getTextFieldColumns()); 478 } 479 480 public JPasswordField createPasswordField() { 481 JPasswordField passwordField = new JPasswordField(); 482 configureTextField(passwordField); 483 return passwordField; 484 } 485 486 public JTextArea createTextArea() { 487 return new JTextArea(); 488 } 489 490 public JTextArea createTextArea(int rows, int columns) { 491 JTextArea textArea = createTextArea(); 492 textArea.setRows(rows); 493 textArea.setColumns(columns); 494 return textArea; 495 } 496 497 public JTextArea createTextAreaAsLabel() { 498 return GuiStandardUtils.textAreaAsLabel(createTextArea()); 499 } 500 501 public JTabbedPane createTabbedPane() { 502 return new JTabbedPane(); 503 } 504 505 public void addConfiguredTab(JTabbedPane tabbedPane, String labelKey, JComponent tabComponent) { 506 org.springframework.richclient.core.LabelInfo info = getLabelInfo(getRequiredMessage(labelKey)); 507 tabbedPane.addTab(info.getText(), tabComponent); 508 int tabIndex = tabbedPane.getTabCount() - 1; 509 tabbedPane.setMnemonicAt(tabIndex, info.getMnemonic()); 510 tabbedPane.setDisplayedMnemonicIndexAt(tabIndex, info.getMnemonicIndex()); 511 tabbedPane.setIconAt(tabIndex, getIcon(labelKey)); 512 tabbedPane.setToolTipTextAt(tabIndex, getCaption(labelKey)); 513 } 514 515 public JScrollPane createScrollPane() { 516 return new JScrollPane(); 517 } 518 519 public JScrollPane createScrollPane(Component view) { 520 return new JScrollPane(view); 521 } 522 523 public JScrollPane createScrollPane(Component view, int vsbPolicy, int hsbPolicy) { 524 return new JScrollPane(view, vsbPolicy, hsbPolicy); 525 } 526 527 public JPanel createPanel() { 528 return new JPanel(); 529 } 530 531 public JPanel createPanel(LayoutManager layoutManager) { 532 return new JPanel(layoutManager); 533 } 534 535 private String getCaption(String labelKey) { 536 return getOptionalMessage(labelKey + ".caption"); 537 } 538 539 protected String getOptionalMessage(String messageKey) { 540 return getMessages().getMessage(messageKey, (String) null); 541 } 542 543 private Icon getIcon(String labelKey) { 544 return getIconSource().getIcon(labelKey); 545 } 546 547 /** 548 * Returns the icon source. Uses the {@link ApplicationServicesLocator} to 549 * find one if none was set explicitly. 550 */ 551 private IconSource getIconSource() { 552 if (iconSource == null) { 553 iconSource = (IconSource) ApplicationServicesLocator.services().getService(IconSource.class); 554 } 555 return iconSource; 556 } 557 558 /** 559 * Construct a JTable with a default model It will delegate the creation to 560 * a TableFactory if it exists. 561 * 562 * @param model the table model 563 * @return The new table. 564 */ 565 public JTable createTable() { 566 return (tableFactory != null) ? tableFactory.createTable() : new JTable(); 567 } 568 569 /** 570 * Construct a JTable with the specified table model. It will delegate the 571 * creation to a TableFactory if it exists. 572 * 573 * @param model the table model 574 * @return The new table. 575 */ 576 public JTable createTable(TableModel model) { 577 return (tableFactory != null) ? tableFactory.createTable(model) : new JTable(model); 578 } 579 580 /** 581 * Allow configuration via XML of a table factory. A simple interface for 582 * creating JTable object, this allows the developer to create an 583 * application specific table factory where, say, each tables have a set of 584 * renderers installed, are sortable, etc. 585 * 586 * @param tableFactory the table factory to use 587 */ 588 public void setTableFactory(TableFactory tableFactory) { 589 this.tableFactory = tableFactory; 590 } 591 592 /** 593 * {@inheritDoc} 594 */ 595 public JComponent createToolBar() { 596 JToolBar toolBar = new JToolBar(); 597 598 toolBar.setFloatable(false); 599 toolBar.setRollover(true); 600 601 return toolBar; 602 } 603 }