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.EnvironmentUtils;
     42 import org.eclipse.wb.internal.core.model.property.Property;
     43 import org.eclipse.wb.internal.core.model.property.category.PropertyCategory;
     44 import org.eclipse.wb.internal.core.model.property.category.PropertyCategoryProvider;
     45 import org.eclipse.wb.internal.core.model.property.category.PropertyCategoryProviders;
     46 import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor;
     47 import org.eclipse.wb.internal.core.model.property.editor.complex.IComplexPropertyEditor;
     48 import org.eclipse.wb.internal.core.model.property.editor.presentation.ButtonPropertyEditorPresentation;
     49 import org.eclipse.wb.internal.core.model.property.editor.presentation.PropertyEditorPresentation;
     50 import org.eclipse.wb.internal.core.utils.check.Assert;
     51 import org.eclipse.wb.internal.core.utils.ui.DrawUtils;
     52 
     53 import java.util.Collection;
     54 import java.util.List;
     55 import 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  */
     64 public 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
    985 public void addSelectionChangedListener(ISelectionChangedListener listener) {
    986     if (!m_selectionListeners.contains(listener)) {
    987       m_selectionListeners.add(listener);
    988     }
    989   }
    990 
    991   @Override
    992 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
    993     m_selectionListeners.add(listener);
    994   }
    995 
    996   @Override
    997 public 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 }