001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.widgets; 003 004 import java.awt.Toolkit; 005 import java.util.Vector; 006 007 import javax.accessibility.Accessible; 008 import javax.swing.ComboBoxModel; 009 import javax.swing.DefaultComboBoxModel; 010 import javax.swing.JComboBox; 011 import javax.swing.JList; 012 import javax.swing.plaf.basic.ComboPopup; 013 014 /** 015 * Class overriding each {@link JComboBox} in JOSM to control consistently the number of displayed items at once.<br/> 016 * This is needed because of the default Java behaviour that may display the top-down list off the screen (see #7917). 017 * 018 * @since 5429 019 */ 020 public class JosmComboBox extends JComboBox { 021 022 /** 023 * The default prototype value used to compute the maximum number of elements to be displayed at once before 024 * displaying a scroll bar 025 */ 026 public static final String DEFAULT_PROTOTYPE_DISPLAY_VALUE = "Prototype display value"; 027 028 /** 029 * Creates a <code>JosmComboBox</code> with a default data model. 030 * The default data model is an empty list of objects. 031 * Use <code>addItem</code> to add items. By default the first item 032 * in the data model becomes selected. 033 * 034 * @see DefaultComboBoxModel 035 */ 036 public JosmComboBox() { 037 this(DEFAULT_PROTOTYPE_DISPLAY_VALUE); 038 } 039 040 /** 041 * Creates a <code>JosmComboBox</code> with a default data model and 042 * the specified prototype display value. 043 * The default data model is an empty list of objects. 044 * Use <code>addItem</code> to add items. By default the first item 045 * in the data model becomes selected. 046 * 047 * @param prototypeDisplayValue the <code>Object</code> used to compute 048 * the maximum number of elements to be displayed at once before 049 * displaying a scroll bar 050 * 051 * @see DefaultComboBoxModel 052 * @since 5450 053 */ 054 public JosmComboBox(Object prototypeDisplayValue) { 055 super(); 056 init(prototypeDisplayValue); 057 } 058 059 /** 060 * Creates a <code>JosmComboBox</code> that takes its items from an 061 * existing <code>ComboBoxModel</code>. Since the 062 * <code>ComboBoxModel</code> is provided, a combo box created using 063 * this constructor does not create a default combo box model and 064 * may impact how the insert, remove and add methods behave. 065 * 066 * @param aModel the <code>ComboBoxModel</code> that provides the 067 * displayed list of items 068 * @see DefaultComboBoxModel 069 */ 070 public JosmComboBox(ComboBoxModel aModel) { 071 super(aModel); 072 init(aModel != null && aModel.getSize() > 0 ? aModel.getElementAt(0) : null); 073 } 074 075 /** 076 * Creates a <code>JosmComboBox</code> that contains the elements 077 * in the specified array. By default the first item in the array 078 * (and therefore the data model) becomes selected. 079 * 080 * @param items an array of objects to insert into the combo box 081 * @see DefaultComboBoxModel 082 */ 083 public JosmComboBox(Object[] items) { 084 super(items); 085 init(items != null && items.length > 0 ? items[0] : null); 086 } 087 088 /** 089 * Creates a <code>JosmComboBox</code> that contains the elements 090 * in the specified Vector. By default the first item in the vector 091 * (and therefore the data model) becomes selected. 092 * 093 * @param items an array of vectors to insert into the combo box 094 * @see DefaultComboBoxModel 095 */ 096 public JosmComboBox(Vector<?> items) { 097 super(items); 098 init(items != null && !items.isEmpty() ? items.get(0) : null); 099 } 100 101 protected void init(Object prototype) { 102 if (prototype != null) { 103 setPrototypeDisplayValue(prototype); 104 int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height; 105 // Compute maximum number of visible items based on the preferred size of the combo box. 106 // This assumes that items have the same height as the combo box, which is not granted by the look and feel 107 int maxsize = (screenHeight/getPreferredSize().height) / 2; 108 // If possible, adjust the maximum number of items with the real height of items 109 // It is not granted this works on every platform (tested OK on Windows) 110 for (int i = 0; i < getUI().getAccessibleChildrenCount(this); i++) { 111 Accessible child = getUI().getAccessibleChild(this, i); 112 if (child instanceof ComboPopup) { 113 JList list = ((ComboPopup)child).getList(); 114 if (list != null) { 115 if (list.getPrototypeCellValue() != prototype) { 116 list.setPrototypeCellValue(prototype); 117 } 118 int height = list.getFixedCellHeight(); 119 if (height > 0) { 120 maxsize = (screenHeight/height) / 2; 121 } 122 } 123 break; 124 } 125 } 126 setMaximumRowCount(Math.max(getMaximumRowCount(), maxsize)); 127 } 128 } 129 }