com.iw.plugins.spindle.ui.propertysheet.PropertySheetViewer.java Source code

Java tutorial

Introduction

Here is the source code for com.iw.plugins.spindle.ui.propertysheet.PropertySheetViewer.java

Source

 package com.iw.plugins.spindle.ui.propertysheet;

 /*******************************************************************************
  * Copyright (c) 2000, 2003 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials 
  * are made available under the terms of the Common Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/cpl-v10.html
  * 
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *     glongman@intelligentworks.com - spindle customization
  *******************************************************************************/
 import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;

 import org.eclipse.jface.action.IStatusLineManager;
 import org.eclipse.jface.util.ListenerList;
 import org.eclipse.jface.viewers.CellEditor;
 import org.eclipse.jface.viewers.ColumnLayoutData;
 import org.eclipse.jface.viewers.ColumnWeightData;
 import org.eclipse.jface.viewers.ICellEditorListener;
 import org.eclipse.jface.viewers.ISelection;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.jface.viewers.SelectionChangedEvent;
 import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.jface.viewers.TableLayout;
 import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.TableTree;
 import org.eclipse.swt.custom.TableTreeEditor;
 import org.eclipse.swt.custom.TableTreeItem;
 import org.eclipse.swt.events.KeyAdapter;
 import org.eclipse.swt.events.KeyEvent;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.events.TreeEvent;
 import org.eclipse.swt.events.TreeListener;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Table;
 import org.eclipse.swt.widgets.TableColumn;
 import org.eclipse.swt.widgets.Widget;
 import org.eclipse.ui.views.properties.IPropertySheetEntry;
 import org.eclipse.ui.views.properties.IPropertySheetEntryListener;

 /**
  * The PropertySheetViewer displays the properties of objects.
  * The model for the viewer consists of a hierarchy of 
  * <code>IPropertySheetEntry</code>. 
  * <p>
  * This viewer also supports the optional catogorization of the first level
  * <code>IPropertySheetEntry</code>s by using instances of
  * <code>PropertySheetCategory</code>. 
  * 
  */
 /*package*/
 class PropertySheetViewer extends Viewer {
     // The input objects for the viewer
     private Object[] input;

     // The root entry of the viewer 
     private IPropertySheetEntry rootEntry;

     // The current categories
     private PropertySheetCategory[] categories;

     // SWT widgets
     private TableTree tableTree;
     private TableTreeEditor tableTreeEditor;
     private static String[] columnLabels = { "Property", "Value" };//PropertiesMessages.getString("PropertyViewer.property"), PropertiesMessages.getString("PropertyViewer.value")}; //$NON-NLS-2$ //$NON-NLS-1$
     private static String MISCELLANEOUS_CATEGORY_NAME = "Misc";//PropertiesMessages.getString("PropertyViewer.miscellaneousCategoryName"); //$NON-NLS-1$

     // Cell editor support.
     private int columnToEdit = 1;
     private CellEditor cellEditor;
     private IPropertySheetEntryListener entryListener;
     private ICellEditorListener editorListener;

     // Flag to indicate if categories (if any) should be shown
     private boolean isShowingCategories = true;
     // Flag to indicate expert properties should be shown
     private boolean isShowingExpertProperties = false;

     // The status line manager for showing messages
     private IStatusLineManager statusLineManager;

     // Cell editor activation listeners
     private ListenerList activationListeners = new ListenerList(3);

     /**
      * Creates a property sheet viewer on a newly-created table tree control
      * under the given parent. The viewer has no input, and no root entry.
      *
      * @param parent the parent control
      */
     public PropertySheetViewer(Composite parent) {
         tableTree = new TableTree(parent, SWT.FULL_SELECTION | SWT.SINGLE | SWT.HIDE_SELECTION);

         // configure the widget
         Table table = tableTree.getTable();
         table.setLinesVisible(true);
         table.setHeaderVisible(true);

         // configure the columns
         addColumns();

         // add our listeners to the widget
         hookControl();

         // create a new table tree editor   
         tableTreeEditor = new TableTreeEditor(tableTree);

         // create the entry and editor listener
         createEntryListener();
         createEditorListener();
     }

     /**
      * Activate a cell editor for the given selected table tree item.
      *
      * @param item the selected table tree item
      */
     private void activateCellEditor(TableTreeItem item) {
         // ensure the cell editor is visible
         tableTree.showSelection();

         // Get the entry for this item
         IPropertySheetEntry activeEntry = (IPropertySheetEntry) item.getData();

         // Get the cell editor for the entry.
         // Note that the editor parent must be the Table control 
         // that is underneath the TableTree
         cellEditor = activeEntry.getEditor(tableTree.getTable());

         if (cellEditor == null)
             // unable to create the editor
             return;

         // activate the cell editor
         cellEditor.activate();

         // if the cell editor has no control we can stop now
         Control control = cellEditor.getControl();
         if (control == null) {
             cellEditor.deactivate();
             cellEditor = null;
             return;
         }

         // add our editor listener
         cellEditor.addListener(editorListener);

         // set the layout of the table tree editor to match the cell editor
         CellEditor.LayoutData layout = cellEditor.getLayoutData();
         tableTreeEditor.horizontalAlignment = layout.horizontalAlignment;
         tableTreeEditor.grabHorizontal = layout.grabHorizontal;
         tableTreeEditor.minimumWidth = layout.minimumWidth;
         tableTreeEditor.setEditor(control, item, columnToEdit);

         // set the error text from the cel editor
         setErrorMessage(cellEditor.getErrorMessage());

         // give focus to the cell editor
         cellEditor.setFocus();

         // notify of activation   
         fireCellEditorActivated(cellEditor);
     }

     /**
      * Adds a cell editor activation listener.
      * Has no effect if an identical activation listener 
      * is already registered.
      *
      * @param listener a cell editor activation listener
      */
     /*package*/
     void addActivationListener(ICellEditorActivationListener listener) {
         activationListeners.add(listener);
     }

     /**
      * Add columns to the table tree and 
      * set up the layout manager accordingly.
      */
     private void addColumns() {
         Table table = tableTree.getTable();

         // create the columns
         TableColumn[] columns = table.getColumns();
         for (int i = 0; i < columnLabels.length; i++) {
             String string = columnLabels[i];
             if (string != null) {
                 TableColumn column;
                 if (i < columns.length)
                     column = columns[i];
                 else
                     column = new TableColumn(table, 0);
                 column.setText(string);
             }
         }

         // property column
         ColumnLayoutData c1Layout = new ColumnWeightData(40, false);

         // value column
         ColumnLayoutData c2Layout = new ColumnWeightData(60, true);

         // set columns in Table layout
         TableLayout layout = new TableLayout();
         layout.addColumnData(c1Layout);
         layout.addColumnData(c2Layout);
         table.setLayout(layout);
     }

     /**
      * Asks the entry currently being edited to apply its
      * current cell editor value.
      */
     private void applyEditorValue() {
         TableTreeItem treeItem = tableTreeEditor.getItem();
         // treeItem can be null when view is opened
         if (treeItem == null || treeItem.isDisposed())
             return;
         IPropertySheetEntry entry = (IPropertySheetEntry) treeItem.getData();
         entry.applyEditorValue();
     }

     /**
      * Creates the child items for the given widget (item or table tree).
      * This method is called when the item is expanded for the first time or
      * when an item is assigned as the root of the table tree.
      */
     private void createChildren(Widget widget) {
         // get the current child items
         TableTreeItem[] childItems;
         if (widget == tableTree)
             childItems = tableTree.getItems();
         else {
             childItems = ((TableTreeItem) widget).getItems();
         }

         if (childItems.length > 0) {
             Object data = childItems[0].getData();
             if (data != null)
                 // children already there!
                 return;
             else
                 // remove the dummy
                 childItems[0].dispose();
         }

         // get the children and create their table tree items
         Object node = widget.getData();
         List children = getChildren(node);
         if (children.isEmpty())
             // this item does't actually have any children
             return;
         for (int i = 0; i < children.size(); i++) {
             // create a new table tree item
             createItem(children.get(i), widget, i);
         }
     }

     /** 
      * Creates a new cell editor listener.
      */
     private void createEditorListener() {
         editorListener = new ICellEditorListener() {
             public void cancelEditor() {
                 deactivateCellEditor();
             }

             public void editorValueChanged(boolean oldValidState, boolean newValidState) {
             }

             public void applyEditorValue() {
             }
         };
     }

     /** 
      * Creates a new property sheet entry listener.
      */
     private void createEntryListener() {
         entryListener = new IPropertySheetEntryListener() {
             public void childEntriesChanged(IPropertySheetEntry entry) {
                 // update the children of the given entry
                 if (entry == rootEntry)
                     updateChildrenOf(entry, tableTree);
                 else {
                     TableTreeItem item = findItem(entry);
                     if (item != null)
                         updateChildrenOf(entry, item);
                 }
             }

             public void valueChanged(IPropertySheetEntry entry) {
                 // update the given entry
                 TableTreeItem item = findItem(entry);
                 if (item != null)
                     updateEntry(entry, item);
             }

             public void errorMessageChanged(IPropertySheetEntry entry) {
                 // update the error message
                 setErrorMessage(entry.getErrorText());
             }
         };
     }

     /**
      * Creates a new table tree item, sets the given entry or
      * category (node)in its user data field, and adds a listener to
      * the node if it is an entry.
      *
      * @param node the entry or category associated with this item
      * @param parent the parent widget
      * @param index indicates the position to insert the item 
      *    into its parent
      */
     private void createItem(Object node, Widget parent, int i) {
         // create the item
         TableTreeItem item;
         if (parent instanceof TableTreeItem)
             item = new TableTreeItem((TableTreeItem) parent, SWT.NONE, i);
         else
             item = new TableTreeItem((TableTree) parent, SWT.NONE, i);

         // set the user data field   
         item.setData(node);

         // add our listener
         if (node instanceof IPropertySheetEntry)
             ((IPropertySheetEntry) node).addPropertySheetEntryListener(entryListener);

         // update the visual presentation   
         if (node instanceof IPropertySheetEntry)
             updateEntry((IPropertySheetEntry) node, item);
         else
             updateCategory((PropertySheetCategory) node, item);
     }

     /**
      * Deactivate the currently active cell editor.
      */
     /*package*/
     void deactivateCellEditor() {
         tableTreeEditor.setEditor(null, null, columnToEdit);
         if (cellEditor != null) {
             cellEditor.deactivate();
             fireCellEditorDeactivated(cellEditor);
             cellEditor.removeListener(editorListener);
             cellEditor = null;
         }
         // clear any error message from the editor
         setErrorMessage(null);
     }

     /**
      * Sends out a selection changed event for the entry table to all 
      * registered listeners.
      */
     private void entrySelectionChanged() {
         SelectionChangedEvent changeEvent = new SelectionChangedEvent(this, getSelection());
         fireSelectionChanged(changeEvent);
     }

     /**
      * Return a table tree item in the property sheet that has 
      * the same entry in its user data field as the supplied
      * entry. Return <code>null</code> if there is no such item.
      *
      * @param entry the entry to serach for
      */
     private TableTreeItem findItem(IPropertySheetEntry entry) {
         // Iterate through tableTreeItems to find item
         TableTreeItem[] items = tableTree.getItems();
         for (int i = 0; i < items.length; i++) {
             TableTreeItem item = items[i];
             TableTreeItem findItem = findItem(entry, item);
             if (findItem != null)
                 return findItem;
         }
         return null;
     }

     /**
      * Return a table tree item in the property sheet that has 
      * the same entry in its user data field as the supplied
      * entry. Return <code>null</code> if there is no such item.
      *
      * @param entry the entry to search for
      * @param item the item look in
      */
     private TableTreeItem findItem(IPropertySheetEntry entry, TableTreeItem item) {
         // compare with current item
         if (entry == item.getData())
             return item;

         // recurse over children
         TableTreeItem[] items = item.getItems();
         for (int i = 0; i < items.length; i++) {
             TableTreeItem childItem = items[i];
             TableTreeItem findItem = findItem(entry, childItem);
             if (findItem != null)
                 return findItem;
         }
         return null;
     }

     /**
      * Notifies all registered cell editor activation listeners
      * of a cell editor activation.
      *
      * @param cellEditor the activated cell editor
      */
     private void fireCellEditorActivated(CellEditor cellEditor) {
         Object[] listeners = activationListeners.getListeners();
         for (int i = 0; i < listeners.length; ++i) {
             ((ICellEditorActivationListener) listeners[i]).cellEditorActivated(cellEditor);
         }
     }

     /**
      * Notifies all registered cell editor activation listeners
      * of a cell editor deactivation.
      *
      * @param cellEditor the deactivated cell editor
      */
     private void fireCellEditorDeactivated(CellEditor cellEditor) {
         Object[] listeners = activationListeners.getListeners();
         for (int i = 0; i < listeners.length; ++i) {
             ((ICellEditorActivationListener) listeners[i]).cellEditorDeactivated(cellEditor);
         }
     }

     /**
      * Returns the active cell editor of this property sheet viewer
      * or <code>null</code> if no cell editor is active.
      *
      * @return the active cell editor
      */
     public CellEditor getActiveCellEditor() {
         return cellEditor;
     }

     /**
      * Returns the children of the given category or entry
      *
      * @node a category or entry
      * @return the children of the given category or entry
      *  (element type <code>IPropertySheetEntry</code> or 
      *  <code>PropertySheetCategory</code>)
      */
     private List getChildren(Object node) {
         // cast the entry or category   
         IPropertySheetEntry entry = null;
         PropertySheetCategory category = null;
         if (node instanceof IPropertySheetEntry)
             entry = (IPropertySheetEntry) node;
         else
             category = (PropertySheetCategory) node;

         // get the child entries or categories
         List children;
         if (category == null)
             children = getChildren(entry);
         else
             children = getChildren(category);

         return children;
     }

     /**
      * Returns the child entries of the given entry
      *
      * @return the children of the given entry
      *  (element type <code>IPropertySheetEntry</code>) 
      */
     private List getChildren(IPropertySheetEntry entry) {
         // if the entry is the root and we are showing categories, and we have more than the
         // defualt category, return the categories
         if (entry == rootEntry && isShowingCategories) {
             if (categories.length > 1 || (categories.length == 1
                     && !categories[0].getCategoryName().equals(MISCELLANEOUS_CATEGORY_NAME)))
                 return Arrays.asList(categories);
         }

         // return the filtered child entries
         return getFilteredEntries(entry.getChildEntries());
     }

     /**
      * Returns the child entries of the given category
      *
      * @return the children of the given category
      *  (element type <code>IPropertySheetEntry</code>)
      */
     private List getChildren(PropertySheetCategory category) {
         return getFilteredEntries(category.getChildEntries());
     }

     /* (non-Javadoc)
      * Method declared on Viewer.
      */
     public Control getControl() {
         return tableTree;
     }

     /**
      * Returns the entries which match the current filter.
      *
      * @entries the entries to filter
      * @return the entries which match the current filter
      *  (element type <code>IPropertySheetEntry</code>)
      */
     private List getFilteredEntries(IPropertySheetEntry[] entires) {
         // if no filter just return all entries
         if (!isShowingExpertProperties)
             return Arrays.asList(entires);

         // check each entry for the filter
         List filteredEntries = new ArrayList(entires.length);
         for (int i = 0; i < entires.length; i++) {
             String[] filters = ((IPropertySheetEntry) entires[i]).getFilters();
             boolean expert = false;
             if (filters != null) {
                 for (int j = 0; j < filters.length; j++) {
                     if (filters[j].equals(IPropertySheetEntry.FILTER_ID_EXPERT)) {
                         expert = true;
                         break;
                     }
                 }
             }
             if (!expert)
                 filteredEntries.add(entires[i]);
         }
         return filteredEntries;
     }

     /**
      * The <code>PropertySheetViewer</code> implementation of this
      * method declared on <code>IInputProvider</code> returns the 
      * objects for which the viewer is currently showing properties. 
      * It returns an <code>Object[]</code> or <code>null</code>.
      */
     public Object getInput() {
         return input;
     }

     /**
      * Returns the root entry for this property sheet viewer.
      * The root entry is not visible in the viewer.
      * 
      * @return the root entry or <code>null</code>.
      */
     public IPropertySheetEntry getRootEntry() {
         return rootEntry;
     }

     /**
      * The <code>PropertySheetViewer</code> implementation of this 
      * <code>ISelectionProvider</code> method
      * returns the result as a <code>StructuredSelection</code>.
      * <p>
      * Note that this method only includes <code>IPropertySheetEntry</code>
      * in the selection (no categories).
      * </p>
      */
     public ISelection getSelection() {
         if (tableTree.getSelectionCount() == 0)
             return StructuredSelection.EMPTY;
         TableTreeItem[] sel = tableTree.getSelection();
         List entries = new ArrayList(sel.length);
         for (int i = 0; i < sel.length; i++) {
             TableTreeItem ti = sel[i];
             Object data = ti.getData();
             if (data instanceof IPropertySheetEntry)
                 entries.add(data);
         }
         return new StructuredSelection(entries);
     }

     /**
      * Selection in the viewer occurred. 
      * Check if there is an active cell editor. 
      * If yes, deactivate it and check if a new cell editor must be activated.
      *
      * @param event the selection event
      */
     private void handleSelect(SelectionEvent event) {
         // deactivate the current cell editor
         if (cellEditor != null) {
             applyEditorValue();
             deactivateCellEditor();
         }

         // get the new selection
         TableTreeItem[] sel = tableTree.getSelection();
         if (sel.length == 0) {
             setMessage(null);
             setErrorMessage(null);
         } else {
             Object object = sel[0].getData(); // assume single selection
             if (object instanceof IPropertySheetEntry) {
                 // get the entry for this item
                 IPropertySheetEntry activeEntry = (IPropertySheetEntry) object;

                 // display the description for the item
                 setMessage(activeEntry.getDescription());

                 // activate a cell editor on the selection
                 activateCellEditor(sel[0]);
             }
         }
         entrySelectionChanged();
     }

     /**
      * The expand icon for a node in this viewer has been
      * selected to collapse a subtree. Deactivate the cell editor
      *
      * @param event the SWT tree event
      */
     private void handleTreeCollapse(TreeEvent event) {
         if (cellEditor != null) {
             applyEditorValue();
             deactivateCellEditor();
         }
     }

     /**
      * The expand icon for a node in this viewer has been
      * selected to expand the subtree. Create the children 
      * 1 level deep.
      * <p>
      * Note that we use a "dummy" item (no user data) to show a "+"
      * icon beside an item which has children before the item is expanded
      * now that it is being expanded we have to create the real child
      * items
      * </p>
      *
      * @param event the SWT tree event
      */
     private void handleTreeExpand(TreeEvent event) {
         createChildren(event.item);
     }

     /**
      * Hides the categories.
      */
     /*package*/
     void hideCategories() {
         isShowingCategories = false;
         categories = null;
         refresh();
     }

     /**
      * Hides the expert properties.
      */
     /*package*/
     void hideExpert() {
         isShowingExpertProperties = false;
         refresh();
     }

     /**
      * Establish this viewer as a listener on the control
      */
     private void hookControl() {
         // Handle selections in the TableTree
         tableTree.addSelectionListener(new SelectionAdapter() {
             public void widgetSelected(SelectionEvent e) {
                 handleSelect(e);
             }
         });

         // Add a tree listener to expand and collapse which
         // allows for lazy creation of children
         tableTree.addTreeListener(new TreeListener() {
             public void treeExpanded(final TreeEvent event) {
                 handleTreeExpand(event);
             }

             public void treeCollapsed(final TreeEvent event) {
                 handleTreeCollapse(event);
             }
         });

         // Refresh the table when F5 pressed
         tableTree.getTable().addKeyListener(new KeyAdapter() {
             public void keyReleased(KeyEvent e) {
                 if (e.character == SWT.ESC)
                     deactivateCellEditor();
                 else if (e.keyCode == SWT.F5)
                     // The following will simulate a reselect
                     setInput(getInput());
             }
         });
     }

     /**
      * Updates all of the items in the tree.
      * <p>
      * Note that this means ensuring that the tree items reflect the state
      * of the model (entry tree) it does not mean telling the model to update 
      * itself.
      * </p>
      */
     public void refresh() {
         if (rootEntry != null) {
             updateChildrenOf(rootEntry, tableTree);
         }
     }

     /**
      * Removes the given cell editor activation listener 
      * from this viewer.
      * Has no effect if an identical activation listener 
      * is not registered.
      *
      * @param listener a cell editor activation listener
      */
     /*package*/
     void removeActivationListener(ICellEditorActivationListener listener) {
         activationListeners.remove(listener);
     }

     /**
      * Remove the given item from the table tree.
      * Remove our listener if the item's user data is a
      * an entry then set the user data to null
      *
      * @param item the item to remove
      */
     private void removeItem(TableTreeItem item) {
         Object data = item.getData();
         if (data instanceof IPropertySheetEntry)
             ((IPropertySheetEntry) data).removePropertySheetEntryListener(entryListener);
         item.setData(null);
         item.dispose();
     }

/**
 * Reset the selected properties to their default values.
 */
public void resetProperties() {
   // Determine the selection
   IStructuredSelection selection = (IStructuredSelection) getSelection();

   // Iterate over entries and reset them
   Iterator enum = selection.iterator();
   while (enum.hasNext())
       ((IPropertySheetEntry) enum.next()).resetPropertyValue();
}

     /**
      * Sets the error message to be displayed in the status line.
      *
      * @param errorMessage the message to be displayed, or <code>null</code>
      */
     private void setErrorMessage(String errorMessage) {
         // show the error message
         if (statusLineManager != null)
             statusLineManager.setErrorMessage(errorMessage);
     }

     /**
      * The <code>PropertySheetViewer</code> implementation of this
      * method declared on <code>Viewer</code> method sets the objects 
      * for which the viewer is currently showing properties. 
      * <p>
      * The input must be an <code>Object[]</code> or <code>null</code>.
      * </p>
      * @param input the input of this viewer, or <code>null</code> if none
      */
     public void setInput(Object newInput) {
         // need to save any changed value when user clicks elsewhere
         applyEditorValue();
         // deactivate our cell editor
         deactivateCellEditor();

         // set the new input to the root entry
         input = (Object[]) newInput;
         if (input == null)
             input = new Object[0];

         if (rootEntry != null) {
             rootEntry.setValues(input);
             // ensure first level children are visible
             updateChildrenOf(rootEntry, tableTree);
         }
     }

     /**
      * Sets the message to be displayed in the status line. This message
      * is displayed when there is no error message.
      *
      * @param message the message to be displayed, or <code>null</code>
      */
     private void setMessage(String message) {
         // show the message
         if (statusLineManager != null)
             statusLineManager.setMessage(message);
     }

     /**
      * Sets the root entry for this property sheet viewer.
      * The root entry is not visible in the viewer.
      * 
      * @param root the root entry
      */
     public void setRootEntry(IPropertySheetEntry root) {
         // If we have a root entry, remove our entry listener
         if (rootEntry != null)
             rootEntry.removePropertySheetEntryListener(entryListener);

         rootEntry = root;

         // Set the root as user data on the tableTree
         tableTree.setData(rootEntry);

         // Add an IPropertySheetEntryListener to listen for entry change notifications
         rootEntry.addPropertySheetEntryListener(entryListener);

         // Pass our input to the root, this will trigger entry change
         // callbacks to update this viewer
         setInput(input);
     }

     /**
      * The <code>PropertySheetViewer</code> implementation of this 
      * <code>Viewer</code> method does nothing.
      */
     public void setSelection(ISelection selection, boolean reveal) {
     }

     /**
      * Sets the status line manager this view will use to show messages.
      *
      * @param manager the status line manager
      */
     public void setStatusLineManager(IStatusLineManager manager) {
         statusLineManager = manager;
     }

     /**
      * Shows the categories.
      */
     /*package*/
     void showCategories() {
         isShowingCategories = true;
         refresh();
     }

     /**
      * Shows the expert properties.
      */
     /*package*/
     void showExpert() {
         isShowingExpertProperties = true;
         refresh();
     }

     /**
      * Updates the categories.
      * Reuses old categories if possible.
      */
     private void updateCategories() {
         // lazy initialize
         if (categories == null)
             categories = new PropertySheetCategory[0];

         // get all the filtered child entries of the root
         List childEntries = getFilteredEntries(rootEntry.getChildEntries());

         // if the list is empty, just set an empty categories array
         if (childEntries.size() == 0) {
             categories = new PropertySheetCategory[0];
             return;
         }

         // cache old categories by their descriptor name
         Map categoryCache = new HashMap(categories.length * 2 + 1);
         for (int i = 0; i < categories.length; i++) {
             categories[i].removeAllEntries();
             categoryCache.put(categories[i].getCategoryName(), categories[i]);
         }

         // create a list of categories to get rid of
         List categoriesToRemove = new ArrayList(Arrays.asList(categories));

         // Determine the categories
         PropertySheetCategory misc = (PropertySheetCategory) categoryCache.get(MISCELLANEOUS_CATEGORY_NAME);
         if (misc == null)
             misc = new PropertySheetCategory(MISCELLANEOUS_CATEGORY_NAME);
         boolean addMisc = false;

         for (int i = 0; i < childEntries.size(); i++) {
             IPropertySheetEntry childEntry = (IPropertySheetEntry) childEntries.get(i);
             String categoryName = childEntry.getCategory();
             if (categoryName == null) {
                 misc.addEntry(childEntry);
                 addMisc = true;
                 categoriesToRemove.remove(misc);
             } else {
                 PropertySheetCategory category = (PropertySheetCategory) categoryCache.get(categoryName);
                 if (category == null) {
                     category = new PropertySheetCategory(categoryName);
                     categoryCache.put(categoryName, category);
                 } else {
                     categoriesToRemove.remove(category);
                 }
                 category.addEntry(childEntry);
             }
         }

         // Add the PSE_MISC category if it has entries
         if (addMisc)
             categoryCache.put(MISCELLANEOUS_CATEGORY_NAME, misc);

         // Sort the categories   
         List list = new ArrayList(categoryCache.values());
         for (int i = 0; i < categoriesToRemove.size(); i++)
             list.remove(categoriesToRemove.get(i));
         Collections.sort(list, new Comparator() {
             Collator coll = Collator.getInstance(Locale.getDefault());

             public int compare(Object a, Object b) {
                 PropertySheetCategory c1, c2;
                 String dname1, dname2;
                 c1 = (PropertySheetCategory) a;
                 dname1 = c1.getCategoryName();
                 c2 = (PropertySheetCategory) b;
                 dname2 = c2.getCategoryName();
                 return coll.compare(dname1, dname2);
             }
         });

         categories = (PropertySheetCategory[]) list.toArray(new PropertySheetCategory[list.size()]);
     }

     /**
      * Update the category (but not its parent or children).
      *
      * @param node the category to update
      * @param item the tree item for the given entry
      */
     private void updateCategory(PropertySheetCategory category, TableTreeItem item) {
         // ensure that backpointer is correct
         item.setData(category);

         // Update the name and value columns
         item.setText(0, category.getCategoryName());
         item.setText(1, ""); //$NON-NLS-1$

         // update the "+" icon   
         if (category.getAutoExpand()) {
             // we auto expand categories when they first appear
             createChildren(item);
             item.setExpanded(true);
             category.setAutoExpand(false);
         } else {
             // we do not want to auto expand categories if the user has collpased them
             updatePlus(category, item);
         }
     }

     /**
      * Update the child entries or categories of the given entry or category.
      * If the given node is the root entry and we are showing categories 
      * then the child entries are categories, otherwise they are entries.
      *
      * @param node the entry or category whose children we will update
      * @param widget the widget for the given entry, either a
      *  <code>TableTree</code> if the node is the root node or a
      *  <code>TableTreeItem</code> otherwise.
      */
     private void updateChildrenOf(Object node, Widget widget) {
         // cast the entry or category   
         IPropertySheetEntry entry = null;
         PropertySheetCategory category = null;
         if (node instanceof IPropertySheetEntry)
             entry = (IPropertySheetEntry) node;
         else
             category = (PropertySheetCategory) node;

         // get the current child table tree items
         TableTreeItem item = null;
         TableTreeItem[] childItems;
         if (node == rootEntry) {
             childItems = tableTree.getItems();
         } else {
             item = (TableTreeItem) widget;
             childItems = item.getItems();
         }

         // optimization! prune collapsed subtrees
         if (item != null && !item.getExpanded()) {
             // remove all children
             for (int i = 0; i < childItems.length; i++) {
                 if (childItems[i].getData() != null) {
                     removeItem(childItems[i]);
                 }
             }

             // append a dummy if necessary
             if ((category != null || entry.hasChildEntries()) && childItems.length == 0) // may already have a dummy
                 // its is either a category (which always has at least one child)
                 // or an entry with chidren.
                 // Note that this test is not perfect, if we have filtering on
                 // then there in fact may be no entires to show when the user
                 // presses the "+" expand icon. But this is an acceptable compromise.
                 new TableTreeItem(item, SWT.NULL);

             return;
         }

         // get the child entries or categories
         if (node == rootEntry && isShowingCategories)
             // update the categories
             updateCategories();
         List children = getChildren(node);

         // remove items
         Set set = new HashSet(childItems.length * 2 + 1);

         for (int i = 0; i < childItems.length; i++) {
             Object data = childItems[i].getData();
             if (data != null) {
                 Object e = data;
                 int ix = children.indexOf(e);
                 if (ix < 0) { // not found
                     removeItem(childItems[i]);
                 } else { // found
                     set.add(e);
                 }
             } else if (data == null) { // the dummy
                 item.dispose();
             }
         }

         // WORKAROUND
         int oldCnt = -1;
         if (widget == tableTree)
             oldCnt = tableTree.getItemCount();

         // add new items
         int newSize = children.size();
         for (int i = 0; i < newSize; i++) {
             Object el = children.get(i);
             if (!set.contains(el))
                 createItem(el, widget, i);
         }

         // WORKAROUND
         if (widget == tableTree && oldCnt == 0 && tableTree.getItemCount() == 1) {
             tableTree.setRedraw(false);
             tableTree.setRedraw(true);
         }

         // get the child table tree items after our changes
         if (entry == rootEntry)
             childItems = tableTree.getItems();
         else
             childItems = item.getItems();

         // update the child items
         // This ensures that the children are in the correct order
         // are showing the correct values.
         for (int i = 0; i < newSize; i++) {
             Object el = children.get(i);
             if (el instanceof IPropertySheetEntry)
                 updateEntry((IPropertySheetEntry) el, childItems[i]);
             else {
                 updateCategory((PropertySheetCategory) el, childItems[i]);
                 updateChildrenOf((PropertySheetCategory) el, childItems[i]);
             }
         }
         // The tree's original selection may no longer apply after the update,
         // so fire the selection changed event.
         entrySelectionChanged();
     }

     /**
      * Update the given entry (but not its children or parent)
      *
      * @param node the entry we will update
      * @param item the tree item for the given entry
      */
     private void updateEntry(IPropertySheetEntry entry, TableTreeItem item) {
         // ensure that backpointer is correct
         item.setData(entry);

         // update the name and value columns
         item.setText(0, entry.getDisplayName());
         item.setText(1, entry.getValueAsString());
         Image image = entry.getImage();
         if (image != null)
             item.setImage(1, image);

         // update the "+" icon   
         updatePlus(entry, item);
     }

     /**
      * Updates the "+"/"-" icon of the tree item from the given entry
      * or category.
      *
      * @parem node the entry or category
      * @param item the table tree item being updated
      */
     private void updatePlus(Object node, TableTreeItem item) {
         // cast the entry or category   
         IPropertySheetEntry entry = null;
         PropertySheetCategory category = null;
         if (node instanceof IPropertySheetEntry)
             entry = (IPropertySheetEntry) node;
         else
             category = (PropertySheetCategory) node;

         boolean hasPlus = item.getItemCount() > 0;
         boolean needsPlus = category != null || entry.hasChildEntries();
         boolean removeAll = false;
         boolean addDummy = false;

         if (hasPlus != needsPlus) {
             if (needsPlus) {
                 addDummy = true;
             } else {
                 removeAll = true;
             }
         }
         if (removeAll) {
             // remove all children
             TableTreeItem[] items = item.getItems();
             for (int i = 0; i < items.length; i++) {
                 removeItem(items[i]);
             }
         }

         if (addDummy) {
             new TableTreeItem(item, SWT.NULL); // append a dummy to create the plus sign
         }
     }
 }