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-2007 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.visualweb.gravy.properties;
043:
044: import java.awt.Component;
045: import java.awt.event.ActionEvent;
046: import java.beans.PropertyEditor;
047: import java.lang.reflect.InvocationTargetException;
048: import java.lang.reflect.Method;
049: import javax.swing.Action;
050: import javax.swing.JComponent;
051: import javax.swing.JTable;
052: import javax.swing.table.TableCellRenderer;
053: import org.netbeans.jemmy.JemmyException;
054: import org.netbeans.jemmy.JemmyProperties;
055: import org.netbeans.jemmy.Waitable;
056: import org.netbeans.jemmy.Waiter;
057: import org.netbeans.jemmy.operators.ContainerOperator;
058: import org.netbeans.jemmy.operators.JTableOperator;
059: import org.netbeans.jemmy.operators.JTextFieldOperator;
060: import org.openide.ErrorManager;
061: import org.openide.nodes.Node;
062:
063: /**
064: * Handles properties in IDE property sheets. Properties are grouped in
065: * property sheet. Their are identified by their display names. Once you
066: * have created a Property instance you can get value, set a new text value,
067: * set a new value by index of possible options or open custom editor.
068: * <p>
069: * Usage:<br>
070: * <pre>
071: PropertySheetOperator pso = new PropertySheetOperator("Properties of MyClass");
072: Property p = new Property(pso, "Name");
073: System.out.println("\nProperty name="+p.getName());
074: System.out.println("\nProperty value="+p.getValue());
075: p.setValue("ANewValue");
076: // set a new value by index where it is applicable
077: //p.setValue(2);
078: // open custom editor where it is applicable
079: //p.openEditor();
080: * </pre>
081: *
082: * @see PropertySheetOperator
083: */
084: public class Property {
085:
086: // DEPRECATED>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
087: /** Container to find property in */
088: protected ContainerOperator contOper;
089: /** Display name of the property */
090: private String name;
091: /** Operator of name button */
092: private SheetButtonOperator nameButtonOperator;
093: /** Operator of value button */
094: private SheetButtonOperator valueButtonOperator;
095: // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<DEPRECATED
096:
097: /** Class name of string renderer. */
098: public static final String STRING_RENDERER = "org.openide.explorer.propertysheet.RendererFactory$StringRenderer"; // NOI18N
099: /** Class name of check box renderer. */
100: public static final String CHECKBOX_RENDERER = "org.openide.explorer.propertysheet.RendererFactory$CheckboxRenderer"; // NOI18N
101: /** Class name of combo box renderer. */
102: public static final String COMBOBOX_RENDERER = "org.openide.explorer.propertysheet.RendererFactory$ComboboxRenderer"; // NOI18N
103: /** Class name of radio button renderer. */
104: public static final String RADIOBUTTON_RENDERER = "org.openide.explorer.propertysheet.RendererFactory$RadioButtonRenderer"; // NOI18N
105: /** Class name of set renderer. */
106: public static final String SET_RENDERER = "org.openide.explorer.propertysheet.RendererFactory$SetRenderer"; // NOI18N
107:
108: /** Instance of Node.Property. */
109: protected Node.Property property;
110: /** Property sheet where this property resides. */
111: protected PropertySheetOperator propertySheetOper;
112:
113: /** Waits for property with given name in specified property sheet.
114: * @param propertySheetOper PropertySheetOperator where to find property.
115: * @param name property display name
116: */
117: public Property(PropertySheetOperator propertySheetOper, String name) {
118: this .propertySheetOper = propertySheetOper;
119: this .property = waitProperty(propertySheetOper, name);
120: }
121:
122: /** Waits for index-th property in specified property sheet.
123: * @param propertySheetOper PropertySheetOperator where to find property.
124: * @param index index (row number) of property inside property sheet
125: * (starts at 0). If there categories shown in property sheet,
126: * rows occupied by their names must by added to index.
127: */
128: public Property(PropertySheetOperator propertySheetOper, int index) {
129: this .propertySheetOper = propertySheetOper;
130: this .property = waitProperty(propertySheetOper, index);
131: }
132:
133: /** Waits for property with given name in specified container.
134: * @param contOper ContainerOperator where to find property. It is
135: * recommended to use {@link PropertySheetOperator}.
136: * @param name property name
137: * @deprecated Use {@link #Property(PropertySheetOperator, String)} instead
138: */
139: public Property(ContainerOperator contOper, String name) {
140: this (new PropertySheetOperator(contOper), name);
141: /*
142: this.contOper = contOper;
143: this.name = name;
144: this.name = nameButtonOperator().getLabel();
145: */
146: }
147:
148: /** Waits for index-th property in specified container.
149: * @param contOper ContainerOperator whete to find property. It is
150: * recommended to use {@link PropertySheetOperator}.
151: * @param index index (row number) of property inside property sheet
152: * (starts at 0)
153: * @deprecated Use {@link #Property(PropertySheetOperator, int)} instead
154: */
155: public Property(ContainerOperator contOper, int index) {
156: this (new PropertySheetOperator(contOper), index);
157: }
158:
159: /** Waits for property with given name in specified property sheet.
160: * @param propSheetOper PropertySheetOperator where to find property.
161: * @param name property display name
162: */
163: private Node.Property waitProperty(
164: final PropertySheetOperator propSheetOper, final String name) {
165: try {
166: Waiter waiter = new Waiter(new Waitable() {
167: public Object actionProduced(Object param) {
168: Node.Property property = null;
169: JTableOperator table = propSheetOper.tblSheet();
170: for (int row = 0; row < table.getRowCount(); row++) {
171: if (table.getValueAt(row, 1) instanceof Node.Property) {
172: property = (Node.Property) table
173: .getValueAt(row, 1);
174: if (propSheetOper.getComparator().equals(
175: property.getDisplayName(), name)) {
176: return property;
177: }
178: }
179: }
180: return null;
181: }
182:
183: public String getDescription() {
184: return ("Wait property " + name);
185: }
186: });
187: return (Node.Property) waiter.waitAction(null);
188: } catch (InterruptedException e) {
189: throw new JemmyException("Interrupted.", e);
190: }
191: }
192:
193: /** Waits for index-th property in specified property sheet.
194: * @param propSheetOper PropertySheetOperator where to find property.
195: * @param index index (row number) of property inside property sheet
196: * (starts at 0). If there are categories shown in property sheet,
197: * rows occupied by their names must by added to index.
198: */
199: private Node.Property waitProperty(
200: final PropertySheetOperator propSheetOper, final int index) {
201: try {
202: Waiter waiter = new Waiter(new Waitable() {
203: public Object actionProduced(Object param) {
204: JTableOperator table = propSheetOper.tblSheet();
205: if (table.getRowCount() <= index) {
206: // If table is empty or index out of bounds,
207: // it returns null to wait until table is populated by values
208: return null;
209: }
210: Object property = table.getValueAt(index, 1);
211: if (property instanceof Node.Property) {
212: return (Node.Property) property;
213: } else {
214: throw new JemmyException("On row " + index
215: + " in table there is no property");
216: }
217: }
218:
219: public String getDescription() {
220: return ("Wait property on row " + index + " in property sheet.");
221: }
222: });
223: //waiter.setOutput(TestOut.getNullOutput());
224: return (Node.Property) waiter.waitAction(null);
225: } catch (InterruptedException e) {
226: throw new JemmyException("Interrupted.", e);
227: }
228: }
229:
230: /** Gets SheetButtonOperator instance of property's name button. It returns
231: * valid button even if properties were reordered.
232: * @return SheetButtonOperator instance of name button
233: * @deprecated JTable used for property sheet instead of SheetButtons
234: */
235: public SheetButtonOperator nameButtonOperator() {
236: throw new JemmyException(
237: "Don't use this! Property sheet uses JTable instead of SheetButton.");
238: //return null;
239: /*
240: if(nameButtonOperator != null) {
241: if(!nameButtonOperator.isValid()) {
242: nameButtonOperator = null;
243: }
244: }
245: if(nameButtonOperator == null) {
246: nameButtonOperator = SheetButtonOperator.nameButton(contOper, name);
247: }
248: return nameButtonOperator;
249: */
250: }
251:
252: /** Gets SheetButtonOperator instance of property's value button. It returns
253: * valid button even if properties were reordered.
254: * @return SheetButtonOperator instance of value button
255: * @deprecated JTable used for property sheet instead of SheetButtons
256: */
257: public SheetButtonOperator valueButtonOperator() {
258: throw new JemmyException(
259: "Don't use this! Property sheet uses JTable instead of SheetButton.");
260: /*
261: if(valueButtonOperator != null) {
262: if(!valueButtonOperator.isValid()) {
263: valueButtonOperator = null;
264: }
265: }
266: if(valueButtonOperator == null) {
267: // Button can be changed (reordered, changed value, etc.).
268: // We need to call nameButtonOperator().getNameButtonIndex()
269: // to find valid name button for this property.
270: valueButtonOperator = SheetButtonOperator.valueButton(contOper,
271: nameButtonOperator().getNameButtonIndex());
272: }
273: return valueButtonOperator;
274: */
275: }
276:
277: /** Gets display name of this property.
278: * It can differ from name given in constructor when only
279: * substring of property name is used there.
280: * @return display name of property
281: */
282: public String getName() {
283: return property.getDisplayName();
284: }
285:
286: /** Gets string representation of property value.
287: * @return value of property
288: */
289: public String getValue() {
290: PropertyEditor pe = property.getPropertyEditor();
291: try {
292: if (property.getValue() != pe.getValue()) {
293: // Need to synchronize property and its property editor.
294: // Otherwise it may cause problem when called pe.getAsText().
295: pe.setValue(property.getValue());
296: }
297: } catch (Exception e) {
298: throw new JemmyException(
299: "Exception while synchronizing value of property and property editor - property.getValue() != pe.getValue()",
300: e);
301: }
302: return pe.getAsText();
303: }
304:
305: /** Sets value of this property to specified text. If a new value is
306: * not accepted, an information or error dialog is displayed by IDE.
307: * If property is not writable JemmyException is thrown.
308: * @param textValue text to be set in property (e.g. "a new value",
309: * "a new item from list", "false", "TRUE")
310: */
311: public void setValue(String textValue) {
312: propertySheetOper.getOutput().printTrace(
313: "Setting value \"" + textValue + "\" of property \""
314: + getName() + "\".");
315: if (!isEnabled()) {
316: throw new JemmyException("Property \"" + getName()
317: + "\" is read only.");
318: }
319: PropertyEditor pe = property.getPropertyEditor();
320: try {
321: if (property.getValue() != pe.getValue()) {
322: // Need to synchronize property and its property editor.
323: // Otherwise it may cause IAE when called pe.setAsText(textValue).
324: pe.setValue(property.getValue());
325: }
326: } catch (Exception e) {
327: throw new JemmyException(
328: "Exception while synchronizing value of property and property editor - property.getValue() != pe.getValue()",
329: e);
330: }
331: try {
332: JemmyProperties.getCurrentOutput().printTrace(
333: "Setting property \"" + getName()
334: + "\" to value \"" + textValue + "\".");
335: pe.setAsText(textValue);
336: property.setValue(pe.getValue());
337: } catch (IllegalAccessException iae) {
338: ErrorManager.getDefault().notify(iae);
339: } catch (IllegalArgumentException iare) {
340: ErrorManager.getDefault().notify(iare);
341: } catch (InvocationTargetException ite) {
342: ErrorManager.getDefault().notify(ite);
343: } catch (Exception e) {
344: throw new JemmyException(
345: "Exception while setting value of property.", e);
346: }
347: }
348:
349: /** Sets value of this property by given index.
350: * It is applicable for properties which can be changed by combo box.
351: * If property doesn't support changing value by index JemmyException
352: * is thrown.
353: * @param index index of item to be selected from possible options
354: */
355: public void setValue(int index) {
356: PropertyEditor pe = property.getPropertyEditor();
357: String[] tags = pe.getTags();
358: if (tags != null) {
359: setValue(tags[index]);
360: } else {
361: throw new JemmyException(
362: "Property doesn't support changing value by index.");
363: }
364: }
365:
366: /** Returns true if this property is in editable state (it is being edited).
367: * It is detected by presence of PropertySheetButton which stands
368: * for property value in non editable state.
369: * @return true - this property is being edited; false otherwise
370: * @deprecated Use {@link #setValue} to change property value
371: */
372: public boolean isEditable() {
373: throw new JemmyException(
374: "Don't use this! Use setValue() to change property value.");
375: /*
376: // wait for name button index-th PropertyPanel
377: Component propertyPanel = SheetButtonOperator.waitPropertyPanel(contOper,
378: nameButtonOperator().getNameButtonIndex());
379: ComponentChooser chooser = new ComponentChooser() {
380: public boolean checkComponent(Component comp) {
381: return comp.getClass().getName().indexOf("PropertySheetButton") != -1;
382: }
383:
384: public String getDescription() {
385: return "PropertySheetButton";
386: }
387: };
388: // if PropertySheetButton not found, property is editable
389: return contOper.findComponent((Container)propertyPanel, chooser) == null;
390: */
391: }
392:
393: /** If this property is not editable, it scrolls to property and clicks
394: * on name button. Otherwise does nothing.
395: * @deprecated Use {@link #setValue} to change property value
396: */
397: public void startEditing() {
398: throw new JemmyException(
399: "Don't use this! Property sheet uses JTable instead of SheetButton.");
400: /*
401: if(!isEditable()) {
402: nameButtonOperator().push();
403: }
404: */
405: }
406:
407: /** If this property is editable, it scrolls to property if needed and
408: * clicks on name button. It cancels editing and sets original value back.
409: * @deprecated Use {@link #setValue} to change property value
410: */
411: public void stopEditing() {
412: throw new JemmyException(
413: "Don't use this! Property sheet uses JTable instead of SheetButton.");
414: /*
415: if(isEditable()) {
416: nameButtonOperator().push();
417: }
418: */
419: }
420:
421: /** Opens custom property editor for the property by click on "..." button.
422: * It checks whether this property supports custom editor by method
423: * {@link #supportsCustomEditor}.
424: */
425: public void openEditor() {
426: if (supportsCustomEditor()) {
427: final JTableOperator table = this .propertySheetOper
428: .tblSheet();
429: for (int row = 0; row < table.getRowCount(); row++) {
430: if (table.getValueAt(row, 1) instanceof Node.Property) {
431: if (this .property == (Node.Property) table
432: .getValueAt(row, 1)) {
433: // Need to request focus before selection because invokeCustomEditor action works
434: // only when table is focused
435: /* mdk
436: table.makeComponentVisible();
437: table.requestFocus();
438: table.waitHasFocus();
439: */
440: // need to select property first
441: ((javax.swing.JTable) table.getSource())
442: .changeSelection(row, 0, false, false);
443: // find action
444: final Action customEditorAction = ((JComponent) table
445: .getSource()).getActionMap().get(
446: "invokeCustomEditor"); // NOI18N
447: // run action in a separate thread (no block)
448: new Thread(new Runnable() {
449: public void run() {
450: customEditorAction
451: .actionPerformed(new ActionEvent(
452: table.getSource(), 0,
453: null));
454: }
455: }, "Thread to open custom editor no block")
456: .start(); // NOI18N
457: return;
458: }
459: }
460: }
461: }
462: }
463:
464: /** Checks whether this property supports custom editor.
465: * @return true is property supports custom editor, false otherwise
466: */
467: public boolean supportsCustomEditor() {
468: return this .property.getPropertyEditor().supportsCustomEditor();
469: }
470:
471: /** Sets default value for this property. If default value is not available,
472: * it does nothing.
473: */
474: public void setDefaultValue() {
475: try {
476: property.restoreDefaultValue();
477: } catch (Exception e) {
478: throw new JemmyException(
479: "Exception while restoring default value.", e);
480: }
481: }
482:
483: /** Returns true if this property is enabled in property sheet, that means
484: * it is possible to change its value by inplace editor.
485: * @return true if this property is enabled, false otherwise
486: */
487: public boolean isEnabled() {
488: return property.canWrite();
489: }
490:
491: /** Returns true if this property can be edited as text by inplace text field.
492: * It can be both for string renderer or combo box renderer.
493: * @return true if this property can be edited, false otherwise
494: */
495: public boolean canEditAsText() {
496: // if not enabled, it cannot be edited
497: if (!isEnabled()) {
498: return false;
499: }
500: final JTableOperator table = propertySheetOper.tblSheet();
501: for (int row = 0; row < table.getRowCount(); row++) {
502: if (table.getValueAt(row, 1) instanceof Node.Property) {
503: if (property == (Node.Property) table
504: .getValueAt(row, 1)) {
505: table.clickForEdit(row, 1);
506: long oldTimeout = propertySheetOper
507: .getTimeouts()
508: .getTimeout(
509: "ComponentOperator.WaitComponentTimeout");
510: propertySheetOper.getTimeouts().setTimeout(
511: "ComponentOperator.WaitComponentTimeout",
512: 1000);
513: try {
514: new JTextFieldOperator(propertySheetOper);
515: return true;
516: } catch (JemmyException e) {
517: // property cannot be edited as text by inplace editor
518: return false;
519: } finally {
520: // push ESC to stop editing
521: table
522: .pushKey(java.awt.event.KeyEvent.VK_ESCAPE);
523: // reset timeout
524: propertySheetOper
525: .getTimeouts()
526: .setTimeout(
527: "ComponentOperator.WaitComponentTimeout",
528: oldTimeout);
529: }
530: }
531: }
532: }
533: // never should happen
534: throw new JemmyException("Property " + getName()
535: + " not found in this sheet:\n"
536: + propertySheetOper.getSource().toString());
537: }
538:
539: /** Returns class name of renderer used to render this property. It can
540: * be used to determine whether correct renderer is used. Possible values
541: * are defined in constants {@link #STRING_RENDERER}, {@link #CHECKBOX_RENDERER},
542: * {@link #COMBOBOX_RENDERER}, {@link #RADIOBUTTON_RENDERER}, {@link #SET_RENDERER}.
543: * @return class name of renderer used to render this property:
544: * <UL>
545: * <LI>org.openide.explorer.propertysheet.RendererFactory$StringRenderer</LI>
546: * <LI>org.openide.explorer.propertysheet.RendererFactory$CheckboxRenderer</LI>
547: * <LI>org.openide.explorer.propertysheet.RendererFactory$ComboboxRenderer</LI>
548: * <LI>org.openide.explorer.propertysheet.RendererFactory$RadioButtonRenderer</LI>
549: * <LI>org.openide.explorer.propertysheet.RendererFactory$SetRenderer</LI>
550: * </UL>
551: * @see #STRING_RENDERER
552: * @see #CHECKBOX_RENDERER
553: * @see #COMBOBOX_RENDERER
554: * @see #RADIOBUTTON_RENDERER
555: * @see #SET_RENDERER
556: */
557: public String getRendererName() {
558: return getRenderer().getClass().getName();
559: }
560:
561: /** Returns component which represents renderer for this property. */
562: private Component getRenderer() {
563: final JTableOperator table = propertySheetOper.tblSheet();
564: for (int row = 0; row < table.getRowCount(); row++) {
565: if (table.getValueAt(row, 1) instanceof Node.Property) {
566: if (property == (Node.Property) table
567: .getValueAt(row, 1)) {
568: // gets component used to render a value
569: TableCellRenderer renderer = table.getCellRenderer(
570: row, 1);
571: Component comp = renderer
572: .getTableCellRendererComponent(
573: (JTable) table.getSource(), table
574: .getValueAt(row, 1), false,
575: false, row, 1);
576: // We need to find a real renderer because it can be embedded
577: // in ButtonPanel (supplies custom editor button "...")
578: // or IconPanel(supplies property marking).
579: try {
580: Class clazz = Class
581: .forName("org.openide.explorer.propertysheet.RendererPropertyDisplayer");
582: Method findInnermostRendererMethod = clazz
583: .getDeclaredMethod(
584: "findInnermostRenderer",
585: new Class[] { JComponent.class });
586: findInnermostRendererMethod.setAccessible(true);
587: comp = (Component) findInnermostRendererMethod
588: .invoke(null, new Object[] { comp });
589: } catch (Exception e) {
590: throw new JemmyException(
591: "RendererPropertyDisplayer.findInnermostRenderer() by reflection failed.",
592: e);
593: }
594: return comp;
595: }
596: }
597: }
598: // never should happen
599: throw new JemmyException("Property " + getName()
600: + " not found in this sheet:\n"
601: + propertySheetOper.getSource().toString());
602: }
603:
604: /** Gets short description for this property. Short description is also
605: * used in tooltip.
606: * @return short description for this property.
607: */
608: public String getShortDescription() {
609: return this.property.getShortDescription();
610: }
611: }
|