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.core.controls.flyout; 12 13 import org.eclipse.jface.action.Action; 14 import org.eclipse.jface.action.IMenuListener; 15 import org.eclipse.jface.action.IMenuManager; 16 import org.eclipse.jface.action.MenuManager; 17 import org.eclipse.jface.resource.JFaceResources; 18 import org.eclipse.swt.SWT; 19 import org.eclipse.swt.events.DisposeEvent; 20 import org.eclipse.swt.events.DisposeListener; 21 import org.eclipse.swt.events.MouseAdapter; 22 import org.eclipse.swt.events.MouseEvent; 23 import org.eclipse.swt.events.MouseMoveListener; 24 import org.eclipse.swt.events.MouseTrackAdapter; 25 import org.eclipse.swt.graphics.Font; 26 import org.eclipse.swt.graphics.GC; 27 import org.eclipse.swt.graphics.Image; 28 import org.eclipse.swt.graphics.Point; 29 import org.eclipse.swt.graphics.Rectangle; 30 import org.eclipse.swt.widgets.Composite; 31 import org.eclipse.swt.widgets.Control; 32 import org.eclipse.swt.widgets.Event; 33 import org.eclipse.swt.widgets.Listener; 34 import org.eclipse.swt.widgets.Sash; 35 import org.eclipse.swt.widgets.Tracker; 36 import org.eclipse.wb.core.controls.Messages; 37 import org.eclipse.wb.draw2d.IColorConstants; 38 import org.eclipse.wb.draw2d.ICursorConstants; 39 import org.eclipse.wb.internal.core.utils.ui.DrawUtils; 40 41 import java.util.ArrayList; 42 import java.util.List; 43 44 /** 45 * {@link FlyoutControlComposite} is container for two {@link Control}'s. One (client control) is 46 * used to fill client area. Second (flyout control) can be docked to any enabled position or 47 * temporary hidden. 48 * 49 * @author scheglov_ke 50 * @coverage core.control 51 */ 52 public final class FlyoutControlComposite extends Composite { 53 private static final int RESIZE_WIDTH = 5; 54 private static final int TITLE_LINES = 30; 55 private static final int TITLE_MARGIN = 5; 56 private static final Font TITLE_FONT = JFaceResources.getFontRegistry().getBold( 57 JFaceResources.DEFAULT_FONT); 58 //////////////////////////////////////////////////////////////////////////// 59 // 60 // Images 61 // 62 //////////////////////////////////////////////////////////////////////////// 63 private static final Image PIN = loadImage("icons/pin.gif"); 64 private static final Image ARROW_LEFT = loadImage("icons/arrow_left.gif"); 65 private static final Image ARROW_RIGHT = loadImage("icons/arrow_right.gif"); 66 private static final Image ARROW_TOP = loadImage("icons/arrow_top.gif"); 67 private static final Image ARROW_BOTTOM = loadImage("icons/arrow_bottom.gif"); 68 69 private static Image loadImage(String path) { 70 return DrawUtils.loadImage(FlyoutControlComposite.class, path); 71 } 72 73 //////////////////////////////////////////////////////////////////////////// 74 // 75 // Instance fields 76 // 77 //////////////////////////////////////////////////////////////////////////// 78 private final IFlyoutPreferences m_preferences; 79 private final FlyoutContainer m_flyoutContainer; 80 private int m_minWidth = 150; 81 private int m_validDockLocations = -1; 82 private final List<IFlyoutMenuContributor> m_menuContributors = 83 new ArrayList<IFlyoutMenuContributor>(); 84 85 //////////////////////////////////////////////////////////////////////////// 86 // 87 // Constructor 88 // 89 //////////////////////////////////////////////////////////////////////////// 90 public FlyoutControlComposite(Composite parent, int style, IFlyoutPreferences preferences) { 91 super(parent, style); 92 m_preferences = preferences; 93 // add listeners 94 addListener(SWT.Resize, new Listener() { 95 @Override 96 public void handleEvent(Event event) { 97 if (getShell().getMinimized()) { 98 return; 99 } 100 layout(); 101 } 102 }); 103 // create container for flyout control 104 m_flyoutContainer = new FlyoutContainer(this, SWT.NO_BACKGROUND); 105 } 106 107 //////////////////////////////////////////////////////////////////////////// 108 // 109 // Parents 110 // 111 //////////////////////////////////////////////////////////////////////////// 112 /** 113 * @return the parent {@link Composite} for flyout {@link Control}. 114 */ 115 public Composite getFlyoutParent() { 116 return m_flyoutContainer; 117 } 118 119 /** 120 * @return the parent {@link Composite} for client {@link Control}. 121 */ 122 public Composite getClientParent() { 123 return this; 124 } 125 126 /** 127 * Sets the bit set with valid docking locations. 128 */ 129 public void setValidDockLocations(int validDockLocations) { 130 m_validDockLocations = validDockLocations; 131 } 132 133 //////////////////////////////////////////////////////////////////////////// 134 // 135 // Access 136 // 137 //////////////////////////////////////////////////////////////////////////// 138 /** 139 * Sets the minimal width of flyout. 140 */ 141 public void setMinWidth(int minWidth) { 142 m_minWidth = minWidth; 143 } 144 145 /** 146 * Sets the text of title. 147 */ 148 public void setTitleText(String text) { 149 m_flyoutContainer.setTitleText(text); 150 } 151 152 /** 153 * Adds new {@link IFlyoutMenuContributor}. 154 */ 155 public void addMenuContributor(IFlyoutMenuContributor contributor) { 156 if (!m_menuContributors.contains(contributor)) { 157 m_menuContributors.add(contributor); 158 } 159 } 160 161 //////////////////////////////////////////////////////////////////////////// 162 // 163 // Layout 164 // 165 //////////////////////////////////////////////////////////////////////////// 166 @Override 167 public void layout() { 168 Rectangle clientArea = getClientArea(); 169 int state = m_preferences.getState(); 170 Control client = getChildren()[1]; 171 // check, may be "clientArea" is empty, for example because CTabFolder page is not visible 172 if (clientArea.width == 0 || clientArea.height == 0) { 173 return; 174 } 175 // check, maybe flyout has no Control, so "client" should fill client area 176 if (m_flyoutContainer.getControl() == null 177 // BEGIN ADT MODIFICATIONS 178 || !m_flyoutContainer.getControl().getVisible() 179 // END ADT MODIFICATIONS 180 ) { 181 m_flyoutContainer.setBounds(0, 0, 0, 0); 182 client.setBounds(clientArea); 183 return; 184 } 185 // prepare width to display 186 int width; 187 int offset; 188 if (state == IFlyoutPreferences.STATE_OPEN) { 189 width = m_preferences.getWidth(); 190 // limit maximum value 191 if (isHorizontal()) { 192 width = Math.min(clientArea.width / 2, width); 193 } else { 194 width = Math.min(clientArea.height / 2, width); 195 } 196 // limit minimum value 197 width = Math.max(width, m_minWidth); 198 width = Math.max(width, 2 * m_flyoutContainer.m_titleHeight + m_flyoutContainer.m_titleWidth); 199 // remember actual width 200 m_preferences.setWidth(width); 201 // 202 offset = width; 203 } else if (state == IFlyoutPreferences.STATE_EXPANDED) { 204 offset = m_flyoutContainer.m_titleHeight; 205 width = m_preferences.getWidth(); 206 } else { 207 width = m_flyoutContainer.m_titleHeight; 208 offset = width; 209 } 210 // change bounds for flyout container and client control 211 { 212 if (isWest()) { 213 m_flyoutContainer.setBounds(0, 0, width, clientArea.height); 214 client.setBounds(offset, 0, clientArea.width - offset, clientArea.height); 215 } else if (isEast()) { 216 m_flyoutContainer.setBounds(clientArea.width - width, 0, width, clientArea.height); 217 client.setBounds(0, 0, clientArea.width - offset, clientArea.height); 218 } else if (isNorth()) { 219 m_flyoutContainer.setBounds(0, 0, clientArea.width, width); 220 client.setBounds(0, offset, clientArea.width, clientArea.height - offset); 221 } else if (isSouth()) { 222 m_flyoutContainer.setBounds(0, clientArea.height - width, clientArea.width, width); 223 client.setBounds(0, 0, clientArea.width, clientArea.height - offset); 224 } 225 } 226 } 227 228 //////////////////////////////////////////////////////////////////////////// 229 // 230 // Internal utils 231 // 232 //////////////////////////////////////////////////////////////////////////// 233 private boolean isHorizontal() { 234 return isWest() || isEast(); 235 } 236 237 private boolean isWest() { 238 return getDockLocation() == IFlyoutPreferences.DOCK_WEST; 239 } 240 241 private boolean isEast() { 242 return getDockLocation() == IFlyoutPreferences.DOCK_EAST; 243 } 244 245 private boolean isNorth() { 246 return getDockLocation() == IFlyoutPreferences.DOCK_NORTH; 247 } 248 249 private boolean isSouth() { 250 return getDockLocation() == IFlyoutPreferences.DOCK_SOUTH; 251 } 252 253 /** 254 * @return <code>true</code> if given docking location is valid. 255 */ 256 private boolean isValidDockLocation(int location) { 257 return (location & m_validDockLocations) == location; 258 } 259 260 /** 261 * @return current docking location. 262 */ 263 private int getDockLocation() { 264 return m_preferences.getDockLocation(); 265 } 266 267 /** 268 * Sets new docking location. 269 */ 270 private void setDockLocation(int dockLocation) { 271 m_preferences.setDockLocation(dockLocation); 272 layout(); 273 } 274 275 // BEGIN ADT MODIFICATIONS 276 /** If the flyout hover is showing, dismiss it */ 277 public void dismissHover() { 278 if (m_flyoutContainer != null) { 279 m_flyoutContainer.dismissHover(); 280 } 281 } 282 283 /** Sets a listener to be modified when windows are opened, collapsed and expanded */ 284 public void setListener(IFlyoutListener listener) { 285 assert m_listener == null; // Only one listener supported 286 m_listener = listener; 287 } 288 private IFlyoutListener m_listener; 289 // END ADT MODIFICATIONS 290 291 //////////////////////////////////////////////////////////////////////////// 292 // 293 // FlyoutContainer 294 // 295 //////////////////////////////////////////////////////////////////////////// 296 /** 297 * Container for flyout {@link Control}. 298 * 299 * @author scheglov_ke 300 */ 301 private final class FlyoutContainer extends Composite { 302 //////////////////////////////////////////////////////////////////////////// 303 // 304 // Container 305 // 306 //////////////////////////////////////////////////////////////////////////// 307 public FlyoutContainer(Composite parent, int style) { 308 super(parent, style); 309 configureMenu(); 310 updateTitleImage("Flyout"); 311 // add listeners 312 addListener(SWT.Dispose, new Listener() { 313 @Override 314 public void handleEvent(Event event) { 315 if (m_titleImage != null) { 316 m_titleImage.dispose(); 317 m_titleImageRotated.dispose(); 318 m_titleImage = null; 319 m_titleImageRotated = null; 320 } 321 if (m_backImage != null) { 322 m_backImage.dispose(); 323 m_backImage = null; 324 } 325 } 326 }); 327 { 328 Listener listener = new Listener() { 329 @Override 330 public void handleEvent(Event event) { 331 layout(); 332 } 333 }; 334 addListener(SWT.Move, listener); 335 addListener(SWT.Resize, listener); 336 } 337 addListener(SWT.Paint, new Listener() { 338 @Override 339 public void handleEvent(Event event) { 340 handlePaint(event.gc); 341 } 342 }); 343 // mouse listeners 344 addMouseListener(new MouseAdapter() { 345 @Override 346 public void mouseDown(MouseEvent event) { 347 if (event.button == 1) { 348 handle_mouseDown(event); 349 } 350 } 351 352 @Override 353 public void mouseUp(MouseEvent event) { 354 if (event.button == 1) { 355 handle_mouseUp(event); 356 } 357 } 358 }); 359 addMouseTrackListener(new MouseTrackAdapter() { 360 @Override 361 public void mouseExit(MouseEvent e) { 362 m_stateHover = false; 363 redraw(); 364 setCursor(null); 365 } 366 367 @Override 368 public void mouseHover(MouseEvent e) { 369 handle_mouseHover(); 370 } 371 }); 372 addMouseMoveListener(new MouseMoveListener() { 373 @Override 374 public void mouseMove(MouseEvent event) { 375 handle_mouseMove(event); 376 } 377 }); 378 } 379 380 // BEGIN ADT MODIFICATIONS 381 private void dismissHover() { 382 int state = m_preferences.getState(); 383 if (state == IFlyoutPreferences.STATE_EXPANDED) { 384 state = IFlyoutPreferences.STATE_COLLAPSED; 385 m_preferences.setState(state); 386 redraw(); 387 FlyoutControlComposite.this.layout(); 388 if (m_listener != null) { 389 m_listener.stateChanged(IFlyoutPreferences.STATE_EXPANDED, state); 390 } 391 } 392 } 393 // END END MODIFICATIONS 394 395 //////////////////////////////////////////////////////////////////////////// 396 // 397 // Events: mouse 398 // 399 //////////////////////////////////////////////////////////////////////////// 400 private boolean m_resize; 401 private boolean m_stateHover; 402 403 /** 404 * Handler for {@link SWT#MouseDown} event. 405 */ 406 private void handle_mouseDown(MouseEvent event) { 407 if (m_stateHover) { 408 int state = m_preferences.getState(); 409 // BEGIN ADT MODIFICATIONS 410 int oldState = state; 411 // END ADT MODIFICATIONS 412 if (state == IFlyoutPreferences.STATE_OPEN) { 413 state = IFlyoutPreferences.STATE_COLLAPSED; 414 } else { 415 state = IFlyoutPreferences.STATE_OPEN; 416 } 417 m_preferences.setState(state); 418 redraw(); 419 FlyoutControlComposite.this.layout(); 420 // BEGIN ADT MODIFICATIONS 421 if (m_listener != null) { 422 m_listener.stateChanged(oldState, state); 423 } 424 // END ADT MODIFICATIONS 425 } else if (getCursor() == ICursorConstants.SIZEWE || getCursor() == ICursorConstants.SIZENS) { 426 m_resize = true; 427 } else if (getCursor() == ICursorConstants.SIZEALL) { 428 handleDocking(); 429 } 430 } 431 432 /** 433 * Handler for {@link SWT#MouseUp} event. 434 */ 435 private void handle_mouseUp(MouseEvent event) { 436 if (m_resize) { 437 m_resize = false; 438 handle_mouseMove(event); 439 } 440 } 441 442 /** 443 * Handler for {@link SWT#MouseMove} event. 444 */ 445 private void handle_mouseMove(MouseEvent event) { 446 final FlyoutControlComposite container = FlyoutControlComposite.this; 447 if (m_resize) { 448 // prepare width 449 int width; 450 if (isHorizontal()) { 451 width = getSize().x; 452 } else { 453 width = getSize().y; 454 } 455 // prepare new width 456 int newWidth = width; 457 if (isWest()) { 458 newWidth = event.x + RESIZE_WIDTH / 2; 459 } else if (isEast()) { 460 newWidth = width - event.x + RESIZE_WIDTH / 2; 461 } else if (isNorth()) { 462 newWidth = event.y + RESIZE_WIDTH / 2; 463 } else if (isSouth()) { 464 newWidth = width - event.y + RESIZE_WIDTH / 2; 465 } 466 // update width 467 if (newWidth != width) { 468 m_preferences.setWidth(newWidth); 469 redraw(); 470 container.layout(); 471 } 472 } else { 473 Rectangle clientArea = getClientArea(); 474 boolean inside = clientArea.contains(event.x, event.y); 475 int x = event.x; 476 int y = event.y; 477 if (inside) { 478 // check for state 479 { 480 boolean oldStateHover = m_stateHover; 481 if (isEast()) { 482 m_stateHover = x > clientArea.width - m_titleHeight && y < m_titleHeight; 483 } else { 484 m_stateHover = x < m_titleHeight && y < m_titleHeight; 485 } 486 if (m_stateHover != oldStateHover) { 487 redraw(); 488 } 489 if (m_stateHover) { 490 setCursor(null); 491 return; 492 } 493 } 494 // check for resize band 495 if (isOpenExpanded()) { 496 if (isWest() && x >= clientArea.width - RESIZE_WIDTH) { 497 setCursor(ICursorConstants.SIZEWE); 498 } else if (isEast() && x <= RESIZE_WIDTH) { 499 setCursor(ICursorConstants.SIZEWE); 500 } else if (isNorth() && y >= clientArea.height - RESIZE_WIDTH) { 501 setCursor(ICursorConstants.SIZENS); 502 } else if (isSouth() && y <= RESIZE_WIDTH) { 503 setCursor(ICursorConstants.SIZENS); 504 } else { 505 setCursor(null); 506 } 507 } 508 // check for docking 509 if (getCursor() == null) { 510 setCursor(ICursorConstants.SIZEALL); 511 } 512 } else { 513 setCursor(null); 514 } 515 } 516 } 517 518 /** 519 * Handler for {@link SWT#MouseHover} event - temporary expands flyout and collapse again when 520 * mouse moves above client. 521 */ 522 private void handle_mouseHover() { 523 if (m_preferences.getState() == IFlyoutPreferences.STATE_COLLAPSED && !m_stateHover) { 524 m_preferences.setState(IFlyoutPreferences.STATE_EXPANDED); 525 // 526 final FlyoutControlComposite container = FlyoutControlComposite.this; 527 container.layout(); 528 // BEGIN ADT MODIFICATIONS 529 if (m_listener != null) { 530 m_listener.stateChanged(IFlyoutPreferences.STATE_COLLAPSED, 531 IFlyoutPreferences.STATE_EXPANDED); 532 } 533 // END ADT MODIFICATIONS 534 // add listeners 535 Listener listener = new Listener() { 536 @Override 537 public void handleEvent(Event event) { 538 if (event.type == SWT.Dispose) { 539 getDisplay().removeFilter(SWT.MouseMove, this); 540 } else { 541 Point p = ((Control) event.widget).toDisplay(event.x, event.y); 542 // during resize mouse can be temporary outside of flyout - ignore 543 if (m_resize) { 544 return; 545 } 546 // mouse in in flyout container - ignore 547 if (getClientArea().contains(toControl(p.x, p.y))) { 548 return; 549 } 550 // mouse is in full container - collapse 551 if (container.getClientArea().contains(container.toControl(p.x, p.y))) { 552 getDisplay().removeFilter(SWT.MouseMove, this); 553 // it is possible, that user restored (OPEN) flyout, so collapse only if we still in expand state 554 if (m_preferences.getState() == IFlyoutPreferences.STATE_EXPANDED) { 555 m_preferences.setState(IFlyoutPreferences.STATE_COLLAPSED); 556 container.layout(); 557 // BEGIN ADT MODIFICATIONS 558 if (m_listener != null) { 559 m_listener.stateChanged(IFlyoutPreferences.STATE_EXPANDED, 560 IFlyoutPreferences.STATE_COLLAPSED); 561 } 562 // END ADT MODIFICATIONS 563 } 564 } 565 } 566 } 567 }; 568 addListener(SWT.Dispose, listener); 569 getDisplay().addFilter(SWT.MouseMove, listener); 570 } 571 } 572 573 /** 574 * Handler for docking. 575 */ 576 private void handleDocking() { 577 final FlyoutControlComposite container = FlyoutControlComposite.this; 578 final int width = m_preferences.getWidth(); 579 final int oldDockLocation = getDockLocation(); 580 final int[] newDockLocation = new int[]{oldDockLocation}; 581 final Tracker dockingTracker = new Tracker(container, SWT.NONE); 582 dockingTracker.setRectangles(new Rectangle[]{getBounds()}); 583 dockingTracker.setStippled(true); 584 dockingTracker.addListener(SWT.Move, new Listener() { 585 @Override 586 public void handleEvent(Event event2) { 587 Rectangle clientArea = container.getClientArea(); 588 Point location = container.toControl(event2.x, event2.y); 589 int h3 = clientArea.height / 3; 590 // check locations 591 if (location.y < h3 && isValidDockLocation(IFlyoutPreferences.DOCK_NORTH)) { 592 dockingTracker.setRectangles(new Rectangle[]{new Rectangle(0, 593 0, 594 clientArea.width, 595 width)}); 596 newDockLocation[0] = IFlyoutPreferences.DOCK_NORTH; 597 } else if (location.y > 2 * h3 && isValidDockLocation(IFlyoutPreferences.DOCK_SOUTH)) { 598 dockingTracker.setRectangles(new Rectangle[]{new Rectangle(0, 599 clientArea.height - width, 600 clientArea.width, 601 width)}); 602 newDockLocation[0] = IFlyoutPreferences.DOCK_SOUTH; 603 } else if (location.x < clientArea.width / 2 604 && isValidDockLocation(IFlyoutPreferences.DOCK_WEST)) { 605 dockingTracker.setRectangles(new Rectangle[]{new Rectangle(0, 606 0, 607 width, 608 clientArea.height)}); 609 newDockLocation[0] = IFlyoutPreferences.DOCK_WEST; 610 } else if (isValidDockLocation(IFlyoutPreferences.DOCK_EAST)) { 611 dockingTracker.setRectangles(new Rectangle[]{new Rectangle(clientArea.width - width, 612 0, 613 width, 614 clientArea.height)}); 615 newDockLocation[0] = IFlyoutPreferences.DOCK_EAST; 616 } else { 617 dockingTracker.setRectangles(new Rectangle[]{getBounds()}); 618 newDockLocation[0] = oldDockLocation; 619 } 620 } 621 }); 622 // start tracking 623 if (dockingTracker.open()) { 624 setDockLocation(newDockLocation[0]); 625 } 626 // dispose tracker 627 dockingTracker.dispose(); 628 } 629 630 //////////////////////////////////////////////////////////////////////////// 631 // 632 // Access 633 // 634 //////////////////////////////////////////////////////////////////////////// 635 /** 636 * @return the {@link Control} installed on this {@link FlyoutControlComposite}, or 637 * <code>null</code> if there are no any {@link Control}. 638 */ 639 private Control getControl() { 640 Control[] children = getChildren(); 641 return children.length == 1 ? children[0] : null; 642 } 643 644 /** 645 * Sets the text of title. 646 */ 647 public void setTitleText(String text) { 648 updateTitleImage(text); 649 } 650 651 //////////////////////////////////////////////////////////////////////////// 652 // 653 // Layout 654 // 655 //////////////////////////////////////////////////////////////////////////// 656 @Override 657 public void layout() { 658 Control control = getControl(); 659 if (control == null) { 660 return; 661 } 662 // OK, we have control, so can continue layout 663 Rectangle clientArea = getClientArea(); 664 if (isOpenExpanded()) { 665 if (isWest()) { 666 int y = m_titleHeight; 667 control.setBounds(0, y, clientArea.width - RESIZE_WIDTH, clientArea.height - y); 668 } else if (isEast()) { 669 int y = m_titleHeight; 670 control.setBounds(RESIZE_WIDTH, y, clientArea.width - RESIZE_WIDTH, clientArea.height - y); 671 } else if (isNorth()) { 672 int y = m_titleHeight; 673 control.setBounds(0, y, clientArea.width, clientArea.height - y - RESIZE_WIDTH); 674 } else if (isSouth()) { 675 int y = RESIZE_WIDTH + m_titleHeight; 676 control.setBounds(0, y, clientArea.width, clientArea.height - y); 677 } 678 } else { 679 control.setBounds(0, 0, 0, 0); 680 } 681 } 682 683 //////////////////////////////////////////////////////////////////////////// 684 // 685 // Paint 686 // 687 //////////////////////////////////////////////////////////////////////////// 688 private Image m_backImage; 689 690 /** 691 * Handler for {@link SWT#Paint} event. 692 */ 693 private void handlePaint(GC paintGC) { 694 Rectangle clientArea = getClientArea(); 695 // prepare back image 696 GC gc; 697 { 698 if (m_backImage == null || !m_backImage.getBounds().equals(clientArea)) { 699 if (m_backImage != null) { 700 m_backImage.dispose(); 701 } 702 m_backImage = new Image(getDisplay(), clientArea.width, clientArea.height); 703 } 704 // prepare GC 705 gc = new GC(m_backImage); 706 gc.setBackground(paintGC.getBackground()); 707 gc.setForeground(paintGC.getForeground()); 708 gc.fillRectangle(clientArea); 709 } 710 // 711 if (isOpenExpanded()) { 712 // draw header 713 { 714 // draw title 715 if (isWest()) { 716 drawStateImage(gc, 0, 0); 717 gc.drawImage(m_titleImage, m_titleHeight, 0); 718 } else if (isEast()) { 719 int x = clientArea.width - m_titleHeight; 720 drawStateImage(gc, x, 0); 721 gc.drawImage(m_titleImage, x - m_titleWidth, 0); 722 } else if (isNorth()) { 723 drawStateImage(gc, 0, 0); 724 gc.drawImage(m_titleImage, m_titleHeight, 0); 725 } else if (isSouth()) { 726 int y = RESIZE_WIDTH; 727 drawStateImage(gc, 0, y); 728 gc.drawImage(m_titleImage, m_titleHeight, y); 729 } 730 } 731 // draw resize band 732 drawResizeBand(gc); 733 } else { 734 if (isHorizontal()) { 735 drawStateImage(gc, 0, 0); 736 gc.drawImage(m_titleImageRotated, 0, m_titleHeight); 737 } else { 738 drawStateImage(gc, 0, 0); 739 gc.drawImage(m_titleImage, m_titleHeight, 0); 740 } 741 DrawUtils.drawHighlightRectangle(gc, 0, 0, clientArea.width, clientArea.height); 742 } 743 // flush back image 744 { 745 gc.dispose(); 746 paintGC.drawImage(m_backImage, 0, 0); 747 } 748 } 749 750 /** 751 * Draws the state image (arrow) at given location. 752 */ 753 private void drawStateImage(GC gc, int x, int y) { 754 DrawUtils.drawImageCHCV(gc, getStateImage(), x, y, m_titleHeight, m_titleHeight); 755 if (m_stateHover) { 756 DrawUtils.drawHighlightRectangle(gc, x, y, m_titleHeight, m_titleHeight); 757 } 758 } 759 760 /** 761 * @return the {@link Image} corresponding to current state (open or collapsed). 762 */ 763 private Image getStateImage() { 764 int location = getDockLocation(); 765 int state = m_preferences.getState(); 766 if (state == IFlyoutPreferences.STATE_OPEN) { 767 switch (location) { 768 case IFlyoutPreferences.DOCK_WEST : 769 return ARROW_LEFT; 770 case IFlyoutPreferences.DOCK_EAST : 771 return ARROW_RIGHT; 772 case IFlyoutPreferences.DOCK_NORTH : 773 return ARROW_TOP; 774 case IFlyoutPreferences.DOCK_SOUTH : 775 return ARROW_BOTTOM; 776 } 777 } else if (state == IFlyoutPreferences.STATE_EXPANDED) { 778 return PIN; 779 } else { 780 switch (location) { 781 case IFlyoutPreferences.DOCK_WEST : 782 return ARROW_RIGHT; 783 case IFlyoutPreferences.DOCK_EAST : 784 return ARROW_LEFT; 785 case IFlyoutPreferences.DOCK_NORTH : 786 return ARROW_BOTTOM; 787 case IFlyoutPreferences.DOCK_SOUTH : 788 return ARROW_TOP; 789 } 790 } 791 // 792 return null; 793 } 794 795 /** 796 * Draws that resize band, {@link Sash} like. 797 */ 798 private void drawResizeBand(GC gc) { 799 Rectangle clientArea = getClientArea(); 800 // prepare locations 801 int x, y, width, height; 802 if (isHorizontal()) { 803 if (isWest()) { 804 x = clientArea.width - RESIZE_WIDTH; 805 } else { 806 x = 0; 807 } 808 y = 0; 809 width = RESIZE_WIDTH; 810 height = clientArea.height; 811 } else { 812 x = 0; 813 if (isNorth()) { 814 y = clientArea.height - RESIZE_WIDTH; 815 } else { 816 y = 0; 817 } 818 width = clientArea.width; 819 height = RESIZE_WIDTH; 820 } 821 // draw band 822 DrawUtils.drawHighlightRectangle(gc, x, y, width, height); 823 } 824 825 /** 826 * @return <code>true</code> if flyout is open or expanded. 827 */ 828 private boolean isOpenExpanded() { 829 int state = m_preferences.getState(); 830 return state == IFlyoutPreferences.STATE_OPEN || state == IFlyoutPreferences.STATE_EXPANDED; 831 } 832 833 //////////////////////////////////////////////////////////////////////////// 834 // 835 // Title image 836 // 837 //////////////////////////////////////////////////////////////////////////// 838 private int m_titleWidth; 839 private int m_titleHeight; 840 private Image m_titleImage; 841 private Image m_titleImageRotated; 842 843 /** 844 * Creates {@link Image} for given title text. 845 */ 846 private void updateTitleImage(String text) { 847 // prepare size of text 848 Point textSize; 849 { 850 GC gc = new GC(this); 851 gc.setFont(TITLE_FONT); 852 textSize = gc.textExtent(text); 853 gc.dispose(); 854 } 855 // dispose existing image 856 if (m_titleImage != null) { 857 m_titleImage.dispose(); 858 m_titleImageRotated.dispose(); 859 } 860 // prepare new image 861 { 862 m_titleWidth = textSize.x + 2 * TITLE_LINES + 4 * TITLE_MARGIN; 863 m_titleHeight = textSize.y; 864 m_titleImage = new Image(getDisplay(), m_titleWidth, m_titleHeight); 865 GC gc = new GC(m_titleImage); 866 try { 867 gc.setBackground(getBackground()); 868 gc.fillRectangle(0, 0, m_titleWidth, m_titleHeight); 869 int x = 0; 870 // draw left lines 871 { 872 x += TITLE_MARGIN; 873 drawTitleLines(gc, x, m_titleHeight, TITLE_LINES); 874 x += TITLE_LINES + TITLE_MARGIN; 875 } 876 // draw text 877 { 878 gc.setForeground(IColorConstants.black); 879 gc.setFont(TITLE_FONT); 880 gc.drawText(text, x, 0); 881 x += textSize.x; 882 } 883 // draw right lines 884 { 885 x += TITLE_MARGIN; 886 drawTitleLines(gc, x, m_titleHeight, TITLE_LINES); 887 } 888 } finally { 889 gc.dispose(); 890 } 891 } 892 // prepare rotated image 893 m_titleImageRotated = DrawUtils.createRotatedImage(m_titleImage); 894 } 895 896 /** 897 * Draws two title lines. 898 */ 899 private void drawTitleLines(GC gc, int x, int height, int width) { 900 drawTitleLine(gc, x, height / 3, width); 901 drawTitleLine(gc, x, 2 * height / 3, width); 902 } 903 904 /** 905 * Draws single title line. 906 */ 907 private void drawTitleLine(GC gc, int x, int y, int width) { 908 int right = x + TITLE_LINES; 909 // 910 gc.setForeground(IColorConstants.buttonLightest); 911 gc.drawLine(x, y, right - 2, y); 912 gc.drawLine(x, y + 1, right - 2, y + 1); 913 // 914 gc.setForeground(IColorConstants.buttonDarker); 915 gc.drawLine(right - 2, y, right - 1, y); 916 gc.drawLine(x + 2, y + 1, right - 2, y + 1); 917 } 918 919 //////////////////////////////////////////////////////////////////////////// 920 // 921 // Menu 922 // 923 //////////////////////////////////////////////////////////////////////////// 924 private void configureMenu() { 925 final MenuManager manager = new MenuManager(); 926 manager.setRemoveAllWhenShown(true); 927 manager.addMenuListener(new IMenuListener() { 928 @Override 929 public void menuAboutToShow(IMenuManager menuMgr) { 930 addDockActions(); 931 for (IFlyoutMenuContributor contributor : m_menuContributors) { 932 contributor.contribute(manager); 933 } 934 } 935 936 private void addDockActions() { 937 MenuManager dockManager = new MenuManager(Messages.FlyoutControlComposite_dockManager); 938 addDockAction( 939 dockManager, 940 Messages.FlyoutControlComposite_dockLeft, 941 IFlyoutPreferences.DOCK_WEST); 942 addDockAction( 943 dockManager, 944 Messages.FlyoutControlComposite_dockRight, 945 IFlyoutPreferences.DOCK_EAST); 946 addDockAction( 947 dockManager, 948 Messages.FlyoutControlComposite_dockTop, 949 IFlyoutPreferences.DOCK_NORTH); 950 addDockAction( 951 dockManager, 952 Messages.FlyoutControlComposite_dockBottom, 953 IFlyoutPreferences.DOCK_SOUTH); 954 manager.add(dockManager); 955 } 956 957 private void addDockAction(MenuManager dockManager, String text, int location) { 958 if ((m_validDockLocations & location) != 0) { 959 dockManager.add(new DockAction(text, location)); 960 } 961 } 962 }); 963 // set menu 964 setMenu(manager.createContextMenu(this)); 965 // dispose it later 966 addDisposeListener(new DisposeListener() { 967 @Override 968 public void widgetDisposed(DisposeEvent e) { 969 manager.dispose(); 970 } 971 }); 972 } 973 } 974 //////////////////////////////////////////////////////////////////////////// 975 // 976 // DockAction 977 // 978 //////////////////////////////////////////////////////////////////////////// 979 private class DockAction extends Action { 980 private final int m_location; 981 982 //////////////////////////////////////////////////////////////////////////// 983 // 984 // Constructor 985 // 986 //////////////////////////////////////////////////////////////////////////// 987 public DockAction(String text, int location) { 988 super(text, AS_RADIO_BUTTON); 989 m_location = location; 990 } 991 992 //////////////////////////////////////////////////////////////////////////// 993 // 994 // Action 995 // 996 //////////////////////////////////////////////////////////////////////////// 997 @Override 998 public boolean isChecked() { 999 return getDockLocation() == m_location; 1000 } 1001 1002 @Override 1003 public void run() { 1004 setDockLocation(m_location); 1005 } 1006 } 1007 } 1008