/*
* Ext GWT 2.2.4 - Ext for GWT
* Copyright(c) 2007-2010, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
package com.extjs.gxt.ui.client.fx;
import java.util.ArrayList;
import java.util.List;
import com.extjs.gxt.ui.client.core.El;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.BaseObservable;
import com.extjs.gxt.ui.client.event.ComponentEvent;
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.PreviewEvent;
import com.extjs.gxt.ui.client.event.ResizeEvent;
import com.extjs.gxt.ui.client.event.ResizeListener;
import com.extjs.gxt.ui.client.util.BaseEventPreview;
import com.extjs.gxt.ui.client.util.Point;
import com.extjs.gxt.ui.client.util.Rectangle;
import com.extjs.gxt.ui.client.widget.BoxComponent;
import com.extjs.gxt.ui.client.widget.Component;
import com.extjs.gxt.ui.client.widget.ComponentHelper;
import com.extjs.gxt.ui.client.widget.Shim;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.RootPanel;
/**
* Applies drag handles to a widget to make it resizable. The drag handles are
* inserted into the widget and positioned absolute.
* <p>
* Here is the list of valid resize handles:
* </p>
*
* <pre>
* Value Description
* ------ -------------------
* 'n' north
* 's' south
* 'e' east
* 'w' west
* 'nw' northwest
* 'sw' southwest
* 'se' southeast
* 'ne' northeast
* 'all' all
* </pre>
*
* <dl>
* <dt><b>Events:</b></dt>
*
* <dd><b>ResizeStart</b> : (source, widget, event) <br>
* <div>Fires before a resize operation start. Listeners can cancel the action by
* calling {@link BaseEvent#setCancelled(boolean)}.</div>
* <ul>
* <li>source : this</li>
* <li>component : resize widget</li>
* <li>event : the dom event</li>
* </ul>
* </dd>
*
* <dd><b>ResizeEnd</b> : (source, widget, event)<br>
* <div>Fires after a resize.</div>
* <ul>
* <li>source : this</li>
* <li>widget : resize widget</li>
* <li>event : the dom event</li>
* </ul>
* </dd>
* </dl>
*/
public class Resizable extends BaseObservable {
protected enum Dir {
E, N, NE, NW, S, SE, SW, W;
}
private class ResizeHandle extends Component {
public Dir dir;
public void onBrowserEvent(Event event) {
switch (DOM.eventGetType(event)) {
case Event.ONMOUSEDOWN:
DOM.eventCancelBubble(event, true);
DOM.eventPreventDefault(event);
handleMouseDown(event, this);
break;
}
}
@Override
protected void onRender(Element target, int index) {
super.onRender(target, index);
setElement(DOM.createDiv(), target, index);
sinkEvents(Event.MOUSEEVENTS);
}
}
private Dir dir;
private boolean dynamic;
private boolean enabled = true;
private List<ResizeHandle> handleList;
private String handles;
private Listener<ComponentEvent> listener;
private int maxHeight = 2000;
private int maxWidth = 2000;
private int minHeight = 50;
private int minWidth = 50;
private boolean preserveRatio = false;
private BaseEventPreview preview;
private El proxyEl;
private String proxyStyle = "x-resizable-proxy";
private BoxComponent resize;
private boolean resizing;
private Rectangle startBox;
private Point startPoint;
/**
* Creates a new resizable instance with 8-way resizing.
*
* @param resize the resize widget
*/
public Resizable(BoxComponent resize) {
this(resize, "all");
}
/**
* Creates a new resizable instance.
*
* @param resize the resize widget
* @param handles the resize handle locations separated by spaces
*/
public Resizable(final BoxComponent resize, String handles) {
this.resize = resize;
this.handles = handles;
listener = new Listener<ComponentEvent>() {
public void handleEvent(ComponentEvent e) {
EventType type = e.getType();
if (type == Events.Render) {
init();
} else if (type == Events.Attach) {
onAttach();
} else if (type == Events.Detach) {
onDetach();
} else if (type == Events.Resize) {
onComponentResize();
}
}
};
resize.addListener(Events.Render, listener);
resize.addListener(Events.Attach, listener);
resize.addListener(Events.Detach, listener);
resize.addListener(Events.Resize, listener);
if (resize.isRendered()) {
init();
}
if (resize.isAttached()) {
onAttach();
}
}
/**
* Adds a resize listener.
*
* @param listener the listener
*/
public void addResizeListener(ResizeListener listener) {
addListener(Events.ResizeStart, listener);
addListener(Events.ResizeEnd, listener);
}
/**
* Returns the max height
*
* @return the max height
*/
public int getMaxHeight() {
return maxHeight;
}
/**
* Returns the max width.
*
* @return the max width
*/
public int getMaxWidth() {
return maxWidth;
}
/**
* Returns the min height.
*
* @return the min height
*/
public int getMinHeight() {
return minHeight;
}
/**
* Returns the min width.
*
* @return the min width
*/
public int getMinWidth() {
return minWidth;
}
/**
* Returns the proxy style.
*
* @return the proxy style
*/
public String getProxyStyle() {
return proxyStyle;
}
/**
* Returns true if widget is being resized directly.
*
* @return the dynamic state
*/
public boolean isDynamic() {
return dynamic;
}
/**
* Returns true if the aspect ratio is being preserved.
*
* @return true if the aspect ratio is being preserved
*/
public boolean isPreserveRatio() {
return preserveRatio;
}
/**
* Returns <code>true</code> if if resizing.
*
* @return the resize state
*/
public boolean isResizing() {
return resizing;
}
/**
* Removes the drag handles.
*/
public void release() {
onDetach();
resize.removeListener(Events.Attach, listener);
resize.removeListener(Events.Detach, listener);
resize.removeListener(Events.Render, listener);
resize.removeListener(Events.Resize, listener);
if (handleList != null) {
for (ResizeHandle handle : handleList) {
DOM.removeChild(resize.getElement(), handle.getElement());
}
}
}
/**
* Removes a resize listener.
*
* @param listener the listener
*/
public void removeResizeListener(ResizeListener listener) {
removeListener(Events.ResizeStart, listener);
removeListener(Events.ResizeEnd, listener);
}
/**
* True to resize the widget directly instead of using a proxy (defaults to
* false).
*
* @param dynamic true to resize directly
*/
public void setDynamic(boolean dynamic) {
this.dynamic = dynamic;
}
/**
* Enables or disables the drag handles.
*
* @param enable <code>true</code> to enable
*/
public void setEnabled(boolean enable) {
if (enabled != enable && handleList != null) {
for (ResizeHandle handle : handleList) {
handle.el().setVisibility(enable);
}
if (enable) {
syncHandleHeight();
}
}
this.enabled = enable;
}
/**
* Sets the max height (defaults to 2000).
*
* @param maxHeight the max height
*/
public void setMaxHeight(int maxHeight) {
this.maxHeight = maxHeight;
}
/**
* Sets the max width (defaults to 2000).
*
* @param maxWidth the max width
*/
public void setMaxWidth(int maxWidth) {
this.maxWidth = maxWidth;
}
/**
* Sets the min height (default to 50).
*
* @param minHeight the min height
*/
public void setMinHeight(int minHeight) {
this.minHeight = minHeight;
}
/**
* Sets the min width (defaults to 50).
*
* @param minWidth the min width
*/
public void setMinWidth(int minWidth) {
this.minWidth = minWidth;
}
/**
* True to preserve the original ratio between height and width during resize
* (defaults to false).
*
* @param preserveRatio true to preserve the original aspect ratio
*/
public void setPreserveRatio(boolean preserveRatio) {
this.preserveRatio = preserveRatio;
}
/**
* Sets the style name used for proxy drags (defaults to 'x-resizable-proxy').
*
* @param proxyStyle the proxy style
*/
public void setProxyStyle(String proxyStyle) {
this.proxyStyle = proxyStyle;
}
public void syncHandleHeight() {
if (resize != null && handleList != null) {
int height = resize.getHeight(true);
for (ResizeHandle r : handleList) {
if (r.dir == Dir.E || r.dir == Dir.W) {
r.el().setHeight(height);
}
}
resize.el().repaint();
}
}
protected Element createProxy() {
Element elem = DOM.createDiv();
El.fly(elem).setStyleName(proxyStyle, true);
El.fly(elem).disableTextSelection(true);
return elem;
}
protected void init() {
resize.el().makePositionable();
if (handleList == null) {
handleList = new ArrayList<ResizeHandle>();
if ("all".equals(handles)) {
handles = "n s e w ne nw se sw";
}
String[] temp = handles.split(" ");
for (int i = 0; i < temp.length; i++) {
if ("n".equals(temp[i])) {
create(Dir.N, "north");
} else if ("nw".equals(temp[i])) {
create(Dir.NW, "northwest");
} else if ("e".equals(temp[i])) {
create(Dir.E, "east");
} else if ("w".equals(temp[i])) {
create(Dir.W, "west");
} else if ("se".equals(temp[i])) {
create(Dir.SE, "southeast");
} else if ("s".equals(temp[i])) {
create(Dir.S, "south");
} else if ("ne".equals(temp[i])) {
create(Dir.NE, "northeast");
} else if ("sw".equals(temp[i])) {
create(Dir.SW, "southwest");
}
}
preview = new BaseEventPreview() {
@Override
public boolean onPreview(PreviewEvent event) {
event.preventDefault();
switch (event.getEventTypeInt()) {
case Event.ONMOUSEMOVE:
int x = event.getClientX();
int y = event.getClientY();
handleMouseMove(x, y);
break;
case Event.ONMOUSEUP:
handleMouseUp(event.getEvent());
break;
}
return true;
}
};
preview.setAutoHide(false);
}
syncHandleHeight();
setEnabled(enabled);
}
protected void onAttach() {
if (handleList != null) {
for (ResizeHandle handle : handleList) {
ComponentHelper.doAttach(handle);
}
}
}
protected void onComponentResize() {
syncHandleHeight();
}
protected void onDetach() {
if (handleList != null) {
for (ResizeHandle handle : handleList) {
ComponentHelper.doDetach(handle);
}
}
}
private int constrain(int v, int diff, int m, int mx) {
if (v - diff < m) {
diff = v - m;
} else if (v - diff > mx) {
diff = mx - v;
}
return diff;
}
private ResizeHandle create(Dir dir, String cls) {
ResizeHandle rh = new ResizeHandle();
rh.setStyleName("x-resizable-handle " + "x-resizable-handle-" + cls);
rh.dir = dir;
rh.render(resize.getElement());
handleList.add(rh);
return rh;
}
private void handleMouseDown(Event event, ResizeHandle handle) {
if (!enabled || !fireEvent(Events.ResizeStart, new ResizeEvent(this, resize, event))) {
return;
}
dir = handle.dir;
startBox = resize.el().getBounds(false);
int x = DOM.eventGetClientX(event);
int y = DOM.eventGetClientY(event);
startPoint = new Point(x, y);
resizing = true;
if (dynamic) {
if (proxyEl != null) {
proxyEl.setVisible(false);
}
} else {
if (proxyEl == null) {
proxyEl = new El(createProxy());
}
Element body = RootPanel.getBodyElement();
DOM.appendChild(body, proxyEl.dom);
proxyEl.makePositionable(true);
proxyEl.setLeft(startBox.x).setTop(startBox.y);
proxyEl.setSize(startBox.width, startBox.height, true);
proxyEl.setVisible(true);
proxyEl.updateZIndex(5);
}
preview.add();
Shim.get().cover(false);
Shim.get().setStyleAttribute("cursor", handle.el().getStyleAttribute("cursor"));
}
private void handleMouseMove(int xin, int yin) {
if (resizing) {
int x = startBox.x;
int y = startBox.y;
float w = startBox.width;
float h = startBox.height;
float ow = w, oh = h;
int mw = minWidth;
int mh = minHeight;
int mxw = maxWidth;
int mxh = maxHeight;
Point eventXY = new Point(xin, yin);
int diffX = -(startPoint.x - Math.max(2, eventXY.x));
int diffY = -(startPoint.y - Math.max(2, eventXY.y));
switch (dir) {
case E:
w += diffX;
w = Math.min(Math.max(mw, w), mxw);
break;
case S:
h += diffY;
h = Math.min(Math.max(mh, h), mxh);
break;
case SE:
w += diffX;
h += diffY;
w = Math.min(Math.max(mw, w), mxw);
h = Math.min(Math.max(mh, h), mxh);
break;
case N:
diffY = constrain((int) h, diffY, mh, mxh);
y += diffY;
h -= diffY;
break;
case W:
diffX = constrain((int) w, diffX, mw, mxw);
x += diffX;
w -= diffX;
break;
case NE:
w += diffX;
w = Math.min(Math.max(mw, w), mxw);
diffY = constrain((int) h, diffY, mh, mxh);
y += diffY;
h -= diffY;
break;
case NW:
diffX = constrain((int) w, diffX, mw, mxw);
diffY = constrain((int) h, diffY, mh, mxh);
y += diffY;
h -= diffY;
x += diffX;
w -= diffX;
break;
case SW:
diffX = constrain((int) w, diffX, mw, mxw);
h += diffY;
h = Math.min(Math.max(mh, h), mxh);
x += diffX;
w -= diffX;
break;
}
if (preserveRatio) {
switch (dir) {
case SE:
case E:
h = oh * (w / ow);
h = Math.min(Math.max(mh, h), mxh);
w = ow * (h / oh);
break;
case S:
w = ow * (h / oh);
w = Math.min(Math.max(mw, w), mxw);
h = oh * (w / ow);
break;
case NE:
w = ow * (h / oh);
w = Math.min(Math.max(mw, w), mxw);
h = oh * (w / ow);
break;
case N: {
float tw = w;
w = ow * (h / oh);
w = Math.min(Math.max(mw, w), mxw);
h = oh * (w / ow);
x += (tw - w) / 2;
}
break;
case SW: {
h = oh * (w / ow);
h = Math.min(Math.max(mh, h), mxh);
float tw = w;
w = ow * (h / oh);
x += tw - w;
break;
}
case W: {
float th = h;
h = oh * (w / ow);
h = Math.min(Math.max(mh, h), mxh);
y += (th - h) / 2;
float tw = w;
w = ow * (h / oh);
x += tw - w;
break;
}
case NW: {
float tw = w;
float th = h;
h = oh * (w / ow);
h = Math.min(Math.max(mh, h), mxh);
w = ow * (h / oh);
y += th - h;
x += tw - w;
break;
}
}
}
if (dynamic) {
resize.setPagePosition(x, y);
resize.setSize((int) w, (int) h);
} else {
proxyEl.setLeftTop(x, y);
proxyEl.setSize((int) w, (int) h, true);
}
}
}
private void handleMouseUp(Event event) {
resizing = false;
preview.remove();
Shim.get().uncover();
if (!dynamic) {
Rectangle rect = dynamic ? resize.el().getBounds() : proxyEl.getBounds();
rect.width = Math.min(rect.width, maxWidth);
rect.height = Math.min(rect.height, maxHeight);
proxyEl.disableTextSelection(false);
proxyEl.setVisible(false);
proxyEl.remove();
resize.setBounds(rect);
}
syncHandleHeight();
fireEvent(Events.ResizeEnd, new ResizeEvent(this, resize, event));
}
}