001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.db.util;
043:
044: import java.awt.Color;
045: import java.awt.Component;
046: import java.awt.Dimension;
047: import java.awt.event.ActionEvent;
048: import java.awt.event.ActionListener;
049: import java.awt.event.InputEvent;
050: import java.awt.event.KeyAdapter;
051: import java.awt.event.KeyEvent;
052: import javax.swing.AbstractListModel;
053: import javax.swing.ComboBoxModel;
054: import javax.swing.DefaultListCellRenderer;
055: import javax.swing.JComboBox;
056: import javax.swing.JLabel;
057: import javax.swing.JList;
058: import javax.swing.JSeparator;
059: import javax.swing.SwingUtilities;
060: import javax.swing.event.ListDataEvent;
061: import javax.swing.event.ListDataListener;
062: import javax.swing.event.PopupMenuEvent;
063: import javax.swing.event.PopupMenuListener;
064:
065: /**
066: * This is an utility class for filling combo boxes with some data (usually
067: * some items). The combo box has a separator item and a "Add item" item allowing
068: * the user to invoke the adding of new items to the combo box. The client of
069: * this class should provide a {@link DataComboBoxModel} and call
070: * the {@link #connect} method.
071: *
072: * @author Andrei Badea
073: */
074: public final class DataComboBoxSupport {
075:
076: private final DataComboBoxModel dataModel;
077: private final boolean allowAdding;
078:
079: private Object previousItem = null;
080: private Object previousNonSpecialItem = null;
081: private int previousIndex = -1;
082:
083: private boolean performingNewItemAction = false;
084:
085: /**
086: * Serves as the separator item.
087: */
088: private static final class Separator extends JSeparator {
089:
090: Separator() {
091: setForeground(Color.BLACK);
092: }
093:
094: public Dimension getPreferredSize() {
095: Dimension size = super .getPreferredSize();
096: return new Dimension(size.width, 1);
097: }
098: }
099:
100: /** Not private because used in tests. */
101: static final Separator SEPARATOR_ITEM = new Separator();
102:
103: /**
104: * Serves as the new item. Not private because used in tests.
105: */
106: final Object NEW_ITEM = new Object() {
107: public String toString() {
108: return dataModel.getNewItemDisplayName();
109: }
110: };
111:
112: /** Not private because used in tests. */
113: DataComboBoxSupport(JComboBox comboBox,
114: DataComboBoxModel dataModel, boolean allowAdding) {
115: this .dataModel = dataModel;
116: this .allowAdding = allowAdding;
117:
118: comboBox.setEditable(false);
119:
120: comboBox.setModel(new ItemComboBoxModel());
121:
122: comboBox.setRenderer(new ItemListCellRenderer());
123: comboBox.addKeyListener(new ItemKeyListener());
124: comboBox.addActionListener(new ItemActionListener());
125: comboBox.addPopupMenuListener(new ItemPopupMenuListener());
126: }
127:
128: /**
129: * Connects a combo box with the specified combo box model.
130: */
131: public static void connect(JComboBox comboBox,
132: DataComboBoxModel dataModel) {
133: connect(comboBox, dataModel, true);
134: }
135:
136: /**
137: * Connects a combo box with the specified combo box model.
138: */
139: public static void connect(JComboBox comboBox,
140: DataComboBoxModel dataModel, boolean allowAdding) {
141: new DataComboBoxSupport(comboBox, dataModel, allowAdding);
142: }
143:
144: private boolean isSpecialItem(Object item) {
145: return item == SEPARATOR_ITEM || item == NEW_ITEM;
146: }
147:
148: private void setPreviousNonSpecialItem(JComboBox comboBox) {
149: if (comboBox.getSelectedItem() == NEW_ITEM) {
150: // no new item added
151: comboBox.setSelectedItem(previousNonSpecialItem);
152: }
153: }
154:
155: private class ItemComboBoxModel extends AbstractListModel implements
156: ComboBoxModel, ListDataListener {
157:
158: // XXX intervalAdded() and intervalRemoved() are not implemented,
159: // but it is enough for the connection and drivers combo boxes
160:
161: public ItemComboBoxModel() {
162: getDelegate().addListDataListener(this );
163: }
164:
165: public Object getElementAt(int index) {
166: if (allowAdding) {
167: if (getSize() == 1) {
168: // there is just NEW_ITEM
169: if (index == 0) {
170: return NEW_ITEM;
171: } else {
172: throw new IllegalStateException(
173: "Index out of bounds: " + index); // NOI18N
174: }
175: }
176:
177: // there are the delegate items, SEPARATOR_ITEM, NEW_ITEM
178: if (index >= 0 && index < getDelegate().getSize()) {
179: return getDelegate().getElementAt(index);
180: } else if (index == getSize() - 2) {
181: return SEPARATOR_ITEM;
182: } else if (index == getSize() - 1) {
183: return NEW_ITEM;
184: } else {
185: throw new IllegalStateException(
186: "Index out of bounds: " + index); // NOI18N
187: }
188: } else {
189: // there are no other items than those of the delegate
190: return getDelegate().getElementAt(index);
191: }
192: }
193:
194: public int getSize() {
195: // 1 = NEW_ITEM
196: // 2 = SEPARATOR, NEW_ITEM
197: if (allowAdding) {
198: return getDelegate().getSize() == 0 ? 1 : getDelegate()
199: .getSize() + 2;
200: } else {
201: return getDelegate().getSize();
202: }
203: }
204:
205: public void setSelectedItem(Object anItem) {
206: previousItem = getDelegate().getSelectedItem();
207: previousIndex = getItemIndex(previousItem);
208:
209: if (!isSpecialItem(previousItem)) {
210: previousNonSpecialItem = previousItem;
211: }
212:
213: getDelegate().setSelectedItem(anItem);
214: }
215:
216: public Object getSelectedItem() {
217: return getDelegate().getSelectedItem();
218: }
219:
220: public Object getPreviousItem() {
221: return previousItem;
222: }
223:
224: private ComboBoxModel getDelegate() {
225: return dataModel.getListModel();
226: }
227:
228: private int getItemIndex(Object item) {
229: if (item == null) {
230: return -1;
231: }
232: for (int i = 0; i < getSize(); i++) {
233: if (getElementAt(i).equals(item)) {
234: return i;
235: }
236: }
237: return -1;
238: }
239:
240: public void intervalRemoved(ListDataEvent e) {
241: throw new UnsupportedOperationException(
242: "This is currently not supported.");
243: }
244:
245: public void intervalAdded(ListDataEvent e) {
246: throw new UnsupportedOperationException(
247: "This is currently not supported.");
248: }
249:
250: public void contentsChanged(ListDataEvent e) {
251: fireContentsChanged(this , 0, getSize());
252: }
253: }
254:
255: private class ItemListCellRenderer extends DefaultListCellRenderer {
256:
257: public Component getListCellRendererComponent(JList list,
258: Object value, int index, boolean isSelected,
259: boolean cellHasFocus) {
260:
261: Component component = super .getListCellRendererComponent(
262: list, value, index, isSelected, cellHasFocus);
263: JLabel label = (JLabel) component;
264:
265: if (value != null && !isSpecialItem(value)) {
266: String displayName = dataModel
267: .getItemDisplayName(value);
268: label.setText(dataModel.getItemDisplayName(value));
269: label.setToolTipText(dataModel
270: .getItemTooltipText(value));
271: } else if (value == SEPARATOR_ITEM) {
272: return SEPARATOR_ITEM;
273: } else if (value != null) {
274: label.setText(value.toString());
275: label.setToolTipText(null);
276: }
277:
278: return label;
279: }
280: }
281:
282: private final class ItemKeyListener extends KeyAdapter {
283:
284: public void keyPressed(KeyEvent e) {
285: JComboBox comboBox = (JComboBox) e.getSource();
286:
287: int keyCode = e.getKeyCode();
288: if (KeyEvent.VK_ENTER == keyCode) {
289: Object selectedItem = comboBox.getSelectedItem();
290: if (selectedItem == NEW_ITEM) {
291: performingNewItemAction = true;
292: try {
293: comboBox.setPopupVisible(false);
294: e.consume();
295: dataModel.newItemActionPerformed();
296: } finally {
297: performingNewItemAction = false;
298: }
299:
300: setPreviousNonSpecialItem(comboBox);
301: }
302: }
303: }
304: }
305:
306: private final class ItemActionListener implements ActionListener {
307:
308: public void actionPerformed(ActionEvent e) {
309: final JComboBox comboBox = (JComboBox) e.getSource();
310:
311: Object selectedItem = comboBox.getSelectedItem();
312: if (selectedItem == SEPARATOR_ITEM) {
313: int newIndex = -1;
314: if (previousIndex != -1) {
315: // skipping the separator when moving up/down with the arrow keys
316: int selectedIndex = comboBox.getSelectedIndex();
317: if (selectedIndex > previousIndex) {
318: // moving down
319: newIndex = selectedIndex + 1;
320: } else {
321: // moving up
322: newIndex = selectedIndex - 1;
323: }
324: }
325: comboBox.setSelectedIndex(newIndex);
326: } else if ((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0) {
327: // handling mouse click, see KeyEvent.getKeyModifiersText(e.getModifiers())
328: if (selectedItem == NEW_ITEM) {
329: performingNewItemAction = true;
330: try {
331: comboBox.setPopupVisible(false);
332: dataModel.newItemActionPerformed();
333: } finally {
334: performingNewItemAction = false;
335: }
336:
337: setPreviousNonSpecialItem(comboBox);
338: // we (or maybe the client) have just selected an item inside an actionPerformed event,
339: // which will not send another actionPerformed event for the new item.
340: // We need to make sure all listeners get an event for the new item,
341: // thus...
342: final Object newSelectedItem = comboBox
343: .getSelectedItem();
344: SwingUtilities.invokeLater(new Runnable() {
345: public void run() {
346: comboBox.setSelectedItem(newSelectedItem);
347: }
348: });
349: }
350: }
351: }
352: }
353:
354: private final class ItemPopupMenuListener implements
355: PopupMenuListener {
356:
357: public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
358: }
359:
360: public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
361: if (!performingNewItemAction) {
362: setPreviousNonSpecialItem((JComboBox) e.getSource());
363: }
364: }
365:
366: public void popupMenuCanceled(PopupMenuEvent e) {
367: // without the check the previous non-special item would be displayed
368: // while calling DataComboBoxModel.newItemActionPerformed()
369: // instead of NEW_ITEM, but this is unwanted. Same for
370: // popupMenuWillBecomeImvisible().
371: if (!performingNewItemAction) {
372: setPreviousNonSpecialItem((JComboBox) e.getSource());
373: }
374: }
375: }
376: }
|