/*
* Ext GWT - Ext for GWT
* Copyright(c) 2007-2009, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
package com.extjs.gxt.ui.client.widget;
import java.util.ArrayList;
import java.util.List;
import com.extjs.gxt.ui.client.GXT;
import com.extjs.gxt.ui.client.Style.LayoutRegion;
import com.extjs.gxt.ui.client.core.El;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.DragEvent;
import com.extjs.gxt.ui.client.event.EventType;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.SplitBarEvent;
import com.extjs.gxt.ui.client.fx.Draggable;
import com.extjs.gxt.ui.client.util.DelayedTask;
import com.extjs.gxt.ui.client.util.Rectangle;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
/**
* Creates a draggable splitter on the side of a widget.
*
* <dl>
* <dt><b>Events:</b></dt>
*
* <dd><b>Resize</b> : SplitBarEvent(splitBar, size)<br>
* <div>Fires after the split bar has been moved.</div>
* <ul>
* <li>splitBar : this</li>
* <li>size : the new size</li>
* </ul>
* </dd>
*
* <dd><b>DragStart</b> : SplitBarEvent(splitBar, dragEvent)<br>
* <div>Fires after a drag has started.</div>
* <ul>
* <li>splitBar : this</li>
* <li>dragEvent : the drag event</li>
* </ul>
* </dd>
*
* <dd><b>DragEnd</b> : SplitBarEvent(splitBar, dragEvent)<br>
* <div>Fires after a drag has ended.</div>
* <ul>
* <li>splitBar : this</li>
* <li>dragEvent : the dom event</li>
* </ul>
* </dd>
*
* <dl>
*/
public class SplitBar extends BoxComponent {
private static List<SplitBar> attachedBars;
private static DelayedTask delayedTask;
static {
attachedBars = new ArrayList<SplitBar>();
delayedTask = new DelayedTask(new Listener<ComponentEvent>() {
public void handleEvent(ComponentEvent ce) {
int count = attachedBars.size();
for (int i = 0; i < count; i++) {
SplitBar bar = attachedBars.get(i);
bar.sync();
}
}
});
}
static void updateHandles() {
delayedTask.delay(400);
}
private boolean autoSize = true;
private int yOffset = 0;
private int xOffset = 0;
private int minSize = 10;
private int maxSize = 2000;
private int handleWidth = 5;
private int barWidth = 2;
private El resizeEl;
private BoxComponent resizeWidget;
private BoxComponent containerWidget;
private Draggable draggable;
private Rectangle startBounds;
private Listener<ComponentEvent> listener;
private DelayedTask delay;
private LayoutRegion region;
/**
* Creates a new split bar.
*
* @param style the bar location
* @param resizeWidget the widget being resized
*/
public SplitBar(final LayoutRegion style, final BoxComponent resizeWidget) {
this.region = style;
this.resizeWidget = resizeWidget;
this.resizeEl = resizeWidget.el();
listener = new Listener<ComponentEvent>() {
public void handleEvent(ComponentEvent e) {
EventType type = e.getType();
if (type == Events.Attach) {
if (!disabled) {
resizeEl.getParent().insertLast(getElement());
ComponentHelper.doAttach(SplitBar.this);
sync();
attachedBars.add(SplitBar.this);
}
} else if (type == Events.Detach) {
if (!disabled) {
removeSplitBar();
}
} else if (type == Events.Resize) {
delay.delay(400);
}
}
};
if (style == LayoutRegion.SOUTH || style == LayoutRegion.NORTH) {
setStyleName("x-hsplitbar");
} else {
setStyleName("x-vsplitbar");
}
resizeWidget.addListener(Events.Attach, listener);
resizeWidget.addListener(Events.Detach, listener);
resizeWidget.addListener(Events.Resize, listener);
draggable = new Draggable(this);
draggable.setUpdateZIndex(false);
draggable.setStartDragDistance(0);
draggable.setProxyStyle("x-splitbar-proxy");
Listener<DragEvent> dragListener = new Listener<DragEvent>() {
public void handleEvent(DragEvent e) {
EventType type = e.getType();
if (type == Events.DragStart) {
onStartDrag(e);
} else if (type == Events.DragEnd) {
onEndDrag(e);
} else if (type == Events.DragCancel) {
onCancelDrag(e);
}
}
};
draggable.addListener(Events.DragStart, dragListener);
draggable.addListener(Events.DragEnd, dragListener);
draggable.addListener(Events.DragCancel, dragListener);
render(DOM.createDiv());
if (resizeWidget.isAttached()) {
ComponentEvent be = createComponentEvent(null);
be.setType(Events.Attach);
listener.handleEvent(be);
}
delay = new DelayedTask(new Listener<ComponentEvent>() {
public void handleEvent(ComponentEvent ce) {
sync();
}
});
}
/**
* Creates a new split bar.
*
* @param style the bar location
* @param resizeWidget the widget being resized
* @param container the widget the split bar proxy will be sized to
*/
public SplitBar(LayoutRegion style, BoxComponent resizeWidget, BoxComponent container) {
this(style, resizeWidget);
this.containerWidget = container;
draggable.setContainer(container);
}
/**
* Returns the bar width.
*
* @return the bar width
*/
public int getBarWidth() {
return barWidth;
}
/**
* Returns the split bar's draggable instance.
*
* @return the draggable instance
*/
public Draggable getDraggable() {
return draggable;
}
/**
* Returns the handle width.
*
* @return the handle width
*/
public int getHandleWidth() {
return handleWidth;
}
/**
* Returns the maximum size.
*
* @return the max size
*/
public int getMaxSize() {
return maxSize;
}
/**
* @return the minSize
*/
public int getMinSize() {
return minSize;
}
/**
* Returns the resize widget.
*
* @return the resize widget
*/
public Component getResizeWidget() {
return resizeWidget;
}
/**
* Returns the x offset.
*
* @return the xOffset the x offset value
*/
public int getXOffset() {
return xOffset;
}
/**
* Returns the y offset.
*
* @return the y offset
*/
public int getYOffset() {
return yOffset;
}
/**
* Returns the auto size state.
*
* @return true if auto size is enabled
*/
public boolean isAutoSize() {
return autoSize;
}
/**
* Removes the split bar from the resize widget.
*/
public void release() {
resizeWidget.removeListener(Events.Attach, listener);
resizeWidget.removeListener(Events.Detach, listener);
resizeWidget.removeListener(Events.Resize, listener);
removeSplitBar();
}
/**
* True to update the size of the the resize widget after a drag operation
* using a proxy (defaults to true).
*
* @param autoSize the auto size state
*/
public void setAutoSize(boolean autoSize) {
this.autoSize = autoSize;
}
/**
* Sets the width of drag proxy during resizing (defaults to 2).
*
* @param barWidth the bar width
*/
public void setBarWidth(int barWidth) {
this.barWidth = barWidth;
}
/**
* Sets the width of the drag handles (defaults to 5).
*
* @param handleWidth the handle width
*/
public void setHandleWidth(int handleWidth) {
this.handleWidth = handleWidth;
}
/**
* Sets the maximum size of the resize widget (defaults to 2000).
*
* @param maxSize the maximum size
*/
public void setMaxSize(int maxSize) {
this.maxSize = maxSize;
}
/**
* Sets he minimum size of the resize widget (defaults to 10).
*
* @param minSize the minimum size
*/
public void setMinSize(int minSize) {
this.minSize = minSize;
}
/**
* The amount of pixels the bar should be offset to the left (defaults to 0).
*
* @param xOffset the xOffset to set
*/
public void setXOffset(int xOffset) {
this.xOffset = xOffset;
}
/**
* Sets the amount of pixels the bar should be offset to the top (defaults to
* 0).
*
* @param yOffset the yOffset to set
*/
public void setYOffset(int yOffset) {
this.yOffset = yOffset;
}
public void sync() {
if (!isAttached() || !resizeWidget.isAttached()) {
return;
}
Rectangle rect = resizeEl.getBounds();
int x = rect.x;
int y = rect.y;
if (!GXT.isBorderBox) {
y -= resizeEl.getFrameWidth("t");
x -= resizeEl.getFrameWidth("l");
}
int w = rect.width;
int h = rect.height;
switch (region) {
case SOUTH:
el().setBounds(x + getYOffset(), y + h + getXOffset(), w, getHandleWidth(), false);
break;
case WEST:
el().setBounds(x - getHandleWidth() + getYOffset(), y + getXOffset(), getHandleWidth(), h, false);
break;
case NORTH:
el().setBounds(x + getYOffset(), y - getHandleWidth() + getXOffset(), w, getHandleWidth(), false);
break;
case EAST:
el().setBounds(x + w + getYOffset(), y + getXOffset(), getHandleWidth(), h, false);
break;
}
}
@Override
protected ComponentEvent createComponentEvent(Event event) {
SplitBarEvent e = new SplitBarEvent(this);
e.setEvent(event);
return e;
}
@Override
protected void onRender(Element target, int index) {
super.onRender(target, index);
setElement(DOM.createDiv());
disableTextSelection(true);
el().makePositionable(true);
sinkEvents(Event.MOUSEEVENTS);
}
protected void removeSplitBar() {
ComponentHelper.doDetach(this);
el().removeFromParent();
attachedBars.remove(this);
}
private void onCancelDrag(BaseEvent be) {
resizeWidget.enableEvents(true);
sync();
}
private void onEndDrag(DragEvent bee) {
int x = bee.getX();
int y = bee.getY();
int width = resizeWidget.getOffsetWidth();
int height = resizeWidget.getOffsetHeight();
int diffY = y - startBounds.y;
int diffX = x - startBounds.x;
resizeWidget.enableEvents(true);
SplitBarEvent be = new SplitBarEvent(this);
switch (region) {
case NORTH: {
be.setSize(height - diffY);
if (isAutoSize()) {
resizeEl.setY(y).setHeight(height);
}
break;
}
case SOUTH: {
be.setSize(height + diffY);
if (isAutoSize()) {
resizeWidget.setHeight(diffY);
}
break;
}
case WEST: {
be.setSize(width - diffX);
if (isAutoSize()) {
el().setX(x);
resizeWidget.setWidth(width - diffX);
}
break;
}
case EAST: {
be.setSize(width + diffX);
if (isAutoSize()) {
resizeWidget.setWidth(diffX);
}
break;
}
}
be.setType(Events.DragEnd);
be.setComponent(this);
fireEvent(Events.DragEnd, be);
fireEvent(Events.Resize, be);
sync();
}
private void onStartDrag(DragEvent de) {
// adjust width of proxy
if (region == LayoutRegion.WEST || region == LayoutRegion.EAST) {
de.setWidth(getBarWidth());
} else {
de.setHeight(getBarWidth());
}
SplitBarEvent se = new SplitBarEvent(this);
se.setDragEvent(de);
fireEvent(Events.DragStart, se);
resizeWidget.enableEvents(false);
if (containerWidget != null) {
switch (region) {
case WEST:
case EAST:
int h = containerWidget.getHeight(true);
de.setHeight(h);
break;
case NORTH:
case SOUTH:
int w = containerWidget.getWidth(true);
de.setWidth(w);
break;
}
}
startBounds = new Rectangle();
startBounds.y = de.getY();
startBounds.x = de.getX();
boolean v = region == LayoutRegion.WEST || region == LayoutRegion.EAST;
int size;
if (v) {
size = resizeEl.getWidth();
} else {
size = resizeEl.getHeight();
}
int c1 = size - getMinSize();
if (size < getMinSize()) {
c1 = 0;
}
int c2 = Math.max(getMaxSize() - size, 0);
if (v) {
draggable.setConstrainVertical(true);
draggable.setXConstraint(region == LayoutRegion.WEST ? c2 : c1, region == LayoutRegion.WEST ? c1 : c2);
} else {
draggable.setConstrainHorizontal(true);
draggable.setYConstraint(region == LayoutRegion.NORTH ? c2 : c1, region == LayoutRegion.NORTH ? c1 : c2);
}
}
}