Add support for window docking in the property sheet code

This checkin migrates more of the WindowBuilder code into the property
sheet library; in particular, it adds the window docking code for use
with the property sheet in ADT. There are also some modifications to
the code to support ADT use cases, documented in the updated README.

Change-Id: I38e0e2137fdcc179aa6f8927d201ff002c831617
diff --git a/propertysheet/README.txt b/propertysheet/README.txt
index 6c2feab..a61ac9c 100644
--- a/propertysheet/README.txt
+++ b/propertysheet/README.txt
@@ -74,6 +74,22 @@
   default" (and for certain categories to be excluded, such as
   deprecations).
 
+WINDOW DOCKING
+---------------
+
+The window docking support (the "FlyoutControlComposite" and
+supporting classes) was also included, since it's used to present the
+property sheet view in ADT. This code was also modified in a couple of
+minor ways, using the same modification markers as above:
+- Support invisible children (where the whole flyout is hidden)
+- Added a "dismiss hover" method used to hide a temporary hover
+  (needed when the hovers are used with native drag & drop)
+- Added a listener interface and notification when window states chane
+  (used to auto-zoom the layout canvas when windows are collapsed or
+  expanded).
+- Changed the sizeall cursor used for dragging composites from the SWT
+  SIZE_ALL cursor to the HAND cursor since (at least on Mac) the
+  cursor looked wrong for docking.
 
 UPDATES
 --------
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/Messages.java b/propertysheet/src/org/eclipse/wb/core/controls/Messages.java
index 3d83ffc..d745db5 100644
--- a/propertysheet/src/org/eclipse/wb/core/controls/Messages.java
+++ b/propertysheet/src/org/eclipse/wb/core/controls/Messages.java
@@ -6,6 +6,12 @@
   private static final String BUNDLE_NAME = "org.eclipse.wb.core.controls.messages"; //$NON-NLS-1$
   public static String CSpinner_canNotParse;
   public static String CSpinner_outOfRange;
+  public static String FlyoutControlComposite_dockBottom;
+  public static String FlyoutControlComposite_dockLeft;
+  public static String FlyoutControlComposite_dockManager;
+  public static String FlyoutControlComposite_dockRight;
+  public static String FlyoutControlComposite_dockTop;
+  public static String FlyoutControlComposite_title;
   static {
     // initialize resource bundle
     NLS.initializeMessages(BUNDLE_NAME, Messages.class);
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/SelfOrientingSashForm.java b/propertysheet/src/org/eclipse/wb/core/controls/SelfOrientingSashForm.java
new file mode 100644
index 0000000..ea94304
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/SelfOrientingSashForm.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.core.controls;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * Instances of the class <code>SelfOrientingSashForm</code> implement a sash form that will
+ * automatically reset its orientation based on the relationship between the width and height of the
+ * client area. This is done so that the sash form can be placed in a view that will sometimes be
+ * tall and narrow and sometimes be short and wide and still lay out its children in a pleasing way.
+ * <p>
+ * 
+ * @author unknown
+ * @author Brian Wilkerson
+ * @version $Revision: 1.2 $
+ * @coverage core.control
+ */
+public class SelfOrientingSashForm extends SashForm {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructors
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Initialize a newly created control to have the given parent and style. The style describes the
+   * behavior and appearance of this control.
+   * <p>
+   * The style value is either one of the style constants defined in the class <code>SWT</code>
+   * which is applicable to instances of this class, or must be built by <em>bitwise OR</em>'ing
+   * together (that is, using the <code>int</code> "|" operator) two or more of those
+   * <code>SWT</code> style constants. The class description for all SWT widget classes should
+   * include a comment which describes the style constants which are applicable to the class.
+   * </p>
+   * 
+   * @param parent
+   *          a widget which will be the parent of the new instance (not null)
+   * @param style
+   *          the style of widget to construct
+   * 
+   * @exception IllegalArgumentException
+   *              <ul>
+   *              <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
+   *              </ul>
+   * @exception SWTException
+   *              <ul>
+   *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the
+   *              parent</li>
+   *              <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
+   *              </ul>
+   */
+  public SelfOrientingSashForm(Composite parent, int style) {
+    super(parent, style);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Layout
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Returns SWT.HORIZONTAL if the controls in the SashForm are laid out side by side or
+   * SWT.VERTICAL if the controls in the SashForm are laid out top to bottom.
+   * 
+   * @return SWT.HORIZONTAL or SWT.VERTICAL
+   */
+  @Override
+  public int getOrientation() {
+    int currentOrientation = super.getOrientation();
+    if (inSetOrientation) {
+      return currentOrientation;
+    }
+    int preferredOrientation = isDisposed() ? currentOrientation : getPreferredOrientation();
+    if (currentOrientation != preferredOrientation) {
+      setOrientation(preferredOrientation);
+    }
+    return preferredOrientation;
+  }
+
+  boolean inSetOrientation = false;
+
+  @Override
+  public void setOrientation(int orientation) {
+    if (inSetOrientation) {
+      return;
+    }
+    inSetOrientation = true;
+    super.setOrientation(orientation);
+    inSetOrientation = false;
+  }
+
+  /**
+   * If the receiver has a layout, ask the layout to <em>lay out</em> (that is, set the size and
+   * location of) the receiver's children. If the argument is <code>true</code> the layout must not
+   * rely on any cached information it is keeping about the children. If it is <code>false</code>
+   * the layout may (potentially) simplify the work it is doing by assuming that the state of the
+   * none of the receiver's children has changed since the last layout. If the receiver does not
+   * have a layout, do nothing.
+   * 
+   * @param changed
+   *          <code>true</code> if the layout must flush its caches, and <code>false</code>
+   *          otherwise
+   * 
+   * @exception SWTException
+   *              <ul>
+   *              <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+   *              <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the
+   *              receiver</li>
+   *              </ul>
+   */
+  @Override
+  public void layout(boolean changed) {
+    Rectangle area;
+    int oldOrientation, newOrientation;
+    area = getClientArea();
+    if (area.width > 0 && area.height > 0) {
+      oldOrientation = super.getOrientation();
+      newOrientation = SWT.HORIZONTAL;
+      if (area.width < area.height) {
+        newOrientation = SWT.VERTICAL;
+      }
+      if (newOrientation != oldOrientation) {
+        setOrientation(newOrientation);
+        changed = true;
+      }
+    }
+    super.layout(changed);
+  }
+
+  private int getPreferredOrientation() {
+    Rectangle area = getClientArea();
+    if (area.width > 0 && area.height > 0 && area.width < area.height) {
+      return SWT.VERTICAL;
+    }
+    return SWT.HORIZONTAL;
+  }
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/flyout/FlyoutControlComposite.java b/propertysheet/src/org/eclipse/wb/core/controls/flyout/FlyoutControlComposite.java
new file mode 100644
index 0000000..23d2f83
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/flyout/FlyoutControlComposite.java
@@ -0,0 +1,1007 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.core.controls.flyout;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IMenuListener;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.events.MouseAdapter;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.events.MouseTrackAdapter;
+import org.eclipse.swt.graphics.Font;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Sash;
+import org.eclipse.swt.widgets.Tracker;
+import org.eclipse.wb.core.controls.Messages;
+import org.eclipse.wb.draw2d.IColorConstants;
+import org.eclipse.wb.draw2d.ICursorConstants;
+import org.eclipse.wb.internal.core.utils.ui.DrawUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link FlyoutControlComposite} is container for two {@link Control}'s. One (client control) is
+ * used to fill client area. Second (flyout control) can be docked to any enabled position or
+ * temporary hidden.
+ *
+ * @author scheglov_ke
+ * @coverage core.control
+ */
+public final class FlyoutControlComposite extends Composite {
+  private static final int RESIZE_WIDTH = 5;
+  private static final int TITLE_LINES = 30;
+  private static final int TITLE_MARGIN = 5;
+  private static final Font TITLE_FONT = JFaceResources.getFontRegistry().getBold(
+      JFaceResources.DEFAULT_FONT);
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Images
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private static final Image PIN = loadImage("icons/pin.gif");
+  private static final Image ARROW_LEFT = loadImage("icons/arrow_left.gif");
+  private static final Image ARROW_RIGHT = loadImage("icons/arrow_right.gif");
+  private static final Image ARROW_TOP = loadImage("icons/arrow_top.gif");
+  private static final Image ARROW_BOTTOM = loadImage("icons/arrow_bottom.gif");
+
+  private static Image loadImage(String path) {
+    return DrawUtils.loadImage(FlyoutControlComposite.class, path);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Instance fields
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private final IFlyoutPreferences m_preferences;
+  private final FlyoutContainer m_flyoutContainer;
+  private int m_minWidth = 150;
+  private int m_validDockLocations = -1;
+  private final List<IFlyoutMenuContributor> m_menuContributors =
+      new ArrayList<IFlyoutMenuContributor>();
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public FlyoutControlComposite(Composite parent, int style, IFlyoutPreferences preferences) {
+    super(parent, style);
+    m_preferences = preferences;
+    // add listeners
+    addListener(SWT.Resize, new Listener() {
+      @Override
+    public void handleEvent(Event event) {
+        if (getShell().getMinimized()) {
+          return;
+        }
+        layout();
+      }
+    });
+    // create container for flyout control
+    m_flyoutContainer = new FlyoutContainer(this, SWT.NO_BACKGROUND);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Parents
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return the parent {@link Composite} for flyout {@link Control}.
+   */
+  public Composite getFlyoutParent() {
+    return m_flyoutContainer;
+  }
+
+  /**
+   * @return the parent {@link Composite} for client {@link Control}.
+   */
+  public Composite getClientParent() {
+    return this;
+  }
+
+  /**
+   * Sets the bit set with valid docking locations.
+   */
+  public void setValidDockLocations(int validDockLocations) {
+    m_validDockLocations = validDockLocations;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Sets the minimal width of flyout.
+   */
+  public void setMinWidth(int minWidth) {
+    m_minWidth = minWidth;
+  }
+
+  /**
+   * Sets the text of title.
+   */
+  public void setTitleText(String text) {
+    m_flyoutContainer.setTitleText(text);
+  }
+
+  /**
+   * Adds new {@link IFlyoutMenuContributor}.
+   */
+  public void addMenuContributor(IFlyoutMenuContributor contributor) {
+    if (!m_menuContributors.contains(contributor)) {
+      m_menuContributors.add(contributor);
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Layout
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  @Override
+  public void layout() {
+    Rectangle clientArea = getClientArea();
+    int state = m_preferences.getState();
+    Control client = getChildren()[1];
+    // check, may be "clientArea" is empty, for example because CTabFolder page is not visible
+    if (clientArea.width == 0 || clientArea.height == 0) {
+      return;
+    }
+    // check, maybe flyout has no Control, so "client" should fill client area
+    if (m_flyoutContainer.getControl() == null
+            // BEGIN ADT MODIFICATIONS
+            || !m_flyoutContainer.getControl().getVisible()
+            // END ADT MODIFICATIONS
+            ) {
+      m_flyoutContainer.setBounds(0, 0, 0, 0);
+      client.setBounds(clientArea);
+      return;
+    }
+    // prepare width to display
+    int width;
+    int offset;
+    if (state == IFlyoutPreferences.STATE_OPEN) {
+      width = m_preferences.getWidth();
+      // limit maximum value
+      if (isHorizontal()) {
+        width = Math.min(clientArea.width / 2, width);
+      } else {
+        width = Math.min(clientArea.height / 2, width);
+      }
+      // limit minimum value
+      width = Math.max(width, m_minWidth);
+      width = Math.max(width, 2 * m_flyoutContainer.m_titleHeight + m_flyoutContainer.m_titleWidth);
+      // remember actual width
+      m_preferences.setWidth(width);
+      //
+      offset = width;
+    } else if (state == IFlyoutPreferences.STATE_EXPANDED) {
+      offset = m_flyoutContainer.m_titleHeight;
+      width = m_preferences.getWidth();
+    } else {
+      width = m_flyoutContainer.m_titleHeight;
+      offset = width;
+    }
+    // change bounds for flyout container and client control
+    {
+      if (isWest()) {
+        m_flyoutContainer.setBounds(0, 0, width, clientArea.height);
+        client.setBounds(offset, 0, clientArea.width - offset, clientArea.height);
+      } else if (isEast()) {
+        m_flyoutContainer.setBounds(clientArea.width - width, 0, width, clientArea.height);
+        client.setBounds(0, 0, clientArea.width - offset, clientArea.height);
+      } else if (isNorth()) {
+        m_flyoutContainer.setBounds(0, 0, clientArea.width, width);
+        client.setBounds(0, offset, clientArea.width, clientArea.height - offset);
+      } else if (isSouth()) {
+        m_flyoutContainer.setBounds(0, clientArea.height - width, clientArea.width, width);
+        client.setBounds(0, 0, clientArea.width, clientArea.height - offset);
+      }
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Internal utils
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private boolean isHorizontal() {
+    return isWest() || isEast();
+  }
+
+  private boolean isWest() {
+    return getDockLocation() == IFlyoutPreferences.DOCK_WEST;
+  }
+
+  private boolean isEast() {
+    return getDockLocation() == IFlyoutPreferences.DOCK_EAST;
+  }
+
+  private boolean isNorth() {
+    return getDockLocation() == IFlyoutPreferences.DOCK_NORTH;
+  }
+
+  private boolean isSouth() {
+    return getDockLocation() == IFlyoutPreferences.DOCK_SOUTH;
+  }
+
+  /**
+   * @return <code>true</code> if given docking location is valid.
+   */
+  private boolean isValidDockLocation(int location) {
+    return (location & m_validDockLocations) == location;
+  }
+
+  /**
+   * @return current docking location.
+   */
+  private int getDockLocation() {
+    return m_preferences.getDockLocation();
+  }
+
+  /**
+   * Sets new docking location.
+   */
+  private void setDockLocation(int dockLocation) {
+    m_preferences.setDockLocation(dockLocation);
+    layout();
+  }
+
+  // BEGIN ADT MODIFICATIONS
+  /** If the flyout hover is showing, dismiss it */
+  public void dismissHover() {
+      if (m_flyoutContainer != null) {
+          m_flyoutContainer.dismissHover();
+      }
+  }
+
+  /** Sets a listener to be modified when windows are opened, collapsed and expanded */
+  public void setListener(IFlyoutListener listener) {
+      assert m_listener == null; // Only one listener supported
+      m_listener = listener;
+  }
+  private IFlyoutListener m_listener;
+  // END ADT MODIFICATIONS
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // FlyoutContainer
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Container for flyout {@link Control}.
+   *
+   * @author scheglov_ke
+   */
+  private final class FlyoutContainer extends Composite {
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Container
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    public FlyoutContainer(Composite parent, int style) {
+      super(parent, style);
+      configureMenu();
+      updateTitleImage("Flyout");
+      // add listeners
+      addListener(SWT.Dispose, new Listener() {
+        @Override
+        public void handleEvent(Event event) {
+          if (m_titleImage != null) {
+            m_titleImage.dispose();
+            m_titleImageRotated.dispose();
+            m_titleImage = null;
+            m_titleImageRotated = null;
+          }
+          if (m_backImage != null) {
+            m_backImage.dispose();
+            m_backImage = null;
+          }
+        }
+      });
+      {
+        Listener listener = new Listener() {
+          @Override
+        public void handleEvent(Event event) {
+            layout();
+          }
+        };
+        addListener(SWT.Move, listener);
+        addListener(SWT.Resize, listener);
+      }
+      addListener(SWT.Paint, new Listener() {
+        @Override
+        public void handleEvent(Event event) {
+          handlePaint(event.gc);
+        }
+      });
+      // mouse listeners
+      addMouseListener(new MouseAdapter() {
+        @Override
+        public void mouseDown(MouseEvent event) {
+          if (event.button == 1) {
+            handle_mouseDown(event);
+          }
+        }
+
+        @Override
+        public void mouseUp(MouseEvent event) {
+          if (event.button == 1) {
+            handle_mouseUp(event);
+          }
+        }
+      });
+      addMouseTrackListener(new MouseTrackAdapter() {
+        @Override
+        public void mouseExit(MouseEvent e) {
+          m_stateHover = false;
+          redraw();
+          setCursor(null);
+        }
+
+        @Override
+        public void mouseHover(MouseEvent e) {
+          handle_mouseHover();
+        }
+      });
+      addMouseMoveListener(new MouseMoveListener() {
+        @Override
+        public void mouseMove(MouseEvent event) {
+          handle_mouseMove(event);
+        }
+      });
+    }
+
+    // BEGIN ADT MODIFICATIONS
+    private void dismissHover() {
+      int state = m_preferences.getState();
+      if (state == IFlyoutPreferences.STATE_EXPANDED) {
+        state = IFlyoutPreferences.STATE_COLLAPSED;
+        m_preferences.setState(state);
+        redraw();
+        FlyoutControlComposite.this.layout();
+        if (m_listener != null) {
+            m_listener.stateChanged(IFlyoutPreferences.STATE_EXPANDED, state);
+        }
+      }
+    }
+    // END END MODIFICATIONS
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Events: mouse
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    private boolean m_resize;
+    private boolean m_stateHover;
+
+    /**
+     * Handler for {@link SWT#MouseDown} event.
+     */
+    private void handle_mouseDown(MouseEvent event) {
+      if (m_stateHover) {
+        int state = m_preferences.getState();
+        // BEGIN ADT MODIFICATIONS
+        int oldState = state;
+        // END ADT MODIFICATIONS
+        if (state == IFlyoutPreferences.STATE_OPEN) {
+          state = IFlyoutPreferences.STATE_COLLAPSED;
+        } else {
+          state = IFlyoutPreferences.STATE_OPEN;
+        }
+        m_preferences.setState(state);
+        redraw();
+        FlyoutControlComposite.this.layout();
+        // BEGIN ADT MODIFICATIONS
+        if (m_listener != null) {
+          m_listener.stateChanged(oldState, state);
+        }
+        // END ADT MODIFICATIONS
+      } else if (getCursor() == ICursorConstants.SIZEWE || getCursor() == ICursorConstants.SIZENS) {
+        m_resize = true;
+      } else if (getCursor() == ICursorConstants.SIZEALL) {
+        handleDocking();
+      }
+    }
+
+    /**
+     * Handler for {@link SWT#MouseUp} event.
+     */
+    private void handle_mouseUp(MouseEvent event) {
+      if (m_resize) {
+        m_resize = false;
+        handle_mouseMove(event);
+      }
+    }
+
+    /**
+     * Handler for {@link SWT#MouseMove} event.
+     */
+    private void handle_mouseMove(MouseEvent event) {
+      final FlyoutControlComposite container = FlyoutControlComposite.this;
+      if (m_resize) {
+        // prepare width
+        int width;
+        if (isHorizontal()) {
+          width = getSize().x;
+        } else {
+          width = getSize().y;
+        }
+        // prepare new width
+        int newWidth = width;
+        if (isWest()) {
+          newWidth = event.x + RESIZE_WIDTH / 2;
+        } else if (isEast()) {
+          newWidth = width - event.x + RESIZE_WIDTH / 2;
+        } else if (isNorth()) {
+          newWidth = event.y + RESIZE_WIDTH / 2;
+        } else if (isSouth()) {
+          newWidth = width - event.y + RESIZE_WIDTH / 2;
+        }
+        // update width
+        if (newWidth != width) {
+          m_preferences.setWidth(newWidth);
+          redraw();
+          container.layout();
+        }
+      } else {
+        Rectangle clientArea = getClientArea();
+        boolean inside = clientArea.contains(event.x, event.y);
+        int x = event.x;
+        int y = event.y;
+        if (inside) {
+          // check for state
+          {
+            boolean oldStateHover = m_stateHover;
+            if (isEast()) {
+              m_stateHover = x > clientArea.width - m_titleHeight && y < m_titleHeight;
+            } else {
+              m_stateHover = x < m_titleHeight && y < m_titleHeight;
+            }
+            if (m_stateHover != oldStateHover) {
+              redraw();
+            }
+            if (m_stateHover) {
+              setCursor(null);
+              return;
+            }
+          }
+          // check for resize band
+          if (isOpenExpanded()) {
+            if (isWest() && x >= clientArea.width - RESIZE_WIDTH) {
+              setCursor(ICursorConstants.SIZEWE);
+            } else if (isEast() && x <= RESIZE_WIDTH) {
+              setCursor(ICursorConstants.SIZEWE);
+            } else if (isNorth() && y >= clientArea.height - RESIZE_WIDTH) {
+              setCursor(ICursorConstants.SIZENS);
+            } else if (isSouth() && y <= RESIZE_WIDTH) {
+              setCursor(ICursorConstants.SIZENS);
+            } else {
+              setCursor(null);
+            }
+          }
+          // check for docking
+          if (getCursor() == null) {
+            setCursor(ICursorConstants.SIZEALL);
+          }
+        } else {
+          setCursor(null);
+        }
+      }
+    }
+
+    /**
+     * Handler for {@link SWT#MouseHover} event - temporary expands flyout and collapse again when
+     * mouse moves above client.
+     */
+    private void handle_mouseHover() {
+      if (m_preferences.getState() == IFlyoutPreferences.STATE_COLLAPSED && !m_stateHover) {
+        m_preferences.setState(IFlyoutPreferences.STATE_EXPANDED);
+        //
+        final FlyoutControlComposite container = FlyoutControlComposite.this;
+        container.layout();
+        // BEGIN ADT MODIFICATIONS
+        if (m_listener != null) {
+            m_listener.stateChanged(IFlyoutPreferences.STATE_COLLAPSED,
+                    IFlyoutPreferences.STATE_EXPANDED);
+        }
+        // END ADT MODIFICATIONS
+        // add listeners
+        Listener listener = new Listener() {
+          @Override
+        public void handleEvent(Event event) {
+            if (event.type == SWT.Dispose) {
+              getDisplay().removeFilter(SWT.MouseMove, this);
+            } else {
+              Point p = ((Control) event.widget).toDisplay(event.x, event.y);
+              // during resize mouse can be temporary outside of flyout - ignore
+              if (m_resize) {
+                return;
+              }
+              // mouse in in flyout container - ignore
+              if (getClientArea().contains(toControl(p.x, p.y))) {
+                return;
+              }
+              // mouse is in full container - collapse
+              if (container.getClientArea().contains(container.toControl(p.x, p.y))) {
+                getDisplay().removeFilter(SWT.MouseMove, this);
+                // it is possible, that user restored (OPEN) flyout, so collapse only if we still in expand state
+                if (m_preferences.getState() == IFlyoutPreferences.STATE_EXPANDED) {
+                  m_preferences.setState(IFlyoutPreferences.STATE_COLLAPSED);
+                  container.layout();
+                  // BEGIN ADT MODIFICATIONS
+                  if (m_listener != null) {
+                      m_listener.stateChanged(IFlyoutPreferences.STATE_EXPANDED,
+                              IFlyoutPreferences.STATE_COLLAPSED);
+                  }
+                  // END ADT MODIFICATIONS
+                }
+              }
+            }
+          }
+        };
+        addListener(SWT.Dispose, listener);
+        getDisplay().addFilter(SWT.MouseMove, listener);
+      }
+    }
+
+    /**
+     * Handler for docking.
+     */
+    private void handleDocking() {
+      final FlyoutControlComposite container = FlyoutControlComposite.this;
+      final int width = m_preferences.getWidth();
+      final int oldDockLocation = getDockLocation();
+      final int[] newDockLocation = new int[]{oldDockLocation};
+      final Tracker dockingTracker = new Tracker(container, SWT.NONE);
+      dockingTracker.setRectangles(new Rectangle[]{getBounds()});
+      dockingTracker.setStippled(true);
+      dockingTracker.addListener(SWT.Move, new Listener() {
+        @Override
+        public void handleEvent(Event event2) {
+          Rectangle clientArea = container.getClientArea();
+          Point location = container.toControl(event2.x, event2.y);
+          int h3 = clientArea.height / 3;
+          // check locations
+          if (location.y < h3 && isValidDockLocation(IFlyoutPreferences.DOCK_NORTH)) {
+            dockingTracker.setRectangles(new Rectangle[]{new Rectangle(0,
+                0,
+                clientArea.width,
+                width)});
+            newDockLocation[0] = IFlyoutPreferences.DOCK_NORTH;
+          } else if (location.y > 2 * h3 && isValidDockLocation(IFlyoutPreferences.DOCK_SOUTH)) {
+            dockingTracker.setRectangles(new Rectangle[]{new Rectangle(0,
+                clientArea.height - width,
+                clientArea.width,
+                width)});
+            newDockLocation[0] = IFlyoutPreferences.DOCK_SOUTH;
+          } else if (location.x < clientArea.width / 2
+              && isValidDockLocation(IFlyoutPreferences.DOCK_WEST)) {
+            dockingTracker.setRectangles(new Rectangle[]{new Rectangle(0,
+                0,
+                width,
+                clientArea.height)});
+            newDockLocation[0] = IFlyoutPreferences.DOCK_WEST;
+          } else if (isValidDockLocation(IFlyoutPreferences.DOCK_EAST)) {
+            dockingTracker.setRectangles(new Rectangle[]{new Rectangle(clientArea.width - width,
+                0,
+                width,
+                clientArea.height)});
+            newDockLocation[0] = IFlyoutPreferences.DOCK_EAST;
+          } else {
+            dockingTracker.setRectangles(new Rectangle[]{getBounds()});
+            newDockLocation[0] = oldDockLocation;
+          }
+        }
+      });
+      // start tracking
+      if (dockingTracker.open()) {
+        setDockLocation(newDockLocation[0]);
+      }
+      // dispose tracker
+      dockingTracker.dispose();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Access
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    /**
+     * @return the {@link Control} installed on this {@link FlyoutControlComposite}, or
+     *         <code>null</code> if there are no any {@link Control}.
+     */
+    private Control getControl() {
+      Control[] children = getChildren();
+      return children.length == 1 ? children[0] : null;
+    }
+
+    /**
+     * Sets the text of title.
+     */
+    public void setTitleText(String text) {
+      updateTitleImage(text);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Layout
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    @Override
+    public void layout() {
+      Control control = getControl();
+      if (control == null) {
+        return;
+      }
+      // OK, we have control, so can continue layout
+      Rectangle clientArea = getClientArea();
+      if (isOpenExpanded()) {
+        if (isWest()) {
+          int y = m_titleHeight;
+          control.setBounds(0, y, clientArea.width - RESIZE_WIDTH, clientArea.height - y);
+        } else if (isEast()) {
+          int y = m_titleHeight;
+          control.setBounds(RESIZE_WIDTH, y, clientArea.width - RESIZE_WIDTH, clientArea.height - y);
+        } else if (isNorth()) {
+          int y = m_titleHeight;
+          control.setBounds(0, y, clientArea.width, clientArea.height - y - RESIZE_WIDTH);
+        } else if (isSouth()) {
+          int y = RESIZE_WIDTH + m_titleHeight;
+          control.setBounds(0, y, clientArea.width, clientArea.height - y);
+        }
+      } else {
+        control.setBounds(0, 0, 0, 0);
+      }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Paint
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    private Image m_backImage;
+
+    /**
+     * Handler for {@link SWT#Paint} event.
+     */
+    private void handlePaint(GC paintGC) {
+      Rectangle clientArea = getClientArea();
+      // prepare back image
+      GC gc;
+      {
+        if (m_backImage == null || !m_backImage.getBounds().equals(clientArea)) {
+          if (m_backImage != null) {
+            m_backImage.dispose();
+          }
+          m_backImage = new Image(getDisplay(), clientArea.width, clientArea.height);
+        }
+        // prepare GC
+        gc = new GC(m_backImage);
+        gc.setBackground(paintGC.getBackground());
+        gc.setForeground(paintGC.getForeground());
+        gc.fillRectangle(clientArea);
+      }
+      //
+      if (isOpenExpanded()) {
+        // draw header
+        {
+          // draw title
+          if (isWest()) {
+            drawStateImage(gc, 0, 0);
+            gc.drawImage(m_titleImage, m_titleHeight, 0);
+          } else if (isEast()) {
+            int x = clientArea.width - m_titleHeight;
+            drawStateImage(gc, x, 0);
+            gc.drawImage(m_titleImage, x - m_titleWidth, 0);
+          } else if (isNorth()) {
+            drawStateImage(gc, 0, 0);
+            gc.drawImage(m_titleImage, m_titleHeight, 0);
+          } else if (isSouth()) {
+            int y = RESIZE_WIDTH;
+            drawStateImage(gc, 0, y);
+            gc.drawImage(m_titleImage, m_titleHeight, y);
+          }
+        }
+        // draw resize band
+        drawResizeBand(gc);
+      } else {
+        if (isHorizontal()) {
+          drawStateImage(gc, 0, 0);
+          gc.drawImage(m_titleImageRotated, 0, m_titleHeight);
+        } else {
+          drawStateImage(gc, 0, 0);
+          gc.drawImage(m_titleImage, m_titleHeight, 0);
+        }
+        DrawUtils.drawHighlightRectangle(gc, 0, 0, clientArea.width, clientArea.height);
+      }
+      // flush back image
+      {
+        gc.dispose();
+        paintGC.drawImage(m_backImage, 0, 0);
+      }
+    }
+
+    /**
+     * Draws the state image (arrow) at given location.
+     */
+    private void drawStateImage(GC gc, int x, int y) {
+      DrawUtils.drawImageCHCV(gc, getStateImage(), x, y, m_titleHeight, m_titleHeight);
+      if (m_stateHover) {
+        DrawUtils.drawHighlightRectangle(gc, x, y, m_titleHeight, m_titleHeight);
+      }
+    }
+
+    /**
+     * @return the {@link Image} corresponding to current state (open or collapsed).
+     */
+    private Image getStateImage() {
+      int location = getDockLocation();
+      int state = m_preferences.getState();
+      if (state == IFlyoutPreferences.STATE_OPEN) {
+        switch (location) {
+          case IFlyoutPreferences.DOCK_WEST :
+            return ARROW_LEFT;
+          case IFlyoutPreferences.DOCK_EAST :
+            return ARROW_RIGHT;
+          case IFlyoutPreferences.DOCK_NORTH :
+            return ARROW_TOP;
+          case IFlyoutPreferences.DOCK_SOUTH :
+            return ARROW_BOTTOM;
+        }
+      } else if (state == IFlyoutPreferences.STATE_EXPANDED) {
+        return PIN;
+      } else {
+        switch (location) {
+          case IFlyoutPreferences.DOCK_WEST :
+            return ARROW_RIGHT;
+          case IFlyoutPreferences.DOCK_EAST :
+            return ARROW_LEFT;
+          case IFlyoutPreferences.DOCK_NORTH :
+            return ARROW_BOTTOM;
+          case IFlyoutPreferences.DOCK_SOUTH :
+            return ARROW_TOP;
+        }
+      }
+      //
+      return null;
+    }
+
+    /**
+     * Draws that resize band, {@link Sash} like.
+     */
+    private void drawResizeBand(GC gc) {
+      Rectangle clientArea = getClientArea();
+      // prepare locations
+      int x, y, width, height;
+      if (isHorizontal()) {
+        if (isWest()) {
+          x = clientArea.width - RESIZE_WIDTH;
+        } else {
+          x = 0;
+        }
+        y = 0;
+        width = RESIZE_WIDTH;
+        height = clientArea.height;
+      } else {
+        x = 0;
+        if (isNorth()) {
+          y = clientArea.height - RESIZE_WIDTH;
+        } else {
+          y = 0;
+        }
+        width = clientArea.width;
+        height = RESIZE_WIDTH;
+      }
+      // draw band
+      DrawUtils.drawHighlightRectangle(gc, x, y, width, height);
+    }
+
+    /**
+     * @return <code>true</code> if flyout is open or expanded.
+     */
+    private boolean isOpenExpanded() {
+      int state = m_preferences.getState();
+      return state == IFlyoutPreferences.STATE_OPEN || state == IFlyoutPreferences.STATE_EXPANDED;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Title image
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    private int m_titleWidth;
+    private int m_titleHeight;
+    private Image m_titleImage;
+    private Image m_titleImageRotated;
+
+    /**
+     * Creates {@link Image} for given title text.
+     */
+    private void updateTitleImage(String text) {
+      // prepare size of text
+      Point textSize;
+      {
+        GC gc = new GC(this);
+        gc.setFont(TITLE_FONT);
+        textSize = gc.textExtent(text);
+        gc.dispose();
+      }
+      // dispose existing image
+      if (m_titleImage != null) {
+        m_titleImage.dispose();
+        m_titleImageRotated.dispose();
+      }
+      // prepare new image
+      {
+        m_titleWidth = textSize.x + 2 * TITLE_LINES + 4 * TITLE_MARGIN;
+        m_titleHeight = textSize.y;
+        m_titleImage = new Image(getDisplay(), m_titleWidth, m_titleHeight);
+        GC gc = new GC(m_titleImage);
+        try {
+          gc.setBackground(getBackground());
+          gc.fillRectangle(0, 0, m_titleWidth, m_titleHeight);
+          int x = 0;
+          // draw left lines
+          {
+            x += TITLE_MARGIN;
+            drawTitleLines(gc, x, m_titleHeight, TITLE_LINES);
+            x += TITLE_LINES + TITLE_MARGIN;
+          }
+          // draw text
+          {
+            gc.setForeground(IColorConstants.black);
+            gc.setFont(TITLE_FONT);
+            gc.drawText(text, x, 0);
+            x += textSize.x;
+          }
+          // draw right lines
+          {
+            x += TITLE_MARGIN;
+            drawTitleLines(gc, x, m_titleHeight, TITLE_LINES);
+          }
+        } finally {
+          gc.dispose();
+        }
+      }
+      // prepare rotated image
+      m_titleImageRotated = DrawUtils.createRotatedImage(m_titleImage);
+    }
+
+    /**
+     * Draws two title lines.
+     */
+    private void drawTitleLines(GC gc, int x, int height, int width) {
+      drawTitleLine(gc, x, height / 3, width);
+      drawTitleLine(gc, x, 2 * height / 3, width);
+    }
+
+    /**
+     * Draws single title line.
+     */
+    private void drawTitleLine(GC gc, int x, int y, int width) {
+      int right = x + TITLE_LINES;
+      //
+      gc.setForeground(IColorConstants.buttonLightest);
+      gc.drawLine(x, y, right - 2, y);
+      gc.drawLine(x, y + 1, right - 2, y + 1);
+      //
+      gc.setForeground(IColorConstants.buttonDarker);
+      gc.drawLine(right - 2, y, right - 1, y);
+      gc.drawLine(x + 2, y + 1, right - 2, y + 1);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Menu
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    private void configureMenu() {
+      final MenuManager manager = new MenuManager();
+      manager.setRemoveAllWhenShown(true);
+      manager.addMenuListener(new IMenuListener() {
+        @Override
+        public void menuAboutToShow(IMenuManager menuMgr) {
+          addDockActions();
+          for (IFlyoutMenuContributor contributor : m_menuContributors) {
+            contributor.contribute(manager);
+          }
+        }
+
+        private void addDockActions() {
+          MenuManager dockManager = new MenuManager(Messages.FlyoutControlComposite_dockManager);
+          addDockAction(
+              dockManager,
+              Messages.FlyoutControlComposite_dockLeft,
+              IFlyoutPreferences.DOCK_WEST);
+          addDockAction(
+              dockManager,
+              Messages.FlyoutControlComposite_dockRight,
+              IFlyoutPreferences.DOCK_EAST);
+          addDockAction(
+              dockManager,
+              Messages.FlyoutControlComposite_dockTop,
+              IFlyoutPreferences.DOCK_NORTH);
+          addDockAction(
+              dockManager,
+              Messages.FlyoutControlComposite_dockBottom,
+              IFlyoutPreferences.DOCK_SOUTH);
+          manager.add(dockManager);
+        }
+
+        private void addDockAction(MenuManager dockManager, String text, int location) {
+          if ((m_validDockLocations & location) != 0) {
+            dockManager.add(new DockAction(text, location));
+          }
+        }
+      });
+      // set menu
+      setMenu(manager.createContextMenu(this));
+      // dispose it later
+      addDisposeListener(new DisposeListener() {
+        @Override
+        public void widgetDisposed(DisposeEvent e) {
+          manager.dispose();
+        }
+      });
+    }
+  }
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // DockAction
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  private class DockAction extends Action {
+    private final int m_location;
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Constructor
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    public DockAction(String text, int location) {
+      super(text, AS_RADIO_BUTTON);
+      m_location = location;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //
+    // Action
+    //
+    ////////////////////////////////////////////////////////////////////////////
+    @Override
+    public boolean isChecked() {
+      return getDockLocation() == m_location;
+    }
+
+    @Override
+    public void run() {
+      setDockLocation(m_location);
+    }
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/flyout/IFlyoutListener.java b/propertysheet/src/org/eclipse/wb/core/controls/flyout/IFlyoutListener.java
new file mode 100644
index 0000000..ed87bb8
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/flyout/IFlyoutListener.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2012 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.core.controls.flyout;
+
+// BEGIN ADT MODIFICATIONS
+/** Interface for listeners interested in window state transitions in flyout windows */
+public interface IFlyoutListener {
+  /**
+   * The flyout has changed state from the old state to the new state (see the
+   * state constants in {@link IFlyoutPreferences})
+   *
+   * @param oldState the old state
+   * @param newState the new state
+   */
+  void stateChanged(int oldState, int newState);
+}
+// END ADT MODIFICATIONS
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/flyout/IFlyoutMenuContributor.java b/propertysheet/src/org/eclipse/wb/core/controls/flyout/IFlyoutMenuContributor.java
new file mode 100644
index 0000000..11a9b08
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/flyout/IFlyoutMenuContributor.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.core.controls.flyout;
+
+import org.eclipse.jface.action.IMenuManager;
+
+/**
+ * Contributes items into {@link IMenuManager} or {@link FlyoutControlComposite}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.control
+ */
+public interface IFlyoutMenuContributor {
+  void contribute(IMenuManager manager);
+}
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/flyout/IFlyoutPreferences.java b/propertysheet/src/org/eclipse/wb/core/controls/flyout/IFlyoutPreferences.java
new file mode 100644
index 0000000..d24edb5
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/flyout/IFlyoutPreferences.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.core.controls.flyout;
+
+/**
+ * Provider for preferences of flyout control of {@link FlyoutControlComposite}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.control
+ */
+public interface IFlyoutPreferences {
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Docking constants
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  int DOCK_WEST = 1;
+  int DOCK_EAST = 2;
+  int DOCK_NORTH = 4;
+  int DOCK_SOUTH = 8;
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // State constants
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  int STATE_OPEN = 0;
+  int STATE_COLLAPSED = 1;
+  int STATE_EXPANDED = 2;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * @return the docking location - {@link #DOCK_WEST}, {@link #DOCK_EAST}, {@link #DOCK_NORTH} or
+   *         {@link #DOCK_SOUTH}.
+   */
+  int getDockLocation();
+
+  /**
+   * @return the state of flyout - {@link #STATE_OPEN} or {@link #STATE_COLLAPSED}.
+   */
+  int getState();
+
+  /**
+   * @return the width of flyout.
+   */
+  int getWidth();
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Modification
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Sets the docking location.
+   */
+  void setDockLocation(int location);
+
+  /**
+   * Sets the state of flyout - {@link #STATE_OPEN} or {@link #STATE_COLLAPSED}.
+   */
+  void setState(int state);
+
+  /**
+   * Sets the width of flyout.
+   */
+  void setWidth(int width);
+}
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/flyout/MemoryFlyoutPreferences.java b/propertysheet/src/org/eclipse/wb/core/controls/flyout/MemoryFlyoutPreferences.java
new file mode 100644
index 0000000..8591589
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/flyout/MemoryFlyoutPreferences.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.core.controls.flyout;
+
+/**
+ * Implementation of {@link IFlyoutPreferences} for keeping settings in memory.
+ * 
+ * @author scheglov_ke
+ * @coverage core.control
+ */
+public final class MemoryFlyoutPreferences implements IFlyoutPreferences {
+  private int m_dockLocation;
+  private int m_state;
+  private int m_width;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public MemoryFlyoutPreferences(int dockLocation, int state, int width) {
+    m_dockLocation = dockLocation;
+    m_state = state;
+    m_width = width;
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // IFlyoutPreferences
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public int getDockLocation() {
+    return m_dockLocation;
+  }
+
+  public int getState() {
+    return m_state;
+  }
+
+  public int getWidth() {
+    return m_width;
+  }
+
+  public void setDockLocation(int location) {
+    m_dockLocation = location;
+  }
+
+  public void setState(int state) {
+    m_state = state;
+  }
+
+  public void setWidth(int width) {
+    m_width = width;
+  }
+}
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/flyout/PluginFlyoutPreferences.java b/propertysheet/src/org/eclipse/wb/core/controls/flyout/PluginFlyoutPreferences.java
new file mode 100644
index 0000000..270dba0
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/flyout/PluginFlyoutPreferences.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.core.controls.flyout;
+
+import org.eclipse.jface.preference.IPreferenceStore;
+
+/**
+ * Implementation of {@link IFlyoutPreferences} for {@link IPreferenceStore}.
+ * 
+ * @author scheglov_ke
+ * @coverage core.control
+ */
+public final class PluginFlyoutPreferences implements IFlyoutPreferences {
+  private final IPreferenceStore m_store;
+  private final String m_dockLocationKey;
+  private final String m_stateKey;
+  private final String m_widthKey;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public PluginFlyoutPreferences(IPreferenceStore store, String prefix) {
+    m_store = store;
+    m_dockLocationKey = prefix + ".flyout.dockLocation";
+    m_stateKey = prefix + ".flyout.state";
+    m_widthKey = prefix + ".flyout.width";
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Initializes defaults using given values.
+   */
+  public void initializeDefaults(int location, int state, int width) {
+    m_store.setDefault(m_dockLocationKey, location);
+    m_store.setDefault(m_stateKey, state);
+    m_store.setDefault(m_widthKey, width);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // IFlyoutPreferences
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public int getDockLocation() {
+    return m_store.getInt(m_dockLocationKey);
+  }
+
+  public int getState() {
+    return m_store.getInt(m_stateKey);
+  }
+
+  public int getWidth() {
+    return m_store.getInt(m_widthKey);
+  }
+
+  public void setDockLocation(int location) {
+    m_store.setValue(m_dockLocationKey, location);
+  }
+
+  public void setState(int state) {
+    m_store.setValue(m_stateKey, state);
+  }
+
+  public void setWidth(int width) {
+    m_store.setValue(m_widthKey, width);
+  }
+}
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/flyout/icons/arrow_bottom.gif b/propertysheet/src/org/eclipse/wb/core/controls/flyout/icons/arrow_bottom.gif
new file mode 100644
index 0000000..4a2df3f
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/flyout/icons/arrow_bottom.gif
Binary files differ
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/flyout/icons/arrow_left.gif b/propertysheet/src/org/eclipse/wb/core/controls/flyout/icons/arrow_left.gif
new file mode 100644
index 0000000..9f7018e
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/flyout/icons/arrow_left.gif
Binary files differ
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/flyout/icons/arrow_right.gif b/propertysheet/src/org/eclipse/wb/core/controls/flyout/icons/arrow_right.gif
new file mode 100644
index 0000000..56fb7de
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/flyout/icons/arrow_right.gif
Binary files differ
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/flyout/icons/arrow_top.gif b/propertysheet/src/org/eclipse/wb/core/controls/flyout/icons/arrow_top.gif
new file mode 100644
index 0000000..eb4e5c2
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/flyout/icons/arrow_top.gif
Binary files differ
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/flyout/icons/pin.gif b/propertysheet/src/org/eclipse/wb/core/controls/flyout/icons/pin.gif
new file mode 100644
index 0000000..61bb170
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/core/controls/flyout/icons/pin.gif
Binary files differ
diff --git a/propertysheet/src/org/eclipse/wb/core/controls/messages.properties b/propertysheet/src/org/eclipse/wb/core/controls/messages.properties
index 75a1ca0..b2b833f 100644
--- a/propertysheet/src/org/eclipse/wb/core/controls/messages.properties
+++ b/propertysheet/src/org/eclipse/wb/core/controls/messages.properties
@@ -1,2 +1,8 @@
 CSpinner_canNotParse=Text "{0}"does not satisfy pattern "{1}" 
 CSpinner_outOfRange=Value {0} is out of range [{1}, {2}]
+FlyoutControlComposite_dockBottom=&Bottom
+FlyoutControlComposite_dockLeft=&Left
+FlyoutControlComposite_dockManager=&Dock On
+FlyoutControlComposite_dockRight=&Right
+FlyoutControlComposite_dockTop=&Top
+FlyoutControlComposite_title=Flyout
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/draw2d/IColorConstants.java b/propertysheet/src/org/eclipse/wb/draw2d/IColorConstants.java
index f12d94e..4246302 100644
--- a/propertysheet/src/org/eclipse/wb/draw2d/IColorConstants.java
+++ b/propertysheet/src/org/eclipse/wb/draw2d/IColorConstants.java
@@ -67,6 +67,7 @@
   Color lightGray = new Color(null, 192, 192, 192);
   Color gray = new Color(null, 128, 128, 128);
   Color darkGray = new Color(null, 64, 64, 64);
+  Color black = new Color(null, 0, 0, 0);
   Color lightBlue = new Color(null, 127, 127, 255);
   Color darkBlue = new Color(null, 0, 0, 127);
 
diff --git a/propertysheet/src/org/eclipse/wb/draw2d/ICursorConstants.java b/propertysheet/src/org/eclipse/wb/draw2d/ICursorConstants.java
index b05035d..273cd46 100644
--- a/propertysheet/src/org/eclipse/wb/draw2d/ICursorConstants.java
+++ b/propertysheet/src/org/eclipse/wb/draw2d/ICursorConstants.java
@@ -24,4 +24,18 @@
    * System resize west-east cursor
    */
   Cursor SIZEWE = new Cursor(null, SWT.CURSOR_SIZEWE);
+  /**
+   * System resize north-south cursor
+   */
+  Cursor SIZENS = new Cursor(null, SWT.CURSOR_SIZENS);
+  /**
+   * System resize all directions cursor.
+   */
+  // BEGIN ADT MODIFICATIONS
+  // The SWT CURSOR_SIZEALL cursor looks wrong; it's cross hairs. Use a hand for resizing
+  // instead. See the icons shown in
+  //  http://www.eclipse.org/articles/Article-SWT-images/graphics-resources.html
+  //Cursor SIZEALL = new Cursor(null, SWT.CURSOR_SIZEALL);
+  Cursor SIZEALL = new Cursor(null, SWT.CURSOR_HAND);
+  // END ADT MODIFICATIONS
 }
\ No newline at end of file
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/editor/structure/IPage.java b/propertysheet/src/org/eclipse/wb/internal/core/editor/structure/IPage.java
new file mode 100644
index 0000000..a86522d
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/editor/structure/IPage.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.editor.structure;
+
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/**
+ * View-like page.
+ * 
+ * @author scheglov_ke
+ * @coverage core.editor.structure
+ */
+public interface IPage {
+  /**
+   * Creates the {@link Control} for this page.
+   */
+  void createControl(Composite parent);
+
+  /**
+   * Disposes this page.
+   * <p>
+   * This is the last method called on the {@link IPage}. Implementors should clean up any resources
+   * associated with the page.
+   * <p>
+   * Note that there is no guarantee that {@link #createControl(Composite)} has been called, so the
+   * control may never have been created.
+   */
+  void dispose();
+
+  /**
+   * @return the {@link Control} of this page, may be <code>null</code>.
+   */
+  Control getControl();
+
+  /**
+   * Allows the page to make contributions to the given {@link IToolBarManager}. The contributions
+   * will be visible when the page is visible. This method is automatically called shortly after
+   * {@link #createControl(Composite)} is called.
+   */
+  void setToolBar(IToolBarManager toolBarManager);
+
+  /**
+   * Asks this page to take focus.
+   */
+  void setFocus();
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/editor/structure/PageSiteComposite.java b/propertysheet/src/org/eclipse/wb/internal/core/editor/structure/PageSiteComposite.java
new file mode 100644
index 0000000..bc57360
--- /dev/null
+++ b/propertysheet/src/org/eclipse/wb/internal/core/editor/structure/PageSiteComposite.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Google, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Google, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.wb.internal.core.editor.structure;
+
+import org.eclipse.jface.action.ToolBarManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.wb.core.controls.CImageLabel;
+import org.eclipse.wb.internal.core.utils.check.Assert;
+import org.eclipse.wb.internal.core.utils.ui.GridDataFactory;
+import org.eclipse.wb.internal.core.utils.ui.GridLayoutFactory;
+
+/**
+ * The site {@link Composite} for {@link IPage}.
+ *
+ * @author scheglov_ke
+ * @coverage core.editor.structure
+ */
+public final class PageSiteComposite extends Composite {
+  private final CImageLabel m_title;
+  private final ToolBarManager m_toolBarManager;
+  private final ToolBar m_toolBar;
+  private IPage m_page;
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Constructor
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  public PageSiteComposite(Composite parent, int style) {
+    super(parent, style);
+    GridLayoutFactory.create(this).noMargins().spacingV(0).columns(2);
+    // title
+    {
+      m_title = new CImageLabel(this, SWT.NONE);
+      GridDataFactory.create(m_title).grabH().fill();
+    }
+    // toolbar
+    {
+      m_toolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT);
+      GridDataFactory.create(m_toolBar).fill();
+      m_toolBarManager = new ToolBarManager(m_toolBar);
+    }
+    // separator
+    {
+      Label separator = new Label(this, SWT.SEPARATOR | SWT.HORIZONTAL);
+      GridDataFactory.create(separator).spanH(2).grabH().fillH();
+    }
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
+  // Access
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Sets the {@link Image} for title;
+   */
+  public void setTitleImage(Image image) {
+    m_title.setImage(image);
+  }
+
+  /**
+   * Sets the text for title.
+   */
+  public void setTitleText(String title) {
+    m_title.setText(title);
+  }
+
+  /**
+   * Sets the {@link IPage} to display.
+   */
+  public void setPage(IPage page) {
+    Assert.isNull(m_page);
+    Assert.isNotNull(page);
+    m_page = page;
+    // create Control
+    m_page.createControl(this);
+    GridDataFactory.create(m_page.getControl()).spanH(2).grab().fill();
+    // set toolbar
+    m_page.setToolBar(m_toolBarManager);
+  }
+
+  // BEGIN ADT MODIFICATIONS
+  public ToolBar getToolBar() {
+      return m_toolBar;
+  }
+  // END ADT MODIFICATIONS
+
+}
diff --git a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/DrawUtils.java b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/DrawUtils.java
index f7cc09d..c22256e 100644
--- a/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/DrawUtils.java
+++ b/propertysheet/src/org/eclipse/wb/internal/core/utils/ui/DrawUtils.java
@@ -262,6 +262,73 @@
 
   ////////////////////////////////////////////////////////////////////////////
   //
+  // Rotated images
+  //
+  ////////////////////////////////////////////////////////////////////////////
+  /**
+   * Returns a new Image that is the given Image rotated left by 90 degrees. The client is
+   * responsible for disposing the returned Image. This method MUST be invoked from the
+   * user-interface (Display) thread.
+   *
+   * @param srcImage
+   *          the Image that is to be rotated left
+   * @return the rotated Image (the client is responsible for disposing it)
+   */
+  public static Image createRotatedImage(Image srcImage) {
+    // prepare Display
+    Display display = Display.getCurrent();
+    if (display == null) {
+      SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);
+    }
+    // rotate ImageData
+    ImageData destData;
+    {
+      ImageData srcData = srcImage.getImageData();
+      if (srcData.depth < 8) {
+        destData = rotatePixelByPixel(srcData);
+      } else {
+        destData = rotateOptimized(srcData);
+      }
+    }
+    // create new image
+    return new Image(display, destData);
+  }
+
+  private static ImageData rotatePixelByPixel(ImageData srcData) {
+    ImageData destData =
+        new ImageData(srcData.height, srcData.width, srcData.depth, srcData.palette);
+    for (int y = 0; y < srcData.height; y++) {
+      for (int x = 0; x < srcData.width; x++) {
+        destData.setPixel(y, srcData.width - x - 1, srcData.getPixel(x, y));
+      }
+    }
+    return destData;
+  }
+
+  private static ImageData rotateOptimized(ImageData srcData) {
+    int bytesPerPixel = Math.max(1, srcData.depth / 8);
+    int destBytesPerLine =
+        ((srcData.height * bytesPerPixel - 1) / srcData.scanlinePad + 1) * srcData.scanlinePad;
+    byte[] newData = new byte[destBytesPerLine * srcData.width];
+    for (int srcY = 0; srcY < srcData.height; srcY++) {
+      for (int srcX = 0; srcX < srcData.width; srcX++) {
+        int destX = srcY;
+        int destY = srcData.width - srcX - 1;
+        int destIndex = destY * destBytesPerLine + destX * bytesPerPixel;
+        int srcIndex = srcY * srcData.bytesPerLine + srcX * bytesPerPixel;
+        System.arraycopy(srcData.data, srcIndex, newData, destIndex, bytesPerPixel);
+      }
+    }
+    return new ImageData(srcData.height,
+        srcData.width,
+        srcData.depth,
+        srcData.palette,
+        srcData.scanlinePad,
+        newData);
+  }
+
+  ////////////////////////////////////////////////////////////////////////////
+  //
   // Colors
   //
   ////////////////////////////////////////////////////////////////////////////