Merge "Add support for window docking in the property sheet code"
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
   //
   ////////////////////////////////////////////////////////////////////////////