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 }