blob: 965bdaa1de9ccf38d039749ccacbc8ff0679ca61 [file] [log] [blame]
/*******************************************************************************
* 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
*******************************************************************************/
package org.eclipse.ui.views.properties;
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.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
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.graphics.Point;
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;
/**
* 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 = {
PropertiesMessages.getString("PropertyViewer.property"), PropertiesMessages.getString("PropertyViewer.value")}; //$NON-NLS-2$ //$NON-NLS-1$
private static String MISCELLANEOUS_CATEGORY_NAME = PropertiesMessages
.getString("PropertyViewer.misc"); //$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.
* @param widget TableTreeItem or TableTree to create the children in.
*/
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;
// 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) {
//Do nothing
}
public void applyEditorValue() {
//Do nothing
}
};
}
/**
* 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 index) {
// create the item
TableTreeItem item;
if (parent instanceof TableTreeItem)
item = new TableTreeItem((TableTreeItem) parent, SWT.NONE, index);
else
item = new TableTreeItem((TableTree) parent, SWT.NONE, index);
// 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
* @return the TableTreeItem for the entry or <code>null</code> if
* there isn't one.
*/
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
* @return the TableTreeItem for the entry or <code>null</code> if
* there isn't one.
*/
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 activatedCellEditor
* the activated cell editor
*/
private void fireCellEditorActivated(CellEditor activatedCellEditor) {
Object[] listeners = activationListeners.getListeners();
for (int i = 0; i < listeners.length; ++i) {
((ICellEditorActivationListener) listeners[i])
.cellEditorActivated(activatedCellEditor);
}
}
/**
* Notifies all registered cell editor activation listeners of a cell editor
* deactivation.
*
* @param activatedCellEditor
* the deactivated cell editor
*/
private void fireCellEditorDeactivated(CellEditor activatedCellEditor) {
Object[] listeners = activationListeners.getListeners();
for (int i = 0; i < listeners.length; ++i) {
((ICellEditorActivationListener) listeners[i])
.cellEditorDeactivated(activatedCellEditor);
}
}
/**
* 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
*
* @param 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
* @param entry The entry to search
*
* @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
*
* @param category The category to search
*
* @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.
*
* @param entries the entries to filter
* @return the entries which match the current filter
* (element type <code>IPropertySheetEntry</code>)
*/
private List getFilteredEntries(IPropertySheetEntry[] entries) {
// if no filter just return all entries
if (isShowingExpertProperties)
return Arrays.asList(entries);
// check each entry for the filter
List filteredEntries = new ArrayList(entries.length);
for (int i = 0; i < entries.length; i++) {
String[] filters = entries[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(entries[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 selection
* the TableTreeItem that is selected
*/
private void handleSelect(TableTreeItem selection) {
// deactivate the current cell editor
if (cellEditor != null) {
applyEditorValue();
deactivateCellEditor();
}
// get the new selection
TableTreeItem[] sel = new TableTreeItem[]{selection};
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
// Part1: Double click only (allow traversal via keyboard without
// activation
tableTree.addSelectionListener(new SelectionAdapter() {
public void widgetDefaultSelected(SelectionEvent e) {
handleSelect((TableTreeItem) e.item);
}
});
// Part2: handle single click activation of cell editor
tableTree.getTable().addMouseListener(new MouseAdapter() {
public void mouseDown(MouseEvent event) {
// only activate if there is a cell editor
Point pt = new Point(event.x, event.y);
TableTreeItem item = tableTree.getItem(pt);
if (item != null) {
handleSelect(item);
}
}
});
// 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 newInput
* 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);
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.viewers.Viewer#setSelection(org.eclipse.jface.viewers.ISelection, boolean)
*/
public void setSelection(ISelection selection, boolean reveal) {
//Do nothing by default
}
/**
* 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 category
* 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()){
//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.
if(childItems.length != 1 || childItems[0].getData() != null)
//if already a dummy - do nothing
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(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 entry
* 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 (item.getImage(1) != image)
item.setImage(1, image);
// update the "+" icon
updatePlus(entry, item);
}
/**
* Updates the "+"/"-" icon of the tree item from the given entry
* or category.
*
* @param 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
}
}
}