KickJava   Java API By Example, From Geeks To Geeks.

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


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.HashSet JavaDoc;
19 import java.util.Iterator JavaDoc;
20 import java.util.List JavaDoc;
21 import java.util.Map JavaDoc;
22 import java.util.Set JavaDoc;
23
24 import org.eclipse.core.runtime.ListenerList;
25 import org.eclipse.jface.action.IStatusLineManager;
26 import org.eclipse.jface.viewers.CellEditor;
27 import org.eclipse.jface.viewers.ICellEditorListener;
28 import org.eclipse.jface.viewers.ISelection;
29 import org.eclipse.jface.viewers.IStructuredSelection;
30 import org.eclipse.jface.viewers.SelectionChangedEvent;
31 import org.eclipse.jface.viewers.StructuredSelection;
32 import org.eclipse.jface.viewers.Viewer;
33 import org.eclipse.swt.SWT;
34 import org.eclipse.swt.custom.TreeEditor;
35 import org.eclipse.swt.events.ControlAdapter;
36 import org.eclipse.swt.events.ControlEvent;
37 import org.eclipse.swt.events.DisposeEvent;
38 import org.eclipse.swt.events.DisposeListener;
39 import org.eclipse.swt.events.KeyAdapter;
40 import org.eclipse.swt.events.KeyEvent;
41 import org.eclipse.swt.events.MouseAdapter;
42 import org.eclipse.swt.events.MouseEvent;
43 import org.eclipse.swt.events.SelectionAdapter;
44 import org.eclipse.swt.events.SelectionEvent;
45 import org.eclipse.swt.events.TreeEvent;
46 import org.eclipse.swt.events.TreeListener;
47 import org.eclipse.swt.graphics.Image;
48 import org.eclipse.swt.graphics.Point;
49 import org.eclipse.swt.graphics.Rectangle;
50 import org.eclipse.swt.widgets.Composite;
51 import org.eclipse.swt.widgets.Control;
52 import org.eclipse.swt.widgets.Tree;
53 import org.eclipse.swt.widgets.TreeColumn;
54 import org.eclipse.swt.widgets.TreeItem;
55 import org.eclipse.swt.widgets.Widget;
56 import org.eclipse.ui.internal.views.properties.PropertiesMessages;
57
58 /**
59  * The PropertySheetViewer displays the properties of objects. The model for the
60  * viewer consists of a hierarchy of <code>IPropertySheetEntry</code>.
61  * <p>
62  * This viewer also supports the optional catogorization of the first level
63  * <code>IPropertySheetEntry</code> s by using instances of
64  * <code>PropertySheetCategory</code>.
65  *
66  */

67 /* package */
68 class PropertySheetViewer extends Viewer {
69     // The input objects for the viewer
70
private Object JavaDoc[] input;
71
72     // The root entry of the viewer
73
private IPropertySheetEntry rootEntry;
74
75     // The current categories
76
private PropertySheetCategory[] categories;
77
78     // SWT widgets
79
private Tree tree;
80
81     /**
82      * Maintain a map from the PropertySheet entry to its
83      * corresponding TreeItem. This is used in 'findItem' to
84      * greatly increase the performance.
85      */

86     private HashMap JavaDoc entryToItemMap = new HashMap JavaDoc();
87     
88     private TreeEditor treeEditor;
89
90     private static String JavaDoc[] columnLabels = {
91             PropertiesMessages.PropertyViewer_property, PropertiesMessages.PropertyViewer_value };
92
93     private static String JavaDoc MISCELLANEOUS_CATEGORY_NAME = PropertiesMessages.PropertyViewer_misc;
94
95     // Cell editor support.
96
private int columnToEdit = 1;
97
98     private CellEditor cellEditor;
99
100     private IPropertySheetEntryListener entryListener;
101
102     private ICellEditorListener editorListener;
103
104     // Flag to indicate if categories (if any) should be shown
105
private boolean isShowingCategories = true;
106
107     // Flag to indicate expert properties should be shown
108
private boolean isShowingExpertProperties = false;
109
110     // The status line manager for showing messages
111
private IStatusLineManager statusLineManager;
112
113     // Cell editor activation listeners
114
private ListenerList activationListeners = new ListenerList();
115     
116     // the property sheet sorter
117
private PropertySheetSorter sorter = new PropertySheetSorter();
118
119     /**
120      * Creates a property sheet viewer on a newly-created tree control
121      * under the given parent. The viewer has no input, and no root entry.
122      *
123      * @param parent
124      * the parent control
125      */

126     public PropertySheetViewer(Composite parent) {
127         tree = new Tree(parent, SWT.FULL_SELECTION | SWT.SINGLE
128                 | SWT.HIDE_SELECTION);
129
130         // configure the widget
131
tree.setLinesVisible(true);
132         tree.setHeaderVisible(true);
133
134         // configure the columns
135
addColumns();
136
137         // add our listeners to the widget
138
hookControl();
139
140         // create a new tree editor
141
treeEditor = new TreeEditor(tree);
142
143         // create the entry and editor listener
144
createEntryListener();
145         createEditorListener();
146     }
147
148     /**
149      * Activate a cell editor for the given selected tree item.
150      *
151      * @param item
152      * the selected tree item
153      */

154     private void activateCellEditor(TreeItem item) {
155         // ensure the cell editor is visible
156
tree.showSelection();
157
158         // Get the entry for this item
159
IPropertySheetEntry activeEntry = (IPropertySheetEntry) item.getData();
160
161         // Get the cell editor for the entry.
162
// Note that the editor parent must be the Tree control
163
cellEditor = activeEntry.getEditor(tree);
164
165         if (cellEditor == null) {
166             // unable to create the editor
167
return;
168         }
169
170         // activate the cell editor
171
cellEditor.activate();
172
173         // if the cell editor has no control we can stop now
174
Control control = cellEditor.getControl();
175         if (control == null) {
176             cellEditor.deactivate();
177             cellEditor = null;
178             return;
179         }
180
181         // add our editor listener
182
cellEditor.addListener(editorListener);
183
184         // set the layout of the tree editor to match the cell editor
185
CellEditor.LayoutData layout = cellEditor.getLayoutData();
186         treeEditor.horizontalAlignment = layout.horizontalAlignment;
187         treeEditor.grabHorizontal = layout.grabHorizontal;
188         treeEditor.minimumWidth = layout.minimumWidth;
189         treeEditor.setEditor(control, item, columnToEdit);
190
191         // set the error text from the cel editor
192
setErrorMessage(cellEditor.getErrorMessage());
193
194         // give focus to the cell editor
195
cellEditor.setFocus();
196
197         // notify of activation
198
fireCellEditorActivated(cellEditor);
199     }
200
201     /**
202      * Adds a cell editor activation listener. Has no effect if an identical
203      * activation listener is already registered.
204      *
205      * @param listener
206      * a cell editor activation listener
207      */

208     /* package */
209     void addActivationListener(ICellEditorActivationListener listener) {
210         activationListeners.add(listener);
211     }
212
213     /**
214      * Add columns to the tree and set up the layout manager accordingly.
215      */

216     private void addColumns() {
217
218         // create the columns
219
TreeColumn[] columns = tree.getColumns();
220         for (int i = 0; i < columnLabels.length; i++) {
221             String JavaDoc string = columnLabels[i];
222             if (string != null) {
223                 TreeColumn column;
224                 if (i < columns.length) {
225                     column = columns[i];
226                 } else {
227                     column = new TreeColumn(tree, 0);
228                 }
229                 column.setText(string);
230             }
231         }
232
233         tree.addControlListener(new ControlAdapter() {
234             public void controlResized(ControlEvent e) {
235                 Rectangle area = tree.getClientArea();
236                 TreeColumn[] columns = tree.getColumns();
237                 if (area.width > 0) {
238                     columns[0].setWidth(area.width * 40 / 100);
239                     columns[1].setWidth(area.width - columns[0].getWidth() - 4);
240                     tree.removeControlListener(this);
241                 }
242             }
243         });
244
245     }
246
247     /**
248      * Asks the entry currently being edited to apply its current cell editor
249      * value.
250      */

251     private void applyEditorValue() {
252         TreeItem treeItem = treeEditor.getItem();
253         // treeItem can be null when view is opened
254
if (treeItem == null || treeItem.isDisposed()) {
255             return;
256         }
257         IPropertySheetEntry entry = (IPropertySheetEntry) treeItem.getData();
258         entry.applyEditorValue();
259     }
260
261     /**
262      * Creates the child items for the given widget (item or tree). This
263      * method is called when the item is expanded for the first time or when an
264      * item is assigned as the root of the tree.
265      * @param widget TreeItem or Tree to create the children in.
266      */

267     private void createChildren(Widget widget) {
268         // get the current child items
269
TreeItem[] childItems = getChildItems(widget);
270
271         if (childItems.length > 0) {
272             Object JavaDoc data = childItems[0].getData();
273             if (data != null) {
274                 // children already there!
275
return;
276             }
277             // remove the dummy
278
childItems[0].dispose();
279         }
280
281         // get the children and create their tree items
282
Object JavaDoc node = widget.getData();
283         List JavaDoc children = getChildren(node);
284         if (children.isEmpty()) {
285             // this item does't actually have any children
286
return;
287         }
288         for (int i = 0; i < children.size(); i++) {
289             // create a new tree item
290
createItem(children.get(i), widget, i);
291         }
292     }
293
294     /**
295      * Creates a new cell editor listener.
296      */

297     private void createEditorListener() {
298         editorListener = new ICellEditorListener() {
299             public void cancelEditor() {
300                 deactivateCellEditor();
301             }
302
303             public void editorValueChanged(boolean oldValidState,
304                     boolean newValidState) {
305                 //Do nothing
306
}
307
308             public void applyEditorValue() {
309                 //Do nothing
310
}
311         };
312     }
313
314     /**
315      * Creates a new property sheet entry listener.
316      */

317     private void createEntryListener() {
318         entryListener = new IPropertySheetEntryListener() {
319             public void childEntriesChanged(IPropertySheetEntry entry) {
320                 // update the children of the given entry
321
if (entry == rootEntry) {
322                     updateChildrenOf(entry, tree);
323                 } else {
324                     TreeItem item = findItem(entry);
325                     if (item != null) {
326                         updateChildrenOf(entry, item);
327                     }
328                 }
329             }
330
331             public void valueChanged(IPropertySheetEntry entry) {
332                 // update the given entry
333
TreeItem item = findItem(entry);
334                 if (item != null) {
335                     updateEntry(entry, item);
336                 }
337             }
338
339             public void errorMessageChanged(IPropertySheetEntry entry) {
340                 // update the error message
341
setErrorMessage(entry.getErrorText());
342             }
343         };
344     }
345
346     /**
347      * Creates a new tree item, sets the given entry or category (node)in
348      * its user data field, and adds a listener to the node if it is an entry.
349      *
350      * @param node
351      * the entry or category associated with this item
352      * @param parent
353      * the parent widget
354      * @param index
355      * indicates the position to insert the item into its parent
356      */

357     private void createItem(Object JavaDoc node, Widget parent, int index) {
358         // create the item
359
TreeItem item;
360         if (parent instanceof TreeItem) {
361             item = new TreeItem((TreeItem) parent, SWT.NONE, index);
362         } else {
363             item = new TreeItem((Tree) parent, SWT.NONE, index);
364         }
365
366         // set the user data field
367
item.setData(node);
368         
369         // Cache the entry <-> tree item relationship
370
entryToItemMap.put(node, item);
371         
372         // Always ensure that if the tree item goes away that it's
373
// removed from the cache
374
item.addDisposeListener(new DisposeListener() {
375             public void widgetDisposed(DisposeEvent e) {
376                 Object JavaDoc possibleEntry = e.widget.getData();
377                 if (possibleEntry != null)
378                     entryToItemMap.remove(possibleEntry);
379             }
380         });
381
382         // add our listener
383
if (node instanceof IPropertySheetEntry) {
384             ((IPropertySheetEntry) node)
385                     .addPropertySheetEntryListener(entryListener);
386         }
387
388         // update the visual presentation
389
if (node instanceof IPropertySheetEntry) {
390             updateEntry((IPropertySheetEntry) node, item);
391         } else {
392             updateCategory((PropertySheetCategory) node, item);
393         }
394     }
395
396     /**
397      * Deactivate the currently active cell editor.
398      */

399     /* package */
400     void deactivateCellEditor() {
401         treeEditor.setEditor(null, null, columnToEdit);
402         if (cellEditor != null) {
403             cellEditor.deactivate();
404             fireCellEditorDeactivated(cellEditor);
405             cellEditor.removeListener(editorListener);
406             cellEditor = null;
407         }
408         // clear any error message from the editor
409
setErrorMessage(null);
410     }
411
412     /**
413      * Sends out a selection changed event for the entry tree to all registered
414      * listeners.
415      */

416     private void entrySelectionChanged() {
417         SelectionChangedEvent changeEvent = new SelectionChangedEvent(this,
418                 getSelection());
419         fireSelectionChanged(changeEvent);
420     }
421
422     /**
423      * Return a tree item in the property sheet that has the same entry in
424      * its user data field as the supplied entry. Return <code>null</code> if
425      * there is no such item.
426      *
427      * @param entry
428      * the entry to serach for
429      * @return the TreeItem for the entry or <code>null</code> if
430      * there isn't one.
431      */

432     private TreeItem findItem(IPropertySheetEntry entry) {
433         // Iterate through treeItems to find item
434
TreeItem[] items = tree.getItems();
435         for (int i = 0; i < items.length; i++) {
436             TreeItem item = items[i];
437             TreeItem findItem = findItem(entry, item);
438             if (findItem != null) {
439                 return findItem;
440             }
441         }
442         return null;
443     }
444
445     /**
446      * Return a tree item in the property sheet that has the same entry in
447      * its user data field as the supplied entry. Return <code>null</code> if
448      * there is no such item.
449      *
450      * @param entry
451      * the entry to search for
452      * @param item
453      * the item look in
454      * @return the TreeItem for the entry or <code>null</code> if
455      * there isn't one.
456      */

457     private TreeItem findItem(IPropertySheetEntry entry, TreeItem item) {
458         // If we can find the TreeItem in the cache, just return it
459
Object JavaDoc mapItem = entryToItemMap.get(entry);
460         if (mapItem != null && mapItem instanceof TreeItem)
461             return (TreeItem) mapItem;
462         
463         // compare with current item
464
if (entry == item.getData()) {
465             return item;
466         }
467
468         // recurse over children
469
TreeItem[] items = item.getItems();
470         for (int i = 0; i < items.length; i++) {
471             TreeItem childItem = items[i];
472             TreeItem findItem = findItem(entry, childItem);
473             if (findItem != null) {
474                 return findItem;
475             }
476         }
477         return null;
478     }
479
480     /**
481      * Notifies all registered cell editor activation listeners of a cell editor
482      * activation.
483      *
484      * @param activatedCellEditor
485      * the activated cell editor
486      */

487     private void fireCellEditorActivated(CellEditor activatedCellEditor) {
488         Object JavaDoc[] listeners = activationListeners.getListeners();
489         for (int i = 0; i < listeners.length; ++i) {
490             ((ICellEditorActivationListener) listeners[i])
491                     .cellEditorActivated(activatedCellEditor);
492         }
493     }
494
495     /**
496      * Notifies all registered cell editor activation listeners of a cell editor
497      * deactivation.
498      *
499      * @param activatedCellEditor
500      * the deactivated cell editor
501      */

502     private void fireCellEditorDeactivated(CellEditor activatedCellEditor) {
503         Object JavaDoc[] listeners = activationListeners.getListeners();
504         for (int i = 0; i < listeners.length; ++i) {
505             ((ICellEditorActivationListener) listeners[i])
506                     .cellEditorDeactivated(activatedCellEditor);
507         }
508     }
509
510     /**
511      * Returns the active cell editor of this property sheet viewer or
512      * <code>null</code> if no cell editor is active.
513      *
514      * @return the active cell editor
515      */

516     public CellEditor getActiveCellEditor() {
517         return cellEditor;
518     }
519
520     private TreeItem[] getChildItems(Widget widget) {
521         if (widget instanceof Tree) {
522             return ((Tree) widget).getItems();
523         }
524         else if (widget instanceof TreeItem) {
525             return ((TreeItem) widget).getItems();
526         }
527         // shouldn't happen
528
return new TreeItem[0];
529     }
530     
531     /**
532      * Returns the sorted children of the given category or entry
533      *
534      * @param node a category or entry
535      * @return the children of the given category or entry
536      * (element type <code>IPropertySheetEntry</code> or
537      * <code>PropertySheetCategory</code>)
538      */

539     private List JavaDoc getChildren(Object JavaDoc node) {
540         // cast the entry or category
541
IPropertySheetEntry entry = null;
542         PropertySheetCategory category = null;
543         if (node instanceof IPropertySheetEntry) {
544             entry = (IPropertySheetEntry) node;
545         } else {
546             category = (PropertySheetCategory) node;
547         }
548
549         // get the child entries or categories
550
List JavaDoc children;
551         if (category == null) {
552             children = getChildren(entry);
553         } else {
554             children = getChildren(category);
555         }
556
557         return children;
558     }
559
560     /**
561      * Returns the child entries of the given entry
562      * @param entry The entry to search
563      *
564      * @return the children of the given entry (element type
565      * <code>IPropertySheetEntry</code>)
566      */

567     private List JavaDoc getChildren(IPropertySheetEntry entry) {
568         // if the entry is the root and we are showing categories, and we have
569
// more than the
570
// defualt category, return the categories
571
if (entry == rootEntry && isShowingCategories) {
572             if (categories.length > 1
573                     || (categories.length == 1 && !categories[0]
574                             .getCategoryName().equals(
575                                     MISCELLANEOUS_CATEGORY_NAME))) {
576                 return Arrays.asList(categories);
577             }
578         }
579
580         // return the sorted & filtered child entries
581
return getSortedEntries(getFilteredEntries(entry.getChildEntries()));
582     }
583
584     /**
585      * Returns the child entries of the given category
586      *
587      * @param category The category to search
588      *
589      * @return the children of the given category (element type
590      * <code>IPropertySheetEntry</code>)
591      */

592     private List JavaDoc getChildren(PropertySheetCategory category) {
593         return getSortedEntries(getFilteredEntries(category.getChildEntries()));
594     }
595
596     /*
597      * (non-Javadoc) Method declared on Viewer.
598      */

599     public Control getControl() {
600         return tree;
601     }
602
603     /**
604      * Returns the entries which match the current filter.
605      *
606      * @param entries the entries to filter
607      * @return the entries which match the current filter
608      * (element type <code>IPropertySheetEntry</code>)
609      */

610     private List JavaDoc getFilteredEntries(IPropertySheetEntry[] entries) {
611         // if no filter just return all entries
612
if (isShowingExpertProperties) {
613             return Arrays.asList(entries);
614         }
615
616         // check each entry for the filter
617
List JavaDoc filteredEntries = new ArrayList JavaDoc(entries.length);
618         for (int i = 0; i < entries.length; i++) {
619             IPropertySheetEntry entry = entries[i];
620             if (entry != null) {
621                 String JavaDoc[] filters = entry.getFilters();
622                 boolean expert = false;
623                 if (filters != null) {
624                     for (int j = 0; j < filters.length; j++) {
625                         if (filters[j].equals(IPropertySheetEntry.FILTER_ID_EXPERT)) {
626                             expert = true;
627                             break;
628                         }
629                     }
630                 }
631                 if (!expert) {
632                     filteredEntries.add(entry);
633                 }
634             }
635         }
636         return filteredEntries;
637     }
638     
639     /**
640      * Returns a sorted list of <code>IPropertySheetEntry</code> entries.
641      *
642      * @param unsortedEntries
643      * unsorted list of <code>IPropertySheetEntry</code>
644      * @return a sorted list of the specified entries
645      */

646     private List JavaDoc getSortedEntries(List JavaDoc unsortedEntries) {
647         IPropertySheetEntry[] propertySheetEntries = (IPropertySheetEntry[]) unsortedEntries
648                 .toArray(new IPropertySheetEntry[unsortedEntries.size()]);
649         sorter.sort(propertySheetEntries);
650         return Arrays.asList(propertySheetEntries);
651     }
652     
653
654     /**
655      * The <code>PropertySheetViewer</code> implementation of this method
656      * declared on <code>IInputProvider</code> returns the objects for which
657      * the viewer is currently showing properties. It returns an
658      * <code>Object[]</code> or <code>null</code>.
659      */

660     public Object JavaDoc getInput() {
661         return input;
662     }
663
664     /**
665      * Returns the root entry for this property sheet viewer. The root entry is
666      * not visible in the viewer.
667      *
668      * @return the root entry or <code>null</code>.
669      */

670     public IPropertySheetEntry getRootEntry() {
671         return rootEntry;
672     }
673
674     /**
675      * The <code>PropertySheetViewer</code> implementation of this
676      * <code>ISelectionProvider</code> method returns the result as a
677      * <code>StructuredSelection</code>.
678      * <p>
679      * Note that this method only includes <code>IPropertySheetEntry</code> in
680      * the selection (no categories).
681      * </p>
682      */

683     public ISelection getSelection() {
684         if (tree.getSelectionCount() == 0) {
685             return StructuredSelection.EMPTY;
686         }
687         TreeItem[] sel = tree.getSelection();
688         List JavaDoc entries = new ArrayList JavaDoc(sel.length);
689         for (int i = 0; i < sel.length; i++) {
690             TreeItem ti = sel[i];
691             Object JavaDoc data = ti.getData();
692             if (data instanceof IPropertySheetEntry) {
693                 entries.add(data);
694             }
695         }
696         return new StructuredSelection(entries);
697     }
698
699     /**
700      * Selection in the viewer occurred. Check if there is an active cell
701      * editor. If yes, deactivate it and check if a new cell editor must be
702      * activated.
703      *
704      * @param selection
705      * the TreeItem that is selected
706      */

707     private void handleSelect(TreeItem selection) {
708         // deactivate the current cell editor
709
if (cellEditor != null) {
710             applyEditorValue();
711             deactivateCellEditor();
712         }
713
714         // get the new selection
715
TreeItem[] sel = new TreeItem[] { selection };
716         if (sel.length == 0) {
717             setMessage(null);
718             setErrorMessage(null);
719         } else {
720             Object JavaDoc object = sel[0].getData(); // assume single selection
721
if (object instanceof IPropertySheetEntry) {
722                 // get the entry for this item
723
IPropertySheetEntry activeEntry = (IPropertySheetEntry) object;
724
725                 // display the description for the item
726
setMessage(activeEntry.getDescription());
727
728                 // activate a cell editor on the selection
729
activateCellEditor(sel[0]);
730             }
731         }
732         entrySelectionChanged();
733     }
734
735     /**
736      * The expand icon for a node in this viewer has been selected to collapse a
737      * subtree. Deactivate the cell editor
738      *
739      * @param event
740      * the SWT tree event
741      */

742     private void handleTreeCollapse(TreeEvent event) {
743         if (cellEditor != null) {
744             applyEditorValue();
745             deactivateCellEditor();
746         }
747     }
748
749     /**
750      * The expand icon for a node in this viewer has been selected to expand the
751      * subtree. Create the children 1 level deep.
752      * <p>
753      * Note that we use a "dummy" item (no user data) to show a "+" icon beside
754      * an item which has children before the item is expanded now that it is
755      * being expanded we have to create the real child items
756      * </p>
757      *
758      * @param event
759      * the SWT tree event
760      */

761     private void handleTreeExpand(TreeEvent event) {
762         createChildren(event.item);
763     }
764
765     /**
766      * Hides the categories.
767      */

768     /* package */
769     void hideCategories() {
770         isShowingCategories = false;
771         categories = null;
772         refresh();
773     }
774
775     /**
776      * Hides the expert properties.
777      */

778     /* package */
779     void hideExpert() {
780         isShowingExpertProperties = false;
781         refresh();
782     }
783
784     /**
785      * Establish this viewer as a listener on the control
786      */

787     private void hookControl() {
788         // Handle selections in the Tree
789
// Part1: Double click only (allow traversal via keyboard without
790
// activation
791
tree.addSelectionListener(new SelectionAdapter() {
792             /* (non-Javadoc)
793              * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
794              */

795             public void widgetSelected(SelectionEvent e) {
796                 // The viewer only owns the status line when there is
797
// no 'active' cell editor
798
if (cellEditor == null || !cellEditor.isActivated()) {
799                     updateStatusLine(e.item);
800                 }
801             }
802
803             /* (non-Javadoc)
804              * @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent)
805              */

806             public void widgetDefaultSelected(SelectionEvent e) {
807                 handleSelect((TreeItem) e.item);
808             }
809         });
810         // Part2: handle single click activation of cell editor
811
tree.addMouseListener(new MouseAdapter() {
812             public void mouseDown(MouseEvent event) {
813                 // only activate if there is a cell editor
814
Point pt = new Point(event.x, event.y);
815                 TreeItem item = tree.getItem(pt);
816                 if (item != null) {
817                     handleSelect(item);
818                 }
819             }
820         });
821
822         // Add a tree listener to expand and collapse which
823
// allows for lazy creation of children
824
tree.addTreeListener(new TreeListener() {
825             public void treeExpanded(final TreeEvent event) {
826                 handleTreeExpand(event);
827             }
828
829             public void treeCollapsed(final TreeEvent event) {
830                 handleTreeCollapse(event);
831             }
832         });
833
834         // Refresh the tree when F5 pressed
835
tree.addKeyListener(new KeyAdapter() {
836             public void keyReleased(KeyEvent e) {
837                 if (e.character == SWT.ESC) {
838                     deactivateCellEditor();
839                 } else if (e.keyCode == SWT.F5) {
840                     // The following will simulate a reselect
841
setInput(getInput());
842                 }
843             }
844         });
845     }
846
847     /**
848      * Update the status line based on the data of item.
849      * @param item
850      */

851     protected void updateStatusLine(Widget item) {
852         setMessage(null);
853         setErrorMessage(null);
854         
855         // Update the status line
856
if (item != null) {
857             if (item.getData() instanceof PropertySheetEntry) {
858                 PropertySheetEntry psEntry = (PropertySheetEntry) item.getData();
859                 
860                 // For entries, show the description if any, else show the label
861
String JavaDoc desc = psEntry.getDescription();
862                 if (desc != null && desc.length() > 0) {
863                     setMessage(psEntry.getDescription());
864                 } else {
865                     setMessage(psEntry.getDisplayName());
866                 }
867             }
868                 
869             else if (item.getData() instanceof PropertySheetCategory) {
870                 PropertySheetCategory psCat = (PropertySheetCategory) item.getData();
871                 setMessage(psCat.getCategoryName());
872             }
873         }
874     }
875
876     /**
877      * Updates all of the items in the tree.
878      * <p>
879      * Note that this means ensuring that the tree items reflect the state of
880      * the model (entry tree) it does not mean telling the model to update
881      * itself.
882      * </p>
883      */

884     public void refresh() {
885         if (rootEntry != null) {
886             updateChildrenOf(rootEntry, tree);
887         }
888     }
889
890     /**
891      * Removes the given cell editor activation listener from this viewer. Has
892      * no effect if an identical activation listener is not registered.
893      *
894      * @param listener
895      * a cell editor activation listener
896      */

897     /* package */
898     void removeActivationListener(ICellEditorActivationListener listener) {
899         activationListeners.remove(listener);
900     }
901
902     /**
903      * Remove the given item from the tree. Remove our listener if the
904      * item's user data is a an entry then set the user data to null
905      *
906      * @param item
907      * the item to remove
908      */

909     private void removeItem(TreeItem item) {
910         Object JavaDoc data = item.getData();
911         if (data instanceof IPropertySheetEntry) {
912             ((IPropertySheetEntry) data)
913                     .removePropertySheetEntryListener(entryListener);
914         }
915         item.setData(null);
916         
917         // We explicitly remove the entry from the map since it's data has been null'd
918
entryToItemMap.remove(data);
919
920         item.dispose();
921     }
922
923     /**
924      * Reset the selected properties to their default values.
925      */

926     public void resetProperties() {
927         // Determine the selection
928
IStructuredSelection selection = (IStructuredSelection) getSelection();
929
930         // Iterate over entries and reset them
931
Iterator JavaDoc itr = selection.iterator();
932         while (itr.hasNext()) {
933             ((IPropertySheetEntry) itr.next()).resetPropertyValue();
934         }
935     }
936
937     /**
938      * Sets the error message to be displayed in the status line.
939      *
940      * @param errorMessage
941      * the message to be displayed, or <code>null</code>
942      */

943     private void setErrorMessage(String JavaDoc errorMessage) {
944         // show the error message
945
if (statusLineManager != null) {
946             statusLineManager.setErrorMessage(errorMessage);
947         }
948     }
949
950     /**
951      * The <code>PropertySheetViewer</code> implementation of this method
952      * declared on <code>Viewer</code> method sets the objects for which the
953      * viewer is currently showing properties.
954      * <p>
955      * The input must be an <code>Object[]</code> or <code>null</code>.
956      * </p>
957      *
958      * @param newInput
959      * the input of this viewer, or <code>null</code> if none
960      */

961     public void setInput(Object JavaDoc newInput) {
962         // need to save any changed value when user clicks elsewhere
963
applyEditorValue();
964         // deactivate our cell editor
965
deactivateCellEditor();
966
967         // set the new input to the root entry
968
input = (Object JavaDoc[]) newInput;
969         if (input == null) {
970             input = new Object JavaDoc[0];
971         }
972
973         if (rootEntry != null) {
974             rootEntry.setValues(input);
975             // ensure first level children are visible
976
updateChildrenOf(rootEntry, tree);
977         }
978     }
979
980     /**
981      * Sets the message to be displayed in the status line. This message is
982      * displayed when there is no error message.
983      *
984      * @param message
985      * the message to be displayed, or <code>null</code>
986      */

987     private void setMessage(String JavaDoc message) {
988         // show the message
989
if (statusLineManager != null) {
990             statusLineManager.setMessage(message);
991         }
992     }
993
994     /**
995      * Sets the root entry for this property sheet viewer. The root entry is not
996      * visible in the viewer.
997      *
998      * @param root
999      * the root entry
1000     */

1001    public void setRootEntry(IPropertySheetEntry root) {
1002        // If we have a root entry, remove our entry listener
1003
if (rootEntry != null) {
1004            rootEntry.removePropertySheetEntryListener(entryListener);
1005        }
1006
1007        rootEntry = root;
1008
1009        // Set the root as user data on the tree
1010
tree.setData(rootEntry);
1011
1012        // Add an IPropertySheetEntryListener to listen for entry change
1013
// notifications
1014
rootEntry.addPropertySheetEntryListener(entryListener);
1015
1016        // Pass our input to the root, this will trigger entry change
1017
// callbacks to update this viewer
1018
setInput(input);
1019    }
1020
1021    /*
1022     * (non-Javadoc)
1023     * @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection, boolean)
1024     */

1025    public void setSelection(ISelection selection, boolean reveal) {
1026        //Do nothing by default
1027
}
1028
1029    /**
1030     * Sets the sorter for this viewer.
1031     * <p>
1032     * The default sorter sorts categories and entries alphabetically.
1033     * A viewer update needs to be triggered after the sorter has changed.
1034     * </p>
1035     * @param sorter the sorter to set (<code>null</code> will reset to the
1036     * default sorter)
1037     * @since 3.1
1038     */

1039    public void setSorter(PropertySheetSorter sorter) {
1040        if (null == sorter) {
1041            sorter = new PropertySheetSorter();
1042        }
1043        this.sorter = sorter;
1044    }
1045
1046    /**
1047     * Sets the status line manager this view will use to show messages.
1048     *
1049     * @param manager
1050     * the status line manager
1051     */

1052    public void setStatusLineManager(IStatusLineManager manager) {
1053        statusLineManager = manager;
1054    }
1055
1056    /**
1057     * Shows the categories.
1058     */

1059    /* package */
1060    void showCategories() {
1061        isShowingCategories = true;
1062        refresh();
1063    }
1064
1065    /**
1066     * Shows the expert properties.
1067     */

1068    /* package */
1069    void showExpert() {
1070        isShowingExpertProperties = true;
1071        refresh();
1072    }
1073
1074    /**
1075     * Updates the categories. Reuses old categories if possible.
1076     */

1077    private void updateCategories() {
1078        // lazy initialize
1079
if (categories == null) {
1080            categories = new PropertySheetCategory[0];
1081        }
1082
1083        // get all the filtered child entries of the root
1084
List JavaDoc childEntries = getFilteredEntries(rootEntry.getChildEntries());
1085
1086        // if the list is empty, just set an empty categories array
1087
if (childEntries.size() == 0) {
1088            categories = new PropertySheetCategory[0];
1089            return;
1090        }
1091
1092        // cache old categories by their descriptor name
1093
Map JavaDoc categoryCache = new HashMap JavaDoc(categories.length * 2 + 1);
1094        for (int i = 0; i < categories.length; i++) {
1095            categories[i].removeAllEntries();
1096            categoryCache.put(categories[i].getCategoryName(), categories[i]);
1097        }
1098
1099        // create a list of categories to get rid of
1100
List JavaDoc categoriesToRemove = new ArrayList JavaDoc(Arrays.asList(categories));
1101
1102        // Determine the categories
1103
PropertySheetCategory misc = (PropertySheetCategory) categoryCache
1104                .get(MISCELLANEOUS_CATEGORY_NAME);
1105        if (misc == null) {
1106            misc = new PropertySheetCategory(MISCELLANEOUS_CATEGORY_NAME);
1107        }
1108        boolean addMisc = false;
1109
1110        for (int i = 0; i < childEntries.size(); i++) {
1111            IPropertySheetEntry childEntry = (IPropertySheetEntry) childEntries
1112                    .get(i);
1113            String JavaDoc categoryName = childEntry.getCategory();
1114            if (categoryName == null) {
1115                misc.addEntry(childEntry);
1116                addMisc = true;
1117                categoriesToRemove.remove(misc);
1118            } else {
1119                PropertySheetCategory category = (PropertySheetCategory) categoryCache
1120                        .get(categoryName);
1121                if (category == null) {
1122                    category = new PropertySheetCategory(categoryName);
1123                    categoryCache.put(categoryName, category);
1124                } else {
1125                    categoriesToRemove.remove(category);
1126                }
1127                category.addEntry(childEntry);
1128            }
1129        }
1130
1131        // Add the PSE_MISC category if it has entries
1132
if (addMisc) {
1133            categoryCache.put(MISCELLANEOUS_CATEGORY_NAME, misc);
1134        }
1135        
1136        // Sort the categories.
1137
// Rather than just sorting categoryCache.values(), we'd like the original order to be preserved
1138
// (with misc added at the end, if needed) before passing to the sorter.
1139
ArrayList JavaDoc categoryList = new ArrayList JavaDoc();
1140        Set JavaDoc seen = new HashSet JavaDoc(childEntries.size());
1141        for (int i = 0; i < childEntries.size(); i++) {
1142            IPropertySheetEntry childEntry = (IPropertySheetEntry) childEntries
1143                    .get(i);
1144            String JavaDoc categoryName = childEntry.getCategory();
1145            if (categoryName != null && !seen.contains(categoryName)) {
1146                seen.add(categoryName);
1147                PropertySheetCategory category = (PropertySheetCategory) categoryCache
1148                        .get(categoryName);
1149                if (category != null) {
1150                    categoryList.add(category);
1151                }
1152            }
1153        }
1154        if (addMisc && !seen.contains(MISCELLANEOUS_CATEGORY_NAME)) {
1155            categoryList.add(misc);
1156        }
1157        
1158        PropertySheetCategory[] categoryArray = (PropertySheetCategory[]) categoryList
1159            .toArray(new PropertySheetCategory[categoryList.size()]);
1160        sorter.sort(categoryArray);
1161        categories = categoryArray;
1162    }
1163
1164    /**
1165     * Update the category (but not its parent or children).
1166     *
1167     * @param category
1168     * the category to update
1169     * @param item
1170     * the tree item for the given entry
1171     */

1172    private void updateCategory(PropertySheetCategory category,
1173            TreeItem item) {
1174        // ensure that backpointer is correct
1175
item.setData(category);
1176        
1177        // Update the map accordingly
1178
entryToItemMap.put(category, item);
1179
1180        // Update the name and value columns
1181
item.setText(0, category.getCategoryName());
1182        item.setText(1, ""); //$NON-NLS-1$
1183

1184        // update the "+" icon
1185
if (category.getAutoExpand()) {
1186            // we auto expand categories when they first appear
1187
createChildren(item);
1188            item.setExpanded(true);
1189            category.setAutoExpand(false);
1190        } else {
1191            // we do not want to auto expand categories if the user has
1192
// collpased them
1193
updatePlus(category, item);
1194        }
1195    }
1196
1197    /**
1198     * Update the child entries or categories of the given entry or category. If
1199     * the given node is the root entry and we are showing categories then the
1200     * child entries are categories, otherwise they are entries.
1201     *
1202     * @param node
1203     * the entry or category whose children we will update
1204     * @param widget
1205     * the widget for the given entry, either a
1206     * <code>TableTree</code> if the node is the root node or a
1207     * <code>TreeItem</code> otherwise.
1208     */

1209    private void updateChildrenOf(Object JavaDoc node, Widget widget) {
1210        // cast the entry or category
1211
IPropertySheetEntry entry = null;
1212        PropertySheetCategory category = null;
1213        if (node instanceof IPropertySheetEntry) {
1214            entry = (IPropertySheetEntry) node;
1215        } else {
1216            category = (PropertySheetCategory) node;
1217        }
1218
1219        // get the current child tree items
1220
TreeItem[] childItems = getChildItems(widget);
1221
1222        // optimization! prune collapsed subtrees
1223
TreeItem item = null;
1224        if (widget instanceof TreeItem) {
1225            item = (TreeItem) widget;
1226        }
1227        if (item != null && !item.getExpanded()) {
1228            // remove all children
1229
for (int i = 0; i < childItems.length; i++) {
1230                if (childItems[i].getData() != null) {
1231                    removeItem(childItems[i]);
1232                }
1233            }
1234
1235            // append a dummy if necessary
1236
if (category != null || entry.hasChildEntries()) {
1237                // may already have a dummy
1238
// It is either a category (which always has at least one child)
1239
// or an entry with chidren.
1240
// Note that this test is not perfect, if we have filtering on
1241
// then there in fact may be no entires to show when the user
1242
// presses the "+" expand icon. But this is an acceptable
1243
// compromise.
1244
childItems = getChildItems(widget);
1245                if (childItems.length == 0) {
1246                    new TreeItem(item, SWT.NULL);
1247                }
1248            }
1249            return;
1250        }
1251
1252        // get the child entries or categories
1253
if (node == rootEntry && isShowingCategories) {
1254            // update the categories
1255
updateCategories();
1256        }
1257        List JavaDoc children = getChildren(node);
1258
1259        // remove items
1260
Set JavaDoc set = new HashSet JavaDoc(childItems.length * 2 + 1);
1261
1262        for (int i = 0; i < childItems.length; i++) {
1263            Object JavaDoc data = childItems[i].getData();
1264            if (data != null) {
1265                Object JavaDoc e = data;
1266                int ix = children.indexOf(e);
1267                if (ix < 0) { // not found
1268
removeItem(childItems[i]);
1269                } else { // found
1270
set.add(e);
1271                }
1272            } else if (data == null) { // the dummy
1273
childItems[i].dispose();
1274            }
1275        }
1276
1277        // WORKAROUND
1278
int oldCnt = -1;
1279        if (widget == tree) {
1280            oldCnt = tree.getItemCount();
1281        }
1282
1283        // add new items
1284
int newSize = children.size();
1285        for (int i = 0; i < newSize; i++) {
1286            Object JavaDoc el = children.get(i);
1287            if (!set.contains(el)) {
1288                createItem(el, widget, i);
1289            }
1290        }
1291
1292        // WORKAROUND
1293
if (widget == tree && oldCnt == 0 && tree.getItemCount() == 1) {
1294            tree.setRedraw(false);
1295            tree.setRedraw(true);
1296        }
1297
1298        // get the child tree items after our changes
1299
childItems = getChildItems(widget);
1300
1301        // update the child items
1302
// This ensures that the children are in the correct order
1303
// are showing the correct values.
1304
for (int i = 0; i < newSize; i++) {
1305            Object JavaDoc el = children.get(i);
1306            if (el instanceof IPropertySheetEntry) {
1307                updateEntry((IPropertySheetEntry) el, childItems[i]);
1308            } else {
1309                updateCategory((PropertySheetCategory) el, childItems[i]);
1310                updateChildrenOf(el, childItems[i]);
1311            }
1312        }
1313        // The tree's original selection may no longer apply after the update,
1314
// so fire the selection changed event.
1315
entrySelectionChanged();
1316    }
1317
1318    /**
1319     * Update the given entry (but not its children or parent)
1320     *
1321     * @param entry
1322     * the entry we will update
1323     * @param item
1324     * the tree item for the given entry
1325     */

1326    private void updateEntry(IPropertySheetEntry entry, TreeItem item) {
1327        // ensure that backpointer is correct
1328
item.setData(entry);
1329        
1330        // update the map accordingly
1331
entryToItemMap.put(entry, item);
1332
1333        // update the name and value columns
1334
item.setText(0, entry.getDisplayName());
1335        item.setText(1, entry.getValueAsString());
1336        Image image = entry.getImage();
1337        if (item.getImage(1) != image) {
1338            item.setImage(1, image);
1339        }
1340
1341        // update the "+" icon
1342
updatePlus(entry, item);
1343    }
1344
1345    /**
1346     * Updates the "+"/"-" icon of the tree item from the given entry
1347     * or category.
1348     *
1349     * @param node the entry or category
1350     * @param item the tree item being updated
1351     */

1352    private void updatePlus(Object JavaDoc node, TreeItem item) {
1353        // cast the entry or category
1354
IPropertySheetEntry entry = null;
1355        PropertySheetCategory category = null;
1356        if (node instanceof IPropertySheetEntry) {
1357            entry = (IPropertySheetEntry) node;
1358        } else {
1359            category = (PropertySheetCategory) node;
1360        }
1361
1362        boolean hasPlus = item.getItemCount() > 0;
1363        boolean needsPlus = category != null || entry.hasChildEntries();
1364        boolean removeAll = false;
1365        boolean addDummy = false;
1366
1367        if (hasPlus != needsPlus) {
1368            if (needsPlus) {
1369                addDummy = true;
1370            } else {
1371                removeAll = true;
1372            }
1373        }
1374        if (removeAll) {
1375            // remove all children
1376
TreeItem[] items = item.getItems();
1377            for (int i = 0; i < items.length; i++) {
1378                removeItem(items[i]);
1379            }
1380        }
1381
1382        if (addDummy) {
1383            new TreeItem(item, SWT.NULL); // append a dummy to create the
1384
// plus sign
1385
}
1386    }
1387}
1388
Popular Tags