1 11 package org.eclipse.ui.forms.widgets; 12 13 import org.eclipse.core.runtime.Assert; 14 import org.eclipse.core.runtime.ListenerList; 15 import org.eclipse.swt.SWT; 16 import org.eclipse.swt.events.FocusEvent; 17 import org.eclipse.swt.events.FocusListener; 18 import org.eclipse.swt.events.KeyAdapter; 19 import org.eclipse.swt.events.KeyEvent; 20 import org.eclipse.swt.events.PaintEvent; 21 import org.eclipse.swt.events.PaintListener; 22 import org.eclipse.swt.events.TraverseEvent; 23 import org.eclipse.swt.events.TraverseListener; 24 import org.eclipse.swt.graphics.Color; 25 import org.eclipse.swt.graphics.Font; 26 import org.eclipse.swt.graphics.FontMetrics; 27 import org.eclipse.swt.graphics.GC; 28 import org.eclipse.swt.graphics.Point; 29 import org.eclipse.swt.graphics.Rectangle; 30 import org.eclipse.swt.widgets.Canvas; 31 import org.eclipse.swt.widgets.Composite; 32 import org.eclipse.swt.widgets.Control; 33 import org.eclipse.swt.widgets.Event; 34 import org.eclipse.swt.widgets.Label; 35 import org.eclipse.swt.widgets.Layout; 36 import org.eclipse.swt.widgets.Listener; 37 import org.eclipse.swt.widgets.Menu; 38 import org.eclipse.ui.forms.events.ExpansionEvent; 39 import org.eclipse.ui.forms.events.HyperlinkAdapter; 40 import org.eclipse.ui.forms.events.HyperlinkEvent; 41 import org.eclipse.ui.forms.events.IExpansionListener; 42 import org.eclipse.ui.internal.forms.widgets.FormUtil; 43 import org.eclipse.ui.internal.forms.widgets.FormsResources; 44 45 69 public class ExpandableComposite extends Canvas { 70 74 public static final int TWISTIE = 1 << 1; 75 76 80 public static final int TREE_NODE = 1 << 2; 81 82 87 public static final int FOCUS_TITLE = 1 << 3; 88 89 93 public static final int CLIENT_INDENT = 1 << 4; 94 95 100 public static final int COMPACT = 1 << 5; 101 102 107 public static final int EXPANDED = 1 << 6; 108 109 113 public static final int TITLE_BAR = 1 << 8; 114 115 122 public static final int SHORT_TITLE_BAR = 1 << 9; 123 124 127 public static final int NO_TITLE = 1 << 12; 128 129 133 public static final int LEFT_TEXT_CLIENT_ALIGNMENT = 1 << 13; 134 135 138 public int marginWidth = 0; 139 140 144 public int marginHeight = 0; 145 146 150 public int clientVerticalSpacing = 3; 151 152 160 public int descriptionVerticalSpacing = 0; 161 162 168 public int titleBarTextMarginWidth = 6; 169 170 173 protected ToggleHyperlink toggle; 174 175 178 protected Control textLabel; 179 180 185 protected int VGAP = 3; 186 191 protected int GAP = 4; 192 193 static final int IGAP = 4; 194 static final int IVGAP = 3; 195 196 private static final Point NULL_SIZE = new Point(0, 0); 197 198 private static final int VSPACE = 3; 199 200 private static final int SEPARATOR_HEIGHT = 2; 201 202 private int expansionStyle = TWISTIE | FOCUS_TITLE | EXPANDED; 203 204 private boolean expanded; 205 206 private Control textClient; 207 208 private Control client; 209 210 private ListenerList listeners = new ListenerList(); 211 212 private Color titleBarForeground; 213 214 private class ExpandableLayout extends Layout implements ILayoutExtension { 215 216 private SizeCache toggleCache = new SizeCache(); 217 218 private SizeCache textClientCache = new SizeCache(); 219 220 private SizeCache textLabelCache = new SizeCache(); 221 222 private SizeCache descriptionCache = new SizeCache(); 223 224 private SizeCache clientCache = new SizeCache(); 225 226 private void initCache(boolean shouldFlush) { 227 toggleCache.setControl(toggle); 228 textClientCache.setControl(textClient); 229 textLabelCache.setControl(textLabel); 230 descriptionCache.setControl(getDescriptionControl()); 231 clientCache.setControl(client); 232 233 if (shouldFlush) { 234 toggleCache.flush(); 235 textClientCache.flush(); 236 textLabelCache.flush(); 237 descriptionCache.flush(); 238 clientCache.flush(); 239 } 240 } 241 242 protected void layout(Composite parent, boolean changed) { 243 initCache(changed); 244 245 Rectangle clientArea = parent.getClientArea(); 246 int thmargin = 0; 247 int tvmargin = 0; 248 249 if (hasTitleBar()) { 250 thmargin = titleBarTextMarginWidth; 251 tvmargin = IVGAP; 252 } 253 int x = marginWidth + thmargin; 254 int y = marginHeight + tvmargin; 255 Point tsize = NULL_SIZE; 256 Point tcsize = NULL_SIZE; 257 if (toggle != null) 258 tsize = toggleCache.computeSize(SWT.DEFAULT, SWT.DEFAULT); 259 int twidth = clientArea.width - marginWidth - marginWidth 260 - thmargin - thmargin; 261 if (tsize.x > 0) 262 twidth -= tsize.x + IGAP; 263 if (textClient != null) 264 tcsize = textClientCache.computeSize(SWT.DEFAULT, SWT.DEFAULT); 265 if (tcsize.x > 0) 266 twidth -= tcsize.x + IGAP; 267 Point size = NULL_SIZE; 268 if (textLabel != null) 269 size = textLabelCache.computeSize(twidth, SWT.DEFAULT); 270 if (textLabel instanceof Label) { 271 Point defSize = textLabelCache.computeSize(SWT.DEFAULT, 272 SWT.DEFAULT); 273 if (defSize.y == size.y) { 274 size.x = Math.min(defSize.x, size.x); 276 } 277 } 278 if (toggle != null) { 279 GC gc = new GC(ExpandableComposite.this); 280 gc.setFont(getFont()); 281 FontMetrics fm = gc.getFontMetrics(); 282 int textHeight = fm.getHeight(); 283 gc.dispose(); 284 if (textClient != null 285 && (expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) != 0) { 286 textHeight = Math.max(textHeight, tcsize.y); 287 } 288 int ty = textHeight / 2 - tsize.y / 2 + 1; 289 ty = Math.max(ty, 0); 290 ty += marginHeight + tvmargin; 291 toggle.setLocation(x, ty); 292 toggle.setSize(tsize); 293 x += tsize.x + IGAP; 294 } 295 if (textLabel != null) { 296 int ty = y; 297 if (textClient != null 298 && (expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) != 0) { 299 if (size.y < tcsize.y) 300 ty = tcsize.y / 2 - size.y / 2 + marginHeight 301 + tvmargin; 302 } 303 textLabelCache.setBounds(x, ty, size.x, size.y); 304 } 305 if (textClient != null) { 306 int tcx; 307 if ((expansionStyle & LEFT_TEXT_CLIENT_ALIGNMENT) != 0) { 308 tcx = x + size.x + GAP; 309 } else { 310 tcx = clientArea.width - tcsize.x - marginWidth - thmargin; 311 } 312 textClientCache.setBounds(tcx, y, tcsize.x, tcsize.y); 313 } 314 int tbarHeight = 0; 315 if (size.y > 0) 316 tbarHeight = size.y; 317 if (tcsize.y > 0) 318 tbarHeight = Math.max(tbarHeight, tcsize.y); 319 y += tbarHeight; 320 if (hasTitleBar()) 321 y += tvmargin; 322 if (getSeparatorControl() != null) { 323 y += VSPACE; 324 getSeparatorControl().setBounds(marginWidth, y, 325 clientArea.width - marginWidth - marginWidth, 326 SEPARATOR_HEIGHT); 327 y += SEPARATOR_HEIGHT; 328 if (expanded) 329 y += VSPACE; 330 } 331 if (expanded) { 332 int areaWidth = clientArea.width - marginWidth - marginWidth 333 - thmargin - thmargin; 334 int cx = marginWidth + thmargin; 335 if ((expansionStyle & CLIENT_INDENT) != 0) { 336 cx = x; 337 areaWidth -= x; 338 } 339 if (client != null) { 340 Point dsize = null; 341 Control desc = getDescriptionControl(); 342 if (desc != null) { 343 dsize = descriptionCache.computeSize(areaWidth, 344 SWT.DEFAULT); 345 y += descriptionVerticalSpacing; 346 descriptionCache.setBounds(cx, y, areaWidth, dsize.y); 347 y += dsize.y + clientVerticalSpacing; 348 } else { 349 y += clientVerticalSpacing; 350 if (getSeparatorControl() != null) 351 y -= VSPACE; 352 } 353 int cwidth = areaWidth; 354 int cheight = clientArea.height - marginHeight 355 - marginHeight - y; 356 clientCache.setBounds(cx, y, cwidth, cheight); 357 } 358 } 359 } 360 361 protected Point computeSize(Composite parent, int wHint, int hHint, 362 boolean changed) { 363 initCache(changed); 364 365 int width = 0, height = 0; 366 Point tsize = NULL_SIZE; 367 int twidth = 0; 368 if (toggle != null) { 369 tsize = toggleCache.computeSize(SWT.DEFAULT, SWT.DEFAULT); 370 twidth = tsize.x + IGAP; 371 } 372 int thmargin = 0; 373 int tvmargin = 0; 374 375 if (hasTitleBar()) { 376 thmargin = titleBarTextMarginWidth; 377 tvmargin = IVGAP; 378 } 379 int innerwHint = wHint; 380 if (innerwHint != SWT.DEFAULT) 381 innerwHint -= twidth + marginWidth + marginWidth + thmargin 382 + thmargin; 383 384 int innertHint = innerwHint; 385 386 Point tcsize = NULL_SIZE; 387 if (textClient != null) { 388 tcsize = textClientCache.computeSize(SWT.DEFAULT, SWT.DEFAULT); 389 if (innertHint != SWT.DEFAULT) 390 innertHint -= IGAP + tcsize.x; 391 } 392 Point size = NULL_SIZE; 393 394 if (textLabel != null) 395 size = textLabelCache.computeSize(innertHint, SWT.DEFAULT); 396 if (textLabel instanceof Label) { 397 Point defSize = textLabelCache.computeSize(SWT.DEFAULT, 398 SWT.DEFAULT); 399 if (defSize.y == size.y) { 400 size.x = Math.min(defSize.x, size.x); 402 } 403 } 404 if (size.x > 0) 405 width = size.x; 406 if (tcsize.x > 0) 407 width += IGAP + tcsize.x; 408 height = tcsize.y > 0 ? Math.max(tcsize.y, size.y) : size.y; 409 if (getSeparatorControl() != null) { 410 height += VSPACE + SEPARATOR_HEIGHT; 411 if (expanded && client != null) 412 height += VSPACE; 413 } 414 if ((expanded || (expansionStyle & COMPACT) == 0) && client != null) { 417 int cwHint = wHint; 418 419 if (cwHint != SWT.DEFAULT) { 420 cwHint -= marginWidth + marginWidth + thmargin + thmargin; 421 if ((expansionStyle & CLIENT_INDENT) != 0) 422 if (tcsize.x > 0) 423 cwHint -= twidth; 424 } 425 Point dsize = null; 426 Point csize = clientCache.computeSize(FormUtil.getWidthHint( 427 cwHint, client), SWT.DEFAULT); 428 if (getDescriptionControl() != null) { 429 int dwHint = cwHint; 430 if (dwHint == SWT.DEFAULT) { 431 dwHint = csize.x; 432 if ((expansionStyle & CLIENT_INDENT) != 0) 433 dwHint -= twidth; 434 } 435 dsize = descriptionCache.computeSize(dwHint, SWT.DEFAULT); 436 } 437 if (dsize != null) { 438 width = Math.max(width, dsize.x); 439 if (expanded) 440 height += descriptionVerticalSpacing + dsize.y 441 + clientVerticalSpacing; 442 } else { 443 height += clientVerticalSpacing; 444 if (getSeparatorControl() != null) 445 height -= VSPACE; 446 } 447 width = Math.max(width, csize.x); 448 if (expanded) 449 height += csize.y; 450 } 451 if (toggle != null) { 452 height = height - size.y + Math.max(size.y, tsize.y); 453 width += twidth; 454 } 455 456 Point result = new Point(width + marginWidth + marginWidth 457 + thmargin + thmargin, height + marginHeight + marginHeight 458 + tvmargin + tvmargin); 459 return result; 460 } 461 462 public int computeMinimumWidth(Composite parent, boolean changed) { 463 return computeSize(parent, 0, SWT.DEFAULT, changed).x; 464 } 465 466 472 public int computeMaximumWidth(Composite parent, boolean changed) { 473 return computeSize(parent, SWT.DEFAULT, SWT.DEFAULT, changed).x; 474 } 475 } 476 477 485 public ExpandableComposite(Composite parent, int style) { 486 this(parent, style, TWISTIE); 487 } 488 489 501 public ExpandableComposite(Composite parent, int style, int expansionStyle) { 502 super(parent, style); 503 this.expansionStyle = expansionStyle; 504 if ((expansionStyle & TITLE_BAR) != 0) 505 setBackgroundMode(SWT.INHERIT_DEFAULT); 506 super.setLayout(new ExpandableLayout()); 507 if (hasTitleBar()) { 508 this.addPaintListener(new PaintListener() { 509 public void paintControl(PaintEvent e) { 510 onPaint(e); 511 } 512 }); 513 } 514 if ((expansionStyle & TWISTIE) != 0) 515 toggle = new Twistie(this, SWT.NULL); 516 else if ((expansionStyle & TREE_NODE) != 0) 517 toggle = new TreeNode(this, SWT.NULL); 518 else 519 expanded = true; 520 if ((expansionStyle & EXPANDED) != 0) 521 expanded = true; 522 if (toggle != null) { 523 toggle.setExpanded(expanded); 524 toggle.addHyperlinkListener(new HyperlinkAdapter() { 525 public void linkActivated(HyperlinkEvent e) { 526 toggleState(); 527 } 528 }); 529 toggle.addPaintListener(new PaintListener() { 530 public void paintControl(PaintEvent e) { 531 if (textLabel instanceof Label && !isFixedStyle()) 532 textLabel.setForeground(toggle.hover ? toggle 533 .getHoverDecorationColor() 534 : getTitleBarForeground()); 535 } 536 }); 537 toggle.addKeyListener(new KeyAdapter() { 538 public void keyPressed(KeyEvent e) { 539 if (e.keyCode == SWT.ARROW_UP) { 540 verticalMove(false); 541 e.doit = false; 542 } else if (e.keyCode == SWT.ARROW_DOWN) { 543 verticalMove(true); 544 e.doit = false; 545 } 546 } 547 }); 548 if ((getExpansionStyle()&FOCUS_TITLE)==0) { 549 toggle.paintFocus=false; 550 toggle.addFocusListener(new FocusListener() { 551 public void focusGained(FocusEvent e) { 552 textLabel.redraw(); 553 } 554 555 public void focusLost(FocusEvent e) { 556 textLabel.redraw(); 557 } 558 }); 559 } 560 } 561 if ((expansionStyle & FOCUS_TITLE) != 0) { 562 Hyperlink link = new Hyperlink(this, SWT.WRAP); 563 link.addHyperlinkListener(new HyperlinkAdapter() { 564 public void linkActivated(HyperlinkEvent e) { 565 programmaticToggleState(); 566 } 567 }); 568 textLabel = link; 569 } else if ((expansionStyle & NO_TITLE) == 0) { 570 final Label label = new Label(this, SWT.WRAP); 571 if (!isFixedStyle()) { 572 label.setCursor(FormsResources.getHandCursor()); 573 Listener listener = new Listener() { 574 public void handleEvent(Event e) { 575 switch (e.type) { 576 case SWT.MouseDown: 577 if (toggle != null) 578 toggle.setFocus(); 579 break; 580 case SWT.MouseUp: 581 label.setCursor(FormsResources.getBusyCursor()); 582 programmaticToggleState(); 583 label.setCursor(FormsResources.getHandCursor()); 584 break; 585 case SWT.MouseEnter: 586 if (toggle != null) { 587 label.setForeground(toggle 588 .getHoverDecorationColor()); 589 toggle.hover = true; 590 toggle.redraw(); 591 } 592 break; 593 case SWT.MouseExit: 594 if (toggle != null) { 595 label.setForeground(getTitleBarForeground()); 596 toggle.hover = false; 597 toggle.redraw(); 598 } 599 break; 600 case SWT.Paint: 601 if (toggle != null) { 602 paintTitleFocus(e.gc); 603 } 604 break; 605 } 606 } 607 }; 608 label.addListener(SWT.MouseDown, listener); 609 label.addListener(SWT.MouseUp, listener); 610 label.addListener(SWT.MouseEnter, listener); 611 label.addListener(SWT.MouseExit, listener); 612 label.addListener(SWT.Paint, listener); 613 } 614 textLabel = label; 615 } 616 if (textLabel != null) { 617 textLabel.setMenu(getMenu()); 618 textLabel.addTraverseListener(new TraverseListener() { 619 public void keyTraversed(TraverseEvent e) { 620 if (e.detail == SWT.TRAVERSE_MNEMONIC) { 621 if (!isVisible() || !isEnabled()) 623 return; 624 if (FormUtil.mnemonicMatch(getText(), e.character)) { 625 e.doit = false; 626 programmaticToggleState(); 627 setFocus(); 628 } 629 } 630 } 631 }); 632 } 633 } 634 635 641 642 public void setMenu(Menu menu) { 643 if (textLabel != null) 644 textLabel.setMenu(menu); 645 super.setMenu(menu); 646 } 647 648 652 public final void setLayout(Layout layout) { 653 } 654 655 658 public void setBackground(Color bg) { 659 super.setBackground(bg); 660 if ((getExpansionStyle() & TITLE_BAR) == 0) { 661 if (textLabel != null) 662 textLabel.setBackground(bg); 663 if (toggle != null) 664 toggle.setBackground(bg); 665 } 666 } 667 668 671 public void setForeground(Color fg) { 672 super.setForeground(fg); 673 if (textLabel != null) 674 textLabel.setForeground(fg); 675 if (toggle != null) 676 toggle.setForeground(fg); 677 } 678 679 685 public void setToggleColor(Color c) { 686 if (toggle != null) 687 toggle.setDecorationColor(c); 688 } 689 690 697 public void setActiveToggleColor(Color c) { 698 if (toggle != null) 699 toggle.setHoverDecorationColor(c); 700 } 701 702 705 public void setFont(Font font) { 706 super.setFont(font); 707 if (textLabel != null) 708 textLabel.setFont(font); 709 if (toggle != null) 710 toggle.setFont(font); 711 } 712 713 718 719 public void setEnabled(boolean enabled) { 720 if (textLabel != null) 721 textLabel.setEnabled(enabled); 722 if (toggle != null) 723 toggle.setEnabled(enabled); 724 super.setEnabled(enabled); 725 } 726 727 734 public void setClient(Control client) { 735 Assert.isTrue(client != null && client.getParent().equals(this)); 736 this.client = client; 737 } 738 739 744 public Control getClient() { 745 return client; 746 } 747 748 757 public void setText(String title) { 758 if (textLabel instanceof Label) 759 ((Label) textLabel).setText(title); 760 else if (textLabel instanceof Hyperlink) 761 ((Hyperlink) textLabel).setText(title); 762 } 763 764 770 public String getText() { 771 if (textLabel instanceof Label) 772 return ((Label) textLabel).getText(); 773 else if (textLabel instanceof Hyperlink) 774 return ((Hyperlink) textLabel).getText(); 775 else 776 return ""; } 778 779 784 public boolean isExpanded() { 785 return expanded; 786 } 787 788 793 public int getExpansionStyle() { 794 return expansionStyle; 795 } 796 797 803 public void setExpanded(boolean expanded) { 804 internalSetExpanded(expanded); 805 if (toggle != null) 806 toggle.setExpanded(expanded); 807 } 808 809 815 protected void internalSetExpanded(boolean expanded) { 816 if (this.expanded != expanded) { 817 this.expanded = expanded; 818 if (getDescriptionControl() != null) 819 getDescriptionControl().setVisible(expanded); 820 if (client != null) 821 client.setVisible(expanded); 822 layout(); 823 } 824 } 825 826 832 public void addExpansionListener(IExpansionListener listener) { 833 listeners.add(listener); 834 } 835 836 842 public void removeExpansionListener(IExpansionListener listener) { 843 listeners.remove(listener); 844 } 845 846 854 protected void onPaint(PaintEvent e) { 855 } 856 857 863 protected Control getDescriptionControl() { 864 return null; 865 } 866 867 873 protected Control getSeparatorControl() { 874 return null; 875 } 876 877 882 public Point computeSize(int wHint, int hHint, boolean changed) { 883 checkWidget(); 884 Point size; 885 ExpandableLayout layout = (ExpandableLayout) getLayout(); 886 if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) { 887 size = layout.computeSize(this, wHint, hHint, changed); 888 } else { 889 size = new Point(wHint, hHint); 890 } 891 Rectangle trim = computeTrim(0, 0, size.x, size.y); 892 return new Point(trim.width, trim.height); 893 } 894 895 905 protected boolean isFixedStyle() { 906 return (expansionStyle & TWISTIE) == 0 907 && (expansionStyle & TREE_NODE) == 0; 908 } 909 910 916 public Control getTextClient() { 917 return textClient; 918 } 919 920 931 public void setTextClient(Control textClient) { 932 if (this.textClient != null) 933 this.textClient.dispose(); 934 this.textClient = textClient; 935 } 936 937 950 public int getTextClientHeightDifference() { 951 if (textClient == null || textLabel == null) 952 return 0; 953 int theight = textLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; 954 int tcheight = textClient.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; 955 return Math.max(tcheight - theight, 0); 956 } 957 958 965 protected boolean hasTitleBar() { 966 return (getExpansionStyle() & TITLE_BAR) != 0 967 || (getExpansionStyle() & SHORT_TITLE_BAR) != 0; 968 } 969 970 976 public void setTitleBarForeground(Color color) { 977 titleBarForeground = color; 978 textLabel.setForeground(color); 979 } 980 981 986 public Color getTitleBarForeground() { 987 return titleBarForeground; 988 } 989 990 992 private void toggleState() { 993 boolean newState = !isExpanded(); 994 fireExpanding(newState, true); 995 internalSetExpanded(newState); 996 fireExpanding(newState, false); 997 if (newState) 998 FormUtil.ensureVisible(this); 999 } 1000 1001 private void fireExpanding(boolean state, boolean before) { 1002 int size = listeners.size(); 1003 if (size == 0) 1004 return; 1005 ExpansionEvent e = new ExpansionEvent(this, state); 1006 Object [] listenerList = listeners.getListeners(); 1007 for (int i = 0; i < size; i++) { 1008 IExpansionListener listener = (IExpansionListener) listenerList[i]; 1009 if (before) 1010 listener.expansionStateChanging(e); 1011 else 1012 listener.expansionStateChanged(e); 1013 } 1014 } 1015 1016 private void verticalMove(boolean down) { 1017 Composite parent = getParent(); 1018 Control[] children = parent.getChildren(); 1019 for (int i = 0; i < children.length; i++) { 1020 Control child = children[i]; 1021 if (child == this) { 1022 ExpandableComposite sibling = getSibling(children, i, down); 1023 if (sibling != null && sibling.toggle != null) { 1024 sibling.setFocus(); 1025 } 1026 break; 1027 } 1028 } 1029 } 1030 1031 private ExpandableComposite getSibling(Control[] children, int index, 1032 boolean down) { 1033 int loc = down ? index + 1 : index - 1; 1034 while (loc >= 0 && loc < children.length) { 1035 Control c = children[loc]; 1036 if (c instanceof ExpandableComposite && c.isVisible()) 1037 return (ExpandableComposite) c; 1038 loc = down ? loc + 1 : loc - 1; 1039 } 1040 return null; 1041 } 1042 1043 private void programmaticToggleState() { 1044 if (toggle != null) 1045 toggle.setExpanded(!toggle.isExpanded()); 1046 toggleState(); 1047 } 1048 1049 private void paintTitleFocus(GC gc) { 1050 Point size = textLabel.getSize(); 1051 gc.setBackground(textLabel.getBackground()); 1052 gc.setForeground(textLabel.getForeground()); 1053 if (toggle.isFocusControl()) 1054 gc.drawFocus(0, 0, size.x, size.y); 1055 } 1056} | Popular Tags |