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 }