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.form;
043:
044: import java.awt.*;
045: import java.beans.*;
046: import java.lang.ref.WeakReference;
047: import java.security.*;
048:
049: import org.openide.explorer.propertysheet.PropertyEnv;
050: import org.openide.explorer.propertysheet.ExPropertyEditor;
051:
052: /** A multiplexing PropertyEditor used in the form editor.
053: * It allows multiple editors to be used with one currently selected.
054: *
055: * @author Ian Formanek
056: */
057:
058: public class FormPropertyEditor implements PropertyEditor,
059: PropertyChangeListener, ExPropertyEditor {
060: private static String NO_VALUE_TEXT;
061:
062: private Object value = BeanSupport.NO_VALUE;
063: private boolean valueEdited;
064:
065: private FormProperty property;
066: private WeakReference<PropertyEnv> propertyEnv;
067:
068: private PropertyEditor[] allEditors;
069: private PropertyEditor lastCurrentEditor;
070:
071: private PropertyChangeSupport changeSupport;
072:
073: /** Crates a new FormPropertyEditor */
074: FormPropertyEditor(FormProperty property) {
075: this .property = property;
076: PropertyEditor prEd = property.getCurrentEditor();
077: if (prEd != null) {
078: prEd.addPropertyChangeListener(this ); // [do we really need to listen to this editor??]
079: value = prEd.getValue();
080: }
081: }
082:
083: Class getPropertyType() {
084: return property.getValueType();
085: }
086:
087: FormProperty getProperty() {
088: return property;
089: }
090:
091: FormPropertyContext getPropertyContext() {
092: return property.getPropertyContext();
093: }
094:
095: PropertyEnv getPropertyEnv() {
096: return propertyEnv != null ? propertyEnv.get() : null;
097: }
098:
099: PropertyEditor getCurrentEditor() {
100: return property.getCurrentEditor();
101: }
102:
103: // -----------------------------------------------------------------------------
104: // PropertyChangeListener implementation
105:
106: public void propertyChange(PropertyChangeEvent evt) {
107: PropertyEditor prEd = property.getCurrentEditor();
108: if (prEd != null) {
109: value = prEd.getValue();
110: valueEdited = false;
111: }
112:
113: // we run this as privileged to avoid security problems - because
114: // the property change can be fired from untrusted property editor code
115: AccessController.doPrivileged(new PrivilegedAction<Object>() {
116: public Object run() {
117: FormPropertyEditor.this .firePropertyChange();
118: return null;
119: }
120: });
121: }
122:
123: // -----------------------------------------------------------------------------
124: // PropertyEditor implementation
125:
126: /**
127: * Set(or change) the object that is to be edited.
128: * @param newValue The new target object to be edited. Note that this
129: * object should not be modified by the PropertyEditor, rather
130: * the PropertyEditor should create a new object to hold any
131: * modified value.
132: */
133: public void setValue(Object newValue) {
134: value = newValue;
135: valueEdited = false;
136:
137: PropertyEditor prEd = property.getCurrentEditor();
138: if (value != BeanSupport.NO_VALUE && prEd != null)
139: prEd.setValue(value);
140: }
141:
142: void setEditedValue(Object newValue) {
143: value = newValue;
144: // the value comes from custom editing where the selected editor can be
145: // different at this moment than the current editor of the edited property
146: valueEdited = true;
147: firePropertyChange();
148: }
149:
150: /**
151: * Gets the value of the property.
152: *
153: * @return The value of the property.
154: */
155: public Object getValue() {
156: if (!valueEdited) {
157: PropertyEditor prEd = property.getCurrentEditor();
158: if (prEd != null) {
159: return prEd.getValue();
160: }
161: }
162: return value;
163: }
164:
165: // -----------------------------------------------------------------------------
166:
167: /**
168: * Determines whether the class will honor the painValue method.
169: *
170: * @return True if the class will honor the paintValue method.
171: */
172: public boolean isPaintable() {
173: PropertyEditor prEd = property.getCurrentEditor();
174: return prEd != null ? prEd.isPaintable() : false;
175: }
176:
177: /**
178: * Paint a representation of the value into a given area of screen
179: * real estate. Note that the propertyEditor is responsible for doing
180: * its own clipping so that it fits into the given rectangle.
181: * <p>
182: * If the PropertyEditor doesn't honor paint requests(see isPaintable)
183: * this method should be a silent noop.
184: *
185: * @param gfx Graphics object to paint into.
186: * @param box Rectangle within graphics object into which we should paint.
187: */
188: public void paintValue(Graphics gfx, Rectangle box) {
189: PropertyEditor prEd = property.getCurrentEditor();
190: if (prEd != null)
191: prEd.paintValue(gfx, box);
192: }
193:
194: // -----------------------------------------------------------------------------
195:
196: /**
197: * This method is intended for use when generating Java code to set
198: * the value of the property. It should return a fragment of Java code
199: * that can be used to initialize a variable with the current property
200: * value.
201: * <p>
202: * Example results are "2", "new Color(127,127,34)", "Color.orange", etc.
203: *
204: * @return A fragment of Java code representing an initializer for the
205: * current value.
206: */
207: public String getJavaInitializationString() {
208: PropertyEditor prEd = property.getCurrentEditor();
209: return prEd != null ? prEd.getJavaInitializationString() : null;
210: }
211:
212: // -----------------------------------------------------------------------------
213:
214: /**
215: * Gets the property value as a string suitable for presentation
216: * to a human to edit.
217: *
218: * @return The property value as a string suitable for presentation
219: * to a human to edit.
220: * <p> Returns "null" is the value can't be expressed as a string.
221: * <p> If a non-null value is returned, then the PropertyEditor should
222: * be prepared to parse that string back in setAsText().
223: */
224: public String getAsText() {
225: if (value == BeanSupport.NO_VALUE) {
226: if (NO_VALUE_TEXT == null)
227: NO_VALUE_TEXT = FormUtils
228: .getBundleString("CTL_ValueNotSet"); // NOI18N
229: return NO_VALUE_TEXT;
230: }
231:
232: PropertyEditor prEd = property.getCurrentEditor();
233: return prEd != null ? prEd.getAsText() : null;
234: }
235:
236: /**
237: * Sets the property value by parsing a given String. May raise
238: * java.lang.IllegalArgumentException if either the String is
239: * badly formatted or if this kind of property can't be expressed
240: * as text.
241: *
242: * @param text The string to be parsed.
243: * @throws java.lang.IllegalArgumentException when the specified text
244: * does not represent valid value.
245: */
246: public void setAsText(String text)
247: throws java.lang.IllegalArgumentException {
248: PropertyEditor prEd = property.getCurrentEditor();
249: if (prEd != null)
250: prEd.setAsText(text);
251: }
252:
253: // -----------------------------------------------------------------------------
254:
255: /**
256: * If the property value must be one of a set of known tagged values,
257: * then this method should return an array of the tag values. This can
258: * be used to represent(for example) enum values. If a PropertyEditor
259: * supports tags, then it should support the use of setAsText with
260: * a tag value as a way of setting the value.
261: *
262: * @return The tag values for this property. May be null if this
263: * property cannot be represented as a tagged value.
264: *
265: */
266: public String[] getTags() {
267: PropertyEditor prEd = property.getCurrentEditor();
268: return prEd != null ? prEd.getTags() : null;
269: }
270:
271: // -----------------------------------------------------------------------------
272:
273: /**
274: * A PropertyEditor may chose to make available a full custom Component
275: * that edits its property value. It is the responsibility of the
276: * PropertyEditor to hook itself up to its editor Component itself and
277: * to report property value changes by firing a PropertyChange event.
278: * <P>
279: * The higher-level code that calls getCustomEditor may either embed
280: * the Component in some larger property sheet, or it may put it in
281: * its own individual dialog, or ...
282: *
283: * @return A java.awt.Component that will allow a human to directly
284: * edit the current property value. May be null if this is
285: * not supported.
286: */
287:
288: public Component getCustomEditor() {
289: // hack: PropertyPicker wants code regenerated - it might lead to
290: // setting values to property editors
291: FormModel formModel = property.getPropertyContext()
292: .getFormModel();
293: if (formModel != null) {
294: JavaCodeGenerator codeGen = (JavaCodeGenerator) FormEditor
295: .getCodeGenerator(formModel);
296: if (codeGen != null) { // may happen property sheet wants something from an already closed form (#111205)
297: codeGen.regenerateCode();
298: }
299: }
300:
301: Component customEditor;
302:
303: PropertyEditor prEd = property.getCurrentEditor();
304: if (prEd != null && prEd.supportsCustomEditor()) {
305: customEditor = prEd.getCustomEditor();
306: if (customEditor instanceof Window)
307: return customEditor;
308: } else
309: customEditor = null;
310:
311: return new FormCustomEditor(this , customEditor);
312: }
313:
314: /**
315: * Determines whether the propertyEditor can provide a custom editor.
316: *
317: * @return True if the propertyEditor can provide a custom editor.
318: */
319: public boolean supportsCustomEditor() {
320: PropertyEditor[] editors = getAllEditors();
321:
322: if (!property.canWrite()) { // read only property
323: for (int i = 0; i < editors.length; i++)
324: if (!editors[i].getClass().equals(
325: RADConnectionPropertyEditor.class)
326: && editors[i].supportsCustomEditor())
327: return true;
328: return false;
329: }
330:
331: // writable property
332: if (editors.length > 1)
333: return true; // we must at least allow to choose the editor
334: if (editors.length == 1)
335: return editors[0].supportsCustomEditor();
336:
337: return false;
338: }
339:
340: synchronized PropertyEditor[] getAllEditors() {
341: if (allEditors != null) {
342: // the current property editor might have changed and so not
343: // present among the cached editors
344: PropertyEditor currentEditor = property.getCurrentEditor();
345: if (currentEditor != lastCurrentEditor) {
346: allEditors = null;
347: }
348: }
349:
350: if (allEditors == null) {
351: PropertyEditor expliciteEditor = property
352: .getExpliciteEditor();
353: PropertyEditor currentEditor = property.getCurrentEditor();
354: lastCurrentEditor = currentEditor;
355: if (expliciteEditor != null
356: && currentEditor != null
357: && expliciteEditor.getClass().equals(
358: currentEditor.getClass())) { // they are the same, take care about the current editor only
359: expliciteEditor = null;
360: }
361: PropertyEditor[] typeEditors = FormPropertyEditorManager
362: .getAllEditors(property);
363:
364: // Explicite editor should be added to editors (if not already present).
365: // The current editor should replace the corresponding default editor.
366: // Replace the delegate editor in ResourceWrapperEditor if needed.
367: for (int i = 0; i < typeEditors.length
368: && (expliciteEditor != null || currentEditor != null); i++) {
369: PropertyEditor prEd = typeEditors[i];
370: ResourceWrapperEditor wrapper = null;
371: if (prEd instanceof ResourceWrapperEditor
372: && !(currentEditor instanceof ResourceWrapperEditor)) {
373: // the current editor might be just loaded and thus not wrapped...
374: wrapper = (ResourceWrapperEditor) prEd;
375: prEd = wrapper.getDelegatedPropertyEditor();
376: }
377: if (currentEditor != null
378: && currentEditor.getClass().equals(
379: prEd.getClass())) {
380: // current editor matches
381: if (wrapper != null) { // silently make it the current editor
382: wrapper
383: .setDelegatedPropertyEditor(currentEditor);
384: boolean fire = property.isChangeFiring();
385: property.setChangeFiring(false);
386: property.setCurrentEditor(wrapper);
387: property.setChangeFiring(fire);
388: PropertyEnv env = getPropertyEnv();
389: if (env != null)
390: wrapper.attachEnv(env);
391: } else {
392: if (prEd instanceof RADConnectionPropertyEditor
393: && ((RADConnectionPropertyEditor) prEd)
394: .getEditorType() != ((RADConnectionPropertyEditor) currentEditor)
395: .getEditorType()) {
396: continue; // there are two types of RAD... editors
397: }
398: typeEditors[i] = currentEditor;
399: }
400: currentEditor = null;
401: } else if (expliciteEditor != null
402: && expliciteEditor.getClass().equals(
403: prEd.getClass())) {
404: if (wrapper != null)
405: wrapper
406: .setDelegatedPropertyEditor(expliciteEditor);
407: else
408: typeEditors[i] = expliciteEditor;
409: expliciteEditor = null;
410: }
411: }
412:
413: int count = typeEditors.length;
414: if (expliciteEditor != null)
415: count++;
416: if (currentEditor != null)
417: count++;
418: if (count > typeEditors.length) {
419: allEditors = new PropertyEditor[count];
420: int index = 0;
421: if (currentEditor != null)
422: allEditors[index++] = currentEditor;
423: if (expliciteEditor != null)
424: allEditors[index++] = expliciteEditor;
425: System.arraycopy(typeEditors, 0, allEditors, index,
426: typeEditors.length);
427: } else
428: allEditors = typeEditors;
429: }
430: return allEditors;
431: }
432:
433: // -------------------------------------------------------------
434: // FormPropertyContainer implementation
435:
436: // public Node.Property[] getProperties() {
437: // if (modifiedEditor instanceof FormPropertyContainer)
438: // return ((FormPropertyContainer)modifiedEditor).getProperties();
439: // else
440: // return null;
441: // }
442:
443: // -----------------------------------------------------------------------------
444:
445: /**
446: * Register a listener for the PropertyChange event. The class will
447: * fire a PropertyChange value whenever the value is updated.
448: *
449: * @param l An object to be invoked when a PropertyChange event is fired.
450: */
451: public void addPropertyChangeListener(PropertyChangeListener l) {
452: synchronized (this ) {
453: if (changeSupport == null)
454: changeSupport = new PropertyChangeSupport(this );
455: }
456: changeSupport.addPropertyChangeListener(l);
457: }
458:
459: /**
460: * Remove a listener for the PropertyChange event.
461: *
462: * @param l The PropertyChange listener to be removed.
463: */
464: public void removePropertyChangeListener(PropertyChangeListener l) {
465: if (changeSupport != null)
466: changeSupport.removePropertyChangeListener(l);
467: }
468:
469: /**
470: * Report that we have been modified to any interested listeners.
471: */
472: void firePropertyChange() {
473: if (changeSupport != null)
474: changeSupport.firePropertyChange(null, null, null);
475: }
476:
477: // -------------
478: // ExPropertyEditor implementation
479:
480: /**
481: * This method is called by the IDE to pass
482: * the environment to the property editor.
483: *
484: * @param env environment.
485: */
486: public void attachEnv(PropertyEnv env) {
487: propertyEnv = new WeakReference<PropertyEnv>(env);
488: PropertyEditor prEd = property.getCurrentEditor();
489: if (prEd instanceof ExPropertyEditor)
490: ((ExPropertyEditor) prEd).attachEnv(env);
491: }
492:
493: // ---------
494: // delegating hashCode() and equals(Object) methods to modifiedEditor - for
495: // PropertyPanel mapping property editors to PropertyEnv
496:
497: @Override
498: public int hashCode() {
499: PropertyEditor prEd = property.getCurrentEditor();
500: return prEd != null ? prEd.hashCode() : super .hashCode();
501: }
502:
503: @Override
504: public boolean equals(Object obj) {
505: return obj != null ? hashCode() == obj.hashCode() : false;
506: }
507: }
|