Home | History | Annotate | Download | only in flyout
      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