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.spi.mobility.project.ui.customizer.support;
043:
044: import java.awt.Component;
045: import java.awt.event.ActionEvent;
046: import java.awt.event.ActionListener;
047: import java.lang.ref.Reference;
048: import java.lang.ref.WeakReference;
049: import java.lang.reflect.Method;
050: import java.util.Enumeration;
051: import java.util.HashMap;
052: import java.util.List;
053: import java.util.Map;
054: import javax.swing.ComboBoxEditor;
055: import javax.swing.ComboBoxModel;
056: import javax.swing.JCheckBox;
057: import javax.swing.JComboBox;
058: import javax.swing.JComponent;
059: import javax.swing.JRadioButton;
060: import javax.swing.JSlider;
061: import javax.swing.JSpinner;
062: import javax.swing.event.ChangeEvent;
063: import javax.swing.event.ChangeListener;
064: import javax.swing.event.DocumentEvent;
065: import javax.swing.event.DocumentListener;
066: import javax.swing.event.TableModelEvent;
067: import javax.swing.event.TableModelListener;
068: import javax.swing.table.TableModel;
069: import javax.swing.text.BadLocationException;
070: import javax.swing.text.Document;
071: import javax.swing.text.JTextComponent;
072: import org.netbeans.spi.mobility.project.ui.customizer.VisualPropertyGroup;
073: import org.openide.ErrorManager;
074:
075: /** Class which makes creation of the GUI easier. Registers JComponent
076: * property names and handles reading/storing the values from the components
077: * automaticaly.
078: *
079: * @author Petr Hrebejk, Adam Sotona
080: */
081: public final class VisualPropertySupport {
082:
083: private static final String WRONG_TYPE = "WrongType"; //NOI18N
084: private static final String CONFIG_PREFFIX = "configs."; //NOI18N
085: private static Reference<VisualPropertySupport> cache = new WeakReference(
086: null);
087:
088: final private Map<String, Object> backup;
089: final private ComponentListener componentListener;
090: final private CheckBoxListener listener;
091:
092: final Map<String, Object> properties;
093: final HashMap<Object, Object> component2property;
094: final HashMap<JCheckBox, VisualPropertyGroup> checkbox2group;
095: private String configuration;
096:
097: public static VisualPropertySupport getDefault(
098: Map<String, Object> properties) {
099: VisualPropertySupport vps = cache.get();
100: if (vps == null || vps.properties != properties) {
101: vps = new VisualPropertySupport(properties);
102: cache = new WeakReference(vps);
103: }
104: return vps;
105: }
106:
107: public static String translatePropertyName(
108: final String configuration, final String propertyName,
109: final boolean useDefault) {
110: return useDefault || configuration == null ? propertyName
111: : prefixPropertyName(configuration, propertyName);
112: }
113:
114: public static String prefixPropertyName(final String configuration,
115: final String propertyName) {
116: return CONFIG_PREFFIX + configuration + '.' + propertyName;
117: }
118:
119: private VisualPropertySupport(Map<String, Object> properties) {
120: this .properties = properties;
121: this .backup = new HashMap<String, Object>();
122: this .component2property = new HashMap<Object, Object>(10);
123: this .componentListener = new ComponentListener();
124: this .checkbox2group = new HashMap<JCheckBox, VisualPropertyGroup>();
125: this .listener = new CheckBoxListener();
126: }
127:
128: /** Registers the checkbox with given group of properties and currently selected configuration,
129: * tests the group for any non-default value and fills the checkbox,
130: * initializes the group components by calling group.refreshGroup().
131: * Don't call this method from VisualPropertyGroup.refreshGroup() method !
132: */
133: public void register(final JCheckBox check,
134: final String configuration, final VisualPropertyGroup group) {
135: check.removeActionListener(listener);
136: this .configuration = configuration; // currently only one selected configuration is supported
137: checkbox2group.put(check, group);
138: check.setEnabled(configuration != null);
139: final boolean useDefaults = !testForNonDefaultValue(group
140: .getGroupPropertyNames());
141: check.setSelected(useDefaults);
142: refreshGroup(group, useDefaults);
143: check.addActionListener(listener);
144: }
145:
146: /** Registers the component with given property, Fills the component
147: * with given object according to default status.
148: * Use this method to register components from VisualPropertyGroup.refreshGroup() method.
149: * Use default property names, don't prefix them, it is our job !
150: */
151: public void register(final JCheckBox component,
152: final String propertyName, final boolean useDefault) {
153: register(component, translatePropertyName(configuration,
154: propertyName, useDefault));
155: component.setEnabled(!useDefault);
156: }
157:
158: /** Registers the component with given property, Fills the component
159: * with given object.
160: */
161: public void register(final JCheckBox component,
162: final String propertyName) {
163: component.removeActionListener(componentListener);
164: final Object value = properties.get(propertyName);
165: component2property.put(component, propertyName);
166: component
167: .setSelected((value instanceof Boolean && ((Boolean) value)
168: .booleanValue())
169: || (value instanceof String && Boolean
170: .parseBoolean((String) value)));
171: component.addActionListener(componentListener);
172: }
173:
174: /** Registers the component with given property, Fills the component
175: * with given object according to default status.
176: * Use this method to register components from VisualPropertyGroup.refreshGroup() method.
177: * Use default property names, don't prefix them, it is our job !
178: */
179: public void register(final JTextComponent component,
180: final String propertyName, final boolean useDefault) {
181: register(component, translatePropertyName(configuration,
182: propertyName, useDefault));
183: component.setEnabled(!useDefault);
184: component.setEditable(!useDefault);
185: }
186:
187: /** Registers the component with given property, Fills the component
188: * with given object.
189: */
190: public void register(final JTextComponent component,
191: final String propertyName) {
192: component.getDocument().removeDocumentListener(
193: componentListener);
194: component2property.put(component.getDocument(), propertyName);
195: component.setText(String.valueOf(properties.get(propertyName)));
196: component.getDocument().addDocumentListener(componentListener);
197: }
198:
199: /** Registers the component with given property, Fills the component
200: * with given object according to default status.
201: * Use this method to register components from VisualPropertyGroup.refreshGroup() method.
202: * Use default property names, don't prefix them, it is our job !
203: */
204: public void register(final JComboBox component,
205: final Object[] items, final String propertyName,
206: final boolean useDefault) {
207: register(component, items, translatePropertyName(configuration,
208: propertyName, useDefault));
209: component.setEnabled(!useDefault);
210: }
211:
212: private Document getDocumentFor(final JComboBox box) {
213: if (box.isEditable()) {
214: final ComboBoxEditor cbe = box.getEditor();
215: if (cbe != null) {
216: final Component c = cbe.getEditorComponent();
217: if (c instanceof JTextComponent) {
218: return ((JTextComponent) c).getDocument();
219: }
220: }
221: }
222: return null;
223: }
224:
225: /** Registers combo box.
226: */
227: public void register(final JComboBox component, Object[] items,
228: final String propertyName) {
229: component.removeActionListener(componentListener);
230: final Document doc = getDocumentFor(component);
231: if (doc != null)
232: doc.removeDocumentListener(componentListener);
233: final Object value = properties.get(propertyName);
234: component2property.put(component, propertyName);
235: if (doc != null)
236: component2property.put(doc, propertyName);
237: // Add all items and find the selected one
238: if (items == null) {
239: final ComboBoxModel model = component.getModel();
240: items = new Object[model.getSize()];
241: for (int i = 0; i < items.length; i++)
242: items[i] = model.getElementAt(i);
243: }
244: component.removeAllItems();
245: boolean selected = false;
246: for (int i = 0; i < items.length; i++) {
247: component.addItem(items[i]);
248: if (items[i].equals(value)) {
249: selected = true;
250: component.setSelectedIndex(i);
251: }
252: }
253: if (!selected) {
254: if (component.isEditable())
255: component.setSelectedItem(value);
256: else if (items.length > 0) {
257:
258: // set special list cell renderer for wrong item
259:
260: component.setSelectedIndex(0);
261: properties.put(propertyName, items[0]);
262: }
263: }
264: component.addActionListener(componentListener);
265: if (doc != null)
266: doc.addDocumentListener(componentListener);
267: }
268:
269: /** Registers the component with given property, Fills the component
270: * with given object according to default status.
271: * Use this method to register components from VisualPropertyGroup.refreshGroup() method.
272: * Use default property names, don't prefix them, it is our job !
273: */
274: public void register(final JSlider component,
275: final String propertyName, final boolean useDefault) {
276: register(component, translatePropertyName(configuration,
277: propertyName, useDefault));
278: component.setEnabled(!useDefault);
279: final Enumeration en = component.getLabelTable().elements();
280: while (en.hasMoreElements())
281: ((JComponent) en.nextElement()).setEnabled(!useDefault);
282: }
283:
284: /** Registers the component with given property, Fills the component
285: * with given object.
286: */
287: public void register(final JSlider component,
288: final String propertyName) {
289: component.removeChangeListener(componentListener);
290: final Object value = properties.get(propertyName);
291: component2property.put(component, propertyName);
292: component.setValue((value instanceof Number ? ((Number) value)
293: .intValue() : (value instanceof String ? Integer
294: .parseInt((String) value) : component.getMinimum())));
295: component.addChangeListener(componentListener);
296: }
297:
298: /** Registers the component with given property, Fills the component
299: * with given object according to default status.
300: * Use this method to register components from VisualPropertyGroup.refreshGroup() method.
301: * Use default property names, don't prefix them, it is our job !
302: */
303: public void register(final JSpinner component,
304: final String propertyName, final boolean useDefault) {
305: register(component, translatePropertyName(configuration,
306: propertyName, useDefault));
307: component.setEnabled(!useDefault);
308: }
309:
310: /** Registers the component with given property, Fills the component
311: * with given object.
312: */
313: public void register(final JSpinner component,
314: final String propertyName) {
315: component.removeChangeListener(componentListener);
316: final Object value = properties.get(propertyName);
317: component2property.put(component, propertyName);
318: component.setValue((value instanceof Number ? ((Number) value)
319: .intValue() : (value instanceof String ? Integer
320: .parseInt((String) value) : 0)));
321: component.addChangeListener(componentListener);
322: }
323:
324: /** Registers the component with given property.
325: * Use this method to register components from VisualPropertyGroup.refreshGroup() method.
326: * Use default property names, don't prefix them, it is our job !
327: */
328: public void register(final JRadioButton component,
329: final String propertyName, final boolean useDefault) {
330: register(component, translatePropertyName(configuration,
331: propertyName, useDefault));
332: component.setEnabled(!useDefault);
333: }
334:
335: /** Registers combo box.
336: */
337: public void register(final JRadioButton component,
338: final String propertyName) {
339: component.removeActionListener(componentListener);
340: final Object value = properties.get(propertyName);
341: component2property.put(component, propertyName);
342: component.setSelected(value != null
343: && value.toString().equals(readValue(component)));
344: component.addActionListener(componentListener);
345: }
346:
347: public static String[] translatePropertyNames(
348: final String configuration, final String[] propertyNames,
349: final boolean useDefault) {
350: String names[] = new String[propertyNames.length];
351: for (int i = 0; i < propertyNames.length; i++)
352: names[i] = translatePropertyName(configuration,
353: propertyNames[i], useDefault);
354: return names;
355: }
356:
357: /** Registers the component with given property.
358: * Use this method to register components from VisualPropertyGroup.refreshGroup() method.
359: * Use default property names, don't prefix them, it is our job !
360: */
361: public void register(final StorableTableModel component,
362: final String[] propertyNames, final boolean useDefault) {
363: register(component, translatePropertyNames(configuration,
364: propertyNames, useDefault));
365: }
366:
367: /** Registers StorableTableModel
368: */
369: public void register(final StorableTableModel component,
370: final String[] propertyNames) {
371: component.removeTableModelListener(componentListener);
372: Object values[] = new Object[propertyNames.length];
373: for (int i = 0; i < values.length; i++)
374: values[i] = properties.get(propertyNames[i]);
375: component2property.put(component, propertyNames);
376: component.setDataDelegates(values);
377: component.addTableModelListener(componentListener);
378: }
379:
380: // Static methods for reading components and models ------------------------
381:
382: protected static Boolean readValue(final JCheckBox checkBox) {
383: return checkBox.isSelected() ? Boolean.TRUE : Boolean.FALSE;
384: }
385:
386: protected static String readValue(final Document document) {
387: try {
388: return document.getText(0, document.getLength());
389: } catch (BadLocationException e) {
390: assert false : "Invalid document "; //NOI18N
391: return ""; // NOI18N
392: }
393: }
394:
395: protected static String readValue(final JComboBox comboBox) {
396: Object selectedItem = comboBox.getSelectedItem();
397: if (selectedItem == null)
398: return null;
399: return selectedItem.toString();
400: }
401:
402: protected static Integer readValue(final JSlider slider) {
403: return new Integer(slider.getValue());
404: }
405:
406: protected static Object readValue(final JSpinner slider) {
407: return slider.getValue();
408: }
409:
410: protected static String readValue(final JRadioButton radio) {
411: return radio.getActionCommand();
412: }
413:
414: // Private methods ---------------------------------------------------------
415:
416: private boolean testForNonDefaultValue(final String[] propertyNames) {
417: if (configuration == null)
418: return true;
419: for (int i = 0; i < propertyNames.length; i++) {
420: if (properties.get(prefixPropertyName(configuration,
421: propertyNames[i])) != null)
422: return true;
423: }
424: return false;
425: }
426:
427: private void removeConfigProperties(final String[] propertyNames) {
428: if (configuration == null)
429: return;
430: for (int i = 0; i < propertyNames.length; i++) {
431: final String name = prefixPropertyName(configuration,
432: propertyNames[i]);
433: backup.put(name, properties.remove(name));
434: }
435: }
436:
437: private void copyFromDefaults(final String[] propertyNames) {
438: if (configuration == null)
439: return;
440: final Class cl[] = {};
441: final Object ob[] = {};
442: for (int i = 0; i < propertyNames.length; i++) {
443: final String prefName = prefixPropertyName(configuration,
444: propertyNames[i]);
445: if (properties.get(prefName) == null) {
446: Object backValue = backup.get(prefName);
447: if (backValue == null) {
448: backValue = properties.get(propertyNames[i]);
449: if (backValue instanceof Cloneable)
450: try {
451: final Method m = backValue.getClass()
452: .getMethod("clone", cl); //NOI18N
453: if (m != null)
454: backValue = m.invoke(backValue, ob);
455: } catch (Exception e) {
456: ErrorManager.getDefault().notify(
457: ErrorManager.INFORMATIONAL, e);
458: }
459: }
460: properties.put(prefName, backValue);
461: }
462: }
463: }
464:
465: protected void refreshGroup(final VisualPropertyGroup g,
466: final boolean useDefaults) {
467: if (useDefaults) {
468: removeConfigProperties(g.getGroupPropertyNames());
469: g.initGroupValues(true);
470: } else {
471: copyFromDefaults(g.getGroupPropertyNames());
472: g.initGroupValues(false);
473: }
474:
475: }
476:
477: private class ComponentListener implements ActionListener,
478: DocumentListener, ChangeListener, TableModelListener {
479:
480: private ComponentListener() {
481: //Just to avoid creation of accessor class
482: }
483:
484: // Implementation of action listener -----------------------------------
485:
486: public void actionPerformed(final ActionEvent e) {
487:
488: final Object source = e.getSource();
489:
490: final String propertyName = (String) component2property
491: .get(source);
492: if (propertyName != null) {
493:
494: if (source instanceof JCheckBox) {
495: properties.put(propertyName,
496: readValue((JCheckBox) source));
497: } else if (source instanceof JComboBox) {
498: properties.put(propertyName,
499: readValue((JComboBox) source));
500: } else if (source instanceof JRadioButton) {
501: properties.put(propertyName,
502: readValue((JRadioButton) source));
503: }
504: }
505:
506: }
507:
508: // Implementation of document listener ---------------------------------
509:
510: public void changedUpdate(final DocumentEvent e) {
511:
512: final Document document = e.getDocument();
513: final String propertyName = (String) component2property
514: .get(document);
515: if (propertyName != null) {
516: properties.put(propertyName, readValue(document));
517: }
518: }
519:
520: public void insertUpdate(final DocumentEvent e) {
521: changedUpdate(e);
522: }
523:
524: public void removeUpdate(final DocumentEvent e) {
525: changedUpdate(e);
526: }
527:
528: public void stateChanged(final ChangeEvent e) {
529: final Object objectSource = e.getSource();
530: String propertyName = null;
531: if (objectSource instanceof JSlider) {
532: final JSlider source = (JSlider) objectSource;
533: propertyName = (String) component2property.get(source);
534: if (propertyName != null)
535: properties.put(propertyName, readValue(source));
536: } else if (objectSource instanceof JSpinner) {
537: final JSpinner source = (JSpinner) objectSource;
538: propertyName = (String) component2property.get(source);
539: if (propertyName != null)
540: properties.put(propertyName, readValue(source));
541: }
542:
543: }
544:
545: public void tableChanged(final TableModelEvent e) {
546: final StorableTableModel source = (StorableTableModel) e
547: .getSource();
548: final String propertyNames[] = (String[]) component2property
549: .get(source);
550: if (propertyNames != null) {
551: final Object values[] = source.getDataDelegates();
552: for (int i = 0; i < propertyNames.length; i++)
553: properties.put(propertyNames[i], values[i]);
554: }
555: }
556: }
557:
558: private class CheckBoxListener implements ActionListener {
559:
560: private CheckBoxListener() {
561: //Just to avoid creation of accessor class
562: }
563:
564: public void actionPerformed(final ActionEvent e) {
565: final JCheckBox cb = (JCheckBox) e.getSource();
566: final VisualPropertyGroup g = checkbox2group.get(cb);
567: if (g != null) {
568: refreshGroup(g, cb.isSelected());
569: }
570: }
571:
572: }
573:
574: public static interface StorableTableModel extends TableModel {
575: public Object[] getDataDelegates();
576:
577: public void setDataDelegates(Object data[]);
578: }
579: }
|