Revision: 6110
Author: j...@google.com
Date: Thu Sep 10 05:47:55 2009
Log: Initial implementations of Stack and Split layout panels, along with a  
few
checkstyle tweaks.
Review: http://gwt-code-reviews.appspot.com/65804
http://code.google.com/p/google-web-toolkit/source/detail?r=6110

Added:
  /trunk/user/javadoc/com/google/gwt/examples/SplitLayoutPanelExample.java
  /trunk/user/javadoc/com/google/gwt/examples/StackLayoutPanelExample.java
  /trunk/user/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java
  /trunk/user/src/com/google/gwt/user/client/ui/StackLayoutPanel.java
Modified:
  /trunk/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java

=======================================
--- /dev/null
+++  
/trunk/user/javadoc/com/google/gwt/examples/SplitLayoutPanelExample.java        
 
Thu Sep 10 05:47:55 2009
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may  
not
+ * use this file except in compliance with the License. You may obtain a  
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,  
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations  
under
+ * the License.
+ */
+package com.google.gwt.examples;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.RootLayoutPanel;
+import com.google.gwt.user.client.ui.SplitLayoutPanel;
+import com.google.gwt.user.client.ui.DockLayoutPanel.Direction;
+
+public class SplitLayoutPanelExample implements EntryPoint {
+
+  public void onModuleLoad() {
+    // Create a three-pane layout with splitters.
+    SplitLayoutPanel p = new SplitLayoutPanel();
+    p.add(new HTML("navigation"), Direction.WEST, 128);
+    p.add(new HTML("list"), Direction.NORTH, 384);
+    p.add(new HTML("details"), Direction.CENTER, 0);
+
+    // Note the explicit call to layout(). This is required for the layout  
to
+    // take effect.
+    p.layout();
+
+    // Attach the LayoutPanel to the RootLayoutPanel. The latter will  
listen for
+    // resize events on the window to ensure that its children are  
informed of
+    // possible size changes.
+    RootLayoutPanel rp = RootLayoutPanel.get();
+    rp.add(p);
+
+    // The RootLayoutPanel also requires that its layout() method be  
explicitly
+    // called for the initial layout to take effect.
+    rp.layout();
+  }
+}
=======================================
--- /dev/null
+++  
/trunk/user/javadoc/com/google/gwt/examples/StackLayoutPanelExample.java        
 
Thu Sep 10 05:47:55 2009
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may  
not
+ * use this file except in compliance with the License. You may obtain a  
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,  
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations  
under
+ * the License.
+ */
+package com.google.gwt.examples;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.RootLayoutPanel;
+import com.google.gwt.user.client.ui.StackLayoutPanel;
+
+public class StackLayoutPanelExample implements EntryPoint {
+
+  public void onModuleLoad() {
+    // Create a three-item stack, with headers sized in EMs.
+    StackLayoutPanel p = new StackLayoutPanel(Unit.EM);
+    p.add(new HTML("this"), new HTML("[this]"), 128);
+    p.add(new HTML("that"), new HTML("[that]"), 384);
+    p.add(new HTML("the other"), new HTML("[the other]"), 0);
+
+    // Note the explicit call to layout(). This is required for the layout  
to
+    // take effect.
+    p.layout();
+
+    // Attach the LayoutPanel to the RootLayoutPanel. The latter will  
listen for
+    // resize events on the window to ensure that its children are  
informed of
+    // possible size changes.
+    RootLayoutPanel rp = RootLayoutPanel.get();
+    rp.add(p);
+
+    // The RootLayoutPanel also requires that its layout() method be  
explicitly
+    // called for the initial layout to take effect.
+    rp.layout();
+  }
+}
=======================================
--- /dev/null
+++ /trunk/user/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java Thu  
Sep 10 05:47:55 2009
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may  
not
+ * use this file except in compliance with the License. You may obtain a  
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,  
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations  
under
+ * the License.
+ */
+package com.google.gwt.user.client.ui;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.Event;
+
+/**
+ * A panel that adds user-positioned splitters between each of its child
+ * widgets.
+ *
+ * <p>
+ * This panel is used in the same way as {...@link DockLayoutPanel}, except  
that
+ * its children's sizes are always specified in {...@link Unit#PX} units, and  
each
+ * pair of child widgets has a splitter between them that the user can  
drag.
+ * </p>
+ *
+ * <p>
+ * This widget will <em>only</em> work in standards mode, which requires
+ * that the HTML page in which it is run have an explicit &lt;!DOCTYPE&gt;
+ * declaration.
+ * </p>
+ *
+ * <p>
+ * NOTE: This class is still very new, and its interface may change without
+ * warning. Use at your own risk.
+ * </p>
+ *
+ * <p>
+ * <h3>Example</h3>
+ * {...@example com.google.gwt.examples.SplitLayoutPanelExample}
+ * </p>
+ *
+ * TODO(jgw):
+ * - RTL Support.
+ * - implement insert().
+ * - Come up with a decent way to specify splitter style and size.
+ */
+public class SplitLayoutPanel extends DockLayoutPanel {
+
+  private class HSplitter extends Splitter {
+    public HSplitter(Widget target, boolean reverse) {
+      super(target, reverse);
+      getElement().getStyle().setPropertyPx("width", 4);
+      setStyleName("LayoutPanel-HDragger");
+    }
+
+    @Override
+    protected int getAbsolutePosition() {
+      return getAbsoluteLeft();
+    }
+
+    @Override
+    protected int getEventPosition(Event event) {
+      return event.getClientX();
+    }
+
+    @Override
+    protected int getTargetPosition() {
+      return target.getAbsoluteLeft();
+    }
+
+    @Override
+    protected int getTargetSize() {
+      return target.getOffsetWidth();
+    }
+  }
+
+  private abstract class Splitter extends Widget {
+    protected final Widget target;
+
+    private int offset;
+    private boolean mouseDown;
+    private Command layoutCommand;
+
+    private final boolean reverse;
+    private int minSize;
+
+    public Splitter(Widget target, boolean reverse) {
+      this.target = target;
+      this.reverse = reverse;
+
+      setElement(Document.get().createDivElement());
+      sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONMOUSEMOVE
+          | Event.ONDBLCLICK);
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+      switch (event.getTypeInt()) {
+        case Event.ONMOUSEDOWN:
+          mouseDown = true;
+          offset = getEventPosition(event) - getAbsolutePosition();
+          Event.setCapture(getElement());
+          event.preventDefault();
+          break;
+
+        case Event.ONMOUSEUP:
+          mouseDown = false;
+          Event.releaseCapture(getElement());
+          event.preventDefault();
+          break;
+
+        case Event.ONMOUSEMOVE:
+          if (mouseDown) {
+            int size;
+            if (reverse) {
+              size = getTargetPosition() + getTargetSize()
+                  - getEventPosition(event) - offset;
+            } else {
+              size = getEventPosition(event) - getTargetPosition() -  
offset;
+            }
+
+            setAssociatedWidgetSize(size);
+            event.preventDefault();
+          }
+          break;
+      }
+    }
+
+    public void setMinSize(int minSize) {
+      this.minSize = minSize;
+      LayoutData layout = (LayoutData) target.getLayoutData();
+
+      // Try resetting the associated widget's size, which will enforce  
the new
+      // minSize value.
+      setAssociatedWidgetSize((int) layout.size);
+    }
+
+    protected abstract int getAbsolutePosition();
+
+    protected abstract int getEventPosition(Event event);
+
+    protected abstract int getTargetPosition();
+
+    protected abstract int getTargetSize();
+
+    private void setAssociatedWidgetSize(int size) {
+      if (size < minSize) {
+        size = minSize;
+      }
+
+      LayoutData layout = (LayoutData) target.getLayoutData();
+      if (size == layout.size) {
+        return;
+      }
+
+      layout.size = size;
+
+      // Defer actually updating the layout, so that if we receive many
+      // mouse events before layout/paint occurs, we'll only update once.
+      if (layoutCommand == null) {
+        layoutCommand = new Command() {
+          public void execute() {
+            layoutCommand = null;
+            layout();
+          }
+        };
+        DeferredCommand.addCommand(layoutCommand);
+      }
+    }
+  }
+
+  private class VSplitter extends Splitter {
+    public VSplitter(Widget target, boolean reverse) {
+      super(target, reverse);
+      getElement().getStyle().setPropertyPx("height", 4);
+      setStyleName("LayoutPanel-VDragger");
+    }
+
+    @Override
+    protected int getAbsolutePosition() {
+      return getAbsoluteTop();
+    }
+
+    @Override
+    protected int getEventPosition(Event event) {
+      return event.getClientY();
+    }
+
+    @Override
+    protected int getTargetPosition() {
+      return target.getAbsoluteTop();
+    }
+
+    @Override
+    protected int getTargetSize() {
+      return target.getOffsetHeight();
+    }
+  }
+
+  private static final int SPLITTER_SIZE = 4;
+
+  public SplitLayoutPanel() {
+    super(Unit.PX);
+  }
+
+  @Override
+  public void add(Widget child, Direction direction, double size) {
+    super.add(child, direction, size);
+    if (direction != Direction.CENTER) {
+      addSplitter();
+    }
+  }
+
+  @Override
+  public boolean remove(Widget child) {
+    assert !(child instanceof Splitter) : "Splitters may not be directly  
removed";
+
+    if (super.remove(child)) {
+      // Remove the associated splitter, if any.
+      int idx = getWidgetIndex(child);
+      if (idx < getWidgetCount() - 1) {
+        remove(idx + 1);
+      }
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Sets the minimum allowable size for the given widget.
+   *
+   * <p>
+   * Its assocated splitter cannot be dragged to a position that would  
make it
+   * smaller than this size. This method has no effect for the
+   * {...@link Direction#CENTER} widget.
+   * </p>
+   *
+   * @param child the child whose minimum size will be set
+   * @param minSize the minimum size for this widget
+   */
+  public void setWidgetMinSize(Widget child, int minSize) {
+    Splitter splitter = getAssociatedSplitter(child);
+    splitter.setMinSize(minSize);
+  }
+
+  private void addSplitter() {
+    assert getChildren().size() > 0 : "Can't add a splitter before any  
children";
+    assert getCenter() == null : "Can't add a splitter after the CENTER  
widget";
+
+    Widget lastChild = getChildren().get(getChildren().size() - 1);
+    LayoutData lastChildLayout = (LayoutData) lastChild.getLayoutData();
+    Splitter splitter;
+    switch (lastChildLayout.direction) {
+      case WEST:
+        splitter = new HSplitter(lastChild, false);
+        break;
+      case EAST:
+        splitter = new HSplitter(lastChild, true);
+        break;
+      case NORTH:
+        splitter = new VSplitter(lastChild, false);
+        break;
+      case SOUTH:
+        splitter = new VSplitter(lastChild, true);
+        break;
+      default:
+        assert false : "Unexpected direction";
+        return;
+    }
+
+    super.add(splitter, lastChildLayout.direction, SPLITTER_SIZE);
+  }
+
+  private Splitter getAssociatedSplitter(Widget child) {
+    // If a widget has a next sibling, it must be a splitter, because the  
only
+    // widget that *isn't* followed by a splitter must be the CENTER,  
which has
+    // no associated splitter.
+    int idx = getWidgetIndex(child);
+    if (idx < getWidgetCount() - 2) {
+      Widget splitter = getWidget(idx + 1);
+      assert splitter instanceof Splitter : "Expected child widget to be  
splitter";
+      return (Splitter) splitter;
+    }
+    return null;
+  }
+}
=======================================
--- /dev/null
+++ /trunk/user/src/com/google/gwt/user/client/ui/StackLayoutPanel.java Thu  
Sep 10 05:47:55 2009
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may  
not
+ * use this file except in compliance with the License. You may obtain a  
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,  
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations  
under
+ * the License.
+ */
+package com.google.gwt.user.client.ui;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.layout.client.Layout.AnimationCallback;
+import com.google.gwt.layout.client.Layout.Layer;
+import com.google.gwt.user.client.Event;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * A panel that stacks its children vertically, displaying only one at a  
time,
+ * with a header for each child which the user can click to display.
+ *
+ * <p>
+ * This widget will <em>only</em> work in standards mode, which requires
+ * that the HTML page in which it is run have an explicit &lt;!DOCTYPE&gt;
+ * declaration.
+ * </p>
+ *
+ * <p>
+ * NOTE: This class is still very new, and its interface may change without
+ * warning. Use at your own risk.
+ * </p>
+ *
+ * <p>
+ * <h3>Example</h3>
+ * {...@example com.google.gwt.examples.StackLayoutPanelExample}
+ * </p>
+ *
+ * TODO(jgw):
+ * - implement insert().
+ * - add() methods with default widgets for headers.
+ * - some way to get the header widget associated with a child.
+ * - make animation configurable (with {...@link HasAnimation}).
+ * - default style.
+ */
+public class StackLayoutPanel extends Composite implements HasWidgets,
+    RequiresLayout, RequiresResize, ProvidesResize {
+
+  private class ClickWrapper extends Composite {
+    private Widget target;
+
+    public ClickWrapper(Widget target, Widget wrappee) {
+      this.target = target;
+      initWidget(wrappee);
+      sinkEvents(Event.ONCLICK);
+    }
+
+    @Override
+    public void onBrowserEvent(Event event) {
+      if (event.getTypeInt() == Event.ONCLICK) {
+        showWidget(target);
+      }
+    }
+  }
+
+  private static class LayoutData {
+    public double headerSize;
+    public Widget header;
+    public Widget widget;
+    public Layer widgetLayer;
+    public Layer headerLayer;
+
+    public LayoutData(Widget widget, Widget header, double headerSize,
+        Layer widgetLayer, Layer headerLayer) {
+      this.widget = widget;
+      this.header = header;
+      this.headerSize = headerSize;
+      this.widgetLayer = widgetLayer;
+      this.headerLayer = headerLayer;
+    }
+  }
+
+  private static final int ANIMATION_TIME = 250;
+
+  private LayoutPanel layoutPanel;
+  private Unit unit;
+  private ArrayList<LayoutData> layoutData = new ArrayList<LayoutData>();
+  private Widget visibleWidget;
+
+  /**
+   * Creates an empty stack panel.
+   *
+   * @param unit the unit to be used for layout
+   */
+  public StackLayoutPanel(Unit unit) {
+    this.unit = unit;
+    initWidget(layoutPanel = new LayoutPanel());
+  }
+
+  public void add(Widget w) {
+    assert false : "Single-argument add() is not supported for this  
widget";
+  }
+
+  /**
+   * Adds a child widget to this stack, along with a widget representing  
the
+   * stack header.
+   *
+   * @param widget the child widget to be added
+   * @param header the header widget
+   * @param headerSize the size of the header widget
+   */
+  public void add(Widget widget, Widget header, double headerSize) {
+    ClickWrapper wrapper = new ClickWrapper(widget, header);
+    layoutPanel.add(wrapper);
+    layoutPanel.add(widget);
+
+    Layer headerLayer = layoutPanel.getLayer(wrapper);
+    headerLayer.setLeftRight(0, Unit.PX, 0, Unit.PX);
+
+    Layer widgetLayer = layoutPanel.getLayer(widget);
+    widgetLayer.setLeftRight(0, Unit.PX, 0, Unit.PX);
+
+    LayoutData data = new LayoutData(widget, wrapper, headerSize,  
widgetLayer,
+        headerLayer);
+    layoutData.add(data);
+
+    if (visibleWidget == null) {
+      visibleWidget = widget;
+    }
+  }
+
+  public void clear() {
+    layoutPanel.clear();
+    visibleWidget = null;
+  }
+
+  public Iterator<Widget> iterator() {
+    return new Iterator<Widget>() {
+      int i = 0, last = -1;
+
+      public boolean hasNext() {
+        return i < layoutData.size();
+      }
+
+      public Widget next() {
+        if (!hasNext()) {
+          throw new NoSuchElementException();
+        }
+        return layoutData.get(last = i++).widget;
+      }
+
+      public void remove() {
+        if (last < 0) {
+          throw new IllegalStateException();
+        }
+
+        StackLayoutPanel.this.remove(layoutData.get(last).widget);
+        i = last;
+        last = -1;
+      }
+    };
+  }
+
+  public void layout() {
+    layout(0);
+  }
+
+  public void layout(int duration) {
+    layout(duration, null);
+  }
+
+  public void layout(int duration, AnimationCallback callback) {
+    int top = 0, bottom = 0;
+    int i = 0, visibleIndex = -1;
+    for (; i < layoutData.size(); ++i) {
+      LayoutData data = layoutData.get(i);
+      data.headerLayer.setTopHeight(top, unit, data.headerSize, unit);
+
+      top += data.headerSize;
+
+      data.widgetLayer.setTopHeight(top, unit, 0, unit);
+
+      if (data.widget == visibleWidget) {
+        visibleIndex = i;
+        break;
+      }
+    }
+
+    assert visibleIndex != -1;
+
+    for (int j = layoutData.size() - 1; j > i; --j) {
+      LayoutData data = layoutData.get(j);
+      data.headerLayer.setBottomHeight(bottom, unit, data.headerSize,  
unit);
+      data.widgetLayer.setBottomHeight(bottom, unit, 0, unit);
+      bottom += data.headerSize;
+    }
+
+    LayoutData data = layoutData.get(visibleIndex);
+    data.widgetLayer.setTopBottom(top, unit, bottom, unit);
+
+    layoutPanel.layout(duration, callback);
+  }
+
+  public void onResize() {
+    layoutPanel.onResize();
+  }
+
+  public boolean remove(Widget child) {
+    if (child.getParent() != this) {
+      return false;
+    }
+
+    LayoutData data = (LayoutData) child.getLayoutData();
+    layoutPanel.remove(data.header);
+    layoutPanel.remove(child);
+    return true;
+  }
+
+  /**
+   * Shows the specified widget.
+   *
+   * @param widget the child widget to be shown.
+   */
+  public void showWidget(Widget widget) {
+    visibleWidget = widget;
+    layout(ANIMATION_TIME);
+  }
+}
=======================================
--- /trunk/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java  Thu  
Sep  3 14:07:51 2009
+++ /trunk/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java  Thu  
Sep 10 05:47:55 2009
@@ -101,6 +101,7 @@
     *
     * @param widget the widget to be added
     * @param direction the widget's direction in the dock
+   * @param size the child widget's size
     *
     * @throws IllegalArgumentException when adding to the {...@link #CENTER}  
and
     *           there is already a different widget there
@@ -114,7 +115,7 @@
     */
    public Element getContainerElementFor(Widget widget) {
      assertIsChild(widget);
-    return  
((LayoutData)widget.getLayoutData()).layer.getContainerElement();
+    return ((LayoutData)  
widget.getLayoutData()).layer.getContainerElement();
    }

    /**

--~--~---------~--~----~------------~-------~--~----~
http://groups.google.com/group/Google-Web-Toolkit-Contributors
-~----------~----~----~----~------~----~------~--~---

Reply via email to