1/*******************************************************************************
2 * Copyright (c) 2011 Google, Inc.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 *    Google, Inc. - initial API and implementation
10 *******************************************************************************/
11package org.eclipse.wb.internal.core.model.property.table;
12
13import com.google.common.collect.Lists;
14import com.google.common.collect.Sets;
15
16import org.eclipse.jface.viewers.ISelection;
17import org.eclipse.jface.viewers.ISelectionChangedListener;
18import org.eclipse.jface.viewers.ISelectionProvider;
19import org.eclipse.jface.viewers.SelectionChangedEvent;
20import org.eclipse.jface.viewers.StructuredSelection;
21import org.eclipse.swt.SWT;
22import org.eclipse.swt.events.KeyAdapter;
23import org.eclipse.swt.events.KeyEvent;
24import org.eclipse.swt.events.MouseAdapter;
25import org.eclipse.swt.events.MouseEvent;
26import org.eclipse.swt.events.MouseMoveListener;
27import org.eclipse.swt.graphics.Color;
28import org.eclipse.swt.graphics.Font;
29import org.eclipse.swt.graphics.GC;
30import org.eclipse.swt.graphics.Image;
31import org.eclipse.swt.graphics.Point;
32import org.eclipse.swt.graphics.Rectangle;
33import org.eclipse.swt.widgets.Canvas;
34import org.eclipse.swt.widgets.Composite;
35import org.eclipse.swt.widgets.Event;
36import org.eclipse.swt.widgets.Listener;
37import org.eclipse.swt.widgets.ScrollBar;
38import org.eclipse.wb.draw2d.IColorConstants;
39import org.eclipse.wb.draw2d.ICursorConstants;
40import org.eclipse.wb.internal.core.DesignerPlugin;
41import org.eclipse.wb.internal.core.EnvironmentUtils;
42import org.eclipse.wb.internal.core.model.property.Property;
43import org.eclipse.wb.internal.core.model.property.category.PropertyCategory;
44import org.eclipse.wb.internal.core.model.property.category.PropertyCategoryProvider;
45import org.eclipse.wb.internal.core.model.property.category.PropertyCategoryProviders;
46import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor;
47import org.eclipse.wb.internal.core.model.property.editor.complex.IComplexPropertyEditor;
48import org.eclipse.wb.internal.core.model.property.editor.presentation.ButtonPropertyEditorPresentation;
49import org.eclipse.wb.internal.core.model.property.editor.presentation.PropertyEditorPresentation;
50import org.eclipse.wb.internal.core.utils.check.Assert;
51import org.eclipse.wb.internal.core.utils.ui.DrawUtils;
52
53import java.util.Collection;
54import java.util.List;
55import java.util.Set;
56
57/**
58 * Control that can display {@link Property}'s and edit them using {@link PropertyEditor}'s.
59 *
60 * @author scheglov_ke
61 * @author lobas_av
62 * @coverage core.model.property.table
63 */
64public class PropertyTable extends Canvas implements ISelectionProvider {
65  ////////////////////////////////////////////////////////////////////////////
66  //
67  // Colors
68  //
69  ////////////////////////////////////////////////////////////////////////////
70  private static final Color COLOR_BACKGROUND = IColorConstants.listBackground;
71  private static final Color COLOR_NO_PROPERTIES = IColorConstants.gray;
72  private static final Color COLOR_LINE = IColorConstants.lightGray;
73  private static final Color COLOR_COMPLEX_LINE = DrawUtils.getShiftedColor(
74      IColorConstants.lightGray,
75      -32);
76  private static final Color COLOR_PROPERTY_BG = DrawUtils.getShiftedColor(COLOR_BACKGROUND, -12);
77  private static final Color COLOR_PROPERTY_BG_MODIFIED = COLOR_BACKGROUND;
78  private static final Color COLOR_PROPERTY_FG_TITLE = IColorConstants.listForeground;
79  private static final Color COLOR_PROPERTY_FG_VALUE =
80      DrawUtils.isDarkColor(IColorConstants.listBackground)
81          ? IColorConstants.lightBlue
82          : IColorConstants.darkBlue;
83  private static final Color COLOR_PROPERTY_BG_SELECTED = IColorConstants.listSelection;
84  private static final Color COLOR_PROPERTY_FG_SELECTED = IColorConstants.listSelectionText;
85  private static final Color COLOR_PROPERTY_FG_ADVANCED = IColorConstants.gray;
86  // BEGIN ADT MODIFICATIONS
87  public static final Color COLOR_PROPERTY_FG_DEFAULT = IColorConstants.darkGray;
88  // END ADT MODIFICATIONS
89  ////////////////////////////////////////////////////////////////////////////
90  //
91  // Sizes
92  //
93  ////////////////////////////////////////////////////////////////////////////
94  private static final int MIN_COLUMN_WIDTH = 75;
95  private static final int MARGIN_LEFT = 2;
96  private static final int MARGIN_RIGHT = 1;
97  private static final int STATE_IMAGE_MARGIN_RIGHT = 4;
98  ////////////////////////////////////////////////////////////////////////////
99  //
100  // Images
101  //
102  ////////////////////////////////////////////////////////////////////////////
103  private static final Image m_plusImage = DesignerPlugin.getImage("properties/plus.gif");
104  private static final Image m_minusImage = DesignerPlugin.getImage("properties/minus.gif");
105  private static int m_stateWidth = 9;
106  ////////////////////////////////////////////////////////////////////////////
107  //
108  // Instance fields
109  //
110  ////////////////////////////////////////////////////////////////////////////
111  private final PropertyTableTooltipHelper m_tooltipHelper;
112  private boolean m_showAdvancedProperties;
113  private Property[] m_rawProperties;
114  private List<PropertyInfo> m_properties;
115  private final Set<String> m_expandedIds = Sets.newTreeSet();
116  // BEGIN ADT MODIFICATIONS
117  // Add support for "expand by default" : If you specify ids to be collapsed by
118  // default, then any *other* ids will be expanded by default.
119  private Set<String> m_collapsedIds = null;
120
121    /**
122     * Sets a set of ids that should be collapsed by default. Everything else
123     * will be expanded by default. If this method is not called, ids are
124     * collapsed by default instead.
125     *
126     * @param names set of ids to be collapsed
127     */
128  public void setDefaultCollapsedNames(Collection<String> names) {
129      m_collapsedIds = Sets.newTreeSet();
130      for (String name : names) {
131          m_collapsedIds.add("|" + name); // See PropertyInfo constructor for id syntax
132      }
133  }
134  // END ADT MODIFICATIONS
135
136  private Image m_bufferedImage;
137  private int m_rowHeight;
138  private int m_selection;
139  private int m_page;
140  private int m_splitter = -1;
141
142  ////////////////////////////////////////////////////////////////////////////
143  //
144  // Constructor
145  //
146  ////////////////////////////////////////////////////////////////////////////
147  public PropertyTable(Composite parent, int style) {
148    super(parent, style | SWT.V_SCROLL | SWT.NO_BACKGROUND | SWT.NO_REDRAW_RESIZE);
149    hookControlEvents();
150    // calculate sizes
151    {
152      GC gc = new GC(this);
153      try {
154        m_rowHeight = 1 + gc.getFontMetrics().getHeight() + 1;
155      } finally {
156        gc.dispose();
157      }
158    }
159    // install tooltip helper
160    m_tooltipHelper = new PropertyTableTooltipHelper(this);
161  }
162
163  ////////////////////////////////////////////////////////////////////////////
164  //
165  // Events
166  //
167  ////////////////////////////////////////////////////////////////////////////
168  /**
169   * Adds listeners for events.
170   */
171  private void hookControlEvents() {
172    addListener(SWT.Dispose, new Listener() {
173      @Override
174    public void handleEvent(Event event) {
175        disposeBufferedImage();
176      }
177    });
178    addListener(SWT.Resize, new Listener() {
179      @Override
180    public void handleEvent(Event event) {
181        handleResize();
182      }
183    });
184    addListener(SWT.Paint, new Listener() {
185      @Override
186    public void handleEvent(Event event) {
187        handlePaint(event.gc, event.x, event.y, event.width, event.height);
188      }
189    });
190    getVerticalBar().addListener(SWT.Selection, new Listener() {
191      @Override
192    public void handleEvent(Event event) {
193        handleVerticalScrolling();
194      }
195    });
196    addMouseListener(new MouseAdapter() {
197      @Override
198      public void mouseDown(MouseEvent event) {
199        forceFocus();
200        handleMouseDown(event);
201      }
202
203      @Override
204      public void mouseUp(MouseEvent event) {
205        handleMouseUp(event);
206      }
207
208      @Override
209      public void mouseDoubleClick(MouseEvent event) {
210        handleMouseDoubleClick(event);
211      }
212    });
213    addMouseMoveListener(new MouseMoveListener() {
214      @Override
215    public void mouseMove(MouseEvent event) {
216        handleMouseMove(event);
217      }
218    });
219    // keyboard
220    addKeyListener(new KeyAdapter() {
221      @Override
222      public void keyPressed(KeyEvent e) {
223        handleKeyDown(e);
224      }
225    });
226  }
227
228  ////////////////////////////////////////////////////////////////////////////
229  //
230  // Events: dispose, resize, scroll
231  //
232  ////////////////////////////////////////////////////////////////////////////
233  /**
234   * Disposes image used for double buffered painting.
235   */
236  private void disposeBufferedImage() {
237    if (m_bufferedImage != null) {
238      m_bufferedImage.dispose();
239      m_bufferedImage = null;
240    }
241  }
242
243  /**
244   * Handles {@link SWT#Resize} event.
245   */
246  private void handleResize() {
247    disposeBufferedImage();
248    configureScrolling();
249    // splitter
250    {
251      // set default value for splitter
252      if (m_splitter <= MIN_COLUMN_WIDTH) {
253        m_splitter = Math.max((int) (getClientArea().width * 0.4), MIN_COLUMN_WIDTH);
254      }
255      configureSplitter();
256    }
257  }
258
259  /**
260   * Handles {@link SWT#Selection} event for vertical {@link ScrollBar}.
261   */
262  private void handleVerticalScrolling() {
263    ScrollBar verticalBar = getVerticalBar();
264    if (verticalBar.getEnabled()) {
265      // update selection
266      m_selection = verticalBar.getSelection();
267      // redraw (but not include vertical bar to avoid flashing)
268      {
269        Rectangle clientArea = getClientArea();
270        redraw(clientArea.x, clientArea.y, clientArea.width, clientArea.height, false);
271      }
272    }
273  }
274
275  ////////////////////////////////////////////////////////////////////////////
276  //
277  // Keyboard
278  //
279  ////////////////////////////////////////////////////////////////////////////
280  /**
281   * Handles {@link SWT#KeyDown} event.
282   */
283  private void handleKeyDown(KeyEvent e) {
284    if (m_activePropertyInfo != null) {
285      try {
286        Property property = m_activePropertyInfo.getProperty();
287        // expand/collapse
288        if (m_activePropertyInfo.isComplex()) {
289          if (!m_activePropertyInfo.isExpanded()
290              && (e.character == '+' || e.keyCode == SWT.ARROW_RIGHT)) {
291            m_activePropertyInfo.expand();
292            configureScrolling();
293            return;
294          }
295          if (m_activePropertyInfo.isExpanded()
296              && (e.character == '-' || e.keyCode == SWT.ARROW_LEFT)) {
297            m_activePropertyInfo.collapse();
298            configureScrolling();
299            return;
300          }
301        }
302        // navigation
303        if (navigate(e)) {
304          return;
305        }
306        // editor activation
307        if (e.character == ' ' || e.character == SWT.CR) {
308          activateEditor(property, null);
309          return;
310        }
311        // DEL
312        if (e.keyCode == SWT.DEL) {
313          e.doit = false;
314          property.setValue(Property.UNKNOWN_VALUE);
315          return;
316        }
317        // send to editor
318        property.getEditor().keyDown(this, property, e);
319      } catch (Throwable ex) {
320        DesignerPlugin.log(ex);
321      }
322    }
323  }
324
325  /**
326   * @return <code>true</code> if given {@link KeyEvent} was navigation event, so new
327   *         {@link PropertyInfo} was selected.
328   */
329  public boolean navigate(KeyEvent e) {
330    int index = m_properties.indexOf(m_activePropertyInfo);
331    Rectangle clientArea = getClientArea();
332    //
333    int newIndex = index;
334    if (e.keyCode == SWT.HOME) {
335      newIndex = 0;
336    } else if (e.keyCode == SWT.END) {
337      newIndex = m_properties.size() - 1;
338    } else if (e.keyCode == SWT.PAGE_UP) {
339      newIndex = Math.max(index - m_page + 1, 0);
340    } else if (e.keyCode == SWT.PAGE_DOWN) {
341      newIndex = Math.min(index + m_page - 1, m_properties.size() - 1);
342    } else if (e.keyCode == SWT.ARROW_UP) {
343      newIndex = Math.max(index - 1, 0);
344    } else if (e.keyCode == SWT.ARROW_DOWN) {
345      newIndex = Math.min(index + 1, m_properties.size() - 1);
346    }
347    // activate new property
348    if (newIndex != index && newIndex < m_properties.size()) {
349      setActivePropertyInfo(m_properties.get(newIndex));
350      // check for scrolling
351      int y = m_rowHeight * (newIndex - m_selection);
352      if (y < 0) {
353        m_selection = newIndex;
354        configureScrolling();
355      } else if (y + m_rowHeight > clientArea.height) {
356        m_selection = newIndex - m_page + 1;
357        configureScrolling();
358      }
359      // repaint
360      redraw();
361      return true;
362    }
363    // no navigation change
364    return false;
365  }
366
367  ////////////////////////////////////////////////////////////////////////////
368  //
369  // Events: mouse
370  //
371  ////////////////////////////////////////////////////////////////////////////
372  private boolean m_splitterResizing;
373  /**
374   * We do expand/collapse on to events: click on state sign and on double click. But when we double
375   * click on state sign, we will have <em>two</em> events, so we should ignore double click.
376   */
377  private long m_lastExpandCollapseTime;
378
379  /**
380   * Handles {@link SWT#MouseDown} event.
381   */
382  private void handleMouseDown(MouseEvent event) {
383    m_splitterResizing = event.button == 1 && m_properties != null && isLocationSplitter(event.x);
384    // click in property
385    if (!m_splitterResizing && m_properties != null) {
386      int propertyIndex = getPropertyIndex(event.y);
387      if (propertyIndex >= m_properties.size()) {
388        return;
389      }
390      // prepare property
391      setActivePropertyInfo(m_properties.get(propertyIndex));
392      Property property = m_activePropertyInfo.getProperty();
393      // de-activate current editor
394      deactivateEditor(true);
395      redraw();
396      // activate editor
397      if (isLocationValue(event.x)) {
398        activateEditor(property, getValueRelativeLocation(event.x, event.y));
399      }
400    }
401  }
402
403  /**
404   * Handles {@link SWT#MouseUp} event.
405   */
406  private void handleMouseUp(MouseEvent event) {
407    if (event.button == 1) {
408      // resize splitter
409      if (m_splitterResizing) {
410        m_splitterResizing = false;
411        return;
412      }
413      // if out of bounds, then ignore
414      if (!getClientArea().contains(event.x, event.y)) {
415        return;
416      }
417      // update
418      if (m_properties != null) {
419        int index = getPropertyIndex(event.y);
420        if (index < m_properties.size()) {
421          PropertyInfo propertyInfo = m_properties.get(index);
422          // check for expand/collapse
423          if (isLocationState(propertyInfo, event.x)) {
424            try {
425              m_lastExpandCollapseTime = System.currentTimeMillis();
426              propertyInfo.flip();
427              configureScrolling();
428            } catch (Throwable e) {
429              DesignerPlugin.log(e);
430            }
431          }
432        }
433      }
434    }
435  }
436
437  /**
438   * Handles {@link SWT#MouseDoubleClick} event.
439   */
440  private void handleMouseDoubleClick(MouseEvent event) {
441    if (System.currentTimeMillis() - m_lastExpandCollapseTime > getDisplay().getDoubleClickTime()) {
442      try {
443        if (m_activePropertyInfo != null) {
444          if (m_activePropertyInfo.isComplex()) {
445            m_activePropertyInfo.flip();
446            configureScrolling();
447          } else {
448            Property property = m_activePropertyInfo.getProperty();
449            property.getEditor().doubleClick(property, getValueRelativeLocation(event.x, event.y));
450          }
451        }
452      } catch (Throwable e) {
453        handleException(e);
454      }
455    }
456  }
457
458  /**
459   * Handles {@link SWT#MouseMove} event.
460   */
461  private void handleMouseMove(MouseEvent event) {
462    int x = event.x;
463    // resize splitter
464    if (m_splitterResizing) {
465      m_splitter = x;
466      configureSplitter();
467      redraw();
468      return;
469    }
470    // if out of bounds, then ignore
471    if (!getClientArea().contains(event.x, event.y)) {
472      return;
473    }
474    // update
475    if (m_properties != null) {
476      // update cursor
477      if (isLocationSplitter(x)) {
478        setCursor(ICursorConstants.SIZEWE);
479      } else {
480        setCursor(null);
481      }
482      // update tooltip helper
483      updateTooltip(event);
484    } else {
485      updateTooltipNoProperty();
486    }
487  }
488
489  /**
490   * Updates {@link PropertyTableTooltipHelper}.
491   */
492  private void updateTooltip(MouseEvent event) {
493    int x = event.x;
494    int propertyIndex = getPropertyIndex(event.y);
495    //
496    if (propertyIndex < m_properties.size()) {
497      PropertyInfo propertyInfo = m_properties.get(propertyIndex);
498      Property property = propertyInfo.getProperty();
499      int y = (propertyIndex - m_selection) * m_rowHeight;
500      // check for title
501      {
502        int titleX = getTitleTextX(propertyInfo);
503        int titleRight = m_splitter - 2;
504        if (titleX <= x && x < titleRight) {
505          m_tooltipHelper.update(property, true, false, titleX, titleRight, y, m_rowHeight);
506          return;
507        }
508      }
509      // check for value
510      {
511        int valueX = m_splitter + 3;
512        if (x > valueX) {
513          m_tooltipHelper.update(
514              property,
515              false,
516              true,
517              valueX,
518              getClientArea().width,
519              y,
520              m_rowHeight);
521        }
522      }
523    } else {
524      updateTooltipNoProperty();
525    }
526  }
527
528  private void updateTooltipNoProperty() {
529    m_tooltipHelper.update(null, false, false, 0, 0, 0, 0);
530  }
531
532  ////////////////////////////////////////////////////////////////////////////
533  //
534  // Editor
535  //
536  ////////////////////////////////////////////////////////////////////////////
537  private PropertyInfo m_activePropertyInfo;
538  private String m_activePropertyId;
539  private PropertyEditor m_activeEditor;
540
541  /**
542   * Tries to activate editor for {@link PropertyInfo} under cursor.
543   *
544   * @param location
545   *          the mouse location, if editor is activated using mouse click, or <code>null</code> if
546   *          it is activated using keyboard.
547   */
548  public void activateEditor(Property property, Point location) {
549    try {
550      // de-activate old editor
551      deactivateEditor(true);
552      // activate editor
553      PropertyEditor editor = property.getEditor();
554      try {
555        if (editor.activate(this, property, location)) {
556          m_activeEditor = editor;
557        }
558      } catch (Throwable e) {
559        handleException(e);
560      }
561      // set bounds
562      setActiveEditorBounds();
563    } catch (NullPointerException e) {
564        if (EnvironmentUtils.IS_MAC) {
565            // Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=388574
566            PropertyEditor editor = property.getEditor();
567            PropertyEditorPresentation presentation = editor.getPresentation();
568            if (presentation instanceof ButtonPropertyEditorPresentation) {
569                ButtonPropertyEditorPresentation button =
570                        (ButtonPropertyEditorPresentation) presentation;
571                try {
572                    button.click(this, property);
573                } catch (Exception ex) {
574                    deactivateEditor(false);
575                    handleException(e);
576                }
577                return;
578            }
579        }
580        DesignerPlugin.log(e);
581    } catch (Throwable e) {
582      DesignerPlugin.log(e);
583    }
584  }
585
586  /**
587   * Deactivates current {@link PropertyEditor}.
588   */
589  public void deactivateEditor(boolean save) {
590    if (m_activeEditor != null) {
591      PropertyEditor activeEditor = m_activeEditor;
592      m_activeEditor = null;
593      if (m_activePropertyInfo != null && m_activePropertyInfo.m_property != null) {
594        activeEditor.deactivate(this, m_activePropertyInfo.m_property, save);
595      }
596    }
597  }
598
599  /**
600   * Sets correct bounds for active editor, for example we need update bounds after scrolling.
601   */
602  private void setActiveEditorBounds() {
603    if (m_activeEditor != null) {
604      int index = m_properties.indexOf(m_activePropertyInfo);
605      if (index == -1) {
606        // it is possible that active property was hidden because its parent was collapsed
607        deactivateEditor(true);
608      } else {
609        // prepare bounds for editor
610        Rectangle bounds;
611        {
612          Rectangle clientArea = getClientArea();
613          int x = m_splitter + 1;
614          int width = clientArea.width - x - MARGIN_RIGHT;
615          int y = m_rowHeight * (index - m_selection) + 1;
616          int height = m_rowHeight - 1;
617          bounds = new Rectangle(x, y, width, height);
618        }
619        // update bounds using presentation
620        {
621          PropertyEditorPresentation presentation = m_activeEditor.getPresentation();
622          if (presentation != null) {
623            int presentationWidth =
624                presentation.show(
625                    this,
626                    m_activePropertyInfo.m_property,
627                    bounds.x,
628                    bounds.y,
629                    bounds.width,
630                    bounds.height);
631            bounds.width -= presentationWidth;
632          }
633        }
634        // set editor bounds
635        m_activeEditor.setBounds(bounds);
636      }
637    }
638  }
639
640  ////////////////////////////////////////////////////////////////////////////
641  //
642  // Exceptions
643  //
644  ////////////////////////////////////////////////////////////////////////////
645  private IPropertyExceptionHandler m_exceptionHandler;
646
647  /**
648   * Sets {@link IPropertyExceptionHandler} for handling all exceptions.
649   */
650  public void setExceptionHandler(IPropertyExceptionHandler exceptionHandler) {
651    m_exceptionHandler = exceptionHandler;
652  }
653
654  /**
655   * Handles given {@link Throwable}.<br>
656   * Right now it just logs it, but in future we can open some dialog here.
657   */
658  public void handleException(Throwable e) {
659    m_exceptionHandler.handle(e);
660  }
661
662  ////////////////////////////////////////////////////////////////////////////
663  //
664  // Scrolling
665  //
666  ////////////////////////////////////////////////////////////////////////////
667  /**
668   * Configures vertical {@link ScrollBar}.
669   */
670  private void configureScrolling() {
671    ScrollBar verticalBar = getVerticalBar();
672    if (m_properties == null) {
673      verticalBar.setEnabled(false);
674    } else {
675      m_page = getClientArea().height / m_rowHeight;
676      m_selection = Math.max(0, Math.min(m_properties.size() - m_page, m_selection));
677      verticalBar.setValues(m_selection, 0, m_properties.size(), m_page, 1, m_page);
678      // enable/disable scrolling
679      if (m_properties.size() <= m_page) {
680        verticalBar.setEnabled(false);
681      } else {
682        verticalBar.setEnabled(true);
683      }
684    }
685    // redraw, we reconfigure scrolling only if list of properties was changed, so we should redraw
686    redraw();
687  }
688
689  ////////////////////////////////////////////////////////////////////////////
690  //
691  // Location/size utils
692  //
693  ////////////////////////////////////////////////////////////////////////////
694  /**
695   * @return the <code>X</code> position for first pixel of {@link PropertyInfo} title (location of
696   *         state image).
697   */
698  private int getTitleX(PropertyInfo propertyInfo) {
699    return MARGIN_LEFT + getLevelIndent() * propertyInfo.getLevel();
700  }
701
702  /**
703   * @return the <code>X</code> position for first pixel of {@link PropertyInfo} title text.
704   */
705  private int getTitleTextX(PropertyInfo propertyInfo) {
706    return getTitleX(propertyInfo) + getLevelIndent();
707  }
708
709  /**
710   * @return the indentation for single level.
711   */
712  private int getLevelIndent() {
713    return m_stateWidth + STATE_IMAGE_MARGIN_RIGHT;
714  }
715
716  /**
717   * Checks horizontal splitter value to boundary values.
718   */
719  private void configureSplitter() {
720    Rectangle clientArea = getClientArea();
721    // check title width
722    if (m_splitter < MIN_COLUMN_WIDTH) {
723      m_splitter = MIN_COLUMN_WIDTH;
724    }
725    // check value width
726    if (clientArea.width - m_splitter < MIN_COLUMN_WIDTH) {
727      m_splitter = clientArea.width - MIN_COLUMN_WIDTH;
728    }
729  }
730
731  /**
732   * @return the index in {@link #m_properties} corresponding given <code>y</code> location.
733   */
734  private int getPropertyIndex(int y) {
735    return m_selection + y / m_rowHeight;
736  }
737
738  /**
739   * @return <code>true</code> if given <code>x</code> coordinate is on state (plus/minus) image.
740   */
741  private boolean isLocationState(PropertyInfo propertyInfo, int x) {
742    int levelX = getTitleX(propertyInfo);
743    return propertyInfo.isComplex() && levelX <= x && x <= levelX + m_stateWidth;
744  }
745
746  /**
747   * Returns <code>true</code> if <code>x</code> coordinate is on splitter.
748   */
749  private boolean isLocationSplitter(int x) {
750    return Math.abs(m_splitter - x) < 2;
751  }
752
753  /**
754   * @return <code>true</code> if given <code>x</code> is on value part of property.
755   */
756  private boolean isLocationValue(int x) {
757    return x > m_splitter + 2;
758  }
759
760  /**
761   * @param x
762   *          the {@link PropertyTable} relative coordinate.
763   * @param y
764   *          the {@link PropertyTable} relative coordinate.
765   *
766   * @return the location relative to the value part of property.
767   */
768  private Point getValueRelativeLocation(int x, int y) {
769    return new Point(x - (m_splitter + 2), y - m_rowHeight * getPropertyIndex(y));
770  }
771
772  ////////////////////////////////////////////////////////////////////////////
773  //
774  // Access
775  //
776  ////////////////////////////////////////////////////////////////////////////
777  /**
778   * Shows or hides {@link Property}-s with {@link PropertyCategory#ADVANCED}.
779   */
780  public void setShowAdvancedProperties(boolean showAdvancedProperties) {
781    m_showAdvancedProperties = showAdvancedProperties;
782    setInput0();
783  }
784
785  /**
786   * Sets the array of {@link Property}'s to display/edit.
787   */
788  public void setInput(Property[] properties) {
789    m_rawProperties = properties;
790    setInput0();
791  }
792
793  private void setInput0() {
794    // send "hide" to all PropertyEditorPresentation's
795    if (m_properties != null) {
796      for (PropertyInfo propertyInfo : m_properties) {
797        Property property = propertyInfo.getProperty();
798        // hide presentation
799        {
800          PropertyEditorPresentation presentation = property.getEditor().getPresentation();
801          if (presentation != null) {
802            presentation.hide(this, property);
803          }
804        }
805      }
806    }
807    // set new properties
808    if (m_rawProperties == null || m_rawProperties.length == 0) {
809      deactivateEditor(false);
810      m_properties = Lists.newArrayList();
811    } else {
812      try {
813        // add PropertyInfo for each Property
814        m_properties = Lists.newArrayList();
815        for (Property property : m_rawProperties) {
816          if (rawProperties_shouldShow(property)) {
817            PropertyInfo propertyInfo = new PropertyInfo(property);
818            m_properties.add(propertyInfo);
819          }
820        }
821        // expand properties using history
822        while (true) {
823          boolean expanded = false;
824          List<PropertyInfo> currentProperties = Lists.newArrayList(m_properties);
825          for (PropertyInfo propertyInfo : currentProperties) {
826            expanded |= propertyInfo.expandFromHistory();
827          }
828          // stop
829          if (!expanded) {
830            break;
831          }
832        }
833      } catch (Throwable e) {
834        DesignerPlugin.log(e);
835      }
836    }
837    // update active property
838    if (m_activePropertyId != null) {
839      PropertyInfo newActivePropertyInfo = null;
840      // try to find corresponding PropertyInfo
841      if (m_properties != null) {
842        for (PropertyInfo propertyInfo : m_properties) {
843          if (propertyInfo.m_id.equals(m_activePropertyId)) {
844            newActivePropertyInfo = propertyInfo;
845            break;
846          }
847        }
848      }
849      // set new PropertyInfo
850      setActivePropertyInfo(newActivePropertyInfo);
851    }
852    // update scroll bar
853    configureScrolling();
854  }
855
856  /**
857   * @return <code>true</code> if given {@link Property} should be displayed.
858   */
859  private boolean rawProperties_shouldShow(Property property) throws Exception {
860    PropertyCategory category = getCategory(property);
861    // filter out hidden properties
862    if (category.isHidden()) {
863      return false;
864    }
865    // filter out advanced properties
866    if (category.isAdvanced()) {
867      if (!m_showAdvancedProperties && !property.isModified()) {
868        return false;
869      }
870    }
871    if (category.isAdvancedReally()) {
872      return m_showAdvancedProperties;
873    }
874    // OK
875    return true;
876  }
877
878  /**
879   * Activates given {@link Property}.
880   */
881  public void setActiveProperty(Property property) {
882    for (PropertyInfo propertyInfo : m_properties) {
883      if (propertyInfo.m_property == property) {
884        setActivePropertyInfo(propertyInfo);
885        break;
886      }
887    }
888  }
889
890  ////////////////////////////////////////////////////////////////////////////
891  //
892  // Access: only for testing
893  //
894  ////////////////////////////////////////////////////////////////////////////
895  /**
896   * @return the count of properties in "expanded" list.
897   */
898  public int forTests_getPropertiesCount() {
899    return m_properties.size();
900  }
901
902  /**
903   * @return the {@link Property} from "expanded" list.
904   */
905  public Property forTests_getProperty(int index) {
906    return m_properties.get(index).getProperty();
907  }
908
909  /**
910   * Expands the {@link PropertyInfo} with given index.
911   */
912  public void forTests_expand(int index) throws Exception {
913    m_properties.get(index).expand();
914  }
915
916  /**
917   * @return the position of splitter.
918   */
919  public int forTests_getSplitter() {
920    return m_splitter;
921  }
922
923  /**
924   * @return the location of state image (plus/minus) for given {@link Property}.
925   */
926  public Point forTests_getStateLocation(Property property) {
927    PropertyInfo propertyInfo = getPropertyInfo(property);
928    if (propertyInfo != null) {
929      int index = m_properties.indexOf(propertyInfo);
930      int x = getTitleX(propertyInfo);
931      int y = m_rowHeight * (index - m_selection) + 1;
932      return new Point(x, y);
933    }
934    return null;
935  }
936
937  /**
938   * @return the location of state image (plus/minus) for given {@link Property}.
939   */
940  public Point forTests_getValueLocation(Property property) {
941    PropertyInfo propertyInfo = getPropertyInfo(property);
942    if (propertyInfo != null) {
943      int index = m_properties.indexOf(propertyInfo);
944      int x = m_splitter + 5;
945      int y = m_rowHeight * (index - m_selection) + 1;
946      return new Point(x, y);
947    }
948    return null;
949  }
950
951  /**
952   * @return the active {@link PropertyEditor}.
953   */
954  public PropertyEditor forTests_getActiveEditor() {
955    return m_activeEditor;
956  }
957
958  /**
959   * @return the {@link PropertyCategory} that is used by this {@link PropertyTable} to display.
960   */
961  public PropertyCategory forTests_getCategory(Property property) {
962    return getCategory(property);
963  }
964
965  /**
966   * @return the {@link PropertyInfo}for given {@link Property}.
967   */
968  private PropertyInfo getPropertyInfo(Property property) {
969    for (PropertyInfo propertyInfo : m_properties) {
970      if (propertyInfo.getProperty() == property) {
971        return propertyInfo;
972      }
973    }
974    return null;
975  }
976
977  ////////////////////////////////////////////////////////////////////////////
978  //
979  // ISelectionProvider
980  //
981  ////////////////////////////////////////////////////////////////////////////
982  private final List<ISelectionChangedListener> m_selectionListeners = Lists.newArrayList();
983
984  @Override
985public void addSelectionChangedListener(ISelectionChangedListener listener) {
986    if (!m_selectionListeners.contains(listener)) {
987      m_selectionListeners.add(listener);
988    }
989  }
990
991  @Override
992public void removeSelectionChangedListener(ISelectionChangedListener listener) {
993    m_selectionListeners.add(listener);
994  }
995
996  @Override
997public ISelection getSelection() {
998    if (m_activePropertyInfo != null) {
999      return new StructuredSelection(m_activePropertyInfo.getProperty());
1000    } else {
1001      return StructuredSelection.EMPTY;
1002    }
1003  }
1004
1005  @Override
1006  public void setSelection(ISelection selection) {
1007    throw new UnsupportedOperationException();
1008  }
1009
1010  /**
1011   * Sets the new active {@link PropertyInfo} and sends event to {@link ISelectionChangedListener}
1012   * 's.
1013   */
1014  private void setActivePropertyInfo(PropertyInfo activePropertyInfo) {
1015    m_activePropertyInfo = activePropertyInfo;
1016    // update m_activePropertyId only when really select property,
1017    // not just remove selection because there are no corresponding property for old active
1018    // so, later for some other component, if we don't select other property, old active will be selected
1019    if (m_activePropertyInfo != null) {
1020      m_activePropertyId = m_activePropertyInfo.m_id;
1021    }
1022    // make sure that active property is visible
1023    if (m_activePropertyInfo != null) {
1024      int row = m_properties.indexOf(m_activePropertyInfo);
1025      if (m_selection <= row && row < m_selection + m_page) {
1026      } else {
1027        m_selection = row;
1028        configureScrolling();
1029      }
1030    }
1031    // send events
1032    SelectionChangedEvent selectionEvent = new SelectionChangedEvent(this, getSelection());
1033    for (ISelectionChangedListener listener : m_selectionListeners) {
1034      listener.selectionChanged(selectionEvent);
1035    }
1036    // re-draw
1037    redraw();
1038  }
1039
1040  ////////////////////////////////////////////////////////////////////////////
1041  //
1042  // Painting
1043  //
1044  ////////////////////////////////////////////////////////////////////////////
1045  private boolean m_painting;
1046  private Font m_baseFont;
1047  private Font m_boldFont;
1048  private Font m_italicFont;
1049
1050  /**
1051   * Handles {@link SWT#Paint} event.
1052   */
1053  private void handlePaint(GC gc, int x, int y, int width, int height) {
1054    // sometimes we disable Eclipse Shell to prevent user actions, but we do this for short time
1055    if (!isEnabled()) {
1056      return;
1057    }
1058    // prevent recursion
1059    if (m_painting) {
1060      return;
1061    }
1062    m_painting = true;
1063    //
1064    try {
1065      setActiveEditorBounds();
1066      // prepare buffered image
1067      if (m_bufferedImage == null || m_bufferedImage.isDisposed()) {
1068        Point size = getSize();
1069        m_bufferedImage = new Image(DesignerPlugin.getStandardDisplay(), size.x, size.y);
1070      }
1071      // prepare buffered GC
1072      GC bufferedGC = null;
1073      try {
1074        // perform some drawing
1075        {
1076          bufferedGC = new GC(m_bufferedImage);
1077          bufferedGC.setClipping(x, y, width, height);
1078          bufferedGC.setBackground(gc.getBackground());
1079          bufferedGC.setForeground(gc.getForeground());
1080          bufferedGC.setFont(gc.getFont());
1081          bufferedGC.setLineStyle(gc.getLineStyle());
1082          bufferedGC.setLineWidth(gc.getLineWidth());
1083        }
1084        // fill client area
1085        {
1086          Rectangle clientArea = getClientArea();
1087          bufferedGC.setBackground(COLOR_BACKGROUND);
1088          bufferedGC.fillRectangle(clientArea);
1089        }
1090        // draw content
1091        if (m_properties == null || m_properties.size() == 0) {
1092          drawEmptyContent(bufferedGC);
1093        } else {
1094          drawContent(bufferedGC);
1095        }
1096      } finally {
1097        // flush image
1098        if (bufferedGC != null) {
1099          bufferedGC.dispose();
1100        }
1101      }
1102      gc.drawImage(m_bufferedImage, 0, 0);
1103    } finally {
1104      m_painting = false;
1105    }
1106  }
1107
1108  /**
1109   * Draws content when there are no properties.
1110   */
1111  private void drawEmptyContent(GC gc) {
1112    Rectangle area = getClientArea();
1113    // draw message
1114    gc.setForeground(COLOR_NO_PROPERTIES);
1115    DrawUtils.drawStringCHCV(
1116        gc,
1117        "<No properties>",
1118        0,
1119        0,
1120        area.width,
1121        area.height);
1122  }
1123
1124  /**
1125   * Draws all {@link PropertyInfo}'s, separators, etc.
1126   */
1127  private void drawContent(GC gc) {
1128    Rectangle clientArea = getClientArea();
1129    // prepare fonts
1130    m_baseFont = gc.getFont();
1131    m_boldFont = DrawUtils.getBoldFont(m_baseFont);
1132    m_italicFont = DrawUtils.getItalicFont(m_baseFont);
1133    // show presentations
1134    int[] presentationsWidth = showPresentations(clientArea);
1135    // draw properties
1136    {
1137      int y = clientArea.y - m_rowHeight * m_selection;
1138      for (int i = 0; i < m_properties.size(); i++) {
1139        // skip, if not visible yet
1140        if (y + m_rowHeight < 0) {
1141          y += m_rowHeight;
1142          continue;
1143        }
1144        // stop, if already invisible
1145        if (y > clientArea.height) {
1146          break;
1147        }
1148        // draw single property
1149        {
1150          PropertyInfo propertyInfo = m_properties.get(i);
1151          drawProperty(gc, propertyInfo, y + 1, m_rowHeight - 1, clientArea.width
1152              - presentationsWidth[i]);
1153          y += m_rowHeight;
1154        }
1155        // draw row separator
1156        gc.setForeground(COLOR_LINE);
1157        gc.drawLine(0, y, clientArea.width, y);
1158      }
1159    }
1160    // draw expand line
1161    drawExpandLines(gc, clientArea);
1162    // draw rectangle around table
1163    gc.setForeground(COLOR_LINE);
1164    gc.drawRectangle(0, 0, clientArea.width - 1, clientArea.height - 1);
1165    // draw splitter
1166    gc.setForeground(COLOR_LINE);
1167    gc.drawLine(m_splitter, 0, m_splitter, clientArea.height);
1168    // dispose font
1169    m_boldFont.dispose();
1170    m_italicFont.dispose();
1171  }
1172
1173  /**
1174   * Shows {@link PropertyEditorPresentation}'s for all {@link Property}'s, i.e. updates also their
1175   * bounds. So, some {@link PropertyEditorPresentation}'s become invisible because they are moved
1176   * above or below visible client area.
1177   *
1178   * @return the array of width for each {@link PropertyEditorPresentation}'s, consumed on the
1179   *         right.
1180   */
1181  private int[] showPresentations(Rectangle clientArea) {
1182    int[] presentationsWidth = new int[m_properties.size()];
1183    // prepare value rectangle
1184    int x = m_splitter + 4;
1185    int w = clientArea.width - x - MARGIN_RIGHT;
1186    // show presentation's for all properties
1187    int y = clientArea.y - m_rowHeight * m_selection;
1188    for (int i = 0; i < m_properties.size(); i++) {
1189      PropertyInfo propertyInfo = m_properties.get(i);
1190      Property property = propertyInfo.getProperty();
1191      PropertyEditorPresentation presentation = property.getEditor().getPresentation();
1192      if (presentation != null) {
1193        presentationsWidth[i] = presentation.show(this, property, x, y + 1, w, m_rowHeight - 1);
1194      }
1195      y += m_rowHeight;
1196    }
1197    return presentationsWidth;
1198  }
1199
1200  /**
1201   * Draws lines from expanded complex property to its last sub-property.
1202   */
1203  private void drawExpandLines(GC gc, Rectangle clientArea) {
1204    int height = m_rowHeight - 1;
1205    int xOffset = m_plusImage.getBounds().width / 2;
1206    int yOffset = (height - m_plusImage.getBounds().width) / 2;
1207    //
1208    int y = clientArea.y - m_selection * m_rowHeight;
1209    gc.setForeground(COLOR_COMPLEX_LINE);
1210    for (int i = 0; i < m_properties.size(); i++) {
1211      PropertyInfo propertyInfo = m_properties.get(i);
1212      //
1213      if (propertyInfo.isExpanded()) {
1214        int index = m_properties.indexOf(propertyInfo);
1215        // prepare index of last sub-property
1216        int index2 = index;
1217        for (; index2 < m_properties.size(); index2++) {
1218          PropertyInfo nextPropertyInfo = m_properties.get(index2);
1219          if (nextPropertyInfo != propertyInfo
1220              && nextPropertyInfo.getLevel() <= propertyInfo.getLevel()) {
1221            break;
1222          }
1223        }
1224        index2--;
1225        // draw line if there are children
1226        if (index2 > index) {
1227          int x = getTitleX(propertyInfo) + xOffset;
1228          int y1 = y + height - yOffset;
1229          int y2 = y + m_rowHeight * (index2 - index) + m_rowHeight / 2;
1230          gc.drawLine(x, y1, x, y2);
1231          gc.drawLine(x, y2, x + m_rowHeight / 3, y2);
1232        }
1233      }
1234      //
1235      y += m_rowHeight;
1236    }
1237  }
1238
1239  /**
1240   * Draws single {@link PropertyInfo} in specified rectangle.
1241   */
1242  private void drawProperty(GC gc, PropertyInfo propertyInfo, int y, int height, int width) {
1243    // remember colors
1244    Color oldBackground = gc.getBackground();
1245    Color oldForeground = gc.getForeground();
1246    // draw property
1247    try {
1248      Property property = propertyInfo.getProperty();
1249      boolean isActiveProperty =
1250          m_activePropertyInfo != null && m_activePropertyInfo.getProperty() == property;
1251      // set background
1252    boolean modified = property.isModified();
1253    {
1254        if (isActiveProperty) {
1255          gc.setBackground(COLOR_PROPERTY_BG_SELECTED);
1256        } else {
1257          if (modified) {
1258            gc.setBackground(COLOR_PROPERTY_BG_MODIFIED);
1259          } else {
1260            gc.setBackground(COLOR_PROPERTY_BG);
1261          }
1262        }
1263        gc.fillRectangle(0, y, width, height);
1264      }
1265      // draw state image
1266      if (propertyInfo.isShowComplex()) {
1267        Image stateImage = propertyInfo.isExpanded() ? m_minusImage : m_plusImage;
1268        DrawUtils.drawImageCV(gc, stateImage, getTitleX(propertyInfo), y, height);
1269      }
1270      // draw title
1271      {
1272        // configure GC
1273        {
1274          gc.setForeground(COLOR_PROPERTY_FG_TITLE);
1275          // check category
1276          if (getCategory(property).isAdvanced()) {
1277            gc.setForeground(COLOR_PROPERTY_FG_ADVANCED);
1278            gc.setFont(m_italicFont);
1279          } else if (getCategory(property).isPreferred() || getCategory(property).isSystem()) {
1280            gc.setFont(m_boldFont);
1281          }
1282          // check for active
1283          if (isActiveProperty) {
1284            gc.setForeground(COLOR_PROPERTY_FG_SELECTED);
1285          }
1286        }
1287        // paint title
1288        int x = getTitleTextX(propertyInfo);
1289        DrawUtils.drawStringCV(gc, property.getTitle(), x, y, m_splitter - x, height);
1290      }
1291      // draw value
1292      {
1293        // configure GC
1294        gc.setFont(m_baseFont);
1295        if (!isActiveProperty) {
1296          gc.setForeground(COLOR_PROPERTY_FG_VALUE);
1297        }
1298        // prepare value rectangle
1299        int x = m_splitter + 4;
1300        int w = width - x - MARGIN_RIGHT;
1301        // paint value
1302
1303        // BEGIN ADT MODIFICATIONS
1304        if (!modified) {
1305            gc.setForeground(COLOR_PROPERTY_FG_DEFAULT);
1306        }
1307        // END ADT MODIFICATIONS
1308
1309        property.getEditor().paint(property, gc, x, y, w, height);
1310      }
1311    } catch (Throwable e) {
1312      DesignerPlugin.log(e);
1313    } finally {
1314      // restore colors
1315      gc.setBackground(oldBackground);
1316      gc.setForeground(oldForeground);
1317    }
1318  }
1319
1320  ////////////////////////////////////////////////////////////////////////////
1321  //
1322  // PropertyCategory
1323  //
1324  ////////////////////////////////////////////////////////////////////////////
1325  private PropertyCategoryProvider m_propertyCategoryProvider =
1326      PropertyCategoryProviders.fromProperty();
1327
1328  /**
1329   * Sets the {@link PropertyCategoryProvider} that can be used to tweak usual
1330   * {@link PropertyCategory}.
1331   */
1332  public void setPropertyCategoryProvider(PropertyCategoryProvider propertyCategoryProvider) {
1333    m_propertyCategoryProvider = propertyCategoryProvider;
1334  }
1335
1336  /**
1337   * @return the {@link PropertyCategory} that is used by this {@link PropertyTable} to display.
1338   */
1339  private PropertyCategory getCategory(Property property) {
1340    return m_propertyCategoryProvider.getCategory(property);
1341  }
1342
1343  ////////////////////////////////////////////////////////////////////////////
1344  //
1345  // PropertyInfo
1346  //
1347  ////////////////////////////////////////////////////////////////////////////
1348  /**
1349   * Class with information about single {@link Property}.
1350   *
1351   * @author scheglov_ke
1352   */
1353  private final class PropertyInfo {
1354    private final String m_id;
1355    private final int m_level;
1356    private final Property m_property;
1357    private final boolean m_stateComplex;
1358    private boolean m_stateExpanded;
1359    private List<PropertyInfo> m_children;
1360
1361    ////////////////////////////////////////////////////////////////////////////
1362    //
1363    // Constructor
1364    //
1365    ////////////////////////////////////////////////////////////////////////////
1366    public PropertyInfo(Property property) {
1367      this(property, "", 0);
1368    }
1369
1370    private PropertyInfo(Property property, String idPrefix, int level) {
1371      // BEGIN ADT MODIFICATIONS
1372      //m_id = idPrefix + "|" + property.getTitle();
1373      m_id = idPrefix + "|" + property.getName();
1374      // END ADT MODIFICATIONS
1375      m_level = level;
1376      m_property = property;
1377      m_stateComplex = property.getEditor() instanceof IComplexPropertyEditor;
1378    }
1379
1380    ////////////////////////////////////////////////////////////////////////////
1381    //
1382    // State
1383    //
1384    ////////////////////////////////////////////////////////////////////////////
1385    /**
1386     * @return <code>true</code> if this property is complex.
1387     */
1388    public boolean isComplex() {
1389      return m_stateComplex;
1390    }
1391
1392    public boolean isShowComplex() throws Exception {
1393      if (m_stateComplex) {
1394        prepareChildren();
1395        return m_children != null && !m_children.isEmpty();
1396      }
1397      return false;
1398    }
1399
1400    /**
1401     * @return <code>true</code> if this complex property is expanded.
1402     */
1403    public boolean isExpanded() {
1404      return m_stateExpanded;
1405    }
1406
1407    ////////////////////////////////////////////////////////////////////////////
1408    //
1409    // Access
1410    //
1411    ////////////////////////////////////////////////////////////////////////////
1412    /**
1413     * @return the level of this property, i.e. on which level of complex property it is located.
1414     */
1415    public int getLevel() {
1416      return m_level;
1417    }
1418
1419    /**
1420     * @return the {@link Property}.
1421     */
1422    public Property getProperty() {
1423      return m_property;
1424    }
1425
1426    /**
1427     * Flips collapsed/expanded state and adds/removes sub-properties.
1428     */
1429    public void flip() throws Exception {
1430      Assert.isTrue(m_stateComplex);
1431      if (m_stateExpanded) {
1432        collapse();
1433      } else {
1434        expand();
1435      }
1436    }
1437
1438    /**
1439     * Expands this property.
1440     */
1441    public void expand() throws Exception {
1442      Assert.isTrue(m_stateComplex);
1443      Assert.isTrue(!m_stateExpanded);
1444      //
1445      m_stateExpanded = true;
1446      m_expandedIds.add(m_id);
1447      // BEGIN ADT MODIFICATIONS
1448      if (m_collapsedIds != null) {
1449          m_collapsedIds.remove(m_id);
1450      }
1451      // END ADT MODIFICATIONS
1452      prepareChildren();
1453      //
1454      int index = m_properties.indexOf(this);
1455      addChildren(index + 1);
1456    }
1457
1458    /**
1459     * Collapses this property.
1460     */
1461    public void collapse() throws Exception {
1462      Assert.isTrue(m_stateComplex);
1463      Assert.isTrue(m_stateExpanded);
1464      //
1465      m_stateExpanded = false;
1466      m_expandedIds.remove(m_id);
1467      // BEGIN ADT MODIFICATIONS
1468      if (m_collapsedIds != null) {
1469          m_collapsedIds.add(m_id);
1470      }
1471      // END ADT MODIFICATIONS
1472      prepareChildren();
1473      //
1474      int index = m_properties.indexOf(this);
1475      removeChildren(index + 1);
1476    }
1477
1478    ////////////////////////////////////////////////////////////////////////////
1479    //
1480    // Internal
1481    //
1482    ////////////////////////////////////////////////////////////////////////////
1483    /**
1484     * Adds children properties.
1485     *
1486     * @return the index for new properties to add.
1487     */
1488    private int addChildren(int index) throws Exception {
1489      prepareChildren();
1490      for (PropertyInfo child : m_children) {
1491        // skip if should not display raw Property
1492        if (!rawProperties_shouldShow(child.m_property)) {
1493          continue;
1494        }
1495        // add child
1496        m_properties.add(index++, child);
1497        // add children of current child
1498        if (child.isExpanded()) {
1499          index = child.addChildren(index);
1500        }
1501      }
1502      return index;
1503    }
1504
1505    /**
1506     * Removes children properties.
1507     */
1508    private void removeChildren(int index) throws Exception {
1509      prepareChildren();
1510      for (PropertyInfo child : m_children) {
1511        // skip if should not display raw Property
1512        if (!rawProperties_shouldShow(child.m_property)) {
1513          continue;
1514        }
1515        // hide presentation
1516        {
1517          PropertyEditorPresentation presentation =
1518              child.getProperty().getEditor().getPresentation();
1519          if (presentation != null) {
1520            presentation.hide(PropertyTable.this, child.getProperty());
1521          }
1522        }
1523        // remove child
1524        m_properties.remove(index);
1525        // remove children of current child
1526        if (child.isExpanded()) {
1527          child.removeChildren(index);
1528        }
1529      }
1530    }
1531
1532    /**
1533     * Prepares children {@link PropertyInfo}'s, for sub-properties.
1534     */
1535    private void prepareChildren() throws Exception {
1536      if (m_children == null) {
1537        m_children = Lists.newArrayList();
1538        for (Property subProperty : getSubProperties()) {
1539          PropertyInfo subPropertyInfo = createSubPropertyInfo(subProperty);
1540          m_children.add(subPropertyInfo);
1541        }
1542      }
1543    }
1544
1545    private PropertyInfo createSubPropertyInfo(Property subProperty) {
1546      return new PropertyInfo(subProperty, m_id, m_level + 1);
1547    }
1548
1549    private Property[] getSubProperties() throws Exception {
1550      IComplexPropertyEditor complexEditor = (IComplexPropertyEditor) m_property.getEditor();
1551      List<Property> subProperties = Lists.newArrayList();
1552      for (Property subProperty : complexEditor.getProperties(m_property)) {
1553        if (getCategory(subProperty).isHidden() && !subProperty.isModified()) {
1554          // skip hidden properties
1555          continue;
1556        }
1557        subProperties.add(subProperty);
1558      }
1559      return subProperties.toArray(new Property[subProperties.size()]);
1560    }
1561
1562    ////////////////////////////////////////////////////////////////////////////
1563    //
1564    // Persistent expanding support
1565    //
1566    ////////////////////////////////////////////////////////////////////////////
1567    /**
1568     * @return <code>true</code> if this {@link PropertyInfo} was expanded from history.
1569     */
1570    public boolean expandFromHistory() throws Exception {
1571      if (isComplex() && !isExpanded() && m_expandedIds.contains(m_id)) {
1572        expand();
1573        return true;
1574      }
1575      // BEGIN ADT MODIFICATIONS
1576      if (m_collapsedIds != null && isComplex() && !isExpanded()
1577              && !m_collapsedIds.contains(m_id)) {
1578          expand();
1579          return true;
1580      }
1581      // END ADT MODIFICATIONS
1582      return false;
1583    }
1584  }
1585
1586  // BEGIN ADT MODIFICATIONS
1587  /** Collapse all top-level properties */
1588  public void collapseAll() {
1589      try {
1590          m_lastExpandCollapseTime = System.currentTimeMillis();
1591          if (m_collapsedIds != null) {
1592              m_collapsedIds.addAll(m_expandedIds);
1593          }
1594          m_expandedIds.clear();
1595          setInput(m_rawProperties);
1596          redraw();
1597      } catch (Throwable e) {
1598          DesignerPlugin.log(e);
1599      }
1600  }
1601
1602  /** Expand all top-level properties */
1603  public void expandAll() {
1604      try {
1605          m_lastExpandCollapseTime = System.currentTimeMillis();
1606          if (m_collapsedIds != null) {
1607              m_collapsedIds.clear();
1608          }
1609          m_expandedIds.clear();
1610          for (PropertyInfo info : m_properties) {
1611              if (info.m_stateComplex) {
1612                  m_expandedIds.add(info.m_id);
1613              }
1614          }
1615          setInput(m_rawProperties);
1616          redraw();
1617      } catch (Throwable e) {
1618          DesignerPlugin.log(e);
1619      }
1620  }
1621  // END ADT MODIFICATIONS
1622}