summaryrefslogtreecommitdiff
path: root/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTable.java
diff options
context:
space:
mode:
Diffstat (limited to 'propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTable.java')
-rw-r--r--propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTable.java1602
1 files changed, 1602 insertions, 0 deletions
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTable.java b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTable.java
new file mode 100644
index 0000000..7a49cb3
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/model/property/table/PropertyTable.java
@@ -0,0 +1,1602 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.model.property.table;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.swt.SWT;
+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.MouseMoveListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Canvas;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.wb.draw2d.IColorConstants;
+import org.eclipse.wb.draw2d.ICursorConstants;
+import org.eclipse.wb.internal.core.DesignerPlugin;
+import org.eclipse.wb.internal.core.model.property.Property;
+import org.eclipse.wb.internal.core.model.property.category.PropertyCategory;
+import org.eclipse.wb.internal.core.model.property.category.PropertyCategoryProvider;
+import org.eclipse.wb.internal.core.model.property.category.PropertyCategoryProviders;
+import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor;
+import org.eclipse.wb.internal.core.model.property.editor.complex.IComplexPropertyEditor;
+import org.eclipse.wb.internal.core.model.property.editor.presentation.PropertyEditorPresentation;
+import org.eclipse.wb.internal.core.utils.check.Assert;
+import org.eclipse.wb.internal.core.utils.ui.DrawUtils;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Control that can display {@link Property}'s and edit them using {@link PropertyEditor}'s.
+ *
+ * @author scheglov_ke
+ * @author lobas_av
+ * @coverage core.model.property.table
+ */
+public class PropertyTable extends Canvas implements ISelectionProvider {
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Colors
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ private static final Color COLOR_BACKGROUND = IColorConstants.listBackground;
+ private static final Color COLOR_NO_PROPERTIES = IColorConstants.gray;
+ private static final Color COLOR_LINE = IColorConstants.lightGray;
+ private static final Color COLOR_COMPLEX_LINE = DrawUtils.getShiftedColor(
+ IColorConstants.lightGray,
+ -32);
+ private static final Color COLOR_PROPERTY_BG = DrawUtils.getShiftedColor(COLOR_BACKGROUND, -12);
+ private static final Color COLOR_PROPERTY_BG_MODIFIED = COLOR_BACKGROUND;
+ private static final Color COLOR_PROPERTY_FG_TITLE = IColorConstants.listForeground;
+ private static final Color COLOR_PROPERTY_FG_VALUE =
+ DrawUtils.isDarkColor(IColorConstants.listBackground)
+ ? IColorConstants.lightBlue
+ : IColorConstants.darkBlue;
+ private static final Color COLOR_PROPERTY_BG_SELECTED = IColorConstants.listSelection;
+ private static final Color COLOR_PROPERTY_FG_SELECTED = IColorConstants.listSelectionText;
+ private static final Color COLOR_PROPERTY_FG_ADVANCED = IColorConstants.gray;
+ // BEGIN ADT MODIFICATIONS
+ public static final Color COLOR_PROPERTY_FG_DEFAULT = IColorConstants.darkGray;
+ // END ADT MODIFICATIONS
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Sizes
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ private static final int MIN_COLUMN_WIDTH = 75;
+ private static final int MARGIN_LEFT = 2;
+ private static final int MARGIN_RIGHT = 1;
+ private static final int STATE_IMAGE_MARGIN_RIGHT = 4;
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Images
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ private static final Image m_plusImage = DesignerPlugin.getImage("properties/plus.gif");
+ private static final Image m_minusImage = DesignerPlugin.getImage("properties/minus.gif");
+ private static int m_stateWidth = 9;
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Instance fields
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ private final PropertyTableTooltipHelper m_tooltipHelper;
+ private boolean m_showAdvancedProperties;
+ private Property[] m_rawProperties;
+ private List<PropertyInfo> m_properties;
+ private final Set<String> m_expandedIds = Sets.newTreeSet();
+ // BEGIN ADT MODIFICATIONS
+ // Add support for "expand by default" : If you specify ids to be collapsed by
+ // default, then any *other* ids will be expanded by default.
+ private Set<String> m_collapsedIds = null;
+
+ /**
+ * Sets a set of ids that should be collapsed by default. Everything else
+ * will be expanded by default. If this method is not called, ids are
+ * collapsed by default instead.
+ *
+ * @param names set of ids to be collapsed
+ */
+ public void setDefaultCollapsedNames(Collection<String> names) {
+ m_collapsedIds = Sets.newTreeSet();
+ for (String name : names) {
+ m_collapsedIds.add("|" + name); // See PropertyInfo constructor for id syntax
+ }
+ }
+ // END ADT MODIFICATIONS
+
+ private Image m_bufferedImage;
+ private int m_rowHeight;
+ private int m_selection;
+ private int m_page;
+ private int m_splitter = -1;
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Constructor
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ public PropertyTable(Composite parent, int style) {
+ super(parent, style | SWT.V_SCROLL | SWT.NO_BACKGROUND | SWT.NO_REDRAW_RESIZE);
+ hookControlEvents();
+ // calculate sizes
+ {
+ GC gc = new GC(this);
+ try {
+ m_rowHeight = 1 + gc.getFontMetrics().getHeight() + 1;
+ } finally {
+ gc.dispose();
+ }
+ }
+ // install tooltip helper
+ m_tooltipHelper = new PropertyTableTooltipHelper(this);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Events
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Adds listeners for events.
+ */
+ private void hookControlEvents() {
+ addListener(SWT.Dispose, new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ disposeBufferedImage();
+ }
+ });
+ addListener(SWT.Resize, new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ handleResize();
+ }
+ });
+ addListener(SWT.Paint, new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ handlePaint(event.gc, event.x, event.y, event.width, event.height);
+ }
+ });
+ getVerticalBar().addListener(SWT.Selection, new Listener() {
+ @Override
+ public void handleEvent(Event event) {
+ handleVerticalScrolling();
+ }
+ });
+ addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseDown(MouseEvent event) {
+ forceFocus();
+ handleMouseDown(event);
+ }
+
+ @Override
+ public void mouseUp(MouseEvent event) {
+ handleMouseUp(event);
+ }
+
+ @Override
+ public void mouseDoubleClick(MouseEvent event) {
+ handleMouseDoubleClick(event);
+ }
+ });
+ addMouseMoveListener(new MouseMoveListener() {
+ @Override
+ public void mouseMove(MouseEvent event) {
+ handleMouseMove(event);
+ }
+ });
+ // keyboard
+ addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ handleKeyDown(e);
+ }
+ });
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Events: dispose, resize, scroll
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Disposes image used for double buffered painting.
+ */
+ private void disposeBufferedImage() {
+ if (m_bufferedImage != null) {
+ m_bufferedImage.dispose();
+ m_bufferedImage = null;
+ }
+ }
+
+ /**
+ * Handles {@link SWT#Resize} event.
+ */
+ private void handleResize() {
+ disposeBufferedImage();
+ configureScrolling();
+ // splitter
+ {
+ // set default value for splitter
+ if (m_splitter <= MIN_COLUMN_WIDTH) {
+ m_splitter = Math.max((int) (getClientArea().width * 0.4), MIN_COLUMN_WIDTH);
+ }
+ configureSplitter();
+ }
+ }
+
+ /**
+ * Handles {@link SWT#Selection} event for vertical {@link ScrollBar}.
+ */
+ private void handleVerticalScrolling() {
+ ScrollBar verticalBar = getVerticalBar();
+ if (verticalBar.getEnabled()) {
+ // update selection
+ m_selection = verticalBar.getSelection();
+ // redraw (but not include vertical bar to avoid flashing)
+ {
+ Rectangle clientArea = getClientArea();
+ redraw(clientArea.x, clientArea.y, clientArea.width, clientArea.height, false);
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Keyboard
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Handles {@link SWT#KeyDown} event.
+ */
+ private void handleKeyDown(KeyEvent e) {
+ if (m_activePropertyInfo != null) {
+ try {
+ Property property = m_activePropertyInfo.getProperty();
+ // expand/collapse
+ if (m_activePropertyInfo.isComplex()) {
+ if (!m_activePropertyInfo.isExpanded()
+ && (e.character == '+' || e.keyCode == SWT.ARROW_RIGHT)) {
+ m_activePropertyInfo.expand();
+ configureScrolling();
+ return;
+ }
+ if (m_activePropertyInfo.isExpanded()
+ && (e.character == '-' || e.keyCode == SWT.ARROW_LEFT)) {
+ m_activePropertyInfo.collapse();
+ configureScrolling();
+ return;
+ }
+ }
+ // navigation
+ if (navigate(e)) {
+ return;
+ }
+ // editor activation
+ if (e.character == ' ' || e.character == SWT.CR) {
+ activateEditor(property, null);
+ return;
+ }
+ // DEL
+ if (e.keyCode == SWT.DEL) {
+ e.doit = false;
+ property.setValue(Property.UNKNOWN_VALUE);
+ return;
+ }
+ // send to editor
+ property.getEditor().keyDown(this, property, e);
+ } catch (Throwable ex) {
+ DesignerPlugin.log(ex);
+ }
+ }
+ }
+
+ /**
+ * @return <code>true</code> if given {@link KeyEvent} was navigation event, so new
+ * {@link PropertyInfo} was selected.
+ */
+ public boolean navigate(KeyEvent e) {
+ int index = m_properties.indexOf(m_activePropertyInfo);
+ Rectangle clientArea = getClientArea();
+ //
+ int newIndex = index;
+ if (e.keyCode == SWT.HOME) {
+ newIndex = 0;
+ } else if (e.keyCode == SWT.END) {
+ newIndex = m_properties.size() - 1;
+ } else if (e.keyCode == SWT.PAGE_UP) {
+ newIndex = Math.max(index - m_page + 1, 0);
+ } else if (e.keyCode == SWT.PAGE_DOWN) {
+ newIndex = Math.min(index + m_page - 1, m_properties.size() - 1);
+ } else if (e.keyCode == SWT.ARROW_UP) {
+ newIndex = Math.max(index - 1, 0);
+ } else if (e.keyCode == SWT.ARROW_DOWN) {
+ newIndex = Math.min(index + 1, m_properties.size() - 1);
+ }
+ // activate new property
+ if (newIndex != index && newIndex < m_properties.size()) {
+ setActivePropertyInfo(m_properties.get(newIndex));
+ // check for scrolling
+ int y = m_rowHeight * (newIndex - m_selection);
+ if (y < 0) {
+ m_selection = newIndex;
+ configureScrolling();
+ } else if (y + m_rowHeight > clientArea.height) {
+ m_selection = newIndex - m_page + 1;
+ configureScrolling();
+ }
+ // repaint
+ redraw();
+ return true;
+ }
+ // no navigation change
+ return false;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Events: mouse
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ private boolean m_splitterResizing;
+ /**
+ * We do expand/collapse on to events: click on state sign and on double click. But when we double
+ * click on state sign, we will have <em>two</em> events, so we should ignore double click.
+ */
+ private long m_lastExpandCollapseTime;
+
+ /**
+ * Handles {@link SWT#MouseDown} event.
+ */
+ private void handleMouseDown(MouseEvent event) {
+ m_splitterResizing = event.button == 1 && m_properties != null && isLocationSplitter(event.x);
+ // click in property
+ if (!m_splitterResizing && m_properties != null) {
+ int propertyIndex = getPropertyIndex(event.y);
+ if (propertyIndex >= m_properties.size()) {
+ return;
+ }
+ // prepare property
+ setActivePropertyInfo(m_properties.get(propertyIndex));
+ Property property = m_activePropertyInfo.getProperty();
+ // de-activate current editor
+ deactivateEditor(true);
+ redraw();
+ // activate editor
+ if (isLocationValue(event.x)) {
+ activateEditor(property, getValueRelativeLocation(event.x, event.y));
+ }
+ }
+ }
+
+ /**
+ * Handles {@link SWT#MouseUp} event.
+ */
+ private void handleMouseUp(MouseEvent event) {
+ if (event.button == 1) {
+ // resize splitter
+ if (m_splitterResizing) {
+ m_splitterResizing = false;
+ return;
+ }
+ // if out of bounds, then ignore
+ if (!getClientArea().contains(event.x, event.y)) {
+ return;
+ }
+ // update
+ if (m_properties != null) {
+ int index = getPropertyIndex(event.y);
+ if (index < m_properties.size()) {
+ PropertyInfo propertyInfo = m_properties.get(index);
+ // check for expand/collapse
+ if (isLocationState(propertyInfo, event.x)) {
+ try {
+ m_lastExpandCollapseTime = System.currentTimeMillis();
+ propertyInfo.flip();
+ configureScrolling();
+ } catch (Throwable e) {
+ DesignerPlugin.log(e);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles {@link SWT#MouseDoubleClick} event.
+ */
+ private void handleMouseDoubleClick(MouseEvent event) {
+ if (System.currentTimeMillis() - m_lastExpandCollapseTime > getDisplay().getDoubleClickTime()) {
+ try {
+ if (m_activePropertyInfo != null) {
+ if (m_activePropertyInfo.isComplex()) {
+ m_activePropertyInfo.flip();
+ configureScrolling();
+ } else {
+ Property property = m_activePropertyInfo.getProperty();
+ property.getEditor().doubleClick(property, getValueRelativeLocation(event.x, event.y));
+ }
+ }
+ } catch (Throwable e) {
+ handleException(e);
+ }
+ }
+ }
+
+ /**
+ * Handles {@link SWT#MouseMove} event.
+ */
+ private void handleMouseMove(MouseEvent event) {
+ int x = event.x;
+ // resize splitter
+ if (m_splitterResizing) {
+ m_splitter = x;
+ configureSplitter();
+ redraw();
+ return;
+ }
+ // if out of bounds, then ignore
+ if (!getClientArea().contains(event.x, event.y)) {
+ return;
+ }
+ // update
+ if (m_properties != null) {
+ // update cursor
+ if (isLocationSplitter(x)) {
+ setCursor(ICursorConstants.SIZEWE);
+ } else {
+ setCursor(null);
+ }
+ // update tooltip helper
+ updateTooltip(event);
+ } else {
+ updateTooltipNoProperty();
+ }
+ }
+
+ /**
+ * Updates {@link PropertyTableTooltipHelper}.
+ */
+ private void updateTooltip(MouseEvent event) {
+ int x = event.x;
+ int propertyIndex = getPropertyIndex(event.y);
+ //
+ if (propertyIndex < m_properties.size()) {
+ PropertyInfo propertyInfo = m_properties.get(propertyIndex);
+ Property property = propertyInfo.getProperty();
+ int y = (propertyIndex - m_selection) * m_rowHeight;
+ // check for title
+ {
+ int titleX = getTitleTextX(propertyInfo);
+ int titleRight = m_splitter - 2;
+ if (titleX <= x && x < titleRight) {
+ m_tooltipHelper.update(property, true, false, titleX, titleRight, y, m_rowHeight);
+ return;
+ }
+ }
+ // check for value
+ {
+ int valueX = m_splitter + 3;
+ if (x > valueX) {
+ m_tooltipHelper.update(
+ property,
+ false,
+ true,
+ valueX,
+ getClientArea().width,
+ y,
+ m_rowHeight);
+ }
+ }
+ } else {
+ updateTooltipNoProperty();
+ }
+ }
+
+ private void updateTooltipNoProperty() {
+ m_tooltipHelper.update(null, false, false, 0, 0, 0, 0);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Editor
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ private PropertyInfo m_activePropertyInfo;
+ private String m_activePropertyId;
+ private PropertyEditor m_activeEditor;
+
+ /**
+ * Tries to activate editor for {@link PropertyInfo} under cursor.
+ *
+ * @param location
+ * the mouse location, if editor is activated using mouse click, or <code>null</code> if
+ * it is activated using keyboard.
+ */
+ public void activateEditor(Property property, Point location) {
+ try {
+ // de-activate old editor
+ deactivateEditor(true);
+ // activate editor
+ PropertyEditor editor = property.getEditor();
+ try {
+ if (editor.activate(this, property, location)) {
+ m_activeEditor = editor;
+ }
+ } catch (Throwable e) {
+ handleException(e);
+ }
+ // set bounds
+ setActiveEditorBounds();
+ } catch (Throwable e) {
+ DesignerPlugin.log(e);
+ }
+ }
+
+ /**
+ * Deactivates current {@link PropertyEditor}.
+ */
+ public void deactivateEditor(boolean save) {
+ if (m_activeEditor != null) {
+ PropertyEditor activeEditor = m_activeEditor;
+ m_activeEditor = null;
+ if (m_activePropertyInfo != null && m_activePropertyInfo.m_property != null) {
+ activeEditor.deactivate(this, m_activePropertyInfo.m_property, save);
+ }
+ }
+ }
+
+ /**
+ * Sets correct bounds for active editor, for example we need update bounds after scrolling.
+ */
+ private void setActiveEditorBounds() {
+ if (m_activeEditor != null) {
+ int index = m_properties.indexOf(m_activePropertyInfo);
+ if (index == -1) {
+ // it is possible that active property was hidden because its parent was collapsed
+ deactivateEditor(true);
+ } else {
+ // prepare bounds for editor
+ Rectangle bounds;
+ {
+ Rectangle clientArea = getClientArea();
+ int x = m_splitter + 1;
+ int width = clientArea.width - x - MARGIN_RIGHT;
+ int y = m_rowHeight * (index - m_selection) + 1;
+ int height = m_rowHeight - 1;
+ bounds = new Rectangle(x, y, width, height);
+ }
+ // update bounds using presentation
+ {
+ PropertyEditorPresentation presentation = m_activeEditor.getPresentation();
+ if (presentation != null) {
+ int presentationWidth =
+ presentation.show(
+ this,
+ m_activePropertyInfo.m_property,
+ bounds.x,
+ bounds.y,
+ bounds.width,
+ bounds.height);
+ bounds.width -= presentationWidth;
+ }
+ }
+ // set editor bounds
+ m_activeEditor.setBounds(bounds);
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Exceptions
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ private IPropertyExceptionHandler m_exceptionHandler;
+
+ /**
+ * Sets {@link IPropertyExceptionHandler} for handling all exceptions.
+ */
+ public void setExceptionHandler(IPropertyExceptionHandler exceptionHandler) {
+ m_exceptionHandler = exceptionHandler;
+ }
+
+ /**
+ * Handles given {@link Throwable}.<br>
+ * Right now it just logs it, but in future we can open some dialog here.
+ */
+ public void handleException(Throwable e) {
+ m_exceptionHandler.handle(e);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Scrolling
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Configures vertical {@link ScrollBar}.
+ */
+ private void configureScrolling() {
+ ScrollBar verticalBar = getVerticalBar();
+ if (m_properties == null) {
+ verticalBar.setEnabled(false);
+ } else {
+ m_page = getClientArea().height / m_rowHeight;
+ m_selection = Math.max(0, Math.min(m_properties.size() - m_page, m_selection));
+ verticalBar.setValues(m_selection, 0, m_properties.size(), m_page, 1, m_page);
+ // enable/disable scrolling
+ if (m_properties.size() <= m_page) {
+ verticalBar.setEnabled(false);
+ } else {
+ verticalBar.setEnabled(true);
+ }
+ }
+ // redraw, we reconfigure scrolling only if list of properties was changed, so we should redraw
+ redraw();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Location/size utils
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * @return the <code>X</code> position for first pixel of {@link PropertyInfo} title (location of
+ * state image).
+ */
+ private int getTitleX(PropertyInfo propertyInfo) {
+ return MARGIN_LEFT + getLevelIndent() * propertyInfo.getLevel();
+ }
+
+ /**
+ * @return the <code>X</code> position for first pixel of {@link PropertyInfo} title text.
+ */
+ private int getTitleTextX(PropertyInfo propertyInfo) {
+ return getTitleX(propertyInfo) + getLevelIndent();
+ }
+
+ /**
+ * @return the indentation for single level.
+ */
+ private int getLevelIndent() {
+ return m_stateWidth + STATE_IMAGE_MARGIN_RIGHT;
+ }
+
+ /**
+ * Checks horizontal splitter value to boundary values.
+ */
+ private void configureSplitter() {
+ Rectangle clientArea = getClientArea();
+ // check title width
+ if (m_splitter < MIN_COLUMN_WIDTH) {
+ m_splitter = MIN_COLUMN_WIDTH;
+ }
+ // check value width
+ if (clientArea.width - m_splitter < MIN_COLUMN_WIDTH) {
+ m_splitter = clientArea.width - MIN_COLUMN_WIDTH;
+ }
+ }
+
+ /**
+ * @return the index in {@link #m_properties} corresponding given <code>y</code> location.
+ */
+ private int getPropertyIndex(int y) {
+ return m_selection + y / m_rowHeight;
+ }
+
+ /**
+ * @return <code>true</code> if given <code>x</code> coordinate is on state (plus/minus) image.
+ */
+ private boolean isLocationState(PropertyInfo propertyInfo, int x) {
+ int levelX = getTitleX(propertyInfo);
+ return propertyInfo.isComplex() && levelX <= x && x <= levelX + m_stateWidth;
+ }
+
+ /**
+ * Returns <code>true</code> if <code>x</code> coordinate is on splitter.
+ */
+ private boolean isLocationSplitter(int x) {
+ return Math.abs(m_splitter - x) < 2;
+ }
+
+ /**
+ * @return <code>true</code> if given <code>x</code> is on value part of property.
+ */
+ private boolean isLocationValue(int x) {
+ return x > m_splitter + 2;
+ }
+
+ /**
+ * @param x
+ * the {@link PropertyTable} relative coordinate.
+ * @param y
+ * the {@link PropertyTable} relative coordinate.
+ *
+ * @return the location relative to the value part of property.
+ */
+ private Point getValueRelativeLocation(int x, int y) {
+ return new Point(x - (m_splitter + 2), y - m_rowHeight * getPropertyIndex(y));
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Access
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Shows or hides {@link Property}-s with {@link PropertyCategory#ADVANCED}.
+ */
+ public void setShowAdvancedProperties(boolean showAdvancedProperties) {
+ m_showAdvancedProperties = showAdvancedProperties;
+ setInput0();
+ }
+
+ /**
+ * Sets the array of {@link Property}'s to display/edit.
+ */
+ public void setInput(Property[] properties) {
+ m_rawProperties = properties;
+ setInput0();
+ }
+
+ private void setInput0() {
+ // send "hide" to all PropertyEditorPresentation's
+ if (m_properties != null) {
+ for (PropertyInfo propertyInfo : m_properties) {
+ Property property = propertyInfo.getProperty();
+ // hide presentation
+ {
+ PropertyEditorPresentation presentation = property.getEditor().getPresentation();
+ if (presentation != null) {
+ presentation.hide(this, property);
+ }
+ }
+ }
+ }
+ // set new properties
+ if (m_rawProperties == null || m_rawProperties.length == 0) {
+ deactivateEditor(false);
+ m_properties = Lists.newArrayList();
+ } else {
+ try {
+ // add PropertyInfo for each Property
+ m_properties = Lists.newArrayList();
+ for (Property property : m_rawProperties) {
+ if (rawProperties_shouldShow(property)) {
+ PropertyInfo propertyInfo = new PropertyInfo(property);
+ m_properties.add(propertyInfo);
+ }
+ }
+ // expand properties using history
+ while (true) {
+ boolean expanded = false;
+ List<PropertyInfo> currentProperties = Lists.newArrayList(m_properties);
+ for (PropertyInfo propertyInfo : currentProperties) {
+ expanded |= propertyInfo.expandFromHistory();
+ }
+ // stop
+ if (!expanded) {
+ break;
+ }
+ }
+ } catch (Throwable e) {
+ DesignerPlugin.log(e);
+ }
+ }
+ // update active property
+ if (m_activePropertyId != null) {
+ PropertyInfo newActivePropertyInfo = null;
+ // try to find corresponding PropertyInfo
+ if (m_properties != null) {
+ for (PropertyInfo propertyInfo : m_properties) {
+ if (propertyInfo.m_id.equals(m_activePropertyId)) {
+ newActivePropertyInfo = propertyInfo;
+ break;
+ }
+ }
+ }
+ // set new PropertyInfo
+ setActivePropertyInfo(newActivePropertyInfo);
+ }
+ // update scroll bar
+ configureScrolling();
+ }
+
+ /**
+ * @return <code>true</code> if given {@link Property} should be displayed.
+ */
+ private boolean rawProperties_shouldShow(Property property) throws Exception {
+ PropertyCategory category = getCategory(property);
+ // filter out hidden properties
+ if (category.isHidden()) {
+ return false;
+ }
+ // filter out advanced properties
+ if (category.isAdvanced()) {
+ if (!m_showAdvancedProperties && !property.isModified()) {
+ return false;
+ }
+ }
+ if (category.isAdvancedReally()) {
+ return m_showAdvancedProperties;
+ }
+ // OK
+ return true;
+ }
+
+ /**
+ * Activates given {@link Property}.
+ */
+ public void setActiveProperty(Property property) {
+ for (PropertyInfo propertyInfo : m_properties) {
+ if (propertyInfo.m_property == property) {
+ setActivePropertyInfo(propertyInfo);
+ break;
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Access: only for testing
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * @return the count of properties in "expanded" list.
+ */
+ public int forTests_getPropertiesCount() {
+ return m_properties.size();
+ }
+
+ /**
+ * @return the {@link Property} from "expanded" list.
+ */
+ public Property forTests_getProperty(int index) {
+ return m_properties.get(index).getProperty();
+ }
+
+ /**
+ * Expands the {@link PropertyInfo} with given index.
+ */
+ public void forTests_expand(int index) throws Exception {
+ m_properties.get(index).expand();
+ }
+
+ /**
+ * @return the position of splitter.
+ */
+ public int forTests_getSplitter() {
+ return m_splitter;
+ }
+
+ /**
+ * @return the location of state image (plus/minus) for given {@link Property}.
+ */
+ public Point forTests_getStateLocation(Property property) {
+ PropertyInfo propertyInfo = getPropertyInfo(property);
+ if (propertyInfo != null) {
+ int index = m_properties.indexOf(propertyInfo);
+ int x = getTitleX(propertyInfo);
+ int y = m_rowHeight * (index - m_selection) + 1;
+ return new Point(x, y);
+ }
+ return null;
+ }
+
+ /**
+ * @return the location of state image (plus/minus) for given {@link Property}.
+ */
+ public Point forTests_getValueLocation(Property property) {
+ PropertyInfo propertyInfo = getPropertyInfo(property);
+ if (propertyInfo != null) {
+ int index = m_properties.indexOf(propertyInfo);
+ int x = m_splitter + 5;
+ int y = m_rowHeight * (index - m_selection) + 1;
+ return new Point(x, y);
+ }
+ return null;
+ }
+
+ /**
+ * @return the active {@link PropertyEditor}.
+ */
+ public PropertyEditor forTests_getActiveEditor() {
+ return m_activeEditor;
+ }
+
+ /**
+ * @return the {@link PropertyCategory} that is used by this {@link PropertyTable} to display.
+ */
+ public PropertyCategory forTests_getCategory(Property property) {
+ return getCategory(property);
+ }
+
+ /**
+ * @return the {@link PropertyInfo}for given {@link Property}.
+ */
+ private PropertyInfo getPropertyInfo(Property property) {
+ for (PropertyInfo propertyInfo : m_properties) {
+ if (propertyInfo.getProperty() == property) {
+ return propertyInfo;
+ }
+ }
+ return null;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // ISelectionProvider
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ private final List<ISelectionChangedListener> m_selectionListeners = Lists.newArrayList();
+
+ @Override
+public void addSelectionChangedListener(ISelectionChangedListener listener) {
+ if (!m_selectionListeners.contains(listener)) {
+ m_selectionListeners.add(listener);
+ }
+ }
+
+ @Override
+public void removeSelectionChangedListener(ISelectionChangedListener listener) {
+ m_selectionListeners.add(listener);
+ }
+
+ @Override
+public ISelection getSelection() {
+ if (m_activePropertyInfo != null) {
+ return new StructuredSelection(m_activePropertyInfo.getProperty());
+ } else {
+ return StructuredSelection.EMPTY;
+ }
+ }
+
+ @Override
+ public void setSelection(ISelection selection) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Sets the new active {@link PropertyInfo} and sends event to {@link ISelectionChangedListener}
+ * 's.
+ */
+ private void setActivePropertyInfo(PropertyInfo activePropertyInfo) {
+ m_activePropertyInfo = activePropertyInfo;
+ // update m_activePropertyId only when really select property,
+ // not just remove selection because there are no corresponding property for old active
+ // so, later for some other component, if we don't select other property, old active will be selected
+ if (m_activePropertyInfo != null) {
+ m_activePropertyId = m_activePropertyInfo.m_id;
+ }
+ // make sure that active property is visible
+ if (m_activePropertyInfo != null) {
+ int row = m_properties.indexOf(m_activePropertyInfo);
+ if (m_selection <= row && row < m_selection + m_page) {
+ } else {
+ m_selection = row;
+ configureScrolling();
+ }
+ }
+ // send events
+ SelectionChangedEvent selectionEvent = new SelectionChangedEvent(this, getSelection());
+ for (ISelectionChangedListener listener : m_selectionListeners) {
+ listener.selectionChanged(selectionEvent);
+ }
+ // re-draw
+ redraw();
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Painting
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ private boolean m_painting;
+ private Font m_baseFont;
+ private Font m_boldFont;
+ private Font m_italicFont;
+
+ /**
+ * Handles {@link SWT#Paint} event.
+ */
+ private void handlePaint(GC gc, int x, int y, int width, int height) {
+ // sometimes we disable Eclipse Shell to prevent user actions, but we do this for short time
+ if (!isEnabled()) {
+ return;
+ }
+ // prevent recursion
+ if (m_painting) {
+ return;
+ }
+ m_painting = true;
+ //
+ try {
+ setActiveEditorBounds();
+ // prepare buffered image
+ if (m_bufferedImage == null || m_bufferedImage.isDisposed()) {
+ Point size = getSize();
+ m_bufferedImage = new Image(DesignerPlugin.getStandardDisplay(), size.x, size.y);
+ }
+ // prepare buffered GC
+ GC bufferedGC = null;
+ try {
+ // perform some drawing
+ {
+ bufferedGC = new GC(m_bufferedImage);
+ bufferedGC.setClipping(x, y, width, height);
+ bufferedGC.setBackground(gc.getBackground());
+ bufferedGC.setForeground(gc.getForeground());
+ bufferedGC.setFont(gc.getFont());
+ bufferedGC.setLineStyle(gc.getLineStyle());
+ bufferedGC.setLineWidth(gc.getLineWidth());
+ }
+ // fill client area
+ {
+ Rectangle clientArea = getClientArea();
+ bufferedGC.setBackground(COLOR_BACKGROUND);
+ bufferedGC.fillRectangle(clientArea);
+ }
+ // draw content
+ if (m_properties == null || m_properties.size() == 0) {
+ drawEmptyContent(bufferedGC);
+ } else {
+ drawContent(bufferedGC);
+ }
+ } finally {
+ // flush image
+ if (bufferedGC != null) {
+ bufferedGC.dispose();
+ }
+ }
+ gc.drawImage(m_bufferedImage, 0, 0);
+ } finally {
+ m_painting = false;
+ }
+ }
+
+ /**
+ * Draws content when there are no properties.
+ */
+ private void drawEmptyContent(GC gc) {
+ Rectangle area = getClientArea();
+ // draw message
+ gc.setForeground(COLOR_NO_PROPERTIES);
+ DrawUtils.drawStringCHCV(
+ gc,
+ "<No properties>",
+ 0,
+ 0,
+ area.width,
+ area.height);
+ }
+
+ /**
+ * Draws all {@link PropertyInfo}'s, separators, etc.
+ */
+ private void drawContent(GC gc) {
+ Rectangle clientArea = getClientArea();
+ // prepare fonts
+ m_baseFont = gc.getFont();
+ m_boldFont = DrawUtils.getBoldFont(m_baseFont);
+ m_italicFont = DrawUtils.getItalicFont(m_baseFont);
+ // show presentations
+ int[] presentationsWidth = showPresentations(clientArea);
+ // draw properties
+ {
+ int y = clientArea.y - m_rowHeight * m_selection;
+ for (int i = 0; i < m_properties.size(); i++) {
+ // skip, if not visible yet
+ if (y + m_rowHeight < 0) {
+ y += m_rowHeight;
+ continue;
+ }
+ // stop, if already invisible
+ if (y > clientArea.height) {
+ break;
+ }
+ // draw single property
+ {
+ PropertyInfo propertyInfo = m_properties.get(i);
+ drawProperty(gc, propertyInfo, y + 1, m_rowHeight - 1, clientArea.width
+ - presentationsWidth[i]);
+ y += m_rowHeight;
+ }
+ // draw row separator
+ gc.setForeground(COLOR_LINE);
+ gc.drawLine(0, y, clientArea.width, y);
+ }
+ }
+ // draw expand line
+ drawExpandLines(gc, clientArea);
+ // draw rectangle around table
+ gc.setForeground(COLOR_LINE);
+ gc.drawRectangle(0, 0, clientArea.width - 1, clientArea.height - 1);
+ // draw splitter
+ gc.setForeground(COLOR_LINE);
+ gc.drawLine(m_splitter, 0, m_splitter, clientArea.height);
+ // dispose font
+ m_boldFont.dispose();
+ m_italicFont.dispose();
+ }
+
+ /**
+ * Shows {@link PropertyEditorPresentation}'s for all {@link Property}'s, i.e. updates also their
+ * bounds. So, some {@link PropertyEditorPresentation}'s become invisible because they are moved
+ * above or below visible client area.
+ *
+ * @return the array of width for each {@link PropertyEditorPresentation}'s, consumed on the
+ * right.
+ */
+ private int[] showPresentations(Rectangle clientArea) {
+ int[] presentationsWidth = new int[m_properties.size()];
+ // prepare value rectangle
+ int x = m_splitter + 4;
+ int w = clientArea.width - x - MARGIN_RIGHT;
+ // show presentation's for all properties
+ int y = clientArea.y - m_rowHeight * m_selection;
+ for (int i = 0; i < m_properties.size(); i++) {
+ PropertyInfo propertyInfo = m_properties.get(i);
+ Property property = propertyInfo.getProperty();
+ PropertyEditorPresentation presentation = property.getEditor().getPresentation();
+ if (presentation != null) {
+ presentationsWidth[i] = presentation.show(this, property, x, y + 1, w, m_rowHeight - 1);
+ }
+ y += m_rowHeight;
+ }
+ return presentationsWidth;
+ }
+
+ /**
+ * Draws lines from expanded complex property to its last sub-property.
+ */
+ private void drawExpandLines(GC gc, Rectangle clientArea) {
+ int height = m_rowHeight - 1;
+ int xOffset = m_plusImage.getBounds().width / 2;
+ int yOffset = (height - m_plusImage.getBounds().width) / 2;
+ //
+ int y = clientArea.y - m_selection * m_rowHeight;
+ gc.setForeground(COLOR_COMPLEX_LINE);
+ for (int i = 0; i < m_properties.size(); i++) {
+ PropertyInfo propertyInfo = m_properties.get(i);
+ //
+ if (propertyInfo.isExpanded()) {
+ int index = m_properties.indexOf(propertyInfo);
+ // prepare index of last sub-property
+ int index2 = index;
+ for (; index2 < m_properties.size(); index2++) {
+ PropertyInfo nextPropertyInfo = m_properties.get(index2);
+ if (nextPropertyInfo != propertyInfo
+ && nextPropertyInfo.getLevel() <= propertyInfo.getLevel()) {
+ break;
+ }
+ }
+ index2--;
+ // draw line if there are children
+ if (index2 > index) {
+ int x = getTitleX(propertyInfo) + xOffset;
+ int y1 = y + height - yOffset;
+ int y2 = y + m_rowHeight * (index2 - index) + m_rowHeight / 2;
+ gc.drawLine(x, y1, x, y2);
+ gc.drawLine(x, y2, x + m_rowHeight / 3, y2);
+ }
+ }
+ //
+ y += m_rowHeight;
+ }
+ }
+
+ /**
+ * Draws single {@link PropertyInfo} in specified rectangle.
+ */
+ private void drawProperty(GC gc, PropertyInfo propertyInfo, int y, int height, int width) {
+ // remember colors
+ Color oldBackground = gc.getBackground();
+ Color oldForeground = gc.getForeground();
+ // draw property
+ try {
+ Property property = propertyInfo.getProperty();
+ boolean isActiveProperty =
+ m_activePropertyInfo != null && m_activePropertyInfo.getProperty() == property;
+ // set background
+ boolean modified = property.isModified();
+ {
+ if (isActiveProperty) {
+ gc.setBackground(COLOR_PROPERTY_BG_SELECTED);
+ } else {
+ if (modified) {
+ gc.setBackground(COLOR_PROPERTY_BG_MODIFIED);
+ } else {
+ gc.setBackground(COLOR_PROPERTY_BG);
+ }
+ }
+ gc.fillRectangle(0, y, width, height);
+ }
+ // draw state image
+ if (propertyInfo.isShowComplex()) {
+ Image stateImage = propertyInfo.isExpanded() ? m_minusImage : m_plusImage;
+ DrawUtils.drawImageCV(gc, stateImage, getTitleX(propertyInfo), y, height);
+ }
+ // draw title
+ {
+ // configure GC
+ {
+ gc.setForeground(COLOR_PROPERTY_FG_TITLE);
+ // check category
+ if (getCategory(property).isAdvanced()) {
+ gc.setForeground(COLOR_PROPERTY_FG_ADVANCED);
+ gc.setFont(m_italicFont);
+ } else if (getCategory(property).isPreferred() || getCategory(property).isSystem()) {
+ gc.setFont(m_boldFont);
+ }
+ // check for active
+ if (isActiveProperty) {
+ gc.setForeground(COLOR_PROPERTY_FG_SELECTED);
+ }
+ }
+ // paint title
+ int x = getTitleTextX(propertyInfo);
+ DrawUtils.drawStringCV(gc, property.getTitle(), x, y, m_splitter - x, height);
+ }
+ // draw value
+ {
+ // configure GC
+ gc.setFont(m_baseFont);
+ if (!isActiveProperty) {
+ gc.setForeground(COLOR_PROPERTY_FG_VALUE);
+ }
+ // prepare value rectangle
+ int x = m_splitter + 4;
+ int w = width - x - MARGIN_RIGHT;
+ // paint value
+
+ // BEGIN ADT MODIFICATIONS
+ if (!modified) {
+ gc.setForeground(COLOR_PROPERTY_FG_DEFAULT);
+ }
+ // END ADT MODIFICATIONS
+
+ property.getEditor().paint(property, gc, x, y, w, height);
+ }
+ } catch (Throwable e) {
+ DesignerPlugin.log(e);
+ } finally {
+ // restore colors
+ gc.setBackground(oldBackground);
+ gc.setForeground(oldForeground);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // PropertyCategory
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ private PropertyCategoryProvider m_propertyCategoryProvider =
+ PropertyCategoryProviders.fromProperty();
+
+ /**
+ * Sets the {@link PropertyCategoryProvider} that can be used to tweak usual
+ * {@link PropertyCategory}.
+ */
+ public void setPropertyCategoryProvider(PropertyCategoryProvider propertyCategoryProvider) {
+ m_propertyCategoryProvider = propertyCategoryProvider;
+ }
+
+ /**
+ * @return the {@link PropertyCategory} that is used by this {@link PropertyTable} to display.
+ */
+ private PropertyCategory getCategory(Property property) {
+ return m_propertyCategoryProvider.getCategory(property);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // PropertyInfo
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Class with information about single {@link Property}.
+ *
+ * @author scheglov_ke
+ */
+ private final class PropertyInfo {
+ private final String m_id;
+ private final int m_level;
+ private final Property m_property;
+ private final boolean m_stateComplex;
+ private boolean m_stateExpanded;
+ private List<PropertyInfo> m_children;
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Constructor
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ public PropertyInfo(Property property) {
+ this(property, "", 0);
+ }
+
+ private PropertyInfo(Property property, String idPrefix, int level) {
+ // BEGIN ADT MODIFICATIONS
+ //m_id = idPrefix + "|" + property.getTitle();
+ m_id = idPrefix + "|" + property.getName();
+ // END ADT MODIFICATIONS
+ m_level = level;
+ m_property = property;
+ m_stateComplex = property.getEditor() instanceof IComplexPropertyEditor;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // State
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * @return <code>true</code> if this property is complex.
+ */
+ public boolean isComplex() {
+ return m_stateComplex;
+ }
+
+ public boolean isShowComplex() throws Exception {
+ if (m_stateComplex) {
+ prepareChildren();
+ return m_children != null && !m_children.isEmpty();
+ }
+ return false;
+ }
+
+ /**
+ * @return <code>true</code> if this complex property is expanded.
+ */
+ public boolean isExpanded() {
+ return m_stateExpanded;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Access
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * @return the level of this property, i.e. on which level of complex property it is located.
+ */
+ public int getLevel() {
+ return m_level;
+ }
+
+ /**
+ * @return the {@link Property}.
+ */
+ public Property getProperty() {
+ return m_property;
+ }
+
+ /**
+ * Flips collapsed/expanded state and adds/removes sub-properties.
+ */
+ public void flip() throws Exception {
+ Assert.isTrue(m_stateComplex);
+ if (m_stateExpanded) {
+ collapse();
+ } else {
+ expand();
+ }
+ }
+
+ /**
+ * Expands this property.
+ */
+ public void expand() throws Exception {
+ Assert.isTrue(m_stateComplex);
+ Assert.isTrue(!m_stateExpanded);
+ //
+ m_stateExpanded = true;
+ m_expandedIds.add(m_id);
+ // BEGIN ADT MODIFICATIONS
+ if (m_collapsedIds != null) {
+ m_collapsedIds.remove(m_id);
+ }
+ // END ADT MODIFICATIONS
+ prepareChildren();
+ //
+ int index = m_properties.indexOf(this);
+ addChildren(index + 1);
+ }
+
+ /**
+ * Collapses this property.
+ */
+ public void collapse() throws Exception {
+ Assert.isTrue(m_stateComplex);
+ Assert.isTrue(m_stateExpanded);
+ //
+ m_stateExpanded = false;
+ m_expandedIds.remove(m_id);
+ // BEGIN ADT MODIFICATIONS
+ if (m_collapsedIds != null) {
+ m_collapsedIds.add(m_id);
+ }
+ // END ADT MODIFICATIONS
+ prepareChildren();
+ //
+ int index = m_properties.indexOf(this);
+ removeChildren(index + 1);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Internal
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * Adds children properties.
+ *
+ * @return the index for new properties to add.
+ */
+ private int addChildren(int index) throws Exception {
+ prepareChildren();
+ for (PropertyInfo child : m_children) {
+ // skip if should not display raw Property
+ if (!rawProperties_shouldShow(child.m_property)) {
+ continue;
+ }
+ // add child
+ m_properties.add(index++, child);
+ // add children of current child
+ if (child.isExpanded()) {
+ index = child.addChildren(index);
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Removes children properties.
+ */
+ private void removeChildren(int index) throws Exception {
+ prepareChildren();
+ for (PropertyInfo child : m_children) {
+ // skip if should not display raw Property
+ if (!rawProperties_shouldShow(child.m_property)) {
+ continue;
+ }
+ // hide presentation
+ {
+ PropertyEditorPresentation presentation =
+ child.getProperty().getEditor().getPresentation();
+ if (presentation != null) {
+ presentation.hide(PropertyTable.this, child.getProperty());
+ }
+ }
+ // remove child
+ m_properties.remove(index);
+ // remove children of current child
+ if (child.isExpanded()) {
+ child.removeChildren(index);
+ }
+ }
+ }
+
+ /**
+ * Prepares children {@link PropertyInfo}'s, for sub-properties.
+ */
+ private void prepareChildren() throws Exception {
+ if (m_children == null) {
+ m_children = Lists.newArrayList();
+ for (Property subProperty : getSubProperties()) {
+ PropertyInfo subPropertyInfo = createSubPropertyInfo(subProperty);
+ m_children.add(subPropertyInfo);
+ }
+ }
+ }
+
+ private PropertyInfo createSubPropertyInfo(Property subProperty) {
+ return new PropertyInfo(subProperty, m_id, m_level + 1);
+ }
+
+ private Property[] getSubProperties() throws Exception {
+ IComplexPropertyEditor complexEditor = (IComplexPropertyEditor) m_property.getEditor();
+ List<Property> subProperties = Lists.newArrayList();
+ for (Property subProperty : complexEditor.getProperties(m_property)) {
+ if (getCategory(subProperty).isHidden() && !subProperty.isModified()) {
+ // skip hidden properties
+ continue;
+ }
+ subProperties.add(subProperty);
+ }
+ return subProperties.toArray(new Property[subProperties.size()]);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //
+ // Persistent expanding support
+ //
+ ////////////////////////////////////////////////////////////////////////////
+ /**
+ * @return <code>true</code> if this {@link PropertyInfo} was expanded from history.
+ */
+ public boolean expandFromHistory() throws Exception {
+ if (isComplex() && !isExpanded() && m_expandedIds.contains(m_id)) {
+ expand();
+ return true;
+ }
+ // BEGIN ADT MODIFICATIONS
+ if (m_collapsedIds != null && isComplex() && !isExpanded()
+ && !m_collapsedIds.contains(m_id)) {
+ expand();
+ return true;
+ }
+ // END ADT MODIFICATIONS
+ return false;
+ }
+ }
+
+ // BEGIN ADT MODIFICATIONS
+ /** Collapse all top-level properties */
+ public void collapseAll() {
+ try {
+ m_lastExpandCollapseTime = System.currentTimeMillis();
+ if (m_collapsedIds != null) {
+ m_collapsedIds.addAll(m_expandedIds);
+ }
+ m_expandedIds.clear();
+ setInput(m_rawProperties);
+ redraw();
+ } catch (Throwable e) {
+ DesignerPlugin.log(e);
+ }
+ }
+
+ /** Expand all top-level properties */
+ public void expandAll() {
+ try {
+ m_lastExpandCollapseTime = System.currentTimeMillis();
+ if (m_collapsedIds != null) {
+ m_collapsedIds.clear();
+ }
+ m_expandedIds.clear();
+ for (PropertyInfo info : m_properties) {
+ if (info.m_stateComplex) {
+ m_expandedIds.add(info.m_id);
+ }
+ }
+ setInput(m_rawProperties);
+ redraw();
+ } catch (Throwable e) {
+ DesignerPlugin.log(e);
+ }
+ }
+ // END ADT MODIFICATIONS
+} \ No newline at end of file