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