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    }