KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > ui > views > properties > PropertySheetEntry


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM Corporation - initial API and implementation
10  * Gunnar Wagenknecht - fix for bug 21756 [PropertiesView] property view sorting
11  *******************************************************************************/

12
13 package org.eclipse.ui.views.properties;
14
15 import java.util.ArrayList JavaDoc;
16 import java.util.Arrays JavaDoc;
17 import java.util.HashMap JavaDoc;
18 import java.util.List JavaDoc;
19 import java.util.Map JavaDoc;
20
21 import org.eclipse.core.commands.common.EventManager;
22 import org.eclipse.jface.viewers.CellEditor;
23 import org.eclipse.jface.viewers.ICellEditorListener;
24 import org.eclipse.jface.viewers.ILabelProvider;
25 import org.eclipse.swt.graphics.Image;
26 import org.eclipse.swt.widgets.Composite;
27 import org.eclipse.ui.internal.views.ViewsPlugin;
28
29 /**
30  * <code>PropertySheetEntry</code> is an implementation of
31  * <code>IPropertySheetEntry</code> which uses <code>IPropertySource</code>
32  * and <code>IPropertyDescriptor</code> to interact with domain model objects.
33  * <p>
34  * Every property sheet entry has a single descriptor (except the root entry
35  * which has none). This descriptor determines what property of its objects it
36  * will display/edit.
37  * </p>
38  * <p>
39  * Entries do not listen for changes in their objects. Since there is no
40  * restriction on properties being independent, a change in one property may
41  * affect other properties. The value of a parent's property may also change. As
42  * a result we are forced to refresh the entire entry tree when a property
43  * changes value.
44  * </p>
45  *
46  * @since 3.0 (was previously internal)
47  */

48 public class PropertySheetEntry extends EventManager implements
49         IPropertySheetEntry {
50
51     /**
52      * The values we are displaying/editing. These objects repesent the value of
53      * one of the properties of the values of our parent entry. Except for the
54      * root entry where they represent the input (selected) objects.
55      */

56     private Object JavaDoc[] values = new Object JavaDoc[0];
57
58     /**
59      * The property sources for the values we are displaying/editing.
60      */

61     private Map JavaDoc sources = new HashMap JavaDoc(0);
62
63     /**
64      * The value of this entry is defined as the the first object in its value
65      * array or, if that object is an <code>IPropertySource</code>, the value
66      * it returns when sent <code>getEditableValue</code>
67      */

68     private Object JavaDoc editValue;
69
70     private PropertySheetEntry parent;
71
72     private IPropertySourceProvider propertySourceProvider;
73
74     private IPropertyDescriptor descriptor;
75
76     private CellEditor editor;
77
78     private String JavaDoc errorText;
79
80     private PropertySheetEntry[] childEntries = null;
81
82     /**
83      * Create the CellEditorListener for this entry. It listens for value
84      * changes in the CellEditor, and cancel and finish requests.
85      */

86     private ICellEditorListener cellEditorListener = new ICellEditorListener() {
87         public void editorValueChanged(boolean oldValidState,
88                 boolean newValidState) {
89             if (!newValidState) {
90                 // currently not valid so show an error message
91
setErrorText(editor.getErrorMessage());
92             } else {
93                 // currently valid
94
setErrorText(null);
95             }
96         }
97
98         public void cancelEditor() {
99             setErrorText(null);
100         }
101
102         public void applyEditorValue() {
103             PropertySheetEntry.this.applyEditorValue();
104         }
105     };
106
107     /*
108      * (non-Javadoc) Method declared on IPropertySheetEntry.
109      */

110     public void addPropertySheetEntryListener(
111             IPropertySheetEntryListener listener) {
112         addListenerObject(listener);
113     }
114
115     /*
116      * (non-Javadoc) Method declared on IPropertySheetEntry.
117      */

118     public void applyEditorValue() {
119         if (editor == null) {
120             return;
121         }
122
123         // Check if editor has a valid value
124
if (!editor.isValueValid()) {
125             setErrorText(editor.getErrorMessage());
126             return;
127         }
128
129         setErrorText(null);
130
131         // See if the value changed and if so update
132
Object JavaDoc newValue = editor.getValue();
133         boolean changed = false;
134         if (values.length > 1) {
135             changed = true;
136         } else if (editValue == null) {
137             if (newValue != null) {
138                 changed = true;
139             }
140         } else if (!editValue.equals(newValue)) {
141             changed = true;
142         }
143
144         // Set the editor value
145
if (changed) {
146             setValue(newValue);
147         }
148     }
149
150     /**
151      * Return the unsorted intersection of all the
152      * <code>IPropertyDescriptor</code>s for the objects.
153      *
154      * @return List
155      */

156     private List JavaDoc computeMergedPropertyDescriptors() {
157         if (values.length == 0) {
158             return new ArrayList JavaDoc(0);
159         }
160
161         IPropertySource firstSource = getPropertySource(values[0]);
162         if (firstSource == null) {
163             return new ArrayList JavaDoc(0);
164         }
165
166         if (values.length == 1) {
167             return Arrays.asList(firstSource.getPropertyDescriptors());
168         }
169
170         // get all descriptors from each object
171
Map JavaDoc[] propertyDescriptorMaps = new Map JavaDoc[values.length];
172         for (int i = 0; i < values.length; i++) {
173             Object JavaDoc object = values[i];
174             IPropertySource source = getPropertySource(object);
175             if (source == null) {
176                 // if one of the selected items is not a property source
177
// then we show no properties
178
return new ArrayList JavaDoc(0);
179             }
180             // get the property descriptors keyed by id
181
propertyDescriptorMaps[i] = computePropertyDescriptorsFor(source);
182         }
183
184         // intersect
185
Map JavaDoc intersection = propertyDescriptorMaps[0];
186         for (int i = 1; i < propertyDescriptorMaps.length; i++) {
187             // get the current ids
188
Object JavaDoc[] ids = intersection.keySet().toArray();
189             for (int j = 0; j < ids.length; j++) {
190                 Object JavaDoc object = propertyDescriptorMaps[i].get(ids[j]);
191                 if (object == null ||
192                 // see if the descriptors (which have the same id) are
193
// compatible
194
!((IPropertyDescriptor) intersection.get(ids[j]))
195                                 .isCompatibleWith((IPropertyDescriptor) object)) {
196                     intersection.remove(ids[j]);
197                 }
198             }
199         }
200
201         // sorting is handled in the PropertySheetViewer, return unsorted (in
202
// the original order)
203
ArrayList JavaDoc result = new ArrayList JavaDoc(intersection.size());
204         IPropertyDescriptor[] firstDescs = firstSource.getPropertyDescriptors();
205         for (int i = 0; i < firstDescs.length; i++) {
206             IPropertyDescriptor desc = firstDescs[i];
207             if (intersection.containsKey(desc.getId())) {
208                 result.add(desc);
209             }
210         }
211         return result;
212     }
213
214     /**
215      * Returns an map of property descritptors (keyed on id) for the given
216      * property source.
217      *
218      * @param source
219      * a property source for which to obtain descriptors
220      * @return a table of decriptors keyed on their id
221      */

222     private Map JavaDoc computePropertyDescriptorsFor(IPropertySource source) {
223         IPropertyDescriptor[] descriptors = source.getPropertyDescriptors();
224         Map JavaDoc result = new HashMap JavaDoc(descriptors.length * 2 + 1);
225         for (int i = 0; i < descriptors.length; i++) {
226             result.put(descriptors[i].getId(), descriptors[i]);
227         }
228         return result;
229     }
230
231     /**
232      * Create our child entries.
233      */

234     private void createChildEntries() {
235         // get the current descriptors
236
List JavaDoc descriptors = computeMergedPropertyDescriptors();
237
238         // rebuild child entries using old when possible
239
PropertySheetEntry[] newEntries = new PropertySheetEntry[descriptors
240                 .size()];
241         for (int i = 0; i < descriptors.size(); i++) {
242             IPropertyDescriptor d = (IPropertyDescriptor) descriptors.get(i);
243             // create new entry
244
PropertySheetEntry entry = createChildEntry();
245             entry.setDescriptor(d);
246             entry.setParent(this);
247             entry.setPropertySourceProvider(propertySourceProvider);
248             entry.refreshValues();
249             newEntries[i] = entry;
250         }
251         // only assign if successful
252
childEntries = newEntries;
253     }
254
255     /**
256      * Factory method to create a new child <code>PropertySheetEntry</code>
257      * instance.
258      * <p>
259      * Subclasses may overwrite to create new instances of their own class.
260      * </p>
261      *
262      * @return a new <code>PropertySheetEntry</code> instance for the
263      * descriptor passed in
264      * @since 3.1
265      */

266     protected PropertySheetEntry createChildEntry() {
267         return new PropertySheetEntry();
268     }
269
270     /*
271      * (non-Javadoc) Method declared on IPropertySheetEntry.
272      */

273     public void dispose() {
274         if (editor != null) {
275             editor.dispose();
276             editor = null;
277         }
278         // recursive call to dispose children
279
PropertySheetEntry[] entriesToDispose = childEntries;
280         childEntries = null;
281         if (entriesToDispose != null) {
282             for (int i = 0; i < entriesToDispose.length; i++) {
283                 // an error in a property source may cause refreshChildEntries
284
// to fail. Since the Workbench handles such errors we
285
// can be left in a state where a child entry is null.
286
if (entriesToDispose[i] != null) {
287                     entriesToDispose[i].dispose();
288                 }
289             }
290         }
291     }
292
293     /**
294      * The child entries of this entry have changed (children added or removed).
295      * Notify all listeners of the change.
296      */

297     private void fireChildEntriesChanged() {
298         Object JavaDoc[] array = getListeners();
299         for (int i = 0; i < array.length; i++) {
300             IPropertySheetEntryListener listener = (IPropertySheetEntryListener) array[i];
301             listener.childEntriesChanged(this);
302         }
303     }
304
305     /**
306      * The error message of this entry has changed. Notify all listeners of the
307      * change.
308      */

309     private void fireErrorMessageChanged() {
310         Object JavaDoc[] array = getListeners();
311         for (int i = 0; i < array.length; i++) {
312             IPropertySheetEntryListener listener = (IPropertySheetEntryListener) array[i];
313             listener.errorMessageChanged(this);
314         }
315     }
316
317     /**
318      * The values of this entry have changed. Notify all listeners of the
319      * change.
320      */

321     private void fireValueChanged() {
322         Object JavaDoc[] array = getListeners();
323         for (int i = 0; i < array.length; i++) {
324             IPropertySheetEntryListener listener = (IPropertySheetEntryListener) array[i];
325             listener.valueChanged(this);
326         }
327     }
328
329     /*
330      * (non-Javadoc) Method declared on IPropertySheetEntry.
331      */

332     public String JavaDoc getCategory() {
333         return descriptor.getCategory();
334     }
335
336     /*
337      * (non-Javadoc) Method declared on IPropertySheetEntry.
338      */

339     public IPropertySheetEntry[] getChildEntries() {
340         if (childEntries == null) {
341             createChildEntries();
342         }
343         return childEntries;
344     }
345
346     /*
347      * (non-Javadoc) Method declared on IPropertySheetEntry.
348      */

349     public String JavaDoc getDescription() {
350         return descriptor.getDescription();
351     }
352
353     /**
354      * Returns the descriptor for this entry.
355      *
356      * @return the descriptor for this entry
357      * @since 3.1 (was previously private)
358      */

359     protected IPropertyDescriptor getDescriptor() {
360         return descriptor;
361     }
362
363     /*
364      * (non-Javadoc) Method declared on IPropertySheetEntry.
365      */

366     public String JavaDoc getDisplayName() {
367         return descriptor.getDisplayName();
368     }
369
370     /*
371      * (non-Javadoc)
372      *
373      * @see org.eclipse.ui.views.properties.IPropertySheetEntry#getEditor(org.eclipse.swt.widgets.Composite)
374      */

375     public CellEditor getEditor(Composite parent) {
376
377         if (editor == null) {
378             editor = descriptor.createPropertyEditor(parent);
379             if (editor != null) {
380                 editor.addListener(cellEditorListener);
381             }
382         }
383         if (editor != null) {
384             editor.setValue(editValue);
385             setErrorText(editor.getErrorMessage());
386         }
387         return editor;
388     }
389
390     /**
391      * Returns the edit value for the object at the given index.
392      *
393      * @param index
394      * the value object index
395      * @return the edit value for the object at the given index
396      */

397     protected Object JavaDoc getEditValue(int index) {
398         Object JavaDoc value = values[index];
399         IPropertySource source = getPropertySource(value);
400         if (source != null) {
401             value = source.getEditableValue();
402         }
403         return value;
404     }
405
406     /*
407      * (non-Javadoc) Method declared on IPropertySheetEntry.
408      */

409     public String JavaDoc getErrorText() {
410         return errorText;
411     }
412
413     /*
414      * (non-Javadoc) Method declared on IPropertySheetEntry.
415      */

416     public String JavaDoc getFilters()[] {
417         return descriptor.getFilterFlags();
418     }
419
420     /*
421      * (non-Javadoc) Method declared on IPropertySheetEntry.
422      */

423     public Object JavaDoc getHelpContextIds() {
424         return descriptor.getHelpContextIds();
425     }
426
427     /*
428      * (non-Javadoc) Method declared on IPropertySheetEntry.
429      */

430     public Image getImage() {
431         ILabelProvider provider = descriptor.getLabelProvider();
432         if (provider == null) {
433             return null;
434         }
435         return provider.getImage(editValue);
436     }
437
438     /**
439      * Returns the parent of this entry.
440      *
441      * @return the parent entry, or <code>null</code> if it has no parent
442      * @since 3.1
443      */

444     protected PropertySheetEntry getParent() {
445         return parent;
446     }
447
448     /**
449      * Returns an property source for the given object.
450      *
451      * @param object
452      * an object for which to obtain a property source or
453      * <code>null</code> if a property source is not available
454      * @return an property source for the given object
455      * @since 3.1 (was previously private)
456      */

457     protected IPropertySource getPropertySource(Object JavaDoc object) {
458         if (sources.containsKey(object))
459             return (IPropertySource) sources.get(object);
460
461         IPropertySource result = null;
462         IPropertySourceProvider provider = propertySourceProvider;
463
464         if (provider == null && object != null) {
465             provider = (IPropertySourceProvider) ViewsPlugin.getAdapter(object,
466                     IPropertySourceProvider.class, false);
467         }
468
469         if (provider != null) {
470             result = provider.getPropertySource(object);
471         } else {
472             result = (IPropertySource)ViewsPlugin.getAdapter(object, IPropertySource.class, false);
473         }
474
475         sources.put(object, result);
476         return result;
477     }
478
479     /*
480      * (non-Javadoc) Method declared on IPropertySheetEntry.
481      */

482     public String JavaDoc getValueAsString() {
483         if (editValue == null) {
484             return "";//$NON-NLS-1$
485
}
486         ILabelProvider provider = descriptor.getLabelProvider();
487         if (provider == null) {
488             return editValue.toString();
489         }
490         String JavaDoc text = provider.getText(editValue);
491         if (text == null) {
492             return "";//$NON-NLS-1$
493
}
494         return text;
495     }
496
497     /**
498      * Returns the value objects of this entry.
499      *
500      * @return the value objects of this entry
501      * @since 3.1 (was previously private)
502      */

503     public Object JavaDoc[] getValues() {
504         return values;
505     }
506
507     /*
508      * (non-Javadoc) Method declared on IPropertySheetEntry.
509      */

510     public boolean hasChildEntries() {
511         if (childEntries != null && childEntries.length > 0) {
512             return true;
513         }
514         // see if we could have entires if we were asked
515
return computeMergedPropertyDescriptors().size() > 0;
516     }
517
518     /**
519      * Update our child entries. This implementation tries to reuse child
520      * entries if possible (if the id of the new descriptor matches the
521      * descriptor id of the old entry).
522      */

523     private void refreshChildEntries() {
524         if (childEntries == null) {
525             // no children to refresh
526
return;
527         }
528
529         // get the current descriptors
530
List JavaDoc descriptors = computeMergedPropertyDescriptors();
531
532         // cache old entries by their descriptor id
533
Map JavaDoc entryCache = new HashMap JavaDoc(childEntries.length * 2 + 1);
534         for (int i = 0; i < childEntries.length; i++) {
535             PropertySheetEntry childEntry = childEntries[i];
536             if (childEntry != null) {
537                 entryCache.put(childEntry.getDescriptor().getId(), childEntry);
538             }
539         }
540
541         // create a list of entries to dispose
542
List JavaDoc entriesToDispose = new ArrayList JavaDoc(Arrays.asList(childEntries));
543
544         // clear the old entries
545
this.childEntries = null;
546
547         // rebuild child entries using old when possible
548
PropertySheetEntry[] newEntries = new PropertySheetEntry[descriptors
549                 .size()];
550         boolean entriesChanged = descriptors.size() != entryCache.size();
551         for (int i = 0; i < descriptors.size(); i++) {
552             IPropertyDescriptor d = (IPropertyDescriptor) descriptors.get(i);
553             // see if we have an entry matching this descriptor
554
PropertySheetEntry entry = (PropertySheetEntry) entryCache.get(d
555                     .getId());
556             if (entry != null) {
557                 // reuse old entry
558
entry.setDescriptor(d);
559                 entriesToDispose.remove(entry);
560             } else {
561                 // create new entry
562
entry = createChildEntry();
563                 entry.setDescriptor(d);
564                 entry.setParent(this);
565                 entry.setPropertySourceProvider(propertySourceProvider);
566                 entriesChanged = true;
567             }
568             entry.refreshValues();
569             newEntries[i] = entry;
570         }
571
572         // only assign if successful
573
this.childEntries = newEntries;
574
575         if (entriesChanged) {
576             fireChildEntriesChanged();
577         }
578
579         // Dispose of entries which are no longer needed
580
for (int i = 0; i < entriesToDispose.size(); i++) {
581             ((IPropertySheetEntry) entriesToDispose.get(i)).dispose();
582         }
583     }
584
585     /**
586      * Refresh the entry tree from the root down.
587      *
588      * @since 3.1 (was previously private)
589      */

590     protected void refreshFromRoot() {
591         if (parent == null) {
592             refreshChildEntries();
593         } else {
594             parent.refreshFromRoot();
595         }
596     }
597
598     /**
599      * Update our value objects. We ask our parent for the property values based
600      * on our descriptor.
601      */

602     private void refreshValues() {
603         // get our parent's value objects
604
Object JavaDoc[] currentSources = parent.getValues();
605
606         // loop through the objects getting our property value from each
607
Object JavaDoc[] newValues = new Object JavaDoc[currentSources.length];
608         for (int i = 0; i < currentSources.length; i++) {
609             IPropertySource source = parent
610                     .getPropertySource(currentSources[i]);
611             newValues[i] = source.getPropertyValue(descriptor.getId());
612         }
613
614         // set our new values
615
setValues(newValues);
616     }
617
618     /*
619      * (non-Javadoc) Method declared on IPropertySheetEntry.
620      */

621     public void removePropertySheetEntryListener(
622             IPropertySheetEntryListener listener) {
623         removeListenerObject(listener);
624     }
625
626     /*
627      * (non-Javadoc) Method declared on IPropertySheetEntry.
628      */

629     public void resetPropertyValue() {
630         if (parent == null) {
631             // root does not have a default value
632
return;
633         }
634
635         // Use our parent's values to reset our values.
636
boolean change = false;
637         Object JavaDoc[] objects = parent.getValues();
638         for (int i = 0; i < objects.length; i++) {
639             IPropertySource source = getPropertySource(objects[i]);
640             if (source.isPropertySet(descriptor.getId())) {
641                 // fix for https://bugs.eclipse.org/bugs/show_bug.cgi?id=21756
642
if (source instanceof IPropertySource2) {
643                     IPropertySource2 extendedSource = (IPropertySource2) source;
644                     // continue with next if property is not resettable
645
if (!extendedSource
646                             .isPropertyResettable(descriptor.getId())) {
647                         continue;
648                     }
649                 }
650                 source.resetPropertyValue(descriptor.getId());
651                 change = true;
652             }
653         }
654         if (change) {
655             refreshFromRoot();
656         }
657     }
658
659     /**
660      * Set the descriptor.
661      *
662      * @param newDescriptor
663      */

664     private void setDescriptor(IPropertyDescriptor newDescriptor) {
665         // if our descriptor is changing, we have to get rid
666
// of our current editor if there is one
667
if (descriptor != newDescriptor && editor != null) {
668             editor.dispose();
669             editor = null;
670         }
671         descriptor = newDescriptor;
672     }
673
674     /**
675      * Set the error text. This should be set to null when the current value is
676      * valid, otherwise it should be set to a error string
677      */

678     private void setErrorText(String JavaDoc newErrorText) {
679         errorText = newErrorText;
680         // inform listeners
681
fireErrorMessageChanged();
682     }
683
684     /**
685      * Sets the parent of the entry to be propertySheetEntry.
686      *
687      * @param propertySheetEntry
688      */

689     private void setParent(PropertySheetEntry propertySheetEntry) {
690         parent = propertySheetEntry;
691     }
692
693     /**
694      * Sets a property source provider for this entry. This provider is used to
695      * obtain an <code>IPropertySource</code> for each of this entries
696      * objects. If no provider is set then a default provider is used.
697      *
698      * @param provider
699      * IPropertySourceProvider
700      */

701     public void setPropertySourceProvider(IPropertySourceProvider provider) {
702         propertySourceProvider = provider;
703     }
704
705     /**
706      * Set the value for this entry.
707      * <p>
708      * We set the given value as the value for all our value objects. We then
709      * call our parent to update the property we represent with the given value.
710      * We then trigger a model refresh.
711      * <p>
712      *
713      * @param newValue
714      * the new value
715      */

716     private void setValue(Object JavaDoc newValue) {
717         // Set the value
718
for (int i = 0; i < values.length; i++) {
719             values[i] = newValue;
720         }
721
722         // Inform our parent
723
parent.valueChanged(this);
724
725         // Refresh the model
726
refreshFromRoot();
727     }
728
729     /**
730      * The <code>PropertySheetEntry</code> implmentation of this method
731      * declared on<code>IPropertySheetEntry</code> will obtain an editable
732      * value for the given objects and update the child entries.
733      * <p>
734      * Updating the child entries will typically call this method on the child
735      * entries and thus the entire entry tree is updated
736      * </p>
737      *
738      * @param objects
739      * the new values for this entry
740      */

741     public void setValues(Object JavaDoc[] objects) {
742         values = objects;
743         sources = new HashMap JavaDoc(values.length * 2 + 1);
744
745         if (values.length == 0) {
746             editValue = null;
747         } else {
748             // set the first value object as the entry's value
749
Object JavaDoc newValue = values[0];
750
751             // see if we should convert the value to an editable value
752
IPropertySource source = getPropertySource(newValue);
753             if (source != null) {
754                 newValue = source.getEditableValue();
755             }
756             editValue = newValue;
757         }
758
759         // update our child entries
760
refreshChildEntries();
761
762         // inform listeners that our value changed
763
fireValueChanged();
764     }
765
766     /**
767      * The value of the given child entry has changed. Therefore we must set
768      * this change into our value objects.
769      * <p>
770      * We must inform our parent so that it can update its value objects
771      * </p>
772      * <p>
773      * Subclasses may override to set the property value in some custom way.
774      * </p>
775      *
776      * @param child
777      * the child entry that changed its value
778      */

779     protected void valueChanged(PropertySheetEntry child) {
780         for (int i = 0; i < values.length; i++) {
781             IPropertySource source = getPropertySource(values[i]);
782             source.setPropertyValue(child.getDescriptor().getId(), child
783                     .getEditValue(i));
784         }
785
786         // inform our parent
787
if (parent != null) {
788             parent.valueChanged(this);
789         }
790     }
791 }
792
Popular Tags