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