001package arcommon.ui.component;
002
003import java.awt.GridBagConstraints;
004import java.awt.GridBagLayout;
005import java.awt.Insets;
006import java.awt.event.ComponentAdapter;
007import java.awt.event.ComponentEvent;
008import java.beans.PropertyChangeEvent;
009import java.beans.PropertyChangeListener;
010import java.util.ArrayList;
011import java.util.Arrays;
012import java.util.Collection;
013import java.util.HashMap;
014import java.util.Iterator;
015import java.util.List;
016import java.util.Map;
017
018import javax.swing.JCheckBox;
019import javax.swing.JComponent;
020import javax.swing.JLabel;
021import javax.swing.JPanel;
022import javax.swing.JRadioButton;
023import javax.swing.JScrollPane;
024import javax.swing.SwingUtilities;
025import javax.swing.border.EmptyBorder;
026import javax.swing.border.TitledBorder;
027
028public class LabelFieldJComponent extends JScrollPane {
029    private static final int LABEL_LEFT_SPACE = 2;
030    private static final int FIELD_LEFT_SPACE = 2;
031
032    private class LabelFieldJComponentData {
033        private boolean visible;
034        private JComponent leftComponent;
035        private Collection<JComponent> fieldComponents;
036        private int[] fullIndexes;
037        private boolean[] scrollPanes;
038        private String titledBorder;
039        
040        public LabelFieldJComponentData() {
041            visible = true;
042        }
043    }
044    
045    private ArrayList<LabelFieldJComponentData> components;
046    private JPanel panel;
047    
048    public LabelFieldJComponent() {
049        getVerticalScrollBar().setUnitIncrement(8);
050 
051        panel = new JPanel();
052        setViewportView(panel);
053        panel.setLayout(new GridBagLayout());
054    }
055    
056    public void addCheckBox(JCheckBox jcheckBox) {
057        addLabelField(null, jcheckBox);
058    }
059    
060    public void addCheckBox(JCheckBox jcheckBox, JComponent[] pcomponents, int fullIndex) {
061        addCheckBox(jcheckBox, pcomponents, new int[] {fullIndex});
062    }
063    
064    public void addCheckBox(JCheckBox jcheckBox, JComponent[] pcomponents, int[] fullIndexes) {
065        addCheckBox(jcheckBox, pcomponents, fullIndexes, null);
066    }
067    
068    public void addCheckBox(JCheckBox jcheckBox, JComponent[] pcomponents, int[] fullIndexes, int[] scrollPaneIndexes) {
069        JComponent[] allComponents = new JComponent[pcomponents.length + 1];
070        allComponents[0] = jcheckBox;
071        System.arraycopy(pcomponents, 0, allComponents, 1, pcomponents.length);
072        if (fullIndexes != null) {
073            for (int i = 0; i < fullIndexes.length; i++) {
074                fullIndexes[i] += 1;
075            }
076        }
077        if (scrollPaneIndexes != null) {
078            for (int i = 0; i < scrollPaneIndexes.length; i++) {
079                scrollPaneIndexes[i] += 1;
080            }
081        }
082        addLabelField(null, allComponents, fullIndexes, scrollPaneIndexes);
083    }
084    
085    /**
086     *  @see #addCheckBox
087     */
088    public void addLabelField(String label, JCheckBox checkBox) {
089        if (label != null) {
090            throw new IllegalArgumentException("label != null");
091        }
092        
093        // This will show the JCheckBox as a value (On the right of the labels)
094        // addLabelField(label, new JComponent[] {checkBox}, new int[] {0});
095        
096        // This will show the JCheckBox on the left and filling the entire line
097        LabelFieldJComponentData labelFieldJComponentData = new LabelFieldJComponentData();
098        labelFieldJComponentData.leftComponent = checkBox;
099        addComponent(labelFieldJComponentData);
100    }
101
102    public void addRadioButton(JRadioButton radioButton) {
103        addLabelField(null, radioButton);
104    }
105    
106    /**
107     *  @see #addRadioButton(JRadioButton)
108     */
109    public void addLabelField(String label, JRadioButton radioButton) {
110        if (label != null) {
111            throw new IllegalArgumentException("label != null");
112        }
113
114        LabelFieldJComponentData labelFieldJComponentData = new LabelFieldJComponentData();
115        labelFieldJComponentData.leftComponent = radioButton;
116        addComponent(labelFieldJComponentData);
117    }
118    
119    public void addLabelField(String label, JComponent component) {
120        addLabelField(label, new JComponent[] {component}, new int[] {0});
121    }
122    
123    public void addLabelField(String label, JComponent[] pcomponents, int fullIndex) {
124        addLabelField(label, pcomponents, new int[] {fullIndex});
125    }
126    
127    public void addLabelField(String label, JComponent[] pcomponents, int[] fullIndexes) {
128        addLabelField(label, pcomponents, fullIndexes, null);
129    }
130    
131    public void addLabelField(String label, JComponent[] pcomponents, int[] fullIndexes, int[] scrollPaneIndexes) {
132        ArrayList<JComponent> arrayList = new ArrayList<JComponent>();
133        for (int i = 0; i < pcomponents.length; i++) {
134            arrayList.add(pcomponents[i]);
135        }
136        addLabelField(label, arrayList, fullIndexes, scrollPaneIndexes);
137    }
138    
139    public void addLabelField(String label, List<JComponent> pcomponents, int fullIndex) {
140        addLabelField(label, pcomponents, new int[] {fullIndex});
141    }
142    
143    public void addLabelField(String label, List<JComponent> pcomponents, int[] fullIndexes) {
144        addLabelField(label, pcomponents, fullIndexes, null);
145    }
146    
147    public void addLabelField(String label, List<JComponent> pcomponents, int[] fullIndexes, int[] scrollPaneIndexes) {
148        addLabelField(label, null, pcomponents, fullIndexes, scrollPaneIndexes);
149    }
150    
151    public void addLabelField(String label, Character mnemonic, List<JComponent> pcomponents, int[] fullIndexes, int[] scrollPaneIndexes) {
152        if (pcomponents == null) {
153            throw new IllegalArgumentException("pcomponents == null");
154        }
155        if (pcomponents.size() == 0) {
156            throw new IllegalArgumentException("pcomponents.size() == 0");
157        }
158
159        final LabelFieldJComponentData labelFieldJComponentData = new LabelFieldJComponentData();
160
161        int offset = 0;
162        if (label != null) {
163            if (label.length() == 0) {
164                throw new RuntimeException("label.length() == 0");
165            }
166            JLabel plabel = new JLabel(label);
167            labelFieldJComponentData.leftComponent = plabel;
168            plabel.setLabelFor(pcomponents.get(0));
169            if (mnemonic != null) {
170                plabel.setDisplayedMnemonic(mnemonic.charValue());
171            }
172        } else {
173            JComponent jComponent = pcomponents.get(0);
174            if (jComponent instanceof JCheckBox) {
175                pcomponents.remove(0);
176                labelFieldJComponentData.leftComponent = jComponent;
177                offset = 1;
178            }
179        }
180
181        for (JComponent component : pcomponents) {
182            if (component == null) {
183                throw new IllegalArgumentException("component == null " + pcomponents);
184            }
185            
186            if (label != null) {
187                component.addPropertyChangeListener("enabled", new PropertyChangeListener() {
188                    @Override
189                    public void propertyChange(PropertyChangeEvent evt) {
190                        updateLabelEnabled(labelFieldJComponentData);
191                    }
192                });
193                
194                component.addComponentListener(new ComponentAdapter () {
195                    public void componentShown(ComponentEvent e) {
196                        updateLabelVisible(labelFieldJComponentData);
197                    }
198
199                    public void componentHidden(ComponentEvent e) {
200                        updateLabelVisible(labelFieldJComponentData);
201                    }
202                });
203            }
204        }
205        labelFieldJComponentData.fieldComponents = new ArrayList<JComponent>(pcomponents);
206        updateLabelEnabled(labelFieldJComponentData);
207        updateLabelVisible(labelFieldJComponentData);
208
209        if (fullIndexes != null) {
210            for (int i = 0; i < fullIndexes.length; i++) {
211                int fullIndex = fullIndexes[i];
212                if (fullIndex == 0 && offset == 1) {
213                    throw new IllegalArgumentException("fullIndex == 0 && offset == 1 " + Arrays.toString(fullIndexes));
214                }
215                fullIndex -= offset;
216                fullIndexes[i] = fullIndex;
217                if (fullIndex < 0 || fullIndex >= pcomponents.size()) {
218                    throw new IllegalArgumentException("fullIndex < 0 || fullIndex >= pcomponents.size() " + fullIndex + " " + pcomponents.size());
219                }
220            }
221            labelFieldJComponentData.fullIndexes = fullIndexes;
222        }
223        
224        if (scrollPaneIndexes != null) {
225            labelFieldJComponentData.scrollPanes = new boolean[pcomponents.size()];
226            for (int i = 0; i < scrollPaneIndexes.length; i++) {
227                labelFieldJComponentData.scrollPanes[scrollPaneIndexes[i]] = true;
228            }
229        } else {
230            for (int i = 0; i < pcomponents.size(); i++) {
231                if (pcomponents.get(i) instanceof JScrollPane) {
232                    if (labelFieldJComponentData.scrollPanes == null) {
233                        labelFieldJComponentData.scrollPanes = new boolean[pcomponents.size()];
234                    }
235                    labelFieldJComponentData.scrollPanes[i] = true;
236                }
237            }
238        }
239        
240        addComponent(labelFieldJComponentData);
241    }
242
243    private void updateLabelEnabled(LabelFieldJComponentData labelFieldJComponentData) {
244        boolean enabled = false;
245        for (JComponent component : labelFieldJComponentData.fieldComponents) {
246            if (component.isEnabled() && component.isVisible()) {
247                enabled = true;
248                break;
249            }
250        }
251        
252        if (labelFieldJComponentData.leftComponent != null && labelFieldJComponentData.leftComponent.isEnabled() != enabled) {
253            labelFieldJComponentData.leftComponent.setEnabled(enabled);
254        }
255    }
256    
257    private void updateLabelVisible(LabelFieldJComponentData labelFieldJComponentData) {
258        boolean visible = false;
259        for (JComponent component : labelFieldJComponentData.fieldComponents) {
260            if (component.isVisible()) {
261                visible = true;
262                break;
263            }
264        }
265        
266        // Visible has been changed => Enable of left component might need to be changed.
267        updateLabelEnabled(labelFieldJComponentData);
268        
269        if (labelFieldJComponentData.leftComponent != null && labelFieldJComponentData.leftComponent.isVisible() != visible) {
270            labelFieldJComponentData.leftComponent.setVisible(visible);
271        }
272
273        boolean update = false;
274        if (labelFieldJComponentData.visible != visible) {
275            labelFieldJComponentData.visible = visible;
276            update = true;
277        }
278        
279        if (labelFieldJComponentData.fieldComponents != null && labelFieldJComponentData.fieldComponents.size() != 1) {
280            update = true;
281        }
282        
283        if (update && components != null) {
284            updateComponents();
285            panel.revalidate();
286        }
287    }
288
289    public void addHeader(String header) {
290        JLabel plabel = new JLabel(header);
291        plabel.setBorder(new EmptyBorder(7, 0, 0, 0));
292
293        LabelFieldJComponentData labelFieldJComponentData = new LabelFieldJComponentData();
294        labelFieldJComponentData.leftComponent = plabel;
295        addComponent(labelFieldJComponentData);
296    }
297    
298    public void addTitledBorder(String title) {
299        LabelFieldJComponentData labelFieldJComponentData = new LabelFieldJComponentData();
300        labelFieldJComponentData.titledBorder = title;
301        addComponent(labelFieldJComponentData);
302    }
303    
304    private GridBagConstraints createGridBagConstraints() {
305        GridBagConstraints gridBagConstraints = new GridBagConstraints();
306        gridBagConstraints.gridy = -1; // -1 is because updateComponents() executes gridBagConstraints.gridy++ before use.
307        return gridBagConstraints;
308    }
309    
310//    private boolean dirty;
311    
312    private void addComponent(LabelFieldJComponentData labelFieldJComponentData) {
313        if (components == null) {
314            components = new ArrayList<LabelFieldJComponentData>();
315        }
316        components.add(labelFieldJComponentData);
317
318        updateComponents();
319
320        // This doesn't work because it will cause the LabelFieldJComponent to not contain any JComponents when it's layouted, and its preferred size is thereby 0,0.
321/*        dirty = true;
322        Runnable runnable = new Runnable() {
323            public void run() {
324                updateComponentsIfDirty();
325            }
326        };
327        SwingUtilities.invokeLater(runnable);*/
328    }
329    
330/*    private void updateComponentsIfDirty() {
331        if (!dirty) {
332            return ;
333        }
334        
335        dirty = false;
336        updateComponents();
337    }*/
338    
339    private void updateComponents() {
340        int maximumNumberOfComponents = 0;
341        Iterator<LabelFieldJComponentData> i = components.iterator();
342        while (i.hasNext()) {
343            LabelFieldJComponentData labelFieldJComponentData = i.next();
344            if (labelFieldJComponentData.fieldComponents != null) {
345                int numberOfVisibleComponents = 0;
346                for (JComponent component : labelFieldJComponentData.fieldComponents) {
347                    if (component.isVisible()) {
348                        numberOfVisibleComponents++;
349                    }
350                }
351                if (maximumNumberOfComponents < numberOfVisibleComponents) {
352                    maximumNumberOfComponents = numberOfVisibleComponents;
353                }
354            }
355        }
356
357        panel.removeAll();
358        JPanel addToPanel = panel;
359        GridBagConstraints titledBorderGridBagConstraints = null;
360        boolean titleBorderFound = false;
361        
362        GridBagConstraints gridBagConstraints = createGridBagConstraints();
363        boolean scrollPaneFound = false;
364        i = components.iterator();
365        while (i.hasNext()) {
366            LabelFieldJComponentData labelFieldJComponentData = i.next();
367            if (!labelFieldJComponentData.visible) {
368                continue;
369            }
370            
371            gridBagConstraints.gridy++;
372            
373            if (labelFieldJComponentData.titledBorder != null) {
374                titleBorderFound = true;
375                
376                if (titledBorderGridBagConstraints == null) {
377                    titledBorderGridBagConstraints = gridBagConstraints;
378                    titledBorderGridBagConstraints.anchor = GridBagConstraints.WEST;
379                    titledBorderGridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
380                    titledBorderGridBagConstraints.weightx = 1;
381                    titledBorderGridBagConstraints.gridx = 0;
382                    titledBorderGridBagConstraints.gridwidth = maximumNumberOfComponents + 1;
383                }
384
385                JPanel titledBorderPanel = new JPanel();
386                panel.add(titledBorderPanel, titledBorderGridBagConstraints);
387                titledBorderPanel.setBorder(new TitledBorder(labelFieldJComponentData.titledBorder));
388                titledBorderPanel.setLayout(new GridBagLayout());
389                titledBorderGridBagConstraints.gridy++;
390                
391                addToPanel = titledBorderPanel;
392                gridBagConstraints = createGridBagConstraints();
393                continue;
394            }
395            
396            gridBagConstraints.weighty = 0.0;
397            gridBagConstraints.anchor = GridBagConstraints.WEST;
398            gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
399            gridBagConstraints.insets = new Insets(0, LABEL_LEFT_SPACE, 0, 0);
400            if (labelFieldJComponentData.scrollPanes != null) {
401                scrollPaneFound = true;
402            }
403
404            JComponent leftComponent = labelFieldJComponentData.leftComponent;
405            Collection<JComponent> fieldComponents = labelFieldJComponentData.fieldComponents;
406            if (fieldComponents == null) { // JCheckBox or JRadioGroup
407                gridBagConstraints.gridx = 0;
408                gridBagConstraints.weightx = 0.01;
409                gridBagConstraints.gridwidth = maximumNumberOfComponents + 1;
410                addToPanel.add(leftComponent, gridBagConstraints);
411                continue;
412            }
413            
414            if (leftComponent != null) {
415                gridBagConstraints.gridx = 0;
416                gridBagConstraints.weightx = 0.0;
417                gridBagConstraints.ipadx = FIELD_LEFT_SPACE;
418                gridBagConstraints.ipady = 0;
419                gridBagConstraints.gridwidth = 1;
420                addToPanel.add(leftComponent, gridBagConstraints);
421            }
422
423            List<JComponent> visibleComponents = new ArrayList<JComponent>();
424            Iterator<JComponent> iterator = fieldComponents.iterator();
425            while (iterator.hasNext()) {
426                JComponent fieldComponent = iterator.next();
427                if (!fieldComponent.isVisible()) {
428                    continue;
429                }
430                
431                visibleComponents.add(fieldComponent);
432            }
433                
434            int index = 0;
435            iterator = visibleComponents.iterator();
436            while (iterator.hasNext()) {
437                JComponent fieldComponent = iterator.next();
438                if (!fieldComponent.isVisible()) {
439                    continue;
440                }
441                
442                if (visibleComponents.size() == 1) {
443                    gridBagConstraints.gridx = 1;
444                    gridBagConstraints.weightx = 1.0;
445                    gridBagConstraints.gridwidth = maximumNumberOfComponents;
446                    if (labelFieldJComponentData.scrollPanes != null) {
447                        gridBagConstraints.weighty = 1.0;
448                        gridBagConstraints.fill = GridBagConstraints.BOTH;
449                    } else {
450                        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
451                    }
452                } else {
453                    boolean full = false;
454                    for (int j = 0; j < labelFieldJComponentData.fullIndexes.length; j++) {
455                        if (labelFieldJComponentData.fullIndexes[j] == index) {
456                            full = true;
457                            break;
458                        }
459                    }
460                    if (full) {
461                        gridBagConstraints.weightx = 1.0;
462                    } else {
463                        gridBagConstraints.weightx = 0.0;
464                    }
465                    if (labelFieldJComponentData.scrollPanes != null) {
466                        gridBagConstraints.weighty = 1.0;
467                        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
468                        gridBagConstraints.fill = GridBagConstraints.BOTH;
469                        if (labelFieldJComponentData.scrollPanes[index]) {
470                            gridBagConstraints.weightx = 1.0;
471                        }
472                    } else {
473                        gridBagConstraints.anchor = GridBagConstraints.WEST;
474                        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
475                    }
476                    if (index == visibleComponents.size() - 1) {
477                        gridBagConstraints.gridwidth = maximumNumberOfComponents - index;
478                    } else {
479                        gridBagConstraints.gridwidth = 1;
480                    }
481                    gridBagConstraints.gridx = index++ + 1;
482                }
483                gridBagConstraints.ipadx = 0;
484                gridBagConstraints.ipady = 0;
485                gridBagConstraints.insets = new Insets(0, 0, 0, 0);
486                addToPanel.add(fieldComponent, gridBagConstraints);
487            }
488        }
489        
490        if (!scrollPaneFound) {
491            gridBagConstraints.gridy++;
492            gridBagConstraints.gridx = 0;
493            gridBagConstraints.weighty = 1.0;
494            gridBagConstraints.weightx = 0.0;
495            gridBagConstraints.gridwidth = 1;
496            gridBagConstraints.insets = new Insets(0, 0, 0, 0);
497            JLabel label = new JLabel("");
498            panel.add(label, gridBagConstraints);
499        }
500        
501        if (titleBorderFound) {
502            Runnable runnable = new Runnable() {
503                public void run() {
504                    updateTitleBorderLeftComponents();
505                }
506            };
507            SwingUtilities.invokeLater(runnable);
508        }
509    }
510    
511    private void updateTitleBorderLeftComponents() {
512        Iterator<LabelFieldJComponentData> i = components.iterator();
513        int max = 0;
514        Map<JComponent, GridBagConstraints> leftToGridBagConstraintsMap = new HashMap<JComponent, GridBagConstraints>();
515        Map<JComponent, GridBagLayout> leftToGridBagLayoutMap = new HashMap<JComponent, GridBagLayout>();
516        while (i.hasNext()) {
517            LabelFieldJComponentData labelFieldJComponentData = i.next();
518            
519            if (labelFieldJComponentData.titledBorder != null) {
520                continue;
521            }
522            
523            if (labelFieldJComponentData.fieldComponents == null) {
524                continue;
525            }
526            
527            JComponent leftComponent = labelFieldJComponentData.leftComponent;
528            if (leftComponent == null) {
529                continue;
530            }
531            
532            JPanel tempPanel = (JPanel)leftComponent.getParent();
533            GridBagLayout gridBagLayout = (GridBagLayout)tempPanel.getLayout();
534            GridBagConstraints gridBagConstraints = gridBagLayout.getConstraints(leftComponent);
535            max = Math.max(max, leftComponent.getSize().width + gridBagConstraints.ipadx);
536            leftToGridBagConstraintsMap.put(leftComponent, gridBagConstraints);
537            leftToGridBagLayoutMap.put(leftComponent, gridBagLayout);
538        }
539        
540        Iterator<JComponent> componentIterator = leftToGridBagConstraintsMap.keySet().iterator();
541        while (componentIterator.hasNext()) {
542            JComponent leftComponent = componentIterator.next();
543            GridBagConstraints gridBagConstraints = leftToGridBagConstraintsMap.get(leftComponent);
544            int padding = max - leftComponent.getSize().width;
545            gridBagConstraints.ipadx = padding;
546            GridBagLayout gridBagLayout = leftToGridBagLayoutMap.get(leftComponent);
547            gridBagLayout.setConstraints(leftComponent, gridBagConstraints);
548            JPanel tempPanel = (JPanel)leftComponent.getParent();
549            tempPanel.revalidate();
550        }
551    }
552    
553    private static final long serialVersionUID = -7751875158251563952L;
554}