/*
* Copyright 2000-2016 Vaadin Ltd.
*
* 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.vaadin.client.debug.internal;
import java.util.ArrayList;
import java.util.Date;
import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Cursor;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.http.client.UrlBuilder;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.storage.client.Storage;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.Location;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.ValueMap;
import com.vaadin.client.ui.VOverlay;
/**
* Debug window implementation.
*
* @since 7.1
* @author Vaadin Ltd
*/
public final class VDebugWindow extends VOverlay {
// CSS classes
static final String STYLENAME = "v-debugwindow";
static final String STYLENAME_BUTTON = STYLENAME + "-button";
static final String STYLENAME_ACTIVE = "active";
protected static final String STYLENAME_HEAD = STYLENAME + "-head";
protected static final String STYLENAME_TABS = STYLENAME + "-tabs";
protected static final String STYLENAME_TAB = STYLENAME + "-tab";
protected static final String STYLENAME_CONTROLS = STYLENAME + "-controls";
protected static final String STYLENAME_SECTION_HEAD = STYLENAME
+ "-section-head";
protected static final String STYLENAME_CONTENT = STYLENAME + "-content";
protected static final String STYLENAME_SELECTED = "selected";
// drag this far before actually moving window
protected static final int MOVE_TRESHOLD = 5;
// window minimum height, minimum width comes from tab+controls
protected static final int MIN_HEIGHT = 40;
// size of area to grab for resize; bottom corners size in CSS
protected static final int HANDLE_SIZE = 7;
// identifiers for localStorage
private static final String STORAGE_PREFIX = "v-debug-";
private static final String STORAGE_FULL_X = "x";
private static final String STORAGE_FULL_Y = "y";
private static final String STORAGE_FULL_W = "w";
private static final String STORAGE_FULL_H = "h";
private static final String STORAGE_MIN_X = "mx";
private static final String STORAGE_MIN_Y = "my";
private static final String STORAGE_ACTIVE_SECTION = "t";
private static final String STORAGE_IS_MINIMIZED = "m";
private static final String STORAGE_FONT_SIZE = "s";
// state, these are persisted
protected Section activeSection;
protected boolean minimized = false;
protected int fullX = -10;
protected int fullY = -10;
protected int fullW = 300;
protected int fullH = 150;
protected int minX = -10;
protected int minY = 10;
protected int fontSize = 1; // 0-2
// Timers since application start, and last timer reset
private static final Duration start = new Duration();
private static Duration lastReset = start;
// outer panel
protected FlowPanel window = new FlowPanel();
// top (tabs + controls)
protected FlowPanel head = new FlowPanel();
protected FlowPanel tabs = new FlowPanel();
protected FlowPanel controls = new FlowPanel();
protected Button minimize = new DebugButton(Icon.MINIMIZE, "Minimize");
protected Button menu = new DebugButton(Icon.MENU, "Menu");
protected Button close = new DebugButton(Icon.CLOSE, "Close");
// menu
protected Menu menuPopup = new Menu();
// section specific area
protected FlowPanel sectionHead = new FlowPanel();
// content wrapper
protected SimplePanel content = new SimplePanel();
// sections
protected ArrayList<Section> sections = new ArrayList<>();
// handles resize/move
protected HandlerRegistration mouseDownHandler = null;
protected HandlerRegistration mouseMoveHandler = null;
// TODO this class should really be a singleton.
static VDebugWindow instance;
/**
* This class should only be instantiated by the framework, use
* {@link #get()} instead to get the singleton instance.
* <p>
* {@link VDebugWindow} provides windowing functionality and shows
* {@link Section}s added with {@link #addSection(Section)} as tabs.
* </p>
* <p>
* {@link Section#getTabButton()} is called to obtain a unique id for the
* Sections; the id should actually be an identifier for an icon in the
* icon-font in use.
* </p>
* <p>
* {@link Section#getControls()} and {@link Section#getContent()} is called
* when the Section is activated (displayed). Additionally
* {@link Section#show()} is called to allow the Section to initialize
* itself as needed when shown. Conversely {@link Section#hide()} is called
* when the Section is deactivated.
* </p>
* <p>
* Sections should take care to prefix CSS classnames used with
* {@link VDebugWindow}.{@link #STYLENAME} to avoid that application theme
* interferes with the debug window content.
* </p>
* <p>
* Some of the window state, such as position and size, is persisted to
* localStorage. Sections can use
* {@link #writeState(Storage, String, Object)} and
* {@link #readState(Storage, String, String)} (and relatives) to write and
* read own persisted settings, keys will automatically be prefixed with
* {@value #STORAGE_PREFIX}.
* </p>
*/
public VDebugWindow() {
super(false, false);
getElement().getStyle().setOverflow(Overflow.HIDDEN);
setStylePrimaryName(STYLENAME);
setWidget(window);
window.add(head);
head.add(tabs);
head.add(controls);
head.add(sectionHead);
window.add(content);
addHandles();
head.setStylePrimaryName(STYLENAME_HEAD);
tabs.setStylePrimaryName(STYLENAME_TABS);
controls.setStylePrimaryName(STYLENAME_CONTROLS);
sectionHead.setStylePrimaryName(STYLENAME_SECTION_HEAD);
content.setStylePrimaryName(STYLENAME_CONTENT);
// add controls TODO move these
controls.add(menu);
menu.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
menuPopup.showRelativeTo(menu);
}
});
controls.add(minimize);
minimize.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
toggleMinimized();
writeStoredState();
}
});
controls.add(close);
close.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
close();
}
});
Style s = content.getElement().getStyle();
s.setOverflow(Overflow.AUTO);
// move/resize
final MouseHandler mouseHandler = new MouseHandler();
mouseDownHandler = this.addDomHandler(mouseHandler,
MouseDownEvent.getType());
mouseMoveHandler = this.addDomHandler(mouseHandler,
MouseMoveEvent.getType());
}
/**
* Adds dummy handle elements to the bottom corners that might have
* scrollbars that interfere with resizing on some platforms.
*
* @since 7.1
*/
private void addHandles() {
Element el = DOM.createDiv();
el.setClassName(VDebugWindow.STYLENAME + "-handle "
+ VDebugWindow.STYLENAME + "-handle-sw");
content.getElement().appendChild(el);
el = DOM.createDiv();
el.setClassName(VDebugWindow.STYLENAME + "-handle "
+ VDebugWindow.STYLENAME + "-handle-se");
content.getElement().appendChild(el);
}
/**
* Gets the {@link #VDebugWindow()} singleton instance.
*
* @return
*/
public static VDebugWindow get() {
if (instance == null) {
instance = GWT.create(VDebugWindow.class);
}
return instance;
}
/**
* Closes the window and stops visual logging.
*/
public void close() {
// TODO disable even more
if (mouseDownHandler != null) {
mouseDownHandler.removeHandler();
mouseMoveHandler.removeHandler();
mouseDownHandler = null;
mouseMoveHandler = null;
}
Highlight.hideAll();
hide();
}
boolean isClosed() {
return !isShowing();
}
/**
* Reads the stored state from localStorage.
*/
private void readStoredState() {
Storage storage = Storage.getLocalStorageIfSupported();
if (storage == null) {
return;
}
fullX = readState(storage, STORAGE_FULL_X, -510);
fullY = readState(storage, STORAGE_FULL_Y, -230);
fullW = readState(storage, STORAGE_FULL_W, 500);
fullH = readState(storage, STORAGE_FULL_H, 150);
minX = readState(storage, STORAGE_MIN_X, -40);
minY = readState(storage, STORAGE_MIN_Y, -70);
setFontSize(readState(storage, STORAGE_FONT_SIZE, 1));
activateSection(readState(storage, STORAGE_ACTIVE_SECTION, 0));
setMinimized(readState(storage, STORAGE_IS_MINIMIZED, false));
applyPositionAndSize();
}
/**
* Writes the persistent state to localStorage.
*/
private void writeStoredState() {
if (isClosed()) {
return;
}
Storage storage = Storage.getLocalStorageIfSupported();
if (storage == null) {
return;
}
writeState(storage, STORAGE_FULL_X, fullX);
writeState(storage, STORAGE_FULL_Y, fullY);
writeState(storage, STORAGE_FULL_W, fullW);
writeState(storage, STORAGE_FULL_H, fullH);
writeState(storage, STORAGE_MIN_X, minX);
writeState(storage, STORAGE_MIN_Y, minY);
writeState(storage, STORAGE_FONT_SIZE, fontSize);
int activeIdx = getActiveSection();
if (activeIdx >= 0) {
writeState(storage, STORAGE_ACTIVE_SECTION, activeIdx);
}
writeState(storage, STORAGE_IS_MINIMIZED, minimized);
}
/**
* Writes the given value to the given {@link Storage} using the given key
* (automatically prefixed with {@value #STORAGE_PREFIX}).
*
* @param storage
* @param key
* @param value
*/
static void writeState(Storage storage, String key, Object value) {
storage.setItem(STORAGE_PREFIX + key, String.valueOf(value));
}
/**
* Returns the item with the given key (automatically prefixed with
* {@value #STORAGE_PREFIX}) as an int from the given {@link Storage},
* returning the given default value instead if not successful (e.g missing
* item).
*
* @param storage
* @param key
* @param def
* @return stored or default value
*/
static int readState(Storage storage, String key, int def) {
try {
return Integer.parseInt(storage.getItem(STORAGE_PREFIX + key));
} catch (Exception e) {
return def;
}
}
/**
* Returns the item with the given key (automatically prefixed with
* {@value #STORAGE_PREFIX}) as a boolean from the given {@link Storage},
* returning the given default value instead if not successful (e.g missing
* item).
*
* @param storage
* @param key
* @param def
* @return stored or default value
*/
static boolean readState(Storage storage, String key, boolean def) {
try {
return Boolean.parseBoolean(storage.getItem(STORAGE_PREFIX + key));
} catch (Exception e) {
return def;
}
}
/**
* Returns the item with the given key (automatically prefixed with
* {@value #STORAGE_PREFIX}) as a String from the given {@link Storage},
* returning the given default value instead if not successful (e.g missing
* item).
*
* @param storage
* @param key
* @param def
* @return stored or default value
*/
static String readState(Storage storage, String key, String def) {
String val = storage.getItem(STORAGE_PREFIX + key);
return val != null ? val : def;
}
/**
* Resets (clears) the stored state from localStorage.
*/
private void resetStoredState() {
Storage storage = Storage.getLocalStorageIfSupported();
if (storage == null) {
return;
}
// note: length is live
for (int i = 0; i < storage.getLength();) {
String key = storage.key(i);
if (key.startsWith(STORAGE_PREFIX)) {
removeState(storage, key.substring(STORAGE_PREFIX.length()));
} else {
i++;
}
}
}
/**
* Removes the item with the given key (automatically prefixed with
* {@value #STORAGE_PREFIX}) from the given {@link Storage}.
*
* @param storage
* @param key
*/
private void removeState(Storage storage, String key) {
storage.removeItem(STORAGE_PREFIX + key);
}
/**
* Applies the appropriate instance variables for width, height, x, y
* depending on if the window is minimized or not.
*
* If the value is negative, the window is positioned that amount of pixels
* from the right/bottom instead of left/top.
*
* Finally, the position is bounds-checked so that the window is not moved
* off-screen (the adjusted values are not saved).
*/
private void applyPositionAndSize() {
int x = 0;
int y = 0;
if (minimized) {
x = minX;
if (minX < 0) {
x = Window.getClientWidth() + minX;
}
y = minY;
if (minY < 0) {
y = Window.getClientHeight() + minY;
}
} else {
x = fullX;
if (fullX < 0) {
x = Window.getClientWidth() + fullX;
}
y = fullY;
if (y < 0) {
y = Window.getClientHeight() + fullY;
}
content.setWidth(fullW + "px");
content.setHeight(fullH + "px");
}
applyBounds(x, y);
}
private void applyBounds() {
int x = getPopupLeft();
int y = getPopupTop();
applyBounds(x, y);
}
private void applyBounds(int x, int y) {
// bounds check
if (x < 0) {
x = 0;
}
if (x > Window.getClientWidth() - getOffsetWidth()) {
// not allowed off-screen to the right
x = Window.getClientWidth() - getOffsetWidth();
}
if (y > Window.getClientHeight() - getOffsetHeight()) {
y = Window.getClientHeight() - getOffsetHeight();
}
if (y < 0) {
y = 0;
}
setPopupPosition(x, y);
}
/**
* Reads position and size from the DOM to local variables (which in turn
* can be stored to localStorage)
*/
private void readPositionAndSize() {
int x = getPopupLeft();
int fromRight = Window.getClientWidth() - x - getOffsetWidth();
if (fromRight < x) {
x -= Window.getClientWidth();
}
int y = getPopupTop();
int fromBottom = Window.getClientHeight() - y - getOffsetHeight();
if (fromBottom < y) {
y -= Window.getClientHeight();
}
if (minimized) {
minY = y;
minX = x;
} else {
fullY = y;
fullX = x;
fullW = content.getOffsetWidth();
fullH = content.getOffsetHeight();
}
}
/**
* Adds the given {@link Section} as a tab in the {@link VDebugWindow} UI.
* {@link Section#getTabButton()} is called to obtain a button which is used
* tab.
*
* @param section
*/
public void addSection(final Section section) {
Button b = section.getTabButton();
b.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
activateSection(section);
writeStoredState();
}
});
b.setStylePrimaryName(STYLENAME_TAB);
tabs.add(b);
sections.add(section);
if (activeSection == null) {
activateSection(section);
}
}
/**
* Activates the given {@link Section}
*
* @param section
*/
void activateSection(Section section) {
if (section != null && section != activeSection) {
Highlight.hideAll();
// remove old stuff
if (activeSection != null) {
activeSection.hide();
content.remove(activeSection.getContent());
sectionHead.remove(activeSection.getControls());
}
// update tab styles
for (int i = 0; i < tabs.getWidgetCount(); i++) {
Widget tab = tabs.getWidget(i);
tab.setStyleDependentName(STYLENAME_SELECTED,
tab == section.getTabButton());
}
// add new stuff
content.add(section.getContent());
sectionHead.add(section.getControls());
activeSection = section;
activeSection.show();
}
}
void activateSection(int n) {
if (n < sections.size()) {
activateSection(sections.get(n));
}
}
int getActiveSection() {
return sections.indexOf(activeSection);
}
/**
* Toggles the window between minimized and full states.
*/
private void toggleMinimized() {
setMinimized(!minimized);
writeStoredState();
}
/**
* Sets whether or not the window is minimized.
*
* @param minimized
*/
private void setMinimized(boolean minimized) {
this.minimized = minimized;
tabs.setVisible(!minimized);
content.setVisible(!minimized);
sectionHead.setVisible(!minimized);
menu.setVisible(!minimized);
applyPositionAndSize();
}
/**
* Sets the font size in use.
*
* @param size
*/
private void setFontSize(int size) {
removeStyleDependentName("size" + fontSize);
fontSize = size;
addStyleDependentName("size" + size);
}
/**
* Gets the font size currently in use.
*
* @return
*/
private int getFontSize() {
return fontSize;
}
/**
* Gets the milliseconds since application start.
*
* @return
*/
static int getMillisSinceStart() {
return start.elapsedMillis();
}
/**
* Gets the milliseconds since last {@link #resetTimer()} call.
*
* @return
*/
static int getMillisSinceReset() {
return lastReset.elapsedMillis();
}
/**
* Resets the timer.
*
* @return Milliseconds elapsed since the timer was last reset.
*/
static int resetTimer() {
int sinceLast = lastReset.elapsedMillis();
lastReset = new Duration();
return sinceLast;
}
/**
* Gets a nicely formatted string with timing information suitable for
* display in tooltips.
*
* @param sinceStart
* @param sinceReset
* @return
*/
static String getTimingTooltip(int sinceStart, int sinceReset) {
String title = formatDuration(sinceStart) + " since start";
title += ",
" + formatDuration(sinceReset) + " since timer reset";
title += "
@ "
+ DateTimeFormat.getFormat("HH:mm:ss.SSS").format(new Date());
return title;
}
/**
* Formats the given milliseconds as hours, minutes, seconds and
* milliseconds.
*
* @param ms
* @return
*/
static String formatDuration(int ms) {
NumberFormat fmt = NumberFormat.getFormat("00");
String seconds = fmt.format((ms / 1000) % 60);
String minutes = fmt.format((ms / (1000 * 60)) % 60);
String hours = fmt.format((ms / (1000 * 60 * 60)) % 24);
String millis = NumberFormat.getFormat("000").format(ms % 1000);
return hours + "h " + minutes + "m " + seconds + "s " + millis + "ms";
}
/**
* Called when the window is initialized.
*/
public void init() {
show();
/*
* Finalize initialization when all entry points have had the chance to
* e.g. register new sections.
*/
Scheduler.get().scheduleFinally(new ScheduledCommand() {
@Override
public void execute() {
readStoredState();
Window.addResizeHandler(
new com.google.gwt.event.logical.shared.ResizeHandler() {
Timer t = new Timer() {
@Override
public void run() {
applyPositionAndSize();
}
};
@Override
public void onResize(ResizeEvent event) {
t.cancel();
// TODO less
t.schedule(1000);
}
});
}
});
}
/**
* Called when the result from analyzeLayouts is received.
*
* @param ac
* @param meta
*/
public void meta(ApplicationConnection ac, ValueMap meta) {
if (isClosed()) {
return;
}
for (Section s : sections) {
s.meta(ac, meta);
}
}
/**
* Called when a response is received
*
* @param ac
* @param uidl
*/
public void uidl(ApplicationConnection ac, ValueMap uidl) {
if (isClosed()) {
return;
}
for (Section s : sections) {
s.uidl(ac, uidl);
}
}
/**
* Gets the container element for this window. The debug window is always
* global to the document and not related to any
* {@link ApplicationConnection} in particular.
*
* @return The global overlay container element.
*/
@Override
public com.google.gwt.user.client.Element getOverlayContainer() {
return RootPanel.get().getElement();
}
/*
* Inner classes
*/
/**
* Popup menu for {@link VDebugWindow}.
*
* @since 7.1
* @author Vaadin Ltd
*/
protected class Menu extends VOverlay {
FlowPanel content = new FlowPanel();
DebugButton[] sizes = new DebugButton[] {
new DebugButton(null, "Small", "A"),
new DebugButton(null, "Medium", "A"),
new DebugButton(null, "Large", "A") };
DebugButton[] modes = new DebugButton[] {
new DebugButton(Icon.DEVMODE_OFF,
"Debug only (causes page reload)"),
new DebugButton(Icon.DEVMODE_ON,
"DevMode (causes page reload)"),
new DebugButton(Icon.DEVMODE_SUPER,
"SuperDevMode (causes page reload)") };
Menu() {
super(true, true);
setWidget(content);
setStylePrimaryName(STYLENAME + "-menu");
content.setStylePrimaryName(STYLENAME + "-menu-content");
FlowPanel size = new FlowPanel();
content.add(size);
final ClickHandler sizeHandler = new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
for (int i = 0; i < sizes.length; i++) {
Button b = sizes[i];
if (b == event.getSource()) {
setSize(i);
}
}
hide();
}
};
for (int i = 0; i < sizes.length; i++) {
Button b = sizes[i];
b.setStyleDependentName("size" + i, true);
b.addClickHandler(sizeHandler);
size.add(b);
}
FlowPanel mode = new FlowPanel();
content.add(mode);
final ClickHandler modeHandler = new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
for (int i = 0; i < modes.length; i++) {
Button b = modes[i];
if (b == event.getSource()) {
setDevMode(i);
}
}
hide();
}
};
modes[getDevMode()].setActive(true);
for (int i = 0; i < modes.length; i++) {
Button b = modes[i];
b.addClickHandler(modeHandler);
mode.add(b);
}
Button reset = new DebugButton(Icon.RESET, "Restore defaults.",
" Reset");
reset.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
resetStoredState();
readStoredState();
hide();
}
});
content.add(reset);
}
private void setSize(int size) {
for (int i = 0; i < sizes.length; i++) {
Button b = sizes[i];
b.setStyleDependentName(STYLENAME_ACTIVE, i == size);
}
setFontSize(size);
writeStoredState();
}
@Override
public void show() {
super.show();
setSize(getFontSize());
}
private int getDevMode() {
if (Location.getParameter("superdevmode") != null) {
return 2;
} else if (Location.getParameter("gwt.codesvr") != null) {
return 1;
} else {
return 0;
}
}
private void setDevMode(int mode) {
UrlBuilder u = Location.createUrlBuilder();
switch (mode) {
case 2:
u.setParameter("superdevmode", "");
u.removeParameter("gwt.codesvr");
break;
case 1:
u.setParameter("gwt.codesvr", "localhost:9997");
u.removeParameter("superdevmode");
break;
default:
u.removeParameter("gwt.codesvr");
u.removeParameter("superdevmode");
}
Location.assign(u.buildString());
}
}
/**
* Handler for resizing and moving window, also updates cursor on mousemove.
*
* @since 7.1
* @author Vaadin Ltd
*/
protected class MouseHandler implements MouseMoveHandler, MouseDownHandler,
NativePreviewHandler {
boolean resizeLeft;
boolean resizeRight;
boolean resizeUp;
boolean resizeDown;
boolean move;
boolean sizing;
// dragging stopped, remove handler on next event
boolean stop;
HandlerRegistration dragHandler;
int startX;
int startY;
int startW;
int startH;
int startTop;
int startLeft;
@Override
public void onMouseMove(MouseMoveEvent event) {
if (null == dragHandler) {
updateResizeFlags(event);
updateCursor();
}
}
@Override
public void onMouseDown(MouseDownEvent event) {
if (event.getNativeButton() != NativeEvent.BUTTON_LEFT
|| dragHandler != null) {
return;
}
updateResizeFlags(event);
if (sizing || move) {
// some os/browsers don't pass events trough scrollbars; hide
// while dragging (esp. important for resize from right/bottom)
content.getElement().getStyle().setOverflow(Overflow.HIDDEN);
startX = event.getClientX();
startY = event.getClientY();
startW = content.getOffsetWidth();
startH = content.getOffsetHeight();
startTop = getPopupTop();
startLeft = getPopupLeft();
dragHandler = Event.addNativePreviewHandler(this);
event.preventDefault();
stop = false;
}
}
@Override
public void onPreviewNativeEvent(NativePreviewEvent event) {
if (event.getTypeInt() == Event.ONMOUSEMOVE && !stop
&& hasMoved(event.getNativeEvent())) {
int dx = event.getNativeEvent().getClientX() - startX;
int dy = event.getNativeEvent().getClientY() - startY;
if (sizing) {
int minWidth = tabs.getOffsetWidth()
+ controls.getOffsetWidth();
if (resizeLeft) {
int w = startW - dx;
if (w < minWidth) {
w = minWidth;
dx = startW - minWidth;
}
content.setWidth(w + "px");
setPopupPosition(startLeft + dx, getPopupTop());
} else if (resizeRight) {
int w = startW + dx;
if (w < minWidth) {
w = minWidth;
}
content.setWidth(w + "px");
}
if (resizeUp) {
int h = startH - dy;
if (h < MIN_HEIGHT) {
h = MIN_HEIGHT;
dy = startH - MIN_HEIGHT;
}
content.setHeight(h + "px");
setPopupPosition(getPopupLeft(), startTop + dy);
} else if (resizeDown) {
int h = startH + dy;
if (h < MIN_HEIGHT) {
h = MIN_HEIGHT;
}
content.setHeight(h + "px");
}
} else if (move) {
setPopupPosition(startLeft + dx, startTop + dy);
}
event.cancel();
} else if (event.getTypeInt() == Event.ONMOUSEUP) {
stop = true;
if (hasMoved(event.getNativeEvent())) {
event.cancel();
}
} else if (event.getTypeInt() == Event.ONCLICK) {
stop = true;
if (hasMoved(event.getNativeEvent())) {
event.cancel();
}
} else if (stop) {
stop = false;
dragHandler.removeHandler();
dragHandler = null;
sizing = false;
move = false;
// restore scrollbars
content.getElement().getStyle().setOverflow(Overflow.AUTO);
updateCursor();
applyBounds();
readPositionAndSize();
writeStoredState();
event.cancel();
}
}
private boolean hasMoved(NativeEvent event) {
return Math.abs(startX - event.getClientX()) > MOVE_TRESHOLD
|| Math.abs(startY - event.getClientY()) > MOVE_TRESHOLD;
}
private void updateCursor() {
Element c = getElement();
if (resizeLeft) {
if (resizeUp) {
c.getStyle().setCursor(Cursor.NW_RESIZE);
} else if (resizeDown) {
c.getStyle().setCursor(Cursor.SW_RESIZE);
} else {
c.getStyle().setCursor(Cursor.W_RESIZE);
}
} else if (resizeRight) {
if (resizeUp) {
c.getStyle().setCursor(Cursor.NE_RESIZE);
} else if (resizeDown) {
c.getStyle().setCursor(Cursor.SE_RESIZE);
} else {
c.getStyle().setCursor(Cursor.E_RESIZE);
}
} else if (resizeUp) {
c.getStyle().setCursor(Cursor.N_RESIZE);
} else if (resizeDown) {
c.getStyle().setCursor(Cursor.S_RESIZE);
} else if (move) {
c.getStyle().setCursor(Cursor.MOVE);
} else {
c.getStyle().setCursor(Cursor.AUTO);
}
}
protected void updateResizeFlags(MouseEvent event) {
if (event.isShiftKeyDown()) {
// resize from lower right
resizeUp = false;
resizeLeft = false;
resizeRight = true;
resizeDown = true;
move = false;
sizing = true;
return;
} else if (event.isAltKeyDown()) {
// move it
move = true;
resizeUp = false;
resizeLeft = false;
resizeRight = false;
resizeDown = false;
sizing = false;
return;
}
Element c = getElement();
int w = c.getOffsetWidth();
int h = c.getOffsetHeight();
int x = event.getRelativeX(c);
int y = event.getRelativeY(c);
resizeLeft = x < HANDLE_SIZE && y > tabs.getOffsetHeight();
resizeRight = (x > (w - HANDLE_SIZE) && y > tabs.getOffsetHeight())
|| (x > (w - 2 * HANDLE_SIZE) && y > (h - 2 * HANDLE_SIZE));
resizeUp = y > tabs.getOffsetHeight()
&& y < tabs.getOffsetHeight() + HANDLE_SIZE;
resizeDown = y > (h - HANDLE_SIZE)
|| (x > (w - 2 * HANDLE_SIZE) && y > (h - 2 * HANDLE_SIZE));
move = !resizeDown && !resizeLeft && !resizeRight && !resizeUp
&& y < head.getOffsetHeight();
sizing = resizeLeft || resizeRight || resizeUp || resizeDown;
}
}
}