View Javadoc

1   /*
2    * Copyright 2006-2016 The JGUIraffe Team.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.jguiraffe.gui.platform.swing.builder.components;
17  
18  import javax.swing.AbstractButton;
19  import javax.swing.BorderFactory;
20  import javax.swing.ButtonGroup;
21  import javax.swing.Icon;
22  import javax.swing.ImageIcon;
23  import javax.swing.JButton;
24  import javax.swing.JCheckBox;
25  import javax.swing.JComboBox;
26  import javax.swing.JComponent;
27  import javax.swing.JDesktopPane;
28  import javax.swing.JLabel;
29  import javax.swing.JList;
30  import javax.swing.JPanel;
31  import javax.swing.JPasswordField;
32  import javax.swing.JProgressBar;
33  import javax.swing.JRadioButton;
34  import javax.swing.JScrollPane;
35  import javax.swing.JSlider;
36  import javax.swing.JSplitPane;
37  import javax.swing.JTabbedPane;
38  import javax.swing.JTable;
39  import javax.swing.JTextArea;
40  import javax.swing.JTextField;
41  import javax.swing.JToggleButton;
42  import javax.swing.JTree;
43  import javax.swing.ListSelectionModel;
44  import javax.swing.SwingConstants;
45  import javax.swing.border.Border;
46  import javax.swing.border.TitledBorder;
47  import javax.swing.event.TableModelEvent;
48  import javax.swing.event.TableModelListener;
49  import javax.swing.text.JTextComponent;
50  import javax.swing.tree.TreeSelectionModel;
51  import java.awt.Component;
52  import java.awt.Container;
53  import java.awt.Dimension;
54  import java.awt.Font;
55  import java.awt.LayoutManager;
56  import java.awt.font.TextAttribute;
57  import java.lang.reflect.Field;
58  import java.util.HashMap;
59  import java.util.Map;
60  
61  import net.sf.jguiraffe.gui.builder.components.ComponentManager;
62  import net.sf.jguiraffe.gui.builder.components.FormBuilderException;
63  import net.sf.jguiraffe.gui.builder.components.FormBuilderRuntimeException;
64  import net.sf.jguiraffe.gui.builder.components.Orientation;
65  import net.sf.jguiraffe.gui.builder.components.WidgetHandler;
66  import net.sf.jguiraffe.gui.builder.components.model.StaticTextData;
67  import net.sf.jguiraffe.gui.builder.components.model.TextIconAlignment;
68  import net.sf.jguiraffe.gui.builder.components.tags.BorderLayoutTag;
69  import net.sf.jguiraffe.gui.builder.components.tags.ButtonLayoutTag;
70  import net.sf.jguiraffe.gui.builder.components.tags.ButtonTag;
71  import net.sf.jguiraffe.gui.builder.components.tags.CheckboxTag;
72  import net.sf.jguiraffe.gui.builder.components.tags.ComboBoxTag;
73  import net.sf.jguiraffe.gui.builder.components.tags.ComponentBaseTag;
74  import net.sf.jguiraffe.gui.builder.components.tags.DesktopPanelTag;
75  import net.sf.jguiraffe.gui.builder.components.tags.FontTag;
76  import net.sf.jguiraffe.gui.builder.components.tags.FormBaseTag;
77  import net.sf.jguiraffe.gui.builder.components.tags.InputComponentTag;
78  import net.sf.jguiraffe.gui.builder.components.tags.LabelTag;
79  import net.sf.jguiraffe.gui.builder.components.tags.ListBoxTag;
80  import net.sf.jguiraffe.gui.builder.components.tags.PanelTag;
81  import net.sf.jguiraffe.gui.builder.components.tags.PasswordFieldTag;
82  import net.sf.jguiraffe.gui.builder.components.tags.PercentLayoutTag;
83  import net.sf.jguiraffe.gui.builder.components.tags.ProgressBarTag;
84  import net.sf.jguiraffe.gui.builder.components.tags.RadioButtonTag;
85  import net.sf.jguiraffe.gui.builder.components.tags.SliderTag;
86  import net.sf.jguiraffe.gui.builder.components.tags.SplitterTag;
87  import net.sf.jguiraffe.gui.builder.components.tags.StaticTextTag;
88  import net.sf.jguiraffe.gui.builder.components.tags.TabbedPaneTag;
89  import net.sf.jguiraffe.gui.builder.components.tags.TextAreaTag;
90  import net.sf.jguiraffe.gui.builder.components.tags.TextFieldTag;
91  import net.sf.jguiraffe.gui.builder.components.tags.TextIconData;
92  import net.sf.jguiraffe.gui.builder.components.tags.ToggleButtonTag;
93  import net.sf.jguiraffe.gui.builder.components.tags.TreeTag;
94  import net.sf.jguiraffe.gui.builder.components.tags.table.TableColumnWidthController;
95  import net.sf.jguiraffe.gui.builder.components.tags.table.TableFormController;
96  import net.sf.jguiraffe.gui.builder.components.tags.table.TableTag;
97  import net.sf.jguiraffe.gui.builder.event.PlatformEventManager;
98  import net.sf.jguiraffe.gui.forms.ComponentHandler;
99  import net.sf.jguiraffe.gui.platform.swing.builder.components.table.SwingTableColumnWidthListener;
100 import net.sf.jguiraffe.gui.platform.swing.builder.components.table.SwingTableModel;
101 import net.sf.jguiraffe.gui.platform.swing.builder.components.table.SwingTableRowHeightUpdater;
102 import net.sf.jguiraffe.gui.platform.swing.builder.components.table.SwingTableSelectionHandler;
103 import net.sf.jguiraffe.gui.platform.swing.builder.event.SwingEventManager;
104 import net.sf.jguiraffe.gui.platform.swing.layout.SwingPercentLayoutAdapter;
105 import net.sf.jguiraffe.gui.platform.swing.layout.SwingSizeHandler;
106 import net.sf.jguiraffe.locators.Locator;
107 import org.apache.commons.jelly.JellyContext;
108 import org.apache.commons.lang.StringUtils;
109 import org.apache.commons.logging.Log;
110 import org.apache.commons.logging.LogFactory;
111 
112 /**
113  * <p>
114  * The Swing specific implementation of the <code>ComponentManager</code>
115  * interface.
116  * </p>
117  * <p>
118  * This class implements the methods of the <code>ComponentManager</code>
119  * interface in a way that standard Swing components are created.
120  * </p>
121  *
122  * @author Oliver Heger
123  * @version $Id: SwingComponentManager.java 205 2012-01-29 18:29:57Z oheger $
124  */
125 public class SwingComponentManager implements ComponentManager
126 {
127     /** Constant for the Swing form builder name. */
128     public static final String BUILDER_NAME = "SWING_FORM_BUILDER";
129 
130     /** An array with the generic tabbed pane placement constants. */
131     private static final TabbedPaneTag.Placement[] TAB_PLACEMENTS = {
132             TabbedPaneTag.Placement.BOTTOM, TabbedPaneTag.Placement.LEFT,
133             TabbedPaneTag.Placement.RIGHT, TabbedPaneTag.Placement.TOP
134     };
135 
136     /** An Array with the Swing tabbed pane placement constants. */
137     private static final int[] SWING_TAB_PLACEMENTS = {
138             JTabbedPane.BOTTOM, JTabbedPane.LEFT, JTabbedPane.RIGHT,
139             JTabbedPane.TOP
140     };
141 
142     /**
143      * The name under which the size handler is stored in the current Jelly
144      * context.
145      */
146     private static final String VAR_SIZE_HANDLER = SwingSizeHandler.class
147             .getName();
148 
149     /** Stores a reference to a logger instance. */
150     private final Log log = LogFactory.getLog(SwingComponentManager.class);
151 
152     /** The mapping between string and TextAttribute constants. */
153     private final Map<String, TextAttribute> textAttributeMapping;
154 
155     /** The object for updating row heights for tables. */
156     private final SwingTableRowHeightUpdater tableRowHeightUpdater;
157 
158     /**
159      * Creates a new instance of {@code SwingComponentManager}.
160      */
161     public SwingComponentManager()
162     {
163         this(new SwingTableRowHeightUpdater());
164     }
165 
166     /**
167      * Creates a new instance of {@code SwingComponentManager} and initializes
168      * it with the given dependencies. This constructor is mainly used for
169      * testing purposes.
170      *
171      * @param tableRowHeightUpdater the {@code SwingTableRowHeightUpdater}
172      */
173     SwingComponentManager(SwingTableRowHeightUpdater tableRowHeightUpdater)
174     {
175         textAttributeMapping = initTextAttributeMapping();
176         this.tableRowHeightUpdater = tableRowHeightUpdater;
177     }
178 
179     /**
180      * Adds the specified component to a container using the given constraints.
181      * This implementation expects the container object to be of type
182      * <code>java.awt.Container</code> and the component of type
183      * <code>java.awt.Component</code>. The constraints may be undefined.
184      *
185      * @param container the container
186      * @param component the component
187      * @param constraints the layout constrains
188      */
189     public void addContainerComponent(Object container, Object component,
190             Object constraints)
191     {
192         Container c = (Container) container;
193         Component comp = (Component) component;
194         if (constraints == null)
195         {
196             c.add(comp);
197         }
198         else
199         {
200             c.add(comp, constraints);
201         }
202     }
203 
204     /**
205      * Sets the layout manager for the specified container. The passed in
206      * container object must be derived from <code>java.awt.Container</code>,
207      * the layout manager object must implement the
208      * <code>java.awt.LayoutManager</code> interface.
209      *
210      * @param container the container
211      * @param layout the layout manager
212      */
213     public void setContainerLayout(Object container, Object layout)
214     {
215         ((Container) container).setLayout((LayoutManager) layout);
216     }
217 
218     /**
219      * Creates the Swing specific platform event manager. This is an instance of
220      * the
221      * {@link net.sf.jguiraffe.gui.platform.swing.builder.event.SwingEventManager
222      * SwingEventManager} class.
223      *
224      * @return the platform event manager
225      */
226     public PlatformEventManager createEventManager()
227     {
228         return new SwingEventManager();
229     }
230 
231     /**
232      * Returns a {@code WidgetHandler} for the specified component. This
233      * implementation expects that the passed in component is either derived
234      * from {@code javax.swing.JComponent} or is a {@code
235      * javax.swing.ButtonGroup}. It will then return a Swing-specific handler
236      * implementation, which allows manipulation of this component. Some
237      * components created by {@code SwingComponentManager} are wrapped inside a
238      * {@code JScrollPane}. If the component passed in happens to be a scroll
239      * pane, the {@code WidgetHandler} is therefore created for the viewport
240      * component.
241      *
242      * @param component the affected component
243      * @return a {@code WidgetHandler} for this component
244      * @throws FormBuilderRuntimeException if the component is a {@code
245      *         ButtonGroup} and does not contain any elements
246      */
247     public WidgetHandler getWidgetHandlerFor(Object component)
248     {
249         JComponent wrappedComp;
250 
251         if (component instanceof JScrollPane)
252         {
253             wrappedComp = (JComponent) ((JScrollPane) component).getViewport()
254                     .getView();
255         }
256 
257         else if (component instanceof ButtonGroup)
258         {
259             ButtonGroup group = (ButtonGroup) component;
260             if (group.getButtonCount() < 1)
261             {
262                 throw new FormBuilderRuntimeException(
263                         "ButtonGroup must not be empty!");
264             }
265             return new SwingRadioGroupWidgetHandler(group);
266         }
267         else
268         {
269             wrappedComp = (JComponent) component;
270         }
271 
272         return new SwingWidgetHandler(wrappedComp);
273     }
274 
275     /**
276      * Creates a label component with the information obtained from the given
277      * tag. This method returns a <code>JLabel</code> object if the
278      * <code>create</code> parameter is <b>false</b>.
279      *
280      * @param tag the label tag
281      * @param create the create flag
282      * @return the label
283      */
284     public Object createLabel(LabelTag tag, boolean create)
285     {
286         if (create)
287         {
288             return null;
289         }
290         else
291         {
292             JLabel label = new JLabel();
293             initLabel(label, tag, tag.getTextIconData());
294             return label;
295         }
296     }
297 
298     /**
299      * Associates a label with another component. The label must be of type
300      * <code>javax.swing.JLabel</code>, the component must be of type
301      * <code>java.awt.Component</code>.
302      *
303      * @param label the label
304      * @param component the component
305      * @param text the text for the label
306      * @throws FormBuilderException if an error occurs
307      */
308     public void linkLabel(Object label, Object component, String text)
309             throws FormBuilderException
310     {
311         JLabel lab = (JLabel) label;
312         lab.setLabelFor((Component) component);
313         if (text != null)
314         {
315             lab.setText(text);
316         }
317     }
318 
319     /**
320      * Creates an icon with the information obtained from the given locator. An
321      * <code>ImageIcon</code> object will be returned.
322      *
323      * @param locator the <code>Locator</code> pointing to the icon's data
324      * @return the icon
325      * @throws FormBuilderException if the icon cannot be loaded
326      */
327     public Object createIcon(Locator locator) throws FormBuilderException
328     {
329         if (locator == null)
330         {
331             throw new FormBuilderException("Locator for icon must not be null!");
332         }
333 
334         if (log.isInfoEnabled())
335         {
336             log.info("Loading icon from " + locator);
337         }
338         return new ImageIcon(locator.getURL());
339     }
340 
341     /**
342      * Creates a font based on the data provided by the given {@code FontTag}.
343      * This implementation creates a {@code java.awt.Font} object using the
344      * constructor that takes a map with attributes. The default font attributes
345      * specified by the tag are mapped to corresponding constants of the {@code
346      * TextAttribute} class. For the map with extended attributes two kinds of
347      * keys are supported:
348      * <ul>
349      * <li>Objects of type {@code TextAttribute} are used directly.</li>
350      * <li>If a key is of type {@code String}, it is checked whether it is the
351      * name of a constant in the {@code TextAttribute} class. In this case the
352      * key is accepted.</li>
353      * </ul>
354      * All other objects appearing as keys in the map are ignored.
355      *
356      * @param tag the {@code FontTag}
357      * @return the newly created font
358      * @throws FormBuilderException if an error occurs
359      */
360     public Object createFont(FontTag tag) throws FormBuilderException
361     {
362         Map<TextAttribute, Object> attrs = new HashMap<TextAttribute, Object>();
363 
364         handleStandardFontAttributes(tag, attrs);
365         handleExtendedFontAttributes(tag, attrs);
366 
367         return new Font(attrs);
368     }
369 
370     /**
371      * Creates a percent layout with the information from the passed in tag. A
372      * Swing specific adapter is created for the layout object maintained by the
373      * tag.
374      *
375      * @param tag the percent layout tag
376      * @return the new layout object
377      * @throws FormBuilderException if an error occurs
378      */
379     public Object createPercentLayout(PercentLayoutTag tag)
380             throws FormBuilderException
381     {
382         return new SwingPercentLayoutAdapter(tag.getPercentLayout());
383     }
384 
385     /**
386      * Creates a button layout with the information from the passed in tag. A
387      * Swing specific adapter is created for the button layout maintained by the
388      * tag.
389      *
390      * @param tag the button layout tag
391      * @return the new layout object
392      * @throws FormBuilderException if an error occurs
393      */
394     public Object createButtonLayout(ButtonLayoutTag tag)
395             throws FormBuilderException
396     {
397         return new SwingPercentLayoutAdapter(tag.getButtonLayout());
398     }
399 
400     /**
401      * Creates a border layout with the information from the passed in tag. A
402      * Swing specific adapter is created for the border layout maintained by the
403      * tag.
404      *
405      * @param tag the border layout tag
406      * @return the new layout object
407      * @throws FormBuilderException if an error occurs
408      */
409     public Object createBorderLayout(BorderLayoutTag tag)
410             throws FormBuilderException
411     {
412         return new SwingPercentLayoutAdapter(tag.getBorderLayout());
413     }
414 
415     /**
416      * Creates a panel with the information obtained from the passed in tag. The
417      * returned object is of type <code>javax.swing.JPanel</code>. If a text is
418      * defined or the <code>border</code> attribute is <b>true</b>, a border
419      * will be added. This implementation also supports border objects that have
420      * been placed into the Jelly context and that are referenced by the
421      * <code>borderref</code> attribute.
422      *
423      * @param tag the panel tag
424      * @param create the create flag
425      * @return the new panel object
426      * @throws FormBuilderException if an error occurs
427      */
428     public Object createPanel(PanelTag tag, boolean create)
429             throws FormBuilderException
430     {
431         if (create)
432         {
433             return null;
434         }
435 
436         JPanel panel = new JPanel();
437         initComponent(panel, tag);
438 
439         if (tag.isBorder() || tag.getTextData().isDefined())
440         {
441             Border border;
442             if (StringUtils.isNotEmpty(tag.getBorderref()))
443             {
444                 border = (Border) tag.getContext().getVariable(
445                         tag.getBorderref());
446                 if (border == null)
447                 {
448                     throw new FormBuilderException("Cannot find border "
449                             + tag.getBorderref());
450                 }
451             }
452             else
453             {
454                 border = createDefaultBorder();
455             }
456 
457             if (tag.getTextData().isDefined())
458             {
459                 border = BorderFactory.createTitledBorder(border, tag
460                         .getTextData().getCaption(),
461                         TitledBorder.DEFAULT_JUSTIFICATION,
462                         TitledBorder.DEFAULT_POSITION,
463                         (tag.getTitleFont() != null) ? (Font) tag
464                                 .getTitleFont() : panel.getFont(), (tag
465                                 .getColor() != null) ? SwingComponentUtils
466                                 .logic2SwingColor(tag.getColor()) : panel
467                                 .getForeground());
468             }
469             panel.setBorder(border);
470         }
471 
472         return panel;
473     }
474 
475     /**
476      * Creates a desktop panel with the information obtained from the passed in
477      * tag. This implementation will return an instance of the
478      * <code>JDesktopPane</code> class.
479      *
480      * @param tag the desktop panel tag
481      * @param create the create flag
482      * @return the new desktop panel
483      * @throws FormBuilderException if an error occurs
484      */
485     public Object createDesktopPanel(DesktopPanelTag tag, boolean create)
486             throws FormBuilderException
487     {
488         if (create)
489         {
490             return null;
491         }
492 
493         else
494         {
495             JDesktopPane pane = new JDesktopPane();
496             initComponent(pane, tag);
497             if (DesktopPanelTag.DragMode.OUTLINE == tag.getDragMode())
498             {
499                 pane.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
500             }
501             else
502             {
503                 pane.setDragMode(JDesktopPane.LIVE_DRAG_MODE);
504             }
505             return pane;
506         }
507     }
508 
509     /**
510      * Creates a splitter component from the information contained in the passed
511      * in tag. This implementation returns an instance of the
512      * <code>JSplitPane</code> class.
513      *
514      * @param tag the splitter tag
515      * @param create the create flag
516      * @return the splitter component
517      * @throws FormBuilderException if an error occurs
518      */
519     public Object createSplitter(SplitterTag tag, boolean create)
520             throws FormBuilderException
521     {
522         if (create)
523         {
524             return null;
525         }
526         else
527         {
528             JSplitPane split = new JSplitPane(
529                     tag.getSplitterOrientation() == Orientation.HORIZONTAL
530                     ? JSplitPane.HORIZONTAL_SPLIT
531                             : JSplitPane.VERTICAL_SPLIT);
532             initComponent(split, tag);
533             split.setResizeWeight(tag.getResizeWeight());
534             if (tag.getPos() > 0)
535             {
536                 split.setDividerLocation(tag.getPos());
537             }
538             if (tag.getSize() > 0)
539             {
540                 split.setDividerSize(tag.getSize());
541             }
542             split.setLeftComponent((Component) tag.getFirstComponent());
543             split.setRightComponent((Component) tag.getSecondComponent());
544 
545             return split;
546         }
547     }
548 
549     /**
550      * Creates a radio group, which contains the specified radio buttons. The
551      * passed in elements must be of type {@code javax.swing.AbstractButton}.
552      * The button group must not be empty.
553      *
554      * @param radios a collection with the radio buttons to add
555      * @return the radio group
556      * @throws FormBuilderException if an error occurs
557      */
558     public ButtonGroup createRadioGroup(Map<String, Object> radios)
559             throws FormBuilderException
560     {
561         if (log.isDebugEnabled())
562         {
563             log.debug("Creating radio group for " + radios.size() + " radios.");
564         }
565         if (radios.isEmpty())
566         {
567             throw new FormBuilderException("Radio group must not be empty!");
568         }
569 
570         ButtonGroup group = new ButtonGroup();
571         for (Object comp : radios.values())
572         {
573             group.add((AbstractButton) comp);
574         }
575         return group;
576     }
577 
578     /**
579      * Creates a component handler for a command button, which is specified by
580      * the given tag.
581      *
582      * @param tag the button tag
583      * @param create the create flag
584      * @return the handler for the button
585      * @throws FormBuilderException if an error occurs
586      */
587     public ComponentHandler<Boolean> createButton(ButtonTag tag, boolean create)
588             throws FormBuilderException
589     {
590         if (create)
591         {
592             return null;
593         }
594 
595         return createButtonHandler(new JButton(), tag, tag.getTextIconData(),
596                 tag.getCommand());
597     }
598 
599     /**
600      * Creates a component handler for a toggle button, which is specified by
601      * the given tag.
602      *
603      * @param tag the toggle button tag
604      * @param create the create flag
605      * @return the handler for the toggle button
606      * @throws FormBuilderException if an error occurs
607      */
608     public ComponentHandler<Boolean> createToggleButton(ToggleButtonTag tag,
609             boolean create) throws FormBuilderException
610     {
611         if (create)
612         {
613             return null;
614         }
615 
616         return createButtonHandler(new JToggleButton(), tag, tag
617                 .getTextIconData(), tag.getCommand());
618     }
619 
620     /**
621      * Creates a component handler for a text field which is specified by the
622      * given tag.
623      *
624      * @param tag the text field tag
625      * @param create the create flag
626      * @return the new component handler
627      * @throws FormBuilderException if an error occurs
628      */
629     public ComponentHandler<String> createTextField(TextFieldTag tag,
630             boolean create) throws FormBuilderException
631     {
632         if (create)
633         {
634             return null;
635         }
636 
637         JTextField text = new JTextField();
638         return initTextField(tag, text);
639     }
640 
641     /**
642      * Creates a component handler for a text area which is specified by the
643      * given tag.
644      *
645      * @param tag the text area tag
646      * @param create the create flag
647      * @return the new component handler
648      * @throws FormBuilderException if an error occurs
649      */
650     public ComponentHandler<String> createTextArea(TextAreaTag tag,
651             boolean create) throws FormBuilderException
652     {
653         if (create)
654         {
655             return null;
656         }
657 
658         JTextArea text = new JTextArea();
659         if (tag.getColumns() > 0)
660         {
661             text.setColumns(tag.getColumns());
662         }
663         if (tag.getRows() > 0)
664         {
665             text.setRows(tag.getRows());
666         }
667         if (tag.isWrap())
668         {
669             text.setLineWrap(true);
670             text.setWrapStyleWord(true);
671         }
672 
673         initText(text, tag, tag.getMaxlength());
674         SwingSizeHandler sizeHandler = fetchSizeHandler(tag);
675         Object container = tag.findContainer().getContainer();
676         return new SwingTextAreaHandler(text, tag.getPreferredScrollWidth()
677                 .toPixel(sizeHandler, container, false), tag
678                 .getPreferredScrollHeight().toPixel(sizeHandler, container,
679                         true));
680     }
681 
682     /**
683      * Creates a component handler for a password text field which is specified
684      * by the given tag.
685      *
686      * @param tag the password tag
687      * @param create the create flag
688      * @return the new component handler
689      * @throws FormBuilderException if an error occurs
690      */
691     public ComponentHandler<String> createPasswordField(PasswordFieldTag tag,
692             boolean create) throws FormBuilderException
693     {
694         if (create)
695         {
696             return null;
697         }
698 
699         JTextField text = new JPasswordField();
700         return initTextField(tag, text);
701     }
702 
703     /**
704      * Creates a component handler for a checkbox, which is specified by the
705      * given tag.
706      *
707      * @param tag the checkbox tag
708      * @param create the create flag
709      * @return the new component handler
710      * @throws FormBuilderException if an error occurs
711      */
712     public ComponentHandler<Boolean> createCheckbox(CheckboxTag tag,
713             boolean create) throws FormBuilderException
714     {
715         if (create)
716         {
717             return null;
718         }
719 
720         return createButtonHandler(new JCheckBox(), tag, tag.getTextIconData(),
721                 null);
722     }
723 
724     /**
725      * Creates a component handler for a radio button, which is specified by the
726      * given tag.
727      *
728      * @param tag the radio button tag
729      * @param create the create flag
730      * @return the new component handler
731      * @throws FormBuilderException if an error occurs
732      */
733     public ComponentHandler<Boolean> createRadioButton(RadioButtonTag tag,
734             boolean create) throws FormBuilderException
735     {
736         if (create)
737         {
738             return null;
739         }
740 
741         return createButtonHandler(new JRadioButton(), tag, tag
742                 .getTextIconData(), null);
743     }
744 
745     /**
746      * Creates a component handler for a combo box, which is specified by the
747      * given tag.
748      *
749      * @param tag the combo box tag
750      * @param create the create flag
751      * @return the new component handler
752      * @throws FormBuilderException if an error occurs
753      */
754     public ComponentHandler<Object> createComboBox(ComboBoxTag tag,
755             boolean create) throws FormBuilderException
756     {
757         if (create)
758         {
759             return null;
760         }
761 
762         JComboBox combo = new JComboBox();
763         initComponent(combo, tag);
764         if (tag.isEditable())
765         {
766             combo.setEditable(true);
767         }
768         return new SwingComboBoxHandler(combo, tag.getListModel());
769     }
770 
771     /**
772      * Creates a component handler for a list, which is specified by the given
773      * tag.
774      *
775      * @param tag the list box tag
776      * @param create the create flag
777      * @return the new component handler
778      * @throws FormBuilderException if an error occurs
779      */
780     public ComponentHandler<Object> createListBox(ListBoxTag tag, boolean create)
781             throws FormBuilderException
782     {
783         if (create)
784         {
785             return null;
786         }
787 
788         JList list = new JList();
789         initComponent(list, tag);
790 
791         SwingSizeHandler sizeHandler = fetchSizeHandler(tag);
792         Object container = tag.findContainer().getContainer();
793         int scrollWidth = tag.getPreferredScrollWidth().toPixel(sizeHandler,
794                 container, false);
795         int scrollHeight = tag.getPreferredScrollHeight().toPixel(sizeHandler,
796                 container, true);
797 
798         if (tag.isMulti())
799         {
800             list
801                     .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
802             return new SwingMultiListBoxHandler(list, tag.getListModel(),
803                     scrollWidth, scrollHeight);
804         }
805         else
806         {
807             list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
808             return new SwingListBoxHandler(list, tag.getListModel(),
809                     scrollWidth, scrollHeight);
810         }
811     }
812 
813     /**
814      * Creates a component handler for a tabbed pane, which is specified by the
815      * given tag.
816      *
817      * @param tag the tabbed pane tag
818      * @param create the create flag
819      * @return the new component handler
820      * @throws FormBuilderException if an error occurs
821      */
822     public ComponentHandler<Integer> createTabbedPane(TabbedPaneTag tag,
823             boolean create) throws FormBuilderException
824     {
825         if (create)
826         {
827             return null;
828         }
829         else
830         {
831             JTabbedPane pane = new JTabbedPane();
832             initComponent(pane, tag);
833             for (int i = 0; i < TAB_PLACEMENTS.length; i++)
834             {
835                 if (TAB_PLACEMENTS[i].equals(tag.getPlacementValue()))
836                 {
837                     pane.setTabPlacement(SWING_TAB_PLACEMENTS[i]);
838                     break;
839                 }
840             }
841 
842             int index = 0;
843             for (TabbedPaneTag.TabData tabData : tag.getTabs())
844             {
845                 pane.addTab(tabData.getTitle(), (Icon) tabData.getIcon(),
846                         (Component) tabData.getComponent(), tabData
847                                 .getToolTip());
848                 pane.setMnemonicAt(index, tabData.getMnemonic());
849                 index++;
850             }
851             return new SwingTabbedPaneHandler(pane);
852         }
853     }
854 
855     /**
856      * Creates a component handler for a static text, which is specified by the
857      * given tag.
858      *
859      * @param tag the static text tag
860      * @param create the create flag
861      * @return the component handler for the new element
862      * @throws FormBuilderException if an error occurs
863      */
864     public ComponentHandler<StaticTextData> createStaticText(StaticTextTag tag,
865             boolean create) throws FormBuilderException
866     {
867         if (create)
868         {
869             return null;
870         }
871         else
872         {
873             JLabel label = new JLabel();
874             initLabel(label, tag, tag.getTextIconData());
875             return new SwingStaticTextComponentHandler(label);
876         }
877     }
878 
879     /**
880      * Creates a component handler for a progress bar, which is specified by the
881      * given tag.
882      *
883      * @param tag the progress bar tag
884      * @param create the create flag
885      * @return the component handler for the progress bar element
886      * @throws FormBuilderException if an error occurs
887      */
888     public ComponentHandler<Integer> createProgressBar(ProgressBarTag tag,
889             boolean create) throws FormBuilderException
890     {
891         if (create)
892         {
893             return null;
894         }
895 
896         else
897         {
898             JProgressBar bar = new JProgressBar();
899             initComponent(bar, tag);
900             bar.setMinimum(tag.getMin().intValue());
901             bar.setMaximum(tag.getMax().intValue());
902             if (tag.getValue() != null)
903             {
904                 bar.setValue(tag.getValue().intValue());
905             }
906             if (tag.isAllowText())
907             {
908                 bar.setStringPainted(true);
909                 bar.setString(tag.getProgressTextData().getCaption());
910             }
911 
912             return new SwingProgressBarHandler(bar);
913         }
914     }
915 
916     /**
917      * Creates a component handler for a slider, which is specified by the given
918      * tag.
919      *
920      * @param tag the slider tag
921      * @param create the create flag
922      * @return the component handler for the slider element
923      * @throws FormBuilderException if an error occurs
924      */
925     public ComponentHandler<Integer> createSlider(SliderTag tag, boolean create)
926             throws FormBuilderException
927     {
928         if (create)
929         {
930             return null;
931         }
932 
933         else
934         {
935             JSlider slider = new JSlider();
936             initComponent(slider, tag);
937             slider.setMinimum(tag.getMin());
938             slider.setMaximum(tag.getMax());
939 
940             if (tag.getMinorTicks() > 0)
941             {
942                 slider.setMinorTickSpacing(tag.getMinorTicks());
943             }
944             if (tag.getMajorTicks() > 0)
945             {
946                 slider.setMajorTickSpacing(tag.getMajorTicks());
947             }
948 
949             slider.setPaintLabels(tag.isShowLabels());
950             slider.setPaintTicks(tag.isShowTicks());
951             slider.setOrientation((tag.getSliderOrientation() == Orientation.VERTICAL)
952                     ? JSlider.VERTICAL : JSlider.HORIZONTAL);
953 
954             return new SwingSliderHandler(slider);
955         }
956     }
957 
958     /**
959      * Creates a component handler for a table specified by the given tag.
960      *
961      * @param tag the tag
962      * @param create the create flag
963      * @return the component handler
964      * @throws FormBuilderException if an error occurs
965      */
966     public ComponentHandler<Object> createTable(TableTag tag, boolean create)
967             throws FormBuilderException
968     {
969         if (create)
970         {
971             return null;
972         }
973 
974         else
975         {
976             JTable table = new JTable();
977             SwingTableModel model = new SwingTableModel(tag, table);
978             table.setModel(model);
979             initTableColumnWidths(tag, table);
980             initComponent(table, tag);
981 
982             if (tag.getEditorSelectionHandler() == null)
983             {
984                 tag.setEditorSelectionHandler(new SwingTableSelectionHandler());
985             }
986 
987             if (tag.isMultiSelection())
988             {
989                 table
990                         .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
991             }
992             else
993             {
994                 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
995             }
996 
997             if (tag.getSelectionBackgroundColor() != null)
998             {
999                 table.setSelectionBackground(SwingComponentUtils
1000                         .logic2SwingColor(tag.getSelectionBackgroundColor()));
1001             }
1002             if (tag.getSelectionForegroundColor() != null)
1003             {
1004                 table.setSelectionForeground(SwingComponentUtils
1005                         .logic2SwingColor(tag.getSelectionForegroundColor()));
1006             }
1007 
1008             if (initColumnRenderers(tag.getTableFormController(), model, table))
1009             {
1010                 getTableRowHeightUpdater().updateRowHeights(table);
1011                 registerRowHeightListener(table, model);
1012             }
1013 
1014             SwingSizeHandler sizeHandler = fetchSizeHandler(tag);
1015             Object container = tag.findContainer().getContainer();
1016             SwingTableComponentHandler handler =
1017                     new SwingTableComponentHandler(table, tag
1018                             .getPreferredScrollWidth().toPixel(sizeHandler,
1019                                     container, false), tag
1020                             .getPreferredScrollHeight().toPixel(sizeHandler,
1021                                     container, true));
1022             registerTableListener(tag, handler);
1023             return handler;
1024         }
1025     }
1026 
1027     /**
1028      * Creates a component handler for a tree specified by the given tag.
1029      *
1030      * @param tag the tag
1031      * @param create the create flag
1032      * @return the component handler
1033      * @throws FormBuilderException if an error occurs
1034      */
1035     public ComponentHandler<Object> createTree(TreeTag tag, boolean create)
1036             throws FormBuilderException
1037     {
1038         if (create)
1039         {
1040             return null;
1041         }
1042 
1043         else
1044         {
1045             SwingConfigurationTreeModel model = new SwingConfigurationTreeModel(
1046                     tag.getTreeModel());
1047             JTree tree = new JTree(model);
1048             initComponent(tree, tag);
1049 
1050             tree.setRootVisible(tag.isRootVisible());
1051             tree.setEditable(tag.isEditable());
1052             tree
1053                     .getSelectionModel()
1054                     .setSelectionMode(
1055                             tag.isMultiSelection() ? TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION
1056                                     : TreeSelectionModel.SINGLE_TREE_SELECTION);
1057 
1058             SwingTreeNodeFormatter formatter = new SwingTreeNodeFormatter();
1059             SwingTreeCellRenderer renderer = new SwingTreeCellRenderer(tag
1060                     .getResolvedIconHandler(), tag.getIcons(), formatter);
1061             tree.setCellRenderer(renderer);
1062             tree.setCellEditor(new SwingTreeCellEditor(tree, renderer));
1063 
1064             SwingSizeHandler sizeHandler = fetchSizeHandler(tag);
1065             Object container = tag.findContainer().getContainer();
1066             return new SwingTreeComponentHandler(tree, model, tag.getName(),
1067                     tag.getPreferredScrollWidth().toPixel(sizeHandler,
1068                             container, false), tag.getPreferredScrollHeight()
1069                             .toPixel(sizeHandler, container, true));
1070         }
1071     }
1072 
1073     /**
1074      * Initializes the given component from the specified tag.
1075      *
1076      * @param component the component to initialize
1077      * @param tag the tag
1078      */
1079     protected void initComponent(JComponent component, ComponentBaseTag tag)
1080     {
1081         if (tag.getFont() != null)
1082         {
1083             component.setFont((Font) tag.getFont());
1084         }
1085 
1086         if (tag.getBackgroundColor() != null)
1087         {
1088             SwingComponentUtils.setBackgroundColor(component, tag
1089                     .getBackgroundColor());
1090         }
1091         if (tag.getForegroundColor() != null)
1092         {
1093             SwingComponentUtils.setForegroundColor(component, tag
1094                     .getForegroundColor());
1095         }
1096 
1097         if (tag.getToolTipData().isDefined())
1098         {
1099             component.setToolTipText(tag.getToolTipData().getCaption());
1100         }
1101 
1102         component.setName(tag.getName());
1103     }
1104 
1105     /**
1106      * Initializes a label from a <code>TextIconData</code> object.
1107      *
1108      * @param label the label to be initialized
1109      * @param tag the tag with the label definition
1110      * @param data the text icon data object
1111      */
1112     protected void initLabel(JLabel label, ComponentBaseTag tag,
1113             TextIconData data)
1114     {
1115         label.setText(data.getCaption());
1116         if (data.getIcon() != null)
1117         {
1118             label.setIcon((Icon) data.getIcon());
1119         }
1120         if (data.getMnemonic() > 0)
1121         {
1122             label.setDisplayedMnemonic(SwingComponentUtils.toMnemonic(data
1123                     .getMnemonic()));
1124         }
1125         label.setHorizontalAlignment(transformAlign(data.getAlignment()));
1126 
1127         initComponent(label, tag);
1128     }
1129 
1130     /**
1131      * Initializes a button component like a toggle button or a checkbox. These
1132      * components can all be handled pretty the same.
1133      *
1134      * @param button the button to initialize
1135      * @param tag the tag for the button
1136      * @param data the text icon data object
1137      * @param command the button's command (can be <b>null</b>)
1138      */
1139     protected void initButton(AbstractButton button, InputComponentTag tag,
1140             TextIconData data, String command)
1141     {
1142         initComponent(button, tag);
1143 
1144         button.setText(data.getCaption());
1145         if (data.getIcon() != null)
1146         {
1147             button.setIcon((Icon) data.getIcon());
1148         }
1149         if (data.getMnemonic() > 0)
1150         {
1151             button.setMnemonic(SwingComponentUtils.toMnemonic(data
1152                     .getMnemonic()));
1153         }
1154         button.setHorizontalAlignment(transformAlign(data.getAlignment()));
1155         if (command != null)
1156         {
1157             button.setActionCommand(command);
1158         }
1159     }
1160 
1161     /**
1162      * Initializes a button component and creates a component handler for it.
1163      * This method calls <code>initButton()</code> and then creates a
1164      * <code>SwingButtonHandler</code> that wraps the button.
1165      *
1166      * @param button the button to initialize
1167      * @param tag the tag for the button
1168      * @param data the text icon data object
1169      * @param command the button's command (can be <b>null</b>)
1170      * @return the component handler for the button
1171      */
1172     protected ComponentHandler<Boolean> createButtonHandler(
1173             AbstractButton button, InputComponentTag tag, TextIconData data,
1174             String command)
1175     {
1176         initButton(button, tag, data, command);
1177         return new SwingButtonHandler(button);
1178     }
1179 
1180     /**
1181      * Initializes a text component. Default initialization will be performed
1182      * and a limited text document will be set if necessary.
1183      *
1184      * @param text the text component
1185      * @param tag the input component tag
1186      * @param maxlen the maximum text length
1187      */
1188     protected void initText(JTextComponent text, InputComponentTag tag,
1189             int maxlen)
1190     {
1191         initComponent(text, tag);
1192         if (maxlen > 0)
1193         {
1194             text.setDocument(new SwingLimitedTextModel(maxlen));
1195         }
1196     }
1197 
1198     /**
1199      * Creates a default border. This method is used for panels that should have
1200      * a border. If no specific border is specified, the default border returned
1201      * by this method is used.
1202      *
1203      * @return the default border
1204      */
1205     protected Border createDefaultBorder()
1206     {
1207         return BorderFactory.createEtchedBorder();
1208     }
1209 
1210     /**
1211      * Initializes the widths of the columns of the specified table. This
1212      * implementation checks whether there is at least one column tag with the
1213      * width attribute defined. In this case it turns off auto resizing of the
1214      * table's columns and sets the preferred and minimum widths of the columns
1215      * affected.
1216      *
1217      * @param tag the tag defining the table
1218      * @param table the table
1219      * @throws FormBuilderException if an error occurs
1220      */
1221     protected void initTableColumnWidths(TableTag tag, JTable table)
1222             throws FormBuilderException
1223     {
1224         TableFormController tableFormController = tag.getTableFormController();
1225         TableColumnWidthController widthCtrl = tag.getColumnWidthController();
1226         int totalWidth =
1227                 tableFormController.calculateFixedColumnWidths(
1228                         fetchSizeHandler(tag), tag.findContainer()
1229                                 .getContainer());
1230 
1231         for (int i = 0; i < tag.getColumnCount(); i++)
1232         {
1233             if (!widthCtrl.isPercentWidth(i))
1234             {
1235                 table.getColumnModel().getColumn(i)
1236                         .setPreferredWidth(widthCtrl.getFixedWidth(i));
1237             }
1238         }
1239 
1240         if (widthCtrl.getNumberOfColumnWithPercentWidth() == 0)
1241         {
1242             table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
1243             Dimension sz = table.getPreferredScrollableViewportSize();
1244             sz.setSize(totalWidth, sz.getHeight());
1245             table.setPreferredScrollableViewportSize(sz);
1246         }
1247     }
1248 
1249     /**
1250      * Registers a specialized listener for resizing table columns if required.
1251      * This method checks whether the table has columns with a relative width.
1252      * If this is the case, a listener is registered that adjusts the columns'
1253      * widths when the table is resized.
1254      *
1255      * @param tag the table tag
1256      * @param handler the handler for the table
1257      * @throws FormBuilderException if an error occurs
1258      */
1259     protected void registerTableListener(TableTag tag,
1260             SwingTableComponentHandler handler) throws FormBuilderException
1261     {
1262         if (tag.getColumnWidthController().getNumberOfColumnWithPercentWidth() > 0)
1263         {
1264             SwingTableColumnWidthListener l = new SwingTableColumnWidthListener(
1265                     handler.getTable(), tag.getColumnWidthController());
1266             ((JComponent) handler.getOuterComponent()).addComponentListener(l);
1267             handler.getTable().getColumnModel().addColumnModelListener(l);
1268         }
1269     }
1270 
1271     /**
1272      * Helper method for transforming a platform independent alignment into a
1273      * Swing constant.
1274      *
1275      * @param al the alignment
1276      * @return the transformed alignment constant
1277      */
1278     static int transformAlign(TextIconAlignment al)
1279     {
1280         int align;
1281         if (TextIconAlignment.RIGHT.equals(al))
1282         {
1283             align = SwingConstants.RIGHT;
1284         }
1285         else if (TextIconAlignment.CENTER.equals(al))
1286         {
1287             align = SwingConstants.CENTER;
1288         }
1289         else
1290         {
1291             align = SwingConstants.LEFT;
1292         }
1293 
1294         return align;
1295     }
1296 
1297     /**
1298      * Transforms a Swing specific alignment constant into a platform
1299      * independent alignment constant.
1300      *
1301      * @param al the Swing alignment constant
1302      * @return the independent alignment constant
1303      */
1304     static TextIconAlignment transformSwingAlign(int al)
1305     {
1306         switch (al)
1307         {
1308         case SwingConstants.RIGHT:
1309             return TextIconAlignment.RIGHT;
1310         case SwingConstants.LEFT:
1311             return TextIconAlignment.LEFT;
1312         case SwingConstants.CENTER:
1313             return TextIconAlignment.CENTER;
1314         default:
1315             throw new IllegalArgumentException("Unknown alignment value: " + al);
1316         }
1317     }
1318 
1319     /**
1320      * Obtains the current {@code SwingSizeHandler}. An instance of {@code
1321      * SwingSizeHandler} is created on demand and stored in the current Jelly
1322      * context. Thus the caching facilities provided by this class can be used.
1323      * Threading issues do not apply because the Jelly context is confined to a
1324      * single thread.
1325      *
1326      * @param tag the tag which is currently processed
1327      * @return the {@code SwingSizeHandler}
1328      */
1329     SwingSizeHandler fetchSizeHandler(FormBaseTag tag)
1330     {
1331         JellyContext ctx = tag.getContext();
1332         SwingSizeHandler sizeHandler = (SwingSizeHandler) ctx
1333                 .getVariable(VAR_SIZE_HANDLER);
1334 
1335         if (sizeHandler == null)
1336         {
1337             sizeHandler = new SwingSizeHandler();
1338             ctx.setVariable(VAR_SIZE_HANDLER, sizeHandler);
1339         }
1340 
1341         return sizeHandler;
1342     }
1343 
1344     /**
1345      * Returns the {@code SwingTableRowHeightUpdater} used by this object.
1346      *
1347      * @return the {@code SwingTableRowHeightUpdater}
1348      */
1349     SwingTableRowHeightUpdater getTableRowHeightUpdater()
1350     {
1351         return tableRowHeightUpdater;
1352     }
1353 
1354     /**
1355      * Helper method for initializing a text field.
1356      *
1357      * @param tag the tag describing the field
1358      * @param text the text field to be initialized
1359      * @return the component handler for the text field
1360      */
1361     private ComponentHandler<String> initTextField(TextFieldTag tag,
1362             JTextField text)
1363     {
1364         if (tag.getColumns() > 0)
1365         {
1366             text.setColumns(tag.getColumns());
1367         }
1368         initText(text, tag, tag.getMaxlength());
1369         return new SwingTextHandler(text);
1370     }
1371 
1372     /**
1373      * Initializes a map which associates strings with {@code TextAttribute}
1374      * constants. All available keys are obtaining using reflection.
1375      *
1376      * @return the map
1377      */
1378     private Map<String, TextAttribute> initTextAttributeMapping()
1379     {
1380         log.info("Initializing TextAttribute mapping.");
1381         Map<String, TextAttribute> map = new HashMap<String, TextAttribute>();
1382 
1383         for (Field field : TextAttribute.class.getFields())
1384         {
1385             if (field.getType().equals(TextAttribute.class))
1386             {
1387                 try
1388                 {
1389                     map.put(field.getName(), (TextAttribute) field.get(null));
1390                 }
1391                 catch (IllegalArgumentException e)
1392                 {
1393                     log.warn("Error when reading field " + field.getName(), e);
1394                 }
1395                 catch (IllegalAccessException e)
1396                 {
1397                     log.warn("Error when reading field " + field.getName(), e);
1398                 }
1399             }
1400         }
1401 
1402         return map;
1403     }
1404 
1405     /**
1406      * Deals with standard font attributes. Populates the specified map with the
1407      * standard attributes defined for the given font tag.
1408      *
1409      * @param tag the font tag
1410      * @param attrs the map to be filled
1411      */
1412     private static void handleStandardFontAttributes(FontTag tag,
1413             Map<TextAttribute, Object> attrs)
1414     {
1415         if (tag.getName() != null)
1416         {
1417             attrs.put(TextAttribute.FAMILY, tag.getName());
1418         }
1419         if (tag.getSize() > 0)
1420         {
1421             attrs.put(TextAttribute.SIZE, Float.valueOf(tag.getSize()));
1422         }
1423         attrs.put(TextAttribute.WEIGHT,
1424                 tag.isBold() ? TextAttribute.WEIGHT_BOLD
1425                         : TextAttribute.WEIGHT_REGULAR);
1426         attrs.put(TextAttribute.POSTURE,
1427                 tag.isItalic() ? TextAttribute.POSTURE_OBLIQUE
1428                         : TextAttribute.POSTURE_REGULAR);
1429     }
1430 
1431     /**
1432      * Deals with extended font attributes. Attributes of type {@code
1433      * TextAttribute} are directly accepted. For other attributes a conversion
1434      * to {@code TextAttribute} is tried.
1435      *
1436      * @param tag the {@code FontTag}
1437      * @param attrs the map to be filled
1438      */
1439     private void handleExtendedFontAttributes(FontTag tag,
1440             Map<TextAttribute, Object> attrs)
1441     {
1442         for (Map.Entry<?, ?> e : tag.getAttributesMap().entrySet())
1443         {
1444             if (e.getKey() instanceof TextAttribute)
1445             {
1446                 attrs.put((TextAttribute) e.getKey(), e.getValue());
1447             }
1448             else
1449             {
1450                 TextAttribute ta = textAttributeMapping.get(e.getKey());
1451                 if (ta != null)
1452                 {
1453                     attrs.put(ta, e.getValue());
1454                 }
1455                 else
1456                 {
1457                     log.warn("Ignoring font attribute " + e.getKey());
1458                 }
1459             }
1460         }
1461     }
1462 
1463     /**
1464      * Registers a special listener at the passed in table model which causes an
1465      * update of the table's row heights on certain changes of the table's
1466      * content.
1467      *
1468      * @param table the table
1469      * @param model the table model
1470      */
1471     private void registerRowHeightListener(final JTable table,
1472             SwingTableModel model)
1473     {
1474         model.addTableModelListener(new TableModelListener()
1475         {
1476             public void tableChanged(TableModelEvent e)
1477             {
1478                 if (e.getType() != TableModelEvent.DELETE)
1479                 {
1480                     getTableRowHeightUpdater().updateRowHeights(table,
1481                             e.getFirstRow(), e.getLastRow());
1482                 }
1483             }
1484         });
1485     }
1486 
1487     /**
1488      * Initializes custom renderer components for the columns of the specified
1489      * table.
1490      *
1491      * @param controller the {@code TableFormController}
1492      * @param tableModel the table model
1493      * @param table the table
1494      * @return a flag whether at least one custom renderer is defined
1495      */
1496     private static boolean initColumnRenderers(TableFormController controller,
1497             SwingTableModel tableModel, JTable table)
1498     {
1499         boolean foundCustomRenderer = false;
1500         for (int i = 0; i < controller.getColumnCount(); i++)
1501         {
1502             if (controller.hasRenderer(i))
1503             {
1504                 table.getColumnModel().getColumn(i)
1505                         .setCellRenderer(tableModel.getRenderer());
1506                 foundCustomRenderer = true;
1507             }
1508         }
1509 
1510         return foundCustomRenderer;
1511     }
1512 }