/*
* Copyright 2009 Fred Sauer
*
* 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.allen_sauer.gwt.dnd.client.util;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.IndexedPanel;
import com.google.gwt.user.client.ui.Widget;
import com.allen_sauer.gwt.dnd.client.util.impl.DOMUtilImpl;
/**
* Provides DOM utility methods.
*/
public class DOMUtil {
/**
* Whether or not debugging is enabled.
*/
public static final boolean DEBUG = false;
private static DOMUtilImpl impl;
static {
impl = (DOMUtilImpl) GWT.create(DOMUtilImpl.class);
}
/**
* Adjust line breaks within in the provided title for optimal readability and display length for
* the current user agent.
*
* @param title the desired raw text
* @return formatted and escaped text
*/
public static String adjustTitleForBrowser(String title) {
return impl.adjustTitleForBrowser(title).replaceAll("</?code>", "`");
}
/**
* Cancel all currently selected region(s) on the current page.
*/
public static void cancelAllDocumentSelections() {
impl.cancelAllDocumentSelections();
}
/**
* Set a widget's border style for debugging purposes.
*
* @param widget the widget to color
* @param color the desired border color
*/
public static void debugWidgetWithColor(Widget widget, String color) {
if (DEBUG) {
widget.getElement().getStyle().setProperty("border", "2px solid " + color);
}
}
/**
* Set an element's location as fast as possible, avoiding some of the overhead in
* {@link com.google.gwt.user.client.ui.AbsolutePanel#setWidgetPosition(Widget, int, int)} .
*
* @param elem the element's whose position is to be modified
* @param left the left pixel offset
* @param top the top pixel offset
*/
public static void fastSetElementPosition(Element elem, int left, int top) {
elem.getStyle().setPropertyPx("left", left);
elem.getStyle().setPropertyPx("top", top);
}
/**
* Find child widget intersection at the provided location using the provided comparator strategy.
* TODO Handle LTR case for Bidi
*
* TODO Change IndexedPanel -> InsertPanel
*
* @param parent the parent widget which contains the children to be compared
* @param location the location of the intersection
* @param comparator the comparator strategy
* @return the index of the matching child
*/
public static int findIntersect(
IndexedPanel parent, Location location, LocationWidgetComparator comparator) {
int widgetCount = parent.getWidgetCount();
// short circuit in case dropTarget has no children
if (widgetCount == 0) {
return 0;
}
if (DEBUG) {
for (int i = 0; i < widgetCount; i++) {
debugWidgetWithColor(parent, i, "white");
}
}
// binary search over range of widgets to find intersection
int low = 0;
int high = widgetCount;
while (true) {
int mid = (low + high) / 2;
assert mid >= low;
assert mid < high;
Widget widget = parent.getWidget(mid);
WidgetArea midArea = new WidgetArea(widget, null);
if (mid == low) {
if (mid == 0) {
if (comparator.locationIndicatesIndexFollowingWidget(midArea, location)) {
debugWidgetWithColor(parent, high, "green");
return high;
} else {
debugWidgetWithColor(parent, mid, "green");
return mid;
}
} else {
debugWidgetWithColor(parent, high, "green");
return high;
}
}
if (midArea.getBottom() < location.getTop()) {
debugWidgetWithColor(parent, mid, "blue");
low = mid;
} else if (midArea.getTop() > location.getTop()) {
debugWidgetWithColor(parent, mid, "red");
high = mid;
} else if (midArea.getRight() < location.getLeft()) {
debugWidgetWithColor(parent, mid, "blue");
low = mid;
} else if (midArea.getLeft() > location.getLeft()) {
debugWidgetWithColor(parent, mid, "red");
high = mid;
} else {
if (comparator.locationIndicatesIndexFollowingWidget(midArea, location)) {
debugWidgetWithColor(parent, mid + 1, "green");
return mid + 1;
} else {
debugWidgetWithColor(parent, mid, "green");
return mid;
}
}
}
}
/**
* Gets an element's CSS based 'border-left-width' in pixels or <code>0</code> (zero) when the
* element is hidden.
*
* @param elem the element to be measured
* @return the width of the left CSS border in pixels
*/
public static int getBorderLeft(Element elem) {
return impl.getBorderLeft(elem);
}
/**
* Gets an element's CSS based 'border-top-widget' in pixels or <code>0</code> (zero) when the
* element is hidden.
*
* @param elem the element to be measured
* @return the width of the top CSS border in pixels
*/
public static int getBorderTop(Element elem) {
return impl.getBorderTop(elem);
}
/**
* Gets an element's client height in pixels or <code>0</code> (zero) when the element is hidden.
* This is equal to offset height minus the top and bottom CSS borders.
*
* @param elem the element to be measured
* @return the element's client height in pixels
*/
public static int getClientHeight(Element elem) {
return impl.getClientHeight(elem);
}
/**
* Gets an element's client widget in pixels or <code>0</code> (zero) when the element is hidden.
* This is equal to offset width minus the left and right CSS borders.
*
* @param elem the element to be measured
* @return the element's client width in pixels
*/
public static int getClientWidth(Element elem) {
return impl.getClientWidth(elem);
}
public static String getEffectiveStyle(Element elem, String styleName) {
return impl.getEffectiveStyle(elem, styleName);
}
/**
* Gets the sum of an element's left and right CSS borders in pixels.
*
* @param widget the widget to be measured
* @return the total border width in pixels
*/
public static int getHorizontalBorders(Widget widget) {
return impl.getHorizontalBorders(widget);
}
/**
* Determine an element's node name via the <code>nodeName</code> property.
*
* @param elem the element whose node name is to be determined
* @return the element's node name
*/
public static String getNodeName(Element elem) {
return elem.getNodeName();
}
/**
* Gets the sum of an element's top and bottom CSS borders in pixels.
*
* @param widget the widget to be measured
* @return the total border height in pixels
*/
public static int getVerticalBorders(Widget widget) {
return impl.getVerticalBorders(widget);
}
/**
* Report a fatal exception via <code>Window.alert()</code> than throw a <code>RuntimeException</code>.
*
* @param msg the message to report
* @throws RuntimeException a new exception based on the provided message
*/
public static void reportFatalAndThrowRuntimeException(String msg) throws RuntimeException {
msg = "gwt-dnd warning: " + msg;
Window.alert(msg);
throw new RuntimeException(msg);
}
/**
* Set the browser's status bar text, if supported and enabled in the client browser.
*
* @param text the message to use as the window status
*/
public static void setStatus(String text) {
Window.setStatus(text);
}
/**
* TODO Change IndexedPanel -> InsertPanel
*/
private static void debugWidgetWithColor(IndexedPanel parent, int index, String color) {
if (DEBUG) {
if (index >= parent.getWidgetCount()) {
debugWidgetWithColor(parent.getWidget(parent.getWidgetCount() - 1), color);
} else {
debugWidgetWithColor(parent.getWidget(index), color);
}
}
}
public native static NativeEvent createTouchEndEvent(boolean bubbles,
boolean cancelable,
int detail,
boolean ctrlKey,
boolean altKey,
boolean shiftKey,
boolean metaKey,
JsArray<Touch> changedTouches) /*-{
var evt = $doc.createEvent('TouchEvent');
var view = null;
evt.initUIEvent("touchend", bubbles, cancelable, view, detail);
evt.changedTouches = changedTouches;
return evt;
}-*/;
}