1 /***
2 * JDualComboBox.java
3 *
4 * This file is part of the creme library.
5 * The creme library intends to ease the development effort of large
6 * applications by providing easy to use support classes.
7 *
8 * Copyright (C) 2003 Denis Bregeon
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 *
24 * contact information: dbregeon@sourceforge.net
25 */
26 package org.jcreme.swing;
27
28 import java.awt.Component;
29 import java.util.Arrays;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.Vector;
33
34 import javax.swing.DefaultListCellRenderer;
35 import javax.swing.JComboBox;
36 import javax.swing.JList;
37 import javax.swing.JSeparator;
38 import javax.swing.ListCellRenderer;
39 import javax.swing.MutableComboBoxModel;
40 import javax.swing.event.ListDataEvent;
41 import javax.swing.event.ListDataListener;
42
43 /***
44 * This class enables to display a JComboBox which model is made of two
45 * different lists. This is intended to present a shorter list at the top of the
46 * choice list and a more comprehensive list at the bottom.
47 *
48 * @author $Author: dbregeon $
49 * @version $Revision: 1.1 $
50 */
51 public class JDualComboBox extends JComboBox {
52 /***
53 * This is a place holder in the ComboBoxModel to mark the separation
54 * between the constituent lists.
55 */
56 public static final String SEPARATOR = "-";
57
58 /***
59 * This class joins two separate lists of values into a MutableComboBoxModel
60 * implementation.
61 *
62 * @author $Author: dbregeon $
63 * @version $Revision: 1.1 $
64 */
65 public class DualComboBoxModel implements MutableComboBoxModel {
66 /***
67 * The list of values in the top part of the list.
68 */
69 protected Vector preferredValues = new Vector();
70
71 /***
72 * The list of values in the bottom part of the list.
73 */
74 protected Vector additionalValues = new Vector();
75
76 /***
77 * A reminder of the current selection.
78 */
79 protected Object selectedItem = null;
80
81 /***
82 * The listeners to this MutableComboBoxModel.
83 */
84 protected HashSet dataChangeListeners = new HashSet();
85
86 /***
87 * Builds the DualComboBoxModel from Object arrays.
88 *
89 * @param preferred
90 * the values to be displayed at the top of the list.
91 * @param additional
92 * the values to be displayed at the bottom of the list.
93 */
94 public DualComboBoxModel(Object[] preferred, Object[] additional) {
95 if (preferred != null) {
96 this.preferredValues.addAll(Arrays.asList(preferred));
97 }
98 if (additional != null) {
99 this.additionalValues.addAll(Arrays.asList(additional));
100 }
101 }
102
103 /***
104 * Builds the DualComboBoxModel from Vectors.
105 *
106 * @param preferred
107 * the values to be displayed at the top of the list.
108 * @param additional
109 * the values to be displayed at the bottom of the list.
110 */
111 public DualComboBoxModel(Vector preferred, Vector additional) {
112 if (preferred != null) {
113 this.preferredValues.addAll(preferred);
114 }
115 if (additional != null) {
116 this.additionalValues.addAll(additional);
117 }
118 }
119
120 /***
121 * @see javax.swing.ComboBoxModel#getSelectedItem()
122 */
123 public Object getSelectedItem() {
124 return this.selectedItem;
125 }
126
127 /***
128 * @see javax.swing.ComboBoxModel#setSelectedItem(java.lang.Object)
129 */
130 public void setSelectedItem(Object anItem) {
131 if ((this.preferredValues.contains(anItem))
132 || (this.additionalValues.contains(anItem))) {
133 this.selectedItem = anItem;
134 }
135 }
136
137 /***
138 * @see javax.swing.ListModel#addListDataListener(javax.swing.event.ListDataListener)
139 */
140 public void addListDataListener(ListDataListener l) {
141 if (l != null) {
142 this.dataChangeListeners.add(l);
143 }
144 }
145
146 /***
147 * @see javax.swing.ListModel#getElementAt(int)
148 */
149 public Object getElementAt(int index) {
150 Object result = null;
151 if ((index >= 0) && (index < getSize())) {
152 if (index < this.preferredValues.size()) {
153 result = this.preferredValues.elementAt(index);
154 } else if (index > this.preferredValues.size()) {
155 result = this.additionalValues.elementAt(index
156 - this.preferredValues.size() - 1);
157 } else {
158 result = SEPARATOR;
159 }
160 }
161 return result;
162 }
163
164 /***
165 * @see javax.swing.ListModel#getSize()
166 */
167 public int getSize() {
168 return this.preferredValues.size() + this.additionalValues.size()
169 + 1;
170 }
171
172 /***
173 * @see javax.swing.ListModel#removeListDataListener(javax.swing.event.ListDataListener)
174 */
175 public void removeListDataListener(ListDataListener l) {
176 if (l != null) {
177 this.dataChangeListeners.remove(l);
178 }
179 }
180
181 /***
182 * @see javax.swing.MutableComboBoxModel#addElement(java.lang.Object)
183 */
184 public void addElement(Object obj) {
185 this.preferredValues.add(obj);
186 fireDataChangeEvent(ListDataEvent.INTERVAL_ADDED,
187 this.preferredValues.size() - 1, this.preferredValues
188 .size() - 1);
189 }
190
191 /***
192 * @see javax.swing.MutableComboBoxModel#insertElementAt(java.lang.Object,
193 * int)
194 */
195 public void insertElementAt(Object obj, int index) {
196 if (index < this.preferredValues.size()) {
197 this.preferredValues.insertElementAt(obj, index);
198 } else if (index == this.preferredValues.size()) {
199 this.preferredValues.add(obj);
200 } else if (index >= this.preferredValues.size() + 1) {
201 this.additionalValues.insertElementAt(obj, index
202 - this.preferredValues.size() - 1);
203 }
204 fireDataChangeEvent(ListDataEvent.INTERVAL_ADDED, index, index);
205 }
206
207 /***
208 * @see javax.swing.MutableComboBoxModel#removeElement(java.lang.Object)
209 */
210 public void removeElement(Object obj) {
211 this.preferredValues.remove(obj);
212 this.additionalValues.remove(obj);
213 fireDataChangeEvent(ListDataEvent.CONTENTS_CHANGED, 0, getSize());
214 }
215
216 /***
217 * @see javax.swing.MutableComboBoxModel#removeElementAt(int)
218 */
219 public void removeElementAt(int index) {
220 if (index < this.preferredValues.size()) {
221 this.preferredValues.removeElementAt(index);
222 } else if (index >= this.preferredValues.size() + 1) {
223 this.additionalValues.removeElementAt(index
224 - this.preferredValues.size() - 1);
225 }
226 fireDataChangeEvent(ListDataEvent.INTERVAL_REMOVED, index, index);
227 }
228
229 protected void fireDataChangeEvent(int type, int index0, int index1) {
230 ListDataEvent event = new ListDataEvent(this, type, index0, index1);
231 Iterator iter = this.dataChangeListeners.iterator();
232 while (iter.hasNext()) {
233 ((ListDataListener) iter.next()).contentsChanged(event);
234 }
235 }
236
237 }
238
239 /***
240 * This ListCellRenderer enables to handle the separator in a
241 * DualComboBoxModel. It otherwise relies on another renderer (linked to the
242 * UI) to display the actual list values.
243 *
244 * @author $Author: dbregeon $
245 * @version $Revision: 1.1 $
246 */
247 protected class CompositeRenderer extends DefaultListCellRenderer {
248 /***
249 * The default Renderer provided by the UI.
250 */
251 protected ListCellRenderer baseRenderer = null;
252
253 /***
254 * The component that displays the separation between top and bottom
255 * parts of the list.
256 */
257 protected JSeparator separator = new JSeparator();
258
259 /***
260 * Builds a CompositeRenderer from a pre-existing renderer.
261 *
262 * @param renderer
263 * a UI linked renderer.
264 */
265 public CompositeRenderer(ListCellRenderer renderer) {
266 this.baseRenderer = renderer;
267 }
268
269 /***
270 * @see javax.swing.ListCellRenderer#getListCellRendererComponent(javax.swing.JList,
271 * java.lang.Object, int, boolean, boolean)
272 */
273 public Component getListCellRendererComponent(JList list, Object value,
274 int index, boolean isSelected, boolean cellHasFocus) {
275 Component result = null;
276 if ((value == SEPARATOR) || (this.baseRenderer == null)) {
277 result = this.separator;
278 } else {
279 result = this.baseRenderer.getListCellRendererComponent(list,
280 value, index, isSelected, cellHasFocus);
281 }
282 return result;
283 }
284
285 /***
286 * Provides access to the renderer that is used to display list values.
287 *
288 * @return the renderer that is used to display list values.
289 */
290 public ListCellRenderer getBaseRenderer() {
291 return this.baseRenderer;
292 }
293
294 }
295
296 /***
297 * Creates a JDualComboBox wih no data.
298 *
299 */
300 public JDualComboBox() {
301 setRenderer(super.getRenderer());
302 }
303
304 /***
305 * Creates a JDualComboBox wih Object arrays data.
306 *
307 * @param preferredValues
308 * the values to be displayed in the top part of the list.
309 * @param additionalValues
310 * the values to be displayed in the bottom part of the list.
311 */
312 public JDualComboBox(Object[] preferredValues, Object[] additionalValues) {
313 setRenderer(super.getRenderer());
314 setModel(new DualComboBoxModel(preferredValues, additionalValues));
315 }
316
317 /***
318 * Creates a JDualComboBox wih Vector arrays data.
319 *
320 * @param preferredValues
321 * the values to be displayed in the top part of the list.
322 * @param additionalValues
323 * the values to be displayed in the bottom part of the list.
324 */
325 public JDualComboBox(Vector preferredValues, Vector additionalValues) {
326 setRenderer(super.getRenderer());
327 setModel(new DualComboBoxModel(preferredValues, additionalValues));
328 }
329
330 /***
331 * @see javax.swing.JComboBox#setRenderer(javax.swing.ListCellRenderer)
332 */
333 public void setRenderer(ListCellRenderer aRenderer) {
334 super.setRenderer(new CompositeRenderer(aRenderer));
335 }
336
337 /***
338 * @see javax.swing.JComboBox#getRenderer()
339 */
340 public ListCellRenderer getRenderer() {
341 ListCellRenderer result = super.getRenderer();
342 if (result instanceof CompositeRenderer) {
343 result = ((CompositeRenderer) super.getRenderer())
344 .getBaseRenderer();
345 }
346 return result;
347 }
348 }