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