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.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