• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
setDefaultCollapsedNames(Collection<String> names)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   ////////////////////////////////////////////////////////////////////////////
PropertyTable(Composite parent, int style)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    */
hookControlEvents()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    */
disposeBufferedImage()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    */
handleResize()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    */
handleVerticalScrolling()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    */
handleKeyDown(KeyEvent e)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    */
navigate(KeyEvent e)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    */
handleMouseDown(MouseEvent event)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    */
handleMouseUp(MouseEvent event)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    */
handleMouseDoubleClick(MouseEvent event)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    */
handleMouseMove(MouseEvent event)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    */
updateTooltip(MouseEvent event)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 
updateTooltipNoProperty()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    */
activateEditor(Property property, Point location)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    */
deactivateEditor(boolean save)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    */
setActiveEditorBounds()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    */
setExceptionHandler(IPropertyExceptionHandler exceptionHandler)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    */
handleException(Throwable e)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    */
configureScrolling()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    */
getTitleX(PropertyInfo propertyInfo)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    */
getTitleTextX(PropertyInfo propertyInfo)705   private int getTitleTextX(PropertyInfo propertyInfo) {
706     return getTitleX(propertyInfo) + getLevelIndent();
707   }
708 
709   /**
710    * @return the indentation for single level.
711    */
getLevelIndent()712   private int getLevelIndent() {
713     return m_stateWidth + STATE_IMAGE_MARGIN_RIGHT;
714   }
715 
716   /**
717    * Checks horizontal splitter value to boundary values.
718    */
configureSplitter()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    */
getPropertyIndex(int y)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    */
isLocationState(PropertyInfo propertyInfo, int x)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    */
isLocationSplitter(int x)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    */
isLocationValue(int x)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    */
getValueRelativeLocation(int x, int y)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    */
setShowAdvancedProperties(boolean showAdvancedProperties)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    */
setInput(Property[] properties)788   public void setInput(Property[] properties) {
789     m_rawProperties = properties;
790     setInput0();
791   }
792 
setInput0()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    */
rawProperties_shouldShow(Property property)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    */
setActiveProperty(Property property)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    */
forTests_getPropertiesCount()898   public int forTests_getPropertiesCount() {
899     return m_properties.size();
900   }
901 
902   /**
903    * @return the {@link Property} from "expanded" list.
904    */
forTests_getProperty(int index)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    */
forTests_expand(int index)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    */
forTests_getSplitter()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    */
forTests_getStateLocation(Property property)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    */
forTests_getValueLocation(Property property)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    */
forTests_getActiveEditor()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    */
forTests_getCategory(Property property)961   public PropertyCategory forTests_getCategory(Property property) {
962     return getCategory(property);
963   }
964 
965   /**
966    * @return the {@link PropertyInfo}for given {@link Property}.
967    */
getPropertyInfo(Property property)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
addSelectionChangedListener(ISelectionChangedListener listener)985 public void addSelectionChangedListener(ISelectionChangedListener listener) {
986     if (!m_selectionListeners.contains(listener)) {
987       m_selectionListeners.add(listener);
988     }
989   }
990 
991   @Override
removeSelectionChangedListener(ISelectionChangedListener listener)992 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
993     m_selectionListeners.add(listener);
994   }
995 
996   @Override
getSelection()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
setSelection(ISelection selection)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    */
setActivePropertyInfo(PropertyInfo activePropertyInfo)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    */
handlePaint(GC gc, int x, int y, int width, int height)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    */
drawEmptyContent(GC gc)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    */
drawContent(GC gc)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    */
showPresentations(Rectangle clientArea)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    */
drawExpandLines(GC gc, Rectangle clientArea)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    */
drawProperty(GC gc, PropertyInfo propertyInfo, int y, int height, int width)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    */
setPropertyCategoryProvider(PropertyCategoryProvider propertyCategoryProvider)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    */
getCategory(Property property)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     ////////////////////////////////////////////////////////////////////////////
PropertyInfo(Property property)1366     public PropertyInfo(Property property) {
1367       this(property, "", 0);
1368     }
1369 
PropertyInfo(Property property, String idPrefix, int level)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      */
isComplex()1388     public boolean isComplex() {
1389       return m_stateComplex;
1390     }
1391 
isShowComplex()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      */
isExpanded()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      */
getLevel()1415     public int getLevel() {
1416       return m_level;
1417     }
1418 
1419     /**
1420      * @return the {@link Property}.
1421      */
getProperty()1422     public Property getProperty() {
1423       return m_property;
1424     }
1425 
1426     /**
1427      * Flips collapsed/expanded state and adds/removes sub-properties.
1428      */
flip()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      */
expand()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      */
collapse()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      */
addChildren(int index)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      */
removeChildren(int index)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      */
prepareChildren()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 
createSubPropertyInfo(Property subProperty)1545     private PropertyInfo createSubPropertyInfo(Property subProperty) {
1546       return new PropertyInfo(subProperty, m_id, m_level + 1);
1547     }
1548 
getSubProperties()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      */
expandFromHistory()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 */
collapseAll()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 */
expandAll()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 }