OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 67 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.ui.component;
15
 
73 ilm 16
import org.openconcerto.ui.coreanimation.Pulseable;
17 ilm 17
import org.openconcerto.utils.CollectionUtils;
18
import org.openconcerto.utils.checks.EmptyChangeSupport;
19
import org.openconcerto.utils.checks.EmptyListener;
20
import org.openconcerto.utils.checks.EmptyObj;
21
import org.openconcerto.utils.checks.MutableValueObject;
22
 
73 ilm 23
import java.awt.Component;
17 ilm 24
import java.awt.event.ItemEvent;
25
import java.awt.event.ItemListener;
26
import java.awt.event.MouseListener;
27
import java.beans.PropertyChangeListener;
28
import java.beans.PropertyChangeSupport;
29
import java.util.ArrayList;
30
import java.util.Collection;
31
import java.util.Iterator;
32
import java.util.LinkedHashMap;
33
import java.util.List;
34
import java.util.Map;
35
 
41 ilm 36
import javax.swing.BoxLayout;
17 ilm 37
import javax.swing.ButtonGroup;
38
import javax.swing.JPanel;
39
import javax.swing.JRadioButton;
40
 
41
/**
42
 * A group of radio buttons (only one can be selected at any time). The buttons are identified by an
43
 * ID and are created when {@link #init()} is called. A special empty button is not shown, it is
44
 * selected when init() finishes.
45
 *
46
 * @author Sylvain CUAZ
47
 * @param <V> type of value
48
 */
73 ilm 49
public class JRadioButtons<V> extends JPanel implements MutableValueObject<V>, EmptyObj, Pulseable {
17 ilm 50
 
51
    public static final class JStringRadioButtons extends JRadioButtons<String> {
52
        public JStringRadioButtons(final Collection<String> choices) {
41 ilm 53
            this(true, choices);
54
        }
55
 
56
        public JStringRadioButtons(final boolean lineAxis, final Collection<String> choices) {
17 ilm 57
            // keep order even if the passed collection is a set (it might still be ordered)
41 ilm 58
            super(lineAxis, CollectionUtils.fillMap(new LinkedHashMap<String, String>(), choices));
17 ilm 59
        }
60
    }
61
 
62
    private final PropertyChangeSupport supp;
63
    private final EmptyChangeSupport emptySupp;
64
    // {id => JRadioButton}
65
    private final Map<V, JRadioButton> choices;
66
    private final ButtonGroup group;
67
    // listeners to add to each of our buttons
68
    private final List<MouseListener> mouseListeners;
69
 
70
    private V emptyID;
71
    private V value;
72
 
73
    public JRadioButtons() {
41 ilm 74
        this(true, null);
17 ilm 75
    }
76
 
41 ilm 77
    public JRadioButtons(final boolean lineAxis, final Map<V, String> choices) {
17 ilm 78
        super();
79
        this.supp = new PropertyChangeSupport(this);
80
        this.emptySupp = new EmptyChangeSupport(this);
81
        // record the order of the buttons
82
        this.choices = new LinkedHashMap<V, JRadioButton>();
83
        this.group = new ButtonGroup();
84
        this.emptyID = null;
85
        this.mouseListeners = new ArrayList<MouseListener>();
86
        this.value = null;
87
 
88
        this.setOpaque(false);
41 ilm 89
        this.setLayout(new BoxLayout(this, lineAxis ? BoxLayout.LINE_AXIS : BoxLayout.PAGE_AXIS));
17 ilm 90
 
91
        if (choices != null)
92
            this.init(null, choices);
93
    }
94
 
67 ilm 95
    public final boolean isInited() {
96
        return this.choices.size() > 0;
97
    }
98
 
17 ilm 99
    /**
100
     * Init this component. If a value of <code>choices</code> is <code>null</code> the ID will be
101
     * used as the label.
102
     *
103
     * @param emptyID the id meaning that no button (on screen) is selected, can be
104
     *        <code>null</code>.
105
     * @param choices the map from id to label of the buttons to display, ids cannot be
106
     *        <code>null</code>.
107
     */
108
    public final void init(final V emptyID, final Map<V, String> choices) {
67 ilm 109
        if (choices.isEmpty())
110
            throw new IllegalArgumentException("Empty choices");
111
        if (this.isInited())
112
            throw new IllegalStateException("Already inited : " + this.choices.keySet());
17 ilm 113
        this.emptyID = emptyID;
114
        for (final Map.Entry<V, String> e : choices.entrySet()) {
115
            final V choice = e.getKey();
116
            if (choice == null)
117
                throw new IllegalArgumentException("A key is null in " + choices);
118
            final String choiceLabel = e.getValue();
119
 
120
            this.addBtn(choiceLabel == null ? choice.toString() : choiceLabel, choice);
121
        }
122
        // pour pouvoir "déselectionner"
123
        this.addBtn("--- ??? ---", this.emptyID);
124
 
125
        // Group the radio buttons.
126
        final Iterator<V> choicesIter = this.choices.keySet().iterator();
127
        while (choicesIter.hasNext()) {
128
            final V id = choicesIter.next();
129
            final JRadioButton btn = this.choices.get(id);
130
            this.group.add(btn);
131
            // maintain the value, otherwise we'd have to create a map from ButtonModel to ID
132
            // since this.group.getSelection() returns a ButtonModel
133
            btn.addItemListener(new ItemListener() {
134
                public void itemStateChanged(ItemEvent e) {
135
                    // ATTN we get 2 events for each change : first DESELECTED then SELECTED
136
                    // MAYBE ignore one but ATTN DESELECTED can be alone with
137
                    // ButtonGroup#clearSelection()
138
                    if (e.getStateChange() == ItemEvent.SELECTED)
139
                        valueChanged(id);
140
                    // DESELECTED
141
                    else if (JRadioButtons.this.value == id)
142
                        valueChanged(null);
143
                }
144
            });
145
            // ne pas mettre l'indéfini dans l'interface
146
            if (id != this.emptyID) {
147
                this.add(btn);
148
            }
149
        }
67 ilm 150
        this.revalidate();
151
        assert this.isInited();
17 ilm 152
 
67 ilm 153
        // select the button
154
        this.setValue(this.value);
17 ilm 155
    }
156
 
157
    private void valueChanged(V id) {
158
        this.value = id;
159
        this.supp.firePropertyChange("value", null, id);
160
        this.emptySupp.fireEmptyChange(isEmpty());
161
    }
162
 
163
    private final void addBtn(String btnLabel, V id) {
164
        final JRadioButton btn = new JRadioButton(btnLabel);
25 ilm 165
        btn.setOpaque(false);
67 ilm 166
        btn.setEnabled(this.isEnabled());
17 ilm 167
        for (final MouseListener l : this.mouseListeners) {
168
            btn.addMouseListener(l);
169
        }
170
        this.choices.put(id, btn);
171
    }
172
 
67 ilm 173
    @Override
17 ilm 174
    public final void setEnabled(boolean b) {
175
        super.setEnabled(b);
176
        for (final JRadioButton btn : this.choices.values()) {
177
            btn.setEnabled(b);
178
        }
179
    }
180
 
73 ilm 181
    @Override
182
    public Collection<? extends Component> getPulseComponents() {
183
        return this.choices.values();
184
    }
185
 
17 ilm 186
    // ** valueObject
187
 
188
    /**
189
     * Set the selected button.
190
     *
191
     * @param id the id of the button to select, <code>null</code> means the empty ID.
192
     */
193
    public void setValue(V id) {
67 ilm 194
        if (!this.isInited()) {
195
            this.valueChanged(id);
196
        } else {
197
            // treat unknown value as empty
198
            // MAYBE add a boolean to throw an exception
199
            if (id == null || !this.choices.containsKey(id))
200
                id = this.emptyID;
201
            this.choices.get(id).setSelected(true);
202
        }
17 ilm 203
    }
204
 
205
    public final V getValue() {
206
        return this.value;
207
    }
208
 
209
    /**
210
     * Selects the empty ID.
211
     */
212
    public final void resetValue() {
213
        this.setValue(null);
214
    }
215
 
216
    public final void addValueListener(PropertyChangeListener l) {
217
        this.supp.addPropertyChangeListener(l);
218
    }
219
 
220
    public final void rmValueListener(PropertyChangeListener l) {
221
        this.supp.removePropertyChangeListener(l);
222
    }
223
 
224
    // ** emptyObj
225
 
226
    public boolean isEmpty() {
227
        return this.getValue() == null || this.getValue().equals(this.emptyID);
228
    }
229
 
230
    public void addEmptyListener(EmptyListener l) {
231
        this.emptySupp.addEmptyListener(l);
232
    }
233
 
73 ilm 234
    @Override
235
    public void removeEmptyListener(EmptyListener l) {
236
        this.emptySupp.removeEmptyListener(l);
237
    }
238
 
17 ilm 239
    // ** mouseListeners
240
 
241
    @Override
242
    public void addMouseListener(MouseListener l) {
243
        this.mouseListeners.add(l);
244
        for (final JRadioButton radio : this.choices.values()) {
245
            radio.addMouseListener(l);
246
        }
247
    }
248
 
249
    @Override
250
    public void removeMouseListener(MouseListener l) {
251
        this.mouseListeners.remove(l);
252
        for (final JRadioButton radio : this.choices.values()) {
253
            radio.removeMouseListener(l);
254
        }
255
    }
256
}