001 package com.softnetConsult.utils.swing;
002
003 import java.awt.BorderLayout;
004 import java.awt.Component;
005 import java.awt.Dimension;
006 import java.awt.FontMetrics;
007 import java.util.Vector;
008
009 import javax.accessibility.Accessible;
010 import javax.swing.ComboBoxModel;
011 import javax.swing.JComboBox;
012 import javax.swing.JScrollPane;
013 import javax.swing.plaf.ComboBoxUI;
014 import javax.swing.plaf.basic.BasicComboPopup;
015
016
017 /**
018 * Implements a JComboBox that dynamically adjusts the size of its selection drop-down
019 * based on the assumption that all items will be displayed using their toString method.
020 *
021 * <p style="font-size:smaller;">This product includes software developed by the
022 * <strong>SoftNet-Consult Java Utility Library</strong> project and its contributors.<br />
023 * (<a href="http://java-tools.sourceforge.net" target="_blank">http://java-tools.sourceforge.net</a>)<br />
024 * Copyright (c) 2007-2008 SoftNet-Consult.<br />
025 * Copyright (c) 2007-2008 G. Paperin.<br />
026 * All rights reserved.
027 * </p>
028 * <p style="font-size:smaller;">File: ResizablePopupComboBox.java<br />
029 * Library API version: {@value com.softnetConsult.utils.APIProperties#apiVersion}<br />
030 * Java compliance version: {@value com.softnetConsult.utils.APIProperties#javaComplianceVersion}
031 * </p>
032 * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or
033 * without modification, are permitted provided that the following terms and conditions are met:
034 * </p>
035 * <p style="font-size:smaller;">1. Redistributions of source code must retain the above
036 * acknowledgement of the SoftNet-Consult Java Utility Library project, the above copyright
037 * notice, this list of conditions and the following disclaimer.<br />
038 * 2. Redistributions in binary form must reproduce the above acknowledgement of the
039 * SoftNet-Consult Java Utility Library project, the above copyright notice, this list of
040 * conditions and the following disclaimer in the documentation and/or other materials
041 * provided with the distribution.<br />
042 * 3. All advertising materials mentioning features or use of this software or any derived
043 * software must display the following acknowledgement:<br />
044 * <em>This product includes software developed by the SoftNet-Consult Java Utility Library
045 * project and its contributors.</em>
046 * </p>
047 * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY
048 * OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
049 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
050 * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
051 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
052 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
053 * </p>
054 * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>)
055 * @version {@value com.softnetConsult.utils.APIProperties#apiVersion}
056 *
057 */
058 public class ResizablePopupComboBox extends JComboBox {
059
060 private Dimension minPopupSize = null;
061 private Dimension maxPopupSize = null;
062 private Dimension prefPopupSize = null;
063
064 /**
065 * Directly calls {@link JComboBox#JComboBox()}.
066 */
067 public ResizablePopupComboBox() {
068 super();
069 }
070
071 /**
072 * Directly calls {@link JComboBox#JComboBox(ComboBoxModel)}.
073 * @param model the {@code ComboBoxModel} that provides the displayed list of items.
074 */
075 public ResizablePopupComboBox(ComboBoxModel model) {
076 super(model);
077 }
078
079 /**
080 * Directly calls {@link JComboBox#JComboBox(Object[])}.
081 * @param items an array of objects to insert into the combo box.
082 */
083 public ResizablePopupComboBox(Object[] items) {
084 super(items);
085 }
086
087 /**
088 * Directly calls {@link JComboBox#JComboBox(Vector)}.
089 * @param items an vector of objects to insert into the combo box.
090 */
091 public ResizablePopupComboBox(Vector<?> items) {
092 super(items);
093 }
094
095 /**
096 * Sets the minimum size for this combo box drop-down popup.
097 * Values that are smaller than {@code 0} will be interpreted as <em>unspecified</em> or <em>any</em>.
098 *
099 * @param width The minimum width of the popup.
100 * @param height The minimum height of the popup.
101 */
102 public void setMinimumPopupSize(int width, int height) {
103 minPopupSize = new Dimension(width, height);
104 }
105
106 /**
107 * Sets the minimum size for this combo box drop-down popup.
108 * The value {@code null} or component values that are smaller than {@code 0} will be interpreted
109 * as <em>unspecified</em> or <em>any</em>.
110 *
111 * @param dim The minimum size for this combo box drop-down popup
112 */
113 public void setMinimumPopupSize(Dimension dim) {
114 if (null == dim)
115 minPopupSize = null;
116 setMinimumPopupSize(dim.width, dim.height);
117 }
118
119 /**
120 * Gets the minimum size for this combo box drop-down popup.
121 *
122 * @return The minimum size for this combo box drop-down popup.
123 * The value {@code null} or component values that are smaller than {@code 0} will be interpreted
124 * as <em>unspecified</em> or <em>any</em>.
125 */
126 public Dimension getMinimumPopupSize() {
127 return new Dimension(minPopupSize.width, minPopupSize.height);
128 }
129
130 /**
131 * Sets the maximum size for this combo box drop-down popup.
132 * Values that are smaller than {@code 0} will be interpreted as <em>unspecified</em> or <em>any</em>.
133 *
134 * @param width The maximum width of the popup.
135 * @param height The maximum height of the popup.
136 */
137 public void setMaximumPopupSize(int width, int height) {
138 maxPopupSize = new Dimension(width, height);
139 }
140
141 /**
142 * Sets the maximum size for this combo box drop-down popup.
143 * The value {@code null} or component values that are smaller than {@code 0} will be interpreted
144 * as <em>unspecified</em> or <em>any</em>.
145 *
146 * @param dim The maximum size for this combo box drop-down popup
147 */
148 public void setMaximumPopupSize(Dimension dim) {
149 if (null == dim)
150 maxPopupSize = null;
151 setMaximumPopupSize(dim.width, dim.height);
152 }
153
154 /**
155 * Gets the maximum size for this combo box drop-down popup.
156 *
157 * @return The maximum size for this combo box drop-down popup.
158 * The value {@code null} or component values that are smaller than {@code 0} will be interpreted
159 * as <em>unspecified</em> or <em>any</em>.
160 */
161 public Dimension getMaximumPopupSize() {
162 return new Dimension(maxPopupSize.width, maxPopupSize.height);
163 }
164
165 /**
166 * Sets the preferred size for this combo box drop-down popup.
167 * Values that are smaller than {@code 0} will be interpreted as <em>unspecified</em> or <em>any</em>.
168 *
169 * @param width The preferred width of the popup.
170 * @param height The preferred height of the popup.
171 */
172 public void setPreferredPopupSize(int width, int height) {
173 prefPopupSize = new Dimension(width, height);
174 }
175
176 /**
177 * Sets the preferred size for this combo box drop-down popup.
178 * The value {@code null} or component values that are smaller than {@code 0} will be interpreted
179 * as <em>unspecified</em> or <em>any</em>.
180 *
181 * @param dim The preferred size for this combo box drop-down popup
182 */
183 public void setPreferredPopupSize(Dimension dim) {
184 if (null == dim)
185 prefPopupSize = null;
186 setPreferredPopupSize(dim.width, dim.height);
187 }
188
189 /**
190 * Gets the preferred size for this combo box drop-down popup.
191 *
192 * @return The preferred size for this combo box drop-down popup.
193 * The value {@code null} or component values that are smaller than {@code 0} will be interpreted
194 * as <em>unspecified</em> or <em>any</em>.
195 */
196 public Dimension setPreferredPopupSize() {
197 return new Dimension(prefPopupSize.width, prefPopupSize.height);
198 }
199
200 @Override
201 public void firePopupMenuWillBecomeVisible() {
202 setPopupSize();
203 super.firePopupMenuWillBecomeVisible();
204 }
205
206 @Override
207 public void updateUI(){
208 super.updateUI();
209 setPopupSize();
210 }
211
212 /**
213 * Gets the drop-down popup component for this combo box.
214 *
215 * @return The drop-down popup component for this combo box.
216 */
217 private BasicComboPopup getComboPopup() {
218
219 ComboBoxUI ui = getUI();
220 if (0 > ui.getAccessibleChildrenCount(this))
221 return null;
222
223 Accessible accs = ui.getAccessibleChild(this, 0);;
224 if (! (accs instanceof BasicComboPopup))
225 return null;
226
227 return (BasicComboPopup) accs;
228 }
229
230 /**
231 * Gets the actual size for the drop-down popup component of this combo box.
232 * If the size can be determined on the basis of the preferred, minimum and maximum
233 * popup size properties, than those values are used. If the size cannot be determined
234 * on the basis of those properties because they are set to {@code null} or because
235 * their {@code width} or {@code height} components are set to negative values, than
236 * the size is determined as follows:<br />
237 * The height is copied from the default dehavious of the popup.<br />
238 * The width is calculated under the assumption that all items of this combo box
239 * will be displayed using their {@code .toString()} methods. The width of the popup
240 * is set to the width of the longest item string under the current font metrics
241 * plus the width of the popup scroller it that is visible.
242 *
243 * @return The actual size for the drop-down popup component of this combo box.
244 */
245 private Dimension calcPopupSize() {
246
247 Dimension prefSize = new Dimension(-1, -1);
248
249 // If there are enough preferences, return the size imediately:
250
251 if (null != prefPopupSize) {
252 prefSize.setSize(prefPopupSize.width, prefPopupSize.height);
253
254 if (null != minPopupSize) {
255 if (0 <= prefSize.width && 0 <= minPopupSize.width && prefSize.width < minPopupSize.width)
256 prefSize.width = minPopupSize.width;
257 if (0 <= prefSize.height && 0 <= minPopupSize.height && prefSize.height < minPopupSize.height)
258 prefSize.height = minPopupSize.height;
259 }
260 if (null != maxPopupSize) {
261 if (0 <= prefSize.width && 0 <= maxPopupSize.width && prefSize.width > maxPopupSize.width)
262 prefSize.width = maxPopupSize.width;
263 if (0 <= prefSize.height && 0 <= maxPopupSize.height && prefSize.height > maxPopupSize.height)
264 prefSize.height = maxPopupSize.height;
265 }
266
267 if (0 <= prefSize.width && 0 <= prefSize.height)
268 return prefSize;
269 }
270
271 // We need the popup for later calculatiopns:
272
273 BasicComboPopup popup = getComboPopup();
274
275
276 // If preferred size is missing the width component, we need to calculate that:
277
278 if (0 > prefSize.width) {
279
280 // Get the max width of all popup items:
281
282 FontMetrics fm = this.getFontMetrics(getFont());
283
284 prefSize.width = this.getPreferredSize().width;
285 for(int i = 0; i < getItemCount(); i++) {
286
287 Object item = getItemAt(i);
288 String itemStr = "null";
289 if (null != item)
290 itemStr = item.toString();
291
292 int strWidth = fm.stringWidth(itemStr);
293 if (prefSize.width < strWidth)
294 prefSize.width = strWidth;
295 }
296
297 // If there is a scroll bar, add its width:
298
299 if (null != popup && popup.getComponentCount() > 0) {
300
301 Component popupComp = popup.getComponent(0);
302 if (null != popupComp && popupComp instanceof JScrollPane) {
303 JScrollPane scrollPane = (JScrollPane) popupComp;
304 if (scrollPane.getVerticalScrollBar().isVisible())
305 prefSize.width += scrollPane.getVerticalScrollBar().getWidth();
306 }
307 }
308 }
309
310 // If preferred size is missing the height component, we need to calculate that:
311
312 if (0 > prefSize.height) {
313 if (null != popup)
314 prefSize.height = popup.getPreferredSize().height;
315 }
316
317 // Ensure bounds:
318
319 if (null != minPopupSize) {
320 if (0 <= minPopupSize.width && (prefSize.width < minPopupSize.width || prefSize.width < 0))
321 prefSize.width = minPopupSize.width;
322 if (0 <= minPopupSize.height && (prefSize.height < minPopupSize.height || prefSize.height < 0))
323 prefSize.height = minPopupSize.height;
324 }
325
326 if (null != maxPopupSize) {
327 if (0 <= maxPopupSize.width && (prefSize.width > maxPopupSize.width || prefSize.width < 0))
328 prefSize.width = maxPopupSize.width;
329 if (0 <= maxPopupSize.height && (prefSize.height > maxPopupSize.height || prefSize.height < 0))
330 prefSize.height = maxPopupSize.height;
331 }
332
333 return prefSize;
334 }
335
336 /**
337 * Calculates the size for the popup of this compo box using {@link #calcPopupSize()} and sets
338 * that as the new drop-down popup size.
339 */
340 private void setPopupSize() {
341
342 Dimension size = calcPopupSize();
343 if (null == size)
344 return;
345
346 BasicComboPopup popup = getComboPopup();
347 if (null == popup)
348 return;
349
350 popup.setLayout(new BorderLayout());
351
352 if (0 < popup.getComponentCount()) {
353 Component popupComp = popup.getComponent(0);
354 popup.remove(0);
355 popup.add(popupComp, BorderLayout.CENTER);
356 }
357
358 popup.setPreferredSize(size);
359 }
360
361 } // public class ResizablePopupComboBox