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