From: Artur Signell Date: Wed, 11 Apr 2012 16:58:26 +0000 (+0300) Subject: Moved each component to its own package on client side X-Git-Tag: 7.0.0.alpha2~82 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=429aeadef913b1d5f3cc932c70b54afa2a57275c;p=vaadin-framework.git Moved each component to its own package on client side Class loaders have problems with referring to static inner classes (e.g. state classes) when they cannot load the declaring class. To deal with this the components have been moved to their own packages and state/rpc classes will be defined in their own Java files. --- diff --git a/src/com/vaadin/terminal/gwt/DefaultWidgetSetBrowserSpecificOverrides.gwt.xml b/src/com/vaadin/terminal/gwt/DefaultWidgetSetBrowserSpecificOverrides.gwt.xml index 82c9f2cf90..3627fa2192 100644 --- a/src/com/vaadin/terminal/gwt/DefaultWidgetSetBrowserSpecificOverrides.gwt.xml +++ b/src/com/vaadin/terminal/gwt/DefaultWidgetSetBrowserSpecificOverrides.gwt.xml @@ -6,27 +6,27 @@ + class="com.vaadin.terminal.gwt.client.ui.upload.UploadIFrameOnloadStrategy"> + class="com.vaadin.terminal.gwt.client.ui.upload.UploadIFrameOnloadStrategy" /> + class="com.vaadin.terminal.gwt.client.ui.upload.UploadIFrameOnloadStrategyIE"> + com.vaadin.terminal.gwt.client.ui.upload.UploadIFrameOnloadStrategytrategy" /> - - + + - - + + diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java index 6f3ff6db45..555112a636 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java @@ -46,11 +46,11 @@ import com.vaadin.terminal.gwt.client.communication.RpcManager; import com.vaadin.terminal.gwt.client.communication.SharedState; import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; import com.vaadin.terminal.gwt.client.ui.VContextMenu; -import com.vaadin.terminal.gwt.client.ui.VNotification; -import com.vaadin.terminal.gwt.client.ui.VNotification.HideEvent; -import com.vaadin.terminal.gwt.client.ui.WindowConnector; import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager; +import com.vaadin.terminal.gwt.client.ui.notification.VNotification; +import com.vaadin.terminal.gwt.client.ui.notification.VNotification.HideEvent; import com.vaadin.terminal.gwt.client.ui.root.RootConnector; +import com.vaadin.terminal.gwt.client.ui.window.WindowConnector; import com.vaadin.terminal.gwt.server.AbstractCommunicationManager; /** diff --git a/src/com/vaadin/terminal/gwt/client/ComponentLocator.java b/src/com/vaadin/terminal/gwt/client/ComponentLocator.java index 8226860533..d847d49e6f 100644 --- a/src/com/vaadin/terminal/gwt/client/ComponentLocator.java +++ b/src/com/vaadin/terminal/gwt/client/ComponentLocator.java @@ -13,12 +13,12 @@ import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ui.SubPartAware; -import com.vaadin.terminal.gwt.client.ui.VGridLayout; -import com.vaadin.terminal.gwt.client.ui.VMeasuringOrderedLayout; -import com.vaadin.terminal.gwt.client.ui.VTabsheetPanel; -import com.vaadin.terminal.gwt.client.ui.VWindow; -import com.vaadin.terminal.gwt.client.ui.WindowConnector; +import com.vaadin.terminal.gwt.client.ui.gridlayout.VGridLayout; +import com.vaadin.terminal.gwt.client.ui.orderedlayout.VMeasuringOrderedLayout; import com.vaadin.terminal.gwt.client.ui.root.VRoot; +import com.vaadin.terminal.gwt.client.ui.tabsheet.VTabsheetPanel; +import com.vaadin.terminal.gwt.client.ui.window.VWindow; +import com.vaadin.terminal.gwt.client.ui.window.WindowConnector; /** * ComponentLocator provides methods for generating a String locator for a given diff --git a/src/com/vaadin/terminal/gwt/client/DateTimeService.java b/src/com/vaadin/terminal/gwt/client/DateTimeService.java index c0151d2819..45ba4a7452 100644 --- a/src/com/vaadin/terminal/gwt/client/DateTimeService.java +++ b/src/com/vaadin/terminal/gwt/client/DateTimeService.java @@ -8,7 +8,7 @@ import java.util.Date; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.i18n.client.LocaleInfo; -import com.vaadin.terminal.gwt.client.ui.VDateField; +import com.vaadin.terminal.gwt.client.ui.datefield.VDateField; /** * This class provides date/time parsing services to all components on the diff --git a/src/com/vaadin/terminal/gwt/client/LayoutManager.java b/src/com/vaadin/terminal/gwt/client/LayoutManager.java index 78709a7134..60a2d3543a 100644 --- a/src/com/vaadin/terminal/gwt/client/LayoutManager.java +++ b/src/com/vaadin/terminal/gwt/client/LayoutManager.java @@ -12,7 +12,7 @@ import com.google.gwt.dom.client.Element; import com.vaadin.terminal.gwt.client.ui.ManagedLayout; import com.vaadin.terminal.gwt.client.ui.PostLayoutListener; import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout; -import com.vaadin.terminal.gwt.client.ui.VNotification; +import com.vaadin.terminal.gwt.client.ui.notification.VNotification; public class LayoutManager { private static final String LOOP_ABORT_MESSAGE = "Aborting layout after 100 passes. This would probably be an infinite loop."; diff --git a/src/com/vaadin/terminal/gwt/client/VDebugConsole.java b/src/com/vaadin/terminal/gwt/client/VDebugConsole.java index ddf007ef37..e3cacc5870 100644 --- a/src/com/vaadin/terminal/gwt/client/VDebugConsole.java +++ b/src/com/vaadin/terminal/gwt/client/VDebugConsole.java @@ -53,10 +53,10 @@ import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; -import com.vaadin.terminal.gwt.client.ui.VNotification; import com.vaadin.terminal.gwt.client.ui.VOverlay; -import com.vaadin.terminal.gwt.client.ui.WindowConnector; +import com.vaadin.terminal.gwt.client.ui.notification.VNotification; import com.vaadin.terminal.gwt.client.ui.root.RootConnector; +import com.vaadin.terminal.gwt.client.ui.window.WindowConnector; /** * A helper console for client side development. The debug console can also be diff --git a/src/com/vaadin/terminal/gwt/client/VUIDLBrowser.java b/src/com/vaadin/terminal/gwt/client/VUIDLBrowser.java index bd79e5f9c6..9fa973dc29 100644 --- a/src/com/vaadin/terminal/gwt/client/VUIDLBrowser.java +++ b/src/com/vaadin/terminal/gwt/client/VUIDLBrowser.java @@ -25,7 +25,7 @@ import com.google.gwt.event.dom.client.MouseOutHandler; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ui.UnknownComponentConnector; -import com.vaadin.terminal.gwt.client.ui.VWindow; +import com.vaadin.terminal.gwt.client.ui.window.VWindow; public class VUIDLBrowser extends SimpleTree { private static final String HELP = "Shift click handle to open recursively. Click components to hightlight them on client side. Shift click components to highlight them also on the server side."; diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbsoluteLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbsoluteLayoutConnector.java deleted file mode 100644 index 7e1683934c..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/AbsoluteLayoutConnector.java +++ /dev/null @@ -1,239 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.HashMap; -import java.util.Map; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.Connector; -import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; -import com.vaadin.terminal.gwt.client.DirectionalManagedLayout; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VCaption; -import com.vaadin.terminal.gwt.client.communication.RpcProxy; -import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; -import com.vaadin.terminal.gwt.client.ui.AbstractLayoutConnector.AbstractLayoutState; -import com.vaadin.terminal.gwt.client.ui.VAbsoluteLayout.AbsoluteWrapper; -import com.vaadin.ui.AbsoluteLayout; - -@Component(AbsoluteLayout.class) -public class AbsoluteLayoutConnector extends - AbstractComponentContainerConnector implements DirectionalManagedLayout { - - public static class AbsoluteLayoutState extends AbstractLayoutState { - // Maps each component to a position - private Map connectorToCssPosition = new HashMap(); - - public String getConnectorPosition(Connector connector) { - return connectorToCssPosition.get(connector.getConnectorId()); - } - - public Map getConnectorToCssPosition() { - return connectorToCssPosition; - } - - public void setConnectorToCssPosition( - Map componentToCssPosition) { - connectorToCssPosition = componentToCssPosition; - } - - } - - public interface AbsoluteLayoutServerRPC extends LayoutClickRPC, ServerRpc { - - } - - private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( - this) { - - @Override - protected ComponentConnector getChildComponent(Element element) { - return getConnectorForElement(element); - } - - @Override - protected LayoutClickRPC getLayoutClickRPC() { - return rpc; - }; - - }; - - private AbsoluteLayoutServerRPC rpc; - - private Map connectorIdToComponentWrapper = new HashMap(); - - @Override - protected void init() { - super.init(); - rpc = RpcProxy.create(AbsoluteLayoutServerRPC.class, this); - } - - /** - * Returns the deepest nested child component which contains "element". The - * child component is also returned if "element" is part of its caption. - * - * @param element - * An element that is a nested sub element of the root element in - * this layout - * @return The Paintable which the element is a part of. Null if the element - * belongs to the layout and not to a child. - */ - protected ComponentConnector getConnectorForElement(Element element) { - return Util.getConnectorForElement(getConnection(), getWidget(), - element); - } - - public void updateCaption(ComponentConnector component) { - VAbsoluteLayout absoluteLayoutWidget = getWidget(); - AbsoluteWrapper componentWrapper = getWrapper(component); - - boolean captionIsNeeded = VCaption.isNeeded(component.getState()); - - VCaption caption = componentWrapper.getCaption(); - - if (captionIsNeeded) { - if (caption == null) { - caption = new VCaption(component, getConnection()); - absoluteLayoutWidget.add(caption); - componentWrapper.setCaption(caption); - } - caption.updateCaption(); - componentWrapper.updateCaptionPosition(); - } else { - if (caption != null) { - caption.removeFromParent(); - } - } - - } - - @Override - protected Widget createWidget() { - return GWT.create(VAbsoluteLayout.class); - } - - @Override - public VAbsoluteLayout getWidget() { - return (VAbsoluteLayout) super.getWidget(); - } - - @Override - public AbsoluteLayoutState getState() { - return (AbsoluteLayoutState) super.getState(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - clickEventHandler.handleEventHandlerRegistration(); - - // TODO Margin handling - - for (ComponentConnector child : getChildren()) { - getWrapper(child).setPosition( - getState().getConnectorPosition(child)); - } - }; - - private AbsoluteWrapper getWrapper(ComponentConnector child) { - String childId = child.getConnectorId(); - AbsoluteWrapper wrapper = connectorIdToComponentWrapper.get(childId); - if (wrapper != null) { - return wrapper; - } - - wrapper = new AbsoluteWrapper(child.getWidget()); - connectorIdToComponentWrapper.put(childId, wrapper); - getWidget().add(wrapper); - return wrapper; - - } - - @Override - public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { - super.onConnectorHierarchyChange(event); - - for (ComponentConnector child : getChildren()) { - getWrapper(child); - } - - for (ComponentConnector oldChild : event.getOldChildren()) { - if (oldChild.getParent() != this) { - String connectorId = oldChild.getConnectorId(); - AbsoluteWrapper absoluteWrapper = connectorIdToComponentWrapper - .remove(connectorId); - absoluteWrapper.destroy(); - } - } - } - - public void layoutVertically() { - VAbsoluteLayout layout = getWidget(); - for (ComponentConnector paintable : getChildren()) { - Widget widget = paintable.getWidget(); - AbsoluteWrapper wrapper = (AbsoluteWrapper) widget.getParent(); - Style wrapperStyle = wrapper.getElement().getStyle(); - - if (paintable.isRelativeHeight()) { - int h; - if (wrapper.top != null && wrapper.bottom != null) { - h = wrapper.getOffsetHeight(); - } else if (wrapper.bottom != null) { - // top not defined, available space 0... bottom of - // wrapper - h = wrapper.getElement().getOffsetTop() - + wrapper.getOffsetHeight(); - } else { - // top defined or both undefined, available space == - // canvas - top - h = layout.canvas.getOffsetHeight() - - wrapper.getElement().getOffsetTop(); - } - wrapperStyle.setHeight(h, Unit.PX); - } else { - wrapperStyle.clearHeight(); - } - - wrapper.updateCaptionPosition(); - } - } - - public void layoutHorizontally() { - VAbsoluteLayout layout = getWidget(); - for (ComponentConnector paintable : getChildren()) { - AbsoluteWrapper wrapper = getWrapper(paintable); - Style wrapperStyle = wrapper.getElement().getStyle(); - - if (paintable.isRelativeWidth()) { - int w; - if (wrapper.left != null && wrapper.right != null) { - w = wrapper.getOffsetWidth(); - } else if (wrapper.right != null) { - // left == null - // available width == right edge == offsetleft + width - w = wrapper.getOffsetWidth() - + wrapper.getElement().getOffsetLeft(); - } else { - // left != null && right == null || left == null && - // right == null - // available width == canvas width - offset left - w = layout.canvas.getOffsetWidth() - - wrapper.getElement().getOffsetLeft(); - } - wrapperStyle.setWidth(w, Unit.PX); - } else { - wrapperStyle.clearWidth(); - } - - wrapper.updateCaptionPosition(); - } - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractDateFieldConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractDateFieldConnector.java deleted file mode 100644 index 7497dc3d35..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractDateFieldConnector.java +++ /dev/null @@ -1,108 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Date; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.LocaleNotLoadedException; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.VConsole; - -public class AbstractDateFieldConnector extends AbstractFieldConnector - implements Paintable { - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (!isRealUpdate(uidl)) { - return; - } - - // Save details - getWidget().client = client; - getWidget().paintableId = uidl.getId(); - getWidget().immediate = getState().isImmediate(); - - getWidget().readonly = isReadOnly(); - getWidget().enabled = isEnabled(); - - if (uidl.hasAttribute("locale")) { - final String locale = uidl.getStringAttribute("locale"); - try { - getWidget().dts.setLocale(locale); - getWidget().currentLocale = locale; - } catch (final LocaleNotLoadedException e) { - getWidget().currentLocale = getWidget().dts.getLocale(); - VConsole.error("Tried to use an unloaded locale \"" + locale - + "\". Using default locale (" - + getWidget().currentLocale + ")."); - VConsole.error(e); - } - } - - // We show week numbers only if the week starts with Monday, as ISO 8601 - // specifies - getWidget().showISOWeekNumbers = uidl - .getBooleanAttribute(VDateField.WEEK_NUMBERS) - && getWidget().dts.getFirstDayOfWeek() == 1; - - int newResolution; - if (uidl.hasVariable("sec")) { - newResolution = VDateField.RESOLUTION_SEC; - } else if (uidl.hasVariable("min")) { - newResolution = VDateField.RESOLUTION_MIN; - } else if (uidl.hasVariable("hour")) { - newResolution = VDateField.RESOLUTION_HOUR; - } else if (uidl.hasVariable("day")) { - newResolution = VDateField.RESOLUTION_DAY; - } else if (uidl.hasVariable("month")) { - newResolution = VDateField.RESOLUTION_MONTH; - } else { - newResolution = VDateField.RESOLUTION_YEAR; - } - - getWidget().currentResolution = newResolution; - - // Add stylename that indicates current resolution - getWidget() - .addStyleName( - VDateField.CLASSNAME - + "-" - + VDateField - .resolutionToString(getWidget().currentResolution)); - - final int year = uidl.getIntVariable("year"); - final int month = (getWidget().currentResolution >= VDateField.RESOLUTION_MONTH) ? uidl - .getIntVariable("month") : -1; - final int day = (getWidget().currentResolution >= VDateField.RESOLUTION_DAY) ? uidl - .getIntVariable("day") : -1; - final int hour = (getWidget().currentResolution >= VDateField.RESOLUTION_HOUR) ? uidl - .getIntVariable("hour") : 0; - final int min = (getWidget().currentResolution >= VDateField.RESOLUTION_MIN) ? uidl - .getIntVariable("min") : 0; - final int sec = (getWidget().currentResolution >= VDateField.RESOLUTION_SEC) ? uidl - .getIntVariable("sec") : 0; - - // Construct new date for this datefield (only if not null) - if (year > -1) { - getWidget().setCurrentDate( - new Date((long) getWidget().getTime(year, month, day, hour, - min, sec, 0))); - } else { - getWidget().setCurrentDate(null); - } - } - - @Override - protected Widget createWidget() { - return GWT.create(VDateField.class); - } - - @Override - public VDateField getWidget() { - return (VDateField) super.getWidget(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractOrderedLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractOrderedLayoutConnector.java deleted file mode 100644 index 0866a3007b..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractOrderedLayoutConnector.java +++ /dev/null @@ -1,304 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.List; - -import com.google.gwt.dom.client.Style; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; -import com.vaadin.terminal.gwt.client.DirectionalManagedLayout; -import com.vaadin.terminal.gwt.client.LayoutManager; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VCaption; -import com.vaadin.terminal.gwt.client.ValueMap; -import com.vaadin.terminal.gwt.client.communication.RpcProxy; -import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.client.ui.layout.ComponentConnectorLayoutSlot; -import com.vaadin.terminal.gwt.client.ui.layout.VLayoutSlot; - -public abstract class AbstractOrderedLayoutConnector extends - AbstractLayoutConnector implements Paintable, DirectionalManagedLayout { - - public static class AbstractOrderedLayoutState extends AbstractLayoutState { - private boolean spacing = false; - - public boolean isSpacing() { - return spacing; - } - - public void setSpacing(boolean spacing) { - this.spacing = spacing; - } - - } - - public interface AbstractOrderedLayoutServerRPC extends LayoutClickRPC, - ServerRpc { - - } - - AbstractOrderedLayoutServerRPC rpc; - - private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( - this) { - - @Override - protected ComponentConnector getChildComponent(Element element) { - return Util.getConnectorForElement(getConnection(), getWidget(), - element); - } - - @Override - protected LayoutClickRPC getLayoutClickRPC() { - return rpc; - }; - - }; - - @Override - public void init() { - rpc = RpcProxy.create(AbstractOrderedLayoutServerRPC.class, this); - getLayoutManager().registerDependency(this, - getWidget().spacingMeasureElement); - } - - @Override - public AbstractOrderedLayoutState getState() { - return (AbstractOrderedLayoutState) super.getState(); - } - - public void updateCaption(ComponentConnector component) { - VMeasuringOrderedLayout layout = getWidget(); - if (VCaption.isNeeded(component.getState())) { - VLayoutSlot layoutSlot = layout.getSlotForChild(component - .getWidget()); - VCaption caption = layoutSlot.getCaption(); - if (caption == null) { - caption = new VCaption(component, getConnection()); - - Widget widget = component.getWidget(); - - layout.setCaption(widget, caption); - } - caption.updateCaption(); - } else { - layout.setCaption(component.getWidget(), null); - getLayoutManager().setNeedsUpdate(this); - } - } - - @Override - public VMeasuringOrderedLayout getWidget() { - return (VMeasuringOrderedLayout) super.getWidget(); - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (!isRealUpdate(uidl)) { - return; - } - clickEventHandler.handleEventHandlerRegistration(); - - VMeasuringOrderedLayout layout = getWidget(); - - ValueMap expandRatios = uidl.getMapAttribute("expandRatios"); - ValueMap alignments = uidl.getMapAttribute("alignments"); - - for (ComponentConnector child : getChildren()) { - VLayoutSlot slot = layout.getSlotForChild(child.getWidget()); - String pid = child.getConnectorId(); - - AlignmentInfo alignment; - if (alignments.containsKey(pid)) { - alignment = new AlignmentInfo(alignments.getInt(pid)); - } else { - alignment = AlignmentInfo.TOP_LEFT; - } - slot.setAlignment(alignment); - - double expandRatio; - if (expandRatios.containsKey(pid)) { - expandRatio = expandRatios.getRawNumber(pid); - } else { - expandRatio = 0; - } - slot.setExpandRatio(expandRatio); - } - - layout.updateMarginStyleNames(new VMarginInfo(getState() - .getMarginsBitmask())); - - layout.updateSpacingStyleName(getState().isSpacing()); - - getLayoutManager().setNeedsUpdate(this); - } - - private int getSizeForInnerSize(int size, boolean isVertical) { - LayoutManager layoutManager = getLayoutManager(); - Element element = getWidget().getElement(); - if (isVertical) { - return size + layoutManager.getBorderHeight(element) - + layoutManager.getPaddingHeight(element); - } else { - return size + layoutManager.getBorderWidth(element) - + layoutManager.getPaddingWidth(element); - } - } - - private static String getSizeProperty(boolean isVertical) { - return isVertical ? "height" : "width"; - } - - private boolean isUndefinedInDirection(boolean isVertical) { - if (isVertical) { - return isUndefinedHeight(); - } else { - return isUndefinedWidth(); - } - } - - private int getInnerSizeInDirection(boolean isVertical) { - if (isVertical) { - return getLayoutManager().getInnerHeight(getWidget().getElement()); - } else { - return getLayoutManager().getInnerWidth(getWidget().getElement()); - } - } - - private void layoutPrimaryDirection() { - VMeasuringOrderedLayout layout = getWidget(); - boolean isVertical = layout.isVertical; - boolean isUndefined = isUndefinedInDirection(isVertical); - - int startPadding = getStartPadding(isVertical); - int spacingSize = getSpacingInDirection(isVertical); - int allocatedSize; - - if (isUndefined) { - allocatedSize = -1; - } else { - allocatedSize = getInnerSizeInDirection(isVertical); - } - - allocatedSize = layout.layoutPrimaryDirection(spacingSize, - allocatedSize, startPadding); - - Style ownStyle = getWidget().getElement().getStyle(); - if (isUndefined) { - ownStyle.setPropertyPx(getSizeProperty(isVertical), - getSizeForInnerSize(allocatedSize, isVertical)); - } else { - ownStyle.setProperty(getSizeProperty(isVertical), - getDefinedSize(isVertical)); - } - } - - private int getSpacingInDirection(boolean isVertical) { - if (isVertical) { - return getLayoutManager().getOuterHeight( - getWidget().spacingMeasureElement); - } else { - return getLayoutManager().getOuterWidth( - getWidget().spacingMeasureElement); - } - } - - private void layoutSecondaryDirection() { - VMeasuringOrderedLayout layout = getWidget(); - boolean isVertical = layout.isVertical; - boolean isUndefined = isUndefinedInDirection(!isVertical); - - int startPadding = getStartPadding(!isVertical); - - int allocatedSize; - if (isUndefined) { - allocatedSize = -1; - } else { - allocatedSize = getInnerSizeInDirection(!isVertical); - } - - allocatedSize = layout.layoutSecondaryDirection(allocatedSize, - startPadding); - - Style ownStyle = getWidget().getElement().getStyle(); - - if (isUndefined) { - ownStyle.setPropertyPx(getSizeProperty(!getWidget().isVertical), - getSizeForInnerSize(allocatedSize, !getWidget().isVertical)); - } else { - ownStyle.setProperty(getSizeProperty(!getWidget().isVertical), - getDefinedSize(!getWidget().isVertical)); - } - } - - private String getDefinedSize(boolean isVertical) { - if (isVertical) { - return getState().getHeight(); - } else { - return getState().getWidth(); - } - } - - private int getStartPadding(boolean isVertical) { - if (isVertical) { - return getLayoutManager().getPaddingTop(getWidget().getElement()); - } else { - return getLayoutManager().getPaddingLeft(getWidget().getElement()); - } - } - - public void layoutHorizontally() { - if (getWidget().isVertical) { - layoutSecondaryDirection(); - } else { - layoutPrimaryDirection(); - } - } - - public void layoutVertically() { - if (getWidget().isVertical) { - layoutPrimaryDirection(); - } else { - layoutSecondaryDirection(); - } - } - - @Override - public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { - super.onConnectorHierarchyChange(event); - List previousChildren = event.getOldChildren(); - int currentIndex = 0; - VMeasuringOrderedLayout layout = getWidget(); - - for (ComponentConnector child : getChildren()) { - Widget childWidget = child.getWidget(); - VLayoutSlot slot = layout.getSlotForChild(childWidget); - - if (childWidget.getParent() != layout) { - // If the child widget was previously attached to another - // AbstractOrderedLayout a slot might be found that belongs to - // another AbstractOrderedLayout. In this case we discard it and - // create a new slot. - slot = new ComponentConnectorLayoutSlot(getWidget() - .getStylePrimaryName(), child, this); - } - layout.addOrMove(slot, currentIndex++); - } - - for (ComponentConnector child : previousChildren) { - if (child.getParent() != this) { - // Remove slot if the connector is no longer a child of this - // layout - layout.removeSlotForWidget(child.getWidget()); - } - } - - }; - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractSplitPanelConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractSplitPanelConnector.java deleted file mode 100644 index 59c524bd1f..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractSplitPanelConnector.java +++ /dev/null @@ -1,281 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.LinkedList; - -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.event.dom.client.DomEvent; -import com.google.gwt.event.dom.client.DomEvent.Type; -import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.ComponentState; -import com.vaadin.terminal.gwt.client.Connector; -import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; -import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.communication.RpcProxy; -import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; -import com.vaadin.terminal.gwt.client.ui.VAbstractSplitPanel.SplitterMoveHandler; -import com.vaadin.terminal.gwt.client.ui.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent; - -public abstract class AbstractSplitPanelConnector extends - AbstractComponentContainerConnector implements SimpleManagedLayout { - - public interface AbstractSplitPanelRPC extends ServerRpc { - - /** - * Called when the position has been updated by the user. - * - * @param position - * The new position in % if the current unit is %, in px - * otherwise - */ - public void setSplitterPosition(float position); - - /** - * Called when a click event has occurred on the splitter. - * - * @param mouseDetails - * Details about the mouse when the event took place - */ - public void splitterClick(MouseEventDetails mouseDetails); - - } - - public static class SplitterState { - private float position; - private String positionUnit; - private boolean positionReversed = false; - private boolean locked = false; - - public float getPosition() { - return position; - } - - public void setPosition(float position) { - this.position = position; - } - - public String getPositionUnit() { - return positionUnit; - } - - public void setPositionUnit(String positionUnit) { - this.positionUnit = positionUnit; - } - - public boolean isPositionReversed() { - return positionReversed; - } - - public void setPositionReversed(boolean positionReversed) { - this.positionReversed = positionReversed; - } - - public boolean isLocked() { - return locked; - } - - public void setLocked(boolean locked) { - this.locked = locked; - } - - } - - public static class AbstractSplitPanelState extends ComponentState { - private Connector firstChild = null; - private Connector secondChild = null; - private SplitterState splitterState = new SplitterState(); - - public boolean hasFirstChild() { - return firstChild != null; - } - - public boolean hasSecondChild() { - return secondChild != null; - } - - public Connector getFirstChild() { - return firstChild; - } - - public void setFirstChild(Connector firstChild) { - this.firstChild = firstChild; - } - - public Connector getSecondChild() { - return secondChild; - } - - public void setSecondChild(Connector secondChild) { - this.secondChild = secondChild; - } - - public SplitterState getSplitterState() { - return splitterState; - } - - public void setSplitterState(SplitterState splitterState) { - this.splitterState = splitterState; - } - - } - - private AbstractSplitPanelRPC rpc; - - @Override - protected void init() { - super.init(); - rpc = RpcProxy.create(AbstractSplitPanelRPC.class, this); - // TODO Remove - getWidget().client = getConnection(); - - getWidget().addHandler(new SplitterMoveHandler() { - - public void splitterMoved(SplitterMoveEvent event) { - String position = getWidget().getSplitterPosition(); - float pos = 0; - if (position.indexOf("%") > 0) { - // Send % values as a fraction to avoid that the splitter - // "jumps" when server responds with the integer pct value - // (e.g. dragged 16.6% -> should not jump to 17%) - pos = Float.valueOf(position.substring(0, - position.length() - 1)); - } else { - pos = Integer.parseInt(position.substring(0, - position.length() - 2)); - } - - rpc.setSplitterPosition(pos); - } - - }, SplitterMoveEvent.TYPE); - } - - public void updateCaption(ComponentConnector component) { - // TODO Implement caption handling - } - - ClickEventHandler clickEventHandler = new ClickEventHandler(this) { - - @Override - protected HandlerRegistration registerHandler( - H handler, Type type) { - if ((Event.getEventsSunk(getWidget().splitter) & Event - .getTypeInt(type.getName())) != 0) { - // If we are already sinking the event for the splitter we do - // not want to additionally sink it for the root element - return getWidget().addHandler(handler, type); - } else { - return getWidget().addDomHandler(handler, type); - } - } - - @Override - protected boolean shouldFireEvent(DomEvent event) { - Element target = event.getNativeEvent().getEventTarget().cast(); - if (!getWidget().splitter.isOrHasChild(target)) { - return false; - } - - return super.shouldFireEvent(event); - }; - - @Override - protected Element getRelativeToElement() { - return getWidget().splitter; - }; - - @Override - protected void fireClick(NativeEvent event, - MouseEventDetails mouseDetails) { - rpc.splitterClick(mouseDetails); - } - - }; - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - getWidget().immediate = getState().isImmediate(); - - getWidget().setEnabled(isEnabled()); - - clickEventHandler.handleEventHandlerRegistration(); - - if (getState().hasStyles()) { - getWidget().componentStyleNames = getState().getStyles(); - } else { - getWidget().componentStyleNames = new LinkedList(); - } - - // Splitter updates - SplitterState splitterState = getState().getSplitterState(); - - getWidget().setLocked(splitterState.isLocked()); - getWidget().setPositionReversed(splitterState.isPositionReversed()); - - getWidget().setStylenames(); - - getWidget().position = splitterState.getPosition() - + splitterState.getPositionUnit(); - - // This is needed at least for cases like #3458 to take - // appearing/disappearing scrollbars into account. - getConnection().runDescendentsLayout(getWidget()); - - getLayoutManager().setNeedsUpdate(this); - - } - - public void layout() { - VAbstractSplitPanel splitPanel = getWidget(); - splitPanel.setSplitPosition(splitPanel.position); - splitPanel.updateSizes(); - } - - @Override - public VAbstractSplitPanel getWidget() { - return (VAbstractSplitPanel) super.getWidget(); - } - - @Override - protected abstract VAbstractSplitPanel createWidget(); - - @Override - public AbstractSplitPanelState getState() { - return (AbstractSplitPanelState) super.getState(); - } - - private ComponentConnector getFirstChild() { - return (ComponentConnector) getState().getFirstChild(); - } - - private ComponentConnector getSecondChild() { - return (ComponentConnector) getState().getSecondChild(); - } - - @Override - public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { - super.onConnectorHierarchyChange(event); - - Widget newFirstChildWidget = null; - if (getFirstChild() != null) { - newFirstChildWidget = getFirstChild().getWidget(); - } - getWidget().setFirstWidget(newFirstChildWidget); - - Widget newSecondChildWidget = null; - if (getSecondChild() != null) { - newSecondChildWidget = getSecondChild().getWidget(); - } - getWidget().setSecondWidget(newSecondChildWidget); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/AccordionConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AccordionConnector.java deleted file mode 100644 index e54f078a94..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/AccordionConnector.java +++ /dev/null @@ -1,79 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Iterator; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.ui.VAccordion.StackItem; -import com.vaadin.ui.Accordion; - -@Component(Accordion.class) -public class AccordionConnector extends TabsheetBaseConnector implements - SimpleManagedLayout { - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - getWidget().selectedUIDLItemIndex = -1; - super.updateFromUIDL(uidl, client); - /* - * Render content after all tabs have been created and we know how large - * the content area is - */ - if (getWidget().selectedUIDLItemIndex >= 0) { - StackItem selectedItem = getWidget().getStackItem( - getWidget().selectedUIDLItemIndex); - UIDL selectedTabUIDL = getWidget().lazyUpdateMap - .remove(selectedItem); - getWidget().open(getWidget().selectedUIDLItemIndex); - - selectedItem.setContent(selectedTabUIDL); - } else if (isRealUpdate(uidl) && getWidget().openTab != null) { - getWidget().close(getWidget().openTab); - } - - getWidget().iLayout(); - // finally render possible hidden tabs - if (getWidget().lazyUpdateMap.size() > 0) { - for (Iterator iterator = getWidget().lazyUpdateMap.keySet() - .iterator(); iterator.hasNext();) { - StackItem item = (StackItem) iterator.next(); - item.setContent(getWidget().lazyUpdateMap.get(item)); - } - getWidget().lazyUpdateMap.clear(); - } - - } - - @Override - public VAccordion getWidget() { - return (VAccordion) super.getWidget(); - } - - @Override - protected Widget createWidget() { - return GWT.create(VAccordion.class); - } - - public void updateCaption(ComponentConnector component) { - /* Accordion does not render its children's captions */ - } - - public void layout() { - VAccordion accordion = getWidget(); - - accordion.updateOpenTabSize(); - - if (isUndefinedHeight()) { - accordion.openTab.setHeightFromWidget(); - } - accordion.iLayout(); - - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/AudioConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AudioConnector.java deleted file mode 100644 index c3c14449c0..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/AudioConnector.java +++ /dev/null @@ -1,42 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.ui.Audio; - -@Component(Audio.class) -public class AudioConnector extends MediaBaseConnector { - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - super.updateFromUIDL(uidl, client); - if (!isRealUpdate(uidl)) { - return; - } - Style style = getWidget().getElement().getStyle(); - - // Make sure that the controls are not clipped if visible. - if (shouldShowControls(uidl) - && (style.getHeight() == null || "".equals(style.getHeight()))) { - if (BrowserInfo.get().isChrome()) { - style.setHeight(32, Unit.PX); - } else { - style.setHeight(25, Unit.PX); - } - } - } - - @Override - protected Widget createWidget() { - return GWT.create(VAudio.class); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/ButtonConnector.java b/src/com/vaadin/terminal/gwt/client/ui/ButtonConnector.java deleted file mode 100644 index 9d8838751f..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/ButtonConnector.java +++ /dev/null @@ -1,139 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.EventHelper; -import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.communication.FieldRpc.FocusAndBlurServerRpc; -import com.vaadin.terminal.gwt.client.communication.RpcProxy; -import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; -import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; -import com.vaadin.ui.Button; - -@Component(value = Button.class, loadStyle = LoadStyle.EAGER) -public class ButtonConnector extends AbstractComponentConnector implements - BlurHandler, FocusHandler { - - /** - * RPC interface for calls from client to server. - * - * @since 7.0 - */ - public interface ButtonServerRpc extends ServerRpc { - /** - * Button click event. - * - * @param mouseEventDetails - * serialized mouse event details - */ - public void click(MouseEventDetails mouseEventDetails); - - /** - * Indicate to the server that the client has disabled the button as a - * result of a click. - */ - public void disableOnClick(); - } - - private ButtonServerRpc rpc = RpcProxy.create(ButtonServerRpc.class, this); - private FocusAndBlurServerRpc focusBlurProxy = RpcProxy.create( - FocusAndBlurServerRpc.class, this); - - private HandlerRegistration focusHandlerRegistration = null; - private HandlerRegistration blurHandlerRegistration = null; - - @Override - public boolean delegateCaptionHandling() { - return false; - } - - @Override - public void init() { - super.init(); - getWidget().buttonRpcProxy = rpc; - getWidget().client = getConnection(); - getWidget().paintableId = getConnectorId(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - focusHandlerRegistration = EventHelper.updateFocusHandler(this, - focusHandlerRegistration); - blurHandlerRegistration = EventHelper.updateBlurHandler(this, - blurHandlerRegistration); - // Set text - getWidget().setText(getState().getCaption()); - - getWidget().disableOnClick = getState().isDisableOnClick(); - - // handle error - if (null != getState().getErrorMessage()) { - if (getWidget().errorIndicatorElement == null) { - getWidget().errorIndicatorElement = DOM.createSpan(); - getWidget().errorIndicatorElement - .setClassName("v-errorindicator"); - } - getWidget().wrapper.insertBefore(getWidget().errorIndicatorElement, - getWidget().captionElement); - - } else if (getWidget().errorIndicatorElement != null) { - getWidget().wrapper.removeChild(getWidget().errorIndicatorElement); - getWidget().errorIndicatorElement = null; - } - - if (getState().getIcon() != null) { - if (getWidget().icon == null) { - getWidget().icon = new Icon(getConnection()); - getWidget().wrapper.insertBefore(getWidget().icon.getElement(), - getWidget().captionElement); - } - getWidget().icon.setUri(getState().getIcon().getURL()); - } else { - if (getWidget().icon != null) { - getWidget().wrapper.removeChild(getWidget().icon.getElement()); - getWidget().icon = null; - } - } - - getWidget().clickShortcut = getState().getClickShortcutKeyCode(); - } - - @Override - protected Widget createWidget() { - return GWT.create(VButton.class); - } - - @Override - public VButton getWidget() { - return (VButton) super.getWidget(); - } - - @Override - public ButtonState getState() { - return (ButtonState) super.getState(); - } - - public void onFocus(FocusEvent event) { - // EventHelper.updateFocusHandler ensures that this is called only when - // there is a listener on server side - focusBlurProxy.focus(); - } - - public void onBlur(BlurEvent event) { - // EventHelper.updateFocusHandler ensures that this is called only when - // there is a listener on server side - focusBlurProxy.blur(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/ButtonState.java b/src/com/vaadin/terminal/gwt/client/ui/ButtonState.java deleted file mode 100644 index 7786f4ca87..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/ButtonState.java +++ /dev/null @@ -1,65 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.vaadin.terminal.gwt.client.ComponentState; -import com.vaadin.ui.Button; - -/** - * Shared state for Button and NativeButton. - * - * @see ComponentState - * - * @since 7.0 - */ -public class ButtonState extends ComponentState { - private boolean disableOnClick = false; - private int clickShortcutKeyCode = 0; - - /** - * Checks whether the button should be disabled on the client side on next - * click. - * - * @return true if the button should be disabled on click - */ - public boolean isDisableOnClick() { - return disableOnClick; - } - - /** - * Sets whether the button should be disabled on the client side on next - * click. - * - * @param disableOnClick - * true if the button should be disabled on click - */ - public void setDisableOnClick(boolean disableOnClick) { - this.disableOnClick = disableOnClick; - } - - /** - * Returns the key code for activating the button via a keyboard shortcut. - * - * See {@link Button#setClickShortcut(int, int...)} for more information. - * - * @return key code or 0 for none - */ - public int getClickShortcutKeyCode() { - return clickShortcutKeyCode; - } - - /** - * Sets the key code for activating the button via a keyboard shortcut. - * - * See {@link Button#setClickShortcut(int, int...)} for more information. - * - * @param clickShortcutKeyCode - * key code or 0 for none - */ - public void setClickShortcutKeyCode(int clickShortcutKeyCode) { - this.clickShortcutKeyCode = clickShortcutKeyCode; - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/CheckBoxConnector.java b/src/com/vaadin/terminal/gwt/client/ui/CheckBoxConnector.java deleted file mode 100644 index f32980c636..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/CheckBoxConnector.java +++ /dev/null @@ -1,165 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.AbstractFieldState; -import com.vaadin.terminal.gwt.client.EventHelper; -import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; -import com.vaadin.terminal.gwt.client.VTooltip; -import com.vaadin.terminal.gwt.client.communication.FieldRpc.FocusAndBlurServerRpc; -import com.vaadin.terminal.gwt.client.communication.RpcProxy; -import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; -import com.vaadin.ui.CheckBox; - -@Component(CheckBox.class) -public class CheckBoxConnector extends AbstractFieldConnector implements - FocusHandler, BlurHandler, ClickHandler { - - public interface CheckBoxServerRpc extends ServerRpc { - public void setChecked(boolean checked, - MouseEventDetails mouseEventDetails); - } - - public static class CheckBoxState extends AbstractFieldState { - private boolean checked = false; - - public boolean isChecked() { - return checked; - } - - public void setChecked(boolean checked) { - this.checked = checked; - } - - } - - private HandlerRegistration focusHandlerRegistration; - private HandlerRegistration blurHandlerRegistration; - - private CheckBoxServerRpc rpc = RpcProxy.create(CheckBoxServerRpc.class, - this); - private FocusAndBlurServerRpc focusBlurRpc = RpcProxy.create( - FocusAndBlurServerRpc.class, this); - - @Override - public boolean delegateCaptionHandling() { - return false; - } - - @Override - protected void init() { - super.init(); - getWidget().addClickHandler(this); - getWidget().client = getConnection(); - getWidget().id = getConnectorId(); - - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - focusHandlerRegistration = EventHelper.updateFocusHandler(this, - focusHandlerRegistration); - blurHandlerRegistration = EventHelper.updateBlurHandler(this, - blurHandlerRegistration); - - if (null != getState().getErrorMessage()) { - if (getWidget().errorIndicatorElement == null) { - getWidget().errorIndicatorElement = DOM.createSpan(); - getWidget().errorIndicatorElement.setInnerHTML(" "); - DOM.setElementProperty(getWidget().errorIndicatorElement, - "className", "v-errorindicator"); - DOM.appendChild(getWidget().getElement(), - getWidget().errorIndicatorElement); - DOM.sinkEvents(getWidget().errorIndicatorElement, - VTooltip.TOOLTIP_EVENTS | Event.ONCLICK); - } else { - DOM.setStyleAttribute(getWidget().errorIndicatorElement, - "display", ""); - } - } else if (getWidget().errorIndicatorElement != null) { - DOM.setStyleAttribute(getWidget().errorIndicatorElement, "display", - "none"); - } - - if (isReadOnly()) { - getWidget().setEnabled(false); - } - - if (getState().getIcon() != null) { - if (getWidget().icon == null) { - getWidget().icon = new Icon(getConnection()); - DOM.insertChild(getWidget().getElement(), - getWidget().icon.getElement(), 1); - getWidget().icon.sinkEvents(VTooltip.TOOLTIP_EVENTS); - getWidget().icon.sinkEvents(Event.ONCLICK); - } - getWidget().icon.setUri(getState().getIcon().getURL()); - } else if (getWidget().icon != null) { - // detach icon - DOM.removeChild(getWidget().getElement(), - getWidget().icon.getElement()); - getWidget().icon = null; - } - - // Set text - getWidget().setText(getState().getCaption()); - getWidget().setValue(getState().isChecked()); - getWidget().immediate = getState().isImmediate(); - } - - @Override - public CheckBoxState getState() { - return (CheckBoxState) super.getState(); - } - - @Override - public VCheckBox getWidget() { - return (VCheckBox) super.getWidget(); - } - - @Override - protected Widget createWidget() { - return GWT.create(VCheckBox.class); - } - - public void onFocus(FocusEvent event) { - // EventHelper.updateFocusHandler ensures that this is called only when - // there is a listener on server side - focusBlurRpc.focus(); - } - - public void onBlur(BlurEvent event) { - // EventHelper.updateFocusHandler ensures that this is called only when - // there is a listener on server side - focusBlurRpc.blur(); - } - - public void onClick(ClickEvent event) { - if (!isEnabled()) { - return; - } - - // Add mouse details - MouseEventDetails details = MouseEventDetailsBuilder - .buildMouseEventDetails(event.getNativeEvent(), getWidget() - .getElement()); - rpc.setChecked(getWidget().getValue(), details); - - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/ComboBoxConnector.java b/src/com/vaadin/terminal/gwt/client/ui/ComboBoxConnector.java deleted file mode 100644 index bc73146965..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/ComboBoxConnector.java +++ /dev/null @@ -1,242 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Iterator; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.ui.VFilterSelect.FilterSelectSuggestion; -import com.vaadin.ui.Select; - -@Component(Select.class) -public class ComboBoxConnector extends AbstractFieldConnector implements - Paintable, SimpleManagedLayout { - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.terminal.gwt.client.Paintable#updateFromUIDL(com.vaadin.terminal - * .gwt.client.UIDL, com.vaadin.terminal.gwt.client.ApplicationConnection) - */ - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - // Save details - getWidget().client = client; - getWidget().paintableId = uidl.getId(); - - getWidget().readonly = isReadOnly(); - getWidget().enabled = isEnabled(); - - getWidget().tb.setEnabled(getWidget().enabled); - getWidget().updateReadOnly(); - - if (!isRealUpdate(uidl)) { - return; - } - - // Inverse logic here to make the default case (text input enabled) - // work without additional UIDL messages - boolean noTextInput = uidl - .hasAttribute(VFilterSelect.ATTR_NO_TEXT_INPUT) - && uidl.getBooleanAttribute(VFilterSelect.ATTR_NO_TEXT_INPUT); - getWidget().setTextInputEnabled(!noTextInput); - - // not a FocusWidget -> needs own tabindex handling - if (uidl.hasAttribute("tabindex")) { - getWidget().tb.setTabIndex(uidl.getIntAttribute("tabindex")); - } - - if (uidl.hasAttribute("filteringmode")) { - getWidget().filteringmode = uidl.getIntAttribute("filteringmode"); - } - - getWidget().immediate = getState().isImmediate(); - - getWidget().nullSelectionAllowed = uidl.hasAttribute("nullselect"); - - getWidget().nullSelectItem = uidl.hasAttribute("nullselectitem") - && uidl.getBooleanAttribute("nullselectitem"); - - getWidget().currentPage = uidl.getIntVariable("page"); - - if (uidl.hasAttribute("pagelength")) { - getWidget().pageLength = uidl.getIntAttribute("pagelength"); - } - - if (uidl.hasAttribute(VFilterSelect.ATTR_INPUTPROMPT)) { - // input prompt changed from server - getWidget().inputPrompt = uidl - .getStringAttribute(VFilterSelect.ATTR_INPUTPROMPT); - } else { - getWidget().inputPrompt = ""; - } - - getWidget().suggestionPopup.updateStyleNames(uidl, getState()); - - getWidget().allowNewItem = uidl.hasAttribute("allownewitem"); - getWidget().lastNewItemString = null; - - getWidget().currentSuggestions.clear(); - if (!getWidget().waitingForFilteringResponse) { - /* - * Clear the current suggestions as the server response always - * includes the new ones. Exception is when filtering, then we need - * to retain the value if the user does not select any of the - * options matching the filter. - */ - getWidget().currentSuggestion = null; - /* - * Also ensure no old items in menu. Unless cleared the old values - * may cause odd effects on blur events. Suggestions in menu might - * not necessary exist in select at all anymore. - */ - getWidget().suggestionPopup.menu.clearItems(); - - } - - final UIDL options = uidl.getChildUIDL(0); - if (uidl.hasAttribute("totalMatches")) { - getWidget().totalMatches = uidl.getIntAttribute("totalMatches"); - } else { - getWidget().totalMatches = 0; - } - - // used only to calculate minimum popup width - String captions = Util.escapeHTML(getWidget().inputPrompt); - - for (final Iterator i = options.getChildIterator(); i.hasNext();) { - final UIDL optionUidl = (UIDL) i.next(); - final FilterSelectSuggestion suggestion = getWidget().new FilterSelectSuggestion( - optionUidl); - getWidget().currentSuggestions.add(suggestion); - if (optionUidl.hasAttribute("selected")) { - if (!getWidget().waitingForFilteringResponse - || getWidget().popupOpenerClicked) { - String newSelectedOptionKey = Integer.toString(suggestion - .getOptionKey()); - if (!newSelectedOptionKey - .equals(getWidget().selectedOptionKey) - || suggestion.getReplacementString().equals( - getWidget().tb.getText())) { - // Update text field if we've got a new selection - // Also update if we've got the same text to retain old - // text selection behavior - getWidget().setPromptingOff( - suggestion.getReplacementString()); - getWidget().selectedOptionKey = newSelectedOptionKey; - } - } - getWidget().currentSuggestion = suggestion; - getWidget().setSelectedItemIcon(suggestion.getIconUri()); - } - - // Collect captions so we can calculate minimum width for textarea - if (captions.length() > 0) { - captions += "|"; - } - captions += Util.escapeHTML(suggestion.getReplacementString()); - } - - if ((!getWidget().waitingForFilteringResponse || getWidget().popupOpenerClicked) - && uidl.hasVariable("selected") - && uidl.getStringArrayVariable("selected").length == 0) { - // select nulled - if (!getWidget().waitingForFilteringResponse - || !getWidget().popupOpenerClicked) { - if (!getWidget().focused) { - /* - * client.updateComponent overwrites all styles so we must - * ALWAYS set the prompting style at this point, even though - * we think it has been set already... - */ - getWidget().prompting = false; - getWidget().setPromptingOn(); - } else { - // we have focus in field, prompting can't be set on, - // instead just clear the input - getWidget().tb.setValue(""); - } - } - getWidget().setSelectedItemIcon(null); - getWidget().selectedOptionKey = null; - } - - if (getWidget().waitingForFilteringResponse - && getWidget().lastFilter.toLowerCase().equals( - uidl.getStringVariable("filter"))) { - getWidget().suggestionPopup.showSuggestions( - getWidget().currentSuggestions, getWidget().currentPage, - getWidget().totalMatches); - getWidget().waitingForFilteringResponse = false; - if (!getWidget().popupOpenerClicked - && getWidget().selectPopupItemWhenResponseIsReceived != VFilterSelect.Select.NONE) { - // we're paging w/ arrows - if (getWidget().selectPopupItemWhenResponseIsReceived == VFilterSelect.Select.LAST) { - getWidget().suggestionPopup.menu.selectLastItem(); - } else { - getWidget().suggestionPopup.menu.selectFirstItem(); - } - - // This is used for paging so we update the keyboard selection - // variable as well. - MenuItem activeMenuItem = getWidget().suggestionPopup.menu - .getSelectedItem(); - getWidget().suggestionPopup.menu - .setKeyboardSelectedItem(activeMenuItem); - - // Update text field to contain the correct text - getWidget().setTextboxText(activeMenuItem.getText()); - getWidget().tb.setSelectionRange( - getWidget().lastFilter.length(), - activeMenuItem.getText().length() - - getWidget().lastFilter.length()); - - getWidget().selectPopupItemWhenResponseIsReceived = VFilterSelect.Select.NONE; // reset - } - if (getWidget().updateSelectionWhenReponseIsReceived) { - getWidget().suggestionPopup.menu - .doPostFilterSelectedItemAction(); - } - } - - // Calculate minumum textarea width - getWidget().suggestionPopupMinWidth = getWidget().minWidth(captions); - - getWidget().popupOpenerClicked = false; - - if (!getWidget().initDone) { - getWidget().updateRootWidth(); - } - - // Focus dependent style names are lost during the update, so we add - // them here back again - if (getWidget().focused) { - getWidget().addStyleDependentName("focus"); - } - - getWidget().initDone = true; - } - - @Override - protected Widget createWidget() { - return GWT.create(VFilterSelect.class); - } - - @Override - public VFilterSelect getWidget() { - return (VFilterSelect) super.getWidget(); - } - - public void layout() { - VFilterSelect widget = getWidget(); - if (widget.initDone) { - widget.updateRootWidth(); - } - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/CssLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/CssLayoutConnector.java deleted file mode 100644 index 554cec0d36..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/CssLayoutConnector.java +++ /dev/null @@ -1,183 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.HashMap; -import java.util.Map; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Style; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.Connector; -import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VCaption; -import com.vaadin.terminal.gwt.client.communication.RpcProxy; -import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; -import com.vaadin.terminal.gwt.client.ui.VCssLayout.FlowPane; -import com.vaadin.ui.CssLayout; - -@Component(CssLayout.class) -public class CssLayoutConnector extends AbstractLayoutConnector { - - public static class CssLayoutState extends AbstractLayoutState { - private Map childCss = new HashMap(); - - public Map getChildCss() { - return childCss; - } - - public void setChildCss(Map childCss) { - this.childCss = childCss; - } - - } - - private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( - this) { - - @Override - protected ComponentConnector getChildComponent(Element element) { - return Util.getConnectorForElement(getConnection(), getWidget(), - element); - } - - @Override - protected LayoutClickRPC getLayoutClickRPC() { - return rpc; - }; - }; - - public interface CssLayoutServerRPC extends LayoutClickRPC, ServerRpc { - - } - - private CssLayoutServerRPC rpc; - - private Map childToCaption = new HashMap(); - - @Override - protected void init() { - super.init(); - rpc = RpcProxy.create(CssLayoutServerRPC.class, this); - } - - @Override - public CssLayoutState getState() { - return (CssLayoutState) super.getState(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - getWidget().setMarginStyles( - new VMarginInfo(getState().getMarginsBitmask())); - - for (ComponentConnector child : getChildren()) { - if (!getState().getChildCss().containsKey(child)) { - continue; - } - String css = getState().getChildCss().get(child); - Style style = child.getWidget().getElement().getStyle(); - // should we remove styles also? How can we know what we have added - // as it is added directly to the child component? - String[] cssRules = css.split(";"); - for (String cssRule : cssRules) { - String parts[] = cssRule.split(":"); - if (parts.length == 2) { - style.setProperty(makeCamelCase(parts[0].trim()), - parts[1].trim()); - } - } - } - - } - - @Override - public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { - super.onConnectorHierarchyChange(event); - - clickEventHandler.handleEventHandlerRegistration(); - - int index = 0; - FlowPane cssLayoutWidgetContainer = getWidget().panel; - for (ComponentConnector child : getChildren()) { - VCaption childCaption = childToCaption.get(child); - if (childCaption != null) { - cssLayoutWidgetContainer.addOrMove(childCaption, index++); - } - cssLayoutWidgetContainer.addOrMove(child.getWidget(), index++); - } - - // Detach old child widgets and possibly their caption - for (ComponentConnector child : event.getOldChildren()) { - if (child.getParent() == this) { - // Skip current children - continue; - } - cssLayoutWidgetContainer.remove(child.getWidget()); - VCaption vCaption = childToCaption.remove(child); - if (vCaption != null) { - cssLayoutWidgetContainer.remove(vCaption); - } - } - } - - private static final String makeCamelCase(String cssProperty) { - // TODO this might be cleaner to implement with regexp - while (cssProperty.contains("-")) { - int indexOf = cssProperty.indexOf("-"); - cssProperty = cssProperty.substring(0, indexOf) - + String.valueOf(cssProperty.charAt(indexOf + 1)) - .toUpperCase() + cssProperty.substring(indexOf + 2); - } - if ("float".equals(cssProperty)) { - if (BrowserInfo.get().isIE()) { - return "styleFloat"; - } else { - return "cssFloat"; - } - } - return cssProperty; - } - - @Override - public VCssLayout getWidget() { - return (VCssLayout) super.getWidget(); - } - - @Override - protected Widget createWidget() { - return GWT.create(VCssLayout.class); - } - - public void updateCaption(ComponentConnector child) { - Widget childWidget = child.getWidget(); - FlowPane cssLayoutWidgetContainer = getWidget().panel; - int widgetPosition = cssLayoutWidgetContainer - .getWidgetIndex(childWidget); - - VCaption caption = childToCaption.get(child); - if (VCaption.isNeeded(child.getState())) { - if (caption == null) { - caption = new VCaption(child, getConnection()); - childToCaption.put(child, caption); - } - if (!caption.isAttached()) { - // Insert caption at widget index == before widget - cssLayoutWidgetContainer.insert(caption, widgetPosition); - } - caption.updateCaption(); - } else if (caption != null) { - childToCaption.remove(child); - cssLayoutWidgetContainer.remove(caption); - } - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/CustomComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/CustomComponentConnector.java deleted file mode 100644 index 68980ef027..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/CustomComponentConnector.java +++ /dev/null @@ -1,44 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; -import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; -import com.vaadin.ui.CustomComponent; - -@Component(value = CustomComponent.class, loadStyle = LoadStyle.EAGER) -public class CustomComponentConnector extends - AbstractComponentContainerConnector { - - @Override - protected Widget createWidget() { - return GWT.create(VCustomComponent.class); - } - - @Override - public VCustomComponent getWidget() { - return (VCustomComponent) super.getWidget(); - } - - public void updateCaption(ComponentConnector component) { - // NOP, custom component dont render composition roots caption - } - - @Override - public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { - super.onConnectorHierarchyChange(event); - - ComponentConnector newChild = null; - if (getChildren().size() == 1) { - newChild = getChildren().get(0); - } - - VCustomComponent customComponent = getWidget(); - customComponent.setWidget(newChild.getWidget()); - - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/CustomFieldConnector.java b/src/com/vaadin/terminal/gwt/client/ui/CustomFieldConnector.java deleted file mode 100644 index 477a4b496a..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/CustomFieldConnector.java +++ /dev/null @@ -1,22 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.ui.CustomField; - -@Component(value = CustomField.class) -public class CustomFieldConnector extends CustomComponentConnector { - - @Override - protected Widget createWidget() { - return GWT.create(VCustomComponent.class); - } - - @Override - public VCustomComponent getWidget() { - return super.getWidget(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/CustomLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/CustomLayoutConnector.java deleted file mode 100644 index f0fb1b7e6f..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/CustomLayoutConnector.java +++ /dev/null @@ -1,150 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.HashMap; -import java.util.Map; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.Connector; -import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; -import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; -import com.vaadin.ui.CustomLayout; - -@Component(CustomLayout.class) -public class CustomLayoutConnector extends AbstractLayoutConnector implements - SimpleManagedLayout { - - public static class CustomLayoutState extends AbstractLayoutState { - Map childLocations = new HashMap(); - private String templateContents; - private String templateName; - - public String getTemplateContents() { - return templateContents; - } - - public void setTemplateContents(String templateContents) { - this.templateContents = templateContents; - } - - public String getTemplateName() { - return templateName; - } - - public void setTemplateName(String templateName) { - this.templateName = templateName; - } - - public Map getChildLocations() { - return childLocations; - } - - public void setChildLocations(Map childLocations) { - this.childLocations = childLocations; - } - - } - - @Override - public CustomLayoutState getState() { - return (CustomLayoutState) super.getState(); - } - - @Override - protected void init() { - super.init(); - getWidget().client = getConnection(); - getWidget().pid = getConnectorId(); - - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - // Evaluate scripts - VCustomLayout.eval(getWidget().scripts); - getWidget().scripts = null; - - } - - private void updateHtmlTemplate() { - if (getWidget().hasTemplate()) { - // We (currently) only do this once. You can't change the template - // later on. - return; - } - String templateName = getState().getTemplateName(); - String templateContents = getState().getTemplateContents(); - - if (templateName != null) { - // Get the HTML-template from client. Overrides templateContents - // (even though both can never be given at the same time) - templateContents = getConnection().getResource( - "layouts/" + templateName + ".html"); - if (templateContents == null) { - templateContents = "Layout file layouts/" - + templateName - + ".html is missing. Components will be drawn for debug purposes."; - } - } - - getWidget().initializeHTML(templateContents, - getConnection().getThemeUri()); - } - - @Override - public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { - super.onConnectorHierarchyChange(event); - - // Must do this once here so the HTML has been set up before we start - // adding child widgets. - - updateHtmlTemplate(); - - // For all contained widgets - for (ComponentConnector child : getChildren()) { - String location = getState().getChildLocations().get(child); - try { - getWidget().setWidget(child.getWidget(), location); - } catch (final IllegalArgumentException e) { - // If no location is found, this component is not visible - } - } - for (ComponentConnector oldChild : event.getOldChildren()) { - if (oldChild.getParent() == this) { - // Connector still a child of this - continue; - } - Widget oldChildWidget = oldChild.getWidget(); - if (oldChildWidget.isAttached()) { - // slot of this widget is emptied, remove it - getWidget().remove(oldChildWidget); - } - } - - } - - @Override - public VCustomLayout getWidget() { - return (VCustomLayout) super.getWidget(); - } - - @Override - protected Widget createWidget() { - return GWT.create(VCustomLayout.class); - } - - public void updateCaption(ComponentConnector paintable) { - getWidget().updateCaption(paintable); - } - - public void layout() { - getWidget().iLayoutJS(DOM.getFirstChild(getWidget().getElement())); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/DragAndDropWrapperConnector.java b/src/com/vaadin/terminal/gwt/client/ui/DragAndDropWrapperConnector.java deleted file mode 100644 index d8cec83612..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/DragAndDropWrapperConnector.java +++ /dev/null @@ -1,73 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.HashMap; -import java.util.Set; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.ui.DragAndDropWrapper; - -@Component(DragAndDropWrapper.class) -public class DragAndDropWrapperConnector extends CustomComponentConnector - implements Paintable { - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - getWidget().client = client; - if (isRealUpdate(uidl) && !uidl.hasAttribute("hidden")) { - UIDL acceptCrit = uidl.getChildByTagName("-ac"); - if (acceptCrit == null) { - getWidget().dropHandler = null; - } else { - if (getWidget().dropHandler == null) { - getWidget().dropHandler = getWidget().new CustomDropHandler(); - } - getWidget().dropHandler.updateAcceptRules(acceptCrit); - } - - Set variableNames = uidl.getVariableNames(); - for (String fileId : variableNames) { - if (fileId.startsWith("rec-")) { - String receiverUrl = uidl.getStringVariable(fileId); - fileId = fileId.substring(4); - if (getWidget().fileIdToReceiver == null) { - getWidget().fileIdToReceiver = new HashMap(); - } - if ("".equals(receiverUrl)) { - Integer id = Integer.parseInt(fileId); - int indexOf = getWidget().fileIds.indexOf(id); - if (indexOf != -1) { - getWidget().files.remove(indexOf); - getWidget().fileIds.remove(indexOf); - } - } else { - getWidget().fileIdToReceiver.put(fileId, receiverUrl); - } - } - } - getWidget().startNextUpload(); - - getWidget().dragStartMode = uidl - .getIntAttribute(VDragAndDropWrapper.DRAG_START_MODE); - getWidget().initDragStartMode(); - getWidget().html5DataFlavors = uidl - .getMapAttribute(VDragAndDropWrapper.HTML5_DATA_FLAVORS); - } - } - - @Override - protected Widget createWidget() { - return GWT.create(VDragAndDropWrapper.class); - } - - @Override - public VDragAndDropWrapper getWidget() { - return (VDragAndDropWrapper) super.getWidget(); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/EmbeddedConnector.java b/src/com/vaadin/terminal/gwt/client/ui/EmbeddedConnector.java deleted file mode 100644 index b716b7637f..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/EmbeddedConnector.java +++ /dev/null @@ -1,206 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Map; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.NodeList; -import com.google.gwt.dom.client.ObjectElement; -import com.google.gwt.dom.client.Style; -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.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.VConsole; -import com.vaadin.terminal.gwt.client.VTooltip; -import com.vaadin.terminal.gwt.client.communication.RpcProxy; -import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.ui.Embedded; - -@Component(Embedded.class) -public class EmbeddedConnector extends AbstractComponentConnector implements - Paintable { - - public interface EmbeddedServerRPC extends ClickRPC, ServerRpc { - } - - public static final String ALTERNATE_TEXT = "alt"; - - EmbeddedServerRPC rpc; - - @Override - protected void init() { - super.init(); - rpc = RpcProxy.create(EmbeddedServerRPC.class, this); - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (!isRealUpdate(uidl)) { - return; - } - - // Save details - getWidget().client = client; - - boolean clearBrowserElement = true; - - clickEventHandler.handleEventHandlerRegistration(); - - if (uidl.hasAttribute("type")) { - getWidget().type = uidl.getStringAttribute("type"); - if (getWidget().type.equals("image")) { - getWidget().addStyleName(VEmbedded.CLASSNAME + "-image"); - Element el = null; - boolean created = false; - NodeList nodes = getWidget().getElement().getChildNodes(); - if (nodes != null && nodes.getLength() == 1) { - Node n = nodes.getItem(0); - if (n.getNodeType() == Node.ELEMENT_NODE) { - Element e = (Element) n; - if (e.getTagName().equals("IMG")) { - el = e; - } - } - } - if (el == null) { - getWidget().setHTML(""); - el = DOM.createImg(); - created = true; - DOM.sinkEvents(el, Event.ONLOAD); - } - - // Set attributes - Style style = el.getStyle(); - style.setProperty("width", getState().getWidth()); - style.setProperty("height", getState().getHeight()); - - DOM.setElementProperty(el, "src", - getWidget().getSrc(uidl, client)); - - if (uidl.hasAttribute(ALTERNATE_TEXT)) { - el.setPropertyString(ALTERNATE_TEXT, - uidl.getStringAttribute(ALTERNATE_TEXT)); - } - - if (created) { - // insert in dom late - getWidget().getElement().appendChild(el); - } - - /* - * Sink tooltip events so tooltip is displayed when hovering the - * image. - */ - getWidget().sinkEvents(VTooltip.TOOLTIP_EVENTS); - - } else if (getWidget().type.equals("browser")) { - getWidget().addStyleName(VEmbedded.CLASSNAME + "-browser"); - if (getWidget().browserElement == null) { - getWidget().setHTML( - ""); - getWidget().browserElement = DOM.getFirstChild(getWidget() - .getElement()); - } - DOM.setElementAttribute(getWidget().browserElement, "src", - getWidget().getSrc(uidl, client)); - clearBrowserElement = false; - } else { - VConsole.log("Unknown Embedded type '" + getWidget().type + "'"); - } - } else if (uidl.hasAttribute("mimetype")) { - final String mime = uidl.getStringAttribute("mimetype"); - if (mime.equals("application/x-shockwave-flash")) { - // Handle embedding of Flash - getWidget().addStyleName(VEmbedded.CLASSNAME + "-flash"); - getWidget().setHTML(getWidget().createFlashEmbed(uidl)); - - } else if (mime.equals("image/svg+xml")) { - getWidget().addStyleName(VEmbedded.CLASSNAME + "-svg"); - String data; - Map parameters = VEmbedded.getParameters(uidl); - if (parameters.get("data") == null) { - data = getWidget().getSrc(uidl, client); - } else { - data = "data:image/svg+xml," + parameters.get("data"); - } - getWidget().setHTML(""); - ObjectElement obj = Document.get().createObjectElement(); - obj.setType(mime); - obj.setData(data); - if (!isUndefinedWidth()) { - obj.getStyle().setProperty("width", "100%"); - } - if (!isUndefinedHeight()) { - obj.getStyle().setProperty("height", "100%"); - } - if (uidl.hasAttribute("classid")) { - obj.setAttribute("classid", - uidl.getStringAttribute("classid")); - } - if (uidl.hasAttribute("codebase")) { - obj.setAttribute("codebase", - uidl.getStringAttribute("codebase")); - } - if (uidl.hasAttribute("codetype")) { - obj.setAttribute("codetype", - uidl.getStringAttribute("codetype")); - } - if (uidl.hasAttribute("archive")) { - obj.setAttribute("archive", - uidl.getStringAttribute("archive")); - } - if (uidl.hasAttribute("standby")) { - obj.setAttribute("standby", - uidl.getStringAttribute("standby")); - } - getWidget().getElement().appendChild(obj); - if (uidl.hasAttribute(ALTERNATE_TEXT)) { - obj.setInnerText(uidl.getStringAttribute(ALTERNATE_TEXT)); - } - } else { - VConsole.log("Unknown Embedded mimetype '" + mime + "'"); - } - } else { - VConsole.log("Unknown Embedded; no type or mimetype attribute"); - } - - if (clearBrowserElement) { - getWidget().browserElement = null; - } - } - - @Override - protected Widget createWidget() { - return GWT.create(VEmbedded.class); - } - - @Override - public VEmbedded getWidget() { - return (VEmbedded) super.getWidget(); - } - - protected final ClickEventHandler clickEventHandler = new ClickEventHandler( - this) { - - @Override - protected void fireClick(NativeEvent event, - MouseEventDetails mouseDetails) { - rpc.click(mouseDetails); - } - - }; - -} \ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/FormConnector.java b/src/com/vaadin/terminal/gwt/client/ui/FormConnector.java deleted file mode 100644 index 8e05522eb5..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/FormConnector.java +++ /dev/null @@ -1,206 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.AbstractFieldState; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.Connector; -import com.vaadin.terminal.gwt.client.LayoutManager; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.ui.Form; - -@Component(Form.class) -public class FormConnector extends AbstractComponentContainerConnector - implements Paintable, SimpleManagedLayout { - - public static class FormState extends AbstractFieldState { - private Connector layout; - private Connector footer; - - public Connector getLayout() { - return layout; - } - - public void setLayout(Connector layout) { - this.layout = layout; - } - - public Connector getFooter() { - return footer; - } - - public void setFooter(Connector footer) { - this.footer = footer; - } - - } - - @Override - public void init() { - VForm form = getWidget(); - getLayoutManager().registerDependency(this, form.footerContainer); - } - - @Override - public boolean delegateCaptionHandling() { - return false; - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - getWidget().client = client; - getWidget().id = uidl.getId(); - - if (!isRealUpdate(uidl)) { - return; - } - - boolean legendEmpty = true; - if (getState().getCaption() != null) { - getWidget().caption.setInnerText(getState().getCaption()); - legendEmpty = false; - } else { - getWidget().caption.setInnerText(""); - } - if (getState().getIcon() != null) { - if (getWidget().icon == null) { - getWidget().icon = new Icon(client); - getWidget().legend.insertFirst(getWidget().icon.getElement()); - } - getWidget().icon.setUri(getState().getIcon().getURL()); - legendEmpty = false; - } else { - if (getWidget().icon != null) { - getWidget().legend.removeChild(getWidget().icon.getElement()); - } - } - if (legendEmpty) { - getWidget().addStyleDependentName("nocaption"); - } else { - getWidget().removeStyleDependentName("nocaption"); - } - - if (null != getState().getErrorMessage()) { - getWidget().errorMessage - .updateMessage(getState().getErrorMessage()); - getWidget().errorMessage.setVisible(true); - } else { - getWidget().errorMessage.setVisible(false); - } - - if (getState().hasDescription()) { - getWidget().desc.setInnerHTML(getState().getDescription()); - if (getWidget().desc.getParentElement() == null) { - getWidget().fieldSet.insertAfter(getWidget().desc, - getWidget().legend); - } - } else { - getWidget().desc.setInnerHTML(""); - if (getWidget().desc.getParentElement() != null) { - getWidget().fieldSet.removeChild(getWidget().desc); - } - } - - // first render footer so it will be easier to handle relative height of - // main layout - if (getState().getFooter() != null) { - // render footer - ComponentConnector newFooter = (ComponentConnector) getState() - .getFooter(); - Widget newFooterWidget = newFooter.getWidget(); - if (getWidget().footer == null) { - getWidget().add(newFooter.getWidget(), - getWidget().footerContainer); - getWidget().footer = newFooterWidget; - } else if (newFooter != getWidget().footer) { - getWidget().remove(getWidget().footer); - getWidget().add(newFooter.getWidget(), - getWidget().footerContainer); - } - getWidget().footer = newFooterWidget; - } else { - if (getWidget().footer != null) { - getWidget().remove(getWidget().footer); - } - } - - ComponentConnector newLayout = (ComponentConnector) getState() - .getLayout(); - Widget newLayoutWidget = newLayout.getWidget(); - if (getWidget().lo == null) { - // Layout not rendered before - getWidget().lo = newLayoutWidget; - getWidget().add(newLayoutWidget, getWidget().fieldContainer); - } else if (getWidget().lo != newLayoutWidget) { - // Layout has changed - getWidget().remove(getWidget().lo); - getWidget().lo = newLayoutWidget; - getWidget().add(newLayoutWidget, getWidget().fieldContainer); - } - - // also recalculates size of the footer if undefined size form - see - // #3710 - client.runDescendentsLayout(getWidget()); - - // We may have actions attached - if (uidl.getChildCount() >= 1) { - UIDL childUidl = uidl.getChildByTagName("actions"); - if (childUidl != null) { - if (getWidget().shortcutHandler == null) { - getWidget().shortcutHandler = new ShortcutActionHandler( - getConnectorId(), client); - getWidget().keyDownRegistration = getWidget() - .addDomHandler(getWidget(), KeyDownEvent.getType()); - } - getWidget().shortcutHandler.updateActionMap(childUidl); - } - } else if (getWidget().shortcutHandler != null) { - getWidget().keyDownRegistration.removeHandler(); - getWidget().shortcutHandler = null; - getWidget().keyDownRegistration = null; - } - } - - public void updateCaption(ComponentConnector component) { - // NOP form don't render caption for neither field layout nor footer - // layout - } - - @Override - public VForm getWidget() { - return (VForm) super.getWidget(); - } - - @Override - protected Widget createWidget() { - return GWT.create(VForm.class); - } - - public void layout() { - VForm form = getWidget(); - - LayoutManager lm = getLayoutManager(); - int footerHeight = lm.getOuterHeight(form.footerContainer) - - lm.getMarginTop(form.footerContainer); - - form.fieldContainer.getStyle().setPaddingBottom(footerHeight, Unit.PX); - form.footerContainer.getStyle().setMarginTop(-footerHeight, Unit.PX); - } - - @Override - public boolean isReadOnly() { - return super.isReadOnly() || getState().isPropertyReadOnly(); - } - - @Override - public FormState getState() { - return (FormState) super.getState(); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/FormLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/FormLayoutConnector.java deleted file mode 100644 index 2b2f129246..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/FormLayoutConnector.java +++ /dev/null @@ -1,101 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; -import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; -import com.vaadin.terminal.gwt.client.ui.AbstractOrderedLayoutConnector.AbstractOrderedLayoutState; -import com.vaadin.terminal.gwt.client.ui.VFormLayout.Caption; -import com.vaadin.terminal.gwt.client.ui.VFormLayout.ErrorFlag; -import com.vaadin.terminal.gwt.client.ui.VFormLayout.VFormLayoutTable; -import com.vaadin.ui.FormLayout; - -@Component(FormLayout.class) -public class FormLayoutConnector extends AbstractLayoutConnector { - - @Override - public AbstractOrderedLayoutState getState() { - return (AbstractOrderedLayoutState) super.getState(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - VFormLayoutTable formLayoutTable = getWidget().table; - - formLayoutTable.setMargins(new VMarginInfo(getState() - .getMarginsBitmask())); - formLayoutTable.setSpacing(getState().isSpacing()); - - } - - @Override - public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { - super.onConnectorHierarchyChange(event); - - VFormLayout formLayout = getWidget(); - VFormLayoutTable formLayoutTable = getWidget().table; - - int childId = 0; - - formLayoutTable.setRowCount(getChildren().size()); - - for (ComponentConnector child : getChildren()) { - Widget childWidget = child.getWidget(); - - Caption caption = formLayoutTable.getCaption(childWidget); - if (caption == null) { - caption = formLayout.new Caption(child); - caption.addClickHandler(formLayoutTable); - } - - ErrorFlag error = formLayoutTable.getError(childWidget); - if (error == null) { - error = formLayout.new ErrorFlag(child); - } - - formLayoutTable.setChild(childId, childWidget, caption, error); - childId++; - } - - for (ComponentConnector oldChild : event.getOldChildren()) { - if (oldChild.getParent() == this) { - continue; - } - - formLayoutTable.cleanReferences(oldChild.getWidget()); - } - - } - - public void updateCaption(ComponentConnector component) { - getWidget().table.updateCaption(component.getWidget(), - component.getState(), component.isEnabled()); - boolean hideErrors = false; - - // FIXME This incorrectly depends on AbstractFieldConnector - if (component instanceof AbstractFieldConnector) { - hideErrors = ((AbstractFieldConnector) component).getState() - .isHideErrors(); - } - - getWidget().table.updateError(component.getWidget(), component - .getState().getErrorMessage(), hideErrors); - } - - @Override - public VFormLayout getWidget() { - return (VFormLayout) super.getWidget(); - } - - @Override - protected Widget createWidget() { - return GWT.create(VFormLayout.class); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/GridLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/GridLayoutConnector.java deleted file mode 100644 index 4b37d2c407..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/GridLayoutConnector.java +++ /dev/null @@ -1,257 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Iterator; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; -import com.vaadin.terminal.gwt.client.ConnectorMap; -import com.vaadin.terminal.gwt.client.DirectionalManagedLayout; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.VCaption; -import com.vaadin.terminal.gwt.client.communication.RpcProxy; -import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; -import com.vaadin.terminal.gwt.client.ui.AbstractLayoutConnector.AbstractLayoutState; -import com.vaadin.terminal.gwt.client.ui.VGridLayout.Cell; -import com.vaadin.terminal.gwt.client.ui.layout.VLayoutSlot; -import com.vaadin.ui.GridLayout; - -@Component(GridLayout.class) -public class GridLayoutConnector extends AbstractComponentContainerConnector - implements Paintable, DirectionalManagedLayout { - - public static class GridLayoutState extends AbstractLayoutState { - private boolean spacing = false; - private int rows = 0; - private int columns = 0; - - public boolean isSpacing() { - return spacing; - } - - public void setSpacing(boolean spacing) { - this.spacing = spacing; - } - - public int getRows() { - return rows; - } - - public void setRows(int rows) { - this.rows = rows; - } - - public int getColumns() { - return columns; - } - - public void setColumns(int cols) { - columns = cols; - } - - } - - private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( - this) { - - @Override - protected ComponentConnector getChildComponent(Element element) { - return getWidget().getComponent(element); - } - - @Override - protected LayoutClickRPC getLayoutClickRPC() { - return rpc; - }; - - }; - - public interface GridLayoutServerRPC extends LayoutClickRPC, ServerRpc { - - } - - private GridLayoutServerRPC rpc; - private boolean needCaptionUpdate = false; - - @Override - public void init() { - rpc = RpcProxy.create(GridLayoutServerRPC.class, this); - getLayoutManager().registerDependency(this, - getWidget().spacingMeasureElement); - } - - @Override - public GridLayoutState getState() { - return (GridLayoutState) super.getState(); - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - clickEventHandler.handleEventHandlerRegistration(); - - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - VGridLayout layout = getWidget(); - layout.client = client; - - if (!isRealUpdate(uidl)) { - return; - } - - int cols = getState().getColumns(); - int rows = getState().getRows(); - - layout.columnWidths = new int[cols]; - layout.rowHeights = new int[rows]; - - layout.setSize(rows, cols); - - final int[] alignments = uidl.getIntArrayAttribute("alignments"); - int alignmentIndex = 0; - - for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { - final UIDL r = (UIDL) i.next(); - if ("gr".equals(r.getTag())) { - for (final Iterator j = r.getChildIterator(); j.hasNext();) { - final UIDL cellUidl = (UIDL) j.next(); - if ("gc".equals(cellUidl.getTag())) { - int row = cellUidl.getIntAttribute("y"); - int col = cellUidl.getIntAttribute("x"); - - Widget previousWidget = null; - - Cell cell = layout.getCell(row, col); - if (cell != null && cell.slot != null) { - // This is an update. Track if the widget changes - // and update the caption if that happens. This - // workaround can be removed once the DOM update is - // done in onContainerHierarchyChange - previousWidget = cell.slot.getWidget(); - } - - cell = layout.createCell(row, col); - - cell.updateFromUidl(cellUidl); - - if (cell.hasContent()) { - cell.setAlignment(new AlignmentInfo( - alignments[alignmentIndex++])); - if (cell.slot.getWidget() != previousWidget) { - // Widget changed or widget moved from another - // slot. Update its caption as the widget might - // have called updateCaption when the widget was - // still in its old slot. This workaround can be - // removed once the DOM update - // is done in onContainerHierarchyChange - updateCaption(ConnectorMap.get(getConnection()) - .getConnector(cell.slot.getWidget())); - } - } - } - } - } - } - - layout.colExpandRatioArray = uidl.getIntArrayAttribute("colExpand"); - layout.rowExpandRatioArray = uidl.getIntArrayAttribute("rowExpand"); - - layout.updateMarginStyleNames(new VMarginInfo(getState() - .getMarginsBitmask())); - - layout.updateSpacingStyleName(getState().isSpacing()); - - if (needCaptionUpdate) { - needCaptionUpdate = false; - - for (ComponentConnector child : getChildren()) { - updateCaption(child); - } - } - getLayoutManager().setNeedsUpdate(this); - } - - @Override - public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { - super.onConnectorHierarchyChange(event); - - VGridLayout layout = getWidget(); - - // clean non rendered components - for (ComponentConnector oldChild : event.getOldChildren()) { - if (oldChild.getParent() == this) { - continue; - } - - Widget childWidget = oldChild.getWidget(); - layout.remove(childWidget); - - Cell cell = layout.widgetToCell.remove(childWidget); - cell.slot.setCaption(null); - cell.slot.getWrapperElement().removeFromParent(); - cell.slot = null; - } - - } - - public void updateCaption(ComponentConnector childConnector) { - if (!childConnector.delegateCaptionHandling()) { - // Check not required by interface but by workarounds in this class - // when updateCaption is explicitly called for all children. - return; - } - - VGridLayout layout = getWidget(); - Cell cell = layout.widgetToCell.get(childConnector.getWidget()); - if (cell == null) { - // workaround before updateFromUidl is removed. We currently update - // the captions at the end of updateFromUidl instead of immediately - // because the DOM has not been set up at this point (as it is done - // in updateFromUidl) - needCaptionUpdate = true; - return; - } - if (VCaption.isNeeded(childConnector.getState())) { - VLayoutSlot layoutSlot = cell.slot; - VCaption caption = layoutSlot.getCaption(); - if (caption == null) { - caption = new VCaption(childConnector, getConnection()); - - Widget widget = childConnector.getWidget(); - - layout.setCaption(widget, caption); - } - caption.updateCaption(); - } else { - layout.setCaption(childConnector.getWidget(), null); - } - } - - @Override - public VGridLayout getWidget() { - return (VGridLayout) super.getWidget(); - } - - @Override - protected Widget createWidget() { - return GWT.create(VGridLayout.class); - } - - public void layoutVertically() { - getWidget().updateHeight(); - } - - public void layoutHorizontally() { - getWidget().updateWidth(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/HorizontalLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/HorizontalLayoutConnector.java deleted file mode 100644 index da1ea99f28..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/HorizontalLayoutConnector.java +++ /dev/null @@ -1,23 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; -import com.vaadin.ui.HorizontalLayout; - -@Component(value = HorizontalLayout.class, loadStyle = LoadStyle.EAGER) -public class HorizontalLayoutConnector extends AbstractOrderedLayoutConnector { - - @Override - public VHorizontalLayout getWidget() { - return (VHorizontalLayout) super.getWidget(); - } - - @Override - protected VHorizontalLayout createWidget() { - return GWT.create(VHorizontalLayout.class); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/HorizontalSplitPanelConnector.java b/src/com/vaadin/terminal/gwt/client/ui/HorizontalSplitPanelConnector.java deleted file mode 100644 index 4f441b2d04..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/HorizontalSplitPanelConnector.java +++ /dev/null @@ -1,18 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; -import com.vaadin.ui.HorizontalSplitPanel; - -@Component(value = HorizontalSplitPanel.class, loadStyle = LoadStyle.EAGER) -public class HorizontalSplitPanelConnector extends AbstractSplitPanelConnector { - - @Override - protected VAbstractSplitPanel createWidget() { - return GWT.create(VSplitPanelHorizontal.class); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/InlineDateFieldConnector.java b/src/com/vaadin/terminal/gwt/client/ui/InlineDateFieldConnector.java deleted file mode 100644 index 31ffe199d8..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/InlineDateFieldConnector.java +++ /dev/null @@ -1,103 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Date; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.DateTimeService; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.FocusChangeListener; -import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.TimeChangeListener; -import com.vaadin.ui.InlineDateField; - -@Component(InlineDateField.class) -public class InlineDateFieldConnector extends AbstractDateFieldConnector { - - @Override - @SuppressWarnings("deprecation") - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - super.updateFromUIDL(uidl, client); - if (!isRealUpdate(uidl)) { - return; - } - - getWidget().calendarPanel.setShowISOWeekNumbers(getWidget() - .isShowISOWeekNumbers()); - getWidget().calendarPanel.setDateTimeService(getWidget() - .getDateTimeService()); - getWidget().calendarPanel.setResolution(getWidget() - .getCurrentResolution()); - Date currentDate = getWidget().getCurrentDate(); - if (currentDate != null) { - getWidget().calendarPanel.setDate(new Date(currentDate.getTime())); - } else { - getWidget().calendarPanel.setDate(null); - } - - if (getWidget().currentResolution > VDateField.RESOLUTION_DAY) { - getWidget().calendarPanel - .setTimeChangeListener(new TimeChangeListener() { - public void changed(int hour, int min, int sec, int msec) { - Date d = getWidget().getDate(); - if (d == null) { - // date currently null, use the value from - // calendarPanel - // (~ client time at the init of the widget) - d = (Date) getWidget().calendarPanel.getDate() - .clone(); - } - d.setHours(hour); - d.setMinutes(min); - d.setSeconds(sec); - DateTimeService.setMilliseconds(d, msec); - - // Always update time changes to the server - getWidget().calendarPanel.setDate(d); - getWidget().updateValueFromPanel(); - } - }); - } - - if (getWidget().currentResolution <= VDateField.RESOLUTION_MONTH) { - getWidget().calendarPanel - .setFocusChangeListener(new FocusChangeListener() { - public void focusChanged(Date date) { - Date date2 = new Date(); - if (getWidget().calendarPanel.getDate() != null) { - date2.setTime(getWidget().calendarPanel - .getDate().getTime()); - } - /* - * Update the value of calendarPanel - */ - date2.setYear(date.getYear()); - date2.setMonth(date.getMonth()); - getWidget().calendarPanel.setDate(date2); - /* - * Then update the value from panel to server - */ - getWidget().updateValueFromPanel(); - } - }); - } else { - getWidget().calendarPanel.setFocusChangeListener(null); - } - - // Update possible changes - getWidget().calendarPanel.renderCalendar(); - } - - @Override - protected Widget createWidget() { - return GWT.create(VDateFieldCalendar.class); - } - - @Override - public VDateFieldCalendar getWidget() { - return (VDateFieldCalendar) super.getWidget(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/LinkConnector.java b/src/com/vaadin/terminal/gwt/client/ui/LinkConnector.java deleted file mode 100644 index 715e902b24..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/LinkConnector.java +++ /dev/null @@ -1,96 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.ui.Link; - -@Component(Link.class) -public class LinkConnector extends AbstractComponentConnector implements - Paintable { - - @Override - public boolean delegateCaptionHandling() { - return false; - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - if (!isRealUpdate(uidl)) { - return; - } - - getWidget().client = client; - - getWidget().enabled = isEnabled(); - - if (uidl.hasAttribute("name")) { - getWidget().target = uidl.getStringAttribute("name"); - getWidget().anchor.setAttribute("target", getWidget().target); - } - if (uidl.hasAttribute("src")) { - getWidget().src = client.translateVaadinUri(uidl - .getStringAttribute("src")); - getWidget().anchor.setAttribute("href", getWidget().src); - } - - if (uidl.hasAttribute("border")) { - if ("none".equals(uidl.getStringAttribute("border"))) { - getWidget().borderStyle = VLink.BORDER_STYLE_NONE; - } else { - getWidget().borderStyle = VLink.BORDER_STYLE_MINIMAL; - } - } else { - getWidget().borderStyle = VLink.BORDER_STYLE_DEFAULT; - } - - getWidget().targetHeight = uidl.hasAttribute("targetHeight") ? uidl - .getIntAttribute("targetHeight") : -1; - getWidget().targetWidth = uidl.hasAttribute("targetWidth") ? uidl - .getIntAttribute("targetWidth") : -1; - - // Set link caption - getWidget().captionElement.setInnerText(getState().getCaption()); - - // handle error - if (null != getState().getErrorMessage()) { - if (getWidget().errorIndicatorElement == null) { - getWidget().errorIndicatorElement = DOM.createDiv(); - DOM.setElementProperty(getWidget().errorIndicatorElement, - "className", "v-errorindicator"); - } - DOM.insertChild(getWidget().getElement(), - getWidget().errorIndicatorElement, 0); - } else if (getWidget().errorIndicatorElement != null) { - DOM.setStyleAttribute(getWidget().errorIndicatorElement, "display", - "none"); - } - - if (getState().getIcon() != null) { - if (getWidget().icon == null) { - getWidget().icon = new Icon(client); - getWidget().anchor.insertBefore(getWidget().icon.getElement(), - getWidget().captionElement); - } - getWidget().icon.setUri(getState().getIcon().getURL()); - } - - } - - @Override - protected Widget createWidget() { - return GWT.create(VLink.class); - } - - @Override - public VLink getWidget() { - return (VLink) super.getWidget(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/ListSelectConnector.java b/src/com/vaadin/terminal/gwt/client/ui/ListSelectConnector.java deleted file mode 100644 index ce9fb91f9a..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/ListSelectConnector.java +++ /dev/null @@ -1,23 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.ui.ListSelect; - -@Component(ListSelect.class) -public class ListSelectConnector extends OptionGroupBaseConnector { - - @Override - protected Widget createWidget() { - return GWT.create(VListSelect.class); - } - - @Override - public VListSelect getWidget() { - return (VListSelect) super.getWidget(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/MenuBar.java b/src/com/vaadin/terminal/gwt/client/ui/MenuBar.java deleted file mode 100644 index f0857f48c1..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/MenuBar.java +++ /dev/null @@ -1,518 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -/* - * Copyright 2007 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. - */ - -// COPIED HERE DUE package privates in GWT -import java.util.ArrayList; -import java.util.List; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.user.client.Command; -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.PopupListener; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.Widget; - -/** - * A standard menu bar widget. A menu bar can contain any number of menu items, - * each of which can either fire a {@link com.google.gwt.user.client.Command} or - * open a cascaded menu bar. - * - *

- * - *

- * - *

CSS Style Rules

- *
    - *
  • .gwt-MenuBar { the menu bar itself }
  • - *
  • .gwt-MenuBar .gwt-MenuItem { menu items }
  • - *
  • - * .gwt-MenuBar .gwt-MenuItem-selected { selected menu items }
  • - *
- * - *

- *

Example

- * {@example com.google.gwt.examples.MenuBarExample} - *

- * - * @deprecated - */ -@Deprecated -public class MenuBar extends Widget implements PopupListener { - - private final Element body; - private final ArrayList items = new ArrayList(); - private MenuBar parentMenu; - private PopupPanel popup; - private MenuItem selectedItem; - private MenuBar shownChildMenu; - private final boolean vertical; - private boolean autoOpen; - - /** - * Creates an empty horizontal menu bar. - */ - public MenuBar() { - this(false); - } - - /** - * Creates an empty menu bar. - * - * @param vertical - * true to orient the menu bar vertically - */ - public MenuBar(boolean vertical) { - super(); - - final Element table = DOM.createTable(); - body = DOM.createTBody(); - DOM.appendChild(table, body); - - if (!vertical) { - final Element tr = DOM.createTR(); - DOM.appendChild(body, tr); - } - - this.vertical = vertical; - - final Element outer = DOM.createDiv(); - DOM.appendChild(outer, table); - setElement(outer); - - sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT); - setStyleName("gwt-MenuBar"); - } - - /** - * Adds a menu item to the bar. - * - * @param item - * the item to be added - */ - public void addItem(MenuItem item) { - Element tr; - if (vertical) { - tr = DOM.createTR(); - DOM.appendChild(body, tr); - } else { - tr = DOM.getChild(body, 0); - } - - DOM.appendChild(tr, item.getElement()); - - item.setParentMenu(this); - item.setSelectionStyle(false); - items.add(item); - } - - /** - * Adds a menu item to the bar, that will fire the given command when it is - * selected. - * - * @param text - * the item's text - * @param asHTML - * true to treat the specified text as html - * @param cmd - * the command to be fired - * @return the {@link MenuItem} object created - */ - public MenuItem addItem(String text, boolean asHTML, Command cmd) { - final MenuItem item = new MenuItem(text, asHTML, cmd); - addItem(item); - return item; - } - - /** - * Adds a menu item to the bar, that will open the specified menu when it is - * selected. - * - * @param text - * the item's text - * @param asHTML - * true to treat the specified text as html - * @param popup - * the menu to be cascaded from it - * @return the {@link MenuItem} object created - */ - public MenuItem addItem(String text, boolean asHTML, MenuBar popup) { - final MenuItem item = new MenuItem(text, asHTML, popup); - addItem(item); - return item; - } - - /** - * Adds a menu item to the bar, that will fire the given command when it is - * selected. - * - * @param text - * the item's text - * @param cmd - * the command to be fired - * @return the {@link MenuItem} object created - */ - public MenuItem addItem(String text, Command cmd) { - final MenuItem item = new MenuItem(text, cmd); - addItem(item); - return item; - } - - /** - * Adds a menu item to the bar, that will open the specified menu when it is - * selected. - * - * @param text - * the item's text - * @param popup - * the menu to be cascaded from it - * @return the {@link MenuItem} object created - */ - public MenuItem addItem(String text, MenuBar popup) { - final MenuItem item = new MenuItem(text, popup); - addItem(item); - return item; - } - - /** - * Removes all menu items from this menu bar. - */ - public void clearItems() { - final Element container = getItemContainerElement(); - while (DOM.getChildCount(container) > 0) { - DOM.removeChild(container, DOM.getChild(container, 0)); - } - items.clear(); - } - - /** - * Gets whether this menu bar's child menus will open when the mouse is - * moved over it. - * - * @return true if child menus will auto-open - */ - public boolean getAutoOpen() { - return autoOpen; - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - - final MenuItem item = findItem(DOM.eventGetTarget(event)); - switch (DOM.eventGetType(event)) { - case Event.ONCLICK: { - // Fire an item's command when the user clicks on it. - if (item != null) { - doItemAction(item, true); - } - break; - } - - case Event.ONMOUSEOVER: { - if (item != null) { - itemOver(item); - } - break; - } - - case Event.ONMOUSEOUT: { - if (item != null) { - itemOver(null); - } - break; - } - } - } - - public void onPopupClosed(PopupPanel sender, boolean autoClosed) { - // If the menu popup was auto-closed, close all of its parents as well. - if (autoClosed) { - closeAllParents(); - } - - // When the menu popup closes, remember that no item is - // currently showing a popup menu. - onHide(); - shownChildMenu = null; - popup = null; - } - - /** - * Removes the specified menu item from the bar. - * - * @param item - * the item to be removed - */ - public void removeItem(MenuItem item) { - final int idx = items.indexOf(item); - if (idx == -1) { - return; - } - - final Element container = getItemContainerElement(); - DOM.removeChild(container, DOM.getChild(container, idx)); - items.remove(idx); - } - - /** - * Sets whether this menu bar's child menus will open when the mouse is - * moved over it. - * - * @param autoOpen - * true to cause child menus to auto-open - */ - public void setAutoOpen(boolean autoOpen) { - this.autoOpen = autoOpen; - } - - /** - * Returns a list containing the MenuItem objects in the menu - * bar. If there are no items in the menu bar, then an empty - * List object will be returned. - * - * @return a list containing the MenuItem objects in the menu - * bar - */ - protected List getItems() { - return items; - } - - /** - * Returns the MenuItem that is currently selected - * (highlighted) by the user. If none of the items in the menu are currently - * selected, then null will be returned. - * - * @return the MenuItem that is currently selected, or - * null if no items are currently selected - */ - protected MenuItem getSelectedItem() { - return selectedItem; - } - - @Override - protected void onDetach() { - // When the menu is detached, make sure to close all of its children. - if (popup != null) { - popup.hide(); - } - - super.onDetach(); - } - - /* - * Closes all parent menu popups. - */ - void closeAllParents() { - MenuBar curMenu = this; - while (curMenu != null) { - curMenu.close(); - - if ((curMenu.parentMenu == null) && (curMenu.selectedItem != null)) { - curMenu.selectedItem.setSelectionStyle(false); - curMenu.selectedItem = null; - } - - curMenu = curMenu.parentMenu; - } - } - - /* - * Performs the action associated with the given menu item. If the item has - * a popup associated with it, the popup will be shown. If it has a command - * associated with it, and 'fireCommand' is true, then the command will be - * fired. Popups associated with other items will be hidden. - * - * @param item the item whose popup is to be shown. @param fireCommand - * true if the item's command should be fired, - * false otherwise. - */ - void doItemAction(final MenuItem item, boolean fireCommand) { - // If the given item is already showing its menu, we're done. - if ((shownChildMenu != null) && (item.getSubMenu() == shownChildMenu)) { - return; - } - - // If another item is showing its menu, then hide it. - if (shownChildMenu != null) { - shownChildMenu.onHide(); - popup.hide(); - } - - // If the item has no popup, optionally fire its command. - if (item.getSubMenu() == null) { - if (fireCommand) { - // Close this menu and all of its parents. - closeAllParents(); - - // Fire the item's command. - final Command cmd = item.getCommand(); - if (cmd != null) { - Scheduler.get().scheduleDeferred(cmd); - } - } - return; - } - - // Ensure that the item is selected. - selectItem(item); - - // Create a new popup for this item, and position it next to - // the item (below if this is a horizontal menu bar, to the - // right if it's a vertical bar). - popup = new VOverlay(true) { - { - setWidget(item.getSubMenu()); - item.getSubMenu().onShow(); - } - - @Override - public boolean onEventPreview(Event event) { - // Hook the popup panel's event preview. We use this to keep it - // from - // auto-hiding when the parent menu is clicked. - switch (DOM.eventGetType(event)) { - case Event.ONCLICK: - // If the event target is part of the parent menu, suppress - // the - // event altogether. - final Element target = DOM.eventGetTarget(event); - final Element parentMenuElement = item.getParentMenu() - .getElement(); - if (DOM.isOrHasChild(parentMenuElement, target)) { - return false; - } - break; - } - - return super.onEventPreview(event); - } - }; - popup.addPopupListener(this); - - if (vertical) { - popup.setPopupPosition( - item.getAbsoluteLeft() + item.getOffsetWidth(), - item.getAbsoluteTop()); - } else { - popup.setPopupPosition(item.getAbsoluteLeft(), - item.getAbsoluteTop() + item.getOffsetHeight()); - } - - shownChildMenu = item.getSubMenu(); - item.getSubMenu().parentMenu = this; - - // Show the popup, ensuring that the menubar's event preview remains on - // top - // of the popup's. - popup.show(); - } - - void itemOver(MenuItem item) { - if (item == null) { - // Don't clear selection if the currently selected item's menu is - // showing. - if ((selectedItem != null) - && (shownChildMenu == selectedItem.getSubMenu())) { - return; - } - } - - // Style the item selected when the mouse enters. - selectItem(item); - - // If child menus are being shown, or this menu is itself - // a child menu, automatically show an item's child menu - // when the mouse enters. - if (item != null) { - if ((shownChildMenu != null) || (parentMenu != null) || autoOpen) { - doItemAction(item, false); - } - } - } - - void selectItem(MenuItem item) { - if (item == selectedItem) { - return; - } - - if (selectedItem != null) { - selectedItem.setSelectionStyle(false); - } - - if (item != null) { - item.setSelectionStyle(true); - } - - selectedItem = item; - } - - /** - * Closes this menu (if it is a popup). - */ - private void close() { - if (parentMenu != null) { - parentMenu.popup.hide(); - } - } - - private MenuItem findItem(Element hItem) { - for (int i = 0; i < items.size(); ++i) { - final MenuItem item = items.get(i); - if (DOM.isOrHasChild(item.getElement(), hItem)) { - return item; - } - } - - return null; - } - - private Element getItemContainerElement() { - if (vertical) { - return body; - } else { - return DOM.getChild(body, 0); - } - } - - /* - * This method is called when a menu bar is hidden, so that it can hide any - * child popups that are currently being shown. - */ - private void onHide() { - if (shownChildMenu != null) { - shownChildMenu.onHide(); - popup.hide(); - } - } - - /* - * This method is called when a menu bar is shown. - */ - private void onShow() { - // Select the first item when a menu is shown. - if (items.size() > 0) { - selectItem(items.get(0)); - } - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/MenuBarConnector.java b/src/com/vaadin/terminal/gwt/client/ui/MenuBarConnector.java deleted file mode 100644 index 0980ea07dd..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/MenuBarConnector.java +++ /dev/null @@ -1,165 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Iterator; -import java.util.Stack; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; -import com.vaadin.terminal.gwt.client.ui.VMenuBar.CustomMenuItem; - -@Component(value = com.vaadin.ui.MenuBar.class, loadStyle = LoadStyle.LAZY) -public class MenuBarConnector extends AbstractComponentConnector implements - Paintable, SimpleManagedLayout { - /** - * This method must be implemented to update the client-side component from - * UIDL data received from server. - * - * This method is called when the page is loaded for the first time, and - * every time UI changes in the component are received from the server. - */ - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (!isRealUpdate(uidl)) { - return; - } - - getWidget().htmlContentAllowed = uidl - .hasAttribute(VMenuBar.HTML_CONTENT_ALLOWED); - - getWidget().openRootOnHover = uidl - .getBooleanAttribute(VMenuBar.OPEN_ROOT_MENU_ON_HOWER); - - getWidget().enabled = isEnabled(); - - // For future connections - getWidget().client = client; - getWidget().uidlId = uidl.getId(); - - // Empty the menu every time it receives new information - if (!getWidget().getItems().isEmpty()) { - getWidget().clearItems(); - } - - UIDL options = uidl.getChildUIDL(0); - - if (null != getState() && !getState().isUndefinedWidth()) { - UIDL moreItemUIDL = options.getChildUIDL(0); - StringBuffer itemHTML = new StringBuffer(); - - if (moreItemUIDL.hasAttribute("icon")) { - itemHTML.append("\"\""); - } - - String moreItemText = moreItemUIDL.getStringAttribute("text"); - if ("".equals(moreItemText)) { - moreItemText = "►"; - } - itemHTML.append(moreItemText); - - getWidget().moreItem = GWT.create(CustomMenuItem.class); - getWidget().moreItem.setHTML(itemHTML.toString()); - getWidget().moreItem.setCommand(VMenuBar.emptyCommand); - - getWidget().collapsedRootItems = new VMenuBar(true, getWidget()); - getWidget().moreItem.setSubMenu(getWidget().collapsedRootItems); - getWidget().moreItem.addStyleName(VMenuBar.CLASSNAME - + "-more-menuitem"); - } - - UIDL uidlItems = uidl.getChildUIDL(1); - Iterator itr = uidlItems.getChildIterator(); - Stack> iteratorStack = new Stack>(); - Stack menuStack = new Stack(); - VMenuBar currentMenu = getWidget(); - - while (itr.hasNext()) { - UIDL item = (UIDL) itr.next(); - CustomMenuItem currentItem = null; - - final int itemId = item.getIntAttribute("id"); - - boolean itemHasCommand = item.hasAttribute("command"); - boolean itemIsCheckable = item - .hasAttribute(VMenuBar.ATTRIBUTE_CHECKED); - - String itemHTML = getWidget().buildItemHTML(item); - - Command cmd = null; - if (!item.hasAttribute("separator")) { - if (itemHasCommand || itemIsCheckable) { - // Construct a command that fires onMenuClick(int) with the - // item's id-number - cmd = new Command() { - public void execute() { - getWidget().hostReference.onMenuClick(itemId); - } - }; - } - } - - currentItem = currentMenu.addItem(itemHTML.toString(), cmd); - currentItem.updateFromUIDL(item, client); - - if (item.getChildCount() > 0) { - menuStack.push(currentMenu); - iteratorStack.push(itr); - itr = item.getChildIterator(); - currentMenu = new VMenuBar(true, currentMenu); - // this is the top-level style that also propagates to items - - // any item specific styles are set above in - // currentItem.updateFromUIDL(item, client) - if (getState().hasStyles()) { - for (String style : getState().getStyles()) { - currentMenu.addStyleDependentName(style); - } - } - currentItem.setSubMenu(currentMenu); - } - - while (!itr.hasNext() && !iteratorStack.empty()) { - boolean hasCheckableItem = false; - for (CustomMenuItem menuItem : currentMenu.getItems()) { - hasCheckableItem = hasCheckableItem - || menuItem.isCheckable(); - } - if (hasCheckableItem) { - currentMenu.addStyleDependentName("check-column"); - } else { - currentMenu.removeStyleDependentName("check-column"); - } - - itr = iteratorStack.pop(); - currentMenu = menuStack.pop(); - } - }// while - - getLayoutManager().setWidthNeedsUpdate(this); - - }// updateFromUIDL - - @Override - protected Widget createWidget() { - return GWT.create(VMenuBar.class); - } - - @Override - public VMenuBar getWidget() { - return (VMenuBar) super.getWidget(); - } - - public void layout() { - getWidget().iLayout(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/MenuItem.java b/src/com/vaadin/terminal/gwt/client/ui/MenuItem.java deleted file mode 100644 index ec02db1c70..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/MenuItem.java +++ /dev/null @@ -1,189 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -/* - * Copyright 2007 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. - */ - -// COPIED HERE DUE package privates in GWT -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.HasHTML; -import com.google.gwt.user.client.ui.UIObject; - -/** - * A widget that can be placed in a - * {@link com.google.gwt.user.client.ui.MenuBar}. Menu items can either fire a - * {@link com.google.gwt.user.client.Command} when they are clicked, or open a - * cascading sub-menu. - * - * @deprecated - */ -@Deprecated -public class MenuItem extends UIObject implements HasHTML { - - private static final String DEPENDENT_STYLENAME_SELECTED_ITEM = "selected"; - - private Command command; - private MenuBar parentMenu, subMenu; - - /** - * Constructs a new menu item that fires a command when it is selected. - * - * @param text - * the item's text - * @param cmd - * the command to be fired when it is selected - */ - public MenuItem(String text, Command cmd) { - this(text, false); - setCommand(cmd); - } - - /** - * Constructs a new menu item that fires a command when it is selected. - * - * @param text - * the item's text - * @param asHTML - * true to treat the specified text as html - * @param cmd - * the command to be fired when it is selected - */ - public MenuItem(String text, boolean asHTML, Command cmd) { - this(text, asHTML); - setCommand(cmd); - } - - /** - * Constructs a new menu item that cascades to a sub-menu when it is - * selected. - * - * @param text - * the item's text - * @param subMenu - * the sub-menu to be displayed when it is selected - */ - public MenuItem(String text, MenuBar subMenu) { - this(text, false); - setSubMenu(subMenu); - } - - /** - * Constructs a new menu item that cascades to a sub-menu when it is - * selected. - * - * @param text - * the item's text - * @param asHTML - * true to treat the specified text as html - * @param subMenu - * the sub-menu to be displayed when it is selected - */ - public MenuItem(String text, boolean asHTML, MenuBar subMenu) { - this(text, asHTML); - setSubMenu(subMenu); - } - - MenuItem(String text, boolean asHTML) { - setElement(DOM.createTD()); - setSelectionStyle(false); - - if (asHTML) { - setHTML(text); - } else { - setText(text); - } - setStyleName("gwt-MenuItem"); - } - - /** - * Gets the command associated with this item. - * - * @return this item's command, or null if none exists - */ - public Command getCommand() { - return command; - } - - public String getHTML() { - return DOM.getInnerHTML(getElement()); - } - - /** - * Gets the menu that contains this item. - * - * @return the parent menu, or null if none exists. - */ - public MenuBar getParentMenu() { - return parentMenu; - } - - /** - * Gets the sub-menu associated with this item. - * - * @return this item's sub-menu, or null if none exists - */ - public MenuBar getSubMenu() { - return subMenu; - } - - public String getText() { - return DOM.getInnerText(getElement()); - } - - /** - * Sets the command associated with this item. - * - * @param cmd - * the command to be associated with this item - */ - public void setCommand(Command cmd) { - command = cmd; - } - - public void setHTML(String html) { - DOM.setInnerHTML(getElement(), html); - } - - /** - * Sets the sub-menu associated with this item. - * - * @param subMenu - * this item's new sub-menu - */ - public void setSubMenu(MenuBar subMenu) { - this.subMenu = subMenu; - } - - public void setText(String text) { - DOM.setInnerText(getElement(), text); - } - - void setParentMenu(MenuBar parentMenu) { - this.parentMenu = parentMenu; - } - - void setSelectionStyle(boolean selected) { - if (selected) { - addStyleDependentName(DEPENDENT_STYLENAME_SELECTED_ITEM); - } else { - removeStyleDependentName(DEPENDENT_STYLENAME_SELECTED_ITEM); - } - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/NativeButtonConnector.java b/src/com/vaadin/terminal/gwt/client/ui/NativeButtonConnector.java deleted file mode 100644 index 6dd7cd0eac..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/NativeButtonConnector.java +++ /dev/null @@ -1,121 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.EventHelper; -import com.vaadin.terminal.gwt.client.communication.FieldRpc.FocusAndBlurServerRpc; -import com.vaadin.terminal.gwt.client.communication.RpcProxy; -import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; -import com.vaadin.terminal.gwt.client.ui.ButtonConnector.ButtonServerRpc; -import com.vaadin.ui.NativeButton; - -@Component(NativeButton.class) -public class NativeButtonConnector extends AbstractComponentConnector implements - BlurHandler, FocusHandler { - - private HandlerRegistration focusHandlerRegistration; - private HandlerRegistration blurHandlerRegistration; - - private FocusAndBlurServerRpc focusBlurRpc = RpcProxy.create( - FocusAndBlurServerRpc.class, this); - - @Override - public void init() { - super.init(); - - getWidget().buttonRpcProxy = RpcProxy.create(ButtonServerRpc.class, - this); - getWidget().client = getConnection(); - getWidget().paintableId = getConnectorId(); - } - - @Override - public boolean delegateCaptionHandling() { - return false; - } - - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - - getWidget().disableOnClick = getState().isDisableOnClick(); - focusHandlerRegistration = EventHelper.updateFocusHandler(this, - focusHandlerRegistration); - blurHandlerRegistration = EventHelper.updateBlurHandler(this, - blurHandlerRegistration); - - // Set text - getWidget().setText(getState().getCaption()); - - // handle error - if (null != getState().getErrorMessage()) { - if (getWidget().errorIndicatorElement == null) { - getWidget().errorIndicatorElement = DOM.createSpan(); - getWidget().errorIndicatorElement - .setClassName("v-errorindicator"); - } - getWidget().getElement().insertBefore( - getWidget().errorIndicatorElement, - getWidget().captionElement); - - } else if (getWidget().errorIndicatorElement != null) { - getWidget().getElement().removeChild( - getWidget().errorIndicatorElement); - getWidget().errorIndicatorElement = null; - } - - if (getState().getIcon() != null) { - if (getWidget().icon == null) { - getWidget().icon = new Icon(getConnection()); - getWidget().getElement().insertBefore( - getWidget().icon.getElement(), - getWidget().captionElement); - } - getWidget().icon.setUri(getState().getIcon().getURL()); - } else { - if (getWidget().icon != null) { - getWidget().getElement().removeChild( - getWidget().icon.getElement()); - getWidget().icon = null; - } - } - - } - - @Override - protected Widget createWidget() { - return GWT.create(VNativeButton.class); - } - - @Override - public VNativeButton getWidget() { - return (VNativeButton) super.getWidget(); - } - - @Override - public ButtonState getState() { - return (ButtonState) super.getState(); - } - - public void onFocus(FocusEvent event) { - // EventHelper.updateFocusHandler ensures that this is called only when - // there is a listener on server side - focusBlurRpc.focus(); - } - - public void onBlur(BlurEvent event) { - // EventHelper.updateFocusHandler ensures that this is called only when - // there is a listener on server side - focusBlurRpc.blur(); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/NativeSelectConnector.java b/src/com/vaadin/terminal/gwt/client/ui/NativeSelectConnector.java deleted file mode 100644 index 0d85f3ed95..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/NativeSelectConnector.java +++ /dev/null @@ -1,23 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.ui.NativeSelect; - -@Component(NativeSelect.class) -public class NativeSelectConnector extends OptionGroupBaseConnector { - - @Override - protected Widget createWidget() { - return GWT.create(VNativeSelect.class); - } - - @Override - public VNativeSelect getWidget() { - return (VNativeSelect) super.getWidget(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/OptionGroupBaseConnector.java b/src/com/vaadin/terminal/gwt/client/ui/OptionGroupBaseConnector.java deleted file mode 100644 index df2e6942c1..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/OptionGroupBaseConnector.java +++ /dev/null @@ -1,90 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; - -public abstract class OptionGroupBaseConnector extends AbstractFieldConnector - implements Paintable { - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - // Save details - getWidget().client = client; - getWidget().paintableId = uidl.getId(); - - if (!isRealUpdate(uidl)) { - return; - } - - getWidget().selectedKeys = uidl.getStringArrayVariableAsSet("selected"); - - getWidget().readonly = isReadOnly(); - getWidget().disabled = !isEnabled(); - getWidget().multiselect = "multi".equals(uidl - .getStringAttribute("selectmode")); - getWidget().immediate = getState().isImmediate(); - getWidget().nullSelectionAllowed = uidl - .getBooleanAttribute("nullselect"); - getWidget().nullSelectionItemAvailable = uidl - .getBooleanAttribute("nullselectitem"); - - if (uidl.hasAttribute("cols")) { - getWidget().cols = uidl.getIntAttribute("cols"); - } - if (uidl.hasAttribute("rows")) { - getWidget().rows = uidl.getIntAttribute("rows"); - } - - final UIDL ops = uidl.getChildUIDL(0); - - if (getWidget().getColumns() > 0) { - getWidget().container.setWidth(getWidget().getColumns() + "em"); - if (getWidget().container != getWidget().optionsContainer) { - getWidget().optionsContainer.setWidth("100%"); - } - } - - getWidget().buildOptions(ops); - - if (uidl.getBooleanAttribute("allownewitem")) { - if (getWidget().newItemField == null) { - getWidget().newItemButton = new VNativeButton(); - getWidget().newItemButton.setText("+"); - getWidget().newItemButton.addClickHandler(getWidget()); - getWidget().newItemField = new VTextField(); - getWidget().newItemField.addKeyPressHandler(getWidget()); - } - getWidget().newItemField.setEnabled(!getWidget().disabled - && !getWidget().readonly); - getWidget().newItemButton.setEnabled(!getWidget().disabled - && !getWidget().readonly); - - if (getWidget().newItemField == null - || getWidget().newItemField.getParent() != getWidget().container) { - getWidget().container.add(getWidget().newItemField); - getWidget().container.add(getWidget().newItemButton); - final int w = getWidget().container.getOffsetWidth() - - getWidget().newItemButton.getOffsetWidth(); - getWidget().newItemField.setWidth(Math.max(w, 0) + "px"); - } - } else if (getWidget().newItemField != null) { - getWidget().container.remove(getWidget().newItemField); - getWidget().container.remove(getWidget().newItemButton); - } - - getWidget().setTabIndex( - uidl.hasAttribute("tabindex") ? uidl - .getIntAttribute("tabindex") : 0); - - } - - @Override - public VOptionGroupBase getWidget() { - return (VOptionGroupBase) super.getWidget(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/OptionGroupConnector.java b/src/com/vaadin/terminal/gwt/client/ui/OptionGroupConnector.java deleted file mode 100644 index 8c9811756f..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/OptionGroupConnector.java +++ /dev/null @@ -1,72 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.ui.CheckBox; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.ui.OptionGroup; - -@Component(OptionGroup.class) -public class OptionGroupConnector extends OptionGroupBaseConnector { - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - getWidget().htmlContentAllowed = uidl - .hasAttribute(VOptionGroup.HTML_CONTENT_ALLOWED); - - super.updateFromUIDL(uidl, client); - - getWidget().sendFocusEvents = client.hasEventListeners(this, - EventId.FOCUS); - getWidget().sendBlurEvents = client.hasEventListeners(this, - EventId.BLUR); - - if (getWidget().focusHandlers != null) { - for (HandlerRegistration reg : getWidget().focusHandlers) { - reg.removeHandler(); - } - getWidget().focusHandlers.clear(); - getWidget().focusHandlers = null; - - for (HandlerRegistration reg : getWidget().blurHandlers) { - reg.removeHandler(); - } - getWidget().blurHandlers.clear(); - getWidget().blurHandlers = null; - } - - if (getWidget().sendFocusEvents || getWidget().sendBlurEvents) { - getWidget().focusHandlers = new ArrayList(); - getWidget().blurHandlers = new ArrayList(); - - // add focus and blur handlers to checkboxes / radio buttons - for (Widget wid : getWidget().panel) { - if (wid instanceof CheckBox) { - getWidget().focusHandlers.add(((CheckBox) wid) - .addFocusHandler(getWidget())); - getWidget().blurHandlers.add(((CheckBox) wid) - .addBlurHandler(getWidget())); - } - } - } - } - - @Override - protected Widget createWidget() { - return GWT.create(VOptionGroup.class); - } - - @Override - public VOptionGroup getWidget() { - return (VOptionGroup) super.getWidget(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/PanelConnector.java b/src/com/vaadin/terminal/gwt/client/ui/PanelConnector.java deleted file mode 100644 index 529353f990..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/PanelConnector.java +++ /dev/null @@ -1,263 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.ComponentState; -import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; -import com.vaadin.terminal.gwt.client.LayoutManager; -import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.communication.RpcProxy; -import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.ui.Panel; - -@Component(Panel.class) -public class PanelConnector extends AbstractComponentContainerConnector - implements Paintable, SimpleManagedLayout, PostLayoutListener { - - public interface PanelServerRPC extends ClickRPC, ServerRpc { - - } - - public static class PanelState extends ComponentState { - private int tabIndex; - private int scrollLeft, scrollTop; - - public int getTabIndex() { - return tabIndex; - } - - public void setTabIndex(int tabIndex) { - this.tabIndex = tabIndex; - } - - public int getScrollLeft() { - return scrollLeft; - } - - public void setScrollLeft(int scrollLeft) { - this.scrollLeft = scrollLeft; - } - - public int getScrollTop() { - return scrollTop; - } - - public void setScrollTop(int scrollTop) { - this.scrollTop = scrollTop; - } - - } - - private Integer uidlScrollTop; - - private ClickEventHandler clickEventHandler = new ClickEventHandler(this) { - - @Override - protected void fireClick(NativeEvent event, - MouseEventDetails mouseDetails) { - rpc.click(mouseDetails); - } - }; - - private Integer uidlScrollLeft; - - private PanelServerRPC rpc; - - @Override - public void init() { - rpc = RpcProxy.create(PanelServerRPC.class, this); - VPanel panel = getWidget(); - LayoutManager layoutManager = getLayoutManager(); - - layoutManager.registerDependency(this, panel.captionNode); - layoutManager.registerDependency(this, panel.bottomDecoration); - layoutManager.registerDependency(this, panel.contentNode); - } - - @Override - public boolean delegateCaptionHandling() { - return false; - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (isRealUpdate(uidl)) { - - // Handle caption displaying and style names, prior generics. - // Affects size calculations - - // Restore default stylenames - getWidget().contentNode.setClassName(VPanel.CLASSNAME + "-content"); - getWidget().bottomDecoration.setClassName(VPanel.CLASSNAME - + "-deco"); - getWidget().captionNode.setClassName(VPanel.CLASSNAME + "-caption"); - boolean hasCaption = false; - if (getState().getCaption() != null - && !"".equals(getState().getCaption())) { - getWidget().setCaption(getState().getCaption()); - hasCaption = true; - } else { - getWidget().setCaption(""); - getWidget().captionNode.setClassName(VPanel.CLASSNAME - + "-nocaption"); - } - - // Add proper stylenames for all elements. This way we can prevent - // unwanted CSS selector inheritance. - final String captionBaseClass = VPanel.CLASSNAME - + (hasCaption ? "-caption" : "-nocaption"); - final String contentBaseClass = VPanel.CLASSNAME + "-content"; - final String decoBaseClass = VPanel.CLASSNAME + "-deco"; - String captionClass = captionBaseClass; - String contentClass = contentBaseClass; - String decoClass = decoBaseClass; - if (getState().hasStyles()) { - for (String style : getState().getStyles()) { - captionClass += " " + captionBaseClass + "-" + style; - contentClass += " " + contentBaseClass + "-" + style; - decoClass += " " + decoBaseClass + "-" + style; - } - } - getWidget().captionNode.setClassName(captionClass); - getWidget().contentNode.setClassName(contentClass); - getWidget().bottomDecoration.setClassName(decoClass); - } - - if (!isRealUpdate(uidl)) { - return; - } - - clickEventHandler.handleEventHandlerRegistration(); - - getWidget().client = client; - getWidget().id = uidl.getId(); - - if (getState().getIcon() != null) { - getWidget().setIconUri(getState().getIcon().getURL(), client); - } else { - getWidget().setIconUri(null, client); - } - - getWidget().setErrorIndicatorVisible( - null != getState().getErrorMessage()); - - // We may have actions attached to this panel - if (uidl.getChildCount() > 0) { - final int cnt = uidl.getChildCount(); - for (int i = 0; i < cnt; i++) { - UIDL childUidl = uidl.getChildUIDL(i); - if (childUidl.getTag().equals("actions")) { - if (getWidget().shortcutHandler == null) { - getWidget().shortcutHandler = new ShortcutActionHandler( - getConnectorId(), client); - } - getWidget().shortcutHandler.updateActionMap(childUidl); - } - } - } - - if (getState().getScrollTop() != getWidget().scrollTop) { - // Sizes are not yet up to date, so changing the scroll position - // is deferred to after the layout phase - uidlScrollTop = getState().getScrollTop(); - } - - if (getState().getScrollLeft() != getWidget().scrollLeft) { - // Sizes are not yet up to date, so changing the scroll position - // is deferred to after the layout phase - uidlScrollLeft = getState().getScrollLeft(); - } - - // And apply tab index - getWidget().contentNode.setTabIndex(getState().getTabIndex()); - } - - public void updateCaption(ComponentConnector component) { - // NOP: layouts caption, errors etc not rendered in Panel - } - - @Override - public VPanel getWidget() { - return (VPanel) super.getWidget(); - } - - @Override - protected Widget createWidget() { - return GWT.create(VPanel.class); - } - - public void layout() { - updateSizes(); - } - - void updateSizes() { - VPanel panel = getWidget(); - - LayoutManager layoutManager = getLayoutManager(); - int top = layoutManager.getInnerHeight(panel.captionNode); - int bottom = layoutManager.getInnerHeight(panel.bottomDecoration); - - Style style = panel.getElement().getStyle(); - panel.captionNode.getStyle().setMarginTop(-top, Unit.PX); - panel.bottomDecoration.getStyle().setMarginBottom(-bottom, Unit.PX); - style.setPaddingTop(top, Unit.PX); - style.setPaddingBottom(bottom, Unit.PX); - - // Update scroll positions - panel.contentNode.setScrollTop(panel.scrollTop); - panel.contentNode.setScrollLeft(panel.scrollLeft); - // Read actual value back to ensure update logic is correct - panel.scrollTop = panel.contentNode.getScrollTop(); - panel.scrollLeft = panel.contentNode.getScrollLeft(); - - Util.runWebkitOverflowAutoFix(panel.contentNode); - } - - public void postLayout() { - VPanel panel = getWidget(); - if (uidlScrollTop != null) { - panel.contentNode.setScrollTop(uidlScrollTop.intValue()); - // Read actual value back to ensure update logic is correct - // TODO Does this trigger reflows? - panel.scrollTop = panel.contentNode.getScrollTop(); - uidlScrollTop = null; - } - - if (uidlScrollLeft != null) { - panel.contentNode.setScrollLeft(uidlScrollLeft.intValue()); - // Read actual value back to ensure update logic is correct - // TODO Does this trigger reflows? - panel.scrollLeft = panel.contentNode.getScrollLeft(); - uidlScrollLeft = null; - } - } - - @Override - public PanelState getState() { - return (PanelState) super.getState(); - } - - @Override - public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { - super.onConnectorHierarchyChange(event); - // We always have 1 child, unless the child is hidden - Widget newChildWidget = null; - if (getChildren().size() == 1) { - ComponentConnector newChild = getChildren().get(0); - newChildWidget = newChild.getWidget(); - } - - getWidget().setWidget(newChildWidget); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/PasswordFieldConnector.java b/src/com/vaadin/terminal/gwt/client/ui/PasswordFieldConnector.java deleted file mode 100644 index 996e82f405..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/PasswordFieldConnector.java +++ /dev/null @@ -1,30 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.ui.PasswordField; - -@Component(PasswordField.class) -public class PasswordFieldConnector extends TextFieldConnector { - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - super.updateFromUIDL(uidl, client); - } - - @Override - protected Widget createWidget() { - return GWT.create(VPasswordField.class); - } - - @Override - public VPasswordField getWidget() { - return (VPasswordField) super.getWidget(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/PopupDateFieldConnector.java b/src/com/vaadin/terminal/gwt/client/ui/PopupDateFieldConnector.java deleted file mode 100644 index 9a65513afd..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/PopupDateFieldConnector.java +++ /dev/null @@ -1,124 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Date; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.DateTimeService; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.FocusChangeListener; -import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.TimeChangeListener; -import com.vaadin.ui.DateField; - -@Component(DateField.class) -public class PopupDateFieldConnector extends TextualDateConnector { - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.terminal.gwt.client.ui.VTextualDate#updateFromUIDL(com.vaadin - * .terminal.gwt.client.UIDL, - * com.vaadin.terminal.gwt.client.ApplicationConnection) - */ - @Override - @SuppressWarnings("deprecation") - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - boolean lastReadOnlyState = getWidget().readonly; - boolean lastEnabledState = getWidget().isEnabled(); - - getWidget().parsable = uidl.getBooleanAttribute("parsable"); - - super.updateFromUIDL(uidl, client); - - String popupStyleNames = getStyleNames( - VPopupCalendar.POPUP_PRIMARY_STYLE_NAME, this); - popupStyleNames += " " - + VDateField.CLASSNAME - + "-" - + VPopupCalendar - .resolutionToString(getWidget().currentResolution); - getWidget().popup.setStyleName(popupStyleNames); - - getWidget().calendar.setDateTimeService(getWidget() - .getDateTimeService()); - getWidget().calendar.setShowISOWeekNumbers(getWidget() - .isShowISOWeekNumbers()); - if (getWidget().calendar.getResolution() != getWidget().currentResolution) { - getWidget().calendar.setResolution(getWidget().currentResolution); - if (getWidget().calendar.getDate() != null) { - getWidget().calendar.setDate((Date) getWidget() - .getCurrentDate().clone()); - // force re-render when changing resolution only - getWidget().calendar.renderCalendar(); - } - } - getWidget().calendarToggle.setEnabled(getWidget().enabled); - - if (getWidget().currentResolution <= VPopupCalendar.RESOLUTION_MONTH) { - getWidget().calendar - .setFocusChangeListener(new FocusChangeListener() { - public void focusChanged(Date date) { - getWidget().updateValue(date); - getWidget().buildDate(); - Date date2 = getWidget().calendar.getDate(); - date2.setYear(date.getYear()); - date2.setMonth(date.getMonth()); - } - }); - } else { - getWidget().calendar.setFocusChangeListener(null); - } - - if (getWidget().currentResolution > VPopupCalendar.RESOLUTION_DAY) { - getWidget().calendar - .setTimeChangeListener(new TimeChangeListener() { - public void changed(int hour, int min, int sec, int msec) { - Date d = getWidget().getDate(); - if (d == null) { - // date currently null, use the value from - // calendarPanel - // (~ client time at the init of the widget) - d = (Date) getWidget().calendar.getDate() - .clone(); - } - d.setHours(hour); - d.setMinutes(min); - d.setSeconds(sec); - DateTimeService.setMilliseconds(d, msec); - - // Always update time changes to the server - getWidget().updateValue(d); - - // Update text field - getWidget().buildDate(); - } - }); - } - - if (getWidget().readonly) { - getWidget().calendarToggle.addStyleName(VPopupCalendar.CLASSNAME - + "-button-readonly"); - } else { - getWidget().calendarToggle.removeStyleName(VPopupCalendar.CLASSNAME - + "-button-readonly"); - } - - getWidget().calendarToggle.setEnabled(true); - } - - @Override - protected Widget createWidget() { - return GWT.create(VPopupCalendar.class); - } - - @Override - public VPopupCalendar getWidget() { - return (VPopupCalendar) super.getWidget(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/PopupViewConnector.java b/src/com/vaadin/terminal/gwt/client/ui/PopupViewConnector.java deleted file mode 100644 index a409b3e5a5..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/PopupViewConnector.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - @VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.VCaption; -import com.vaadin.terminal.gwt.client.VCaptionWrapper; -import com.vaadin.ui.PopupView; - -@Component(PopupView.class) -public class PopupViewConnector extends AbstractComponentContainerConnector - implements Paintable, PostLayoutListener { - - private boolean centerAfterLayout = false; - - @Override - public boolean delegateCaptionHandling() { - return false; - } - - /** - * - * - * @see com.vaadin.terminal.gwt.client.ComponentConnector#updateFromUIDL(com.vaadin.terminal.gwt.client.UIDL, - * com.vaadin.terminal.gwt.client.ApplicationConnection) - */ - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (!isRealUpdate(uidl)) { - return; - } - // These are for future server connections - getWidget().client = client; - getWidget().uidlId = uidl.getId(); - - getWidget().hostPopupVisible = uidl - .getBooleanVariable("popupVisibility"); - - getWidget().setHTML(uidl.getStringAttribute("html")); - - if (uidl.hasAttribute("hideOnMouseOut")) { - getWidget().popup.setHideOnMouseOut(uidl - .getBooleanAttribute("hideOnMouseOut")); - } - - // Render the popup if visible and show it. - if (getWidget().hostPopupVisible) { - UIDL popupUIDL = uidl.getChildUIDL(0); - - // showPopupOnTop(popup, hostReference); - getWidget().preparePopup(getWidget().popup); - getWidget().popup.updateFromUIDL(popupUIDL, client); - if (getState().hasStyles()) { - final StringBuffer styleBuf = new StringBuffer(); - final String primaryName = getWidget().popup - .getStylePrimaryName(); - styleBuf.append(primaryName); - for (String style : getState().getStyles()) { - styleBuf.append(" "); - styleBuf.append(primaryName); - styleBuf.append("-"); - styleBuf.append(style); - } - getWidget().popup.setStyleName(styleBuf.toString()); - } else { - getWidget().popup.setStyleName(getWidget().popup - .getStylePrimaryName()); - } - getWidget().showPopup(getWidget().popup); - centerAfterLayout = true; - - // The popup shouldn't be visible, try to hide it. - } else { - getWidget().popup.hide(); - } - }// updateFromUIDL - - public void updateCaption(ComponentConnector component) { - if (VCaption.isNeeded(component.getState())) { - if (getWidget().popup.captionWrapper != null) { - getWidget().popup.captionWrapper.updateCaption(); - } else { - getWidget().popup.captionWrapper = new VCaptionWrapper( - component, getConnection()); - getWidget().popup.setWidget(getWidget().popup.captionWrapper); - getWidget().popup.captionWrapper.updateCaption(); - } - } else { - if (getWidget().popup.captionWrapper != null) { - getWidget().popup - .setWidget(getWidget().popup.popupComponentWidget); - } - } - } - - @Override - public VPopupView getWidget() { - return (VPopupView) super.getWidget(); - } - - @Override - protected Widget createWidget() { - return GWT.create(VPopupView.class); - } - - public void postLayout() { - if (centerAfterLayout) { - centerAfterLayout = false; - getWidget().center(); - } - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/ProgressIndicatorConnector.java b/src/com/vaadin/terminal/gwt/client/ui/ProgressIndicatorConnector.java deleted file mode 100644 index 8b3bab8e6b..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/ProgressIndicatorConnector.java +++ /dev/null @@ -1,64 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.ui.ProgressIndicator; - -@Component(ProgressIndicator.class) -public class ProgressIndicatorConnector extends AbstractFieldConnector - implements Paintable { - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - if (!isRealUpdate(uidl)) { - return; - } - - // Save details - getWidget().client = client; - - getWidget().indeterminate = uidl.getBooleanAttribute("indeterminate"); - - if (getWidget().indeterminate) { - String basename = VProgressIndicator.CLASSNAME + "-indeterminate"; - getWidget().addStyleName(basename); - if (!isEnabled()) { - getWidget().addStyleName(basename + "-disabled"); - } else { - getWidget().removeStyleName(basename + "-disabled"); - } - } else { - try { - final float f = Float.parseFloat(uidl - .getStringAttribute("state")); - final int size = Math.round(100 * f); - DOM.setStyleAttribute(getWidget().indicator, "width", size - + "%"); - } catch (final Exception e) { - } - } - - if (isEnabled()) { - getWidget().interval = uidl.getIntAttribute("pollinginterval"); - getWidget().poller.scheduleRepeating(getWidget().interval); - } - } - - @Override - protected Widget createWidget() { - return GWT.create(VProgressIndicator.class); - } - - @Override - public VProgressIndicator getWidget() { - return (VProgressIndicator) super.getWidget(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/SliderConnector.java b/src/com/vaadin/terminal/gwt/client/ui/SliderConnector.java deleted file mode 100644 index 616e33ff7a..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/SliderConnector.java +++ /dev/null @@ -1,75 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.ui.Slider; - -@Component(Slider.class) -public class SliderConnector extends AbstractFieldConnector implements - Paintable { - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - getWidget().client = client; - getWidget().id = uidl.getId(); - - if (!isRealUpdate(uidl)) { - return; - } - - getWidget().immediate = getState().isImmediate(); - getWidget().disabled = !isEnabled(); - getWidget().readonly = isReadOnly(); - - getWidget().vertical = uidl.hasAttribute("vertical"); - - // TODO should style names be used? - - if (getWidget().vertical) { - getWidget().addStyleName(VSlider.CLASSNAME + "-vertical"); - } else { - getWidget().removeStyleName(VSlider.CLASSNAME + "-vertical"); - } - - getWidget().min = uidl.getDoubleAttribute("min"); - getWidget().max = uidl.getDoubleAttribute("max"); - getWidget().resolution = uidl.getIntAttribute("resolution"); - getWidget().value = new Double(uidl.getDoubleVariable("value")); - - getWidget().setFeedbackValue(getWidget().value); - - getWidget().buildBase(); - - if (!getWidget().vertical) { - // Draw handle with a delay to allow base to gain maximum width - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - getWidget().buildHandle(); - getWidget().setValue(getWidget().value, false); - } - }); - } else { - getWidget().buildHandle(); - getWidget().setValue(getWidget().value, false); - } - } - - @Override - public VSlider getWidget() { - return (VSlider) super.getWidget(); - } - - @Override - protected Widget createWidget() { - return GWT.create(VSlider.class); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/Table.java b/src/com/vaadin/terminal/gwt/client/ui/Table.java deleted file mode 100644 index ed3521335a..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/Table.java +++ /dev/null @@ -1,15 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.user.client.ui.HasWidgets; -import com.vaadin.terminal.gwt.client.ComponentConnector; - -public interface Table extends ComponentConnector, HasWidgets { - final int SELECT_MODE_NONE = 0; - final int SELECT_MODE_SINGLE = 1; - final int SELECT_MODE_MULTI = 2; - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/TableConnector.java b/src/com/vaadin/terminal/gwt/client/ui/TableConnector.java deleted file mode 100644 index 7acdc84b5f..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/TableConnector.java +++ /dev/null @@ -1,316 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Iterator; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.AbstractFieldState; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.DirectionalManagedLayout; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.ui.VScrollTable.ContextMenuDetails; -import com.vaadin.terminal.gwt.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow; - -@Component(com.vaadin.ui.Table.class) -public class TableConnector extends AbstractComponentContainerConnector - implements Paintable, DirectionalManagedLayout, PostLayoutListener { - - @Override - protected void init() { - super.init(); - getWidget().init(getConnection()); - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.terminal.gwt.client.Paintable#updateFromUIDL(com.vaadin.terminal - * .gwt.client.UIDL, com.vaadin.terminal.gwt.client.ApplicationConnection) - */ - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - getWidget().rendering = true; - - // If a row has an open context menu, it will be closed as the row is - // detached. Retain a reference here so we can restore the menu if - // required. - ContextMenuDetails contextMenuBeforeUpdate = getWidget().contextMenu; - - if (uidl.hasAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_FIRST)) { - getWidget().serverCacheFirst = uidl - .getIntAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_FIRST); - getWidget().serverCacheLast = uidl - .getIntAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_LAST); - } else { - getWidget().serverCacheFirst = -1; - getWidget().serverCacheLast = -1; - } - /* - * We need to do this before updateComponent since updateComponent calls - * this.setHeight() which will calculate a new body height depending on - * the space available. - */ - if (uidl.hasAttribute("colfooters")) { - getWidget().showColFooters = uidl.getBooleanAttribute("colfooters"); - } - - getWidget().tFoot.setVisible(getWidget().showColFooters); - - if (!isRealUpdate(uidl)) { - getWidget().rendering = false; - return; - } - - getWidget().enabled = isEnabled(); - - if (BrowserInfo.get().isIE8() && !getWidget().enabled) { - /* - * The disabled shim will not cover the table body if it is relative - * in IE8. See #7324 - */ - getWidget().scrollBodyPanel.getElement().getStyle() - .setPosition(Position.STATIC); - } else if (BrowserInfo.get().isIE8()) { - getWidget().scrollBodyPanel.getElement().getStyle() - .setPosition(Position.RELATIVE); - } - - getWidget().paintableId = uidl.getStringAttribute("id"); - getWidget().immediate = getState().isImmediate(); - - int previousTotalRows = getWidget().totalRows; - getWidget().updateTotalRows(uidl); - boolean totalRowsChanged = (getWidget().totalRows != previousTotalRows); - - getWidget().updateDragMode(uidl); - - getWidget().updateSelectionProperties(uidl, getState(), isReadOnly()); - - if (uidl.hasAttribute("alb")) { - getWidget().bodyActionKeys = uidl.getStringArrayAttribute("alb"); - } else { - // Need to clear the actions if the action handlers have been - // removed - getWidget().bodyActionKeys = null; - } - - getWidget().setCacheRateFromUIDL(uidl); - - getWidget().recalcWidths = uidl.hasAttribute("recalcWidths"); - if (getWidget().recalcWidths) { - getWidget().tHead.clear(); - getWidget().tFoot.clear(); - } - - getWidget().updatePageLength(uidl); - - getWidget().updateFirstVisibleAndScrollIfNeeded(uidl); - - getWidget().showRowHeaders = uidl.getBooleanAttribute("rowheaders"); - getWidget().showColHeaders = uidl.getBooleanAttribute("colheaders"); - - getWidget().updateSortingProperties(uidl); - - boolean keyboardSelectionOverRowFetchInProgress = getWidget() - .selectSelectedRows(uidl); - - getWidget().updateActionMap(uidl); - - getWidget().updateColumnProperties(uidl); - - UIDL ac = uidl.getChildByTagName("-ac"); - if (ac == null) { - if (getWidget().dropHandler != null) { - // remove dropHandler if not present anymore - getWidget().dropHandler = null; - } - } else { - if (getWidget().dropHandler == null) { - getWidget().dropHandler = getWidget().new VScrollTableDropHandler(); - } - getWidget().dropHandler.updateAcceptRules(ac); - } - - UIDL partialRowAdditions = uidl.getChildByTagName("prows"); - UIDL partialRowUpdates = uidl.getChildByTagName("urows"); - if (partialRowUpdates != null || partialRowAdditions != null) { - // we may have pending cache row fetch, cancel it. See #2136 - getWidget().rowRequestHandler.cancel(); - - getWidget().updateRowsInBody(partialRowUpdates); - getWidget().addAndRemoveRows(partialRowAdditions); - } else { - UIDL rowData = uidl.getChildByTagName("rows"); - if (rowData != null) { - // we may have pending cache row fetch, cancel it. See #2136 - getWidget().rowRequestHandler.cancel(); - - if (!getWidget().recalcWidths - && getWidget().initializedAndAttached) { - getWidget().updateBody(rowData, - uidl.getIntAttribute("firstrow"), - uidl.getIntAttribute("rows")); - if (getWidget().headerChangedDuringUpdate) { - getWidget().triggerLazyColumnAdjustment(true); - } else if (!getWidget().isScrollPositionVisible() - || totalRowsChanged - || getWidget().lastRenderedHeight != getWidget().scrollBody - .getOffsetHeight()) { - // webkits may still bug with their disturbing scrollbar - // bug, see #3457 - // Run overflow fix for the scrollable area - // #6698 - If there's a scroll going on, don't abort it - // by changing overflows as the length of the contents - // *shouldn't* have changed (unless the number of rows - // or the height of the widget has also changed) - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - Util.runWebkitOverflowAutoFix(getWidget().scrollBodyPanel - .getElement()); - } - }); - } - } else { - getWidget().initializeRows(uidl, rowData); - } - } - } - - // If a row had an open context menu before the update, and after the - // update there's a row with the same key as that row, restore the - // context menu. See #8526. - showSavedContextMenu(contextMenuBeforeUpdate); - - if (!getWidget().isSelectable()) { - getWidget().scrollBody.addStyleName(VScrollTable.CLASSNAME - + "-body-noselection"); - } else { - getWidget().scrollBody.removeStyleName(VScrollTable.CLASSNAME - + "-body-noselection"); - } - - getWidget().hideScrollPositionAnnotation(); - - // selection is no in sync with server, avoid excessive server visits by - // clearing to flag used during the normal operation - if (!keyboardSelectionOverRowFetchInProgress) { - getWidget().selectionChanged = false; - } - - /* - * This is called when the Home or page up button has been pressed in - * selectable mode and the next selected row was not yet rendered in the - * client - */ - if (getWidget().selectFirstItemInNextRender - || getWidget().focusFirstItemInNextRender) { - getWidget().selectFirstRenderedRowInViewPort( - getWidget().focusFirstItemInNextRender); - getWidget().selectFirstItemInNextRender = getWidget().focusFirstItemInNextRender = false; - } - - /* - * This is called when the page down or end button has been pressed in - * selectable mode and the next selected row was not yet rendered in the - * client - */ - if (getWidget().selectLastItemInNextRender - || getWidget().focusLastItemInNextRender) { - getWidget().selectLastRenderedRowInViewPort( - getWidget().focusLastItemInNextRender); - getWidget().selectLastItemInNextRender = getWidget().focusLastItemInNextRender = false; - } - getWidget().multiselectPending = false; - - if (getWidget().focusedRow != null) { - if (!getWidget().focusedRow.isAttached() - && !getWidget().rowRequestHandler.isRunning()) { - // focused row has been orphaned, can't focus - getWidget().focusRowFromBody(); - } - } - - getWidget().tabIndex = uidl.hasAttribute("tabindex") ? uidl - .getIntAttribute("tabindex") : 0; - getWidget().setProperTabIndex(); - - getWidget().resizeSortedColumnForSortIndicator(); - - // Remember this to detect situations where overflow hack might be - // needed during scrolling - getWidget().lastRenderedHeight = getWidget().scrollBody - .getOffsetHeight(); - - getWidget().rendering = false; - getWidget().headerChangedDuringUpdate = false; - - } - - @Override - protected Widget createWidget() { - return GWT.create(VScrollTable.class); - } - - @Override - public VScrollTable getWidget() { - return (VScrollTable) super.getWidget(); - } - - public void updateCaption(ComponentConnector component) { - // NOP, not rendered - } - - public void layoutVertically() { - getWidget().updateHeight(); - } - - public void layoutHorizontally() { - getWidget().updateWidth(); - } - - public void postLayout() { - getWidget().sizeInit(); - } - - @Override - public boolean isReadOnly() { - return super.isReadOnly() || getState().isPropertyReadOnly(); - } - - @Override - public AbstractFieldState getState() { - return (AbstractFieldState) super.getState(); - } - - /** - * Shows a saved row context menu if the row for the context menu is still - * visible. Does nothing if a context menu has not been saved. - * - * @param savedContextMenu - */ - public void showSavedContextMenu(ContextMenuDetails savedContextMenu) { - if (isEnabled() && savedContextMenu != null) { - Iterator iterator = getWidget().scrollBody.iterator(); - while (iterator.hasNext()) { - Widget w = iterator.next(); - VScrollTableRow row = (VScrollTableRow) w; - if (row.getKey().equals(savedContextMenu.rowKey)) { - getWidget().contextMenu = savedContextMenu; - getConnection().getContextMenu().showAt(row, - savedContextMenu.left, savedContextMenu.top); - } - } - } - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/TabsheetBaseConnector.java b/src/com/vaadin/terminal/gwt/client/ui/TabsheetBaseConnector.java deleted file mode 100644 index 48d14b6b0f..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/TabsheetBaseConnector.java +++ /dev/null @@ -1,98 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.Iterator; - -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; - -public abstract class TabsheetBaseConnector extends - AbstractComponentContainerConnector implements Paintable { - - public static final String ATTRIBUTE_TAB_DISABLED = "disabled"; - public static final String ATTRIBUTE_TAB_DESCRIPTION = "description"; - public static final String ATTRIBUTE_TAB_ERROR_MESSAGE = "error"; - public static final String ATTRIBUTE_TAB_CAPTION = "caption"; - public static final String ATTRIBUTE_TAB_ICON = "icon"; - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - getWidget().client = client; - - if (!isRealUpdate(uidl)) { - return; - } - - // Update member references - getWidget().id = uidl.getId(); - getWidget().disabled = !isEnabled(); - - // Render content - final UIDL tabs = uidl.getChildUIDL(0); - - // Widgets in the TabSheet before update - ArrayList oldWidgets = new ArrayList(); - for (Iterator iterator = getWidget().getWidgetIterator(); iterator - .hasNext();) { - oldWidgets.add(iterator.next()); - } - - // Clear previous values - getWidget().tabKeys.clear(); - getWidget().disabledTabKeys.clear(); - - int index = 0; - for (final Iterator it = tabs.getChildIterator(); it.hasNext();) { - final UIDL tab = (UIDL) it.next(); - final String key = tab.getStringAttribute("key"); - final boolean selected = tab.getBooleanAttribute("selected"); - final boolean hidden = tab.getBooleanAttribute("hidden"); - - if (tab.getBooleanAttribute(ATTRIBUTE_TAB_DISABLED)) { - getWidget().disabledTabKeys.add(key); - } - - getWidget().tabKeys.add(key); - - if (selected) { - getWidget().activeTabIndex = index; - } - getWidget().renderTab(tab, index, selected, hidden); - index++; - } - - int tabCount = getWidget().getTabCount(); - while (tabCount-- > index) { - getWidget().removeTab(index); - } - - for (int i = 0; i < getWidget().getTabCount(); i++) { - ComponentConnector p = getWidget().getTab(i); - // null for PlaceHolder widgets - if (p != null) { - oldWidgets.remove(p.getWidget()); - } - } - - // Detach any old tab widget, should be max 1 - for (Iterator iterator = oldWidgets.iterator(); iterator - .hasNext();) { - Widget oldWidget = iterator.next(); - if (oldWidget.isAttached()) { - oldWidget.removeFromParent(); - } - } - - } - - @Override - public VTabsheetBase getWidget() { - return (VTabsheetBase) super.getWidget(); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/TabsheetConnector.java b/src/com/vaadin/terminal/gwt/client/ui/TabsheetConnector.java deleted file mode 100644 index afb9b69e6e..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/TabsheetConnector.java +++ /dev/null @@ -1,102 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.ui.TabSheet; - -@Component(TabSheet.class) -public class TabsheetConnector extends TabsheetBaseConnector implements - SimpleManagedLayout { - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - if (isRealUpdate(uidl)) { - // Handle stylename changes before generics (might affect size - // calculations) - getWidget().handleStyleNames(uidl, getState()); - } - - super.updateFromUIDL(uidl, client); - if (!isRealUpdate(uidl)) { - return; - } - - // tabs; push or not - if (!isUndefinedWidth()) { - DOM.setStyleAttribute(getWidget().tabs, "overflow", "hidden"); - } else { - getWidget().showAllTabs(); - DOM.setStyleAttribute(getWidget().tabs, "width", ""); - DOM.setStyleAttribute(getWidget().tabs, "overflow", "visible"); - getWidget().updateDynamicWidth(); - } - - if (!isUndefinedHeight()) { - // Must update height after the styles have been set - getWidget().updateContentNodeHeight(); - getWidget().updateOpenTabSize(); - } - - getWidget().iLayout(); - - // Re run relative size update to ensure optimal scrollbars - // TODO isolate to situation that visible tab has undefined height - try { - client.handleComponentRelativeSize(getWidget().tp - .getWidget(getWidget().tp.getVisibleWidget())); - } catch (Exception e) { - // Ignore, most likely empty tabsheet - } - - getWidget().waitingForResponse = false; - } - - @Override - protected Widget createWidget() { - return GWT.create(VTabsheet.class); - } - - @Override - public VTabsheet getWidget() { - return (VTabsheet) super.getWidget(); - } - - public void updateCaption(ComponentConnector component) { - /* Tabsheet does not render its children's captions */ - } - - public void layout() { - VTabsheet tabsheet = getWidget(); - - tabsheet.updateContentNodeHeight(); - - if (isUndefinedWidth()) { - tabsheet.contentNode.getStyle().setProperty("width", ""); - } else { - int contentWidth = tabsheet.getOffsetWidth() - - tabsheet.getContentAreaBorderWidth(); - if (contentWidth < 0) { - contentWidth = 0; - } - tabsheet.contentNode.getStyle().setProperty("width", - contentWidth + "px"); - } - - tabsheet.updateOpenTabSize(); - if (isUndefinedWidth()) { - tabsheet.updateDynamicWidth(); - } - - tabsheet.iLayout(); - - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/TextAreaConnector.java b/src/com/vaadin/terminal/gwt/client/ui/TextAreaConnector.java deleted file mode 100644 index 30a02cd86d..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/TextAreaConnector.java +++ /dev/null @@ -1,40 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.ui.TextArea; - -@Component(TextArea.class) -public class TextAreaConnector extends TextFieldConnector { - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - // Call parent renderer explicitly - super.updateFromUIDL(uidl, client); - - if (uidl.hasAttribute("rows")) { - getWidget().setRows(uidl.getIntAttribute("rows")); - } - - if (getWidget().getMaxLength() >= 0) { - getWidget().sinkEvents(Event.ONKEYUP); - } - } - - @Override - protected Widget createWidget() { - return GWT.create(VTextArea.class); - } - - @Override - public VTextArea getWidget() { - return (VTextArea) super.getWidget(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/TextFieldConnector.java b/src/com/vaadin/terminal/gwt/client/ui/TextFieldConnector.java deleted file mode 100644 index 8bd59e286e..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/TextFieldConnector.java +++ /dev/null @@ -1,121 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; -import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; -import com.vaadin.ui.TextField; - -@Component(value = TextField.class, loadStyle = LoadStyle.EAGER) -public class TextFieldConnector extends AbstractFieldConnector implements - Paintable, BeforeShortcutActionListener { - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - // Save details - getWidget().client = client; - getWidget().paintableId = uidl.getId(); - - if (!isRealUpdate(uidl)) { - return; - } - - getWidget().setReadOnly(isReadOnly()); - - getWidget().inputPrompt = uidl - .getStringAttribute(VTextField.ATTR_INPUTPROMPT); - - getWidget().setMaxLength( - uidl.hasAttribute("maxLength") ? uidl - .getIntAttribute("maxLength") : -1); - - getWidget().immediate = getState().isImmediate(); - - getWidget().listenTextChangeEvents = hasEventListener("ie"); - if (getWidget().listenTextChangeEvents) { - getWidget().textChangeEventMode = uidl - .getStringAttribute(VTextField.ATTR_TEXTCHANGE_EVENTMODE); - if (getWidget().textChangeEventMode - .equals(VTextField.TEXTCHANGE_MODE_EAGER)) { - getWidget().textChangeEventTimeout = 1; - } else { - getWidget().textChangeEventTimeout = uidl - .getIntAttribute(VTextField.ATTR_TEXTCHANGE_TIMEOUT); - if (getWidget().textChangeEventTimeout < 1) { - // Sanitize and allow lazy/timeout with timeout set to 0 to - // work as eager - getWidget().textChangeEventTimeout = 1; - } - } - getWidget().sinkEvents(VTextField.TEXTCHANGE_EVENTS); - getWidget().attachCutEventListener(getWidget().getElement()); - } - - if (uidl.hasAttribute("cols")) { - getWidget().setColumns( - new Integer(uidl.getStringAttribute("cols")).intValue()); - } - - final String text = uidl.getStringVariable("text"); - - /* - * We skip the text content update if field has been repainted, but text - * has not been changed. Additional sanity check verifies there is no - * change in the que (in which case we count more on the server side - * value). - */ - if (!(uidl - .getBooleanAttribute(VTextField.ATTR_NO_VALUE_CHANGE_BETWEEN_PAINTS) - && getWidget().valueBeforeEdit != null && text - .equals(getWidget().valueBeforeEdit))) { - getWidget().updateFieldContent(text); - } - - if (uidl.hasAttribute("selpos")) { - final int pos = uidl.getIntAttribute("selpos"); - final int length = uidl.getIntAttribute("sellen"); - /* - * Gecko defers setting the text so we need to defer the selection. - */ - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - getWidget().setSelectionRange(pos, length); - } - }); - } - - // Here for backward compatibility; to be moved to TextArea. - // Optimization: server does not send attribute for the default 'true' - // state. - if (uidl.hasAttribute("wordwrap") - && uidl.getBooleanAttribute("wordwrap") == false) { - getWidget().setWordwrap(false); - } else { - getWidget().setWordwrap(true); - } - } - - @Override - protected Widget createWidget() { - return GWT.create(VTextField.class); - } - - @Override - public VTextField getWidget() { - return (VTextField) super.getWidget(); - } - - public void onBeforeShortcutAction(Event e) { - getWidget().valueChange(false); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/TextualDateConnector.java b/src/com/vaadin/terminal/gwt/client/ui/TextualDateConnector.java deleted file mode 100644 index 40fb72a88b..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/TextualDateConnector.java +++ /dev/null @@ -1,56 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.UIDL; - -public class TextualDateConnector extends AbstractDateFieldConnector { - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - int origRes = getWidget().currentResolution; - String oldLocale = getWidget().currentLocale; - super.updateFromUIDL(uidl, client); - if (origRes != getWidget().currentResolution - || oldLocale != getWidget().currentLocale) { - // force recreating format string - getWidget().formatStr = null; - } - if (uidl.hasAttribute("format")) { - getWidget().formatStr = uidl.getStringAttribute("format"); - } - - getWidget().inputPrompt = uidl - .getStringAttribute(VTextualDate.ATTR_INPUTPROMPT); - - getWidget().lenient = !uidl.getBooleanAttribute("strict"); - - getWidget().buildDate(); - // not a FocusWidget -> needs own tabindex handling - if (uidl.hasAttribute("tabindex")) { - getWidget().text.setTabIndex(uidl.getIntAttribute("tabindex")); - } - - if (getWidget().readonly) { - getWidget().text.addStyleDependentName("readonly"); - } else { - getWidget().text.removeStyleDependentName("readonly"); - } - - } - - @Override - protected Widget createWidget() { - return GWT.create(VTextualDate.class); - } - - @Override - public VTextualDate getWidget() { - return (VTextualDate) super.getWidget(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/TreeConnector.java b/src/com/vaadin/terminal/gwt/client/ui/TreeConnector.java deleted file mode 100644 index 78a0a8453b..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/TreeConnector.java +++ /dev/null @@ -1,258 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Iterator; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.AbstractFieldState; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.TooltipInfo; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.ui.VTree.TreeNode; -import com.vaadin.ui.Tree; - -@Component(Tree.class) -public class TreeConnector extends AbstractComponentConnector implements - Paintable { - - public static final String ATTRIBUTE_NODE_STYLE = "style"; - public static final String ATTRIBUTE_NODE_CAPTION = "caption"; - public static final String ATTRIBUTE_NODE_ICON = "icon"; - - public static final String ATTRIBUTE_ACTION_CAPTION = "caption"; - public static final String ATTRIBUTE_ACTION_ICON = ATTRIBUTE_NODE_ICON; - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (!isRealUpdate(uidl)) { - return; - } - - getWidget().rendering = true; - - getWidget().client = client; - - if (uidl.hasAttribute("partialUpdate")) { - handleUpdate(uidl); - getWidget().rendering = false; - return; - } - - getWidget().paintableId = uidl.getId(); - - getWidget().immediate = getState().isImmediate(); - - getWidget().disabled = !isEnabled(); - getWidget().readonly = isReadOnly(); - - getWidget().dragMode = uidl.hasAttribute("dragMode") ? uidl - .getIntAttribute("dragMode") : 0; - - getWidget().isNullSelectionAllowed = uidl - .getBooleanAttribute("nullselect"); - - if (uidl.hasAttribute("alb")) { - getWidget().bodyActionKeys = uidl.getStringArrayAttribute("alb"); - } - - getWidget().body.clear(); - // clear out any references to nodes that no longer are attached - getWidget().clearNodeToKeyMap(); - TreeNode childTree = null; - UIDL childUidl = null; - for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { - childUidl = (UIDL) i.next(); - if ("actions".equals(childUidl.getTag())) { - updateActionMap(childUidl); - continue; - } else if ("-ac".equals(childUidl.getTag())) { - getWidget().updateDropHandler(childUidl); - continue; - } - childTree = getWidget().new TreeNode(); - updateNodeFromUIDL(childTree, childUidl); - getWidget().body.add(childTree); - childTree.addStyleDependentName("root"); - childTree.childNodeContainer.addStyleDependentName("root"); - } - if (childTree != null && childUidl != null) { - boolean leaf = !childUidl.getTag().equals("node"); - childTree.addStyleDependentName(leaf ? "leaf-last" : "last"); - childTree.childNodeContainer.addStyleDependentName("last"); - } - final String selectMode = uidl.getStringAttribute("selectmode"); - getWidget().selectable = !"none".equals(selectMode); - getWidget().isMultiselect = "multi".equals(selectMode); - - if (getWidget().isMultiselect) { - if (BrowserInfo.get().isTouchDevice()) { - // Always use the simple mode for touch devices that do not have - // shift/ctrl keys (#8595) - getWidget().multiSelectMode = VTree.MULTISELECT_MODE_SIMPLE; - } else { - getWidget().multiSelectMode = uidl - .getIntAttribute("multiselectmode"); - } - } - - getWidget().selectedIds = uidl.getStringArrayVariableAsSet("selected"); - - // Update lastSelection and focusedNode to point to *actual* nodes again - // after the old ones have been cleared from the body. This fixes focus - // and keyboard navigation issues as described in #7057 and other - // tickets. - if (getWidget().lastSelection != null) { - getWidget().lastSelection = getWidget().getNodeByKey( - getWidget().lastSelection.key); - } - if (getWidget().focusedNode != null) { - getWidget().setFocusedNode( - getWidget().getNodeByKey(getWidget().focusedNode.key)); - } - - if (getWidget().lastSelection == null - && getWidget().focusedNode == null - && !getWidget().selectedIds.isEmpty()) { - getWidget().setFocusedNode( - getWidget().getNodeByKey( - getWidget().selectedIds.iterator().next())); - getWidget().focusedNode.setFocused(false); - } - - getWidget().rendering = false; - - } - - @Override - protected Widget createWidget() { - return GWT.create(VTree.class); - } - - @Override - public VTree getWidget() { - return (VTree) super.getWidget(); - } - - private void handleUpdate(UIDL uidl) { - final TreeNode rootNode = getWidget().getNodeByKey( - uidl.getStringAttribute("rootKey")); - if (rootNode != null) { - if (!rootNode.getState()) { - // expanding node happened server side - rootNode.setState(true, false); - } - renderChildNodes(rootNode, (Iterator) uidl.getChildIterator()); - } - } - - /** - * Registers action for the root and also for individual nodes - * - * @param uidl - */ - private void updateActionMap(UIDL uidl) { - final Iterator it = uidl.getChildIterator(); - while (it.hasNext()) { - final UIDL action = (UIDL) it.next(); - final String key = action.getStringAttribute("key"); - final String caption = action - .getStringAttribute(ATTRIBUTE_ACTION_CAPTION); - String iconUrl = null; - if (action.hasAttribute(ATTRIBUTE_ACTION_ICON)) { - iconUrl = getConnection().translateVaadinUri( - action.getStringAttribute(ATTRIBUTE_ACTION_ICON)); - } - getWidget().registerAction(key, caption, iconUrl); - } - - } - - public void updateNodeFromUIDL(TreeNode treeNode, UIDL uidl) { - String nodeKey = uidl.getStringAttribute("key"); - treeNode.setText(uidl.getStringAttribute(ATTRIBUTE_NODE_CAPTION)); - treeNode.key = nodeKey; - - getWidget().registerNode(treeNode); - - if (uidl.hasAttribute("al")) { - treeNode.actionKeys = uidl.getStringArrayAttribute("al"); - } - - if (uidl.getTag().equals("node")) { - if (uidl.getChildCount() == 0) { - treeNode.childNodeContainer.setVisible(false); - } else { - renderChildNodes(treeNode, (Iterator) uidl.getChildIterator()); - treeNode.childrenLoaded = true; - } - } else { - treeNode.addStyleName(TreeNode.CLASSNAME + "-leaf"); - } - if (uidl.hasAttribute(ATTRIBUTE_NODE_STYLE)) { - treeNode.setNodeStyleName(uidl - .getStringAttribute(ATTRIBUTE_NODE_STYLE)); - } - - String description = uidl.getStringAttribute("descr"); - if (description != null && getConnection() != null) { - // Set tooltip - TooltipInfo info = new TooltipInfo(description); - getConnection().registerTooltip(this, nodeKey, info); - } else { - // Remove possible previous tooltip - getConnection().registerTooltip(this, nodeKey, null); - } - - if (uidl.getBooleanAttribute("expanded") && !treeNode.getState()) { - treeNode.setState(true, false); - } - - if (uidl.getBooleanAttribute("selected")) { - treeNode.setSelected(true); - // ensure that identifier is in selectedIds array (this may be a - // partial update) - getWidget().selectedIds.add(nodeKey); - } - - treeNode.setIcon(uidl.getStringAttribute(ATTRIBUTE_NODE_ICON)); - } - - void renderChildNodes(TreeNode containerNode, Iterator i) { - containerNode.childNodeContainer.clear(); - containerNode.childNodeContainer.setVisible(true); - while (i.hasNext()) { - final UIDL childUidl = i.next(); - // actions are in bit weird place, don't mix them with children, - // but current node's actions - if ("actions".equals(childUidl.getTag())) { - updateActionMap(childUidl); - continue; - } - final TreeNode childTree = getWidget().new TreeNode(); - updateNodeFromUIDL(childTree, childUidl); - containerNode.childNodeContainer.add(childTree); - if (!i.hasNext()) { - childTree - .addStyleDependentName(childTree.isLeaf() ? "leaf-last" - : "last"); - childTree.childNodeContainer.addStyleDependentName("last"); - } - } - containerNode.childrenLoaded = true; - } - - @Override - public boolean isReadOnly() { - return super.isReadOnly() || getState().isPropertyReadOnly(); - } - - @Override - public AbstractFieldState getState() { - return (AbstractFieldState) super.getState(); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/TreeTableConnector.java b/src/com/vaadin/terminal/gwt/client/ui/TreeTableConnector.java deleted file mode 100644 index 15dda772c5..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/TreeTableConnector.java +++ /dev/null @@ -1,99 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow; -import com.vaadin.terminal.gwt.client.ui.VTreeTable.PendingNavigationEvent; -import com.vaadin.ui.TreeTable; - -@Component(TreeTable.class) -public class TreeTableConnector extends TableConnector { - public static final String ATTRIBUTE_HIERARCHY_COLUMN_INDEX = "hci"; - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - FocusableScrollPanel widget = null; - int scrollPosition = 0; - if (getWidget().collapseRequest) { - widget = (FocusableScrollPanel) getWidget().getWidget(1); - scrollPosition = widget.getScrollPosition(); - } - getWidget().animationsEnabled = uidl.getBooleanAttribute("animate"); - getWidget().colIndexOfHierarchy = uidl - .hasAttribute(ATTRIBUTE_HIERARCHY_COLUMN_INDEX) ? uidl - .getIntAttribute(ATTRIBUTE_HIERARCHY_COLUMN_INDEX) : 0; - int oldTotalRows = getWidget().getTotalRows(); - super.updateFromUIDL(uidl, client); - if (getWidget().collapseRequest) { - if (getWidget().collapsedRowKey != null - && getWidget().scrollBody != null) { - VScrollTableRow row = getWidget().getRenderedRowByKey( - getWidget().collapsedRowKey); - if (row != null) { - getWidget().setRowFocus(row); - getWidget().focus(); - } - } - - int scrollPosition2 = widget.getScrollPosition(); - if (scrollPosition != scrollPosition2) { - widget.setScrollPosition(scrollPosition); - } - - // check which rows are needed from the server and initiate a - // deferred fetch - getWidget().onScroll(null); - } - // Recalculate table size if collapse request, or if page length is zero - // (not sent by server) and row count changes (#7908). - if (getWidget().collapseRequest - || (!uidl.hasAttribute("pagelength") && getWidget() - .getTotalRows() != oldTotalRows)) { - /* - * Ensure that possibly removed/added scrollbars are considered. - * Triggers row calculations, removes cached rows etc. Basically - * cleans up state. Be careful if touching this, you will break - * pageLength=0 if you remove this. - */ - getWidget().triggerLazyColumnAdjustment(true); - - getWidget().collapseRequest = false; - } - if (uidl.hasAttribute("focusedRow")) { - String key = uidl.getStringAttribute("focusedRow"); - getWidget().setRowFocus(getWidget().getRenderedRowByKey(key)); - getWidget().focusParentResponsePending = false; - } else if (uidl.hasAttribute("clearFocusPending")) { - // Special case to detect a response to a focusParent request that - // does not return any focusedRow because the selected node has no - // parent - getWidget().focusParentResponsePending = false; - } - - while (!getWidget().collapseRequest - && !getWidget().focusParentResponsePending - && !getWidget().pendingNavigationEvents.isEmpty()) { - // Keep replaying any queued events as long as we don't have any - // potential content changes pending - PendingNavigationEvent event = getWidget().pendingNavigationEvents - .removeFirst(); - getWidget() - .handleNavigation(event.keycode, event.ctrl, event.shift); - } - } - - @Override - protected Widget createWidget() { - return GWT.create(VTreeTable.class); - } - - @Override - public VTreeTable getWidget() { - return (VTreeTable) super.getWidget(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/TwinColSelectConnector.java b/src/com/vaadin/terminal/gwt/client/ui/TwinColSelectConnector.java deleted file mode 100644 index 55e75879ca..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/TwinColSelectConnector.java +++ /dev/null @@ -1,61 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.DirectionalManagedLayout; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.ui.TwinColSelect; - -@Component(TwinColSelect.class) -public class TwinColSelectConnector extends OptionGroupBaseConnector implements - DirectionalManagedLayout { - - @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - // Captions are updated before super call to ensure the widths are set - // correctly - if (isRealUpdate(uidl)) { - getWidget().updateCaptions(uidl); - getLayoutManager().setWidthNeedsUpdate(this); - } - - super.updateFromUIDL(uidl, client); - } - - @Override - protected void init() { - getLayoutManager().registerDependency(this, - getWidget().captionWrapper.getElement()); - } - - @Override - protected Widget createWidget() { - return GWT.create(VTwinColSelect.class); - } - - @Override - public VTwinColSelect getWidget() { - return (VTwinColSelect) super.getWidget(); - } - - public void layoutVertically() { - if (isUndefinedHeight()) { - getWidget().clearInternalHeights(); - } else { - getWidget().setInternalHeights(); - } - } - - public void layoutHorizontally() { - if (isUndefinedWidth()) { - getWidget().clearInternalWidths(); - } else { - getWidget().setInternalWidths(); - } - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/UploadConnector.java b/src/com/vaadin/terminal/gwt/client/ui/UploadConnector.java deleted file mode 100644 index 72d97f953a..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/UploadConnector.java +++ /dev/null @@ -1,65 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Paintable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; -import com.vaadin.ui.Upload; - -@Component(value = Upload.class, loadStyle = LoadStyle.LAZY) -public class UploadConnector extends AbstractComponentConnector implements - Paintable { - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - if (!isRealUpdate(uidl)) { - return; - } - if (uidl.hasAttribute("notStarted")) { - getWidget().t.schedule(400); - return; - } - if (uidl.hasAttribute("forceSubmit")) { - getWidget().submit(); - return; - } - getWidget().setImmediate(getState().isImmediate()); - getWidget().client = client; - getWidget().paintableId = uidl.getId(); - getWidget().nextUploadId = uidl.getIntAttribute("nextid"); - final String action = client.translateVaadinUri(uidl - .getStringVariable("action")); - getWidget().element.setAction(action); - if (uidl.hasAttribute("buttoncaption")) { - getWidget().submitButton.setText(uidl - .getStringAttribute("buttoncaption")); - getWidget().submitButton.setVisible(true); - } else { - getWidget().submitButton.setVisible(false); - } - getWidget().fu.setName(getWidget().paintableId + "_file"); - - if (!isEnabled() || isReadOnly()) { - getWidget().disableUpload(); - } else if (!uidl.getBooleanAttribute("state")) { - // Enable the button only if an upload is not in progress - getWidget().enableUpload(); - getWidget().ensureTargetFrame(); - } - } - - @Override - protected Widget createWidget() { - return GWT.create(VUpload.class); - } - - @Override - public VUpload getWidget() { - return (VUpload) super.getWidget(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/UploadIFrameOnloadStrategy.java b/src/com/vaadin/terminal/gwt/client/ui/UploadIFrameOnloadStrategy.java deleted file mode 100644 index eabb21eb4f..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/UploadIFrameOnloadStrategy.java +++ /dev/null @@ -1,25 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -public class UploadIFrameOnloadStrategy { - - native void hookEvents(com.google.gwt.dom.client.Element iframe, - VUpload upload) - /*-{ - iframe.onload = function() { - upload.@com.vaadin.terminal.gwt.client.ui.VUpload::onSubmitComplete()(); - }; - }-*/; - - /** - * @param iframe - * the iframe whose onLoad event is to be cleaned - */ - native void unHookEvents(com.google.gwt.dom.client.Element iframe) - /*-{ - iframe.onload = null; - }-*/; - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/UploadIFrameOnloadStrategyIE.java b/src/com/vaadin/terminal/gwt/client/ui/UploadIFrameOnloadStrategyIE.java deleted file mode 100644 index f4b41b0646..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/UploadIFrameOnloadStrategyIE.java +++ /dev/null @@ -1,29 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.dom.client.Element; - -/** - * IE does not have onload, detect onload via readystatechange - * - */ -public class UploadIFrameOnloadStrategyIE extends UploadIFrameOnloadStrategy { - @Override - native void hookEvents(Element iframe, VUpload upload) - /*-{ - iframe.onreadystatechange = function() { - if (iframe.readyState == 'complete') { - upload.@com.vaadin.terminal.gwt.client.ui.VUpload::onSubmitComplete()(); - } - }; - }-*/; - - @Override - native void unHookEvents(Element iframe) - /*-{ - iframe.onreadystatechange = null; - }-*/; - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VAbsoluteLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VAbsoluteLayout.java deleted file mode 100644 index 4b411e910c..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VAbsoluteLayout.java +++ /dev/null @@ -1,134 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Style; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.ComplexPanel; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.VCaption; - -public class VAbsoluteLayout extends ComplexPanel { - - /** Tag name for widget creation */ - public static final String TAGNAME = "absolutelayout"; - - /** Class name, prefix in styling */ - public static final String CLASSNAME = "v-absolutelayout"; - - private DivElement marginElement; - - protected final Element canvas = DOM.createDiv(); - - private Object previousStyleName; - - protected ApplicationConnection client; - - public VAbsoluteLayout() { - setElement(Document.get().createDivElement()); - setStyleName(CLASSNAME); - marginElement = Document.get().createDivElement(); - canvas.getStyle().setProperty("position", "relative"); - canvas.getStyle().setProperty("overflow", "hidden"); - marginElement.appendChild(canvas); - getElement().appendChild(marginElement); - - canvas.setClassName(CLASSNAME + "-canvas"); - canvas.setClassName(CLASSNAME + "-margin"); - } - - @Override - public void add(Widget child) { - super.add(child, canvas); - } - - public static class AbsoluteWrapper extends SimplePanel { - private String css; - String left; - String top; - String right; - String bottom; - private String zIndex; - - private VCaption caption; - - public AbsoluteWrapper(Widget child) { - setWidget(child); - setStyleName(CLASSNAME + "-wrapper"); - } - - public VCaption getCaption() { - return caption; - } - - public void setCaption(VCaption caption) { - this.caption = caption; - } - - public void destroy() { - if (caption != null) { - caption.removeFromParent(); - } - removeFromParent(); - } - - public void setPosition(String stringAttribute) { - if (css == null || !css.equals(stringAttribute)) { - css = stringAttribute; - top = right = bottom = left = zIndex = null; - if (!css.equals("")) { - String[] properties = css.split(";"); - for (int i = 0; i < properties.length; i++) { - String[] keyValue = properties[i].split(":"); - if (keyValue[0].equals("left")) { - left = keyValue[1]; - } else if (keyValue[0].equals("top")) { - top = keyValue[1]; - } else if (keyValue[0].equals("right")) { - right = keyValue[1]; - } else if (keyValue[0].equals("bottom")) { - bottom = keyValue[1]; - } else if (keyValue[0].equals("z-index")) { - zIndex = keyValue[1]; - } - } - } - // ensure ne values - Style style = getElement().getStyle(); - /* - * IE8 dies when nulling zIndex, even in IE7 mode. All other css - * properties (and even in older IE's) accept null values just - * fine. Assign empty string instead of null. - */ - if (zIndex != null) { - style.setProperty("zIndex", zIndex); - } else { - style.setProperty("zIndex", ""); - } - style.setProperty("top", top); - style.setProperty("left", left); - style.setProperty("right", right); - style.setProperty("bottom", bottom); - - } - updateCaptionPosition(); - } - - void updateCaptionPosition() { - if (caption != null) { - Style style = caption.getElement().getStyle(); - style.setProperty("position", "absolute"); - style.setPropertyPx("left", getElement().getOffsetLeft()); - style.setPropertyPx("top", getElement().getOffsetTop() - - caption.getHeight()); - } - } - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VAbstractSplitPanel.java b/src/com/vaadin/terminal/gwt/client/ui/VAbstractSplitPanel.java deleted file mode 100644 index 784f5d6e40..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VAbstractSplitPanel.java +++ /dev/null @@ -1,649 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.List; - -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.Style; -import com.google.gwt.event.dom.client.TouchCancelEvent; -import com.google.gwt.event.dom.client.TouchCancelHandler; -import com.google.gwt.event.dom.client.TouchEndEvent; -import com.google.gwt.event.dom.client.TouchEndHandler; -import com.google.gwt.event.dom.client.TouchMoveEvent; -import com.google.gwt.event.dom.client.TouchMoveHandler; -import com.google.gwt.event.dom.client.TouchStartEvent; -import com.google.gwt.event.dom.client.TouchStartHandler; -import com.google.gwt.event.shared.EventHandler; -import com.google.gwt.event.shared.GwtEvent; -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.ComplexPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VConsole; -import com.vaadin.terminal.gwt.client.ui.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent; - -public class VAbstractSplitPanel extends ComplexPanel { - - private boolean enabled = false; - - public static final String CLASSNAME = "v-splitpanel"; - - public static final int ORIENTATION_HORIZONTAL = 0; - - public static final int ORIENTATION_VERTICAL = 1; - - private static final int MIN_SIZE = 30; - - private int orientation = ORIENTATION_HORIZONTAL; - - Widget firstChild; - - Widget secondChild; - - private final Element wrapper = DOM.createDiv(); - - private final Element firstContainer = DOM.createDiv(); - - private final Element secondContainer = DOM.createDiv(); - - final Element splitter = DOM.createDiv(); - - private boolean resizing; - - private boolean resized = false; - - private int origX; - - private int origY; - - private int origMouseX; - - private int origMouseY; - - private boolean locked = false; - - private boolean positionReversed = false; - - List componentStyleNames; - - private Element draggingCurtain; - - ApplicationConnection client; - - boolean immediate; - - /* The current position of the split handle in either percentages or pixels */ - String position; - - protected Element scrolledContainer; - - protected int origScrollTop; - - private TouchScrollDelegate touchScrollDelegate; - - public VAbstractSplitPanel() { - this(ORIENTATION_HORIZONTAL); - } - - public VAbstractSplitPanel(int orientation) { - setElement(DOM.createDiv()); - switch (orientation) { - case ORIENTATION_HORIZONTAL: - setStyleName(CLASSNAME + "-horizontal"); - break; - case ORIENTATION_VERTICAL: - default: - setStyleName(CLASSNAME + "-vertical"); - break; - } - // size below will be overridden in update from uidl, initial size - // needed to keep IE alive - setWidth(MIN_SIZE + "px"); - setHeight(MIN_SIZE + "px"); - constructDom(); - setOrientation(orientation); - sinkEvents(Event.MOUSEEVENTS); - - addDomHandler(new TouchCancelHandler() { - public void onTouchCancel(TouchCancelEvent event) { - // TODO When does this actually happen?? - VConsole.log("TOUCH CANCEL"); - } - }, TouchCancelEvent.getType()); - addDomHandler(new TouchStartHandler() { - public void onTouchStart(TouchStartEvent event) { - Node target = event.getTouches().get(0).getTarget().cast(); - if (splitter.isOrHasChild(target)) { - onMouseDown(Event.as(event.getNativeEvent())); - } else { - getTouchScrollDelegate().onTouchStart(event); - } - } - - }, TouchStartEvent.getType()); - addDomHandler(new TouchMoveHandler() { - public void onTouchMove(TouchMoveEvent event) { - if (resizing) { - onMouseMove(Event.as(event.getNativeEvent())); - } - } - }, TouchMoveEvent.getType()); - addDomHandler(new TouchEndHandler() { - public void onTouchEnd(TouchEndEvent event) { - if (resizing) { - onMouseUp(Event.as(event.getNativeEvent())); - } - } - }, TouchEndEvent.getType()); - - } - - private TouchScrollDelegate getTouchScrollDelegate() { - if (touchScrollDelegate == null) { - touchScrollDelegate = new TouchScrollDelegate(firstContainer, - secondContainer); - } - return touchScrollDelegate; - } - - protected void constructDom() { - DOM.appendChild(splitter, DOM.createDiv()); // for styling - DOM.appendChild(getElement(), wrapper); - DOM.setStyleAttribute(wrapper, "position", "relative"); - DOM.setStyleAttribute(wrapper, "width", "100%"); - DOM.setStyleAttribute(wrapper, "height", "100%"); - - DOM.appendChild(wrapper, secondContainer); - DOM.appendChild(wrapper, firstContainer); - DOM.appendChild(wrapper, splitter); - - DOM.setStyleAttribute(splitter, "position", "absolute"); - DOM.setStyleAttribute(secondContainer, "position", "absolute"); - - DOM.setStyleAttribute(firstContainer, "overflow", "auto"); - DOM.setStyleAttribute(secondContainer, "overflow", "auto"); - - } - - private void setOrientation(int orientation) { - this.orientation = orientation; - if (orientation == ORIENTATION_HORIZONTAL) { - DOM.setStyleAttribute(splitter, "height", "100%"); - DOM.setStyleAttribute(splitter, "top", "0"); - DOM.setStyleAttribute(firstContainer, "height", "100%"); - DOM.setStyleAttribute(secondContainer, "height", "100%"); - } else { - DOM.setStyleAttribute(splitter, "width", "100%"); - DOM.setStyleAttribute(splitter, "left", "0"); - DOM.setStyleAttribute(firstContainer, "width", "100%"); - DOM.setStyleAttribute(secondContainer, "width", "100%"); - } - - DOM.setElementProperty(firstContainer, "className", CLASSNAME - + "-first-container"); - DOM.setElementProperty(secondContainer, "className", CLASSNAME - + "-second-container"); - } - - @Override - public boolean remove(Widget w) { - boolean removed = super.remove(w); - if (removed) { - if (firstChild == w) { - firstChild = null; - } else { - secondChild = null; - } - } - return removed; - } - - void setLocked(boolean newValue) { - if (locked != newValue) { - locked = newValue; - splitterSize = -1; - setStylenames(); - } - } - - void setPositionReversed(boolean reversed) { - if (positionReversed != reversed) { - if (orientation == ORIENTATION_HORIZONTAL) { - DOM.setStyleAttribute(splitter, "right", ""); - DOM.setStyleAttribute(splitter, "left", ""); - } else if (orientation == ORIENTATION_VERTICAL) { - DOM.setStyleAttribute(splitter, "top", ""); - DOM.setStyleAttribute(splitter, "bottom", ""); - } - - positionReversed = reversed; - } - } - - void setSplitPosition(String pos) { - if (pos == null) { - return; - } - - // Convert percentage values to pixels - if (pos.indexOf("%") > 0) { - int size = orientation == ORIENTATION_HORIZONTAL ? getOffsetWidth() - : getOffsetHeight(); - float percentage = Float.parseFloat(pos.substring(0, - pos.length() - 1)); - pos = percentage / 100 * size + "px"; - } - - String attributeName; - if (orientation == ORIENTATION_HORIZONTAL) { - if (positionReversed) { - attributeName = "right"; - } else { - attributeName = "left"; - } - } else { - if (positionReversed) { - attributeName = "bottom"; - } else { - attributeName = "top"; - } - } - - Style style = splitter.getStyle(); - if (!pos.equals(style.getProperty(attributeName))) { - style.setProperty(attributeName, pos); - updateSizes(); - } - } - - void updateSizes() { - if (!isAttached()) { - return; - } - - int wholeSize; - int pixelPosition; - - switch (orientation) { - case ORIENTATION_HORIZONTAL: - wholeSize = DOM.getElementPropertyInt(wrapper, "clientWidth"); - pixelPosition = DOM.getElementPropertyInt(splitter, "offsetLeft"); - - // reposition splitter in case it is out of box - if ((pixelPosition > 0 && pixelPosition + getSplitterSize() > wholeSize) - || (positionReversed && pixelPosition < 0)) { - pixelPosition = wholeSize - getSplitterSize(); - if (pixelPosition < 0) { - pixelPosition = 0; - } - setSplitPosition(pixelPosition + "px"); - return; - } - - DOM.setStyleAttribute(firstContainer, "width", pixelPosition + "px"); - int secondContainerWidth = (wholeSize - pixelPosition - getSplitterSize()); - if (secondContainerWidth < 0) { - secondContainerWidth = 0; - } - DOM.setStyleAttribute(secondContainer, "width", - secondContainerWidth + "px"); - DOM.setStyleAttribute(secondContainer, "left", - (pixelPosition + getSplitterSize()) + "px"); - - break; - case ORIENTATION_VERTICAL: - wholeSize = DOM.getElementPropertyInt(wrapper, "clientHeight"); - pixelPosition = DOM.getElementPropertyInt(splitter, "offsetTop"); - - // reposition splitter in case it is out of box - if ((pixelPosition > 0 && pixelPosition + getSplitterSize() > wholeSize) - || (positionReversed && pixelPosition < 0)) { - pixelPosition = wholeSize - getSplitterSize(); - if (pixelPosition < 0) { - pixelPosition = 0; - } - setSplitPosition(pixelPosition + "px"); - return; - } - - DOM.setStyleAttribute(firstContainer, "height", pixelPosition - + "px"); - int secondContainerHeight = (wholeSize - pixelPosition - getSplitterSize()); - if (secondContainerHeight < 0) { - secondContainerHeight = 0; - } - DOM.setStyleAttribute(secondContainer, "height", - secondContainerHeight + "px"); - DOM.setStyleAttribute(secondContainer, "top", - (pixelPosition + getSplitterSize()) + "px"); - - break; - } - - } - - void setFirstWidget(Widget w) { - if (firstChild != null) { - firstChild.removeFromParent(); - } - if (w != null) { - super.add(w, firstContainer); - } - firstChild = w; - } - - void setSecondWidget(Widget w) { - if (secondChild != null) { - secondChild.removeFromParent(); - } - if (w != null) { - super.add(w, secondContainer); - } - secondChild = w; - } - - @Override - public void onBrowserEvent(Event event) { - switch (DOM.eventGetType(event)) { - case Event.ONMOUSEMOVE: - // case Event.ONTOUCHMOVE: - if (resizing) { - onMouseMove(event); - } - break; - case Event.ONMOUSEDOWN: - // case Event.ONTOUCHSTART: - onMouseDown(event); - break; - case Event.ONMOUSEOUT: - // Dragging curtain interferes with click events if added in - // mousedown so we add it only when needed i.e., if the mouse moves - // outside the splitter. - if (resizing) { - showDraggingCurtain(); - } - break; - case Event.ONMOUSEUP: - // case Event.ONTOUCHEND: - if (resizing) { - onMouseUp(event); - } - break; - case Event.ONCLICK: - resizing = false; - break; - } - // Only fire click event listeners if the splitter isn't moved - if (Util.isTouchEvent(event) || !resized) { - super.onBrowserEvent(event); - } else if (DOM.eventGetType(event) == Event.ONMOUSEUP) { - // Reset the resized flag after a mouseup has occured so the next - // mousedown/mouseup can be interpreted as a click. - resized = false; - } - } - - public void onMouseDown(Event event) { - if (locked || !isEnabled()) { - return; - } - final Element trg = event.getEventTarget().cast(); - if (trg == splitter || trg == DOM.getChild(splitter, 0)) { - resizing = true; - DOM.setCapture(getElement()); - origX = DOM.getElementPropertyInt(splitter, "offsetLeft"); - origY = DOM.getElementPropertyInt(splitter, "offsetTop"); - origMouseX = Util.getTouchOrMouseClientX(event); - origMouseY = Util.getTouchOrMouseClientY(event); - event.stopPropagation(); - event.preventDefault(); - } - } - - public void onMouseMove(Event event) { - switch (orientation) { - case ORIENTATION_HORIZONTAL: - final int x = Util.getTouchOrMouseClientX(event); - onHorizontalMouseMove(x); - break; - case ORIENTATION_VERTICAL: - default: - final int y = Util.getTouchOrMouseClientY(event); - onVerticalMouseMove(y); - break; - } - - } - - private void onHorizontalMouseMove(int x) { - int newX = origX + x - origMouseX; - if (newX < 0) { - newX = 0; - } - if (newX + getSplitterSize() > getOffsetWidth()) { - newX = getOffsetWidth() - getSplitterSize(); - } - - if (position.indexOf("%") > 0) { - float pos = newX; - // 100% needs special handling - if (newX + getSplitterSize() >= getOffsetWidth()) { - pos = getOffsetWidth(); - } - // Reversed position - if (positionReversed) { - pos = getOffsetWidth() - pos - getSplitterSize(); - } - position = (pos / getOffsetWidth() * 100) + "%"; - } else { - // Reversed position - if (positionReversed) { - position = (getOffsetWidth() - newX - getSplitterSize()) + "px"; - } else { - position = newX + "px"; - } - } - - if (origX != newX) { - resized = true; - } - - // Reversed position - if (positionReversed) { - newX = getOffsetWidth() - newX - getSplitterSize(); - } - - setSplitPosition(newX + "px"); - client.doLayout(false); - } - - private void onVerticalMouseMove(int y) { - int newY = origY + y - origMouseY; - if (newY < 0) { - newY = 0; - } - - if (newY + getSplitterSize() > getOffsetHeight()) { - newY = getOffsetHeight() - getSplitterSize(); - } - - if (position.indexOf("%") > 0) { - float pos = newY; - // 100% needs special handling - if (newY + getSplitterSize() >= getOffsetHeight()) { - pos = getOffsetHeight(); - } - // Reversed position - if (positionReversed) { - pos = getOffsetHeight() - pos - getSplitterSize(); - } - position = pos / getOffsetHeight() * 100 + "%"; - } else { - // Reversed position - if (positionReversed) { - position = (getOffsetHeight() - newY - getSplitterSize()) - + "px"; - } else { - position = newY + "px"; - } - } - - if (origY != newY) { - resized = true; - } - - // Reversed position - if (positionReversed) { - newY = getOffsetHeight() - newY - getSplitterSize(); - } - - setSplitPosition(newY + "px"); - client.doLayout(false); - } - - public void onMouseUp(Event event) { - DOM.releaseCapture(getElement()); - hideDraggingCurtain(); - resizing = false; - if (!Util.isTouchEvent(event)) { - onMouseMove(event); - } - fireEvent(new SplitterMoveEvent(this)); - } - - public interface SplitterMoveHandler extends EventHandler { - public void splitterMoved(SplitterMoveEvent event); - - public static class SplitterMoveEvent extends - GwtEvent { - - public static final Type TYPE = new Type(); - - private Widget splitPanel; - - public SplitterMoveEvent(Widget splitPanel) { - this.splitPanel = splitPanel; - } - - @Override - public com.google.gwt.event.shared.GwtEvent.Type getAssociatedType() { - return TYPE; - } - - @Override - protected void dispatch(SplitterMoveHandler handler) { - handler.splitterMoved(this); - } - - } - } - - String getSplitterPosition() { - return position; - } - - /** - * Used in FF to avoid losing mouse capture when pointer is moved on an - * iframe. - */ - private void showDraggingCurtain() { - if (!isDraggingCurtainRequired()) { - return; - } - if (draggingCurtain == null) { - draggingCurtain = DOM.createDiv(); - DOM.setStyleAttribute(draggingCurtain, "position", "absolute"); - DOM.setStyleAttribute(draggingCurtain, "top", "0px"); - DOM.setStyleAttribute(draggingCurtain, "left", "0px"); - DOM.setStyleAttribute(draggingCurtain, "width", "100%"); - DOM.setStyleAttribute(draggingCurtain, "height", "100%"); - DOM.setStyleAttribute(draggingCurtain, "zIndex", "" - + VOverlay.Z_INDEX); - - DOM.appendChild(wrapper, draggingCurtain); - } - } - - /** - * A dragging curtain is required in Gecko and Webkit. - * - * @return true if the browser requires a dragging curtain - */ - private boolean isDraggingCurtainRequired() { - return (BrowserInfo.get().isGecko() || BrowserInfo.get().isWebkit()); - } - - /** - * Hides dragging curtain - */ - private void hideDraggingCurtain() { - if (draggingCurtain != null) { - DOM.removeChild(wrapper, draggingCurtain); - draggingCurtain = null; - } - } - - private int splitterSize = -1; - - private int getSplitterSize() { - if (splitterSize < 0) { - if (isAttached()) { - switch (orientation) { - case ORIENTATION_HORIZONTAL: - splitterSize = DOM.getElementPropertyInt(splitter, - "offsetWidth"); - break; - - default: - splitterSize = DOM.getElementPropertyInt(splitter, - "offsetHeight"); - break; - } - } - } - return splitterSize; - } - - void setStylenames() { - final String splitterSuffix = (orientation == ORIENTATION_HORIZONTAL ? "-hsplitter" - : "-vsplitter"); - final String firstContainerSuffix = "-first-container"; - final String secondContainerSuffix = "-second-container"; - String lockedSuffix = ""; - - String splitterStyle = CLASSNAME + splitterSuffix; - String firstStyle = CLASSNAME + firstContainerSuffix; - String secondStyle = CLASSNAME + secondContainerSuffix; - - if (locked) { - splitterStyle = CLASSNAME + splitterSuffix + "-locked"; - lockedSuffix = "-locked"; - } - for (String style : componentStyleNames) { - splitterStyle += " " + CLASSNAME + splitterSuffix + "-" + style - + lockedSuffix; - firstStyle += " " + CLASSNAME + firstContainerSuffix + "-" + style; - secondStyle += " " + CLASSNAME + secondContainerSuffix + "-" - + style; - } - DOM.setElementProperty(splitter, "className", splitterStyle); - DOM.setElementProperty(firstContainer, "className", firstStyle); - DOM.setElementProperty(secondContainer, "className", secondStyle); - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public boolean isEnabled() { - return enabled; - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VAccordion.java b/src/com/vaadin/terminal/gwt/client/ui/VAccordion.java deleted file mode 100644 index 9d71f4645d..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VAccordion.java +++ /dev/null @@ -1,508 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -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.ComplexPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.ConnectorMap; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VCaption; - -public class VAccordion extends VTabsheetBase { - - public static final String CLASSNAME = "v-accordion"; - - private Set widgets = new HashSet(); - - HashMap lazyUpdateMap = new HashMap(); - - StackItem openTab = null; - - int selectedUIDLItemIndex = -1; - - public VAccordion() { - super(CLASSNAME); - } - - @Override - protected void renderTab(UIDL tabUidl, int index, boolean selected, - boolean hidden) { - StackItem item; - int itemIndex; - if (getWidgetCount() <= index) { - // Create stackItem and render caption - item = new StackItem(tabUidl); - if (getWidgetCount() == 0) { - item.addStyleDependentName("first"); - } - itemIndex = getWidgetCount(); - add(item, getElement()); - } else { - item = getStackItem(index); - item = moveStackItemIfNeeded(item, index, tabUidl); - itemIndex = index; - } - item.updateCaption(tabUidl); - - item.setVisible(!hidden); - - if (selected) { - selectedUIDLItemIndex = itemIndex; - } - - if (tabUidl.getChildCount() > 0) { - lazyUpdateMap.put(item, tabUidl.getChildUIDL(0)); - } - } - - /** - * This method tries to find out if a tab has been rendered with a different - * index previously. If this is the case it re-orders the children so the - * same StackItem is used for rendering this time. E.g. if the first tab has - * been removed all tabs which contain cached content must be moved 1 step - * up to preserve the cached content. - * - * @param item - * @param newIndex - * @param tabUidl - * @return - */ - private StackItem moveStackItemIfNeeded(StackItem item, int newIndex, - UIDL tabUidl) { - UIDL tabContentUIDL = null; - ComponentConnector tabContent = null; - if (tabUidl.getChildCount() > 0) { - tabContentUIDL = tabUidl.getChildUIDL(0); - tabContent = client.getPaintable(tabContentUIDL); - } - - Widget itemWidget = item.getComponent(); - if (tabContent != null) { - if (tabContent != itemWidget) { - /* - * This is not the same widget as before, find out if it has - * been moved - */ - int oldIndex = -1; - StackItem oldItem = null; - for (int i = 0; i < getWidgetCount(); i++) { - Widget w = getWidget(i); - oldItem = (StackItem) w; - if (tabContent == oldItem.getComponent()) { - oldIndex = i; - break; - } - } - - if (oldIndex != -1 && oldIndex > newIndex) { - /* - * The tab has previously been rendered in another position - * so we must move the cached content to correct position. - * We move only items with oldIndex > newIndex to prevent - * moving items already rendered in this update. If for - * instance tabs 1,2,3 are removed and added as 3,2,1 we - * cannot re-use "1" when we get to the third tab. - */ - insert(oldItem, getElement(), newIndex, true); - return oldItem; - } - } - } else { - // Tab which has never been loaded. Must assure we use an empty - // StackItem - Widget oldWidget = item.getComponent(); - if (oldWidget != null) { - oldWidget.removeFromParent(); - } - } - return item; - } - - void open(int itemIndex) { - StackItem item = (StackItem) getWidget(itemIndex); - boolean alreadyOpen = false; - if (openTab != null) { - if (openTab.isOpen()) { - if (openTab == item) { - alreadyOpen = true; - } else { - openTab.close(); - } - } - } - - if (!alreadyOpen) { - item.open(); - activeTabIndex = itemIndex; - openTab = item; - } - - // Update the size for the open tab - updateOpenTabSize(); - } - - void close(StackItem item) { - if (!item.isOpen()) { - return; - } - - item.close(); - activeTabIndex = -1; - openTab = null; - - } - - @Override - protected void selectTab(final int index, final UIDL contentUidl) { - StackItem item = getStackItem(index); - if (index != activeTabIndex) { - open(index); - iLayout(); - // TODO Check if this is needed - client.runDescendentsLayout(this); - - } - item.setContent(contentUidl); - } - - public void onSelectTab(StackItem item) { - final int index = getWidgetIndex(item); - if (index != activeTabIndex && !disabled && !readonly - && !disabledTabKeys.contains(tabKeys.get(index))) { - addStyleDependentName("loading"); - client.updateVariable(id, "selected", "" + tabKeys.get(index), true); - } - } - - /** - * Sets the size of the open tab - */ - void updateOpenTabSize() { - if (openTab == null) { - return; - } - - // WIDTH - if (!isDynamicWidth()) { - openTab.setWidth("100%"); - } else { - openTab.setWidth(null); - } - - // HEIGHT - if (!isDynamicHeight()) { - int usedPixels = 0; - for (Widget w : getChildren()) { - StackItem item = (StackItem) w; - if (item == openTab) { - usedPixels += item.getCaptionHeight(); - } else { - // This includes the captionNode borders - usedPixels += item.getHeight(); - } - } - - int offsetHeight = getOffsetHeight(); - - int spaceForOpenItem = offsetHeight - usedPixels; - - if (spaceForOpenItem < 0) { - spaceForOpenItem = 0; - } - - openTab.setHeight(spaceForOpenItem); - } else { - openTab.setHeightFromWidget(); - - } - - } - - public void iLayout() { - if (openTab == null) { - return; - } - - if (isDynamicWidth()) { - int maxWidth = 40; - for (Widget w : getChildren()) { - StackItem si = (StackItem) w; - int captionWidth = si.getCaptionWidth(); - if (captionWidth > maxWidth) { - maxWidth = captionWidth; - } - } - int widgetWidth = openTab.getWidgetWidth(); - if (widgetWidth > maxWidth) { - maxWidth = widgetWidth; - } - super.setWidth(maxWidth + "px"); - openTab.setWidth(maxWidth); - } - - Util.runWebkitOverflowAutoFix(openTab.getContainerElement()); - - } - - /** - * A StackItem has always two children, Child 0 is a VCaption, Child 1 is - * the actual child widget. - */ - protected class StackItem extends ComplexPanel implements ClickHandler { - - public void setHeight(int height) { - if (height == -1) { - super.setHeight(""); - DOM.setStyleAttribute(content, "height", "0px"); - } else { - super.setHeight((height + getCaptionHeight()) + "px"); - DOM.setStyleAttribute(content, "height", height + "px"); - DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px"); - - } - } - - public Widget getComponent() { - if (getWidgetCount() < 2) { - return null; - } - return getWidget(1); - } - - @Override - public void setVisible(boolean visible) { - super.setVisible(visible); - } - - public void setHeightFromWidget() { - Widget widget = getChildWidget(); - if (widget == null) { - return; - } - - int paintableHeight = widget.getElement().getOffsetHeight(); - setHeight(paintableHeight); - - } - - /** - * Returns caption width including padding - * - * @return - */ - public int getCaptionWidth() { - if (caption == null) { - return 0; - } - - int captionWidth = caption.getRequiredWidth(); - int padding = Util.measureHorizontalPaddingAndBorder( - caption.getElement(), 18); - return captionWidth + padding; - } - - public void setWidth(int width) { - if (width == -1) { - super.setWidth(""); - } else { - super.setWidth(width + "px"); - } - } - - public int getHeight() { - return getOffsetHeight(); - } - - public int getCaptionHeight() { - return captionNode.getOffsetHeight(); - } - - private VCaption caption; - private boolean open = false; - private Element content = DOM.createDiv(); - private Element captionNode = DOM.createDiv(); - - public StackItem(UIDL tabUidl) { - setElement(DOM.createDiv()); - caption = new VCaption(client); - caption.addClickHandler(this); - super.add(caption, captionNode); - DOM.appendChild(captionNode, caption.getElement()); - DOM.appendChild(getElement(), captionNode); - DOM.appendChild(getElement(), content); - setStyleName(CLASSNAME + "-item"); - DOM.setElementProperty(content, "className", CLASSNAME - + "-item-content"); - DOM.setElementProperty(captionNode, "className", CLASSNAME - + "-item-caption"); - close(); - } - - @Override - public void onBrowserEvent(Event event) { - onSelectTab(this); - } - - public Element getContainerElement() { - return content; - } - - public Widget getChildWidget() { - if (getWidgetCount() > 1) { - return getWidget(1); - } else { - return null; - } - } - - public void replaceWidget(Widget newWidget) { - if (getWidgetCount() > 1) { - Widget oldWidget = getWidget(1); - ComponentConnector oldPaintable = ConnectorMap.get(client) - .getConnector(oldWidget); - ConnectorMap.get(client).unregisterConnector(oldPaintable); - widgets.remove(oldWidget); - remove(1); - } - add(newWidget, content); - widgets.add(newWidget); - } - - public void open() { - open = true; - DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px"); - DOM.setStyleAttribute(content, "left", "0px"); - DOM.setStyleAttribute(content, "visibility", ""); - addStyleDependentName("open"); - } - - public void hide() { - DOM.setStyleAttribute(content, "visibility", "hidden"); - } - - public void close() { - DOM.setStyleAttribute(content, "visibility", "hidden"); - DOM.setStyleAttribute(content, "top", "-100000px"); - DOM.setStyleAttribute(content, "left", "-100000px"); - removeStyleDependentName("open"); - setHeight(-1); - setWidth(""); - open = false; - } - - public boolean isOpen() { - return open; - } - - public void setContent(UIDL contentUidl) { - final ComponentConnector newPntbl = client - .getPaintable(contentUidl); - Widget newWidget = newPntbl.getWidget(); - if (getChildWidget() == null) { - add(newWidget, content); - widgets.add(newWidget); - } else if (getChildWidget() != newWidget) { - replaceWidget(newWidget); - } - if (contentUidl.getBooleanAttribute("cached")) { - /* - * The size of a cached, relative sized component must be - * updated to report correct size. - */ - client.handleComponentRelativeSize(newPntbl.getWidget()); - } - if (isOpen() && isDynamicHeight()) { - setHeightFromWidget(); - } - } - - public void onClick(ClickEvent event) { - onSelectTab(this); - } - - public void updateCaption(UIDL uidl) { - // TODO need to call this because the caption does not have an owner - caption.updateCaptionWithoutOwner( - uidl.getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_CAPTION), - uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DISABLED), - uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DESCRIPTION), - uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_ERROR_MESSAGE), - uidl.getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_ICON)); - } - - public int getWidgetWidth() { - return DOM.getFirstChild(content).getOffsetWidth(); - } - - public boolean contains(ComponentConnector p) { - return (getChildWidget() == p.getWidget()); - } - - public boolean isCaptionVisible() { - return caption.isVisible(); - } - - } - - @Override - protected void clearPaintables() { - clear(); - } - - boolean isDynamicWidth() { - ComponentConnector paintable = ConnectorMap.get(client).getConnector( - this); - return paintable.isUndefinedWidth(); - } - - boolean isDynamicHeight() { - ComponentConnector paintable = ConnectorMap.get(client).getConnector( - this); - return paintable.isUndefinedHeight(); - } - - @Override - @SuppressWarnings("unchecked") - protected Iterator getWidgetIterator() { - return widgets.iterator(); - } - - @Override - protected int getTabCount() { - return getWidgetCount(); - } - - @Override - protected void removeTab(int index) { - StackItem item = getStackItem(index); - remove(item); - } - - @Override - protected ComponentConnector getTab(int index) { - if (index < getWidgetCount()) { - Widget w = getStackItem(index); - return ConnectorMap.get(client).getConnector(w); - } - - return null; - } - - StackItem getStackItem(int index) { - return (StackItem) getWidget(index); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VAudio.java b/src/com/vaadin/terminal/gwt/client/ui/VAudio.java deleted file mode 100644 index f6df827237..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VAudio.java +++ /dev/null @@ -1,26 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.dom.client.AudioElement; -import com.google.gwt.dom.client.Document; - -public class VAudio extends VMediaBase { - private static String CLASSNAME = "v-audio"; - - private AudioElement audio; - - public VAudio() { - audio = Document.get().createAudioElement(); - setMediaElement(audio); - setStyleName(CLASSNAME); - } - - @Override - protected String getDefaultAltHtml() { - return "Your browser does not support the audio element."; - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VButton.java b/src/com/vaadin/terminal/gwt/client/ui/VButton.java deleted file mode 100644 index 454ca79320..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VButton.java +++ /dev/null @@ -1,439 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.Accessibility; -import com.google.gwt.user.client.ui.FocusWidget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VTooltip; -import com.vaadin.terminal.gwt.client.ui.ButtonConnector.ButtonServerRpc; - -public class VButton extends FocusWidget implements ClickHandler { - - public static final String CLASSNAME = "v-button"; - private static final String CLASSNAME_PRESSED = "v-pressed"; - - // mouse movement is checked before synthesizing click event on mouseout - protected static int MOVE_THRESHOLD = 3; - protected int mousedownX = 0; - protected int mousedownY = 0; - - protected String paintableId; - - protected ApplicationConnection client; - - protected final Element wrapper = DOM.createSpan(); - - protected Element errorIndicatorElement; - - protected final Element captionElement = DOM.createSpan(); - - protected Icon icon; - - /** - * Helper flag to handle special-case where the button is moved from under - * mouse while clicking it. In this case mouse leaves the button without - * moving. - */ - protected boolean clickPending; - - private boolean enabled = true; - - private int tabIndex = 0; - - protected boolean disableOnClick = false; - - /* - * BELOW PRIVATE MEMBERS COPY-PASTED FROM GWT CustomButton - */ - - /** - * If true, this widget is capturing with the mouse held down. - */ - private boolean isCapturing; - - /** - * If true, this widget has focus with the space bar down. - */ - private boolean isFocusing; - - /** - * Used to decide whether to allow clicks to propagate up to the superclass - * or container elements. - */ - private boolean disallowNextClick = false; - private boolean isHovering; - - protected int clickShortcut = 0; - // TODO Move this to VButtonPaintable - ButtonServerRpc buttonRpcProxy; - - public VButton() { - super(DOM.createDiv()); - setTabIndex(0); - sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.FOCUSEVENTS - | Event.KEYEVENTS); - sinkEvents(VTooltip.TOOLTIP_EVENTS); - - setStyleName(CLASSNAME); - - // Add a11y role "button" - Accessibility.setRole(getElement(), Accessibility.ROLE_BUTTON); - - wrapper.setClassName(getStylePrimaryName() + "-wrap"); - getElement().appendChild(wrapper); - captionElement.setClassName(getStylePrimaryName() + "-caption"); - wrapper.appendChild(captionElement); - - addClickHandler(this); - } - - public void setText(String text) { - captionElement.setInnerText(text); - } - - @SuppressWarnings("deprecation") - @Override - /* - * Copy-pasted from GWT CustomButton, some minor modifications done: - * - * -for IE/Opera added CLASSNAME_PRESSED - * - * -event.preventDefault() commented from ONMOUSEDOWN (Firefox won't apply - * :active styles if it is present) - * - * -Tooltip event handling added - * - * -onload event handler added (for icon handling) - */ - public void onBrowserEvent(Event event) { - if (client != null) { - client.handleTooltipEvent(event, this); - } - if (DOM.eventGetType(event) == Event.ONLOAD) { - Util.notifyParentOfSizeChange(this, true); - } - // Should not act on button if disabled. - if (!isEnabled()) { - // This can happen when events are bubbled up from non-disabled - // children - return; - } - - int type = DOM.eventGetType(event); - switch (type) { - case Event.ONCLICK: - // If clicks are currently disallowed, keep it from bubbling or - // being passed to the superclass. - if (disallowNextClick) { - event.stopPropagation(); - disallowNextClick = false; - return; - } - break; - case Event.ONMOUSEDOWN: - if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) { - // This was moved from mouseover, which iOS sometimes skips. - // We're certainly hovering at this point, and we don't actually - // need that information before this point. - setHovering(true); - } - if (event.getButton() == Event.BUTTON_LEFT) { - // save mouse position to detect movement before synthesizing - // event later - mousedownX = event.getClientX(); - mousedownY = event.getClientY(); - - disallowNextClick = true; - clickPending = true; - setFocus(true); - DOM.setCapture(getElement()); - isCapturing = true; - // Prevent dragging (on some browsers); - // DOM.eventPreventDefault(event); - if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) { - addStyleName(CLASSNAME_PRESSED); - } - } - break; - case Event.ONMOUSEUP: - if (isCapturing) { - isCapturing = false; - DOM.releaseCapture(getElement()); - if (isHovering() && event.getButton() == Event.BUTTON_LEFT) { - // Click ok - disallowNextClick = false; - } - if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) { - removeStyleName(CLASSNAME_PRESSED); - } - // Explicitly prevent IE 8 from propagating mouseup events - // upward (fixes #6753) - if (BrowserInfo.get().isIE8()) { - event.stopPropagation(); - } - } - break; - case Event.ONMOUSEMOVE: - clickPending = false; - if (isCapturing) { - // Prevent dragging (on other browsers); - DOM.eventPreventDefault(event); - } - break; - case Event.ONMOUSEOUT: - Element to = event.getRelatedTarget(); - if (getElement().isOrHasChild(DOM.eventGetTarget(event)) - && (to == null || !getElement().isOrHasChild(to))) { - if (clickPending - && Math.abs(mousedownX - event.getClientX()) < MOVE_THRESHOLD - && Math.abs(mousedownY - event.getClientY()) < MOVE_THRESHOLD) { - onClick(); - break; - } - clickPending = false; - if (isCapturing) { - } - setHovering(false); - if (BrowserInfo.get().isIE() || BrowserInfo.get().isOpera()) { - removeStyleName(CLASSNAME_PRESSED); - } - } - break; - case Event.ONBLUR: - if (isFocusing) { - isFocusing = false; - } - break; - case Event.ONLOSECAPTURE: - if (isCapturing) { - isCapturing = false; - } - break; - } - - super.onBrowserEvent(event); - - // Synthesize clicks based on keyboard events AFTER the normal key - // handling. - if ((event.getTypeInt() & Event.KEYEVENTS) != 0) { - switch (type) { - case Event.ONKEYDOWN: - if (event.getKeyCode() == 32 /* space */) { - isFocusing = true; - event.preventDefault(); - } - break; - case Event.ONKEYUP: - if (isFocusing && event.getKeyCode() == 32 /* space */) { - isFocusing = false; - - /* - * If click shortcut is space then the shortcut handler will - * take care of the click. - */ - if (clickShortcut != 32 /* space */) { - onClick(); - } - - event.preventDefault(); - } - break; - case Event.ONKEYPRESS: - if (event.getKeyCode() == KeyCodes.KEY_ENTER) { - - /* - * If click shortcut is enter then the shortcut handler will - * take care of the click. - */ - if (clickShortcut != KeyCodes.KEY_ENTER) { - onClick(); - } - - event.preventDefault(); - } - break; - } - } - } - - final void setHovering(boolean hovering) { - if (hovering != isHovering()) { - isHovering = hovering; - } - } - - final boolean isHovering() { - return isHovering; - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event - * .dom.client.ClickEvent) - */ - public void onClick(ClickEvent event) { - if (paintableId == null || client == null) { - return; - } - if (BrowserInfo.get().isSafari()) { - VButton.this.setFocus(true); - } - if (disableOnClick) { - setEnabled(false); - buttonRpcProxy.disableOnClick(); - } - - // Add mouse details - MouseEventDetails details = MouseEventDetailsBuilder - .buildMouseEventDetails(event.getNativeEvent(), getElement()); - buttonRpcProxy.click(details); - - clickPending = false; - } - - /* - * ALL BELOW COPY-PASTED FROM GWT CustomButton - */ - - /** - * Called internally when the user finishes clicking on this button. The - * default behavior is to fire the click event to listeners. Subclasses that - * override {@link #onClickStart()} should override this method to restore - * the normal widget display. - *

- * To add custom code for a click event, override - * {@link #onClick(ClickEvent)} instead of this. - */ - protected void onClick() { - // Allow the click we're about to synthesize to pass through to the - // superclass and containing elements. Element.dispatchEvent() is - // synchronous, so we simply set and clear the flag within this method. - - disallowNextClick = false; - - // Mouse coordinates are not always available (e.g., when the click is - // caused by a keyboard event). - NativeEvent evt = Document.get().createClickEvent(1, 0, 0, 0, 0, false, - false, false, false); - getElement().dispatchEvent(evt); - } - - /** - * Sets whether this button is enabled. - * - * @param enabled - * true to enable the button, false to - * disable it - */ - - @Override - public final void setEnabled(boolean enabled) { - if (isEnabled() != enabled) { - this.enabled = enabled; - if (!enabled) { - cleanupCaptureState(); - Accessibility.removeState(getElement(), - Accessibility.STATE_PRESSED); - super.setTabIndex(-1); - addStyleName(ApplicationConnection.DISABLED_CLASSNAME); - } else { - Accessibility.setState(getElement(), - Accessibility.STATE_PRESSED, "false"); - super.setTabIndex(tabIndex); - removeStyleName(ApplicationConnection.DISABLED_CLASSNAME); - } - } - } - - @Override - public final boolean isEnabled() { - return enabled; - } - - @Override - public final void setTabIndex(int index) { - super.setTabIndex(index); - tabIndex = index; - } - - /** - * Resets internal state if this button can no longer service events. This - * can occur when the widget becomes detached or disabled. - */ - private void cleanupCaptureState() { - if (isCapturing || isFocusing) { - DOM.releaseCapture(getElement()); - isCapturing = false; - isFocusing = false; - } - } - - private static native int getHorizontalBorderAndPaddingWidth(Element elem) - /*-{ - // THIS METHOD IS ONLY USED FOR INTERNET EXPLORER, IT DOESN'T WORK WITH OTHERS - - var convertToPixel = function(elem, value) { - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // Remember the original values - var left = elem.style.left, rsLeft = elem.runtimeStyle.left; - - // Put in the new values to get a computed value out - elem.runtimeStyle.left = elem.currentStyle.left; - elem.style.left = value || 0; - var ret = elem.style.pixelLeft; - - // Revert the changed values - elem.style.left = left; - elem.runtimeStyle.left = rsLeft; - - return ret; - } - - var ret = 0; - - var sides = ["Right","Left"]; - for(var i=0; i<2; i++) { - var side = sides[i]; - var value; - // Border ------------------------------------------------------- - if(elem.currentStyle["border"+side+"Style"] != "none") { - value = elem.currentStyle["border"+side+"Width"]; - if ( !/^\d+(px)?$/i.test( value ) && /^\d/.test( value ) ) { - ret += convertToPixel(elem, value); - } else if(value.length > 2) { - ret += parseInt(value.substr(0, value.length-2)); - } - } - - // Padding ------------------------------------------------------- - value = elem.currentStyle["padding"+side]; - if ( !/^\d+(px)?$/i.test( value ) && /^\d/.test( value ) ) { - ret += convertToPixel(elem, value); - } else if(value.length > 2) { - ret += parseInt(value.substr(0, value.length-2)); - } - } - - return ret; - }-*/; - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCalendarPanel.java b/src/com/vaadin/terminal/gwt/client/ui/VCalendarPanel.java deleted file mode 100644 index 6802f16b6b..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VCalendarPanel.java +++ /dev/null @@ -1,1742 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Date; -import java.util.Iterator; - -import com.google.gwt.dom.client.Node; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.DomEvent; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.KeyPressEvent; -import com.google.gwt.event.dom.client.KeyPressHandler; -import com.google.gwt.event.dom.client.MouseDownEvent; -import com.google.gwt.event.dom.client.MouseDownHandler; -import com.google.gwt.event.dom.client.MouseOutEvent; -import com.google.gwt.event.dom.client.MouseOutHandler; -import com.google.gwt.event.dom.client.MouseUpEvent; -import com.google.gwt.event.dom.client.MouseUpHandler; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.FlexTable; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.InlineHTML; -import com.google.gwt.user.client.ui.ListBox; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.DateTimeService; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VConsole; -import com.vaadin.terminal.gwt.client.ui.label.VLabel; - -@SuppressWarnings("deprecation") -public class VCalendarPanel extends FocusableFlexTable implements - KeyDownHandler, KeyPressHandler, MouseOutHandler, MouseDownHandler, - MouseUpHandler, BlurHandler, FocusHandler, SubPartAware { - - public interface SubmitListener { - - /** - * Called when calendar user triggers a submitting operation in calendar - * panel. Eg. clicking on day or hitting enter. - */ - void onSubmit(); - - /** - * On eg. ESC key. - */ - void onCancel(); - } - - /** - * Blur listener that listens to blur event from the panel - */ - public interface FocusOutListener { - /** - * @return true if the calendar panel is not used after focus moves out - */ - boolean onFocusOut(DomEvent event); - } - - /** - * FocusChangeListener is notified when the panel changes its _focused_ - * value. - */ - public interface FocusChangeListener { - void focusChanged(Date focusedDate); - } - - /** - * Dispatches an event when the panel when time is changed - */ - public interface TimeChangeListener { - - void changed(int hour, int min, int sec, int msec); - } - - /** - * Represents a Date button in the calendar - */ - private class VEventButton extends Button { - public VEventButton() { - addMouseDownHandler(VCalendarPanel.this); - addMouseOutHandler(VCalendarPanel.this); - addMouseUpHandler(VCalendarPanel.this); - } - } - - private static final String CN_FOCUSED = "focused"; - - private static final String CN_TODAY = "today"; - - private static final String CN_SELECTED = "selected"; - - private static final String CN_OFFMONTH = "offmonth"; - - /** - * Represents a click handler for when a user selects a value by using the - * mouse - */ - private ClickHandler dayClickHandler = new ClickHandler() { - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt - * .event.dom.client.ClickEvent) - */ - public void onClick(ClickEvent event) { - Day day = (Day) event.getSource(); - focusDay(day.getDate()); - selectFocused(); - onSubmit(); - } - }; - - private VEventButton prevYear; - - private VEventButton nextYear; - - private VEventButton prevMonth; - - private VEventButton nextMonth; - - private VTime time; - - private FlexTable days = new FlexTable(); - - private int resolution = VDateField.RESOLUTION_YEAR; - - private int focusedRow; - - private Timer mouseTimer; - - private Date value; - - private boolean enabled = true; - - private boolean readonly = false; - - private DateTimeService dateTimeService; - - private boolean showISOWeekNumbers; - - private Date displayedMonth; - - private Date focusedDate; - - private Day selectedDay; - - private Day focusedDay; - - private FocusOutListener focusOutListener; - - private SubmitListener submitListener; - - private FocusChangeListener focusChangeListener; - - private TimeChangeListener timeChangeListener; - - private boolean hasFocus = false; - - public VCalendarPanel() { - - setStyleName(VDateField.CLASSNAME + "-calendarpanel"); - - /* - * Firefox auto-repeat works correctly only if we use a key press - * handler, other browsers handle it correctly when using a key down - * handler - */ - if (BrowserInfo.get().isGecko()) { - addKeyPressHandler(this); - } else { - addKeyDownHandler(this); - } - addFocusHandler(this); - addBlurHandler(this); - - } - - /** - * Sets the focus to given date in the current view. Used when moving in the - * calendar with the keyboard. - * - * @param date - * A Date representing the day of month to be focused. Must be - * one of the days currently visible. - */ - private void focusDay(Date date) { - // Only used when calender body is present - if (resolution > VDateField.RESOLUTION_MONTH) { - if (focusedDay != null) { - focusedDay.removeStyleDependentName(CN_FOCUSED); - } - - if (date != null && focusedDate != null) { - focusedDate.setTime(date.getTime()); - int rowCount = days.getRowCount(); - for (int i = 0; i < rowCount; i++) { - int cellCount = days.getCellCount(i); - for (int j = 0; j < cellCount; j++) { - Widget widget = days.getWidget(i, j); - if (widget != null && widget instanceof Day) { - Day curday = (Day) widget; - if (curday.getDate().equals(date)) { - curday.addStyleDependentName(CN_FOCUSED); - focusedDay = curday; - focusedRow = i; - return; - } - } - } - } - } - } - } - - /** - * Sets the selection highlight to a given day in the current view - * - * @param date - * A Date representing the day of month to be selected. Must be - * one of the days currently visible. - * - */ - private void selectDate(Date date) { - if (selectedDay != null) { - selectedDay.removeStyleDependentName(CN_SELECTED); - } - - int rowCount = days.getRowCount(); - for (int i = 0; i < rowCount; i++) { - int cellCount = days.getCellCount(i); - for (int j = 0; j < cellCount; j++) { - Widget widget = days.getWidget(i, j); - if (widget != null && widget instanceof Day) { - Day curday = (Day) widget; - if (curday.getDate().equals(date)) { - curday.addStyleDependentName(CN_SELECTED); - selectedDay = curday; - return; - } - } - } - } - } - - /** - * Updates year, month, day from focusedDate to value - */ - private void selectFocused() { - if (focusedDate != null) { - if (value == null) { - // No previously selected value (set to null on server side). - // Create a new date using current date and time - value = new Date(); - } - /* - * #5594 set Date (day) to 1 in order to prevent any kind of - * wrapping of months when later setting the month. (e.g. 31 -> - * month with 30 days -> wraps to the 1st of the following month, - * e.g. 31st of May -> 31st of April = 1st of May) - */ - value.setDate(1); - if (value.getYear() != focusedDate.getYear()) { - value.setYear(focusedDate.getYear()); - } - if (value.getMonth() != focusedDate.getMonth()) { - value.setMonth(focusedDate.getMonth()); - } - if (value.getDate() != focusedDate.getDate()) { - } - // We always need to set the date, even if it hasn't changed, since - // it was forced to 1 above. - value.setDate(focusedDate.getDate()); - - selectDate(focusedDate); - } else { - VConsole.log("Trying to select a the focused date which is NULL!"); - } - } - - protected boolean onValueChange() { - return false; - } - - public int getResolution() { - return resolution; - } - - public void setResolution(int resolution) { - this.resolution = resolution; - if (time != null) { - time.removeFromParent(); - time = null; - } - } - - private boolean isReadonly() { - return readonly; - } - - private boolean isEnabled() { - return enabled; - } - - private void clearCalendarBody(boolean remove) { - if (!remove) { - // Leave the cells in place but clear their contents - - // This has the side effect of ensuring that the calendar always - // contain 7 rows. - for (int row = 1; row < 7; row++) { - for (int col = 0; col < 8; col++) { - days.setHTML(row, col, " "); - } - } - } else if (getRowCount() > 1) { - removeRow(1); - days.clear(); - } - } - - /** - * Builds the top buttons and current month and year header. - * - * @param needsMonth - * Should the month buttons be visible? - */ - private void buildCalendarHeader(boolean needsMonth) { - - getRowFormatter().addStyleName(0, - VDateField.CLASSNAME + "-calendarpanel-header"); - - if (prevMonth == null && needsMonth) { - prevMonth = new VEventButton(); - prevMonth.setHTML("‹"); - prevMonth.setStyleName("v-button-prevmonth"); - prevMonth.setTabIndex(-1); - nextMonth = new VEventButton(); - nextMonth.setHTML("›"); - nextMonth.setStyleName("v-button-nextmonth"); - nextMonth.setTabIndex(-1); - getFlexCellFormatter().setStyleName(0, 3, - VDateField.CLASSNAME + "-calendarpanel-nextmonth"); - getFlexCellFormatter().setStyleName(0, 1, - VDateField.CLASSNAME + "-calendarpanel-prevmonth"); - - setWidget(0, 3, nextMonth); - setWidget(0, 1, prevMonth); - } else if (prevMonth != null && !needsMonth) { - // Remove month traverse buttons - remove(prevMonth); - remove(nextMonth); - prevMonth = null; - nextMonth = null; - } - - if (prevYear == null) { - prevYear = new VEventButton(); - prevYear.setHTML("«"); - prevYear.setStyleName("v-button-prevyear"); - prevYear.setTabIndex(-1); - nextYear = new VEventButton(); - nextYear.setHTML("»"); - nextYear.setStyleName("v-button-nextyear"); - nextYear.setTabIndex(-1); - setWidget(0, 0, prevYear); - setWidget(0, 4, nextYear); - getFlexCellFormatter().setStyleName(0, 0, - VDateField.CLASSNAME + "-calendarpanel-prevyear"); - getFlexCellFormatter().setStyleName(0, 4, - VDateField.CLASSNAME + "-calendarpanel-nextyear"); - } - - final String monthName = needsMonth ? getDateTimeService().getMonth( - focusedDate.getMonth()) : ""; - final int year = focusedDate.getYear() + 1900; - getFlexCellFormatter().setStyleName(0, 2, - VDateField.CLASSNAME + "-calendarpanel-month"); - setHTML(0, 2, "" + monthName + " " + year - + ""); - } - - private DateTimeService getDateTimeService() { - return dateTimeService; - } - - public void setDateTimeService(DateTimeService dateTimeService) { - this.dateTimeService = dateTimeService; - } - - /** - * Returns whether ISO 8601 week numbers should be shown in the value - * selector or not. ISO 8601 defines that a week always starts with a Monday - * so the week numbers are only shown if this is the case. - * - * @return true if week number should be shown, false otherwise - */ - public boolean isShowISOWeekNumbers() { - return showISOWeekNumbers; - } - - public void setShowISOWeekNumbers(boolean showISOWeekNumbers) { - this.showISOWeekNumbers = showISOWeekNumbers; - } - - /** - * Builds the day and time selectors of the calendar. - */ - private void buildCalendarBody() { - - final int weekColumn = 0; - final int firstWeekdayColumn = 1; - final int headerRow = 0; - - setWidget(1, 0, days); - setCellPadding(0); - setCellSpacing(0); - getFlexCellFormatter().setColSpan(1, 0, 5); - getFlexCellFormatter().setStyleName(1, 0, - VDateField.CLASSNAME + "-calendarpanel-body"); - - days.getFlexCellFormatter().setStyleName(headerRow, weekColumn, - "v-week"); - days.setHTML(headerRow, weekColumn, ""); - // Hide the week column if week numbers are not to be displayed. - days.getFlexCellFormatter().setVisible(headerRow, weekColumn, - isShowISOWeekNumbers()); - - days.getRowFormatter().setStyleName(headerRow, - VDateField.CLASSNAME + "-calendarpanel-weekdays"); - - if (isShowISOWeekNumbers()) { - days.getFlexCellFormatter().setStyleName(headerRow, weekColumn, - "v-first"); - days.getFlexCellFormatter().setStyleName(headerRow, - firstWeekdayColumn, ""); - days.getRowFormatter().addStyleName(headerRow, - VDateField.CLASSNAME + "-calendarpanel-weeknumbers"); - } else { - days.getFlexCellFormatter().setStyleName(headerRow, weekColumn, ""); - days.getFlexCellFormatter().setStyleName(headerRow, - firstWeekdayColumn, "v-first"); - } - - days.getFlexCellFormatter().setStyleName(headerRow, - firstWeekdayColumn + 6, "v-last"); - - // Print weekday names - final int firstDay = getDateTimeService().getFirstDayOfWeek(); - for (int i = 0; i < 7; i++) { - int day = i + firstDay; - if (day > 6) { - day = 0; - } - if (getResolution() > VDateField.RESOLUTION_MONTH) { - days.setHTML(headerRow, firstWeekdayColumn + i, "" - + getDateTimeService().getShortDay(day) + ""); - } else { - days.setHTML(headerRow, firstWeekdayColumn + i, ""); - } - } - - // Zero out hours, minutes, seconds, and milliseconds to compare dates - // without time part - final Date tmp = new Date(); - final Date today = new Date(tmp.getYear(), tmp.getMonth(), - tmp.getDate()); - - final Date selectedDate = value == null ? null : new Date( - value.getYear(), value.getMonth(), value.getDate()); - - final int startWeekDay = getDateTimeService().getStartWeekDay( - displayedMonth); - final Date curr = (Date) displayedMonth.clone(); - // Start from the first day of the week that at least partially belongs - // to the current month - curr.setDate(1 - startWeekDay); - - // No month has more than 6 weeks so 6 is a safe maximum for rows. - for (int weekOfMonth = 1; weekOfMonth < 7; weekOfMonth++) { - for (int dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) { - - // Actually write the day of month - Day day = new Day((Date) curr.clone()); - - if (curr.equals(selectedDate)) { - day.addStyleDependentName(CN_SELECTED); - selectedDay = day; - } - if (curr.equals(today)) { - day.addStyleDependentName(CN_TODAY); - } - if (curr.equals(focusedDate)) { - focusedDay = day; - focusedRow = weekOfMonth; - if (hasFocus) { - day.addStyleDependentName(CN_FOCUSED); - } - } - if (curr.getMonth() != displayedMonth.getMonth()) { - day.addStyleDependentName(CN_OFFMONTH); - } - - days.setWidget(weekOfMonth, firstWeekdayColumn + dayOfWeek, day); - - // ISO week numbers if requested - days.getCellFormatter().setVisible(weekOfMonth, weekColumn, - isShowISOWeekNumbers()); - if (isShowISOWeekNumbers()) { - final String baseCssClass = VDateField.CLASSNAME - + "-calendarpanel-weeknumber"; - String weekCssClass = baseCssClass; - - int weekNumber = DateTimeService.getISOWeekNumber(curr); - - days.setHTML(weekOfMonth, 0, "" + weekNumber - + ""); - } - curr.setDate(curr.getDate() + 1); - } - } - } - - /** - * Do we need the time selector - * - * @return True if it is required - */ - private boolean isTimeSelectorNeeded() { - return getResolution() > VDateField.RESOLUTION_DAY; - } - - /** - * Updates the calendar and text field with the selected dates. - */ - public void renderCalendar() { - if (focusedDate == null) { - Date now = new Date(); - // focusedDate must have zero hours, mins, secs, millisecs - focusedDate = new Date(now.getYear(), now.getMonth(), now.getDate()); - displayedMonth = new Date(now.getYear(), now.getMonth(), 1); - } - - if (getResolution() <= VDateField.RESOLUTION_MONTH - && focusChangeListener != null) { - focusChangeListener.focusChanged(new Date(focusedDate.getTime())); - } - - final boolean needsMonth = getResolution() > VDateField.RESOLUTION_YEAR; - boolean needsBody = getResolution() >= VDateField.RESOLUTION_DAY; - buildCalendarHeader(needsMonth); - clearCalendarBody(!needsBody); - if (needsBody) { - buildCalendarBody(); - } - - if (isTimeSelectorNeeded() && time == null) { - time = new VTime(); - setWidget(2, 0, time); - getFlexCellFormatter().setColSpan(2, 0, 5); - getFlexCellFormatter().setStyleName(2, 0, - VDateField.CLASSNAME + "-calendarpanel-time"); - } else if (isTimeSelectorNeeded()) { - time.updateTimes(); - } else if (time != null) { - remove(time); - } - } - - /** - * Moves the focus forward the given number of days. - */ - private void focusNextDay(int days) { - int oldMonth = focusedDate.getMonth(); - focusedDate.setDate(focusedDate.getDate() + days); - - if (focusedDate.getMonth() == oldMonth) { - // Month did not change, only move the selection - focusDay(focusedDate); - } else { - // If the month changed we need to re-render the calendar - displayedMonth.setMonth(focusedDate.getMonth()); - renderCalendar(); - } - } - - /** - * Moves the focus backward the given number of days. - */ - private void focusPreviousDay(int days) { - focusNextDay(-days); - } - - /** - * Selects the next month - */ - private void focusNextMonth() { - - int currentMonth = focusedDate.getMonth(); - focusedDate.setMonth(currentMonth + 1); - int requestedMonth = (currentMonth + 1) % 12; - - /* - * If the selected value was e.g. 31.3 the new value would be 31.4 but - * this value is invalid so the new value will be 1.5. This is taken - * care of by decreasing the value until we have the correct month. - */ - while (focusedDate.getMonth() != requestedMonth) { - focusedDate.setDate(focusedDate.getDate() - 1); - } - displayedMonth.setMonth(displayedMonth.getMonth() + 1); - - renderCalendar(); - } - - /** - * Selects the previous month - */ - private void focusPreviousMonth() { - int currentMonth = focusedDate.getMonth(); - focusedDate.setMonth(currentMonth - 1); - - /* - * If the selected value was e.g. 31.12 the new value would be 31.11 but - * this value is invalid so the new value will be 1.12. This is taken - * care of by decreasing the value until we have the correct month. - */ - while (focusedDate.getMonth() == currentMonth) { - focusedDate.setDate(focusedDate.getDate() - 1); - } - displayedMonth.setMonth(displayedMonth.getMonth() - 1); - - renderCalendar(); - } - - /** - * Selects the previous year - */ - private void focusPreviousYear(int years) { - int currentMonth = focusedDate.getMonth(); - focusedDate.setYear(focusedDate.getYear() - years); - displayedMonth.setYear(displayedMonth.getYear() - years); - /* - * If the focused date was a leap day (Feb 29), the new date becomes Mar - * 1 if the new year is not also a leap year. Set it to Feb 28 instead. - */ - if (focusedDate.getMonth() != currentMonth) { - focusedDate.setDate(0); - } - renderCalendar(); - } - - /** - * Selects the next year - */ - private void focusNextYear(int years) { - int currentMonth = focusedDate.getMonth(); - focusedDate.setYear(focusedDate.getYear() + years); - displayedMonth.setYear(displayedMonth.getYear() + years); - /* - * If the focused date was a leap day (Feb 29), the new date becomes Mar - * 1 if the new year is not also a leap year. Set it to Feb 28 instead. - */ - if (focusedDate.getMonth() != currentMonth) { - focusedDate.setDate(0); - } - renderCalendar(); - } - - /** - * Handles a user click on the component - * - * @param sender - * The component that was clicked - * @param updateVariable - * Should the value field be updated - * - */ - private void processClickEvent(Widget sender) { - if (!isEnabled() || isReadonly()) { - return; - } - if (sender == prevYear) { - focusPreviousYear(1); - } else if (sender == nextYear) { - focusNextYear(1); - } else if (sender == prevMonth) { - focusPreviousMonth(); - } else if (sender == nextMonth) { - focusNextMonth(); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt - * .event.dom.client.KeyDownEvent) - */ - public void onKeyDown(KeyDownEvent event) { - handleKeyPress(event); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google - * .gwt.event.dom.client.KeyPressEvent) - */ - public void onKeyPress(KeyPressEvent event) { - handleKeyPress(event); - } - - /** - * Handles the keypress from both the onKeyPress event and the onKeyDown - * event - * - * @param event - * The keydown/keypress event - */ - private void handleKeyPress(DomEvent event) { - if (time != null - && time.getElement().isOrHasChild( - (Node) event.getNativeEvent().getEventTarget().cast())) { - int nativeKeyCode = event.getNativeEvent().getKeyCode(); - if (nativeKeyCode == getSelectKey()) { - onSubmit(); // submit happens if enter key hit down on listboxes - event.preventDefault(); - event.stopPropagation(); - } - return; - } - - // Check tabs - int keycode = event.getNativeEvent().getKeyCode(); - if (keycode == KeyCodes.KEY_TAB && event.getNativeEvent().getShiftKey()) { - if (onTabOut(event)) { - return; - } - } - - // Handle the navigation - if (handleNavigation(keycode, event.getNativeEvent().getCtrlKey() - || event.getNativeEvent().getMetaKey(), event.getNativeEvent() - .getShiftKey())) { - event.preventDefault(); - } - - } - - /** - * Notifies submit-listeners of a submit event - */ - private void onSubmit() { - if (getSubmitListener() != null) { - getSubmitListener().onSubmit(); - } - } - - /** - * Notifies submit-listeners of a cancel event - */ - private void onCancel() { - if (getSubmitListener() != null) { - getSubmitListener().onCancel(); - } - } - - /** - * Handles the keyboard navigation when the resolution is set to years. - * - * @param keycode - * The keycode to process - * @param ctrl - * Is ctrl pressed? - * @param shift - * is shift pressed - * @return Returns true if the keycode was processed, else false - */ - protected boolean handleNavigationYearMode(int keycode, boolean ctrl, - boolean shift) { - - // Ctrl and Shift selection not supported - if (ctrl || shift) { - return false; - } - - else if (keycode == getPreviousKey()) { - focusNextYear(10); // Add 10 years - return true; - } - - else if (keycode == getForwardKey()) { - focusNextYear(1); // Add 1 year - return true; - } - - else if (keycode == getNextKey()) { - focusPreviousYear(10); // Subtract 10 years - return true; - } - - else if (keycode == getBackwardKey()) { - focusPreviousYear(1); // Subtract 1 year - return true; - - } else if (keycode == getSelectKey()) { - value = (Date) focusedDate.clone(); - onSubmit(); - return true; - - } else if (keycode == getResetKey()) { - // Restore showing value the selected value - focusedDate.setTime(value.getTime()); - renderCalendar(); - return true; - - } else if (keycode == getCloseKey()) { - // TODO fire listener, on users responsibility?? - - return true; - } - return false; - } - - /** - * Handle the keyboard navigation when the resolution is set to MONTH - * - * @param keycode - * The keycode to handle - * @param ctrl - * Was the ctrl key pressed? - * @param shift - * Was the shift key pressed? - * @return - */ - protected boolean handleNavigationMonthMode(int keycode, boolean ctrl, - boolean shift) { - - // Ctrl selection not supported - if (ctrl) { - return false; - - } else if (keycode == getPreviousKey()) { - focusNextYear(1); // Add 1 year - return true; - - } else if (keycode == getForwardKey()) { - focusNextMonth(); // Add 1 month - return true; - - } else if (keycode == getNextKey()) { - focusPreviousYear(1); // Subtract 1 year - return true; - - } else if (keycode == getBackwardKey()) { - focusPreviousMonth(); // Subtract 1 month - return true; - - } else if (keycode == getSelectKey()) { - value = (Date) focusedDate.clone(); - onSubmit(); - return true; - - } else if (keycode == getResetKey()) { - // Restore showing value the selected value - focusedDate.setTime(value.getTime()); - renderCalendar(); - return true; - - } else if (keycode == getCloseKey() || keycode == KeyCodes.KEY_TAB) { - - // TODO fire close event - - return true; - } - - return false; - } - - /** - * Handle keyboard navigation what the resolution is set to DAY - * - * @param keycode - * The keycode to handle - * @param ctrl - * Was the ctrl key pressed? - * @param shift - * Was the shift key pressed? - * @return Return true if the key press was handled by the method, else - * return false. - */ - protected boolean handleNavigationDayMode(int keycode, boolean ctrl, - boolean shift) { - - // Ctrl key is not in use - if (ctrl) { - return false; - } - - /* - * Jumps to the next day. - */ - if (keycode == getForwardKey() && !shift) { - focusNextDay(1); - return true; - - /* - * Jumps to the previous day - */ - } else if (keycode == getBackwardKey() && !shift) { - focusPreviousDay(1); - return true; - - /* - * Jumps one week forward in the calendar - */ - } else if (keycode == getNextKey() && !shift) { - focusNextDay(7); - return true; - - /* - * Jumps one week back in the calendar - */ - } else if (keycode == getPreviousKey() && !shift) { - focusPreviousDay(7); - return true; - - /* - * Selects the value that is chosen - */ - } else if (keycode == getSelectKey() && !shift) { - selectFocused(); - onSubmit(); // submit - return true; - - } else if (keycode == getCloseKey()) { - onCancel(); - // TODO close event - - return true; - - /* - * Jumps to the next month - */ - } else if (shift && keycode == getForwardKey()) { - focusNextMonth(); - return true; - - /* - * Jumps to the previous month - */ - } else if (shift && keycode == getBackwardKey()) { - focusPreviousMonth(); - return true; - - /* - * Jumps to the next year - */ - } else if (shift && keycode == getPreviousKey()) { - focusNextYear(1); - return true; - - /* - * Jumps to the previous year - */ - } else if (shift && keycode == getNextKey()) { - focusPreviousYear(1); - return true; - - /* - * Resets the selection - */ - } else if (keycode == getResetKey() && !shift) { - // Restore showing value the selected value - focusedDate = new Date(value.getYear(), value.getMonth(), - value.getDate()); - displayedMonth = new Date(value.getYear(), value.getMonth(), 1); - renderCalendar(); - return true; - } - - return false; - } - - /** - * Handles the keyboard navigation - * - * @param keycode - * The key code that was pressed - * @param ctrl - * Was the ctrl key pressed - * @param shift - * Was the shift key pressed - * @return Return true if key press was handled by the component, else - * return false - */ - protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { - if (!isEnabled() || isReadonly()) { - return false; - } - - else if (resolution == VDateField.RESOLUTION_YEAR) { - return handleNavigationYearMode(keycode, ctrl, shift); - } - - else if (resolution == VDateField.RESOLUTION_MONTH) { - return handleNavigationMonthMode(keycode, ctrl, shift); - } - - else if (resolution == VDateField.RESOLUTION_DAY) { - return handleNavigationDayMode(keycode, ctrl, shift); - } - - else { - return handleNavigationDayMode(keycode, ctrl, shift); - } - - } - - /** - * Returns the reset key which will reset the calendar to the previous - * selection. By default this is backspace but it can be overriden to change - * the key to whatever you want. - * - * @return - */ - protected int getResetKey() { - return KeyCodes.KEY_BACKSPACE; - } - - /** - * Returns the select key which selects the value. By default this is the - * enter key but it can be changed to whatever you like by overriding this - * method. - * - * @return - */ - protected int getSelectKey() { - return KeyCodes.KEY_ENTER; - } - - /** - * Returns the key that closes the popup window if this is a VPopopCalendar. - * Else this does nothing. By default this is the Escape key but you can - * change the key to whatever you want by overriding this method. - * - * @return - */ - protected int getCloseKey() { - return KeyCodes.KEY_ESCAPE; - } - - /** - * The key that selects the next day in the calendar. By default this is the - * right arrow key but by overriding this method it can be changed to - * whatever you like. - * - * @return - */ - protected int getForwardKey() { - return KeyCodes.KEY_RIGHT; - } - - /** - * The key that selects the previous day in the calendar. By default this is - * the left arrow key but by overriding this method it can be changed to - * whatever you like. - * - * @return - */ - protected int getBackwardKey() { - return KeyCodes.KEY_LEFT; - } - - /** - * The key that selects the next week in the calendar. By default this is - * the down arrow key but by overriding this method it can be changed to - * whatever you like. - * - * @return - */ - protected int getNextKey() { - return KeyCodes.KEY_DOWN; - } - - /** - * The key that selects the previous week in the calendar. By default this - * is the up arrow key but by overriding this method it can be changed to - * whatever you like. - * - * @return - */ - protected int getPreviousKey() { - return KeyCodes.KEY_UP; - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.MouseOutHandler#onMouseOut(com.google - * .gwt.event.dom.client.MouseOutEvent) - */ - public void onMouseOut(MouseOutEvent event) { - if (mouseTimer != null) { - mouseTimer.cancel(); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google - * .gwt.event.dom.client.MouseDownEvent) - */ - public void onMouseDown(MouseDownEvent event) { - // Allow user to click-n-hold for fast-forward or fast-rewind. - // Timer is first used for a 500ms delay after mousedown. After that has - // elapsed, another timer is triggered to go off every 150ms. Both - // timers are cancelled on mouseup or mouseout. - if (event.getSource() instanceof VEventButton) { - final VEventButton sender = (VEventButton) event.getSource(); - processClickEvent(sender); - mouseTimer = new Timer() { - @Override - public void run() { - mouseTimer = new Timer() { - @Override - public void run() { - processClickEvent(sender); - } - }; - mouseTimer.scheduleRepeating(150); - } - }; - mouseTimer.schedule(500); - } - - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.MouseUpHandler#onMouseUp(com.google.gwt - * .event.dom.client.MouseUpEvent) - */ - public void onMouseUp(MouseUpEvent event) { - if (mouseTimer != null) { - mouseTimer.cancel(); - } - } - - /** - * Sets the data of the Panel. - * - * @param currentDate - * The date to set - */ - public void setDate(Date currentDate) { - - // Check that we are not re-rendering an already active date - if (currentDate == value && currentDate != null) { - return; - } - - Date oldDisplayedMonth = displayedMonth; - value = currentDate; - - if (value == null) { - focusedDate = displayedMonth = null; - } else { - focusedDate = new Date(value.getYear(), value.getMonth(), - value.getDate()); - displayedMonth = new Date(value.getYear(), value.getMonth(), 1); - } - - // Re-render calendar if the displayed month is changed, - // or if a time selector is needed but does not exist. - if ((isTimeSelectorNeeded() && time == null) - || oldDisplayedMonth == null || value == null - || oldDisplayedMonth.getYear() != value.getYear() - || oldDisplayedMonth.getMonth() != value.getMonth()) { - renderCalendar(); - } else { - focusDay(focusedDate); - selectFocused(); - if (isTimeSelectorNeeded()) { - time.updateTimes(); - } - } - - if (!hasFocus) { - focusDay(null); - } - } - - /** - * TimeSelector is a widget consisting of list boxes that modifie the Date - * object that is given for. - * - */ - public class VTime extends FlowPanel implements ChangeHandler { - - private ListBox hours; - - private ListBox mins; - - private ListBox sec; - - private ListBox ampm; - - /** - * Constructor - */ - public VTime() { - super(); - setStyleName(VDateField.CLASSNAME + "-time"); - buildTime(); - } - - private ListBox createListBox() { - ListBox lb = new ListBox(); - lb.setStyleName(VNativeSelect.CLASSNAME); - lb.addChangeHandler(this); - lb.addBlurHandler(VCalendarPanel.this); - lb.addFocusHandler(VCalendarPanel.this); - return lb; - } - - /** - * Constructs the ListBoxes and updates their value - * - * @param redraw - * Should new instances of the listboxes be created - */ - private void buildTime() { - clear(); - - hours = createListBox(); - if (getDateTimeService().isTwelveHourClock()) { - hours.addItem("12"); - for (int i = 1; i < 12; i++) { - hours.addItem((i < 10) ? "0" + i : "" + i); - } - } else { - for (int i = 0; i < 24; i++) { - hours.addItem((i < 10) ? "0" + i : "" + i); - } - } - - hours.addChangeHandler(this); - if (getDateTimeService().isTwelveHourClock()) { - ampm = createListBox(); - final String[] ampmText = getDateTimeService().getAmPmStrings(); - ampm.addItem(ampmText[0]); - ampm.addItem(ampmText[1]); - ampm.addChangeHandler(this); - } - - if (getResolution() >= VDateField.RESOLUTION_MIN) { - mins = createListBox(); - for (int i = 0; i < 60; i++) { - mins.addItem((i < 10) ? "0" + i : "" + i); - } - mins.addChangeHandler(this); - } - if (getResolution() >= VDateField.RESOLUTION_SEC) { - sec = createListBox(); - for (int i = 0; i < 60; i++) { - sec.addItem((i < 10) ? "0" + i : "" + i); - } - sec.addChangeHandler(this); - } - - final String delimiter = getDateTimeService().getClockDelimeter(); - if (isReadonly()) { - int h = 0; - if (value != null) { - h = value.getHours(); - } - if (getDateTimeService().isTwelveHourClock()) { - h -= h < 12 ? 0 : 12; - } - add(new VLabel(h < 10 ? "0" + h : "" + h)); - } else { - add(hours); - } - - if (getResolution() >= VDateField.RESOLUTION_MIN) { - add(new VLabel(delimiter)); - if (isReadonly()) { - final int m = mins.getSelectedIndex(); - add(new VLabel(m < 10 ? "0" + m : "" + m)); - } else { - add(mins); - } - } - if (getResolution() >= VDateField.RESOLUTION_SEC) { - add(new VLabel(delimiter)); - if (isReadonly()) { - final int s = sec.getSelectedIndex(); - add(new VLabel(s < 10 ? "0" + s : "" + s)); - } else { - add(sec); - } - } - if (getResolution() == VDateField.RESOLUTION_HOUR) { - add(new VLabel(delimiter + "00")); // o'clock - } - if (getDateTimeService().isTwelveHourClock()) { - add(new VLabel(" ")); - if (isReadonly()) { - int i = 0; - if (value != null) { - i = (value.getHours() < 12) ? 0 : 1; - } - add(new VLabel(ampm.getItemText(i))); - } else { - add(ampm); - } - } - - if (isReadonly()) { - return; - } - - // Update times - updateTimes(); - - ListBox lastDropDown = getLastDropDown(); - lastDropDown.addKeyDownHandler(new KeyDownHandler() { - public void onKeyDown(KeyDownEvent event) { - boolean shiftKey = event.getNativeEvent().getShiftKey(); - if (shiftKey) { - return; - } else { - int nativeKeyCode = event.getNativeKeyCode(); - if (nativeKeyCode == KeyCodes.KEY_TAB) { - onTabOut(event); - } - } - } - }); - - } - - private ListBox getLastDropDown() { - int i = getWidgetCount() - 1; - while (i >= 0) { - Widget widget = getWidget(i); - if (widget instanceof ListBox) { - return (ListBox) widget; - } - i--; - } - return null; - } - - /** - * Updates the valus to correspond to the values in value - */ - public void updateTimes() { - boolean selected = true; - if (value == null) { - value = new Date(); - selected = false; - } - if (getDateTimeService().isTwelveHourClock()) { - int h = value.getHours(); - ampm.setSelectedIndex(h < 12 ? 0 : 1); - h -= ampm.getSelectedIndex() * 12; - hours.setSelectedIndex(h); - } else { - hours.setSelectedIndex(value.getHours()); - } - if (getResolution() >= VDateField.RESOLUTION_MIN) { - mins.setSelectedIndex(value.getMinutes()); - } - if (getResolution() >= VDateField.RESOLUTION_SEC) { - sec.setSelectedIndex(value.getSeconds()); - } - if (getDateTimeService().isTwelveHourClock()) { - ampm.setSelectedIndex(value.getHours() < 12 ? 0 : 1); - } - - hours.setEnabled(isEnabled()); - if (mins != null) { - mins.setEnabled(isEnabled()); - } - if (sec != null) { - sec.setEnabled(isEnabled()); - } - if (ampm != null) { - ampm.setEnabled(isEnabled()); - } - - } - - private int getMilliseconds() { - return DateTimeService.getMilliseconds(value); - } - - private DateTimeService getDateTimeService() { - if (dateTimeService == null) { - dateTimeService = new DateTimeService(); - } - return dateTimeService; - } - - /* - * (non-Javadoc) VT - * - * @see - * com.google.gwt.event.dom.client.ChangeHandler#onChange(com.google.gwt - * .event.dom.client.ChangeEvent) - */ - public void onChange(ChangeEvent event) { - /* - * Value from dropdowns gets always set for the value. Like year and - * month when resolution is month or year. - */ - if (event.getSource() == hours) { - int h = hours.getSelectedIndex(); - if (getDateTimeService().isTwelveHourClock()) { - h = h + ampm.getSelectedIndex() * 12; - } - value.setHours(h); - if (timeChangeListener != null) { - timeChangeListener.changed(h, value.getMinutes(), - value.getSeconds(), - DateTimeService.getMilliseconds(value)); - } - event.preventDefault(); - event.stopPropagation(); - } else if (event.getSource() == mins) { - final int m = mins.getSelectedIndex(); - value.setMinutes(m); - if (timeChangeListener != null) { - timeChangeListener.changed(value.getHours(), m, - value.getSeconds(), - DateTimeService.getMilliseconds(value)); - } - event.preventDefault(); - event.stopPropagation(); - } else if (event.getSource() == sec) { - final int s = sec.getSelectedIndex(); - value.setSeconds(s); - if (timeChangeListener != null) { - timeChangeListener.changed(value.getHours(), - value.getMinutes(), s, - DateTimeService.getMilliseconds(value)); - } - event.preventDefault(); - event.stopPropagation(); - } else if (event.getSource() == ampm) { - final int h = hours.getSelectedIndex() - + (ampm.getSelectedIndex() * 12); - value.setHours(h); - if (timeChangeListener != null) { - timeChangeListener.changed(h, value.getMinutes(), - value.getSeconds(), - DateTimeService.getMilliseconds(value)); - } - event.preventDefault(); - event.stopPropagation(); - } - } - - } - - /** - * A widget representing a single day in the calendar panel. - */ - private class Day extends InlineHTML { - private static final String BASECLASS = VDateField.CLASSNAME - + "-calendarpanel-day"; - private final Date date; - - Day(Date date) { - super("" + date.getDate()); - setStyleName(BASECLASS); - this.date = date; - addClickHandler(dayClickHandler); - } - - public Date getDate() { - return date; - } - } - - public Date getDate() { - return value; - } - - /** - * If true should be returned if the panel will not be used after this - * event. - * - * @param event - * @return - */ - protected boolean onTabOut(DomEvent event) { - if (focusOutListener != null) { - return focusOutListener.onFocusOut(event); - } - return false; - } - - /** - * A focus out listener is triggered when the panel loosed focus. This can - * happen either after a user clicks outside the panel or tabs out. - * - * @param listener - * The listener to trigger - */ - public void setFocusOutListener(FocusOutListener listener) { - focusOutListener = listener; - } - - /** - * The submit listener is called when the user selects a value from the - * calender either by clicking the day or selects it by keyboard. - * - * @param submitListener - * The listener to trigger - */ - public void setSubmitListener(SubmitListener submitListener) { - this.submitListener = submitListener; - } - - /** - * The given FocusChangeListener is notified when the focused date changes - * by user either clicking on a new date or by using the keyboard. - * - * @param listener - * The FocusChangeListener to be notified - */ - public void setFocusChangeListener(FocusChangeListener listener) { - focusChangeListener = listener; - } - - /** - * The time change listener is triggered when the user changes the time. - * - * @param listener - */ - public void setTimeChangeListener(TimeChangeListener listener) { - timeChangeListener = listener; - } - - /** - * Returns the submit listener that listens to selection made from the panel - * - * @return The listener or NULL if no listener has been set - */ - public SubmitListener getSubmitListener() { - return submitListener; - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event - * .dom.client.BlurEvent) - */ - public void onBlur(final BlurEvent event) { - if (event.getSource() instanceof VCalendarPanel) { - hasFocus = false; - focusDay(null); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event - * .dom.client.FocusEvent) - */ - public void onFocus(FocusEvent event) { - if (event.getSource() instanceof VCalendarPanel) { - hasFocus = true; - - // Focuses the current day if the calendar shows the days - if (focusedDay != null) { - focusDay(focusedDate); - } - } - } - - private static final String SUBPART_NEXT_MONTH = "nextmon"; - private static final String SUBPART_PREV_MONTH = "prevmon"; - - private static final String SUBPART_NEXT_YEAR = "nexty"; - private static final String SUBPART_PREV_YEAR = "prevy"; - private static final String SUBPART_HOUR_SELECT = "h"; - private static final String SUBPART_MINUTE_SELECT = "m"; - private static final String SUBPART_SECS_SELECT = "s"; - private static final String SUBPART_MSECS_SELECT = "ms"; - private static final String SUBPART_AMPM_SELECT = "ampm"; - private static final String SUBPART_DAY = "day"; - private static final String SUBPART_MONTH_YEAR_HEADER = "header"; - - public String getSubPartName(Element subElement) { - if (contains(nextMonth, subElement)) { - return SUBPART_NEXT_MONTH; - } else if (contains(prevMonth, subElement)) { - return SUBPART_PREV_MONTH; - } else if (contains(nextYear, subElement)) { - return SUBPART_NEXT_YEAR; - } else if (contains(prevYear, subElement)) { - return SUBPART_PREV_YEAR; - } else if (contains(days, subElement)) { - // Day, find out which dayOfMonth and use that as the identifier - Day day = Util.findWidget(subElement, Day.class); - if (day != null) { - Date date = day.getDate(); - int id = date.getDate(); - // Zero or negative ids map to days of the preceding month, - // past-the-end-of-month ids to days of the following month - if (date.getMonth() < displayedMonth.getMonth()) { - id -= DateTimeService.getNumberOfDaysInMonth(date); - } else if (date.getMonth() > displayedMonth.getMonth()) { - id += DateTimeService - .getNumberOfDaysInMonth(displayedMonth); - } - return SUBPART_DAY + id; - } - } else if (time != null) { - if (contains(time.hours, subElement)) { - return SUBPART_HOUR_SELECT; - } else if (contains(time.mins, subElement)) { - return SUBPART_MINUTE_SELECT; - } else if (contains(time.sec, subElement)) { - return SUBPART_SECS_SELECT; - } else if (contains(time.ampm, subElement)) { - return SUBPART_AMPM_SELECT; - - } - } else if (getCellFormatter().getElement(0, 2).isOrHasChild(subElement)) { - return SUBPART_MONTH_YEAR_HEADER; - } - - return null; - } - - /** - * Checks if subElement is inside the widget DOM hierarchy. - * - * @param w - * @param subElement - * @return true if {@code w} is a parent of subElement, false otherwise. - */ - private boolean contains(Widget w, Element subElement) { - if (w == null || w.getElement() == null) { - return false; - } - - return w.getElement().isOrHasChild(subElement); - } - - public Element getSubPartElement(String subPart) { - if (SUBPART_NEXT_MONTH.equals(subPart)) { - return nextMonth.getElement(); - } - if (SUBPART_PREV_MONTH.equals(subPart)) { - return prevMonth.getElement(); - } - if (SUBPART_NEXT_YEAR.equals(subPart)) { - return nextYear.getElement(); - } - if (SUBPART_PREV_YEAR.equals(subPart)) { - return prevYear.getElement(); - } - if (SUBPART_HOUR_SELECT.equals(subPart)) { - return time.hours.getElement(); - } - if (SUBPART_MINUTE_SELECT.equals(subPart)) { - return time.mins.getElement(); - } - if (SUBPART_SECS_SELECT.equals(subPart)) { - return time.sec.getElement(); - } - if (SUBPART_AMPM_SELECT.equals(subPart)) { - return time.ampm.getElement(); - } - if (subPart.startsWith(SUBPART_DAY)) { - // Zero or negative ids map to days in the preceding month, - // past-the-end-of-month ids to days in the following month - int dayOfMonth = Integer.parseInt(subPart.substring(SUBPART_DAY - .length())); - Date date = new Date(displayedMonth.getYear(), - displayedMonth.getMonth(), dayOfMonth); - Iterator iter = days.iterator(); - while (iter.hasNext()) { - Widget w = iter.next(); - if (w instanceof Day) { - Day day = (Day) w; - if (day.getDate().equals(date)) { - return day.getElement(); - } - } - } - } - - if (SUBPART_MONTH_YEAR_HEADER.equals(subPart)) { - return (Element) getCellFormatter().getElement(0, 2).getChild(0); - } - return null; - } - - @Override - protected void onDetach() { - super.onDetach(); - if (mouseTimer != null) { - mouseTimer.cancel(); - } - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCheckBox.java b/src/com/vaadin/terminal/gwt/client/ui/VCheckBox.java deleted file mode 100644 index f2ac958d24..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VCheckBox.java +++ /dev/null @@ -1,59 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.Event; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VTooltip; - -public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements - Field { - - public static final String CLASSNAME = "v-checkbox"; - - String id; - - boolean immediate; - - ApplicationConnection client; - - Element errorIndicatorElement; - - Icon icon; - - public VCheckBox() { - setStyleName(CLASSNAME); - - sinkEvents(VTooltip.TOOLTIP_EVENTS); - Element el = DOM.getFirstChild(getElement()); - while (el != null) { - DOM.sinkEvents(el, - (DOM.getEventsSunk(el) | VTooltip.TOOLTIP_EVENTS)); - el = DOM.getNextSibling(el); - } - } - - @Override - public void onBrowserEvent(Event event) { - if (icon != null && (event.getTypeInt() == Event.ONCLICK) - && (DOM.eventGetTarget(event) == icon.getElement())) { - // Click on icon should do nothing if widget is disabled - if (isEnabled()) { - setValue(!getValue()); - } - } - super.onBrowserEvent(event); - if (event.getTypeInt() == Event.ONLOAD) { - Util.notifyParentOfSizeChange(this, true); - } - if (client != null) { - client.handleTooltipEvent(event, this); - } - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCssLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VCssLayout.java deleted file mode 100644 index 69c3659d3f..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VCssLayout.java +++ /dev/null @@ -1,71 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.StyleConstants; - -public class VCssLayout extends SimplePanel { - public static final String TAGNAME = "csslayout"; - public static final String CLASSNAME = "v-" + TAGNAME; - - FlowPane panel = new FlowPane(); - - Element margin = DOM.createDiv(); - - public VCssLayout() { - super(); - getElement().appendChild(margin); - setStyleName(CLASSNAME); - margin.setClassName(CLASSNAME + "-margin"); - setWidget(panel); - } - - @Override - protected Element getContainerElement() { - return margin; - } - - public class FlowPane extends FlowPanel { - - public FlowPane() { - super(); - setStyleName(CLASSNAME + "-container"); - } - - void addOrMove(Widget child, int index) { - if (child.getParent() == this) { - int currentIndex = getWidgetIndex(child); - if (index == currentIndex) { - return; - } - } - insert(child, index); - } - - } - - /** - * Sets CSS classes for margin based on the given parameters. - * - * @param margins - * A {@link VMarginInfo} object that provides info on - * top/left/bottom/right margins - */ - protected void setMarginStyles(VMarginInfo margins) { - setStyleName(margin, VCssLayout.CLASSNAME + "-" - + StyleConstants.MARGIN_TOP, margins.hasTop()); - setStyleName(margin, VCssLayout.CLASSNAME + "-" - + StyleConstants.MARGIN_RIGHT, margins.hasRight()); - setStyleName(margin, VCssLayout.CLASSNAME + "-" - + StyleConstants.MARGIN_BOTTOM, margins.hasBottom()); - setStyleName(margin, VCssLayout.CLASSNAME + "-" - + StyleConstants.MARGIN_LEFT, margins.hasLeft()); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCustomComponent.java b/src/com/vaadin/terminal/gwt/client/ui/VCustomComponent.java deleted file mode 100644 index f552870a53..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VCustomComponent.java +++ /dev/null @@ -1,18 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.user.client.ui.SimplePanel; - -public class VCustomComponent extends SimplePanel { - - private static final String CLASSNAME = "v-customcomponent"; - - public VCustomComponent() { - super(); - setStyleName(CLASSNAME); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCustomLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VCustomLayout.java deleted file mode 100644 index aa23119964..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VCustomLayout.java +++ /dev/null @@ -1,408 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.HashMap; -import java.util.Iterator; - -import com.google.gwt.dom.client.ImageElement; -import com.google.gwt.dom.client.NodeList; -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.ComplexPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VCaption; -import com.vaadin.terminal.gwt.client.VCaptionWrapper; - -/** - * Custom Layout implements complex layout defined with HTML template. - * - * @author Vaadin Ltd - * - */ -public class VCustomLayout extends ComplexPanel { - - public static final String CLASSNAME = "v-customlayout"; - - /** Location-name to containing element in DOM map */ - private final HashMap locationToElement = new HashMap(); - - /** Location-name to contained widget map */ - final HashMap locationToWidget = new HashMap(); - - /** Widget to captionwrapper map */ - private final HashMap childWidgetToCaptionWrapper = new HashMap(); - - /** Name of the currently rendered style */ - String currentTemplateName; - - /** Unexecuted scripts loaded from the template */ - String scripts = ""; - - /** Paintable ID of this paintable */ - String pid; - - ApplicationConnection client; - - private boolean htmlInitialized = false; - - private Element elementWithNativeResizeFunction; - - private String height = ""; - - private String width = ""; - - public VCustomLayout() { - setElement(DOM.createDiv()); - // Clear any unwanted styling - DOM.setStyleAttribute(getElement(), "border", "none"); - DOM.setStyleAttribute(getElement(), "margin", "0"); - DOM.setStyleAttribute(getElement(), "padding", "0"); - - if (BrowserInfo.get().isIE()) { - DOM.setStyleAttribute(getElement(), "position", "relative"); - } - - setStyleName(CLASSNAME); - } - - /** - * Sets widget to given location. - * - * If location already contains a widget it will be removed. - * - * @param widget - * Widget to be set into location. - * @param location - * location name where widget will be added - * - * @throws IllegalArgumentException - * if no such location is found in the layout. - */ - public void setWidget(Widget widget, String location) { - - if (widget == null) { - return; - } - - // If no given location is found in the layout, and exception is throws - Element elem = locationToElement.get(location); - if (elem == null && hasTemplate()) { - throw new IllegalArgumentException("No location " + location - + " found"); - } - - // Get previous widget - final Widget previous = locationToWidget.get(location); - // NOP if given widget already exists in this location - if (previous == widget) { - return; - } - - if (previous != null) { - remove(previous); - } - - // if template is missing add element in order - if (!hasTemplate()) { - elem = getElement(); - } - - // Add widget to location - super.add(widget, elem); - locationToWidget.put(location, widget); - } - - /** Initialize HTML-layout. */ - public void initializeHTML(String template, String themeUri) { - - // Connect body of the template to DOM - template = extractBodyAndScriptsFromTemplate(template); - - // TODO prefix img src:s here with a regeps, cannot work further with IE - - String relImgPrefix = themeUri + "/layouts/"; - - // prefix all relative image elements to point to theme dir with a - // regexp search - template = template.replaceAll( - "<((?:img)|(?:IMG))\\s([^>]*)src=\"((?![a-z]+:)[^/][^\"]+)\"", - "<$1 $2src=\"" + relImgPrefix + "$3\""); - // also support src attributes without quotes - template = template - .replaceAll( - "<((?:img)|(?:IMG))\\s([^>]*)src=[^\"]((?![a-z]+:)[^/][^ />]+)[ />]", - "<$1 $2src=\"" + relImgPrefix + "$3\""); - // also prefix relative style="...url(...)..." - template = template - .replaceAll( - "(<[^>]+style=\"[^\"]*url\\()((?![a-z]+:)[^/][^\"]+)(\\)[^>]*>)", - "$1 " + relImgPrefix + "$2 $3"); - - getElement().setInnerHTML(template); - - // Remap locations to elements - locationToElement.clear(); - scanForLocations(getElement()); - - initImgElements(); - - elementWithNativeResizeFunction = DOM.getFirstChild(getElement()); - if (elementWithNativeResizeFunction == null) { - elementWithNativeResizeFunction = getElement(); - } - publishResizedFunction(elementWithNativeResizeFunction); - - htmlInitialized = true; - } - - private native boolean uriEndsWithSlash() - /*-{ - var path = $wnd.location.pathname; - if(path.charAt(path.length - 1) == "/") - return true; - return false; - }-*/; - - boolean hasTemplate() { - return htmlInitialized; - } - - /** Collect locations from template */ - private void scanForLocations(Element elem) { - - final String location = elem.getAttribute("location"); - if (!"".equals(location)) { - locationToElement.put(location, elem); - elem.setInnerHTML(""); - - } else { - final int len = DOM.getChildCount(elem); - for (int i = 0; i < len; i++) { - scanForLocations(DOM.getChild(elem, i)); - } - } - } - - /** Evaluate given script in browser document */ - static native void eval(String script) - /*-{ - try { - if (script != null) - eval("{ var document = $doc; var window = $wnd; "+ script + "}"); - } catch (e) { - } - }-*/; - - /** - * Img elements needs some special handling in custom layout. Img elements - * will get their onload events sunk. This way custom layout can notify - * parent about possible size change. - */ - private void initImgElements() { - NodeList nodeList = getElement() - .getElementsByTagName("IMG"); - for (int i = 0; i < nodeList.getLength(); i++) { - com.google.gwt.dom.client.ImageElement img = (ImageElement) nodeList - .getItem(i); - DOM.sinkEvents((Element) img.cast(), Event.ONLOAD); - } - } - - /** - * Extract body part and script tags from raw html-template. - * - * Saves contents of all script-tags to private property: scripts. Returns - * contents of the body part for the html without script-tags. Also replaces - * all _UID_ tags with an unique id-string. - * - * @param html - * Original HTML-template received from server - * @return html that is used to create the HTMLPanel. - */ - private String extractBodyAndScriptsFromTemplate(String html) { - - // Replace UID:s - html = html.replaceAll("_UID_", pid + "__"); - - // Exctract script-tags - scripts = ""; - int endOfPrevScript = 0; - int nextPosToCheck = 0; - String lc = html.toLowerCase(); - String res = ""; - int scriptStart = lc.indexOf(" 0) { - res += html.substring(endOfPrevScript, scriptStart); - scriptStart = lc.indexOf(">", scriptStart); - final int j = lc.indexOf("", scriptStart); - scripts += html.substring(scriptStart + 1, j) + ";"; - nextPosToCheck = endOfPrevScript = j + "".length(); - scriptStart = lc.indexOf("", startOfBody) + 1; - final int endOfBody = lc.indexOf("", startOfBody); - if (endOfBody > startOfBody) { - res = html.substring(startOfBody, endOfBody); - } else { - res = html.substring(startOfBody); - } - } - - return res; - } - - /** Update caption for given widget */ - public void updateCaption(ComponentConnector paintable) { - Widget widget = paintable.getWidget(); - VCaptionWrapper wrapper = childWidgetToCaptionWrapper.get(widget); - if (VCaption.isNeeded(paintable.getState())) { - if (wrapper == null) { - // Add a wrapper between the layout and the child widget - final String loc = getLocation(widget); - super.remove(widget); - wrapper = new VCaptionWrapper(paintable, client); - super.add(wrapper, locationToElement.get(loc)); - childWidgetToCaptionWrapper.put(widget, wrapper); - } - wrapper.updateCaption(); - } else { - if (wrapper != null) { - // Remove the wrapper and add the widget directly to the layout - final String loc = getLocation(widget); - super.remove(wrapper); - super.add(widget, locationToElement.get(loc)); - childWidgetToCaptionWrapper.remove(widget); - } - } - } - - /** Get the location of an widget */ - public String getLocation(Widget w) { - for (final Iterator i = locationToWidget.keySet().iterator(); i - .hasNext();) { - final String location = i.next(); - if (locationToWidget.get(location) == w) { - return location; - } - } - return null; - } - - /** Removes given widget from the layout */ - @Override - public boolean remove(Widget w) { - final String location = getLocation(w); - if (location != null) { - locationToWidget.remove(location); - } - final VCaptionWrapper cw = childWidgetToCaptionWrapper.get(w); - if (cw != null) { - childWidgetToCaptionWrapper.remove(w); - return super.remove(cw); - } else if (w != null) { - return super.remove(w); - } - return false; - } - - /** Adding widget without specifying location is not supported */ - @Override - public void add(Widget w) { - throw new UnsupportedOperationException(); - } - - /** Clear all widgets from the layout */ - @Override - public void clear() { - super.clear(); - locationToWidget.clear(); - childWidgetToCaptionWrapper.clear(); - } - - /** - * This method is published to JS side with the same name into first DOM - * node of custom layout. This way if one implements some resizeable - * containers in custom layout he/she can notify children after resize. - */ - public void notifyChildrenOfSizeChange() { - client.runDescendentsLayout(this); - } - - @Override - public void onDetach() { - super.onDetach(); - if (elementWithNativeResizeFunction != null) { - detachResizedFunction(elementWithNativeResizeFunction); - } - } - - private native void detachResizedFunction(Element element) - /*-{ - element.notifyChildrenOfSizeChange = null; - }-*/; - - private native void publishResizedFunction(Element element) - /*-{ - var self = this; - element.notifyChildrenOfSizeChange = function() { - self.@com.vaadin.terminal.gwt.client.ui.VCustomLayout::notifyChildrenOfSizeChange()(); - }; - }-*/; - - /** - * In custom layout one may want to run layout functions made with - * JavaScript. This function tests if one exists (with name "iLayoutJS" in - * layouts first DOM node) and runs et. Return value is used to determine if - * children needs to be notified of size changes. - * - * Note! When implementing a JS layout function you most likely want to call - * notifyChildrenOfSizeChange() function on your custom layouts main - * element. That method is used to control whether child components layout - * functions are to be run. - * - * @param el - * @return true if layout function exists and was run successfully, else - * false. - */ - native boolean iLayoutJS(Element el) - /*-{ - if(el && el.iLayoutJS) { - try { - el.iLayoutJS(); - return true; - } catch (e) { - return false; - } - } else { - return false; - } - }-*/; - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (event.getTypeInt() == Event.ONLOAD) { - Util.notifyParentOfSizeChange(this, true); - event.cancelBubble(true); - } - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VDateField.java b/src/com/vaadin/terminal/gwt/client/ui/VDateField.java deleted file mode 100644 index 325b72dda7..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VDateField.java +++ /dev/null @@ -1,195 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Date; - -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.FlowPanel; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.DateTimeService; -import com.vaadin.terminal.gwt.client.VTooltip; - -public class VDateField extends FlowPanel implements Field { - - public static final String CLASSNAME = "v-datefield"; - - protected String paintableId; - - protected ApplicationConnection client; - - protected boolean immediate; - - public static final int RESOLUTION_YEAR = 1; - public static final int RESOLUTION_MONTH = 2; - public static final int RESOLUTION_DAY = 4; - public static final int RESOLUTION_HOUR = 8; - public static final int RESOLUTION_MIN = 16; - public static final int RESOLUTION_SEC = 32; - - public static final String WEEK_NUMBERS = "wn"; - - static String resolutionToString(int res) { - if (res > RESOLUTION_DAY) { - return "full"; - } - if (res == RESOLUTION_DAY) { - return "day"; - } - if (res == RESOLUTION_MONTH) { - return "month"; - } - return "year"; - } - - protected int currentResolution = RESOLUTION_YEAR; - - protected String currentLocale; - - protected boolean readonly; - - protected boolean enabled; - - /** - * The date that is selected in the date field. Null if an invalid date is - * specified. - */ - private Date date = null; - - protected DateTimeService dts; - - protected boolean showISOWeekNumbers = false; - - public VDateField() { - setStyleName(CLASSNAME); - dts = new DateTimeService(); - sinkEvents(VTooltip.TOOLTIP_EVENTS); - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (client != null) { - client.handleTooltipEvent(event, this); - } - } - - /* - * We need this redundant native function because Java's Date object doesn't - * have a setMilliseconds method. - */ - protected static native double getTime(int y, int m, int d, int h, int mi, - int s, int ms) - /*-{ - try { - var date = new Date(2000,1,1,1); // don't use current date here - if(y && y >= 0) date.setFullYear(y); - if(m && m >= 1) date.setMonth(m-1); - if(d && d >= 0) date.setDate(d); - if(h >= 0) date.setHours(h); - if(mi >= 0) date.setMinutes(mi); - if(s >= 0) date.setSeconds(s); - if(ms >= 0) date.setMilliseconds(ms); - return date.getTime(); - } catch (e) { - // TODO print some error message on the console - //console.log(e); - return (new Date()).getTime(); - } - }-*/; - - public int getMilliseconds() { - return DateTimeService.getMilliseconds(date); - } - - public void setMilliseconds(int ms) { - DateTimeService.setMilliseconds(date, ms); - } - - public int getCurrentResolution() { - return currentResolution; - } - - public void setCurrentResolution(int currentResolution) { - this.currentResolution = currentResolution; - } - - public String getCurrentLocale() { - return currentLocale; - } - - public void setCurrentLocale(String currentLocale) { - this.currentLocale = currentLocale; - } - - public Date getCurrentDate() { - return date; - } - - public void setCurrentDate(Date date) { - this.date = date; - } - - public boolean isImmediate() { - return immediate; - } - - public boolean isReadonly() { - return readonly; - } - - public boolean isEnabled() { - return enabled; - } - - public DateTimeService getDateTimeService() { - return dts; - } - - public String getId() { - return paintableId; - } - - public ApplicationConnection getClient() { - return client; - } - - /** - * Returns whether ISO 8601 week numbers should be shown in the date - * selector or not. ISO 8601 defines that a week always starts with a Monday - * so the week numbers are only shown if this is the case. - * - * @return true if week number should be shown, false otherwise - */ - public boolean isShowISOWeekNumbers() { - return showISOWeekNumbers; - } - - /** - * Returns a copy of the current date. Modifying the returned date will not - * modify the value of this VDateField. Use {@link #setDate(Date)} to change - * the current date. - * - * @return A copy of the current date - */ - protected Date getDate() { - Date current = getCurrentDate(); - if (current == null) { - return null; - } else { - return (Date) getCurrentDate().clone(); - } - } - - /** - * Sets the current date for this VDateField. - * - * @param date - * The new date to use - */ - protected void setDate(Date date) { - this.date = date; - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VDateFieldCalendar.java b/src/com/vaadin/terminal/gwt/client/ui/VDateFieldCalendar.java deleted file mode 100644 index 6bf1d4a3a7..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VDateFieldCalendar.java +++ /dev/null @@ -1,87 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Date; - -import com.google.gwt.event.dom.client.DomEvent; -import com.vaadin.terminal.gwt.client.DateTimeService; -import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.FocusOutListener; -import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.SubmitListener; - -/** - * A client side implementation for InlineDateField - */ -public class VDateFieldCalendar extends VDateField { - - protected final VCalendarPanel calendarPanel; - - public VDateFieldCalendar() { - super(); - calendarPanel = new VCalendarPanel(); - add(calendarPanel); - calendarPanel.setSubmitListener(new SubmitListener() { - public void onSubmit() { - updateValueFromPanel(); - } - - public void onCancel() { - // TODO Auto-generated method stub - - } - }); - calendarPanel.setFocusOutListener(new FocusOutListener() { - public boolean onFocusOut(DomEvent event) { - updateValueFromPanel(); - return false; - } - }); - } - - /** - * TODO refactor: almost same method as in VPopupCalendar.updateValue - */ - @SuppressWarnings("deprecation") - protected void updateValueFromPanel() { - Date date2 = calendarPanel.getDate(); - Date currentDate = getCurrentDate(); - if (currentDate == null || date2.getTime() != currentDate.getTime()) { - setCurrentDate((Date) date2.clone()); - getClient().updateVariable(getId(), "year", date2.getYear() + 1900, - false); - if (getCurrentResolution() > VDateField.RESOLUTION_YEAR) { - getClient().updateVariable(getId(), "month", - date2.getMonth() + 1, false); - if (getCurrentResolution() > RESOLUTION_MONTH) { - getClient().updateVariable(getId(), "day", date2.getDate(), - false); - if (getCurrentResolution() > RESOLUTION_DAY) { - getClient().updateVariable(getId(), "hour", - date2.getHours(), false); - if (getCurrentResolution() > RESOLUTION_HOUR) { - getClient().updateVariable(getId(), "min", - date2.getMinutes(), false); - if (getCurrentResolution() > RESOLUTION_MIN) { - getClient().updateVariable(getId(), "sec", - date2.getSeconds(), false); - if (getCurrentResolution() > RESOLUTION_SEC) { - getClient().updateVariable( - getId(), - "msec", - DateTimeService - .getMilliseconds(date2), - false); - } - } - } - } - } - } - if (isImmediate()) { - getClient().sendPendingVariableChanges(); - } - } - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java b/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java deleted file mode 100644 index d87bc78038..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapper.java +++ /dev/null @@ -1,586 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.JsArrayString; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.event.dom.client.MouseDownEvent; -import com.google.gwt.event.dom.client.MouseDownHandler; -import com.google.gwt.event.dom.client.TouchStartEvent; -import com.google.gwt.event.dom.client.TouchStartHandler; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.xhr.client.ReadyStateChangeHandler; -import com.google.gwt.xhr.client.XMLHttpRequest; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.ConnectorMap; -import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VConsole; -import com.vaadin.terminal.gwt.client.VTooltip; -import com.vaadin.terminal.gwt.client.ValueMap; -import com.vaadin.terminal.gwt.client.ui.dd.DDUtil; -import com.vaadin.terminal.gwt.client.ui.dd.HorizontalDropLocation; -import com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler; -import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCallback; -import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager; -import com.vaadin.terminal.gwt.client.ui.dd.VDragEvent; -import com.vaadin.terminal.gwt.client.ui.dd.VDropHandler; -import com.vaadin.terminal.gwt.client.ui.dd.VHasDropHandler; -import com.vaadin.terminal.gwt.client.ui.dd.VHtml5DragEvent; -import com.vaadin.terminal.gwt.client.ui.dd.VHtml5File; -import com.vaadin.terminal.gwt.client.ui.dd.VTransferable; -import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; - -/** - * - * Must have features pending: - * - * drop details: locations + sizes in document hierarchy up to wrapper - * - */ -public class VDragAndDropWrapper extends VCustomComponent implements - VHasDropHandler { - public static final String DRAG_START_MODE = "dragStartMode"; - public static final String HTML5_DATA_FLAVORS = "html5-data-flavors"; - - private static final String CLASSNAME = "v-ddwrapper"; - protected static final String DRAGGABLE = "draggable"; - - public VDragAndDropWrapper() { - super(); - sinkEvents(VTooltip.TOOLTIP_EVENTS); - - hookHtml5Events(getElement()); - setStyleName(CLASSNAME); - addDomHandler(new MouseDownHandler() { - public void onMouseDown(MouseDownEvent event) { - if (startDrag(event.getNativeEvent())) { - event.preventDefault(); // prevent text selection - } - } - }, MouseDownEvent.getType()); - - addDomHandler(new TouchStartHandler() { - public void onTouchStart(TouchStartEvent event) { - if (startDrag(event.getNativeEvent())) { - /* - * Dont let eg. panel start scrolling. - */ - event.stopPropagation(); - } - } - }, TouchStartEvent.getType()); - - sinkEvents(Event.TOUCHEVENTS); - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - - if (client != null) { - client.handleTooltipEvent(event, this); - } - } - - /** - * Starts a drag and drop operation from mousedown or touchstart event if - * required conditions are met. - * - * @param event - * @return true if the event was handled as a drag start event - */ - private boolean startDrag(NativeEvent event) { - if (dragStartMode == WRAPPER || dragStartMode == COMPONENT) { - VTransferable transferable = new VTransferable(); - transferable.setDragSource(ConnectorMap.get(client).getConnector( - VDragAndDropWrapper.this)); - - ComponentConnector paintable = Util.findPaintable(client, - (Element) event.getEventTarget().cast()); - Widget widget = paintable.getWidget(); - transferable.setData("component", paintable); - VDragEvent dragEvent = VDragAndDropManager.get().startDrag( - transferable, event, true); - - transferable.setData("mouseDown", MouseEventDetailsBuilder - .buildMouseEventDetails(event).serialize()); - - if (dragStartMode == WRAPPER) { - dragEvent.createDragImage(getElement(), true); - } else { - dragEvent.createDragImage(widget.getElement(), true); - } - return true; - } - return false; - } - - protected final static int NONE = 0; - protected final static int COMPONENT = 1; - protected final static int WRAPPER = 2; - protected final static int HTML5 = 3; - - protected int dragStartMode; - - ApplicationConnection client; - VAbstractDropHandler dropHandler; - private VDragEvent vaadinDragEvent; - - int filecounter = 0; - Map fileIdToReceiver; - ValueMap html5DataFlavors; - private Element dragStartElement; - - protected void initDragStartMode() { - Element div = getElement(); - if (dragStartMode == HTML5) { - if (dragStartElement == null) { - dragStartElement = getDragStartElement(); - dragStartElement.setPropertyBoolean(DRAGGABLE, true); - VConsole.log("draggable = " - + dragStartElement.getPropertyBoolean(DRAGGABLE)); - hookHtml5DragStart(dragStartElement); - VConsole.log("drag start listeners hooked."); - } - } else { - dragStartElement = null; - if (div.hasAttribute(DRAGGABLE)) { - div.removeAttribute(DRAGGABLE); - } - } - } - - protected Element getDragStartElement() { - return getElement(); - } - - private boolean uploading; - - private ReadyStateChangeHandler readyStateChangeHandler = new ReadyStateChangeHandler() { - public void onReadyStateChange(XMLHttpRequest xhr) { - if (xhr.getReadyState() == XMLHttpRequest.DONE) { - // visit server for possible - // variable changes - client.sendPendingVariableChanges(); - uploading = false; - startNextUpload(); - xhr.clearOnReadyStateChange(); - } - } - }; - private Timer dragleavetimer; - - void startNextUpload() { - Scheduler.get().scheduleDeferred(new Command() { - - public void execute() { - if (!uploading) { - if (fileIds.size() > 0) { - - uploading = true; - final Integer fileId = fileIds.remove(0); - VHtml5File file = files.remove(0); - final String receiverUrl = client - .translateVaadinUri(fileIdToReceiver - .remove(fileId.toString())); - ExtendedXHR extendedXHR = (ExtendedXHR) ExtendedXHR - .create(); - extendedXHR - .setOnReadyStateChange(readyStateChangeHandler); - extendedXHR.open("POST", receiverUrl); - extendedXHR.postFile(file); - } - } - - } - }); - - } - - public boolean html5DragStart(VHtml5DragEvent event) { - if (dragStartMode == HTML5) { - /* - * Populate html5 payload with dataflavors from the serverside - */ - JsArrayString flavors = html5DataFlavors.getKeyArray(); - for (int i = 0; i < flavors.length(); i++) { - String flavor = flavors.get(i); - event.setHtml5DataFlavor(flavor, - html5DataFlavors.getString(flavor)); - } - event.setEffectAllowed("copy"); - return true; - } - return false; - } - - public boolean html5DragEnter(VHtml5DragEvent event) { - if (dropHandler == null) { - return true; - } - try { - if (dragleavetimer != null) { - // returned quickly back to wrapper - dragleavetimer.cancel(); - dragleavetimer = null; - } - if (VDragAndDropManager.get().getCurrentDropHandler() != getDropHandler()) { - VTransferable transferable = new VTransferable(); - transferable.setDragSource(ConnectorMap.get(client) - .getConnector(this)); - - vaadinDragEvent = VDragAndDropManager.get().startDrag( - transferable, event, false); - VDragAndDropManager.get().setCurrentDropHandler( - getDropHandler()); - } - try { - event.preventDefault(); - event.stopPropagation(); - } catch (Exception e) { - // VConsole.log("IE9 fails"); - } - return false; - } catch (Exception e) { - GWT.getUncaughtExceptionHandler().onUncaughtException(e); - return true; - } - } - - public boolean html5DragLeave(VHtml5DragEvent event) { - if (dropHandler == null) { - return true; - } - - try { - dragleavetimer = new Timer() { - @Override - public void run() { - // Yes, dragleave happens before drop. Makes no sense to me. - // IMO shouldn't fire leave at all if drop happens (I guess - // this - // is what IE does). - // In Vaadin we fire it only if drop did not happen. - if (vaadinDragEvent != null - && VDragAndDropManager.get() - .getCurrentDropHandler() == getDropHandler()) { - VDragAndDropManager.get().interruptDrag(); - } - } - }; - dragleavetimer.schedule(350); - try { - event.preventDefault(); - event.stopPropagation(); - } catch (Exception e) { - // VConsole.log("IE9 fails"); - } - return false; - } catch (Exception e) { - GWT.getUncaughtExceptionHandler().onUncaughtException(e); - return true; - } - } - - public boolean html5DragOver(VHtml5DragEvent event) { - if (dropHandler == null) { - return true; - } - - if (dragleavetimer != null) { - // returned quickly back to wrapper - dragleavetimer.cancel(); - dragleavetimer = null; - } - - vaadinDragEvent.setCurrentGwtEvent(event); - getDropHandler().dragOver(vaadinDragEvent); - - String s = event.getEffectAllowed(); - if ("all".equals(s) || s.contains("opy")) { - event.setDropEffect("copy"); - } else { - event.setDropEffect(s); - } - - try { - event.preventDefault(); - event.stopPropagation(); - } catch (Exception e) { - // VConsole.log("IE9 fails"); - } - return false; - } - - public boolean html5DragDrop(VHtml5DragEvent event) { - if (dropHandler == null || !currentlyValid) { - return true; - } - try { - - VTransferable transferable = vaadinDragEvent.getTransferable(); - - JsArrayString types = event.getTypes(); - for (int i = 0; i < types.length(); i++) { - String type = types.get(i); - if (isAcceptedType(type)) { - String data = event.getDataAsText(type); - if (data != null) { - transferable.setData(type, data); - } - } - } - - int fileCount = event.getFileCount(); - if (fileCount > 0) { - transferable.setData("filecount", fileCount); - for (int i = 0; i < fileCount; i++) { - final int fileId = filecounter++; - final VHtml5File file = event.getFile(i); - transferable.setData("fi" + i, "" + fileId); - transferable.setData("fn" + i, file.getName()); - transferable.setData("ft" + i, file.getType()); - transferable.setData("fs" + i, file.getSize()); - queueFilePost(fileId, file); - } - - } - - VDragAndDropManager.get().endDrag(); - vaadinDragEvent = null; - try { - event.preventDefault(); - event.stopPropagation(); - } catch (Exception e) { - // VConsole.log("IE9 fails"); - } - return false; - } catch (Exception e) { - GWT.getUncaughtExceptionHandler().onUncaughtException(e); - return true; - } - - } - - protected String[] acceptedTypes = new String[] { "Text", "Url", - "text/html", "text/plain", "text/rtf" }; - - private boolean isAcceptedType(String type) { - for (String t : acceptedTypes) { - if (t.equals(type)) { - return true; - } - } - return false; - } - - static class ExtendedXHR extends XMLHttpRequest { - - protected ExtendedXHR() { - } - - public final native void postFile(VHtml5File file) - /*-{ - - this.setRequestHeader('Content-Type', 'multipart/form-data'); - this.send(file); - }-*/; - - } - - /** - * Currently supports only FF36 as no other browser supports natively File - * api. - * - * @param fileId - * @param data - */ - List fileIds = new ArrayList(); - List files = new ArrayList(); - - private void queueFilePost(final int fileId, final VHtml5File file) { - fileIds.add(fileId); - files.add(file); - } - - public VDropHandler getDropHandler() { - return dropHandler; - } - - protected VerticalDropLocation verticalDropLocation; - protected HorizontalDropLocation horizontalDropLocation; - private VerticalDropLocation emphasizedVDrop; - private HorizontalDropLocation emphasizedHDrop; - - /** - * Flag used by html5 dd - */ - private boolean currentlyValid; - - private static final String OVER_STYLE = "v-ddwrapper-over"; - - public class CustomDropHandler extends VAbstractDropHandler { - - @Override - public void dragEnter(VDragEvent drag) { - updateDropDetails(drag); - currentlyValid = false; - super.dragEnter(drag); - } - - @Override - public void dragLeave(VDragEvent drag) { - deEmphasis(true); - dragleavetimer = null; - } - - @Override - public void dragOver(final VDragEvent drag) { - boolean detailsChanged = updateDropDetails(drag); - if (detailsChanged) { - currentlyValid = false; - validate(new VAcceptCallback() { - public void accepted(VDragEvent event) { - dragAccepted(drag); - } - }, drag); - } - } - - @Override - public boolean drop(VDragEvent drag) { - deEmphasis(true); - - Map dd = drag.getDropDetails(); - - // this is absolute layout based, and we may want to set - // component - // relatively to where the drag ended. - // need to add current location of the drop area - - int absoluteLeft = getAbsoluteLeft(); - int absoluteTop = getAbsoluteTop(); - - dd.put("absoluteLeft", absoluteLeft); - dd.put("absoluteTop", absoluteTop); - - if (verticalDropLocation != null) { - dd.put("verticalLocation", verticalDropLocation.toString()); - dd.put("horizontalLocation", horizontalDropLocation.toString()); - } - - return super.drop(drag); - } - - @Override - protected void dragAccepted(VDragEvent drag) { - currentlyValid = true; - emphasis(drag); - } - - @Override - public ComponentConnector getConnector() { - return ConnectorMap.get(client).getConnector( - VDragAndDropWrapper.this); - } - - public ApplicationConnection getApplicationConnection() { - return client; - } - - } - - protected native void hookHtml5DragStart(Element el) - /*-{ - var me = this; - el.addEventListener("dragstart", function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }, false); - }-*/; - - /** - * Prototype code, memory leak risk. - * - * @param el - */ - protected native void hookHtml5Events(Element el) - /*-{ - var me = this; - - el.addEventListener("dragenter", function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }, false); - - el.addEventListener("dragleave", function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }, false); - - el.addEventListener("dragover", function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }, false); - - el.addEventListener("drop", function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }, false); - }-*/; - - public boolean updateDropDetails(VDragEvent drag) { - VerticalDropLocation oldVL = verticalDropLocation; - verticalDropLocation = DDUtil.getVerticalDropLocation(getElement(), - drag.getCurrentGwtEvent(), 0.2); - drag.getDropDetails().put("verticalLocation", - verticalDropLocation.toString()); - HorizontalDropLocation oldHL = horizontalDropLocation; - horizontalDropLocation = DDUtil.getHorizontalDropLocation(getElement(), - drag.getCurrentGwtEvent(), 0.2); - drag.getDropDetails().put("horizontalLocation", - horizontalDropLocation.toString()); - if (oldHL != horizontalDropLocation || oldVL != verticalDropLocation) { - return true; - } else { - return false; - } - } - - protected void deEmphasis(boolean doLayout) { - if (emphasizedVDrop != null) { - VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, false); - VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-" - + emphasizedVDrop.toString().toLowerCase(), false); - VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-" - + emphasizedHDrop.toString().toLowerCase(), false); - } - if (doLayout) { - client.doLayout(false); - } - } - - protected void emphasis(VDragEvent drag) { - deEmphasis(false); - VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE, true); - VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-" - + verticalDropLocation.toString().toLowerCase(), true); - VDragAndDropWrapper.setStyleName(getElement(), OVER_STYLE + "-" - + horizontalDropLocation.toString().toLowerCase(), true); - emphasizedVDrop = verticalDropLocation; - emphasizedHDrop = horizontalDropLocation; - - // TODO build (to be an example) an emphasis mode where drag image - // is fitted before or after the content - client.doLayout(false); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapperIE.java b/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapperIE.java deleted file mode 100644 index 438ec49873..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VDragAndDropWrapperIE.java +++ /dev/null @@ -1,69 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.dom.client.AnchorElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.user.client.Element; -import com.vaadin.terminal.gwt.client.VConsole; - -public class VDragAndDropWrapperIE extends VDragAndDropWrapper { - private AnchorElement anchor = null; - - @Override - protected Element getDragStartElement() { - VConsole.log("IE get drag start element..."); - Element div = getElement(); - if (dragStartMode == HTML5) { - if (anchor == null) { - anchor = Document.get().createAnchorElement(); - anchor.setHref("#"); - anchor.setClassName("drag-start"); - div.appendChild(anchor); - } - VConsole.log("IE get drag start element..."); - return (Element) anchor.cast(); - } else { - if (anchor != null) { - div.removeChild(anchor); - anchor = null; - } - return div; - } - } - - @Override - protected native void hookHtml5DragStart(Element el) - /*-{ - var me = this; - - el.attachEvent("ondragstart", function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragStart(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }); - }-*/; - - @Override - protected native void hookHtml5Events(Element el) - /*-{ - var me = this; - - el.attachEvent("ondragenter", function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragEnter(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }); - - el.attachEvent("ondragleave", function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragLeave(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }); - - el.attachEvent("ondragover", function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragOver(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }); - - el.attachEvent("ondrop", function(ev) { - return me.@com.vaadin.terminal.gwt.client.ui.VDragAndDropWrapper::html5DragDrop(Lcom/vaadin/terminal/gwt/client/ui/dd/VHtml5DragEvent;)(ev); - }); - }-*/; - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VEmbedded.java b/src/com/vaadin/terminal/gwt/client/ui/VEmbedded.java deleted file mode 100644 index 26b45d10e1..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VEmbedded.java +++ /dev/null @@ -1,239 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -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.HTML; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.ConnectorMap; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VConsole; - -public class VEmbedded extends HTML { - public static String CLASSNAME = "v-embedded"; - - protected Element browserElement; - - protected String type; - - protected ApplicationConnection client; - - public VEmbedded() { - setStyleName(CLASSNAME); - } - - /** - * Creates the Object and Embed tags for the Flash plugin so it works - * cross-browser - * - * @param uidl - * The UIDL - * @return Tags concatenated into a string - */ - protected String createFlashEmbed(UIDL uidl) { - /* - * To ensure cross-browser compatibility we are using the twice-cooked - * method to embed flash i.e. we add a OBJECT tag for IE ActiveX and - * inside it a EMBED for all other browsers. - */ - - StringBuilder html = new StringBuilder(); - - // Start the object tag - html.append(""); - - // Ensure we have an movie parameter - Map parameters = getParameters(uidl); - if (parameters.get("movie") == null) { - parameters.put("movie", getSrc(uidl, client)); - } - - // Add parameters to OBJECT - for (String name : parameters.keySet()) { - html.append(""); - } - - // Build inner EMBED tag - html.append(""); - - if (uidl.hasAttribute(EmbeddedConnector.ALTERNATE_TEXT)) { - html.append(uidl - .getStringAttribute(EmbeddedConnector.ALTERNATE_TEXT)); - } - - // End object tag - html.append(""); - - return html.toString(); - } - - /** - * Returns a map (name -> value) of all parameters in the UIDL. - * - * @param uidl - * @return - */ - protected static Map getParameters(UIDL uidl) { - Map parameters = new HashMap(); - - Iterator childIterator = uidl.getChildIterator(); - while (childIterator.hasNext()) { - - Object child = childIterator.next(); - if (child instanceof UIDL) { - - UIDL childUIDL = (UIDL) child; - if (childUIDL.getTag().equals("embeddedparam")) { - String name = childUIDL.getStringAttribute("name"); - String value = childUIDL.getStringAttribute("value"); - parameters.put(name, value); - } - } - - } - - return parameters; - } - - /** - * Helper to return translated src-attribute from embedded's UIDL - * - * @param uidl - * @param client - * @return - */ - protected String getSrc(UIDL uidl, ApplicationConnection client) { - String url = client.translateVaadinUri(uidl.getStringAttribute("src")); - if (url == null) { - return ""; - } - return url; - } - - @Override - protected void onDetach() { - if (BrowserInfo.get().isIE()) { - // Force browser to fire unload event when component is detached - // from the view (IE doesn't do this automatically) - if (browserElement != null) { - /* - * src was previously set to javascript:false, but this was not - * enough to overcome a bug when detaching an iframe with a pdf - * loaded in IE9. about:blank seems to cause the adobe reader - * plugin to unload properly before the iframe is removed. See - * #7855 - */ - DOM.setElementAttribute(browserElement, "src", "about:blank"); - } - } - super.onDetach(); - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (DOM.eventGetType(event) == Event.ONLOAD) { - VConsole.log("Embeddable onload"); - Util.notifyParentOfSizeChange(this, true); - } - - client.handleTooltipEvent(event, this); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VFilterSelect.java b/src/com/vaadin/terminal/gwt/client/ui/VFilterSelect.java deleted file mode 100644 index 82bbff9d85..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VFilterSelect.java +++ /dev/null @@ -1,1671 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; -import com.google.gwt.event.dom.client.LoadEvent; -import com.google.gwt.event.dom.client.LoadHandler; -import com.google.gwt.event.logical.shared.CloseEvent; -import com.google.gwt.event.logical.shared.CloseHandler; -import com.google.gwt.user.client.Command; -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.Timer; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Image; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.PopupPanel.PositionCallback; -import com.google.gwt.user.client.ui.SuggestOracle.Suggestion; -import com.google.gwt.user.client.ui.TextBox; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.ComponentState; -import com.vaadin.terminal.gwt.client.ConnectorMap; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.Focusable; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VConsole; -import com.vaadin.terminal.gwt.client.VTooltip; - -/** - * Client side implementation of the Select component. - * - * TODO needs major refactoring (to be extensible etc) - */ -@SuppressWarnings("deprecation") -public class VFilterSelect extends Composite implements Field, KeyDownHandler, - KeyUpHandler, ClickHandler, FocusHandler, BlurHandler, Focusable { - - /** - * Represents a suggestion in the suggestion popup box - */ - public class FilterSelectSuggestion implements Suggestion, Command { - - private final String key; - private final String caption; - private String iconUri; - - /** - * Constructor - * - * @param uidl - * The UIDL recieved from the server - */ - public FilterSelectSuggestion(UIDL uidl) { - key = uidl.getStringAttribute("key"); - caption = uidl.getStringAttribute("caption"); - if (uidl.hasAttribute("icon")) { - iconUri = client.translateVaadinUri(uidl - .getStringAttribute("icon")); - } - } - - /** - * Gets the visible row in the popup as a HTML string. The string - * contains an image tag with the rows icon (if an icon has been - * specified) and the caption of the item - */ - public String getDisplayString() { - final StringBuffer sb = new StringBuffer(); - if (iconUri != null) { - sb.append("\"\""); - } - String content; - if ("".equals(caption)) { - // Ensure that empty options use the same height as other - // options and are not collapsed (#7506) - content = " "; - } else { - content = Util.escapeHTML(caption); - } - sb.append("" + content + ""); - return sb.toString(); - } - - /** - * Get a string that represents this item. This is used in the text box. - */ - public String getReplacementString() { - return caption; - } - - /** - * Get the option key which represents the item on the server side. - * - * @return The key of the item - */ - public int getOptionKey() { - return Integer.parseInt(key); - } - - /** - * Get the URI of the icon. Used when constructing the displayed option. - * - * @return - */ - public String getIconUri() { - return iconUri; - } - - /** - * Executes a selection of this item. - */ - public void execute() { - onSuggestionSelected(this); - } - } - - /** - * Represents the popup box with the selection options. Wraps a suggestion - * menu. - */ - public class SuggestionPopup extends VOverlay implements PositionCallback, - CloseHandler { - - private static final String Z_INDEX = "30000"; - - protected final SuggestionMenu menu; - - private final Element up = DOM.createDiv(); - private final Element down = DOM.createDiv(); - private final Element status = DOM.createDiv(); - - private boolean isPagingEnabled = true; - - private long lastAutoClosed; - - private int popupOuterPadding = -1; - - private int topPosition; - - /** - * Default constructor - */ - SuggestionPopup() { - super(true, false, true); - menu = new SuggestionMenu(); - setWidget(menu); - setStyleName(CLASSNAME + "-suggestpopup"); - DOM.setStyleAttribute(getElement(), "zIndex", Z_INDEX); - - final Element root = getContainerElement(); - - DOM.setInnerHTML(up, "Prev"); - DOM.sinkEvents(up, Event.ONCLICK); - DOM.setInnerHTML(down, "Next"); - DOM.sinkEvents(down, Event.ONCLICK); - DOM.insertChild(root, up, 0); - DOM.appendChild(root, down); - DOM.appendChild(root, status); - DOM.setElementProperty(status, "className", CLASSNAME + "-status"); - DOM.sinkEvents(root, Event.ONMOUSEDOWN | Event.ONMOUSEWHEEL); - addCloseHandler(this); - } - - /** - * Shows the popup where the user can see the filtered options - * - * @param currentSuggestions - * The filtered suggestions - * @param currentPage - * The current page number - * @param totalSuggestions - * The total amount of suggestions - */ - public void showSuggestions( - Collection currentSuggestions, - int currentPage, int totalSuggestions) { - - // Add TT anchor point - DOM.setElementProperty(getElement(), "id", - "VAADIN_COMBOBOX_OPTIONLIST"); - - menu.setSuggestions(currentSuggestions); - final int x = VFilterSelect.this.getAbsoluteLeft(); - topPosition = tb.getAbsoluteTop(); - topPosition += tb.getOffsetHeight(); - setPopupPosition(x, topPosition); - - int nullOffset = (nullSelectionAllowed && "".equals(lastFilter) ? 1 - : 0); - boolean firstPage = (currentPage == 0); - final int first = currentPage * pageLength + 1 - - (firstPage ? 0 : nullOffset); - final int last = first + currentSuggestions.size() - 1 - - (firstPage && "".equals(lastFilter) ? nullOffset : 0); - final int matches = totalSuggestions - nullOffset; - if (last > 0) { - // nullsel not counted, as requested by user - DOM.setInnerText(status, (matches == 0 ? 0 : first) + "-" - + last + "/" + matches); - } else { - DOM.setInnerText(status, ""); - } - // We don't need to show arrows or statusbar if there is only one - // page - if (totalSuggestions <= pageLength || pageLength == 0) { - setPagingEnabled(false); - } else { - setPagingEnabled(true); - } - setPrevButtonActive(first > 1); - setNextButtonActive(last < matches); - - // clear previously fixed width - menu.setWidth(""); - DOM.setStyleAttribute(DOM.getFirstChild(menu.getElement()), - "width", ""); - - setPopupPositionAndShow(this); - - } - - /** - * Should the next page button be visible to the user? - * - * @param active - */ - private void setNextButtonActive(boolean active) { - if (active) { - DOM.sinkEvents(down, Event.ONCLICK); - DOM.setElementProperty(down, "className", CLASSNAME - + "-nextpage"); - } else { - DOM.sinkEvents(down, 0); - DOM.setElementProperty(down, "className", CLASSNAME - + "-nextpage-off"); - } - } - - /** - * Should the previous page button be visible to the user - * - * @param active - */ - private void setPrevButtonActive(boolean active) { - if (active) { - DOM.sinkEvents(up, Event.ONCLICK); - DOM.setElementProperty(up, "className", CLASSNAME + "-prevpage"); - } else { - DOM.sinkEvents(up, 0); - DOM.setElementProperty(up, "className", CLASSNAME - + "-prevpage-off"); - } - - } - - /** - * Selects the next item in the filtered selections - */ - public void selectNextItem() { - final MenuItem cur = menu.getSelectedItem(); - final int index = 1 + menu.getItems().indexOf(cur); - if (menu.getItems().size() > index) { - final MenuItem newSelectedItem = menu.getItems().get(index); - menu.selectItem(newSelectedItem); - tb.setText(newSelectedItem.getText()); - tb.setSelectionRange(lastFilter.length(), newSelectedItem - .getText().length() - lastFilter.length()); - - } else if (hasNextPage()) { - selectPopupItemWhenResponseIsReceived = Select.FIRST; - filterOptions(currentPage + 1, lastFilter); - } - } - - /** - * Selects the previous item in the filtered selections - */ - public void selectPrevItem() { - final MenuItem cur = menu.getSelectedItem(); - final int index = -1 + menu.getItems().indexOf(cur); - if (index > -1) { - final MenuItem newSelectedItem = menu.getItems().get(index); - menu.selectItem(newSelectedItem); - tb.setText(newSelectedItem.getText()); - tb.setSelectionRange(lastFilter.length(), newSelectedItem - .getText().length() - lastFilter.length()); - } else if (index == -1) { - if (currentPage > 0) { - selectPopupItemWhenResponseIsReceived = Select.LAST; - filterOptions(currentPage - 1, lastFilter); - } - } else { - final MenuItem newSelectedItem = menu.getItems().get( - menu.getItems().size() - 1); - menu.selectItem(newSelectedItem); - tb.setText(newSelectedItem.getText()); - tb.setSelectionRange(lastFilter.length(), newSelectedItem - .getText().length() - lastFilter.length()); - } - } - - /* - * Using a timer to scroll up or down the pages so when we receive lots - * of consecutive mouse wheel events the pages does not flicker. - */ - private LazyPageScroller lazyPageScroller = new LazyPageScroller(); - - private class LazyPageScroller extends Timer { - private int pagesToScroll = 0; - - @Override - public void run() { - if (pagesToScroll != 0) { - if (!waitingForFilteringResponse) { - /* - * Avoid scrolling while we are waiting for a response - * because otherwise the waiting flag will be reset in - * the first response and the second response will be - * ignored, causing an empty popup... - * - * As long as the scrolling delay is suitable - * double/triple clicks will work by scrolling two or - * three pages at a time and this should not be a - * problem. - */ - filterOptions(currentPage + pagesToScroll, lastFilter); - } - pagesToScroll = 0; - } - } - - public void scrollUp() { - if (currentPage + pagesToScroll > 0) { - pagesToScroll--; - cancel(); - schedule(200); - } - } - - public void scrollDown() { - if (totalMatches > (currentPage + pagesToScroll + 1) - * pageLength) { - pagesToScroll++; - cancel(); - schedule(200); - } - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt - * .user.client.Event) - */ - @Override - public void onBrowserEvent(Event event) { - if (event.getTypeInt() == Event.ONCLICK) { - final Element target = DOM.eventGetTarget(event); - if (target == up || target == DOM.getChild(up, 0)) { - lazyPageScroller.scrollUp(); - } else if (target == down || target == DOM.getChild(down, 0)) { - lazyPageScroller.scrollDown(); - } - } else if (event.getTypeInt() == Event.ONMOUSEWHEEL) { - int velocity = event.getMouseWheelVelocityY(); - if (velocity > 0) { - lazyPageScroller.scrollDown(); - } else { - lazyPageScroller.scrollUp(); - } - } - - /* - * Prevent the keyboard focus from leaving the textfield by - * preventing the default behaviour of the browser. Fixes #4285. - */ - handleMouseDownEvent(event); - } - - /** - * Should paging be enabled. If paging is enabled then only a certain - * amount of items are visible at a time and a scrollbar or buttons are - * visible to change page. If paging is turned of then all options are - * rendered into the popup menu. - * - * @param paging - * Should the paging be turned on? - */ - public void setPagingEnabled(boolean paging) { - if (isPagingEnabled == paging) { - return; - } - if (paging) { - DOM.setStyleAttribute(down, "display", ""); - DOM.setStyleAttribute(up, "display", ""); - DOM.setStyleAttribute(status, "display", ""); - } else { - DOM.setStyleAttribute(down, "display", "none"); - DOM.setStyleAttribute(up, "display", "none"); - DOM.setStyleAttribute(status, "display", "none"); - } - isPagingEnabled = paging; - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.PopupPanel$PositionCallback#setPosition - * (int, int) - */ - public void setPosition(int offsetWidth, int offsetHeight) { - - int top = -1; - int left = -1; - - // reset menu size and retrieve its "natural" size - menu.setHeight(""); - if (currentPage > 0) { - // fix height to avoid height change when getting to last page - menu.fixHeightTo(pageLength); - } - offsetHeight = getOffsetHeight(); - - final int desiredWidth = getMainWidth(); - int naturalMenuWidth = DOM.getElementPropertyInt( - DOM.getFirstChild(menu.getElement()), "offsetWidth"); - - if (popupOuterPadding == -1) { - popupOuterPadding = Util.measureHorizontalPaddingAndBorder( - getElement(), 2); - } - - if (naturalMenuWidth < desiredWidth) { - menu.setWidth((desiredWidth - popupOuterPadding) + "px"); - DOM.setStyleAttribute(DOM.getFirstChild(menu.getElement()), - "width", "100%"); - naturalMenuWidth = desiredWidth; - } - - if (BrowserInfo.get().isIE()) { - /* - * IE requires us to specify the width for the container - * element. Otherwise it will be 100% wide - */ - int rootWidth = naturalMenuWidth - popupOuterPadding; - DOM.setStyleAttribute(getContainerElement(), "width", rootWidth - + "px"); - } - - if (offsetHeight + getPopupTop() > Window.getClientHeight() - + Window.getScrollTop()) { - // popup on top of input instead - top = getPopupTop() - offsetHeight - - VFilterSelect.this.getOffsetHeight(); - if (top < 0) { - top = 0; - } - } else { - top = getPopupTop(); - /* - * Take popup top margin into account. getPopupTop() returns the - * top value including the margin but the value we give must not - * include the margin. - */ - int topMargin = (top - topPosition); - top -= topMargin; - } - - // fetch real width (mac FF bugs here due GWT popups overflow:auto ) - offsetWidth = DOM.getElementPropertyInt( - DOM.getFirstChild(menu.getElement()), "offsetWidth"); - if (offsetWidth + getPopupLeft() > Window.getClientWidth() - + Window.getScrollLeft()) { - left = VFilterSelect.this.getAbsoluteLeft() - + VFilterSelect.this.getOffsetWidth() - + Window.getScrollLeft() - offsetWidth; - if (left < 0) { - left = 0; - } - } else { - left = getPopupLeft(); - } - setPopupPosition(left, top); - } - - /** - * Was the popup just closed? - * - * @return true if popup was just closed - */ - public boolean isJustClosed() { - final long now = (new Date()).getTime(); - return (lastAutoClosed > 0 && (now - lastAutoClosed) < 200); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google - * .gwt.event.logical.shared.CloseEvent) - */ - @Override - public void onClose(CloseEvent event) { - if (event.isAutoClosed()) { - lastAutoClosed = (new Date()).getTime(); - } - } - - /** - * Updates style names in suggestion popup to help theme building. - * - * @param uidl - * UIDL for the whole combo box - * @param componentState - * shared state of the combo box - */ - public void updateStyleNames(UIDL uidl, ComponentState componentState) { - setStyleName(CLASSNAME + "-suggestpopup"); - if (componentState.hasStyles()) { - for (String style : componentState.getStyles()) { - if (!"".equals(style)) { - addStyleDependentName(style); - } - } - } - } - - } - - /** - * The menu where the suggestions are rendered - */ - public class SuggestionMenu extends MenuBar implements SubPartAware, - LoadHandler { - - /** - * Tracks the item that is currently selected using the keyboard. This - * is need only because mouseover changes the selection and we do not - * want to use that selection when pressing enter to select the item. - */ - private MenuItem keyboardSelectedItem; - - private VLazyExecutor delayedImageLoadExecutioner = new VLazyExecutor( - 100, new ScheduledCommand() { - - public void execute() { - if (suggestionPopup.isVisible() - && suggestionPopup.isAttached()) { - setWidth(""); - DOM.setStyleAttribute( - DOM.getFirstChild(getElement()), "width", - ""); - suggestionPopup - .setPopupPositionAndShow(suggestionPopup); - } - - } - }); - - /** - * Default constructor - */ - SuggestionMenu() { - super(true); - setStyleName(CLASSNAME + "-suggestmenu"); - addDomHandler(this, LoadEvent.getType()); - } - - /** - * Fixes menus height to use same space as full page would use. Needed - * to avoid height changes when quickly "scrolling" to last page - */ - public void fixHeightTo(int pagelenth) { - if (currentSuggestions.size() > 0) { - final int pixels = pagelenth * (getOffsetHeight() - 2) - / currentSuggestions.size(); - setHeight((pixels + 2) + "px"); - } - } - - /** - * Sets the suggestions rendered in the menu - * - * @param suggestions - * The suggestions to be rendered in the menu - */ - public void setSuggestions( - Collection suggestions) { - // Reset keyboard selection when contents is updated to avoid - // reusing old, invalid data - setKeyboardSelectedItem(null); - - clearItems(); - final Iterator it = suggestions.iterator(); - while (it.hasNext()) { - final FilterSelectSuggestion s = it.next(); - final MenuItem mi = new MenuItem(s.getDisplayString(), true, s); - - Util.sinkOnloadForImages(mi.getElement()); - - this.addItem(mi); - if (s == currentSuggestion) { - selectItem(mi); - } - } - } - - /** - * Send the current selection to the server. Triggered when a selection - * is made or on a blur event. - */ - public void doSelectedItemAction() { - // do not send a value change event if null was and stays selected - final String enteredItemValue = tb.getText(); - if (nullSelectionAllowed && "".equals(enteredItemValue) - && selectedOptionKey != null - && !"".equals(selectedOptionKey)) { - if (nullSelectItem) { - reset(); - return; - } - // null is not visible on pages != 0, and not visible when - // filtering: handle separately - client.updateVariable(paintableId, "filter", "", false); - client.updateVariable(paintableId, "page", 0, false); - client.updateVariable(paintableId, "selected", new String[] {}, - immediate); - suggestionPopup.hide(); - return; - } - - updateSelectionWhenReponseIsReceived = waitingForFilteringResponse; - if (!waitingForFilteringResponse) { - doPostFilterSelectedItemAction(); - } - } - - /** - * Triggered after a selection has been made - */ - public void doPostFilterSelectedItemAction() { - final MenuItem item = getSelectedItem(); - final String enteredItemValue = tb.getText(); - - updateSelectionWhenReponseIsReceived = false; - - // check for exact match in menu - int p = getItems().size(); - if (p > 0) { - for (int i = 0; i < p; i++) { - final MenuItem potentialExactMatch = getItems().get(i); - if (potentialExactMatch.getText().equals(enteredItemValue)) { - selectItem(potentialExactMatch); - // do not send a value change event if null was and - // stays selected - if (!"".equals(enteredItemValue) - || (selectedOptionKey != null && !"" - .equals(selectedOptionKey))) { - doItemAction(potentialExactMatch, true); - } - suggestionPopup.hide(); - return; - } - } - } - if (allowNewItem) { - - if (!prompting && !enteredItemValue.equals(lastNewItemString)) { - /* - * Store last sent new item string to avoid double sends - */ - lastNewItemString = enteredItemValue; - client.updateVariable(paintableId, "newitem", - enteredItemValue, immediate); - } - } else if (item != null - && !"".equals(lastFilter) - && (filteringmode == FILTERINGMODE_CONTAINS ? item - .getText().toLowerCase() - .contains(lastFilter.toLowerCase()) : item - .getText().toLowerCase() - .startsWith(lastFilter.toLowerCase()))) { - doItemAction(item, true); - } else { - // currentSuggestion has key="" for nullselection - if (currentSuggestion != null - && !currentSuggestion.key.equals("")) { - // An item (not null) selected - String text = currentSuggestion.getReplacementString(); - tb.setText(text); - selectedOptionKey = currentSuggestion.key; - } else { - // Null selected - tb.setText(""); - selectedOptionKey = null; - } - } - suggestionPopup.hide(); - } - - private static final String SUBPART_PREFIX = "item"; - - public Element getSubPartElement(String subPart) { - int index = Integer.parseInt(subPart.substring(SUBPART_PREFIX - .length())); - - MenuItem item = getItems().get(index); - - return item.getElement(); - } - - public String getSubPartName(Element subElement) { - if (!getElement().isOrHasChild(subElement)) { - return null; - } - - Element menuItemRoot = subElement; - while (menuItemRoot != null - && !menuItemRoot.getTagName().equalsIgnoreCase("td")) { - menuItemRoot = menuItemRoot.getParentElement().cast(); - } - // "menuItemRoot" is now the root of the menu item - - final int itemCount = getItems().size(); - for (int i = 0; i < itemCount; i++) { - if (getItems().get(i).getElement() == menuItemRoot) { - String name = SUBPART_PREFIX + i; - return name; - } - } - return null; - } - - public void onLoad(LoadEvent event) { - // Handle icon onload events to ensure shadow is resized - // correctly - delayedImageLoadExecutioner.trigger(); - - } - - public void selectFirstItem() { - MenuItem firstItem = getItems().get(0); - selectItem(firstItem); - } - - private MenuItem getKeyboardSelectedItem() { - return keyboardSelectedItem; - } - - protected void setKeyboardSelectedItem(MenuItem firstItem) { - keyboardSelectedItem = firstItem; - } - - public void selectLastItem() { - List items = getItems(); - MenuItem lastItem = items.get(items.size() - 1); - selectItem(lastItem); - } - } - - public static final int FILTERINGMODE_OFF = 0; - public static final int FILTERINGMODE_STARTSWITH = 1; - public static final int FILTERINGMODE_CONTAINS = 2; - - private static final String CLASSNAME = "v-filterselect"; - private static final String STYLE_NO_INPUT = "no-input"; - - protected int pageLength = 10; - - private boolean enableDebug = false; - - private final FlowPanel panel = new FlowPanel(); - - /** - * The text box where the filter is written - */ - protected final TextBox tb = new TextBox() { - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.TextBoxBase#onBrowserEvent(com.google - * .gwt.user.client.Event) - */ - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (client != null) { - client.handleTooltipEvent(event, VFilterSelect.this); - } - } - - @Override - // Overridden to avoid selecting text when text input is disabled - public void setSelectionRange(int pos, int length) { - if (textInputEnabled) { - super.setSelectionRange(pos, length); - } else { - super.setSelectionRange(getValue().length(), 0); - } - }; - }; - - protected final SuggestionPopup suggestionPopup = new SuggestionPopup(); - - /** - * Used when measuring the width of the popup - */ - private final HTML popupOpener = new HTML("") { - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt - * .user.client.Event) - */ - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (client != null) { - client.handleTooltipEvent(event, VFilterSelect.this); - } - - /* - * Prevent the keyboard focus from leaving the textfield by - * preventing the default behaviour of the browser. Fixes #4285. - */ - handleMouseDownEvent(event); - } - }; - - private final Image selectedItemIcon = new Image(); - - protected ApplicationConnection client; - - protected String paintableId; - - protected int currentPage; - - /** - * A collection of available suggestions (options) as received from the - * server. - */ - protected final List currentSuggestions = new ArrayList(); - - protected boolean immediate; - - protected String selectedOptionKey; - - protected boolean waitingForFilteringResponse = false; - protected boolean updateSelectionWhenReponseIsReceived = false; - private boolean tabPressedWhenPopupOpen = false; - protected boolean initDone = false; - - protected String lastFilter = ""; - - protected enum Select { - NONE, FIRST, LAST - }; - - protected Select selectPopupItemWhenResponseIsReceived = Select.NONE; - - /** - * The current suggestion selected from the dropdown. This is one of the - * values in currentSuggestions except when filtering, in this case - * currentSuggestion might not be in currentSuggestions. - */ - protected FilterSelectSuggestion currentSuggestion; - - protected int totalMatches; - protected boolean allowNewItem; - protected boolean nullSelectionAllowed; - protected boolean nullSelectItem; - protected boolean enabled; - protected boolean readonly; - - protected int filteringmode = FILTERINGMODE_OFF; - - // shown in unfocused empty field, disappears on focus (e.g "Search here") - private static final String CLASSNAME_PROMPT = "prompt"; - protected static final String ATTR_INPUTPROMPT = "prompt"; - public static final String ATTR_NO_TEXT_INPUT = "noInput"; - protected String inputPrompt = ""; - protected boolean prompting = false; - - // Set true when popupopened has been clicked. Cleared on each UIDL-update. - // This handles the special case where are not filtering yet and the - // selected value has changed on the server-side. See #2119 - protected boolean popupOpenerClicked; - protected int suggestionPopupMinWidth = 0; - private int popupWidth = -1; - /* - * Stores the last new item string to avoid double submissions. Cleared on - * uidl updates - */ - protected String lastNewItemString; - protected boolean focused = false; - - /** - * If set to false, the component should not allow entering text to the - * field even for filtering. - */ - private boolean textInputEnabled = true; - - /** - * Default constructor - */ - public VFilterSelect() { - selectedItemIcon.setStyleName("v-icon"); - selectedItemIcon.addLoadHandler(new LoadHandler() { - public void onLoad(LoadEvent event) { - if (BrowserInfo.get().isIE8()) { - // IE8 needs some help to discover it should reposition the - // text field - forceReflow(); - } - updateRootWidth(); - updateSelectedIconPosition(); - } - }); - - tb.sinkEvents(VTooltip.TOOLTIP_EVENTS); - popupOpener.sinkEvents(VTooltip.TOOLTIP_EVENTS | Event.ONMOUSEDOWN); - panel.add(tb); - panel.add(popupOpener); - initWidget(panel); - setStyleName(CLASSNAME); - tb.addKeyDownHandler(this); - tb.addKeyUpHandler(this); - tb.setStyleName(CLASSNAME + "-input"); - tb.addFocusHandler(this); - tb.addBlurHandler(this); - tb.addClickHandler(this); - popupOpener.setStyleName(CLASSNAME + "-button"); - popupOpener.addClickHandler(this); - } - - /** - * Does the Select have more pages? - * - * @return true if a next page exists, else false if the current page is the - * last page - */ - public boolean hasNextPage() { - if (totalMatches > (currentPage + 1) * pageLength) { - return true; - } else { - return false; - } - } - - /** - * Filters the options at a certain page. Uses the text box input as a - * filter - * - * @param page - * The page which items are to be filtered - */ - public void filterOptions(int page) { - filterOptions(page, tb.getText()); - } - - /** - * Filters the options at certain page using the given filter - * - * @param page - * The page to filter - * @param filter - * The filter to apply to the components - */ - public void filterOptions(int page, String filter) { - filterOptions(page, filter, true); - } - - /** - * Filters the options at certain page using the given filter - * - * @param page - * The page to filter - * @param filter - * The filter to apply to the options - * @param immediate - * Whether to send the options request immediately - */ - private void filterOptions(int page, String filter, boolean immediate) { - if (filter.equals(lastFilter) && currentPage == page) { - if (!suggestionPopup.isAttached()) { - suggestionPopup.showSuggestions(currentSuggestions, - currentPage, totalMatches); - } - return; - } - if (!filter.equals(lastFilter)) { - // we are on subsequent page and text has changed -> reset page - if ("".equals(filter)) { - // let server decide - page = -1; - } else { - page = 0; - } - } - - waitingForFilteringResponse = true; - client.updateVariable(paintableId, "filter", filter, false); - client.updateVariable(paintableId, "page", page, immediate); - lastFilter = filter; - currentPage = page; - } - - protected void updateReadOnly() { - tb.setReadOnly(readonly || !textInputEnabled); - } - - protected void setTextInputEnabled(boolean textInputEnabled) { - // Always update styles as they might have been overwritten - if (textInputEnabled) { - removeStyleDependentName(STYLE_NO_INPUT); - } else { - addStyleDependentName(STYLE_NO_INPUT); - } - - if (this.textInputEnabled == textInputEnabled) { - return; - } - - this.textInputEnabled = textInputEnabled; - updateReadOnly(); - } - - /** - * Sets the text in the text box. - * - * @param text - * the text to set in the text box - */ - protected void setTextboxText(final String text) { - tb.setText(text); - } - - /** - * Turns prompting on. When prompting is turned on a command prompt is shown - * in the text box if nothing has been entered. - */ - protected void setPromptingOn() { - if (!prompting) { - prompting = true; - addStyleDependentName(CLASSNAME_PROMPT); - } - setTextboxText(inputPrompt); - } - - /** - * Turns prompting off. When prompting is turned on a command prompt is - * shown in the text box if nothing has been entered. - * - * @param text - * The text the text box should contain. - */ - protected void setPromptingOff(String text) { - setTextboxText(text); - if (prompting) { - prompting = false; - removeStyleDependentName(CLASSNAME_PROMPT); - } - } - - /** - * Triggered when a suggestion is selected - * - * @param suggestion - * The suggestion that just got selected. - */ - public void onSuggestionSelected(FilterSelectSuggestion suggestion) { - updateSelectionWhenReponseIsReceived = false; - - currentSuggestion = suggestion; - String newKey; - if (suggestion.key.equals("")) { - // "nullselection" - newKey = ""; - } else { - // normal selection - newKey = String.valueOf(suggestion.getOptionKey()); - } - - String text = suggestion.getReplacementString(); - if ("".equals(newKey) && !focused) { - setPromptingOn(); - } else { - setPromptingOff(text); - } - setSelectedItemIcon(suggestion.getIconUri()); - if (!(newKey.equals(selectedOptionKey) || ("".equals(newKey) && selectedOptionKey == null))) { - selectedOptionKey = newKey; - client.updateVariable(paintableId, "selected", - new String[] { selectedOptionKey }, immediate); - // currentPage = -1; // forget the page - } - suggestionPopup.hide(); - } - - /** - * Sets the icon URI of the selected item. The icon is shown on the left - * side of the item caption text. Set the URI to null to remove the icon. - * - * @param iconUri - * The URI of the icon - */ - protected void setSelectedItemIcon(String iconUri) { - if (iconUri == null || iconUri.length() == 0) { - if (selectedItemIcon.isAttached()) { - panel.remove(selectedItemIcon); - if (BrowserInfo.get().isIE8()) { - // IE8 needs some help to discover it should reposition the - // text field - forceReflow(); - } - updateRootWidth(); - } - } else { - panel.insert(selectedItemIcon, 0); - selectedItemIcon.setUrl(iconUri); - updateRootWidth(); - updateSelectedIconPosition(); - } - } - - private void forceReflow() { - Util.setStyleTemporarily(tb.getElement(), "zoom", "1"); - } - - /** - * Positions the icon vertically in the middle. Should be called after the - * icon has loaded - */ - private void updateSelectedIconPosition() { - // Position icon vertically to middle - int availableHeight = 0; - availableHeight = getOffsetHeight(); - - int iconHeight = Util.getRequiredHeight(selectedItemIcon); - int marginTop = (availableHeight - iconHeight) / 2; - DOM.setStyleAttribute(selectedItemIcon.getElement(), "marginTop", - marginTop + "px"); - } - - private static Set navigationKeyCodes = new HashSet(); - static { - navigationKeyCodes.add(KeyCodes.KEY_DOWN); - navigationKeyCodes.add(KeyCodes.KEY_UP); - navigationKeyCodes.add(KeyCodes.KEY_PAGEDOWN); - navigationKeyCodes.add(KeyCodes.KEY_PAGEUP); - navigationKeyCodes.add(KeyCodes.KEY_ENTER); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt - * .event.dom.client.KeyDownEvent) - */ - public void onKeyDown(KeyDownEvent event) { - if (enabled && !readonly) { - int keyCode = event.getNativeKeyCode(); - - debug("key down: " + keyCode); - if (waitingForFilteringResponse - && navigationKeyCodes.contains(keyCode)) { - /* - * Keyboard navigation events should not be handled while we are - * waiting for a response. This avoids flickering, disappearing - * items, wrongly interpreted responses and more. - */ - debug("Ignoring " + keyCode - + " because we are waiting for a filtering response"); - DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); - event.stopPropagation(); - return; - } - - if (suggestionPopup.isAttached()) { - debug("Keycode " + keyCode + " target is popup"); - popupKeyDown(event); - } else { - debug("Keycode " + keyCode + " target is text field"); - inputFieldKeyDown(event); - } - } - } - - private void debug(String string) { - if (enableDebug) { - VConsole.error(string); - } - } - - /** - * Triggered when a key is pressed in the text box - * - * @param event - * The KeyDownEvent - */ - private void inputFieldKeyDown(KeyDownEvent event) { - switch (event.getNativeKeyCode()) { - case KeyCodes.KEY_DOWN: - case KeyCodes.KEY_UP: - case KeyCodes.KEY_PAGEDOWN: - case KeyCodes.KEY_PAGEUP: - // open popup as from gadget - filterOptions(-1, ""); - lastFilter = ""; - tb.selectAll(); - break; - case KeyCodes.KEY_ENTER: - /* - * This only handles the case when new items is allowed, a text is - * entered, the popup opener button is clicked to close the popup - * and enter is then pressed (see #7560). - */ - if (!allowNewItem) { - return; - } - - if (currentSuggestion != null - && tb.getText().equals( - currentSuggestion.getReplacementString())) { - // Retain behavior from #6686 by returning without stopping - // propagation if there's nothing to do - return; - } - suggestionPopup.menu.doSelectedItemAction(); - - event.stopPropagation(); - break; - } - - } - - /** - * Triggered when a key was pressed in the suggestion popup. - * - * @param event - * The KeyDownEvent of the key - */ - private void popupKeyDown(KeyDownEvent event) { - // Propagation of handled events is stopped so other handlers such as - // shortcut key handlers do not also handle the same events. - switch (event.getNativeKeyCode()) { - case KeyCodes.KEY_DOWN: - suggestionPopup.selectNextItem(); - suggestionPopup.menu.setKeyboardSelectedItem(suggestionPopup.menu - .getSelectedItem()); - DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); - event.stopPropagation(); - break; - case KeyCodes.KEY_UP: - suggestionPopup.selectPrevItem(); - suggestionPopup.menu.setKeyboardSelectedItem(suggestionPopup.menu - .getSelectedItem()); - DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); - event.stopPropagation(); - break; - case KeyCodes.KEY_PAGEDOWN: - if (hasNextPage()) { - filterOptions(currentPage + 1, lastFilter); - } - event.stopPropagation(); - break; - case KeyCodes.KEY_PAGEUP: - if (currentPage > 0) { - filterOptions(currentPage - 1, lastFilter); - } - event.stopPropagation(); - break; - case KeyCodes.KEY_TAB: - tabPressedWhenPopupOpen = true; - filterOptions(currentPage); - // onBlur() takes care of the rest - break; - case KeyCodes.KEY_ESCAPE: - reset(); - event.stopPropagation(); - break; - case KeyCodes.KEY_ENTER: - if (suggestionPopup.menu.getKeyboardSelectedItem() == null) { - /* - * Nothing selected using up/down. Happens e.g. when entering a - * text (causes popup to open) and then pressing enter. - */ - if (!allowNewItem) { - /* - * New items are not allowed: If there is only one - * suggestion, select that. Otherwise do nothing. - */ - if (currentSuggestions.size() == 1) { - onSuggestionSelected(currentSuggestions.get(0)); - } - } else { - // Handle addition of new items. - suggestionPopup.menu.doSelectedItemAction(); - } - } else { - /* - * Get the suggestion that was navigated to using up/down. - */ - currentSuggestion = ((FilterSelectSuggestion) suggestionPopup.menu - .getKeyboardSelectedItem().getCommand()); - onSuggestionSelected(currentSuggestion); - } - - event.stopPropagation(); - break; - } - - } - - /** - * Triggered when a key was depressed - * - * @param event - * The KeyUpEvent of the key depressed - */ - public void onKeyUp(KeyUpEvent event) { - if (enabled && !readonly) { - switch (event.getNativeKeyCode()) { - case KeyCodes.KEY_ENTER: - case KeyCodes.KEY_TAB: - case KeyCodes.KEY_SHIFT: - case KeyCodes.KEY_CTRL: - case KeyCodes.KEY_ALT: - case KeyCodes.KEY_DOWN: - case KeyCodes.KEY_UP: - case KeyCodes.KEY_PAGEDOWN: - case KeyCodes.KEY_PAGEUP: - case KeyCodes.KEY_ESCAPE: - ; // NOP - break; - default: - if (textInputEnabled) { - filterOptions(currentPage); - } - break; - } - } - } - - /** - * Resets the Select to its initial state - */ - private void reset() { - if (currentSuggestion != null) { - String text = currentSuggestion.getReplacementString(); - setPromptingOff(text); - selectedOptionKey = currentSuggestion.key; - } else { - if (focused) { - setPromptingOff(""); - } else { - setPromptingOn(); - } - selectedOptionKey = null; - } - lastFilter = ""; - suggestionPopup.hide(); - } - - /** - * Listener for popupopener - */ - public void onClick(ClickEvent event) { - if (textInputEnabled - && event.getNativeEvent().getEventTarget().cast() == tb - .getElement()) { - // Don't process clicks on the text field if text input is enabled - return; - } - if (enabled && !readonly) { - // ask suggestionPopup if it was just closed, we are using GWT - // Popup's auto close feature - if (!suggestionPopup.isJustClosed()) { - // If a focus event is not going to be sent, send the options - // request immediately; otherwise queue in the same burst as the - // focus event. Fixes #8321. - boolean immediate = focused - || !client.hasEventListeners(this, EventId.FOCUS); - filterOptions(-1, "", immediate); - popupOpenerClicked = true; - lastFilter = ""; - } - DOM.eventPreventDefault(DOM.eventGetCurrentEvent()); - focus(); - tb.selectAll(); - } - } - - /** - * Calculate minimum width for FilterSelect textarea - */ - protected native int minWidth(String captions) - /*-{ - if(!captions || captions.length <= 0) - return 0; - captions = captions.split("|"); - var d = $wnd.document.createElement("div"); - var html = ""; - for(var i=0; i < captions.length; i++) { - html += "
" + captions[i] + "
"; - // TODO apply same CSS classname as in suggestionmenu - } - d.style.position = "absolute"; - d.style.top = "0"; - d.style.left = "0"; - d.style.visibility = "hidden"; - d.innerHTML = html; - $wnd.document.body.appendChild(d); - var w = d.offsetWidth; - $wnd.document.body.removeChild(d); - return w; - }-*/; - - /** - * A flag which prevents a focus event from taking place - */ - boolean iePreventNextFocus = false; - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event - * .dom.client.FocusEvent) - */ - public void onFocus(FocusEvent event) { - - /* - * When we disable a blur event in ie we need to refocus the textfield. - * This will cause a focus event we do not want to process, so in that - * case we just ignore it. - */ - if (BrowserInfo.get().isIE() && iePreventNextFocus) { - iePreventNextFocus = false; - return; - } - - focused = true; - if (prompting && !readonly) { - setPromptingOff(""); - } - addStyleDependentName("focus"); - - if (client.hasEventListeners(this, EventId.FOCUS)) { - client.updateVariable(paintableId, EventId.FOCUS, "", true); - } - } - - /** - * A flag which cancels the blur event and sets the focus back to the - * textfield if the Browser is IE - */ - boolean preventNextBlurEventInIE = false; - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event - * .dom.client.BlurEvent) - */ - - public void onBlur(BlurEvent event) { - - if (BrowserInfo.get().isIE() && preventNextBlurEventInIE) { - /* - * Clicking in the suggestion popup or on the popup button in IE - * causes a blur event to be sent for the field. In other browsers - * this is prevented by canceling/preventing default behavior for - * the focus event, in IE we handle it here by refocusing the text - * field and ignoring the resulting focus event for the textfield - * (in onFocus). - */ - preventNextBlurEventInIE = false; - - Element focusedElement = Util.getIEFocusedElement(); - if (getElement().isOrHasChild(focusedElement) - || suggestionPopup.getElement() - .isOrHasChild(focusedElement)) { - - // IF the suggestion popup or another part of the VFilterSelect - // was focused, move the focus back to the textfield and prevent - // the triggered focus event (in onFocus). - iePreventNextFocus = true; - tb.setFocus(true); - return; - } - } - - focused = false; - if (!readonly) { - // much of the TAB handling takes place here - if (tabPressedWhenPopupOpen) { - tabPressedWhenPopupOpen = false; - suggestionPopup.menu.doSelectedItemAction(); - suggestionPopup.hide(); - } else if (!suggestionPopup.isAttached() - || suggestionPopup.isJustClosed()) { - suggestionPopup.menu.doSelectedItemAction(); - } - if (selectedOptionKey == null) { - setPromptingOn(); - } else if (currentSuggestion != null) { - setPromptingOff(currentSuggestion.caption); - } - } - removeStyleDependentName("focus"); - - if (client.hasEventListeners(this, EventId.BLUR)) { - client.updateVariable(paintableId, EventId.BLUR, "", true); - } - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.gwt.client.Focusable#focus() - */ - public void focus() { - focused = true; - if (prompting && !readonly) { - setPromptingOff(""); - } - tb.setFocus(true); - } - - /** - * Calculates the width of the select if the select has undefined width. - * Should be called when the width changes or when the icon changes. - */ - protected void updateRootWidth() { - ComponentConnector paintable = ConnectorMap.get(client).getConnector( - this); - if (paintable.isUndefinedWidth()) { - - /* - * When the select has a undefined with we need to check that we are - * only setting the text box width relative to the first page width - * of the items. If this is not done the text box width will change - * when the popup is used to view longer items than the text box is - * wide. - */ - int w = Util.getRequiredWidth(this); - if ((!initDone || currentPage + 1 < 0) - && suggestionPopupMinWidth > w) { - /* - * We want to compensate for the paddings just to preserve the - * exact size as in Vaadin 6.x, but we get here before - * MeasuredSize has been initialized. - * Util.measureHorizontalPaddingAndBorder does not work with - * border-box, so we must do this the hard way. - */ - Style style = getElement().getStyle(); - String originalPadding = style.getPadding(); - String originalBorder = style.getBorderWidth(); - style.setPaddingLeft(0, Unit.PX); - style.setBorderWidth(0, Unit.PX); - int offset = w - Util.getRequiredWidth(this); - style.setProperty("padding", originalPadding); - style.setProperty("borderWidth", originalBorder); - - setWidth(suggestionPopupMinWidth + offset + "px"); - } - - /* - * Lock the textbox width to its current value if it's not already - * locked - */ - if (!tb.getElement().getStyle().getWidth().endsWith("px")) { - tb.setWidth((tb.getOffsetWidth() - selectedItemIcon - .getOffsetWidth()) + "px"); - } - } - } - - /** - * Get the width of the select in pixels where the text area and icon has - * been included. - * - * @return The width in pixels - */ - private int getMainWidth() { - return getOffsetWidth(); - } - - @Override - public void setWidth(String width) { - super.setWidth(width); - if (width.length() != 0) { - tb.setWidth("100%"); - } - } - - /** - * Handles special behavior of the mouse down event - * - * @param event - */ - private void handleMouseDownEvent(Event event) { - /* - * Prevent the keyboard focus from leaving the textfield by preventing - * the default behaviour of the browser. Fixes #4285. - */ - if (event.getTypeInt() == Event.ONMOUSEDOWN) { - event.preventDefault(); - event.stopPropagation(); - - /* - * In IE the above wont work, the blur event will still trigger. So, - * we set a flag here to prevent the next blur event from happening. - * This is not needed if do not already have focus, in that case - * there will not be any blur event and we should not cancel the - * next blur. - */ - if (BrowserInfo.get().isIE() && focused) { - preventNextBlurEventInIE = true; - } - } - } - - @Override - protected void onDetach() { - super.onDetach(); - suggestionPopup.hide(); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VForm.java b/src/com/vaadin/terminal/gwt/client/ui/VForm.java deleted file mode 100644 index be205d2a88..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VForm.java +++ /dev/null @@ -1,78 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.shared.HandlerRegistration; -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.ComplexPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.VErrorMessage; - -public class VForm extends ComplexPanel implements KeyDownHandler { - - protected String id; - - public static final String CLASSNAME = "v-form"; - - Widget lo; - Element legend = DOM.createLegend(); - Element caption = DOM.createSpan(); - private Element errorIndicatorElement = DOM.createDiv(); - Element desc = DOM.createDiv(); - Icon icon; - VErrorMessage errorMessage = new VErrorMessage(); - - Element fieldContainer = DOM.createDiv(); - - Element footerContainer = DOM.createDiv(); - - Element fieldSet = DOM.createFieldSet(); - - Widget footer; - - ApplicationConnection client; - - ShortcutActionHandler shortcutHandler; - - HandlerRegistration keyDownRegistration; - - public VForm() { - setElement(DOM.createDiv()); - getElement().appendChild(fieldSet); - setStyleName(CLASSNAME); - fieldSet.appendChild(legend); - legend.appendChild(caption); - errorIndicatorElement.setClassName("v-errorindicator"); - errorIndicatorElement.getStyle().setDisplay(Display.NONE); - errorIndicatorElement.setInnerText(" "); // needed for IE - desc.setClassName("v-form-description"); - fieldSet.appendChild(desc); // Adding description for initial padding - // measurements, removed later if no - // description is set - fieldContainer.setClassName(CLASSNAME + "-content"); - fieldSet.appendChild(fieldContainer); - errorMessage.setVisible(false); - errorMessage.setStyleName(CLASSNAME + "-errormessage"); - fieldSet.appendChild(errorMessage.getElement()); - fieldSet.appendChild(footerContainer); - } - - public void onKeyDown(KeyDownEvent event) { - shortcutHandler.handleKeyboardEvent(Event.as(event.getNativeEvent())); - } - - @Override - protected void add(Widget child, Element container) { - // Overridden to allow VFormPaintable to call this. Should be removed - // once functionality from VFormPaintable is moved to VForm. - super.add(child, container); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VFormLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VFormLayout.java deleted file mode 100644 index 4a9bc8ca66..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VFormLayout.java +++ /dev/null @@ -1,376 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -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.FlexTable; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.ComponentState; -import com.vaadin.terminal.gwt.client.Focusable; -import com.vaadin.terminal.gwt.client.StyleConstants; -import com.vaadin.terminal.gwt.client.VTooltip; - -/** - * Two col Layout that places caption on left col and field on right col - */ -public class VFormLayout extends SimplePanel { - - private final static String CLASSNAME = "v-formlayout"; - - ApplicationConnection client; - VFormLayoutTable table; - - public VFormLayout() { - super(); - setStyleName(CLASSNAME); - table = new VFormLayoutTable(); - setWidget(table); - } - - /** - * Parses the stylenames from shared state - * - * @param state - * shared state of the component - * @param enabled - * @return An array of stylenames - */ - private String[] getStylesFromState(ComponentState state, boolean enabled) { - List styles = new ArrayList(); - if (state.hasStyles()) { - for (String name : state.getStyles()) { - styles.add(name); - } - } - - if (!enabled) { - styles.add(ApplicationConnection.DISABLED_CLASSNAME); - } - - return styles.toArray(new String[styles.size()]); - } - - public class VFormLayoutTable extends FlexTable implements ClickHandler { - - private static final int COLUMN_CAPTION = 0; - private static final int COLUMN_ERRORFLAG = 1; - private static final int COLUMN_WIDGET = 2; - - private HashMap widgetToCaption = new HashMap(); - private HashMap widgetToError = new HashMap(); - - public VFormLayoutTable() { - DOM.setElementProperty(getElement(), "cellPadding", "0"); - DOM.setElementProperty(getElement(), "cellSpacing", "0"); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt - * .event.dom.client.ClickEvent) - */ - public void onClick(ClickEvent event) { - Caption caption = (Caption) event.getSource(); - if (caption.getOwner() != null) { - if (caption.getOwner() instanceof Focusable) { - ((Focusable) caption.getOwner()).focus(); - } else if (caption.getOwner() instanceof com.google.gwt.user.client.ui.Focusable) { - ((com.google.gwt.user.client.ui.Focusable) caption - .getOwner()).setFocus(true); - } - } - } - - public void setMargins(VMarginInfo margins) { - Element margin = getElement(); - setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_TOP, - margins.hasTop()); - setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_RIGHT, - margins.hasRight()); - setStyleName(margin, - CLASSNAME + "-" + StyleConstants.MARGIN_BOTTOM, - margins.hasBottom()); - setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_LEFT, - margins.hasLeft()); - - } - - public void setSpacing(boolean spacing) { - setStyleName(getElement(), CLASSNAME + "-" + "spacing", spacing); - - } - - public void setRowCount(int rowNr) { - for (int i = 0; i < rowNr; i++) { - prepareCell(i, COLUMN_CAPTION); - getCellFormatter().setStyleName(i, COLUMN_CAPTION, - CLASSNAME + "-captioncell"); - - prepareCell(i, 1); - getCellFormatter().setStyleName(i, COLUMN_ERRORFLAG, - CLASSNAME + "-errorcell"); - - prepareCell(i, 2); - getCellFormatter().setStyleName(i, COLUMN_WIDGET, - CLASSNAME + "-contentcell"); - - String rowstyles = CLASSNAME + "-row"; - if (i == 0) { - rowstyles += " " + CLASSNAME + "-firstrow"; - } - if (i == rowNr - 1) { - rowstyles += " " + CLASSNAME + "-lastrow"; - } - - getRowFormatter().setStyleName(i, rowstyles); - - } - while (getRowCount() != rowNr) { - removeRow(rowNr); - } - } - - public void setChild(int rowNr, Widget childWidget, Caption caption, - ErrorFlag error) { - setWidget(rowNr, COLUMN_WIDGET, childWidget); - setWidget(rowNr, COLUMN_CAPTION, caption); - setWidget(rowNr, COLUMN_ERRORFLAG, error); - - widgetToCaption.put(childWidget, caption); - widgetToError.put(childWidget, error); - - } - - public Caption getCaption(Widget childWidget) { - return widgetToCaption.get(childWidget); - } - - public ErrorFlag getError(Widget childWidget) { - return widgetToError.get(childWidget); - } - - public void cleanReferences(Widget oldChildWidget) { - widgetToError.remove(oldChildWidget); - widgetToCaption.remove(oldChildWidget); - - } - - public void updateCaption(Widget widget, ComponentState state, - boolean enabled) { - final Caption c = widgetToCaption.get(widget); - if (c != null) { - c.updateCaption(state, enabled); - } - } - - public void updateError(Widget widget, String errorMessage, - boolean hideErrors) { - final ErrorFlag e = widgetToError.get(widget); - if (e != null) { - e.updateError(errorMessage, hideErrors); - } - - } - - } - - // TODO why duplicated here? - public class Caption extends HTML { - - public static final String CLASSNAME = "v-caption"; - - private final ComponentConnector owner; - - private Element requiredFieldIndicator; - - private Icon icon; - - private Element captionText; - - /** - * - * @param component - * optional owner of caption. If not set, getOwner will - * return null - */ - public Caption(ComponentConnector component) { - super(); - owner = component; - - sinkEvents(VTooltip.TOOLTIP_EVENTS); - } - - private void setStyles(String[] styles) { - String styleName = CLASSNAME; - - if (styles != null) { - for (String style : styles) { - if (ApplicationConnection.DISABLED_CLASSNAME.equals(style)) { - // Add v-disabled also without classname prefix so - // generic v-disabled CSS rules work - styleName += " " + style; - } - - styleName += " " + CLASSNAME + "-" + style; - } - } - - setStyleName(styleName); - } - - public void updateCaption(ComponentState state, boolean enabled) { - // Update styles as they might have changed when the caption changed - setStyles(getStylesFromState(state, enabled)); - - boolean isEmpty = true; - - if (state.getIcon() != null) { - if (icon == null) { - icon = new Icon(owner.getConnection()); - - DOM.insertChild(getElement(), icon.getElement(), 0); - } - icon.setUri(state.getIcon().getURL()); - isEmpty = false; - } else { - if (icon != null) { - DOM.removeChild(getElement(), icon.getElement()); - icon = null; - } - - } - - if (state.getCaption() != null) { - if (captionText == null) { - captionText = DOM.createSpan(); - DOM.insertChild(getElement(), captionText, icon == null ? 0 - : 1); - } - String c = state.getCaption(); - if (c == null) { - c = ""; - } else { - isEmpty = false; - } - DOM.setInnerText(captionText, c); - } else { - // TODO should span also be removed - } - - if (state.hasDescription() && captionText != null) { - addStyleDependentName("hasdescription"); - } else { - removeStyleDependentName("hasdescription"); - } - - boolean required = owner instanceof AbstractFieldConnector - && ((AbstractFieldConnector) owner).isRequired(); - if (required) { - if (requiredFieldIndicator == null) { - requiredFieldIndicator = DOM.createSpan(); - DOM.setInnerText(requiredFieldIndicator, "*"); - DOM.setElementProperty(requiredFieldIndicator, "className", - "v-required-field-indicator"); - DOM.appendChild(getElement(), requiredFieldIndicator); - } - } else { - if (requiredFieldIndicator != null) { - DOM.removeChild(getElement(), requiredFieldIndicator); - requiredFieldIndicator = null; - } - } - - // Workaround for IE weirdness, sometimes returns bad height in some - // circumstances when Caption is empty. See #1444 - // IE7 bugs more often. I wonder what happens when IE8 arrives... - // FIXME: This could be unnecessary for IE8+ - if (BrowserInfo.get().isIE()) { - if (isEmpty) { - setHeight("0px"); - DOM.setStyleAttribute(getElement(), "overflow", "hidden"); - } else { - setHeight(""); - DOM.setStyleAttribute(getElement(), "overflow", ""); - } - - } - - } - - /** - * Returns Paintable for which this Caption belongs to. - * - * @return owner Widget - */ - public ComponentConnector getOwner() { - return owner; - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - owner.getConnection().handleTooltipEvent(event, owner); - } - } - - class ErrorFlag extends HTML { - private static final String CLASSNAME = VFormLayout.CLASSNAME - + "-error-indicator"; - Element errorIndicatorElement; - - private ComponentConnector owner; - - public ErrorFlag(ComponentConnector owner) { - setStyleName(CLASSNAME); - sinkEvents(VTooltip.TOOLTIP_EVENTS); - this.owner = owner; - } - - public void updateError(String errorMessage, boolean hideErrors) { - boolean showError = null != errorMessage; - if (hideErrors) { - showError = false; - } - - if (showError) { - if (errorIndicatorElement == null) { - errorIndicatorElement = DOM.createDiv(); - DOM.setInnerHTML(errorIndicatorElement, " "); - DOM.setElementProperty(errorIndicatorElement, "className", - "v-errorindicator"); - DOM.appendChild(getElement(), errorIndicatorElement); - } - - } else if (errorIndicatorElement != null) { - DOM.removeChild(getElement(), errorIndicatorElement); - errorIndicatorElement = null; - } - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (owner != null) { - client.handleTooltipEvent(event, owner); - } - } - - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VGridLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VGridLayout.java deleted file mode 100644 index f218594edb..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VGridLayout.java +++ /dev/null @@ -1,674 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; - -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.ComplexPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.ConnectorMap; -import com.vaadin.terminal.gwt.client.LayoutManager; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VCaption; -import com.vaadin.terminal.gwt.client.ui.layout.ComponentConnectorLayoutSlot; -import com.vaadin.terminal.gwt.client.ui.layout.VLayoutSlot; - -public class VGridLayout extends ComplexPanel { - - public static final String CLASSNAME = "v-gridlayout"; - - ApplicationConnection client; - - HashMap widgetToCell = new HashMap(); - - int[] columnWidths; - int[] rowHeights; - - int[] colExpandRatioArray; - - int[] rowExpandRatioArray; - - int[] minColumnWidths; - - private int[] minRowHeights; - - DivElement spacingMeasureElement; - - public VGridLayout() { - super(); - setElement(Document.get().createDivElement()); - - spacingMeasureElement = Document.get().createDivElement(); - Style spacingStyle = spacingMeasureElement.getStyle(); - spacingStyle.setPosition(Position.ABSOLUTE); - getElement().appendChild(spacingMeasureElement); - - setStyleName(CLASSNAME); - } - - private GridLayoutConnector getConnector() { - return (GridLayoutConnector) ConnectorMap.get(client) - .getConnector(this); - } - - /** - * Returns the column widths measured in pixels - * - * @return - */ - protected int[] getColumnWidths() { - return columnWidths; - } - - /** - * Returns the row heights measured in pixels - * - * @return - */ - protected int[] getRowHeights() { - return rowHeights; - } - - /** - * Returns the spacing between the cells horizontally in pixels - * - * @return - */ - protected int getHorizontalSpacing() { - return LayoutManager.get(client).getOuterWidth(spacingMeasureElement); - } - - /** - * Returns the spacing between the cells vertically in pixels - * - * @return - */ - protected int getVerticalSpacing() { - return LayoutManager.get(client).getOuterHeight(spacingMeasureElement); - } - - static int[] cloneArray(int[] toBeCloned) { - int[] clone = new int[toBeCloned.length]; - for (int i = 0; i < clone.length; i++) { - clone[i] = toBeCloned[i] * 1; - } - return clone; - } - - void expandRows() { - if (!isUndefinedHeight()) { - int usedSpace = minRowHeights[0]; - int verticalSpacing = getVerticalSpacing(); - for (int i = 1; i < minRowHeights.length; i++) { - usedSpace += verticalSpacing + minRowHeights[i]; - } - int availableSpace = LayoutManager.get(client).getInnerHeight( - getElement()); - int excessSpace = availableSpace - usedSpace; - int distributed = 0; - if (excessSpace > 0) { - for (int i = 0; i < rowHeights.length; i++) { - int ew = excessSpace * rowExpandRatioArray[i] / 1000; - rowHeights[i] = minRowHeights[i] + ew; - distributed += ew; - } - excessSpace -= distributed; - int c = 0; - while (excessSpace > 0) { - rowHeights[c % rowHeights.length]++; - excessSpace--; - c++; - } - } - } - } - - void updateHeight() { - // Detect minimum heights & calculate spans - detectRowHeights(); - - // Expand - expandRows(); - - // Position - layoutCellsVertically(); - } - - void updateWidth() { - // Detect widths & calculate spans - detectColWidths(); - // Expand - expandColumns(); - // Position - layoutCellsHorizontally(); - - } - - void expandColumns() { - if (!isUndefinedWidth()) { - int usedSpace = minColumnWidths[0]; - int horizontalSpacing = getHorizontalSpacing(); - for (int i = 1; i < minColumnWidths.length; i++) { - usedSpace += horizontalSpacing + minColumnWidths[i]; - } - - int availableSpace = LayoutManager.get(client).getInnerWidth( - getElement()); - int excessSpace = availableSpace - usedSpace; - int distributed = 0; - if (excessSpace > 0) { - for (int i = 0; i < columnWidths.length; i++) { - int ew = excessSpace * colExpandRatioArray[i] / 1000; - columnWidths[i] = minColumnWidths[i] + ew; - distributed += ew; - } - excessSpace -= distributed; - int c = 0; - while (excessSpace > 0) { - columnWidths[c % columnWidths.length]++; - excessSpace--; - c++; - } - } - } - } - - void layoutCellsVertically() { - int verticalSpacing = getVerticalSpacing(); - LayoutManager layoutManager = LayoutManager.get(client); - Element element = getElement(); - int paddingTop = layoutManager.getPaddingTop(element); - int y = paddingTop; - - for (int i = 0; i < cells.length; i++) { - y = paddingTop; - for (int j = 0; j < cells[i].length; j++) { - Cell cell = cells[i][j]; - if (cell != null) { - cell.layoutVertically(y); - } - y += rowHeights[j] + verticalSpacing; - } - } - - if (isUndefinedHeight()) { - int outerHeight = y - verticalSpacing - + layoutManager.getPaddingBottom(element) - + layoutManager.getBorderHeight(element); - element.getStyle().setHeight(outerHeight, Unit.PX); - } - } - - void layoutCellsHorizontally() { - LayoutManager layoutManager = LayoutManager.get(client); - Element element = getElement(); - int x = layoutManager.getPaddingLeft(element); - int horizontalSpacing = getHorizontalSpacing(); - for (int i = 0; i < cells.length; i++) { - for (int j = 0; j < cells[i].length; j++) { - Cell cell = cells[i][j]; - if (cell != null) { - cell.layoutHorizontally(x); - } - } - x += columnWidths[i] + horizontalSpacing; - } - - if (isUndefinedWidth()) { - int outerWidth = x - horizontalSpacing - + layoutManager.getPaddingRight(element) - + layoutManager.getBorderWidth(element); - element.getStyle().setWidth(outerWidth, Unit.PX); - } - } - - private boolean isUndefinedHeight() { - return getConnector().isUndefinedHeight(); - } - - private boolean isUndefinedWidth() { - return getConnector().isUndefinedWidth(); - } - - private void detectRowHeights() { - for (int i = 0; i < rowHeights.length; i++) { - rowHeights[i] = 0; - } - - // collect min rowheight from non-rowspanned cells - for (int i = 0; i < cells.length; i++) { - for (int j = 0; j < cells[i].length; j++) { - Cell cell = cells[i][j]; - if (cell != null) { - if (cell.rowspan == 1) { - if (!cell.hasRelativeHeight() - && rowHeights[j] < cell.getHeight()) { - rowHeights[j] = cell.getHeight(); - } - } else { - storeRowSpannedCell(cell); - } - } - } - } - - distributeRowSpanHeights(); - - minRowHeights = cloneArray(rowHeights); - } - - private void detectColWidths() { - // collect min colwidths from non-colspanned cells - for (int i = 0; i < columnWidths.length; i++) { - columnWidths[i] = 0; - } - - for (int i = 0; i < cells.length; i++) { - for (int j = 0; j < cells[i].length; j++) { - Cell cell = cells[i][j]; - if (cell != null) { - if (cell.colspan == 1) { - if (!cell.hasRelativeWidth() - && columnWidths[i] < cell.getWidth()) { - columnWidths[i] = cell.getWidth(); - } - } else { - storeColSpannedCell(cell); - } - } - } - } - - distributeColSpanWidths(); - - minColumnWidths = cloneArray(columnWidths); - } - - private void storeRowSpannedCell(Cell cell) { - SpanList l = null; - for (SpanList list : rowSpans) { - if (list.span < cell.rowspan) { - continue; - } else { - // insert before this - l = list; - break; - } - } - if (l == null) { - l = new SpanList(cell.rowspan); - rowSpans.add(l); - } else if (l.span != cell.rowspan) { - SpanList newL = new SpanList(cell.rowspan); - rowSpans.add(rowSpans.indexOf(l), newL); - l = newL; - } - l.cells.add(cell); - } - - /** - * Iterates colspanned cells, ensures cols have enough space to accommodate - * them - */ - void distributeColSpanWidths() { - for (SpanList list : colSpans) { - for (Cell cell : list.cells) { - // cells with relative content may return non 0 here if on - // subsequent renders - int width = cell.hasRelativeWidth() ? 0 : cell.getWidth(); - distributeSpanSize(columnWidths, cell.col, cell.colspan, - getHorizontalSpacing(), width, colExpandRatioArray); - } - } - } - - /** - * Iterates rowspanned cells, ensures rows have enough space to accommodate - * them - */ - private void distributeRowSpanHeights() { - for (SpanList list : rowSpans) { - for (Cell cell : list.cells) { - // cells with relative content may return non 0 here if on - // subsequent renders - int height = cell.hasRelativeHeight() ? 0 : cell.getHeight(); - distributeSpanSize(rowHeights, cell.row, cell.rowspan, - getVerticalSpacing(), height, rowExpandRatioArray); - } - } - } - - private static void distributeSpanSize(int[] dimensions, - int spanStartIndex, int spanSize, int spacingSize, int size, - int[] expansionRatios) { - int allocated = dimensions[spanStartIndex]; - for (int i = 1; i < spanSize; i++) { - allocated += spacingSize + dimensions[spanStartIndex + i]; - } - if (allocated < size) { - // dimensions needs to be expanded due spanned cell - int neededExtraSpace = size - allocated; - int allocatedExtraSpace = 0; - - // Divide space according to expansion ratios if any span has a - // ratio - int totalExpansion = 0; - for (int i = 0; i < spanSize; i++) { - int itemIndex = spanStartIndex + i; - totalExpansion += expansionRatios[itemIndex]; - } - - for (int i = 0; i < spanSize; i++) { - int itemIndex = spanStartIndex + i; - int expansion; - if (totalExpansion == 0) { - // Divide equally among all cells if there are no - // expansion ratios - expansion = neededExtraSpace / spanSize; - } else { - expansion = neededExtraSpace * expansionRatios[itemIndex] - / totalExpansion; - } - dimensions[itemIndex] += expansion; - allocatedExtraSpace += expansion; - } - - // We might still miss a couple of pixels because of - // rounding errors... - if (neededExtraSpace > allocatedExtraSpace) { - for (int i = 0; i < spanSize; i++) { - // Add one pixel to every cell until we have - // compensated for any rounding error - int itemIndex = spanStartIndex + i; - dimensions[itemIndex] += 1; - allocatedExtraSpace += 1; - if (neededExtraSpace == allocatedExtraSpace) { - break; - } - } - } - } - } - - private LinkedList colSpans = new LinkedList(); - private LinkedList rowSpans = new LinkedList(); - - private class SpanList { - final int span; - List cells = new LinkedList(); - - public SpanList(int span) { - this.span = span; - } - } - - void storeColSpannedCell(Cell cell) { - SpanList l = null; - for (SpanList list : colSpans) { - if (list.span < cell.colspan) { - continue; - } else { - // insert before this - l = list; - break; - } - } - if (l == null) { - l = new SpanList(cell.colspan); - colSpans.add(l); - } else if (l.span != cell.colspan) { - - SpanList newL = new SpanList(cell.colspan); - colSpans.add(colSpans.indexOf(l), newL); - l = newL; - } - l.cells.add(cell); - } - - Cell[][] cells; - - /** - * Private helper class. - */ - class Cell { - public Cell(int row, int col) { - this.row = row; - this.col = col; - } - - public boolean hasContent() { - return hasContent; - } - - public boolean hasRelativeHeight() { - if (slot != null) { - return slot.getChild().isRelativeHeight(); - } else { - return true; - } - } - - /** - * @return total of spanned cols - */ - private int getAvailableWidth() { - int width = columnWidths[col]; - for (int i = 1; i < colspan; i++) { - width += getHorizontalSpacing() + columnWidths[col + i]; - } - return width; - } - - /** - * @return total of spanned rows - */ - private int getAvailableHeight() { - int height = rowHeights[row]; - for (int i = 1; i < rowspan; i++) { - height += getVerticalSpacing() + rowHeights[row + i]; - } - return height; - } - - public void layoutHorizontally(int x) { - if (slot != null) { - slot.positionHorizontally(x, getAvailableWidth()); - } - } - - public void layoutVertically(int y) { - if (slot != null) { - slot.positionVertically(y, getAvailableHeight()); - } - } - - public int getWidth() { - if (slot != null) { - return slot.getUsedWidth(); - } else { - return 0; - } - } - - public int getHeight() { - if (slot != null) { - return slot.getUsedHeight(); - } else { - return 0; - } - } - - protected boolean hasRelativeWidth() { - if (slot != null) { - return slot.getChild().isRelativeWidth(); - } else { - return true; - } - } - - final int row; - final int col; - int colspan = 1; - int rowspan = 1; - - private boolean hasContent; - - private AlignmentInfo alignment; - - ComponentConnectorLayoutSlot slot; - - public void updateFromUidl(UIDL cellUidl) { - // Set cell width - colspan = cellUidl.hasAttribute("w") ? cellUidl - .getIntAttribute("w") : 1; - // Set cell height - rowspan = cellUidl.hasAttribute("h") ? cellUidl - .getIntAttribute("h") : 1; - // ensure we will lose reference to old cells, now overlapped by - // this cell - for (int i = 0; i < colspan; i++) { - for (int j = 0; j < rowspan; j++) { - if (i > 0 || j > 0) { - cells[col + i][row + j] = null; - } - } - } - - UIDL childUidl = cellUidl.getChildUIDL(0); // we are interested - // about childUidl - hasContent = childUidl != null; - if (hasContent) { - ComponentConnector childConnector = client - .getPaintable(childUidl); - - if (slot == null || slot.getChild() != childConnector) { - slot = new ComponentConnectorLayoutSlot(CLASSNAME, - childConnector, getConnector()); - Element slotWrapper = slot.getWrapperElement(); - getElement().appendChild(slotWrapper); - - Widget widget = childConnector.getWidget(); - insert(widget, slotWrapper, getWidgetCount(), false); - Cell oldCell = widgetToCell.put(widget, this); - if (oldCell != null) { - oldCell.slot.getWrapperElement().removeFromParent(); - oldCell.slot = null; - } - } - - } - } - - public void setAlignment(AlignmentInfo alignmentInfo) { - slot.setAlignment(alignmentInfo); - } - } - - Cell getCell(int row, int col) { - return cells[col][row]; - } - - /** - * Creates a new Cell with the given coordinates. If an existing cell was - * found, returns that one. - * - * @param row - * @param col - * @return - */ - Cell createCell(int row, int col) { - Cell cell = getCell(row, col); - if (cell == null) { - cell = new Cell(row, col); - cells[col][row] = cell; - } - return cell; - } - - /** - * Returns the deepest nested child component which contains "element". The - * child component is also returned if "element" is part of its caption. - * - * @param element - * An element that is a nested sub element of the root element in - * this layout - * @return The Paintable which the element is a part of. Null if the element - * belongs to the layout and not to a child. - */ - ComponentConnector getComponent(Element element) { - return Util.getConnectorForElement(client, this, element); - } - - void setCaption(Widget widget, VCaption caption) { - VLayoutSlot slot = widgetToCell.get(widget).slot; - - if (caption != null) { - // Logical attach. - getChildren().add(caption); - } - - // Physical attach if not null, also removes old caption - slot.setCaption(caption); - - if (caption != null) { - // Adopt. - adopt(caption); - } - } - - private void togglePrefixedStyleName(String name, boolean enabled) { - if (enabled) { - addStyleDependentName(name); - } else { - removeStyleDependentName(name); - } - } - - void updateMarginStyleNames(VMarginInfo marginInfo) { - togglePrefixedStyleName("margin-top", marginInfo.hasTop()); - togglePrefixedStyleName("margin-right", marginInfo.hasRight()); - togglePrefixedStyleName("margin-bottom", marginInfo.hasBottom()); - togglePrefixedStyleName("margin-left", marginInfo.hasLeft()); - } - - void updateSpacingStyleName(boolean spacingEnabled) { - String styleName = getStylePrimaryName(); - if (spacingEnabled) { - spacingMeasureElement.addClassName(styleName + "-spacing-on"); - spacingMeasureElement.removeClassName(styleName + "-spacing-off"); - } else { - spacingMeasureElement.removeClassName(styleName + "-spacing-on"); - spacingMeasureElement.addClassName(styleName + "-spacing-off"); - } - } - - public void setSize(int rows, int cols) { - if (cells == null) { - cells = new Cell[cols][rows]; - } else if (cells.length != cols || cells[0].length != rows) { - Cell[][] newCells = new Cell[cols][rows]; - for (int i = 0; i < cells.length; i++) { - for (int j = 0; j < cells[i].length; j++) { - if (i < cols && j < rows) { - newCells[i][j] = cells[i][j]; - } - } - } - cells = newCells; - } - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VHorizontalLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VHorizontalLayout.java deleted file mode 100644 index 30796b1660..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VHorizontalLayout.java +++ /dev/null @@ -1,14 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -public class VHorizontalLayout extends VMeasuringOrderedLayout { - - public static final String CLASSNAME = "v-horizontallayout"; - - public VHorizontalLayout() { - super(CLASSNAME, false); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VLink.java b/src/com/vaadin/terminal/gwt/client/ui/VLink.java deleted file mode 100644 index 22c3d10657..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VLink.java +++ /dev/null @@ -1,116 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -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.Window; -import com.google.gwt.user.client.ui.HTML; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VTooltip; - -public class VLink extends HTML implements ClickHandler { - - public static final String CLASSNAME = "v-link"; - - protected static final int BORDER_STYLE_DEFAULT = 0; - protected static final int BORDER_STYLE_MINIMAL = 1; - protected static final int BORDER_STYLE_NONE = 2; - - protected String src; - - protected String target; - - protected int borderStyle = BORDER_STYLE_DEFAULT; - - protected boolean enabled; - - protected int targetWidth; - - protected int targetHeight; - - protected Element errorIndicatorElement; - - protected final Element anchor = DOM.createAnchor(); - - protected final Element captionElement = DOM.createSpan(); - - protected Icon icon; - - protected ApplicationConnection client; - - public VLink() { - super(); - getElement().appendChild(anchor); - anchor.appendChild(captionElement); - addClickHandler(this); - sinkEvents(VTooltip.TOOLTIP_EVENTS); - setStyleName(CLASSNAME); - } - - public void onClick(ClickEvent event) { - if (enabled) { - if (target == null) { - target = "_self"; - } - String features; - switch (borderStyle) { - case BORDER_STYLE_NONE: - features = "menubar=no,location=no,status=no"; - break; - case BORDER_STYLE_MINIMAL: - features = "menubar=yes,location=no,status=no"; - break; - default: - features = ""; - break; - } - - if (targetWidth > 0) { - features += (features.length() > 0 ? "," : "") + "width=" - + targetWidth; - } - if (targetHeight > 0) { - features += (features.length() > 0 ? "," : "") + "height=" - + targetHeight; - } - - if (features.length() > 0) { - // if 'special features' are set, use window.open(), unless - // a modifier key is held (ctrl to open in new tab etc) - Event e = DOM.eventGetCurrentEvent(); - if (!e.getCtrlKey() && !e.getAltKey() && !e.getShiftKey() - && !e.getMetaKey()) { - Window.open(src, target, features); - e.preventDefault(); - } - } - } - } - - @Override - public void onBrowserEvent(Event event) { - final Element target = DOM.eventGetTarget(event); - if (event.getTypeInt() == Event.ONLOAD) { - Util.notifyParentOfSizeChange(this, true); - } - if (client != null) { - client.handleTooltipEvent(event, this); - } - if (target == captionElement || target == anchor - || (icon != null && target == icon.getElement())) { - super.onBrowserEvent(event); - } - if (!enabled) { - event.preventDefault(); - } - - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VListSelect.java b/src/com/vaadin/terminal/gwt/client/ui/VListSelect.java deleted file mode 100644 index 9ac5e5ab4c..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VListSelect.java +++ /dev/null @@ -1,143 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.Iterator; - -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.ListBox; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.VTooltip; - -public class VListSelect extends VOptionGroupBase { - - public static final String CLASSNAME = "v-select"; - - private static final int VISIBLE_COUNT = 10; - - protected TooltipListBox select; - - private int lastSelectedIndex = -1; - - public VListSelect() { - super(new TooltipListBox(true), CLASSNAME); - select = (TooltipListBox) optionsContainer; - select.setSelect(this); - select.addChangeHandler(this); - select.addClickHandler(this); - select.setStyleName(CLASSNAME + "-select"); - select.setVisibleItemCount(VISIBLE_COUNT); - } - - @Override - protected void buildOptions(UIDL uidl) { - select.setClient(client); - select.setMultipleSelect(isMultiselect()); - select.setEnabled(!isDisabled() && !isReadonly()); - select.clear(); - if (!isMultiselect() && isNullSelectionAllowed() - && !isNullSelectionItemAvailable()) { - // can't unselect last item in singleselect mode - select.addItem("", (String) null); - } - for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { - final UIDL optionUidl = (UIDL) i.next(); - select.addItem(optionUidl.getStringAttribute("caption"), - optionUidl.getStringAttribute("key")); - if (optionUidl.hasAttribute("selected")) { - int itemIndex = select.getItemCount() - 1; - select.setItemSelected(itemIndex, true); - lastSelectedIndex = itemIndex; - } - } - if (getRows() > 0) { - select.setVisibleItemCount(getRows()); - } - } - - @Override - protected String[] getSelectedItems() { - final ArrayList selectedItemKeys = new ArrayList(); - for (int i = 0; i < select.getItemCount(); i++) { - if (select.isItemSelected(i)) { - selectedItemKeys.add(select.getValue(i)); - } - } - return selectedItemKeys.toArray(new String[selectedItemKeys.size()]); - } - - @Override - public void onChange(ChangeEvent event) { - final int si = select.getSelectedIndex(); - if (si == -1 && !isNullSelectionAllowed()) { - select.setSelectedIndex(lastSelectedIndex); - } else { - lastSelectedIndex = si; - if (isMultiselect()) { - client.updateVariable(paintableId, "selected", - getSelectedItems(), isImmediate()); - } else { - client.updateVariable(paintableId, "selected", - new String[] { "" + getSelectedItem() }, isImmediate()); - } - } - } - - @Override - public void setHeight(String height) { - select.setHeight(height); - super.setHeight(height); - } - - @Override - public void setWidth(String width) { - select.setWidth(width); - super.setWidth(width); - } - - @Override - protected void setTabIndex(int tabIndex) { - ((TooltipListBox) optionsContainer).setTabIndex(tabIndex); - } - - public void focus() { - select.setFocus(true); - } -} - -/** - * Extended ListBox to listen tooltip events and forward them to generic - * handler. - */ -class TooltipListBox extends ListBox { - private ApplicationConnection client; - private Widget widget; - - TooltipListBox(boolean isMultiselect) { - super(isMultiselect); - sinkEvents(VTooltip.TOOLTIP_EVENTS); - } - - public void setClient(ApplicationConnection client) { - this.client = client; - } - - public void setSelect(Widget widget) { - this.widget = widget; - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (client != null) { - client.handleTooltipEvent(event, widget); - } - } - -} \ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/VMeasuringOrderedLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VMeasuringOrderedLayout.java deleted file mode 100644 index b7d28c56b9..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VMeasuringOrderedLayout.java +++ /dev/null @@ -1,226 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.HashMap; -import java.util.Map; - -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.ComplexPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.VCaption; -import com.vaadin.terminal.gwt.client.ui.layout.VLayoutSlot; - -public class VMeasuringOrderedLayout extends ComplexPanel { - - final boolean isVertical; - - final DivElement spacingMeasureElement; - - private Map widgetToSlot = new HashMap(); - - protected VMeasuringOrderedLayout(String className, boolean isVertical) { - DivElement element = Document.get().createDivElement(); - setElement(element); - - spacingMeasureElement = Document.get().createDivElement(); - Style spacingStyle = spacingMeasureElement.getStyle(); - spacingStyle.setPosition(Position.ABSOLUTE); - getElement().appendChild(spacingMeasureElement); - - setStyleName(className); - this.isVertical = isVertical; - } - - public void addOrMove(VLayoutSlot layoutSlot, int index) { - Widget widget = layoutSlot.getWidget(); - Element wrapperElement = layoutSlot.getWrapperElement(); - - Element containerElement = getElement(); - Node childAtIndex = containerElement.getChild(index); - if (childAtIndex != wrapperElement) { - // Insert at correct location not attached or at wrong location - containerElement.insertBefore(wrapperElement, childAtIndex); - insert(widget, wrapperElement, index, false); - } - - widgetToSlot.put(widget, layoutSlot); - } - - private void togglePrefixedStyleName(String name, boolean enabled) { - if (enabled) { - addStyleDependentName(name); - } else { - removeStyleDependentName(name); - } - } - - void updateMarginStyleNames(VMarginInfo marginInfo) { - togglePrefixedStyleName("margin-top", marginInfo.hasTop()); - togglePrefixedStyleName("margin-right", marginInfo.hasRight()); - togglePrefixedStyleName("margin-bottom", marginInfo.hasBottom()); - togglePrefixedStyleName("margin-left", marginInfo.hasLeft()); - } - - void updateSpacingStyleName(boolean spacingEnabled) { - String styleName = getStylePrimaryName(); - if (spacingEnabled) { - spacingMeasureElement.addClassName(styleName + "-spacing-on"); - spacingMeasureElement.removeClassName(styleName + "-spacing-off"); - } else { - spacingMeasureElement.removeClassName(styleName + "-spacing-on"); - spacingMeasureElement.addClassName(styleName + "-spacing-off"); - } - } - - public void removeSlotForWidget(Widget widget) { - VLayoutSlot slot = getSlotForChild(widget); - VCaption caption = slot.getCaption(); - if (caption != null) { - // Must remove using setCaption to ensure dependencies (layout -> - // caption) are unregistered - slot.setCaption(null); - } - - remove(slot.getWidget()); - getElement().removeChild(slot.getWrapperElement()); - widgetToSlot.remove(widget); - } - - public VLayoutSlot getSlotForChild(Widget widget) { - return widgetToSlot.get(widget); - } - - public void setCaption(Widget child, VCaption caption) { - VLayoutSlot slot = getSlotForChild(child); - - if (caption != null) { - // Logical attach. - getChildren().add(caption); - } - - // Physical attach if not null, also removes old caption - slot.setCaption(caption); - - if (caption != null) { - // Adopt. - adopt(caption); - } - } - - public int layoutPrimaryDirection(int spacingSize, int allocatedSize, - int startPadding) { - int actuallyAllocated = 0; - double totalExpand = 0; - - int childCount = 0; - for (Widget child : this) { - if (child instanceof VCaption) { - continue; - } - childCount++; - - VLayoutSlot slot = getSlotForChild(child); - totalExpand += slot.getExpandRatio(); - - if (!slot.isRelativeInDirection(isVertical)) { - actuallyAllocated += slot.getUsedSizeInDirection(isVertical); - } - } - - actuallyAllocated += spacingSize * (childCount - 1); - - if (allocatedSize == -1) { - allocatedSize = actuallyAllocated; - } - - double unallocatedSpace = Math - .max(0, allocatedSize - actuallyAllocated); - - double currentLocation = startPadding; - - for (Widget child : this) { - if (child instanceof VCaption) { - continue; - } - - VLayoutSlot slot = getSlotForChild(child); - - double childExpandRatio; - if (totalExpand == 0) { - childExpandRatio = 1d / childCount; - } else { - childExpandRatio = slot.getExpandRatio() / totalExpand; - } - - double extraPixels = unallocatedSpace * childExpandRatio; - double endLocation = currentLocation + extraPixels; - if (!slot.isRelativeInDirection(isVertical)) { - endLocation += slot.getUsedSizeInDirection(isVertical); - } - - /* - * currentLocation and allocatedSpace are used with full precision - * to avoid missing pixels in the end. The pixel dimensions passed - * to the DOM are still rounded. Otherwise e.g. 10.5px start - * position + 10.5px space might be cause the component to go 1px - * beyond the edge as the effect of the browser's rounding may cause - * something similar to 11px + 11px. - * - * It's most efficient to use doubles all the way because native - * javascript emulates other number types using doubles. - */ - double roundedLocation = Math.round(currentLocation); - - /* - * Space is calculated as the difference between rounded start and - * end locations. Just rounding the space would cause e.g. 10.5px + - * 10.5px = 21px -> 11px + 11px = 22px but in this way we get 11px + - * 10px = 21px. - */ - double roundedSpace = Math.round(endLocation) - roundedLocation; - - slot.positionInDirection(roundedLocation, roundedSpace, isVertical); - - currentLocation = endLocation + spacingSize; - } - - return allocatedSize; - } - - public int layoutSecondaryDirection(int allocatedSize, int startPadding) { - int maxSize = 0; - for (Widget child : this) { - if (child instanceof VCaption) { - continue; - } - - VLayoutSlot slot = getSlotForChild(child); - if (!slot.isRelativeInDirection(!isVertical)) { - maxSize = Math.max(maxSize, - slot.getUsedSizeInDirection(!isVertical)); - } - } - - if (allocatedSize == -1) { - allocatedSize = maxSize; - } - - for (Widget child : this) { - if (child instanceof VCaption) { - continue; - } - - VLayoutSlot slot = getSlotForChild(child); - slot.positionInDirection(startPadding, allocatedSize, !isVertical); - } - - return allocatedSize; - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java b/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java deleted file mode 100644 index b01861bc7a..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VMenuBar.java +++ /dev/null @@ -1,1433 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.List; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.KeyPressEvent; -import com.google.gwt.event.dom.client.KeyPressHandler; -import com.google.gwt.event.logical.shared.CloseEvent; -import com.google.gwt.event.logical.shared.CloseHandler; -import com.google.gwt.user.client.Command; -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.Timer; -import com.google.gwt.user.client.ui.HasHTML; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.RootPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.LayoutManager; -import com.vaadin.terminal.gwt.client.TooltipInfo; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VTooltip; - -public class VMenuBar extends SimpleFocusablePanel implements - CloseHandler, KeyPressHandler, KeyDownHandler, - FocusHandler, SubPartAware { - - // The hierarchy of VMenuBar is a bit weird as VMenuBar is the Paintable, - // used for the root menu but also used for the sub menus. - - /** Set the CSS class name to allow styling. */ - public static final String CLASSNAME = "v-menubar"; - - /** For server connections **/ - protected String uidlId; - protected ApplicationConnection client; - - protected final VMenuBar hostReference = this; - protected CustomMenuItem moreItem = null; - - // Only used by the root menu bar - protected VMenuBar collapsedRootItems; - - // Construct an empty command to be used when the item has no command - // associated - protected static final Command emptyCommand = null; - - public static final String OPEN_ROOT_MENU_ON_HOWER = "ormoh"; - - public static final String ATTRIBUTE_CHECKED = "checked"; - public static final String ATTRIBUTE_ITEM_DESCRIPTION = "description"; - public static final String ATTRIBUTE_ITEM_ICON = "icon"; - public static final String ATTRIBUTE_ITEM_DISABLED = "disabled"; - public static final String ATTRIBUTE_ITEM_STYLE = "style"; - - public static final String HTML_CONTENT_ALLOWED = "usehtml"; - - /** Widget fields **/ - protected boolean subMenu; - protected ArrayList items; - protected Element containerElement; - protected VOverlay popup; - protected VMenuBar visibleChildMenu; - protected boolean menuVisible = false; - protected VMenuBar parentMenu; - protected CustomMenuItem selected; - - boolean enabled = true; - - private String width = "notinited"; - - private VLazyExecutor iconLoadedExecutioner = new VLazyExecutor(100, - new ScheduledCommand() { - - public void execute() { - iLayout(true); - } - }); - - boolean openRootOnHover; - - boolean htmlContentAllowed; - - public VMenuBar() { - // Create an empty horizontal menubar - this(false, null); - - // Navigation is only handled by the root bar - addFocusHandler(this); - - /* - * Firefox auto-repeat works correctly only if we use a key press - * handler, other browsers handle it correctly when using a key down - * handler - */ - if (BrowserInfo.get().isGecko()) { - addKeyPressHandler(this); - } else { - addKeyDownHandler(this); - } - } - - public VMenuBar(boolean subMenu, VMenuBar parentMenu) { - - items = new ArrayList(); - popup = null; - visibleChildMenu = null; - - containerElement = getElement(); - - if (!subMenu) { - setStyleName(CLASSNAME); - } else { - setStyleName(CLASSNAME + "-submenu"); - this.parentMenu = parentMenu; - } - this.subMenu = subMenu; - - sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT - | Event.ONLOAD); - - sinkEvents(VTooltip.TOOLTIP_EVENTS); - } - - @Override - protected void onDetach() { - super.onDetach(); - if (!subMenu) { - setSelected(null); - hideChildren(); - menuVisible = false; - } - } - - void updateSize() { - // Take from setWidth - if (!subMenu) { - // Only needed for root level menu - hideChildren(); - setSelected(null); - menuVisible = false; - } - } - - /** - * Build the HTML content for a menu item. - * - * @param item - * @return - */ - protected String buildItemHTML(UIDL item) { - // Construct html from the text and the optional icon - StringBuffer itemHTML = new StringBuffer(); - if (item.hasAttribute("separator")) { - itemHTML.append("---"); - } else { - // Add submenu indicator - if (item.getChildCount() > 0) { - String bgStyle = ""; - itemHTML.append(""); - } - - itemHTML.append(""); - if (item.hasAttribute("icon")) { - itemHTML.append("\"\""); - } - String itemText = item.getStringAttribute("text"); - if (!htmlContentAllowed) { - itemText = Util.escapeHTML(itemText); - } - itemHTML.append(itemText); - itemHTML.append(""); - } - return itemHTML.toString(); - } - - /** - * This is called by the items in the menu and it communicates the - * information to the server - * - * @param clickedItemId - * id of the item that was clicked - */ - public void onMenuClick(int clickedItemId) { - // Updating the state to the server can not be done before - // the server connection is known, i.e., before updateFromUIDL() - // has been called. - if (uidlId != null && client != null) { - // Communicate the user interaction parameters to server. This call - // will initiate an AJAX request to the server. - client.updateVariable(uidlId, "clickedId", clickedItemId, true); - } - } - - /** Widget methods **/ - - /** - * Returns a list of items in this menu - */ - public List getItems() { - return items; - } - - /** - * Remove all the items in this menu - */ - public void clearItems() { - Element e = getContainerElement(); - while (DOM.getChildCount(e) > 0) { - DOM.removeChild(e, DOM.getChild(e, 0)); - } - items.clear(); - } - - /** - * Returns the containing element of the menu - * - * @return - */ - @Override - public Element getContainerElement() { - return containerElement; - } - - /** - * Add a new item to this menu - * - * @param html - * items text - * @param cmd - * items command - * @return the item created - */ - public CustomMenuItem addItem(String html, Command cmd) { - CustomMenuItem item = GWT.create(CustomMenuItem.class); - item.setHTML(html); - item.setCommand(cmd); - - addItem(item); - return item; - } - - /** - * Add a new item to this menu - * - * @param item - */ - public void addItem(CustomMenuItem item) { - if (items.contains(item)) { - return; - } - DOM.appendChild(getContainerElement(), item.getElement()); - item.setParentMenu(this); - item.setSelected(false); - items.add(item); - } - - public void addItem(CustomMenuItem item, int index) { - if (items.contains(item)) { - return; - } - DOM.insertChild(getContainerElement(), item.getElement(), index); - item.setParentMenu(this); - item.setSelected(false); - items.add(index, item); - } - - /** - * Remove the given item from this menu - * - * @param item - */ - public void removeItem(CustomMenuItem item) { - if (items.contains(item)) { - int index = items.indexOf(item); - - DOM.removeChild(getContainerElement(), - DOM.getChild(getContainerElement(), index)); - items.remove(index); - } - } - - /* - * @see - * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user - * .client.Event) - */ - @Override - public void onBrowserEvent(Event e) { - super.onBrowserEvent(e); - - // Handle onload events (icon loaded, size changes) - if (DOM.eventGetType(e) == Event.ONLOAD) { - VMenuBar parent = getParentMenu(); - if (parent != null) { - // The onload event for an image in a popup should be sent to - // the parent, which owns the popup - parent.iconLoaded(); - } else { - // Onload events for images in the root menu are handled by the - // root menu itself - iconLoaded(); - } - return; - } - - Element targetElement = DOM.eventGetTarget(e); - CustomMenuItem targetItem = null; - for (int i = 0; i < items.size(); i++) { - CustomMenuItem item = items.get(i); - if (DOM.isOrHasChild(item.getElement(), targetElement)) { - targetItem = item; - } - } - - // Handle tooltips - if (targetItem == null && client != null) { - // Handle root menubar tooltips - client.handleTooltipEvent(e, this); - } else if (targetItem != null) { - // Handle item tooltips - targetItem.onBrowserEvent(e); - } - - if (targetItem != null) { - switch (DOM.eventGetType(e)) { - - case Event.ONCLICK: - if (isEnabled() && targetItem.isEnabled()) { - itemClick(targetItem); - } - if (subMenu) { - // Prevent moving keyboard focus to child menus - VMenuBar parent = parentMenu; - while (parent.getParentMenu() != null) { - parent = parent.getParentMenu(); - } - parent.setFocus(true); - } - - break; - - case Event.ONMOUSEOVER: - LazyCloser.cancelClosing(); - - if (isEnabled() && targetItem.isEnabled()) { - itemOver(targetItem); - } - break; - - case Event.ONMOUSEOUT: - itemOut(targetItem); - LazyCloser.schedule(); - break; - } - } else if (subMenu && DOM.eventGetType(e) == Event.ONCLICK && subMenu) { - // Prevent moving keyboard focus to child menus - VMenuBar parent = parentMenu; - while (parent.getParentMenu() != null) { - parent = parent.getParentMenu(); - } - parent.setFocus(true); - } - } - - private boolean isEnabled() { - return enabled; - } - - private void iconLoaded() { - iconLoadedExecutioner.trigger(); - } - - /** - * When an item is clicked - * - * @param item - */ - public void itemClick(CustomMenuItem item) { - if (item.getCommand() != null) { - setSelected(null); - - if (visibleChildMenu != null) { - visibleChildMenu.hideChildren(); - } - - hideParents(true); - menuVisible = false; - Scheduler.get().scheduleDeferred(item.getCommand()); - - } else { - if (item.getSubMenu() != null - && item.getSubMenu() != visibleChildMenu) { - setSelected(item); - showChildMenu(item); - menuVisible = true; - } else if (!subMenu) { - setSelected(null); - hideChildren(); - menuVisible = false; - } - } - } - - /** - * When the user hovers the mouse over the item - * - * @param item - */ - public void itemOver(CustomMenuItem item) { - if ((openRootOnHover || subMenu || menuVisible) && !item.isSeparator()) { - setSelected(item); - if (!subMenu && openRootOnHover && !menuVisible) { - menuVisible = true; // start opening menus - LazyCloser.prepare(this); - } - } - - if (menuVisible && visibleChildMenu != item.getSubMenu() - && popup != null) { - popup.hide(); - } - - if (menuVisible && item.getSubMenu() != null - && visibleChildMenu != item.getSubMenu()) { - showChildMenu(item); - } - } - - /** - * When the mouse is moved away from an item - * - * @param item - */ - public void itemOut(CustomMenuItem item) { - if (visibleChildMenu != item.getSubMenu()) { - hideChildMenu(item); - setSelected(null); - } else if (visibleChildMenu == null) { - setSelected(null); - } - } - - /** - * Used to autoclose submenus when they the menu is in a mode which opens - * root menus on mouse hover. - */ - private static class LazyCloser extends Timer { - static LazyCloser INSTANCE; - private VMenuBar activeRoot; - - @Override - public void run() { - activeRoot.hideChildren(); - activeRoot.setSelected(null); - activeRoot.menuVisible = false; - activeRoot = null; - } - - public static void cancelClosing() { - if (INSTANCE != null) { - INSTANCE.cancel(); - } - } - - public static void prepare(VMenuBar vMenuBar) { - if (INSTANCE == null) { - INSTANCE = new LazyCloser(); - } - if (INSTANCE.activeRoot == vMenuBar) { - INSTANCE.cancel(); - } else if (INSTANCE.activeRoot != null) { - INSTANCE.cancel(); - INSTANCE.run(); - } - INSTANCE.activeRoot = vMenuBar; - } - - public static void schedule() { - if (INSTANCE != null && INSTANCE.activeRoot != null) { - INSTANCE.schedule(750); - } - } - - } - - /** - * Shows the child menu of an item. The caller must ensure that the item has - * a submenu. - * - * @param item - */ - public void showChildMenu(CustomMenuItem item) { - - int left = 0; - int top = 0; - if (subMenu) { - left = item.getParentMenu().getAbsoluteLeft() - + item.getParentMenu().getOffsetWidth(); - top = item.getAbsoluteTop(); - } else { - left = item.getAbsoluteLeft(); - top = item.getParentMenu().getAbsoluteTop() - + item.getParentMenu().getOffsetHeight(); - } - showChildMenuAt(item, top, left); - } - - protected void showChildMenuAt(CustomMenuItem item, int top, int left) { - final int shadowSpace = 10; - - popup = new VOverlay(true, false, true); - popup.setStyleName(CLASSNAME + "-popup"); - popup.setWidget(item.getSubMenu()); - popup.addCloseHandler(this); - popup.addAutoHidePartner(item.getElement()); - - // at 0,0 because otherwise IE7 add extra scrollbars (#5547) - popup.setPopupPosition(0, 0); - - item.getSubMenu().onShow(); - visibleChildMenu = item.getSubMenu(); - item.getSubMenu().setParentMenu(this); - - popup.show(); - - if (left + popup.getOffsetWidth() >= RootPanel.getBodyElement() - .getOffsetWidth() - shadowSpace) { - if (subMenu) { - left = item.getParentMenu().getAbsoluteLeft() - - popup.getOffsetWidth() - shadowSpace; - } else { - left = RootPanel.getBodyElement().getOffsetWidth() - - popup.getOffsetWidth() - shadowSpace; - } - // Accommodate space for shadow - if (left < shadowSpace) { - left = shadowSpace; - } - } - - top = adjustPopupHeight(top, shadowSpace); - - popup.setPopupPosition(left, top); - - } - - private int adjustPopupHeight(int top, final int shadowSpace) { - // Check that the popup will fit the screen - int availableHeight = RootPanel.getBodyElement().getOffsetHeight() - - top - shadowSpace; - int missingHeight = popup.getOffsetHeight() - availableHeight; - if (missingHeight > 0) { - // First move the top of the popup to get more space - // Don't move above top of screen, don't move more than needed - int moveUpBy = Math.min(top - shadowSpace, missingHeight); - - // Update state - top -= moveUpBy; - missingHeight -= moveUpBy; - availableHeight += moveUpBy; - - if (missingHeight > 0) { - int contentWidth = visibleChildMenu.getOffsetWidth(); - - // If there's still not enough room, limit height to fit and add - // a scroll bar - Style style = popup.getElement().getStyle(); - style.setHeight(availableHeight, Unit.PX); - style.setOverflowY(Overflow.SCROLL); - - // Make room for the scroll bar by adjusting the width of the - // popup - style.setWidth(contentWidth + Util.getNativeScrollbarSize(), - Unit.PX); - popup.updateShadowSizeAndPosition(); - } - } - return top; - } - - /** - * Hides the submenu of an item - * - * @param item - */ - public void hideChildMenu(CustomMenuItem item) { - if (visibleChildMenu != null - && !(visibleChildMenu == item.getSubMenu())) { - popup.hide(); - } - } - - /** - * When the menu is shown. - */ - public void onShow() { - // remove possible previous selection - if (selected != null) { - selected.setSelected(false); - selected = null; - } - menuVisible = true; - } - - /** - * Listener method, fired when this menu is closed - */ - public void onClose(CloseEvent event) { - hideChildren(); - if (event.isAutoClosed()) { - hideParents(true); - menuVisible = false; - } - visibleChildMenu = null; - popup = null; - } - - /** - * Recursively hide all child menus - */ - public void hideChildren() { - if (visibleChildMenu != null) { - visibleChildMenu.hideChildren(); - popup.hide(); - } - } - - /** - * Recursively hide all parent menus - */ - public void hideParents(boolean autoClosed) { - if (visibleChildMenu != null) { - popup.hide(); - setSelected(null); - menuVisible = !autoClosed; - } - - if (getParentMenu() != null) { - getParentMenu().hideParents(autoClosed); - } - } - - /** - * Returns the parent menu of this menu, or null if this is the top-level - * menu - * - * @return - */ - public VMenuBar getParentMenu() { - return parentMenu; - } - - /** - * Set the parent menu of this menu - * - * @param parent - */ - public void setParentMenu(VMenuBar parent) { - parentMenu = parent; - } - - /** - * Returns the currently selected item of this menu, or null if nothing is - * selected - * - * @return - */ - public CustomMenuItem getSelected() { - return selected; - } - - /** - * Set the currently selected item of this menu - * - * @param item - */ - public void setSelected(CustomMenuItem item) { - // If we had something selected, unselect - if (item != selected && selected != null) { - selected.setSelected(false); - } - // If we have a valid selection, select it - if (item != null) { - item.setSelected(true); - } - - selected = item; - } - - /** - * - * A class to hold information on menu items - * - */ - protected static class CustomMenuItem extends Widget implements HasHTML { - - private ApplicationConnection client; - - protected String html = null; - protected Command command = null; - protected VMenuBar subMenu = null; - protected VMenuBar parentMenu = null; - protected boolean enabled = true; - protected boolean isSeparator = false; - protected boolean checkable = false; - protected boolean checked = false; - - /** - * Default menu item {@link Widget} constructor for GWT.create(). - * - * Use {@link #setHTML(String)} and {@link #setCommand(Command)} after - * constructing a menu item. - */ - public CustomMenuItem() { - this("", null); - } - - /** - * Creates a menu item {@link Widget}. - * - * @param html - * @param cmd - * @deprecated use the default constructor and {@link #setHTML(String)} - * and {@link #setCommand(Command)} instead - */ - @Deprecated - public CustomMenuItem(String html, Command cmd) { - // We need spans to allow inline-block in IE - setElement(DOM.createSpan()); - - setHTML(html); - setCommand(cmd); - setSelected(false); - setStyleName(CLASSNAME + "-menuitem"); - - sinkEvents(VTooltip.TOOLTIP_EVENTS); - } - - public void setSelected(boolean selected) { - if (selected && isSelectable()) { - addStyleDependentName("selected"); - // needed for IE6 to have a single style name to match for an - // element - // TODO Can be optimized now that IE6 is not supported any more - if (checkable) { - if (checked) { - removeStyleDependentName("selected-unchecked"); - addStyleDependentName("selected-checked"); - } else { - removeStyleDependentName("selected-checked"); - addStyleDependentName("selected-unchecked"); - } - } - } else { - removeStyleDependentName("selected"); - // needed for IE6 to have a single style name to match for an - // element - removeStyleDependentName("selected-checked"); - removeStyleDependentName("selected-unchecked"); - } - } - - public void setChecked(boolean checked) { - if (checkable && !isSeparator) { - this.checked = checked; - - if (checked) { - addStyleDependentName("checked"); - removeStyleDependentName("unchecked"); - } else { - addStyleDependentName("unchecked"); - removeStyleDependentName("checked"); - } - } else { - this.checked = false; - } - } - - public boolean isChecked() { - return checked; - } - - public void setCheckable(boolean checkable) { - if (checkable && !isSeparator) { - this.checkable = true; - } else { - setChecked(false); - this.checkable = false; - } - } - - public boolean isCheckable() { - return checkable; - } - - /* - * setters and getters for the fields - */ - - public void setSubMenu(VMenuBar subMenu) { - this.subMenu = subMenu; - } - - public VMenuBar getSubMenu() { - return subMenu; - } - - public void setParentMenu(VMenuBar parentMenu) { - this.parentMenu = parentMenu; - } - - public VMenuBar getParentMenu() { - return parentMenu; - } - - public void setCommand(Command command) { - this.command = command; - } - - public Command getCommand() { - return command; - } - - public String getHTML() { - return html; - } - - public void setHTML(String html) { - this.html = html; - DOM.setInnerHTML(getElement(), html); - - // Sink the onload event for any icons. The onload - // events are handled by the parent VMenuBar. - Util.sinkOnloadForImages(getElement()); - } - - public String getText() { - return html; - } - - public void setText(String text) { - setHTML(Util.escapeHTML(text)); - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - if (enabled) { - removeStyleDependentName("disabled"); - } else { - addStyleDependentName("disabled"); - } - } - - public boolean isEnabled() { - return enabled; - } - - private void setSeparator(boolean separator) { - isSeparator = separator; - if (separator) { - setStyleName(CLASSNAME + "-separator"); - } else { - setStyleName(CLASSNAME + "-menuitem"); - setEnabled(enabled); - } - } - - public boolean isSeparator() { - return isSeparator; - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - this.client = client; - setSeparator(uidl.hasAttribute("separator")); - setEnabled(!uidl.hasAttribute(ATTRIBUTE_ITEM_DISABLED)); - - if (!isSeparator() && uidl.hasAttribute(ATTRIBUTE_CHECKED)) { - // if the selected attribute is present (either true or false), - // the item is selectable - setCheckable(true); - setChecked(uidl.getBooleanAttribute(ATTRIBUTE_CHECKED)); - } else { - setCheckable(false); - } - - if (uidl.hasAttribute(ATTRIBUTE_ITEM_STYLE)) { - String itemStyle = uidl - .getStringAttribute(ATTRIBUTE_ITEM_STYLE); - addStyleDependentName(itemStyle); - } - - if (uidl.hasAttribute(ATTRIBUTE_ITEM_DESCRIPTION)) { - String description = uidl - .getStringAttribute(ATTRIBUTE_ITEM_DESCRIPTION); - TooltipInfo info = new TooltipInfo(description); - - VMenuBar root = findRootMenu(); - client.registerTooltip(root, this, info); - } - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (client != null) { - client.handleTooltipEvent(event, findRootMenu(), this); - } - } - - private VMenuBar findRootMenu() { - VMenuBar menubar = getParentMenu(); - - // Traverse up until root menu is found - while (menubar.getParentMenu() != null) { - menubar = menubar.getParentMenu(); - } - - return menubar; - } - - /** - * Checks if the item can be selected. - * - * @return true if it is possible to select this item, false otherwise - */ - public boolean isSelectable() { - return !isSeparator() && isEnabled(); - } - - } - - /** - * @author Jouni Koivuviita / Vaadin Ltd. - */ - public void iLayout() { - iLayout(false); - updateSize(); - } - - public void iLayout(boolean iconLoadEvent) { - // Only collapse if there is more than one item in the root menu and the - // menu has an explicit size - if ((getItems().size() > 1 || (collapsedRootItems != null && collapsedRootItems - .getItems().size() > 0)) - && getElement().getStyle().getProperty("width") != null - && moreItem != null) { - - // Measure the width of the "more" item - final boolean morePresent = getItems().contains(moreItem); - addItem(moreItem); - final int moreItemWidth = moreItem.getOffsetWidth(); - if (!morePresent) { - removeItem(moreItem); - } - - int availableWidth = LayoutManager.get(client).getInnerWidth( - getElement()); - - // Used width includes the "more" item if present - int usedWidth = getConsumedWidth(); - int diff = availableWidth - usedWidth; - removeItem(moreItem); - - if (diff < 0) { - // Too many items: collapse last items from root menu - int widthNeeded = usedWidth - availableWidth; - if (!morePresent) { - widthNeeded += moreItemWidth; - } - int widthReduced = 0; - - while (widthReduced < widthNeeded && getItems().size() > 0) { - // Move last root menu item to collapsed menu - CustomMenuItem collapse = getItems().get( - getItems().size() - 1); - widthReduced += collapse.getOffsetWidth(); - removeItem(collapse); - collapsedRootItems.addItem(collapse, 0); - } - } else if (collapsedRootItems.getItems().size() > 0) { - // Space available for items: expand first items from collapsed - // menu - int widthAvailable = diff + moreItemWidth; - int widthGrowth = 0; - - while (widthAvailable > widthGrowth - && collapsedRootItems.getItems().size() > 0) { - // Move first item from collapsed menu to the root menu - CustomMenuItem expand = collapsedRootItems.getItems() - .get(0); - collapsedRootItems.removeItem(expand); - addItem(expand); - widthGrowth += expand.getOffsetWidth(); - if (collapsedRootItems.getItems().size() > 0) { - widthAvailable -= moreItemWidth; - } - if (widthGrowth > widthAvailable) { - removeItem(expand); - collapsedRootItems.addItem(expand, 0); - } else { - widthAvailable = diff + moreItemWidth; - } - } - } - if (collapsedRootItems.getItems().size() > 0) { - addItem(moreItem); - } - } - - // If a popup is open we might need to adjust the shadow as well if an - // icon shown in that popup was loaded - if (popup != null) { - // Forces a recalculation of the shadow size - popup.show(); - } - if (iconLoadEvent) { - // Size have changed if the width is undefined - Util.notifyParentOfSizeChange(this, false); - } - } - - private int getConsumedWidth() { - int w = 0; - for (CustomMenuItem item : getItems()) { - if (!collapsedRootItems.getItems().contains(item)) { - w += item.getOffsetWidth(); - } - } - return w; - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google - * .gwt.event.dom.client.KeyPressEvent) - */ - public void onKeyPress(KeyPressEvent event) { - if (handleNavigation(event.getNativeEvent().getKeyCode(), - event.isControlKeyDown() || event.isMetaKeyDown(), - event.isShiftKeyDown())) { - event.preventDefault(); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt - * .event.dom.client.KeyDownEvent) - */ - public void onKeyDown(KeyDownEvent event) { - if (handleNavigation(event.getNativeEvent().getKeyCode(), - event.isControlKeyDown() || event.isMetaKeyDown(), - event.isShiftKeyDown())) { - event.preventDefault(); - } - } - - /** - * Get the key that moves the selection upwards. By default it is the up - * arrow key but by overriding this you can change the key to whatever you - * want. - * - * @return The keycode of the key - */ - protected int getNavigationUpKey() { - return KeyCodes.KEY_UP; - } - - /** - * Get the key that moves the selection downwards. By default it is the down - * arrow key but by overriding this you can change the key to whatever you - * want. - * - * @return The keycode of the key - */ - protected int getNavigationDownKey() { - return KeyCodes.KEY_DOWN; - } - - /** - * Get the key that moves the selection left. By default it is the left - * arrow key but by overriding this you can change the key to whatever you - * want. - * - * @return The keycode of the key - */ - protected int getNavigationLeftKey() { - return KeyCodes.KEY_LEFT; - } - - /** - * Get the key that moves the selection right. By default it is the right - * arrow key but by overriding this you can change the key to whatever you - * want. - * - * @return The keycode of the key - */ - protected int getNavigationRightKey() { - return KeyCodes.KEY_RIGHT; - } - - /** - * Get the key that selects a menu item. By default it is the Enter key but - * by overriding this you can change the key to whatever you want. - * - * @return - */ - protected int getNavigationSelectKey() { - return KeyCodes.KEY_ENTER; - } - - /** - * Get the key that closes the menu. By default it is the escape key but by - * overriding this yoy can change the key to whatever you want. - * - * @return - */ - protected int getCloseMenuKey() { - return KeyCodes.KEY_ESCAPE; - } - - /** - * Handles the keyboard events handled by the MenuBar - * - * @param event - * The keyboard event received - * @return true iff the navigation event was handled - */ - public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { - - // If tab or shift+tab close menus - if (keycode == KeyCodes.KEY_TAB) { - setSelected(null); - hideChildren(); - menuVisible = false; - return false; - } - - if (ctrl || shift || !isEnabled()) { - // Do not handle tab key, nor ctrl keys - return false; - } - - if (keycode == getNavigationLeftKey()) { - if (getSelected() == null) { - // If nothing is selected then select the last item - setSelected(items.get(items.size() - 1)); - if (!getSelected().isSelectable()) { - handleNavigation(keycode, ctrl, shift); - } - } else if (visibleChildMenu == null && getParentMenu() == null) { - // If this is the root menu then move to the left - int idx = items.indexOf(getSelected()); - if (idx > 0) { - setSelected(items.get(idx - 1)); - } else { - setSelected(items.get(items.size() - 1)); - } - - if (!getSelected().isSelectable()) { - handleNavigation(keycode, ctrl, shift); - } - } else if (visibleChildMenu != null) { - // Redirect all navigation to the submenu - visibleChildMenu.handleNavigation(keycode, ctrl, shift); - - } else if (getParentMenu().getParentMenu() == null) { - // Inside a sub menu, whose parent is a root menu item - VMenuBar root = getParentMenu(); - - root.getSelected().getSubMenu().setSelected(null); - root.hideChildren(); - - // Get the root menus items and select the previous one - int idx = root.getItems().indexOf(root.getSelected()); - idx = idx > 0 ? idx : root.getItems().size(); - CustomMenuItem selected = root.getItems().get(--idx); - - while (selected.isSeparator() || !selected.isEnabled()) { - idx = idx > 0 ? idx : root.getItems().size(); - selected = root.getItems().get(--idx); - } - - root.setSelected(selected); - openMenuAndFocusFirstIfPossible(selected); - } else { - getParentMenu().getSelected().getSubMenu().setSelected(null); - getParentMenu().hideChildren(); - } - - return true; - - } else if (keycode == getNavigationRightKey()) { - - if (getSelected() == null) { - // If nothing is selected then select the first item - setSelected(items.get(0)); - if (!getSelected().isSelectable()) { - handleNavigation(keycode, ctrl, shift); - } - } else if (visibleChildMenu == null && getParentMenu() == null) { - // If this is the root menu then move to the right - int idx = items.indexOf(getSelected()); - - if (idx < items.size() - 1) { - setSelected(items.get(idx + 1)); - } else { - setSelected(items.get(0)); - } - - if (!getSelected().isSelectable()) { - handleNavigation(keycode, ctrl, shift); - } - } else if (visibleChildMenu == null - && getSelected().getSubMenu() != null) { - // If the item has a submenu then show it and move the selection - // there - showChildMenu(getSelected()); - menuVisible = true; - visibleChildMenu.handleNavigation(keycode, ctrl, shift); - } else if (visibleChildMenu == null) { - - // Get the root menu - VMenuBar root = getParentMenu(); - while (root.getParentMenu() != null) { - root = root.getParentMenu(); - } - - // Hide the submenu - root.hideChildren(); - - // Get the root menus items and select the next one - int idx = root.getItems().indexOf(root.getSelected()); - idx = idx < root.getItems().size() - 1 ? idx : -1; - CustomMenuItem selected = root.getItems().get(++idx); - - while (selected.isSeparator() || !selected.isEnabled()) { - idx = idx < root.getItems().size() - 1 ? idx : -1; - selected = root.getItems().get(++idx); - } - - root.setSelected(selected); - openMenuAndFocusFirstIfPossible(selected); - } else if (visibleChildMenu != null) { - // Redirect all navigation to the submenu - visibleChildMenu.handleNavigation(keycode, ctrl, shift); - } - - return true; - - } else if (keycode == getNavigationUpKey()) { - - if (getSelected() == null) { - // If nothing is selected then select the last item - setSelected(items.get(items.size() - 1)); - if (!getSelected().isSelectable()) { - handleNavigation(keycode, ctrl, shift); - } - } else if (visibleChildMenu != null) { - // Redirect all navigation to the submenu - visibleChildMenu.handleNavigation(keycode, ctrl, shift); - } else { - // Select the previous item if possible or loop to the last item - int idx = items.indexOf(getSelected()); - if (idx > 0) { - setSelected(items.get(idx - 1)); - } else { - setSelected(items.get(items.size() - 1)); - } - - if (!getSelected().isSelectable()) { - handleNavigation(keycode, ctrl, shift); - } - } - - return true; - - } else if (keycode == getNavigationDownKey()) { - - if (getSelected() == null) { - // If nothing is selected then select the first item - selectFirstItem(); - } else if (visibleChildMenu == null && getParentMenu() == null) { - // If this is the root menu the show the child menu with arrow - // down, if there is a child menu - openMenuAndFocusFirstIfPossible(getSelected()); - } else if (visibleChildMenu != null) { - // Redirect all navigation to the submenu - visibleChildMenu.handleNavigation(keycode, ctrl, shift); - } else { - // Select the next item if possible or loop to the first item - int idx = items.indexOf(getSelected()); - if (idx < items.size() - 1) { - setSelected(items.get(idx + 1)); - } else { - setSelected(items.get(0)); - } - - if (!getSelected().isSelectable()) { - handleNavigation(keycode, ctrl, shift); - } - } - return true; - - } else if (keycode == getCloseMenuKey()) { - setSelected(null); - hideChildren(); - menuVisible = false; - - } else if (keycode == getNavigationSelectKey()) { - if (getSelected() == null) { - // If nothing is selected then select the first item - selectFirstItem(); - } else if (visibleChildMenu != null) { - // Redirect all navigation to the submenu - visibleChildMenu.handleNavigation(keycode, ctrl, shift); - menuVisible = false; - } else if (visibleChildMenu == null - && getSelected().getSubMenu() != null) { - // If the item has a sub menu then show it and move the - // selection there - openMenuAndFocusFirstIfPossible(getSelected()); - } else { - Command command = getSelected().getCommand(); - if (command != null) { - command.execute(); - } - - setSelected(null); - hideParents(true); - } - } - - return false; - } - - private void selectFirstItem() { - for (int i = 0; i < items.size(); i++) { - CustomMenuItem item = items.get(i); - if (item.isSelectable()) { - setSelected(item); - break; - } - } - } - - private void openMenuAndFocusFirstIfPossible(CustomMenuItem menuItem) { - VMenuBar subMenu = menuItem.getSubMenu(); - if (subMenu == null) { - // No child menu? Nothing to do - return; - } - - VMenuBar parentMenu = menuItem.getParentMenu(); - parentMenu.showChildMenu(menuItem); - - menuVisible = true; - // Select the first item in the newly open submenu - subMenu.selectFirstItem(); - - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event - * .dom.client.FocusEvent) - */ - public void onFocus(FocusEvent event) { - - } - - private final String SUBPART_PREFIX = "item"; - - public Element getSubPartElement(String subPart) { - int index = Integer - .parseInt(subPart.substring(SUBPART_PREFIX.length())); - CustomMenuItem item = getItems().get(index); - - return item.getElement(); - } - - public String getSubPartName(Element subElement) { - if (!getElement().isOrHasChild(subElement)) { - return null; - } - - Element menuItemRoot = subElement; - while (menuItemRoot != null && menuItemRoot.getParentElement() != null - && menuItemRoot.getParentElement() != getElement()) { - menuItemRoot = menuItemRoot.getParentElement().cast(); - } - // "menuItemRoot" is now the root of the menu item - - final int itemCount = getItems().size(); - for (int i = 0; i < itemCount; i++) { - if (getItems().get(i).getElement() == menuItemRoot) { - String name = SUBPART_PREFIX + i; - return name; - } - } - return null; - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java b/src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java deleted file mode 100644 index b3f60c91ad..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VNativeButton.java +++ /dev/null @@ -1,131 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.dom.client.Element; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.Button; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VTooltip; -import com.vaadin.terminal.gwt.client.ui.ButtonConnector.ButtonServerRpc; - -public class VNativeButton extends Button implements ClickHandler { - - public static final String CLASSNAME = "v-nativebutton"; - - protected String width = null; - - protected String paintableId; - - protected ApplicationConnection client; - - ButtonServerRpc buttonRpcProxy; - - protected Element errorIndicatorElement; - - protected final Element captionElement = DOM.createSpan(); - - protected Icon icon; - - /** - * Helper flag to handle special-case where the button is moved from under - * mouse while clicking it. In this case mouse leaves the button without - * moving. - */ - private boolean clickPending; - - protected boolean disableOnClick = false; - - public VNativeButton() { - setStyleName(CLASSNAME); - - getElement().appendChild(captionElement); - captionElement.setClassName(getStyleName() + "-caption"); - - addClickHandler(this); - - sinkEvents(VTooltip.TOOLTIP_EVENTS); - sinkEvents(Event.ONMOUSEDOWN); - sinkEvents(Event.ONMOUSEUP); - } - - @Override - public void setText(String text) { - captionElement.setInnerText(text); - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - - if (DOM.eventGetType(event) == Event.ONLOAD) { - Util.notifyParentOfSizeChange(this, true); - - } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN - && event.getButton() == Event.BUTTON_LEFT) { - clickPending = true; - } else if (DOM.eventGetType(event) == Event.ONMOUSEMOVE) { - clickPending = false; - } else if (DOM.eventGetType(event) == Event.ONMOUSEOUT) { - if (clickPending) { - click(); - } - clickPending = false; - } - - if (client != null) { - client.handleTooltipEvent(event, this); - } - } - - @Override - public void setWidth(String width) { - this.width = width; - super.setWidth(width); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event - * .dom.client.ClickEvent) - */ - public void onClick(ClickEvent event) { - if (paintableId == null || client == null) { - return; - } - - if (BrowserInfo.get().isSafari()) { - VNativeButton.this.setFocus(true); - } - if (disableOnClick) { - setEnabled(false); - buttonRpcProxy.disableOnClick(); - } - - // Add mouse details - MouseEventDetails details = MouseEventDetailsBuilder - .buildMouseEventDetails(event.getNativeEvent(), getElement()); - buttonRpcProxy.click(details); - - clickPending = false; - } - - @Override - public void setEnabled(boolean enabled) { - if (isEnabled() != enabled) { - super.setEnabled(enabled); - setStyleName(ApplicationConnection.DISABLED_CLASSNAME, !enabled); - } - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VNativeSelect.java b/src/com/vaadin/terminal/gwt/client/ui/VNativeSelect.java deleted file mode 100644 index ec2f6e42a1..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VNativeSelect.java +++ /dev/null @@ -1,109 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.Iterator; - -import com.google.gwt.event.dom.client.ChangeEvent; -import com.vaadin.terminal.gwt.client.UIDL; - -public class VNativeSelect extends VOptionGroupBase implements Field { - - public static final String CLASSNAME = "v-select"; - - protected TooltipListBox select; - - private boolean firstValueIsTemporaryNullItem = false; - - public VNativeSelect() { - super(new TooltipListBox(false), CLASSNAME); - select = (TooltipListBox) optionsContainer; - select.setSelect(this); - select.setVisibleItemCount(1); - select.addChangeHandler(this); - select.setStyleName(CLASSNAME + "-select"); - - } - - @Override - protected void buildOptions(UIDL uidl) { - select.setClient(client); - select.setEnabled(!isDisabled() && !isReadonly()); - select.clear(); - firstValueIsTemporaryNullItem = false; - - if (isNullSelectionAllowed() && !isNullSelectionItemAvailable()) { - // can't unselect last item in singleselect mode - select.addItem("", (String) null); - } - boolean selected = false; - for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { - final UIDL optionUidl = (UIDL) i.next(); - select.addItem(optionUidl.getStringAttribute("caption"), - optionUidl.getStringAttribute("key")); - if (optionUidl.hasAttribute("selected")) { - select.setItemSelected(select.getItemCount() - 1, true); - selected = true; - } - } - if (!selected && !isNullSelectionAllowed()) { - // null-select not allowed, but value not selected yet; add null and - // remove when something is selected - select.insertItem("", (String) null, 0); - select.setItemSelected(0, true); - firstValueIsTemporaryNullItem = true; - } - } - - @Override - protected String[] getSelectedItems() { - final ArrayList selectedItemKeys = new ArrayList(); - for (int i = 0; i < select.getItemCount(); i++) { - if (select.isItemSelected(i)) { - selectedItemKeys.add(select.getValue(i)); - } - } - return selectedItemKeys.toArray(new String[selectedItemKeys.size()]); - } - - @Override - public void onChange(ChangeEvent event) { - - if (select.isMultipleSelect()) { - client.updateVariable(paintableId, "selected", getSelectedItems(), - isImmediate()); - } else { - client.updateVariable(paintableId, "selected", new String[] { "" - + getSelectedItem() }, isImmediate()); - } - if (firstValueIsTemporaryNullItem) { - // remove temporary empty item - select.removeItem(0); - firstValueIsTemporaryNullItem = false; - } - } - - @Override - public void setHeight(String height) { - select.setHeight(height); - super.setHeight(height); - } - - @Override - public void setWidth(String width) { - select.setWidth(width); - super.setWidth(width); - } - - @Override - protected void setTabIndex(int tabIndex) { - ((TooltipListBox) optionsContainer).setTabIndex(tabIndex); - } - - public void focus() { - select.setFocus(true); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VNotification.java b/src/com/vaadin/terminal/gwt/client/ui/VNotification.java deleted file mode 100644 index 1fc63e6a56..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VNotification.java +++ /dev/null @@ -1,430 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.Date; -import java.util.EventObject; -import java.util.Iterator; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.KeyCodes; -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.Timer; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.ui.root.VRoot; - -public class VNotification extends VOverlay { - - public static final int CENTERED = 1; - public static final int CENTERED_TOP = 2; - public static final int CENTERED_BOTTOM = 3; - public static final int TOP_LEFT = 4; - public static final int TOP_RIGHT = 5; - public static final int BOTTOM_LEFT = 6; - public static final int BOTTOM_RIGHT = 7; - - public static final int DELAY_FOREVER = -1; - public static final int DELAY_NONE = 0; - - private static final String STYLENAME = "v-Notification"; - private static final int mouseMoveThreshold = 7; - private static final int Z_INDEX_BASE = 20000; - public static final String STYLE_SYSTEM = "system"; - private static final int FADE_ANIMATION_INTERVAL = 50; // == 20 fps - - private int startOpacity = 90; - private int fadeMsec = 400; - private int delayMsec = 1000; - - private Timer fader; - private Timer delay; - - private int x = -1; - private int y = -1; - - private String temporaryStyle; - - private ArrayList listeners; - private static final int TOUCH_DEVICE_IDLE_DELAY = 1000; - - public static final String ATTRIBUTE_NOTIFICATION_STYLE = "style"; - public static final String ATTRIBUTE_NOTIFICATION_CAPTION = "caption"; - public static final String ATTRIBUTE_NOTIFICATION_MESSAGE = "message"; - public static final String ATTRIBUTE_NOTIFICATION_ICON = "icon"; - public static final String ATTRIBUTE_NOTIFICATION_POSITION = "position"; - public static final String ATTRIBUTE_NOTIFICATION_DELAY = "delay"; - - /** - * Default constructor. You should use GWT.create instead. - */ - public VNotification() { - setStyleName(STYLENAME); - sinkEvents(Event.ONCLICK); - DOM.setStyleAttribute(getElement(), "zIndex", "" + Z_INDEX_BASE); - } - - /** - * @deprecated Use static {@link #createNotification(int)} instead to enable - * GWT deferred binding. - * - * @param delayMsec - */ - @Deprecated - public VNotification(int delayMsec) { - this(); - this.delayMsec = delayMsec; - if (BrowserInfo.get().isTouchDevice()) { - new Timer() { - @Override - public void run() { - if (isAttached()) { - fade(); - } - } - }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY); - } - } - - /** - * @deprecated Use static {@link #createNotification(int, int, int)} instead - * to enable GWT deferred binding. - * - * @param delayMsec - * @param fadeMsec - * @param startOpacity - */ - @Deprecated - public VNotification(int delayMsec, int fadeMsec, int startOpacity) { - this(delayMsec); - this.fadeMsec = fadeMsec; - this.startOpacity = startOpacity; - } - - public void startDelay() { - DOM.removeEventPreview(this); - if (delayMsec > 0) { - if (delay == null) { - delay = new Timer() { - @Override - public void run() { - fade(); - } - }; - delay.schedule(delayMsec); - } - } else if (delayMsec == 0) { - fade(); - } - } - - @Override - public void show() { - show(CENTERED); - } - - public void show(String style) { - show(CENTERED, style); - } - - public void show(int position) { - show(position, null); - } - - public void show(Widget widget, int position, String style) { - setWidget(widget); - show(position, style); - } - - public void show(String html, int position, String style) { - setWidget(new HTML(html)); - show(position, style); - } - - public void show(int position, String style) { - setOpacity(getElement(), startOpacity); - if (style != null) { - temporaryStyle = style; - addStyleName(style); - addStyleDependentName(style); - } - super.show(); - setPosition(position); - } - - @Override - public void hide() { - DOM.removeEventPreview(this); - cancelDelay(); - cancelFade(); - if (temporaryStyle != null) { - removeStyleName(temporaryStyle); - removeStyleDependentName(temporaryStyle); - temporaryStyle = null; - } - super.hide(); - fireEvent(new HideEvent(this)); - } - - public void fade() { - DOM.removeEventPreview(this); - cancelDelay(); - if (fader == null) { - fader = new Timer() { - private final long start = new Date().getTime(); - - @Override - public void run() { - /* - * To make animation smooth, don't count that event happens - * on time. Reduce opacity according to the actual time - * spent instead of fixed decrement. - */ - long now = new Date().getTime(); - long timeEplaced = now - start; - float remainingFraction = 1 - timeEplaced - / (float) fadeMsec; - int opacity = (int) (startOpacity * remainingFraction); - if (opacity <= 0) { - cancel(); - hide(); - if (BrowserInfo.get().isOpera()) { - // tray notification on opera needs to explicitly - // define - // size, reset it - DOM.setStyleAttribute(getElement(), "width", ""); - DOM.setStyleAttribute(getElement(), "height", ""); - } - } else { - setOpacity(getElement(), opacity); - } - } - }; - fader.scheduleRepeating(FADE_ANIMATION_INTERVAL); - } - } - - public void setPosition(int position) { - final Element el = getElement(); - DOM.setStyleAttribute(el, "top", ""); - DOM.setStyleAttribute(el, "left", ""); - DOM.setStyleAttribute(el, "bottom", ""); - DOM.setStyleAttribute(el, "right", ""); - switch (position) { - case TOP_LEFT: - DOM.setStyleAttribute(el, "top", "0px"); - DOM.setStyleAttribute(el, "left", "0px"); - break; - case TOP_RIGHT: - DOM.setStyleAttribute(el, "top", "0px"); - DOM.setStyleAttribute(el, "right", "0px"); - break; - case BOTTOM_RIGHT: - DOM.setStyleAttribute(el, "position", "absolute"); - if (BrowserInfo.get().isOpera()) { - // tray notification on opera needs explicitly defined size - DOM.setStyleAttribute(el, "width", getOffsetWidth() + "px"); - DOM.setStyleAttribute(el, "height", getOffsetHeight() + "px"); - } - DOM.setStyleAttribute(el, "bottom", "0px"); - DOM.setStyleAttribute(el, "right", "0px"); - break; - case BOTTOM_LEFT: - DOM.setStyleAttribute(el, "bottom", "0px"); - DOM.setStyleAttribute(el, "left", "0px"); - break; - case CENTERED_TOP: - center(); - DOM.setStyleAttribute(el, "top", "0px"); - break; - case CENTERED_BOTTOM: - center(); - DOM.setStyleAttribute(el, "top", ""); - DOM.setStyleAttribute(el, "bottom", "0px"); - break; - default: - case CENTERED: - center(); - break; - } - } - - private void cancelFade() { - if (fader != null) { - fader.cancel(); - fader = null; - } - } - - private void cancelDelay() { - if (delay != null) { - delay.cancel(); - delay = null; - } - } - - private void setOpacity(Element el, int opacity) { - DOM.setStyleAttribute(el, "opacity", "" + (opacity / 100.0)); - if (BrowserInfo.get().isIE()) { - DOM.setStyleAttribute(el, "filter", "Alpha(opacity=" + opacity - + ")"); - } - } - - @Override - public void onBrowserEvent(Event event) { - DOM.removeEventPreview(this); - if (fader == null) { - fade(); - } - } - - @Override - public boolean onEventPreview(Event event) { - int type = DOM.eventGetType(event); - // "modal" - if (delayMsec == -1 || temporaryStyle == STYLE_SYSTEM) { - if (type == Event.ONCLICK) { - if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) { - fade(); - return false; - } - } else if (type == Event.ONKEYDOWN - && event.getKeyCode() == KeyCodes.KEY_ESCAPE) { - fade(); - return false; - } - if (temporaryStyle == STYLE_SYSTEM) { - return true; - } else { - return false; - } - } - // default - switch (type) { - case Event.ONMOUSEMOVE: - - if (x < 0) { - x = DOM.eventGetClientX(event); - y = DOM.eventGetClientY(event); - } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold - || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) { - startDelay(); - } - break; - case Event.ONMOUSEDOWN: - case Event.ONMOUSEWHEEL: - case Event.ONSCROLL: - startDelay(); - break; - case Event.ONKEYDOWN: - if (event.getRepeat()) { - return true; - } - startDelay(); - break; - default: - break; - } - return true; - } - - public void addEventListener(EventListener listener) { - if (listeners == null) { - listeners = new ArrayList(); - } - listeners.add(listener); - } - - public void removeEventListener(EventListener listener) { - if (listeners == null) { - return; - } - listeners.remove(listener); - } - - private void fireEvent(HideEvent event) { - if (listeners != null) { - for (Iterator it = listeners.iterator(); it - .hasNext();) { - EventListener l = it.next(); - l.notificationHidden(event); - } - } - } - - public static void showNotification(ApplicationConnection client, - final UIDL notification) { - boolean onlyPlainText = notification - .hasAttribute(VRoot.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED); - String html = ""; - if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_ICON)) { - final String parsedUri = client.translateVaadinUri(notification - .getStringAttribute(ATTRIBUTE_NOTIFICATION_ICON)); - html += ""; - } - if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_CAPTION)) { - String caption = notification - .getStringAttribute(ATTRIBUTE_NOTIFICATION_CAPTION); - if (onlyPlainText) { - caption = Util.escapeHTML(caption); - caption = caption.replaceAll("\\n", "
"); - } - html += "

" + caption + "

"; - } - if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_MESSAGE)) { - String message = notification - .getStringAttribute(ATTRIBUTE_NOTIFICATION_MESSAGE); - if (onlyPlainText) { - message = Util.escapeHTML(message); - message = message.replaceAll("\\n", "
"); - } - html += "

" + message + "

"; - } - - final String style = notification - .hasAttribute(ATTRIBUTE_NOTIFICATION_STYLE) ? notification - .getStringAttribute(ATTRIBUTE_NOTIFICATION_STYLE) : null; - final int position = notification - .getIntAttribute(ATTRIBUTE_NOTIFICATION_POSITION); - final int delay = notification - .getIntAttribute(ATTRIBUTE_NOTIFICATION_DELAY); - createNotification(delay).show(html, position, style); - } - - public static VNotification createNotification(int delayMsec) { - final VNotification notification = GWT.create(VNotification.class); - notification.delayMsec = delayMsec; - if (BrowserInfo.get().isTouchDevice()) { - new Timer() { - @Override - public void run() { - if (notification.isAttached()) { - notification.fade(); - } - } - }.schedule(notification.delayMsec + TOUCH_DEVICE_IDLE_DELAY); - } - return notification; - } - - public class HideEvent extends EventObject { - - public HideEvent(Object source) { - super(source); - } - } - - public interface EventListener extends java.util.EventListener { - public void notificationHidden(HideEvent event); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VOptionGroup.java b/src/com/vaadin/terminal/gwt/client/ui/VOptionGroup.java deleted file mode 100644 index 737e67735a..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VOptionGroup.java +++ /dev/null @@ -1,195 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.LoadEvent; -import com.google.gwt.event.dom.client.LoadHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.ui.CheckBox; -import com.google.gwt.user.client.ui.FocusWidget; -import com.google.gwt.user.client.ui.Focusable; -import com.google.gwt.user.client.ui.Panel; -import com.google.gwt.user.client.ui.RadioButton; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; - -public class VOptionGroup extends VOptionGroupBase implements FocusHandler, - BlurHandler { - - public static final String HTML_CONTENT_ALLOWED = "usehtml"; - - public static final String CLASSNAME = "v-select-optiongroup"; - - public static final String ATTRIBUTE_OPTION_DISABLED = "disabled"; - - protected final Panel panel; - - private final Map optionsToKeys; - - protected boolean sendFocusEvents = false; - protected boolean sendBlurEvents = false; - protected List focusHandlers = null; - protected List blurHandlers = null; - - private final LoadHandler iconLoadHandler = new LoadHandler() { - public void onLoad(LoadEvent event) { - Util.notifyParentOfSizeChange(VOptionGroup.this, true); - } - }; - - /** - * used to check whether a blur really was a blur of the complete - * optiongroup: if a control inside this optiongroup gains focus right after - * blur of another control inside this optiongroup (meaning: if onFocus - * fires after onBlur has fired), the blur and focus won't be sent to the - * server side as only a focus change inside this optiongroup occured - */ - private boolean blurOccured = false; - - protected boolean htmlContentAllowed = false; - - public VOptionGroup() { - super(CLASSNAME); - panel = (Panel) optionsContainer; - optionsToKeys = new HashMap(); - } - - /* - * Return true if no elements were changed, false otherwise. - */ - @Override - protected void buildOptions(UIDL uidl) { - panel.clear(); - for (final Iterator it = uidl.getChildIterator(); it.hasNext();) { - final UIDL opUidl = (UIDL) it.next(); - CheckBox op; - - String itemHtml = opUidl.getStringAttribute("caption"); - if (!htmlContentAllowed) { - itemHtml = Util.escapeHTML(itemHtml); - } - - String icon = opUidl.getStringAttribute("icon"); - if (icon != null && icon.length() != 0) { - String iconUrl = client.translateVaadinUri(icon); - itemHtml = "\"\"" + itemHtml; - } - - if (isMultiselect()) { - op = new VCheckBox(); - op.setHTML(itemHtml); - } else { - op = new RadioButton(paintableId, itemHtml, true); - op.setStyleName("v-radiobutton"); - } - - if (icon != null && icon.length() != 0) { - Util.sinkOnloadForImages(op.getElement()); - op.addHandler(iconLoadHandler, LoadEvent.getType()); - } - - op.addStyleName(CLASSNAME_OPTION); - op.setValue(opUidl.getBooleanAttribute("selected")); - boolean enabled = !opUidl - .getBooleanAttribute(ATTRIBUTE_OPTION_DISABLED) - && !isReadonly() && !isDisabled(); - op.setEnabled(enabled); - setStyleName(op.getElement(), - ApplicationConnection.DISABLED_CLASSNAME, !enabled); - op.addClickHandler(this); - optionsToKeys.put(op, opUidl.getStringAttribute("key")); - panel.add(op); - } - } - - @Override - protected String[] getSelectedItems() { - return selectedKeys.toArray(new String[selectedKeys.size()]); - } - - @Override - public void onClick(ClickEvent event) { - super.onClick(event); - if (event.getSource() instanceof CheckBox) { - final boolean selected = ((CheckBox) event.getSource()).getValue(); - final String key = optionsToKeys.get(event.getSource()); - if (!isMultiselect()) { - selectedKeys.clear(); - } - if (selected) { - selectedKeys.add(key); - } else { - selectedKeys.remove(key); - } - client.updateVariable(paintableId, "selected", getSelectedItems(), - isImmediate()); - } - } - - @Override - protected void setTabIndex(int tabIndex) { - for (Iterator iterator = panel.iterator(); iterator.hasNext();) { - FocusWidget widget = (FocusWidget) iterator.next(); - widget.setTabIndex(tabIndex); - } - } - - public void focus() { - Iterator iterator = panel.iterator(); - if (iterator.hasNext()) { - ((Focusable) iterator.next()).setFocus(true); - } - } - - public void onFocus(FocusEvent arg0) { - if (!blurOccured) { - // no blur occured before this focus event - // panel was blurred => fire the event to the server side if - // requested by server side - if (sendFocusEvents) { - client.updateVariable(paintableId, EventId.FOCUS, "", true); - } - } else { - // blur occured before this focus event - // another control inside the panel (checkbox / radio box) was - // blurred => do not fire the focus and set blurOccured to false, so - // blur will not be fired, too - blurOccured = false; - } - } - - public void onBlur(BlurEvent arg0) { - blurOccured = true; - if (sendBlurEvents) { - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - // check whether blurOccured still is true and then send the - // event out to the server - if (blurOccured) { - client.updateVariable(paintableId, EventId.BLUR, "", - true); - blurOccured = false; - } - } - }); - } - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VOptionGroupBase.java b/src/com/vaadin/terminal/gwt/client/ui/VOptionGroupBase.java deleted file mode 100644 index bcd5cc891f..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VOptionGroupBase.java +++ /dev/null @@ -1,165 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Set; - -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyPressEvent; -import com.google.gwt.event.dom.client.KeyPressHandler; -import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.Panel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Focusable; -import com.vaadin.terminal.gwt.client.UIDL; - -abstract class VOptionGroupBase extends Composite implements Field, - ClickHandler, ChangeHandler, KeyPressHandler, Focusable { - - public static final String CLASSNAME_OPTION = "v-select-option"; - - protected ApplicationConnection client; - - protected String paintableId; - - protected Set selectedKeys; - - protected boolean immediate; - - protected boolean multiselect; - - protected boolean disabled; - - protected boolean readonly; - - protected int cols = 0; - - protected int rows = 0; - - protected boolean nullSelectionAllowed = true; - - protected boolean nullSelectionItemAvailable = false; - - /** - * Widget holding the different options (e.g. ListBox or Panel for radio - * buttons) (optional, fallbacks to container Panel) - */ - protected Widget optionsContainer; - - /** - * Panel containing the component - */ - protected final Panel container; - - protected VTextField newItemField; - - protected VNativeButton newItemButton; - - public VOptionGroupBase(String classname) { - container = new FlowPanel(); - initWidget(container); - optionsContainer = container; - container.setStyleName(classname); - immediate = false; - multiselect = false; - } - - /* - * Call this if you wish to specify your own container for the option - * elements (e.g. SELECT) - */ - public VOptionGroupBase(Widget w, String classname) { - this(classname); - optionsContainer = w; - container.add(optionsContainer); - } - - protected boolean isImmediate() { - return immediate; - } - - protected boolean isMultiselect() { - return multiselect; - } - - protected boolean isDisabled() { - return disabled; - } - - protected boolean isReadonly() { - return readonly; - } - - protected boolean isNullSelectionAllowed() { - return nullSelectionAllowed; - } - - protected boolean isNullSelectionItemAvailable() { - return nullSelectionItemAvailable; - } - - /** - * @return "cols" specified in uidl, 0 if not specified - */ - protected int getColumns() { - return cols; - } - - /** - * @return "rows" specified in uidl, 0 if not specified - */ - - protected int getRows() { - return rows; - } - - abstract protected void setTabIndex(int tabIndex); - - public void onClick(ClickEvent event) { - if (event.getSource() == newItemButton - && !newItemField.getText().equals("")) { - client.updateVariable(paintableId, "newitem", - newItemField.getText(), true); - newItemField.setText(""); - } - } - - public void onChange(ChangeEvent event) { - if (multiselect) { - client.updateVariable(paintableId, "selected", getSelectedItems(), - immediate); - } else { - client.updateVariable(paintableId, "selected", new String[] { "" - + getSelectedItem() }, immediate); - } - } - - public void onKeyPress(KeyPressEvent event) { - if (event.getSource() == newItemField - && event.getCharCode() == KeyCodes.KEY_ENTER) { - newItemButton.click(); - } - } - - protected abstract void buildOptions(UIDL uidl); - - protected abstract String[] getSelectedItems(); - - protected String getSelectedItem() { - final String[] sel = getSelectedItems(); - if (sel.length > 0) { - return sel[0]; - } else { - return null; - } - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VOverlay.java b/src/com/vaadin/terminal/gwt/client/ui/VOverlay.java index 3d462fb0c6..df655ef959 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VOverlay.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VOverlay.java @@ -27,7 +27,7 @@ public class VOverlay extends PopupPanel implements CloseHandler { * The z-index value from where all overlays live. This can be overridden in * any extending class. */ - protected static int Z_INDEX = 20000; + public static int Z_INDEX = 20000; private static int leftFix = -1; @@ -249,7 +249,7 @@ public class VOverlay extends PopupPanel implements CloseHandler { * size of overlay without using normal 'setWidth(String)' and * 'setHeight(String)' methods (if not calling super.setWidth/Height). */ - protected void updateShadowSizeAndPosition() { + public void updateShadowSizeAndPosition() { updateShadowSizeAndPosition(1.0); } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPanel.java b/src/com/vaadin/terminal/gwt/client/ui/VPanel.java deleted file mode 100644 index 26eb5cb798..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VPanel.java +++ /dev/null @@ -1,186 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.event.dom.client.TouchStartEvent; -import com.google.gwt.event.dom.client.TouchStartHandler; -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.SimplePanel; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Focusable; -import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; - -public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, - Focusable { - - public static final String CLASSNAME = "v-panel"; - - ApplicationConnection client; - - String id; - - final Element captionNode = DOM.createDiv(); - - private final Element captionText = DOM.createSpan(); - - private Icon icon; - - final Element bottomDecoration = DOM.createDiv(); - - final Element contentNode = DOM.createDiv(); - - private Element errorIndicatorElement; - - ShortcutActionHandler shortcutHandler; - - int scrollTop; - - int scrollLeft; - - private TouchScrollDelegate touchScrollDelegate; - - public VPanel() { - super(); - DivElement captionWrap = Document.get().createDivElement(); - captionWrap.appendChild(captionNode); - captionNode.appendChild(captionText); - - captionWrap.setClassName(CLASSNAME + "-captionwrap"); - captionNode.setClassName(CLASSNAME + "-caption"); - contentNode.setClassName(CLASSNAME + "-content"); - bottomDecoration.setClassName(CLASSNAME + "-deco"); - - getElement().appendChild(captionWrap); - - /* - * Make contentNode focusable only by using the setFocus() method. This - * behaviour can be changed by invoking setTabIndex() in the serverside - * implementation - */ - contentNode.setTabIndex(-1); - - getElement().appendChild(contentNode); - - getElement().appendChild(bottomDecoration); - setStyleName(CLASSNAME); - DOM.sinkEvents(getElement(), Event.ONKEYDOWN); - DOM.sinkEvents(contentNode, Event.ONSCROLL | Event.TOUCHEVENTS); - addHandler(new TouchStartHandler() { - public void onTouchStart(TouchStartEvent event) { - getTouchScrollDelegate().onTouchStart(event); - } - }, TouchStartEvent.getType()); - } - - /** - * Sets the keyboard focus on the Panel - * - * @param focus - * Should the panel have focus or not. - */ - public void setFocus(boolean focus) { - if (focus) { - getContainerElement().focus(); - } else { - getContainerElement().blur(); - } - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.gwt.client.Focusable#focus() - */ - public void focus() { - setFocus(true); - - } - - @Override - protected Element getContainerElement() { - return contentNode; - } - - void setCaption(String text) { - DOM.setInnerHTML(captionText, text); - } - - void setErrorIndicatorVisible(boolean showError) { - if (showError) { - if (errorIndicatorElement == null) { - errorIndicatorElement = DOM.createSpan(); - DOM.setElementProperty(errorIndicatorElement, "className", - "v-errorindicator"); - DOM.sinkEvents(errorIndicatorElement, Event.MOUSEEVENTS); - sinkEvents(Event.MOUSEEVENTS); - } - DOM.insertBefore(captionNode, errorIndicatorElement, captionText); - } else if (errorIndicatorElement != null) { - DOM.removeChild(captionNode, errorIndicatorElement); - errorIndicatorElement = null; - } - } - - void setIconUri(String iconUri, ApplicationConnection client) { - if (iconUri == null) { - if (icon != null) { - DOM.removeChild(captionNode, icon.getElement()); - icon = null; - } - } else { - if (icon == null) { - icon = new Icon(client); - DOM.insertChild(captionNode, icon.getElement(), 0); - } - icon.setUri(iconUri); - } - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - - final Element target = DOM.eventGetTarget(event); - final int type = DOM.eventGetType(event); - if (type == Event.ONKEYDOWN && shortcutHandler != null) { - shortcutHandler.handleKeyboardEvent(event); - return; - } - if (type == Event.ONSCROLL) { - int newscrollTop = DOM.getElementPropertyInt(contentNode, - "scrollTop"); - int newscrollLeft = DOM.getElementPropertyInt(contentNode, - "scrollLeft"); - if (client != null - && (newscrollLeft != scrollLeft || newscrollTop != scrollTop)) { - scrollLeft = newscrollLeft; - scrollTop = newscrollTop; - client.updateVariable(id, "scrollTop", scrollTop, false); - client.updateVariable(id, "scrollLeft", scrollLeft, false); - } - } else if (captionNode.isOrHasChild(target)) { - if (client != null) { - client.handleTooltipEvent(event, this); - } - } - } - - protected TouchScrollDelegate getTouchScrollDelegate() { - if (touchScrollDelegate == null) { - touchScrollDelegate = new TouchScrollDelegate(contentNode); - } - return touchScrollDelegate; - - } - - public ShortcutActionHandler getShortcutActionHandler() { - return shortcutHandler; - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPasswordField.java b/src/com/vaadin/terminal/gwt/client/ui/VPasswordField.java deleted file mode 100644 index 5182457067..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VPasswordField.java +++ /dev/null @@ -1,21 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.user.client.DOM; - -/** - * This class represents a password field. - * - * @author Vaadin Ltd. - * - */ -public class VPasswordField extends VTextField { - - public VPasswordField() { - super(DOM.createInputPassword()); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java b/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java deleted file mode 100644 index cbd3a7af8f..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java +++ /dev/null @@ -1,364 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Date; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.DomEvent; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.logical.shared.CloseEvent; -import com.google.gwt.event.logical.shared.CloseHandler; -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.Timer; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.PopupPanel.PositionCallback; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.VConsole; -import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.FocusOutListener; -import com.vaadin.terminal.gwt.client.ui.VCalendarPanel.SubmitListener; - -/** - * Represents a date selection component with a text field and a popup date - * selector. - * - * Note: To change the keyboard assignments used in the popup dialog you - * should extend com.vaadin.terminal.gwt.client.ui.VCalendarPanel - * and then pass set it by calling the - * setCalendarPanel(VCalendarPanel panel) method. - * - */ -public class VPopupCalendar extends VTextualDate implements Field, - ClickHandler, CloseHandler, SubPartAware { - - protected static final String POPUP_PRIMARY_STYLE_NAME = VDateField.CLASSNAME - + "-popup"; - - protected final Button calendarToggle; - - protected VCalendarPanel calendar; - - protected final VOverlay popup; - private boolean open = false; - protected boolean parsable = true; - - public VPopupCalendar() { - super(); - - calendarToggle = new Button(); - calendarToggle.setStyleName(CLASSNAME + "-button"); - calendarToggle.setText(""); - calendarToggle.addClickHandler(this); - // -2 instead of -1 to avoid FocusWidget.onAttach to reset it - calendarToggle.getElement().setTabIndex(-2); - add(calendarToggle); - - calendar = GWT.create(VCalendarPanel.class); - calendar.setFocusOutListener(new FocusOutListener() { - public boolean onFocusOut(DomEvent event) { - event.preventDefault(); - closeCalendarPanel(); - return true; - } - }); - - calendar.setSubmitListener(new SubmitListener() { - public void onSubmit() { - // Update internal value and send valuechange event if immediate - updateValue(calendar.getDate()); - - // Update text field (a must when not immediate). - buildDate(true); - - closeCalendarPanel(); - } - - public void onCancel() { - closeCalendarPanel(); - } - }); - - popup = new VOverlay(true, true, true); - popup.setStyleName(POPUP_PRIMARY_STYLE_NAME); - popup.setWidget(calendar); - popup.addCloseHandler(this); - - DOM.setElementProperty(calendar.getElement(), "id", - "PID_VAADIN_POPUPCAL"); - - sinkEvents(Event.ONKEYDOWN); - - } - - @SuppressWarnings("deprecation") - protected void updateValue(Date newDate) { - Date currentDate = getCurrentDate(); - if (currentDate == null || newDate.getTime() != currentDate.getTime()) { - setCurrentDate((Date) newDate.clone()); - getClient().updateVariable(getId(), "year", - newDate.getYear() + 1900, false); - if (getCurrentResolution() > VDateField.RESOLUTION_YEAR) { - getClient().updateVariable(getId(), "month", - newDate.getMonth() + 1, false); - if (getCurrentResolution() > RESOLUTION_MONTH) { - getClient().updateVariable(getId(), "day", - newDate.getDate(), false); - if (getCurrentResolution() > RESOLUTION_DAY) { - getClient().updateVariable(getId(), "hour", - newDate.getHours(), false); - if (getCurrentResolution() > RESOLUTION_HOUR) { - getClient().updateVariable(getId(), "min", - newDate.getMinutes(), false); - if (getCurrentResolution() > RESOLUTION_MIN) { - getClient().updateVariable(getId(), "sec", - newDate.getSeconds(), false); - } - } - } - } - } - if (isImmediate()) { - getClient().sendPendingVariableChanges(); - } - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String) - */ - @Override - public void setStyleName(String style) { - // make sure the style is there before size calculation - super.setStyleName(style + " " + CLASSNAME + "-popupcalendar"); - } - - /** - * Opens the calendar panel popup - */ - public void openCalendarPanel() { - - if (!open && !readonly) { - open = true; - - if (getCurrentDate() != null) { - calendar.setDate((Date) getCurrentDate().clone()); - } else { - calendar.setDate(new Date()); - } - - // clear previous values - popup.setWidth(""); - popup.setHeight(""); - popup.setPopupPositionAndShow(new PositionCallback() { - public void setPosition(int offsetWidth, int offsetHeight) { - final int w = offsetWidth; - final int h = offsetHeight; - final int browserWindowWidth = Window.getClientWidth() - + Window.getScrollLeft(); - final int browserWindowHeight = Window.getClientHeight() - + Window.getScrollTop(); - int t = calendarToggle.getAbsoluteTop(); - int l = calendarToggle.getAbsoluteLeft(); - - // Add a little extra space to the right to avoid - // problems with IE7 scrollbars and to make it look - // nicer. - int extraSpace = 30; - - boolean overflowRight = false; - if (l + +w + extraSpace > browserWindowWidth) { - overflowRight = true; - // Part of the popup is outside the browser window - // (to the right) - l = browserWindowWidth - w - extraSpace; - } - - if (t + h + calendarToggle.getOffsetHeight() + 30 > browserWindowHeight) { - // Part of the popup is outside the browser window - // (below) - t = browserWindowHeight - h - - calendarToggle.getOffsetHeight() - 30; - if (!overflowRight) { - // Show to the right of the popup button unless we - // are in the lower right corner of the screen - l += calendarToggle.getOffsetWidth(); - } - } - - // fix size - popup.setWidth(w + "px"); - popup.setHeight(h + "px"); - - popup.setPopupPosition(l, - t + calendarToggle.getOffsetHeight() + 2); - - /* - * We have to wait a while before focusing since the popup - * needs to be opened before we can focus - */ - Timer focusTimer = new Timer() { - @Override - public void run() { - setFocus(true); - } - }; - - focusTimer.schedule(100); - } - }); - } else { - VConsole.error("Cannot reopen popup, it is already open!"); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event - * .dom.client.ClickEvent) - */ - public void onClick(ClickEvent event) { - if (event.getSource() == calendarToggle && isEnabled()) { - openCalendarPanel(); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google.gwt - * .event.logical.shared.CloseEvent) - */ - public void onClose(CloseEvent event) { - if (event.getSource() == popup) { - buildDate(); - if (!BrowserInfo.get().isTouchDevice()) { - /* - * Move focus to textbox, unless on touch device (avoids opening - * virtual keyboard). - */ - focus(); - } - - // TODO resolve what the "Sigh." is all about and document it here - // Sigh. - Timer t = new Timer() { - @Override - public void run() { - open = false; - } - }; - t.schedule(100); - } - } - - /** - * Sets focus to Calendar panel. - * - * @param focus - */ - public void setFocus(boolean focus) { - calendar.setFocus(focus); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.gwt.client.ui.VTextualDate#buildDate() - */ - @Override - protected void buildDate() { - // Save previous value - String previousValue = getText(); - super.buildDate(); - - // Restore previous value if the input could not be parsed - if (!parsable) { - setText(previousValue); - } - } - - /** - * Update the text field contents from the date. See {@link #buildDate()}. - * - * @param forceValid - * true to force the text field to be updated, false to only - * update if the parsable flag is true. - */ - protected void buildDate(boolean forceValid) { - if (forceValid) { - parsable = true; - } - buildDate(); - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.terminal.gwt.client.ui.VDateField#onBrowserEvent(com.google - * .gwt.user.client.Event) - */ - @Override - public void onBrowserEvent(com.google.gwt.user.client.Event event) { - super.onBrowserEvent(event); - if (DOM.eventGetType(event) == Event.ONKEYDOWN - && event.getKeyCode() == getOpenCalenderPanelKey()) { - openCalendarPanel(); - event.preventDefault(); - } - } - - /** - * Get the key code that opens the calendar panel. By default it is the down - * key but you can override this to be whatever you like - * - * @return - */ - protected int getOpenCalenderPanelKey() { - return KeyCodes.KEY_DOWN; - } - - /** - * Closes the open popup panel - */ - public void closeCalendarPanel() { - if (open) { - popup.hide(true); - } - } - - private final String CALENDAR_TOGGLE_ID = "popupButton"; - - @Override - public Element getSubPartElement(String subPart) { - if (subPart.equals(CALENDAR_TOGGLE_ID)) { - return calendarToggle.getElement(); - } - - return super.getSubPartElement(subPart); - } - - @Override - public String getSubPartName(Element subElement) { - if (calendarToggle.getElement().isOrHasChild(subElement)) { - return CALENDAR_TOGGLE_ID; - } - - return super.getSubPartName(subElement); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPopupView.java b/src/com/vaadin/terminal/gwt/client/ui/VPopupView.java deleted file mode 100644 index c13c4339a5..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VPopupView.java +++ /dev/null @@ -1,347 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.logical.shared.CloseEvent; -import com.google.gwt.event.logical.shared.CloseHandler; -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.Focusable; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.HasWidgets; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.RootPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.VCaptionWrapper; -import com.vaadin.terminal.gwt.client.VTooltip; -import com.vaadin.terminal.gwt.client.ui.richtextarea.VRichTextArea; - -public class VPopupView extends HTML { - - public static final String CLASSNAME = "v-popupview"; - - /** For server-client communication */ - String uidlId; - ApplicationConnection client; - - /** This variable helps to communicate popup visibility to the server */ - boolean hostPopupVisible; - - final CustomPopup popup; - private final Label loading = new Label(); - - /** - * loading constructor - */ - public VPopupView() { - super(); - popup = new CustomPopup(); - - setStyleName(CLASSNAME); - popup.setStyleName(CLASSNAME + "-popup"); - loading.setStyleName(CLASSNAME + "-loading"); - - setHTML(""); - popup.setWidget(loading); - - // When we click to open the popup... - addClickHandler(new ClickHandler() { - public void onClick(ClickEvent event) { - updateState(true); - } - }); - - // ..and when we close it - popup.addCloseHandler(new CloseHandler() { - public void onClose(CloseEvent event) { - updateState(false); - } - }); - - popup.setAnimationEnabled(true); - sinkEvents(VTooltip.TOOLTIP_EVENTS); - } - - /** - * Update popup visibility to server - * - * @param visibility - */ - private void updateState(boolean visible) { - // If we know the server connection - // then update the current situation - if (uidlId != null && client != null && isAttached()) { - client.updateVariable(uidlId, "popupVisibility", visible, true); - } - } - - void preparePopup(final CustomPopup popup) { - popup.setVisible(false); - popup.show(); - } - - /** - * Determines the correct position for a popup and displays the popup at - * that position. - * - * By default, the popup is shown centered relative to its host component, - * ensuring it is visible on the screen if possible. - * - * Can be overridden to customize the popup position. - * - * @param popup - */ - protected void showPopup(final CustomPopup popup) { - popup.setPopupPosition(0, 0); - - popup.setVisible(true); - } - - void center() { - int windowTop = RootPanel.get().getAbsoluteTop(); - int windowLeft = RootPanel.get().getAbsoluteLeft(); - int windowRight = windowLeft + RootPanel.get().getOffsetWidth(); - int windowBottom = windowTop + RootPanel.get().getOffsetHeight(); - - int offsetWidth = popup.getOffsetWidth(); - int offsetHeight = popup.getOffsetHeight(); - - int hostHorizontalCenter = VPopupView.this.getAbsoluteLeft() - + VPopupView.this.getOffsetWidth() / 2; - int hostVerticalCenter = VPopupView.this.getAbsoluteTop() - + VPopupView.this.getOffsetHeight() / 2; - - int left = hostHorizontalCenter - offsetWidth / 2; - int top = hostVerticalCenter - offsetHeight / 2; - - // Don't show the popup outside the screen. - if ((left + offsetWidth) > windowRight) { - left -= (left + offsetWidth) - windowRight; - } - - if ((top + offsetHeight) > windowBottom) { - top -= (top + offsetHeight) - windowBottom; - } - - if (left < 0) { - left = 0; - } - - if (top < 0) { - top = 0; - } - - popup.setPopupPosition(left, top); - } - - /** - * Make sure that we remove the popup when the main widget is removed. - * - * @see com.google.gwt.user.client.ui.Widget#onUnload() - */ - @Override - protected void onDetach() { - popup.hide(); - super.onDetach(); - } - - private static native void nativeBlur(Element e) - /*-{ - if(e && e.blur) { - e.blur(); - } - }-*/; - - /** - * This class is only protected to enable overriding showPopup, and is - * currently not intended to be extended or otherwise used directly. Its API - * (other than it being a VOverlay) is to be considered private and - * potentially subject to change. - */ - protected class CustomPopup extends VOverlay { - - private ComponentConnector popupComponentPaintable = null; - Widget popupComponentWidget = null; - VCaptionWrapper captionWrapper = null; - - private boolean hasHadMouseOver = false; - private boolean hideOnMouseOut = true; - private final Set activeChildren = new HashSet(); - private boolean hiding = false; - - public CustomPopup() { - super(true, false, true); // autoHide, not modal, dropshadow - } - - // For some reason ONMOUSEOUT events are not always received, so we have - // to use ONMOUSEMOVE that doesn't target the popup - @Override - public boolean onEventPreview(Event event) { - Element target = DOM.eventGetTarget(event); - boolean eventTargetsPopup = DOM.isOrHasChild(getElement(), target); - int type = DOM.eventGetType(event); - - // Catch children that use keyboard, so we can unfocus them when - // hiding - if (eventTargetsPopup && type == Event.ONKEYPRESS) { - activeChildren.add(target); - } - - if (eventTargetsPopup && type == Event.ONMOUSEMOVE) { - hasHadMouseOver = true; - } - - if (!eventTargetsPopup && type == Event.ONMOUSEMOVE) { - if (hasHadMouseOver && hideOnMouseOut) { - hide(); - return true; - } - } - - // Was the TAB key released outside of our popup? - if (!eventTargetsPopup && type == Event.ONKEYUP - && event.getKeyCode() == KeyCodes.KEY_TAB) { - // Should we hide on focus out (mouse out)? - if (hideOnMouseOut) { - hide(); - return true; - } - } - - return super.onEventPreview(event); - } - - @Override - public void hide(boolean autoClosed) { - hiding = true; - syncChildren(); - if (popupComponentWidget != null && popupComponentWidget != loading) { - remove(popupComponentWidget); - } - hasHadMouseOver = false; - super.hide(autoClosed); - } - - @Override - public void show() { - hiding = false; - super.show(); - } - - /** - * Try to sync all known active child widgets to server - */ - public void syncChildren() { - // Notify children with focus - if ((popupComponentWidget instanceof Focusable)) { - ((Focusable) popupComponentWidget).setFocus(false); - } else { - - checkForRTE(popupComponentWidget); - } - - // Notify children that have used the keyboard - for (Element e : activeChildren) { - try { - nativeBlur(e); - } catch (Exception ignored) { - } - } - activeChildren.clear(); - } - - private void checkForRTE(Widget popupComponentWidget2) { - if (popupComponentWidget2 instanceof VRichTextArea) { - ((VRichTextArea) popupComponentWidget2) - .synchronizeContentToServer(); - } else if (popupComponentWidget2 instanceof HasWidgets) { - HasWidgets hw = (HasWidgets) popupComponentWidget2; - Iterator iterator = hw.iterator(); - while (iterator.hasNext()) { - checkForRTE(iterator.next()); - } - } - } - - @Override - public boolean remove(Widget w) { - - popupComponentPaintable = null; - popupComponentWidget = null; - captionWrapper = null; - - return super.remove(w); - } - - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - ComponentConnector newPopupComponent = client.getPaintable(uidl - .getChildUIDL(0)); - - if (newPopupComponent != popupComponentPaintable) { - Widget newWidget = newPopupComponent.getWidget(); - setWidget(newWidget); - popupComponentWidget = newWidget; - popupComponentPaintable = newPopupComponent; - } - - } - - public void setHideOnMouseOut(boolean hideOnMouseOut) { - this.hideOnMouseOut = hideOnMouseOut; - } - - /* - * - * We need a hack make popup act as a child of VPopupView in Vaadin's - * component tree, but work in default GWT manner when closing or - * opening. - * - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.Widget#getParent() - */ - @Override - public Widget getParent() { - if (!isAttached() || hiding) { - return super.getParent(); - } else { - return VPopupView.this; - } - } - - @Override - protected void onDetach() { - super.onDetach(); - hiding = false; - } - - @Override - public Element getContainerElement() { - return super.getContainerElement(); - } - - }// class CustomPopup - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (client != null) { - client.handleTooltipEvent(event, this); - } - } - -}// class VPopupView diff --git a/src/com/vaadin/terminal/gwt/client/ui/VProgressIndicator.java b/src/com/vaadin/terminal/gwt/client/ui/VProgressIndicator.java deleted file mode 100644 index cff6bf89bd..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VProgressIndicator.java +++ /dev/null @@ -1,71 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.Util; - -public class VProgressIndicator extends Widget { - - public static final String CLASSNAME = "v-progressindicator"; - Element wrapper = DOM.createDiv(); - Element indicator = DOM.createDiv(); - protected ApplicationConnection client; - protected final Poller poller; - protected boolean indeterminate = false; - private boolean pollerSuspendedDueDetach; - protected int interval; - - public VProgressIndicator() { - setElement(DOM.createDiv()); - getElement().appendChild(wrapper); - setStyleName(CLASSNAME); - wrapper.appendChild(indicator); - indicator.setClassName(CLASSNAME + "-indicator"); - wrapper.setClassName(CLASSNAME + "-wrapper"); - poller = new Poller(); - } - - @Override - protected void onAttach() { - super.onAttach(); - if (pollerSuspendedDueDetach) { - poller.scheduleRepeating(interval); - } - } - - @Override - protected void onDetach() { - super.onDetach(); - if (interval > 0) { - poller.cancel(); - pollerSuspendedDueDetach = true; - } - } - - @Override - public void setVisible(boolean visible) { - super.setVisible(visible); - if (!visible) { - poller.cancel(); - } - } - - class Poller extends Timer { - - @Override - public void run() { - if (!client.hasActiveRequest() - && Util.isAttachedAndDisplayed(VProgressIndicator.this)) { - client.sendPendingVariableChanges(); - } - } - - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java deleted file mode 100644 index b372ad7e13..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java +++ /dev/null @@ -1,6676 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.NodeList; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.dom.client.Style.Visibility; -import com.google.gwt.dom.client.TableCellElement; -import com.google.gwt.dom.client.TableRowElement; -import com.google.gwt.dom.client.TableSectionElement; -import com.google.gwt.dom.client.Touch; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ContextMenuEvent; -import com.google.gwt.event.dom.client.ContextMenuHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.KeyPressEvent; -import com.google.gwt.event.dom.client.KeyPressHandler; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; -import com.google.gwt.event.dom.client.ScrollEvent; -import com.google.gwt.event.dom.client.ScrollHandler; -import com.google.gwt.event.dom.client.TouchStartEvent; -import com.google.gwt.event.dom.client.TouchStartHandler; -import com.google.gwt.event.logical.shared.CloseEvent; -import com.google.gwt.event.logical.shared.CloseHandler; -import com.google.gwt.user.client.Command; -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.Timer; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HasWidgets; -import com.google.gwt.user.client.ui.Panel; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.RootPanel; -import com.google.gwt.user.client.ui.UIObject; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.ComponentState; -import com.vaadin.terminal.gwt.client.ConnectorMap; -import com.vaadin.terminal.gwt.client.Focusable; -import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; -import com.vaadin.terminal.gwt.client.TooltipInfo; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VConsole; -import com.vaadin.terminal.gwt.client.VTooltip; -import com.vaadin.terminal.gwt.client.ui.VScrollTable.VScrollTableBody.VScrollTableRow; -import com.vaadin.terminal.gwt.client.ui.dd.DDUtil; -import com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler; -import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCallback; -import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager; -import com.vaadin.terminal.gwt.client.ui.dd.VDragEvent; -import com.vaadin.terminal.gwt.client.ui.dd.VHasDropHandler; -import com.vaadin.terminal.gwt.client.ui.dd.VTransferable; -import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; -import com.vaadin.terminal.gwt.client.ui.label.VLabel; - -/** - * VScrollTable - * - * VScrollTable is a FlowPanel having two widgets in it: * TableHead component * - * ScrollPanel - * - * TableHead contains table's header and widgets + logic for resizing, - * reordering and hiding columns. - * - * ScrollPanel contains VScrollTableBody object which handles content. To save - * some bandwidth and to improve clients responsiveness with loads of data, in - * VScrollTableBody all rows are not necessary rendered. There are "spacers" in - * VScrollTableBody to use the exact same space as non-rendered rows would use. - * This way we can use seamlessly traditional scrollbars and scrolling to fetch - * more rows instead of "paging". - * - * In VScrollTable we listen to scroll events. On horizontal scrolling we also - * update TableHeads scroll position which has its scrollbars hidden. On - * vertical scroll events we will check if we are reaching the end of area where - * we have rows rendered and - * - * TODO implement unregistering for child components in Cells - */ -public class VScrollTable extends FlowPanel implements HasWidgets, - ScrollHandler, VHasDropHandler, FocusHandler, BlurHandler, Focusable, - ActionOwner { - - public enum SelectMode { - NONE(0), SINGLE(1), MULTI(2); - private int id; - - private SelectMode(int id) { - this.id = id; - } - - public int getId() { - return id; - } - } - - private static final String ROW_HEADER_COLUMN_KEY = "0"; - - public static final String CLASSNAME = "v-table"; - public static final String CLASSNAME_SELECTION_FOCUS = CLASSNAME + "-focus"; - - public static final String ATTRIBUTE_PAGEBUFFER_FIRST = "pb-ft"; - public static final String ATTRIBUTE_PAGEBUFFER_LAST = "pb-l"; - - public static final String ITEM_CLICK_EVENT_ID = "itemClick"; - public static final String HEADER_CLICK_EVENT_ID = "handleHeaderClick"; - public static final String FOOTER_CLICK_EVENT_ID = "handleFooterClick"; - public static final String COLUMN_RESIZE_EVENT_ID = "columnResize"; - public static final String COLUMN_REORDER_EVENT_ID = "columnReorder"; - - private static final double CACHE_RATE_DEFAULT = 2; - - /** - * The default multi select mode where simple left clicks only selects one - * item, CTRL+left click selects multiple items and SHIFT-left click selects - * a range of items. - */ - private static final int MULTISELECT_MODE_DEFAULT = 0; - - /** - * The simple multiselect mode is what the table used to have before - * ctrl/shift selections were added. That is that when this is set clicking - * on an item selects/deselects the item and no ctrl/shift selections are - * available. - */ - private static final int MULTISELECT_MODE_SIMPLE = 1; - - /** - * multiple of pagelength which component will cache when requesting more - * rows - */ - private double cache_rate = CACHE_RATE_DEFAULT; - /** - * fraction of pageLenght which can be scrolled without making new request - */ - private double cache_react_rate = 0.75 * cache_rate; - - public static final char ALIGN_CENTER = 'c'; - public static final char ALIGN_LEFT = 'b'; - public static final char ALIGN_RIGHT = 'e'; - private static final int CHARCODE_SPACE = 32; - private int firstRowInViewPort = 0; - private int pageLength = 15; - private int lastRequestedFirstvisible = 0; // to detect "serverside scroll" - - protected boolean showRowHeaders = false; - - private String[] columnOrder; - - protected ApplicationConnection client; - protected String paintableId; - - boolean immediate; - private boolean nullSelectionAllowed = true; - - private SelectMode selectMode = SelectMode.NONE; - - private final HashSet selectedRowKeys = new HashSet(); - - /* - * When scrolling and selecting at the same time, the selections are not in - * sync with the server while retrieving new rows (until key is released). - */ - private HashSet unSyncedselectionsBeforeRowFetch; - - /* - * These are used when jumping between pages when pressing Home and End - */ - boolean selectLastItemInNextRender = false; - boolean selectFirstItemInNextRender = false; - boolean focusFirstItemInNextRender = false; - boolean focusLastItemInNextRender = false; - - /* - * The currently focused row - */ - VScrollTableRow focusedRow; - - /* - * Helper to store selection range start in when using the keyboard - */ - private VScrollTableRow selectionRangeStart; - - /* - * Flag for notifying when the selection has changed and should be sent to - * the server - */ - boolean selectionChanged = false; - - /* - * The speed (in pixels) which the scrolling scrolls vertically/horizontally - */ - private int scrollingVelocity = 10; - - private Timer scrollingVelocityTimer = null; - - String[] bodyActionKeys; - - private boolean enableDebug = false; - - /** - * Represents a select range of rows - */ - private class SelectionRange { - private VScrollTableRow startRow; - private final int length; - - /** - * Constuctor. - */ - public SelectionRange(VScrollTableRow row1, VScrollTableRow row2) { - VScrollTableRow endRow; - if (row2.isBefore(row1)) { - startRow = row2; - endRow = row1; - } else { - startRow = row1; - endRow = row2; - } - length = endRow.getIndex() - startRow.getIndex() + 1; - } - - public SelectionRange(VScrollTableRow row, int length) { - startRow = row; - this.length = length; - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return startRow.getKey() + "-" + length; - } - - private boolean inRange(VScrollTableRow row) { - return row.getIndex() >= startRow.getIndex() - && row.getIndex() < startRow.getIndex() + length; - } - - public Collection split(VScrollTableRow row) { - assert row.isAttached(); - ArrayList ranges = new ArrayList(2); - - int endOfFirstRange = row.getIndex() - 1; - if (!(endOfFirstRange - startRow.getIndex() < 0)) { - // create range of first part unless its length is < 1 - ranges.add(new SelectionRange(startRow, endOfFirstRange - - startRow.getIndex() + 1)); - } - int startOfSecondRange = row.getIndex() + 1; - if (!(getEndIndex() - startOfSecondRange < 0)) { - // create range of second part unless its length is < 1 - VScrollTableRow startOfRange = scrollBody - .getRowByRowIndex(startOfSecondRange); - ranges.add(new SelectionRange(startOfRange, getEndIndex() - - startOfSecondRange + 1)); - } - return ranges; - } - - private int getEndIndex() { - return startRow.getIndex() + length - 1; - } - - }; - - private final HashSet selectedRowRanges = new HashSet(); - - boolean initializedAndAttached = false; - - /** - * Flag to indicate if a column width recalculation is needed due update. - */ - boolean headerChangedDuringUpdate = false; - - protected final TableHead tHead = new TableHead(); - - final TableFooter tFoot = new TableFooter(); - - final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel(true); - - private KeyPressHandler navKeyPressHandler = new KeyPressHandler() { - public void onKeyPress(KeyPressEvent keyPressEvent) { - // This is used for Firefox only, since Firefox auto-repeat - // works correctly only if we use a key press handler, other - // browsers handle it correctly when using a key down handler - if (!BrowserInfo.get().isGecko()) { - return; - } - - NativeEvent event = keyPressEvent.getNativeEvent(); - if (!enabled) { - // Cancel default keyboard events on a disabled Table - // (prevents scrolling) - event.preventDefault(); - } else if (hasFocus) { - // Key code in Firefox/onKeyPress is present only for - // special keys, otherwise 0 is returned - int keyCode = event.getKeyCode(); - if (keyCode == 0 && event.getCharCode() == ' ') { - // Provide a keyCode for space to be compatible with - // FireFox keypress event - keyCode = CHARCODE_SPACE; - } - - if (handleNavigation(keyCode, - event.getCtrlKey() || event.getMetaKey(), - event.getShiftKey())) { - event.preventDefault(); - } - - startScrollingVelocityTimer(); - } - } - - }; - - private KeyUpHandler navKeyUpHandler = new KeyUpHandler() { - - public void onKeyUp(KeyUpEvent keyUpEvent) { - NativeEvent event = keyUpEvent.getNativeEvent(); - int keyCode = event.getKeyCode(); - - if (!isFocusable()) { - cancelScrollingVelocityTimer(); - } else if (isNavigationKey(keyCode)) { - if (keyCode == getNavigationDownKey() - || keyCode == getNavigationUpKey()) { - /* - * in multiselect mode the server may still have value from - * previous page. Clear it unless doing multiselection or - * just moving focus. - */ - if (!event.getShiftKey() && !event.getCtrlKey()) { - instructServerToForgetPreviousSelections(); - } - sendSelectedRows(); - } - cancelScrollingVelocityTimer(); - navKeyDown = false; - } - } - }; - - private KeyDownHandler navKeyDownHandler = new KeyDownHandler() { - - public void onKeyDown(KeyDownEvent keyDownEvent) { - NativeEvent event = keyDownEvent.getNativeEvent(); - // This is not used for Firefox - if (BrowserInfo.get().isGecko()) { - return; - } - - if (!enabled) { - // Cancel default keyboard events on a disabled Table - // (prevents scrolling) - event.preventDefault(); - } else if (hasFocus) { - if (handleNavigation(event.getKeyCode(), event.getCtrlKey() - || event.getMetaKey(), event.getShiftKey())) { - navKeyDown = true; - event.preventDefault(); - } - - startScrollingVelocityTimer(); - } - } - }; - int totalRows; - - private Set collapsedColumns; - - final RowRequestHandler rowRequestHandler; - VScrollTableBody scrollBody; - private int firstvisible = 0; - private boolean sortAscending; - private String sortColumn; - private String oldSortColumn; - private boolean columnReordering; - - /** - * This map contains captions and icon urls for actions like: * "33_c" -> - * "Edit" * "33_i" -> "http://dom.com/edit.png" - */ - private final HashMap actionMap = new HashMap(); - private String[] visibleColOrder; - private boolean initialContentReceived = false; - private Element scrollPositionElement; - boolean enabled; - boolean showColHeaders; - boolean showColFooters; - - /** flag to indicate that table body has changed */ - private boolean isNewBody = true; - - /* - * Read from the "recalcWidths" -attribute. When it is true, the table will - * recalculate the widths for columns - desirable in some cases. For #1983, - * marked experimental. - */ - boolean recalcWidths = false; - - boolean rendering = false; - private boolean hasFocus = false; - private int dragmode; - - private int multiselectmode; - int tabIndex; - private TouchScrollDelegate touchScrollDelegate; - - int lastRenderedHeight; - - /** - * Values (serverCacheFirst+serverCacheLast) sent by server that tells which - * rows (indexes) are in the server side cache (page buffer). -1 means - * unknown. The server side cache row MUST MATCH the client side cache rows. - * - * If the client side cache contains additional rows with e.g. buttons, it - * will cause out of sync when such a button is pressed. - * - * If the server side cache contains additional rows with e.g. buttons, - * scrolling in the client will cause empty buttons to be rendered - * (cached=true request for non-existing components) - */ - int serverCacheFirst = -1; - int serverCacheLast = -1; - - private boolean sizeNeedsInit = true; - - /** - * Used to recall the position of an open context menu if we need to close - * and reopen it during a row update. - */ - class ContextMenuDetails { - String rowKey; - int left; - int top; - - ContextMenuDetails(String rowKey, int left, int top) { - this.rowKey = rowKey; - this.left = left; - this.top = top; - } - } - - protected ContextMenuDetails contextMenu = null; - - public VScrollTable() { - setMultiSelectMode(MULTISELECT_MODE_DEFAULT); - - scrollBodyPanel.setStyleName(CLASSNAME + "-body-wrapper"); - scrollBodyPanel.addFocusHandler(this); - scrollBodyPanel.addBlurHandler(this); - - scrollBodyPanel.addScrollHandler(this); - scrollBodyPanel.setStyleName(CLASSNAME + "-body"); - - /* - * Firefox auto-repeat works correctly only if we use a key press - * handler, other browsers handle it correctly when using a key down - * handler - */ - if (BrowserInfo.get().isGecko()) { - scrollBodyPanel.addKeyPressHandler(navKeyPressHandler); - } else { - scrollBodyPanel.addKeyDownHandler(navKeyDownHandler); - } - scrollBodyPanel.addKeyUpHandler(navKeyUpHandler); - - scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS); - scrollBodyPanel.addDomHandler(new TouchStartHandler() { - public void onTouchStart(TouchStartEvent event) { - getTouchScrollDelegate().onTouchStart(event); - } - }, TouchStartEvent.getType()); - - scrollBodyPanel.sinkEvents(Event.ONCONTEXTMENU); - scrollBodyPanel.addDomHandler(new ContextMenuHandler() { - public void onContextMenu(ContextMenuEvent event) { - handleBodyContextMenu(event); - } - }, ContextMenuEvent.getType()); - - setStyleName(CLASSNAME); - - add(tHead); - add(scrollBodyPanel); - add(tFoot); - - rowRequestHandler = new RowRequestHandler(); - } - - public void init(ApplicationConnection client) { - this.client = client; - // Add a handler to clear saved context menu details when the menu - // closes. See #8526. - client.getContextMenu().addCloseHandler(new CloseHandler() { - public void onClose(CloseEvent event) { - contextMenu = null; - } - }); - } - - protected TouchScrollDelegate getTouchScrollDelegate() { - if (touchScrollDelegate == null) { - touchScrollDelegate = new TouchScrollDelegate( - scrollBodyPanel.getElement()); - } - return touchScrollDelegate; - - } - - private void handleBodyContextMenu(ContextMenuEvent event) { - if (enabled && bodyActionKeys != null) { - int left = Util.getTouchOrMouseClientX(event.getNativeEvent()); - int top = Util.getTouchOrMouseClientY(event.getNativeEvent()); - top += Window.getScrollTop(); - left += Window.getScrollLeft(); - client.getContextMenu().showAt(this, left, top); - - // Only prevent browser context menu if there are action handlers - // registered - event.stopPropagation(); - event.preventDefault(); - } - } - - /** - * Fires a column resize event which sends the resize information to the - * server. - * - * @param columnId - * The columnId of the column which was resized - * @param originalWidth - * The width in pixels of the column before the resize event - * @param newWidth - * The width in pixels of the column after the resize event - */ - private void fireColumnResizeEvent(String columnId, int originalWidth, - int newWidth) { - client.updateVariable(paintableId, "columnResizeEventColumn", columnId, - false); - client.updateVariable(paintableId, "columnResizeEventPrev", - originalWidth, false); - client.updateVariable(paintableId, "columnResizeEventCurr", newWidth, - immediate); - - } - - /** - * Non-immediate variable update of column widths for a collection of - * columns. - * - * @param columns - * the columns to trigger the events for. - */ - private void sendColumnWidthUpdates(Collection columns) { - String[] newSizes = new String[columns.size()]; - int ix = 0; - for (HeaderCell cell : columns) { - newSizes[ix++] = cell.getColKey() + ":" + cell.getWidth(); - } - client.updateVariable(paintableId, "columnWidthUpdates", newSizes, - false); - } - - /** - * Moves the focus one step down - * - * @return Returns true if succeeded - */ - private boolean moveFocusDown() { - return moveFocusDown(0); - } - - /** - * Moves the focus down by 1+offset rows - * - * @return Returns true if succeeded, else false if the selection could not - * be move downwards - */ - private boolean moveFocusDown(int offset) { - if (isSelectable()) { - if (focusedRow == null && scrollBody.iterator().hasNext()) { - // FIXME should focus first visible from top, not first rendered - // ?? - return setRowFocus((VScrollTableRow) scrollBody.iterator() - .next()); - } else { - VScrollTableRow next = getNextRow(focusedRow, offset); - if (next != null) { - return setRowFocus(next); - } - } - } - - return false; - } - - /** - * Moves the selection one step up - * - * @return Returns true if succeeded - */ - private boolean moveFocusUp() { - return moveFocusUp(0); - } - - /** - * Moves the focus row upwards - * - * @return Returns true if succeeded, else false if the selection could not - * be move upwards - * - */ - private boolean moveFocusUp(int offset) { - if (isSelectable()) { - if (focusedRow == null && scrollBody.iterator().hasNext()) { - // FIXME logic is exactly the same as in moveFocusDown, should - // be the opposite?? - return setRowFocus((VScrollTableRow) scrollBody.iterator() - .next()); - } else { - VScrollTableRow prev = getPreviousRow(focusedRow, offset); - if (prev != null) { - return setRowFocus(prev); - } else { - VConsole.log("no previous available"); - } - } - } - - return false; - } - - /** - * Selects a row where the current selection head is - * - * @param ctrlSelect - * Is the selection a ctrl+selection - * @param shiftSelect - * Is the selection a shift+selection - * @return Returns truw - */ - private void selectFocusedRow(boolean ctrlSelect, boolean shiftSelect) { - if (focusedRow != null) { - // Arrows moves the selection and clears previous selections - if (isSelectable() && !ctrlSelect && !shiftSelect) { - deselectAll(); - focusedRow.toggleSelection(); - selectionRangeStart = focusedRow; - } else if (isSelectable() && ctrlSelect && !shiftSelect) { - // Ctrl+arrows moves selection head - selectionRangeStart = focusedRow; - // No selection, only selection head is moved - } else if (isMultiSelectModeAny() && !ctrlSelect && shiftSelect) { - // Shift+arrows selection selects a range - focusedRow.toggleShiftSelection(shiftSelect); - } - } - } - - /** - * Sends the selection to the server if changed since the last update/visit. - */ - protected void sendSelectedRows() { - sendSelectedRows(immediate); - } - - /** - * Sends the selection to the server if it has been changed since the last - * update/visit. - * - * @param immediately - * set to true to immediately send the rows - */ - protected void sendSelectedRows(boolean immediately) { - // Don't send anything if selection has not changed - if (!selectionChanged) { - return; - } - - // Reset selection changed flag - selectionChanged = false; - - // Note: changing the immediateness of this might require changes to - // "clickEvent" immediateness also. - if (isMultiSelectModeDefault()) { - // Convert ranges to a set of strings - Set ranges = new HashSet(); - for (SelectionRange range : selectedRowRanges) { - ranges.add(range.toString()); - } - - // Send the selected row ranges - client.updateVariable(paintableId, "selectedRanges", - ranges.toArray(new String[selectedRowRanges.size()]), false); - - // clean selectedRowKeys so that they don't contain excess values - for (Iterator iterator = selectedRowKeys.iterator(); iterator - .hasNext();) { - String key = iterator.next(); - VScrollTableRow renderedRowByKey = getRenderedRowByKey(key); - if (renderedRowByKey != null) { - for (SelectionRange range : selectedRowRanges) { - if (range.inRange(renderedRowByKey)) { - iterator.remove(); - } - } - } else { - // orphaned selected key, must be in a range, ignore - iterator.remove(); - } - - } - } - - // Send the selected rows - client.updateVariable(paintableId, "selected", - selectedRowKeys.toArray(new String[selectedRowKeys.size()]), - immediately); - - } - - /** - * Get the key that moves the selection head upwards. By default it is the - * up arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationUpKey() { - return KeyCodes.KEY_UP; - } - - /** - * Get the key that moves the selection head downwards. By default it is the - * down arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationDownKey() { - return KeyCodes.KEY_DOWN; - } - - /** - * Get the key that scrolls to the left in the table. By default it is the - * left arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationLeftKey() { - return KeyCodes.KEY_LEFT; - } - - /** - * Get the key that scroll to the right on the table. By default it is the - * right arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationRightKey() { - return KeyCodes.KEY_RIGHT; - } - - /** - * Get the key that selects an item in the table. By default it is the space - * bar key but by overriding this you can change the key to whatever you - * want. - * - * @return - */ - protected int getNavigationSelectKey() { - return CHARCODE_SPACE; - } - - /** - * Get the key the moves the selection one page up in the table. By default - * this is the Page Up key but by overriding this you can change the key to - * whatever you want. - * - * @return - */ - protected int getNavigationPageUpKey() { - return KeyCodes.KEY_PAGEUP; - } - - /** - * Get the key the moves the selection one page down in the table. By - * default this is the Page Down key but by overriding this you can change - * the key to whatever you want. - * - * @return - */ - protected int getNavigationPageDownKey() { - return KeyCodes.KEY_PAGEDOWN; - } - - /** - * Get the key the moves the selection to the beginning of the table. By - * default this is the Home key but by overriding this you can change the - * key to whatever you want. - * - * @return - */ - protected int getNavigationStartKey() { - return KeyCodes.KEY_HOME; - } - - /** - * Get the key the moves the selection to the end of the table. By default - * this is the End key but by overriding this you can change the key to - * whatever you want. - * - * @return - */ - protected int getNavigationEndKey() { - return KeyCodes.KEY_END; - } - - void initializeRows(UIDL uidl, UIDL rowData) { - if (scrollBody != null) { - scrollBody.removeFromParent(); - } - scrollBody = createScrollBody(); - - scrollBody.renderInitialRows(rowData, uidl.getIntAttribute("firstrow"), - uidl.getIntAttribute("rows")); - scrollBodyPanel.add(scrollBody); - - // New body starts scrolled to the left, make sure the header and footer - // are also scrolled to the left - tHead.setHorizontalScrollPosition(0); - tFoot.setHorizontalScrollPosition(0); - - initialContentReceived = true; - sizeNeedsInit = true; - scrollBody.restoreRowVisibility(); - } - - void updateColumnProperties(UIDL uidl) { - updateColumnOrder(uidl); - - updateCollapsedColumns(uidl); - - UIDL vc = uidl.getChildByTagName("visiblecolumns"); - if (vc != null) { - tHead.updateCellsFromUIDL(vc); - tFoot.updateCellsFromUIDL(vc); - } - - updateHeader(uidl.getStringArrayAttribute("vcolorder")); - updateFooter(uidl.getStringArrayAttribute("vcolorder")); - } - - private void updateCollapsedColumns(UIDL uidl) { - if (uidl.hasVariable("collapsedcolumns")) { - tHead.setColumnCollapsingAllowed(true); - collapsedColumns = uidl - .getStringArrayVariableAsSet("collapsedcolumns"); - } else { - tHead.setColumnCollapsingAllowed(false); - } - } - - private void updateColumnOrder(UIDL uidl) { - if (uidl.hasVariable("columnorder")) { - columnReordering = true; - columnOrder = uidl.getStringArrayVariable("columnorder"); - } else { - columnReordering = false; - columnOrder = null; - } - } - - boolean selectSelectedRows(UIDL uidl) { - boolean keyboardSelectionOverRowFetchInProgress = false; - - if (uidl.hasVariable("selected")) { - final Set selectedKeys = uidl - .getStringArrayVariableAsSet("selected"); - if (scrollBody != null) { - Iterator iterator = scrollBody.iterator(); - while (iterator.hasNext()) { - /* - * Make the focus reflect to the server side state unless we - * are currently selecting multiple rows with keyboard. - */ - VScrollTableRow row = (VScrollTableRow) iterator.next(); - boolean selected = selectedKeys.contains(row.getKey()); - if (!selected - && unSyncedselectionsBeforeRowFetch != null - && unSyncedselectionsBeforeRowFetch.contains(row - .getKey())) { - selected = true; - keyboardSelectionOverRowFetchInProgress = true; - } - if (selected != row.isSelected()) { - row.toggleSelection(); - if (!isSingleSelectMode() && !selected) { - // Update selection range in case a row is - // unselected from the middle of a range - #8076 - removeRowFromUnsentSelectionRanges(row); - } - } - } - } - } - unSyncedselectionsBeforeRowFetch = null; - return keyboardSelectionOverRowFetchInProgress; - } - - void updateSortingProperties(UIDL uidl) { - oldSortColumn = sortColumn; - if (uidl.hasVariable("sortascending")) { - sortAscending = uidl.getBooleanVariable("sortascending"); - sortColumn = uidl.getStringVariable("sortcolumn"); - } - } - - void resizeSortedColumnForSortIndicator() { - // Force recalculation of the captionContainer element inside the header - // cell to accomodate for the size of the sort arrow. - HeaderCell sortedHeader = tHead.getHeaderCell(sortColumn); - if (sortedHeader != null) { - tHead.resizeCaptionContainer(sortedHeader); - } - // Also recalculate the width of the captionContainer element in the - // previously sorted header, since this now has more room. - HeaderCell oldSortedHeader = tHead.getHeaderCell(oldSortColumn); - if (oldSortedHeader != null) { - tHead.resizeCaptionContainer(oldSortedHeader); - } - } - - void updateFirstVisibleAndScrollIfNeeded(UIDL uidl) { - firstvisible = uidl.hasVariable("firstvisible") ? uidl - .getIntVariable("firstvisible") : 0; - if (firstvisible != lastRequestedFirstvisible && scrollBody != null) { - // received 'surprising' firstvisible from server: scroll there - firstRowInViewPort = firstvisible; - scrollBodyPanel - .setScrollPosition(measureRowHeightOffset(firstvisible)); - } - } - - protected int measureRowHeightOffset(int rowIx) { - return (int) (rowIx * scrollBody.getRowHeight()); - } - - void updatePageLength(UIDL uidl) { - int oldPageLength = pageLength; - if (uidl.hasAttribute("pagelength")) { - pageLength = uidl.getIntAttribute("pagelength"); - } else { - // pagelenght is "0" meaning scrolling is turned off - pageLength = totalRows; - } - - if (oldPageLength != pageLength && initializedAndAttached) { - // page length changed, need to update size - sizeNeedsInit = true; - } - } - - void updateSelectionProperties(UIDL uidl, ComponentState state, - boolean readOnly) { - setMultiSelectMode(uidl.hasAttribute("multiselectmode") ? uidl - .getIntAttribute("multiselectmode") : MULTISELECT_MODE_DEFAULT); - - nullSelectionAllowed = uidl.hasAttribute("nsa") ? uidl - .getBooleanAttribute("nsa") : true; - - if (uidl.hasAttribute("selectmode")) { - if (readOnly) { - selectMode = SelectMode.NONE; - } else if (uidl.getStringAttribute("selectmode").equals("multi")) { - selectMode = SelectMode.MULTI; - } else if (uidl.getStringAttribute("selectmode").equals("single")) { - selectMode = SelectMode.SINGLE; - } else { - selectMode = SelectMode.NONE; - } - } - } - - void updateDragMode(UIDL uidl) { - dragmode = uidl.hasAttribute("dragmode") ? uidl - .getIntAttribute("dragmode") : 0; - if (BrowserInfo.get().isIE()) { - if (dragmode > 0) { - getElement().setPropertyJSO("onselectstart", - getPreventTextSelectionIEHack()); - } else { - getElement().setPropertyJSO("onselectstart", null); - } - } - } - - protected void updateTotalRows(UIDL uidl) { - int newTotalRows = uidl.getIntAttribute("totalrows"); - if (newTotalRows != getTotalRows()) { - if (scrollBody != null) { - if (getTotalRows() == 0) { - tHead.clear(); - tFoot.clear(); - } - initializedAndAttached = false; - initialContentReceived = false; - isNewBody = true; - } - setTotalRows(newTotalRows); - } - } - - protected void setTotalRows(int newTotalRows) { - totalRows = newTotalRows; - } - - protected int getTotalRows() { - return totalRows; - } - - void focusRowFromBody() { - if (selectedRowKeys.size() == 1) { - // try to focus a row currently selected and in viewport - String selectedRowKey = selectedRowKeys.iterator().next(); - if (selectedRowKey != null) { - VScrollTableRow renderedRow = getRenderedRowByKey(selectedRowKey); - if (renderedRow == null || !renderedRow.isInViewPort()) { - setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort)); - } else { - setRowFocus(renderedRow); - } - } - } else { - // multiselect mode - setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort)); - } - } - - protected VScrollTableBody createScrollBody() { - return new VScrollTableBody(); - } - - /** - * Selects the last row visible in the table - * - * @param focusOnly - * Should the focus only be moved to the last row - */ - void selectLastRenderedRowInViewPort(boolean focusOnly) { - int index = firstRowInViewPort + getFullyVisibleRowCount(); - VScrollTableRow lastRowInViewport = scrollBody.getRowByRowIndex(index); - if (lastRowInViewport == null) { - // this should not happen in normal situations (white space at the - // end of viewport). Select the last rendered as a fallback. - lastRowInViewport = scrollBody.getRowByRowIndex(scrollBody - .getLastRendered()); - if (lastRowInViewport == null) { - return; // empty table - } - } - setRowFocus(lastRowInViewport); - if (!focusOnly) { - selectFocusedRow(false, multiselectPending); - sendSelectedRows(); - } - } - - /** - * Selects the first row visible in the table - * - * @param focusOnly - * Should the focus only be moved to the first row - */ - void selectFirstRenderedRowInViewPort(boolean focusOnly) { - int index = firstRowInViewPort; - VScrollTableRow firstInViewport = scrollBody.getRowByRowIndex(index); - if (firstInViewport == null) { - // this should not happen in normal situations - return; - } - setRowFocus(firstInViewport); - if (!focusOnly) { - selectFocusedRow(false, multiselectPending); - sendSelectedRows(); - } - } - - void setCacheRateFromUIDL(UIDL uidl) { - setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr") - : CACHE_RATE_DEFAULT); - } - - private void setCacheRate(double d) { - if (cache_rate != d) { - cache_rate = d; - cache_react_rate = 0.75 * d; - } - } - - void updateActionMap(UIDL mainUidl) { - UIDL actionsUidl = mainUidl.getChildByTagName("actions"); - if (actionsUidl == null) { - return; - } - - final Iterator it = actionsUidl.getChildIterator(); - while (it.hasNext()) { - final UIDL action = (UIDL) it.next(); - final String key = action.getStringAttribute("key"); - final String caption = action.getStringAttribute("caption"); - actionMap.put(key + "_c", caption); - if (action.hasAttribute("icon")) { - // TODO need some uri handling ?? - actionMap.put(key + "_i", client.translateVaadinUri(action - .getStringAttribute("icon"))); - } else { - actionMap.remove(key + "_i"); - } - } - - } - - public String getActionCaption(String actionKey) { - return actionMap.get(actionKey + "_c"); - } - - public String getActionIcon(String actionKey) { - return actionMap.get(actionKey + "_i"); - } - - private void updateHeader(String[] strings) { - if (strings == null) { - return; - } - - int visibleCols = strings.length; - int colIndex = 0; - if (showRowHeaders) { - tHead.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex); - visibleCols++; - visibleColOrder = new String[visibleCols]; - visibleColOrder[colIndex] = ROW_HEADER_COLUMN_KEY; - colIndex++; - } else { - visibleColOrder = new String[visibleCols]; - tHead.removeCell(ROW_HEADER_COLUMN_KEY); - } - - int i; - for (i = 0; i < strings.length; i++) { - final String cid = strings[i]; - visibleColOrder[colIndex] = cid; - tHead.enableColumn(cid, colIndex); - colIndex++; - } - - tHead.setVisible(showColHeaders); - setContainerHeight(); - - } - - /** - * Updates footers. - *

- * Update headers whould be called before this method is called! - *

- * - * @param strings - */ - private void updateFooter(String[] strings) { - if (strings == null) { - return; - } - - // Add dummy column if row headers are present - int colIndex = 0; - if (showRowHeaders) { - tFoot.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex); - colIndex++; - } else { - tFoot.removeCell(ROW_HEADER_COLUMN_KEY); - } - - int i; - for (i = 0; i < strings.length; i++) { - final String cid = strings[i]; - tFoot.enableColumn(cid, colIndex); - colIndex++; - } - - tFoot.setVisible(showColFooters); - } - - /** - * @param uidl - * which contains row data - * @param firstRow - * first row in data set - * @param reqRows - * amount of rows in data set - */ - void updateBody(UIDL uidl, int firstRow, int reqRows) { - if (uidl == null || reqRows < 1) { - // container is empty, remove possibly existing rows - if (firstRow <= 0) { - while (scrollBody.getLastRendered() > scrollBody.firstRendered) { - scrollBody.unlinkRow(false); - } - scrollBody.unlinkRow(false); - } - return; - } - - scrollBody.renderRows(uidl, firstRow, reqRows); - - discardRowsOutsideCacheWindow(); - } - - void updateRowsInBody(UIDL partialRowUpdates) { - if (partialRowUpdates == null) { - return; - } - int firstRowIx = partialRowUpdates.getIntAttribute("firsturowix"); - int count = partialRowUpdates.getIntAttribute("numurows"); - scrollBody.unlinkRows(firstRowIx, count); - scrollBody.insertRows(partialRowUpdates, firstRowIx, count); - } - - /** - * Updates the internal cache by unlinking rows that fall outside of the - * caching window. - */ - protected void discardRowsOutsideCacheWindow() { - int firstRowToKeep = (int) (firstRowInViewPort - pageLength - * cache_rate); - int lastRowToKeep = (int) (firstRowInViewPort + pageLength + pageLength - * cache_rate); - debug("Client side calculated cache rows to keep: " + firstRowToKeep - + "-" + lastRowToKeep); - - if (serverCacheFirst != -1) { - firstRowToKeep = serverCacheFirst; - lastRowToKeep = serverCacheLast; - debug("Server cache rows that override: " + serverCacheFirst + "-" - + serverCacheLast); - if (firstRowToKeep < scrollBody.getFirstRendered() - || lastRowToKeep > scrollBody.getLastRendered()) { - debug("*** Server wants us to keep " + serverCacheFirst + "-" - + serverCacheLast + " but we only have rows " - + scrollBody.getFirstRendered() + "-" - + scrollBody.getLastRendered() + " rendered!"); - } - } - discardRowsOutsideOf(firstRowToKeep, lastRowToKeep); - - scrollBody.fixSpacers(); - - scrollBody.restoreRowVisibility(); - } - - private void discardRowsOutsideOf(int optimalFirstRow, int optimalLastRow) { - /* - * firstDiscarded and lastDiscarded are only calculated for debug - * purposes - */ - int firstDiscarded = -1, lastDiscarded = -1; - boolean cont = true; - while (cont && scrollBody.getLastRendered() > optimalFirstRow - && scrollBody.getFirstRendered() < optimalFirstRow) { - if (firstDiscarded == -1) { - firstDiscarded = scrollBody.getFirstRendered(); - } - - // removing row from start - cont = scrollBody.unlinkRow(true); - } - if (firstDiscarded != -1) { - lastDiscarded = scrollBody.getFirstRendered() - 1; - debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded); - } - firstDiscarded = lastDiscarded = -1; - - cont = true; - while (cont && scrollBody.getLastRendered() > optimalLastRow) { - if (lastDiscarded == -1) { - lastDiscarded = scrollBody.getLastRendered(); - } - - // removing row from the end - cont = scrollBody.unlinkRow(false); - } - if (lastDiscarded != -1) { - firstDiscarded = scrollBody.getLastRendered() + 1; - debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded); - } - - debug("Now in cache: " + scrollBody.getFirstRendered() + "-" - + scrollBody.getLastRendered()); - } - - /** - * Inserts rows in the table body or removes them from the table body based - * on the commands in the UIDL. - * - * @param partialRowAdditions - * the UIDL containing row updates. - */ - protected void addAndRemoveRows(UIDL partialRowAdditions) { - if (partialRowAdditions == null) { - return; - } - if (partialRowAdditions.hasAttribute("hide")) { - scrollBody.unlinkAndReindexRows( - partialRowAdditions.getIntAttribute("firstprowix"), - partialRowAdditions.getIntAttribute("numprows")); - scrollBody.ensureCacheFilled(); - } else { - if (partialRowAdditions.hasAttribute("delbelow")) { - scrollBody.insertRowsDeleteBelow(partialRowAdditions, - partialRowAdditions.getIntAttribute("firstprowix"), - partialRowAdditions.getIntAttribute("numprows")); - } else { - scrollBody.insertAndReindexRows(partialRowAdditions, - partialRowAdditions.getIntAttribute("firstprowix"), - partialRowAdditions.getIntAttribute("numprows")); - } - } - - discardRowsOutsideCacheWindow(); - } - - /** - * Gives correct column index for given column key ("cid" in UIDL). - * - * @param colKey - * @return column index of visible columns, -1 if column not visible - */ - private int getColIndexByKey(String colKey) { - // return 0 if asked for rowHeaders - if (ROW_HEADER_COLUMN_KEY.equals(colKey)) { - return 0; - } - for (int i = 0; i < visibleColOrder.length; i++) { - if (visibleColOrder[i].equals(colKey)) { - return i; - } - } - return -1; - } - - private boolean isMultiSelectModeSimple() { - return selectMode == SelectMode.MULTI - && multiselectmode == MULTISELECT_MODE_SIMPLE; - } - - private boolean isSingleSelectMode() { - return selectMode == SelectMode.SINGLE; - } - - private boolean isMultiSelectModeAny() { - return selectMode == SelectMode.MULTI; - } - - private boolean isMultiSelectModeDefault() { - return selectMode == SelectMode.MULTI - && multiselectmode == MULTISELECT_MODE_DEFAULT; - } - - private void setMultiSelectMode(int multiselectmode) { - if (BrowserInfo.get().isTouchDevice()) { - // Always use the simple mode for touch devices that do not have - // shift/ctrl keys - this.multiselectmode = MULTISELECT_MODE_SIMPLE; - } else { - this.multiselectmode = multiselectmode; - } - - } - - protected boolean isSelectable() { - return selectMode.getId() > SelectMode.NONE.getId(); - } - - private boolean isCollapsedColumn(String colKey) { - if (collapsedColumns == null) { - return false; - } - if (collapsedColumns.contains(colKey)) { - return true; - } - return false; - } - - private String getColKeyByIndex(int index) { - return tHead.getHeaderCell(index).getColKey(); - } - - private void setColWidth(int colIndex, int w, boolean isDefinedWidth) { - final HeaderCell hcell = tHead.getHeaderCell(colIndex); - - // Make sure that the column grows to accommodate the sort indicator if - // necessary. - if (w < hcell.getMinWidth()) { - w = hcell.getMinWidth(); - } - - // Set header column width - hcell.setWidth(w, isDefinedWidth); - - // Ensure indicators have been taken into account - tHead.resizeCaptionContainer(hcell); - - // Set body column width - scrollBody.setColWidth(colIndex, w); - - // Set footer column width - FooterCell fcell = tFoot.getFooterCell(colIndex); - fcell.setWidth(w, isDefinedWidth); - } - - private int getColWidth(String colKey) { - return tHead.getHeaderCell(colKey).getWidth(); - } - - /** - * Get a rendered row by its key - * - * @param key - * The key to search with - * @return - */ - protected VScrollTableRow getRenderedRowByKey(String key) { - if (scrollBody != null) { - final Iterator it = scrollBody.iterator(); - VScrollTableRow r = null; - while (it.hasNext()) { - r = (VScrollTableRow) it.next(); - if (r.getKey().equals(key)) { - return r; - } - } - } - return null; - } - - /** - * Returns the next row to the given row - * - * @param row - * The row to calculate from - * - * @return The next row or null if no row exists - */ - private VScrollTableRow getNextRow(VScrollTableRow row, int offset) { - final Iterator it = scrollBody.iterator(); - VScrollTableRow r = null; - while (it.hasNext()) { - r = (VScrollTableRow) it.next(); - if (r == row) { - r = null; - while (offset >= 0 && it.hasNext()) { - r = (VScrollTableRow) it.next(); - offset--; - } - return r; - } - } - - return null; - } - - /** - * Returns the previous row from the given row - * - * @param row - * The row to calculate from - * @return The previous row or null if no row exists - */ - private VScrollTableRow getPreviousRow(VScrollTableRow row, int offset) { - final Iterator it = scrollBody.iterator(); - final Iterator offsetIt = scrollBody.iterator(); - VScrollTableRow r = null; - VScrollTableRow prev = null; - while (it.hasNext()) { - r = (VScrollTableRow) it.next(); - if (offset < 0) { - prev = (VScrollTableRow) offsetIt.next(); - } - if (r == row) { - return prev; - } - offset--; - } - - return null; - } - - protected void reOrderColumn(String columnKey, int newIndex) { - - final int oldIndex = getColIndexByKey(columnKey); - - // Change header order - tHead.moveCell(oldIndex, newIndex); - - // Change body order - scrollBody.moveCol(oldIndex, newIndex); - - // Change footer order - tFoot.moveCell(oldIndex, newIndex); - - /* - * Build new columnOrder and update it to server Note that columnOrder - * also contains collapsed columns so we cannot directly build it from - * cells vector Loop the old columnOrder and append in order to new - * array unless on moved columnKey. On new index also put the moved key - * i == index on columnOrder, j == index on newOrder - */ - final String oldKeyOnNewIndex = visibleColOrder[newIndex]; - if (showRowHeaders) { - newIndex--; // columnOrder don't have rowHeader - } - // add back hidden rows, - for (int i = 0; i < columnOrder.length; i++) { - if (columnOrder[i].equals(oldKeyOnNewIndex)) { - break; // break loop at target - } - if (isCollapsedColumn(columnOrder[i])) { - newIndex++; - } - } - // finally we can build the new columnOrder for server - final String[] newOrder = new String[columnOrder.length]; - for (int i = 0, j = 0; j < newOrder.length; i++) { - if (j == newIndex) { - newOrder[j] = columnKey; - j++; - } - if (i == columnOrder.length) { - break; - } - if (columnOrder[i].equals(columnKey)) { - continue; - } - newOrder[j] = columnOrder[i]; - j++; - } - columnOrder = newOrder; - // also update visibleColumnOrder - int i = showRowHeaders ? 1 : 0; - for (int j = 0; j < newOrder.length; j++) { - final String cid = newOrder[j]; - if (!isCollapsedColumn(cid)) { - visibleColOrder[i++] = cid; - } - } - client.updateVariable(paintableId, "columnorder", columnOrder, false); - if (client.hasEventListeners(this, COLUMN_REORDER_EVENT_ID)) { - client.sendPendingVariableChanges(); - } - } - - @Override - protected void onDetach() { - rowRequestHandler.cancel(); - super.onDetach(); - // ensure that scrollPosElement will be detached - if (scrollPositionElement != null) { - final Element parent = DOM.getParent(scrollPositionElement); - if (parent != null) { - DOM.removeChild(parent, scrollPositionElement); - } - } - } - - /** - * Run only once when component is attached and received its initial - * content. This function: - * - * * Syncs headers and bodys "natural widths and saves the values. - * - * * Sets proper width and height - * - * * Makes deferred request to get some cache rows - */ - void sizeInit() { - if (!sizeNeedsInit) { - return; - } - sizeNeedsInit = false; - - scrollBody.setContainerHeight(); - - /* - * We will use browsers table rendering algorithm to find proper column - * widths. If content and header take less space than available, we will - * divide extra space relatively to each column which has not width set. - * - * Overflow pixels are added to last column. - */ - - Iterator headCells = tHead.iterator(); - Iterator footCells = tFoot.iterator(); - int i = 0; - int totalExplicitColumnsWidths = 0; - int total = 0; - float expandRatioDivider = 0; - - final int[] widths = new int[tHead.visibleCells.size()]; - - tHead.enableBrowserIntelligence(); - tFoot.enableBrowserIntelligence(); - - // first loop: collect natural widths - while (headCells.hasNext()) { - final HeaderCell hCell = (HeaderCell) headCells.next(); - final FooterCell fCell = (FooterCell) footCells.next(); - int w = hCell.getWidth(); - if (hCell.isDefinedWidth()) { - // server has defined column width explicitly - totalExplicitColumnsWidths += w; - } else { - if (hCell.getExpandRatio() > 0) { - expandRatioDivider += hCell.getExpandRatio(); - w = 0; - } else { - // get and store greater of header width and column width, - // and - // store it as a minimumn natural col width - int headerWidth = hCell.getNaturalColumnWidth(i); - int footerWidth = fCell.getNaturalColumnWidth(i); - w = headerWidth > footerWidth ? headerWidth : footerWidth; - } - hCell.setNaturalMinimumColumnWidth(w); - fCell.setNaturalMinimumColumnWidth(w); - } - widths[i] = w; - total += w; - i++; - } - - tHead.disableBrowserIntelligence(); - tFoot.disableBrowserIntelligence(); - - boolean willHaveScrollbarz = willHaveScrollbars(); - - // fix "natural" width if width not set - if (isDynamicWidth()) { - int w = total; - w += scrollBody.getCellExtraWidth() * visibleColOrder.length; - if (willHaveScrollbarz) { - w += Util.getNativeScrollbarSize(); - } - setContentWidth(w); - } - - int availW = scrollBody.getAvailableWidth(); - if (BrowserInfo.get().isIE()) { - // Hey IE, are you really sure about this? - availW = scrollBody.getAvailableWidth(); - } - availW -= scrollBody.getCellExtraWidth() * visibleColOrder.length; - - if (willHaveScrollbarz) { - availW -= Util.getNativeScrollbarSize(); - } - - // TODO refactor this code to be the same as in resize timer - boolean needsReLayout = false; - - if (availW > total) { - // natural size is smaller than available space - final int extraSpace = availW - total; - final int totalWidthR = total - totalExplicitColumnsWidths; - int checksum = 0; - needsReLayout = true; - - if (extraSpace == 1) { - // We cannot divide one single pixel so we give it the first - // undefined column - headCells = tHead.iterator(); - i = 0; - checksum = availW; - while (headCells.hasNext()) { - HeaderCell hc = (HeaderCell) headCells.next(); - if (!hc.isDefinedWidth()) { - widths[i]++; - break; - } - i++; - } - - } else if (expandRatioDivider > 0) { - // visible columns have some active expand ratios, excess - // space is divided according to them - headCells = tHead.iterator(); - i = 0; - while (headCells.hasNext()) { - HeaderCell hCell = (HeaderCell) headCells.next(); - if (hCell.getExpandRatio() > 0) { - int w = widths[i]; - final int newSpace = Math.round((extraSpace * (hCell - .getExpandRatio() / expandRatioDivider))); - w += newSpace; - widths[i] = w; - } - checksum += widths[i]; - i++; - } - } else if (totalWidthR > 0) { - // no expand ratios defined, we will share extra space - // relatively to "natural widths" among those without - // explicit width - headCells = tHead.iterator(); - i = 0; - while (headCells.hasNext()) { - HeaderCell hCell = (HeaderCell) headCells.next(); - if (!hCell.isDefinedWidth()) { - int w = widths[i]; - final int newSpace = Math.round((float) extraSpace - * (float) w / totalWidthR); - w += newSpace; - widths[i] = w; - } - checksum += widths[i]; - i++; - } - } - - if (extraSpace > 0 && checksum != availW) { - /* - * There might be in some cases a rounding error of 1px when - * extra space is divided so if there is one then we give the - * first undefined column 1 more pixel - */ - headCells = tHead.iterator(); - i = 0; - while (headCells.hasNext()) { - HeaderCell hc = (HeaderCell) headCells.next(); - if (!hc.isDefinedWidth()) { - widths[i] += availW - checksum; - break; - } - i++; - } - } - - } else { - // bodys size will be more than available and scrollbar will appear - } - - // last loop: set possibly modified values or reset if new tBody - i = 0; - headCells = tHead.iterator(); - while (headCells.hasNext()) { - final HeaderCell hCell = (HeaderCell) headCells.next(); - if (isNewBody || hCell.getWidth() == -1) { - final int w = widths[i]; - setColWidth(i, w, false); - } - i++; - } - - initializedAndAttached = true; - - if (needsReLayout) { - scrollBody.reLayoutComponents(); - } - - updatePageLength(); - - /* - * Fix "natural" height if height is not set. This must be after width - * fixing so the components' widths have been adjusted. - */ - if (isDynamicHeight()) { - /* - * We must force an update of the row height as this point as it - * might have been (incorrectly) calculated earlier - */ - - int bodyHeight; - if (pageLength == totalRows) { - /* - * A hack to support variable height rows when paging is off. - * Generally this is not supported by scrolltable. We want to - * show all rows so the bodyHeight should be equal to the table - * height. - */ - // int bodyHeight = scrollBody.getOffsetHeight(); - bodyHeight = scrollBody.getRequiredHeight(); - } else { - bodyHeight = (int) Math.round(scrollBody.getRowHeight(true) - * pageLength); - } - boolean needsSpaceForHorizontalSrollbar = (total > availW); - if (needsSpaceForHorizontalSrollbar) { - bodyHeight += Util.getNativeScrollbarSize(); - } - scrollBodyPanel.setHeight(bodyHeight + "px"); - Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); - } - - isNewBody = false; - - if (firstvisible > 0) { - scrollBodyPanel - .setScrollPosition(measureRowHeightOffset(firstvisible)); - firstRowInViewPort = firstvisible; - } - - if (enabled) { - // Do we need cache rows - if (scrollBody.getLastRendered() + 1 < firstRowInViewPort - + pageLength + (int) cache_react_rate * pageLength) { - if (totalRows - 1 > scrollBody.getLastRendered()) { - // fetch cache rows - int firstInNewSet = scrollBody.getLastRendered() + 1; - rowRequestHandler.setReqFirstRow(firstInNewSet); - int lastInNewSet = (int) (firstRowInViewPort + pageLength + cache_rate - * pageLength); - if (lastInNewSet > totalRows - 1) { - lastInNewSet = totalRows - 1; - } - rowRequestHandler.setReqRows(lastInNewSet - firstInNewSet - + 1); - rowRequestHandler.deferRowFetch(1); - } - } - } - - /* - * Ensures the column alignments are correct at initial loading.
- * (child components widths are correct) - */ - scrollBody.reLayoutComponents(); - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); - } - }); - - client.doLayout(true); - } - - /** - * Note, this method is not official api although declared as protected. - * Extend at you own risk. - * - * @return true if content area will have scrollbars visible. - */ - protected boolean willHaveScrollbars() { - if (isDynamicHeight()) { - if (pageLength < totalRows) { - return true; - } - } else { - int fakeheight = (int) Math.round(scrollBody.getRowHeight() - * totalRows); - int availableHeight = scrollBodyPanel.getElement().getPropertyInt( - "clientHeight"); - if (fakeheight > availableHeight) { - return true; - } - } - return false; - } - - private void announceScrollPosition() { - if (scrollPositionElement == null) { - scrollPositionElement = DOM.createDiv(); - scrollPositionElement.setClassName(CLASSNAME + "-scrollposition"); - scrollPositionElement.getStyle().setPosition(Position.ABSOLUTE); - scrollPositionElement.getStyle().setDisplay(Display.NONE); - getElement().appendChild(scrollPositionElement); - } - - Style style = scrollPositionElement.getStyle(); - style.setMarginLeft(getElement().getOffsetWidth() / 2 - 80, Unit.PX); - style.setMarginTop(-scrollBodyPanel.getOffsetHeight(), Unit.PX); - - // indexes go from 1-totalRows, as rowheaders in index-mode indicate - int last = (firstRowInViewPort + pageLength); - if (last > totalRows) { - last = totalRows; - } - scrollPositionElement.setInnerHTML("" + (firstRowInViewPort + 1) - + " – " + (last) + "..." + ""); - style.setDisplay(Display.BLOCK); - } - - void hideScrollPositionAnnotation() { - if (scrollPositionElement != null) { - DOM.setStyleAttribute(scrollPositionElement, "display", "none"); - } - } - - boolean isScrollPositionVisible() { - return scrollPositionElement != null - && !scrollPositionElement.getStyle().getDisplay() - .equals(Display.NONE.toString()); - } - - class RowRequestHandler extends Timer { - - private int reqFirstRow = 0; - private int reqRows = 0; - private boolean isRunning = false; - - public void deferRowFetch() { - deferRowFetch(250); - } - - public boolean isRunning() { - return isRunning; - } - - public void deferRowFetch(int msec) { - isRunning = true; - if (reqRows > 0 && reqFirstRow < totalRows) { - schedule(msec); - - // tell scroll position to user if currently "visible" rows are - // not rendered - if (totalRows > pageLength - && ((firstRowInViewPort + pageLength > scrollBody - .getLastRendered()) || (firstRowInViewPort < scrollBody - .getFirstRendered()))) { - announceScrollPosition(); - } else { - hideScrollPositionAnnotation(); - } - } - } - - public void setReqFirstRow(int reqFirstRow) { - if (reqFirstRow < 0) { - reqFirstRow = 0; - } else if (reqFirstRow >= totalRows) { - reqFirstRow = totalRows - 1; - } - this.reqFirstRow = reqFirstRow; - } - - public void setReqRows(int reqRows) { - this.reqRows = reqRows; - } - - @Override - public void run() { - if (client.hasActiveRequest() || navKeyDown) { - // if client connection is busy, don't bother loading it more - VConsole.log("Postponed rowfetch"); - schedule(250); - } else { - - int firstToBeRendered = scrollBody.firstRendered; - if (reqFirstRow < firstToBeRendered) { - firstToBeRendered = reqFirstRow; - } else if (firstRowInViewPort - (int) (cache_rate * pageLength) > firstToBeRendered) { - firstToBeRendered = firstRowInViewPort - - (int) (cache_rate * pageLength); - if (firstToBeRendered < 0) { - firstToBeRendered = 0; - } - } - - int lastToBeRendered = scrollBody.lastRendered; - - if (reqFirstRow + reqRows - 1 > lastToBeRendered) { - lastToBeRendered = reqFirstRow + reqRows - 1; - } else if (firstRowInViewPort + pageLength + pageLength - * cache_rate < lastToBeRendered) { - lastToBeRendered = (firstRowInViewPort + pageLength + (int) (pageLength * cache_rate)); - if (lastToBeRendered >= totalRows) { - lastToBeRendered = totalRows - 1; - } - // due Safari 3.1 bug (see #2607), verify reqrows, original - // problem unknown, but this should catch the issue - if (reqFirstRow + reqRows - 1 > lastToBeRendered) { - reqRows = lastToBeRendered - reqFirstRow; - } - } - - client.updateVariable(paintableId, "firstToBeRendered", - firstToBeRendered, false); - - client.updateVariable(paintableId, "lastToBeRendered", - lastToBeRendered, false); - // remember which firstvisible we requested, in case the server - // has - // a differing opinion - lastRequestedFirstvisible = firstRowInViewPort; - client.updateVariable(paintableId, "firstvisible", - firstRowInViewPort, false); - client.updateVariable(paintableId, "reqfirstrow", reqFirstRow, - false); - client.updateVariable(paintableId, "reqrows", reqRows, true); - - if (selectionChanged) { - unSyncedselectionsBeforeRowFetch = new HashSet( - selectedRowKeys); - } - isRunning = false; - } - } - - public int getReqFirstRow() { - return reqFirstRow; - } - - /** - * Sends request to refresh content at this position. - */ - public void refreshContent() { - isRunning = true; - int first = (int) (firstRowInViewPort - pageLength * cache_rate); - int reqRows = (int) (2 * pageLength * cache_rate + pageLength); - if (first < 0) { - reqRows = reqRows + first; - first = 0; - } - setReqFirstRow(first); - setReqRows(reqRows); - run(); - } - } - - public class HeaderCell extends Widget { - - Element td = DOM.createTD(); - - Element captionContainer = DOM.createDiv(); - - Element sortIndicator = DOM.createDiv(); - - Element colResizeWidget = DOM.createDiv(); - - Element floatingCopyOfHeaderCell; - - private boolean sortable = false; - private final String cid; - private boolean dragging; - - private int dragStartX; - private int colIndex; - private int originalWidth; - - private boolean isResizing; - - private int headerX; - - private boolean moved; - - private int closestSlot; - - private int width = -1; - - private int naturalWidth = -1; - - private char align = ALIGN_LEFT; - - boolean definedWidth = false; - - private float expandRatio = 0; - - private boolean sorted; - - public void setSortable(boolean b) { - sortable = b; - } - - /** - * Makes room for the sorting indicator in case the column that the - * header cell belongs to is sorted. This is done by resizing the width - * of the caption container element by the correct amount - */ - public void resizeCaptionContainer(int rightSpacing) { - int captionContainerWidth = width - - colResizeWidget.getOffsetWidth() - rightSpacing; - - if (td.getClassName().contains("-asc") - || td.getClassName().contains("-desc")) { - // Leave room for the sort indicator - captionContainerWidth -= sortIndicator.getOffsetWidth(); - } - - if (captionContainerWidth < 0) { - rightSpacing += captionContainerWidth; - captionContainerWidth = 0; - } - - captionContainer.getStyle().setPropertyPx("width", - captionContainerWidth); - - // Apply/Remove spacing if defined - if (rightSpacing > 0) { - colResizeWidget.getStyle().setMarginLeft(rightSpacing, Unit.PX); - } else { - colResizeWidget.getStyle().clearMarginLeft(); - } - } - - public void setNaturalMinimumColumnWidth(int w) { - naturalWidth = w; - } - - public HeaderCell(String colId, String headerText) { - cid = colId; - - DOM.setElementProperty(colResizeWidget, "className", CLASSNAME - + "-resizer"); - - setText(headerText); - - DOM.appendChild(td, colResizeWidget); - - DOM.setElementProperty(sortIndicator, "className", CLASSNAME - + "-sort-indicator"); - DOM.appendChild(td, sortIndicator); - - DOM.setElementProperty(captionContainer, "className", CLASSNAME - + "-caption-container"); - - // ensure no clipping initially (problem on column additions) - DOM.setStyleAttribute(captionContainer, "overflow", "visible"); - - DOM.appendChild(td, captionContainer); - - DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK - | Event.ONCONTEXTMENU | Event.TOUCHEVENTS); - - setElement(td); - - setAlign(ALIGN_LEFT); - } - - public void disableAutoWidthCalculation() { - definedWidth = true; - expandRatio = 0; - } - - public void setWidth(int w, boolean ensureDefinedWidth) { - if (ensureDefinedWidth) { - definedWidth = true; - // on column resize expand ratio becomes zero - expandRatio = 0; - } - if (width == -1) { - // go to default mode, clip content if necessary - DOM.setStyleAttribute(captionContainer, "overflow", ""); - } - width = w; - if (w == -1) { - DOM.setStyleAttribute(captionContainer, "width", ""); - setWidth(""); - } else { - tHead.resizeCaptionContainer(this); - - /* - * if we already have tBody, set the header width properly, if - * not defer it. IE will fail with complex float in table header - * unless TD width is not explicitly set. - */ - if (scrollBody != null) { - int tdWidth = width + scrollBody.getCellExtraWidth(); - setWidth(tdWidth + "px"); - } else { - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - int tdWidth = width - + scrollBody.getCellExtraWidth(); - setWidth(tdWidth + "px"); - } - }); - } - } - } - - public void setUndefinedWidth() { - definedWidth = false; - setWidth(-1, false); - } - - /** - * Detects if width is fixed by developer on server side or resized to - * current width by user. - * - * @return true if defined, false if "natural" width - */ - public boolean isDefinedWidth() { - return definedWidth && width >= 0; - } - - public int getWidth() { - return width; - } - - public void setText(String headerText) { - DOM.setInnerHTML(captionContainer, headerText); - } - - public String getColKey() { - return cid; - } - - private void setSorted(boolean sorted) { - this.sorted = sorted; - if (sorted) { - if (sortAscending) { - this.setStyleName(CLASSNAME + "-header-cell-asc"); - } else { - this.setStyleName(CLASSNAME + "-header-cell-desc"); - } - } else { - this.setStyleName(CLASSNAME + "-header-cell"); - } - } - - /** - * Handle column reordering. - */ - @Override - public void onBrowserEvent(Event event) { - if (enabled && event != null) { - if (isResizing - || event.getEventTarget().cast() == colResizeWidget) { - if (dragging - && (event.getTypeInt() == Event.ONMOUSEUP || event - .getTypeInt() == Event.ONTOUCHEND)) { - // Handle releasing column header on spacer #5318 - handleCaptionEvent(event); - } else { - onResizeEvent(event); - } - } else { - /* - * Ensure focus before handling caption event. Otherwise - * variables changed from caption event may be before - * variables from other components that fire variables when - * they lose focus. - */ - if (event.getTypeInt() == Event.ONMOUSEDOWN - || event.getTypeInt() == Event.ONTOUCHSTART) { - scrollBodyPanel.setFocus(true); - } - handleCaptionEvent(event); - boolean stopPropagation = true; - if (event.getTypeInt() == Event.ONCONTEXTMENU - && !client.hasEventListeners(VScrollTable.this, - HEADER_CLICK_EVENT_ID)) { - // Prevent showing the browser's context menu only when - // there is a header click listener. - stopPropagation = false; - } - if (stopPropagation) { - event.stopPropagation(); - event.preventDefault(); - } - } - } - } - - private void createFloatingCopy() { - floatingCopyOfHeaderCell = DOM.createDiv(); - DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td)); - floatingCopyOfHeaderCell = DOM - .getChild(floatingCopyOfHeaderCell, 2); - DOM.setElementProperty(floatingCopyOfHeaderCell, "className", - CLASSNAME + "-header-drag"); - // otherwise might wrap or be cut if narrow column - DOM.setStyleAttribute(floatingCopyOfHeaderCell, "width", "auto"); - updateFloatingCopysPosition(DOM.getAbsoluteLeft(td), - DOM.getAbsoluteTop(td)); - DOM.appendChild(RootPanel.get().getElement(), - floatingCopyOfHeaderCell); - } - - private void updateFloatingCopysPosition(int x, int y) { - x -= DOM.getElementPropertyInt(floatingCopyOfHeaderCell, - "offsetWidth") / 2; - DOM.setStyleAttribute(floatingCopyOfHeaderCell, "left", x + "px"); - if (y > 0) { - DOM.setStyleAttribute(floatingCopyOfHeaderCell, "top", (y + 7) - + "px"); - } - } - - private void hideFloatingCopy() { - DOM.removeChild(RootPanel.get().getElement(), - floatingCopyOfHeaderCell); - floatingCopyOfHeaderCell = null; - } - - /** - * Fires a header click event after the user has clicked a column header - * cell - * - * @param event - * The click event - */ - private void fireHeaderClickedEvent(Event event) { - if (client.hasEventListeners(VScrollTable.this, - HEADER_CLICK_EVENT_ID)) { - MouseEventDetails details = MouseEventDetailsBuilder - .buildMouseEventDetails(event); - client.updateVariable(paintableId, "headerClickEvent", - details.toString(), false); - client.updateVariable(paintableId, "headerClickCID", cid, true); - } - } - - protected void handleCaptionEvent(Event event) { - switch (DOM.eventGetType(event)) { - case Event.ONTOUCHSTART: - case Event.ONMOUSEDOWN: - if (columnReordering - && Util.isTouchEventOrLeftMouseButton(event)) { - if (event.getTypeInt() == Event.ONTOUCHSTART) { - /* - * prevent using this event in e.g. scrolling - */ - event.stopPropagation(); - } - dragging = true; - moved = false; - colIndex = getColIndexByKey(cid); - DOM.setCapture(getElement()); - headerX = tHead.getAbsoluteLeft(); - event.preventDefault(); // prevent selecting text && - // generated touch events - } - break; - case Event.ONMOUSEUP: - case Event.ONTOUCHEND: - case Event.ONTOUCHCANCEL: - if (columnReordering - && Util.isTouchEventOrLeftMouseButton(event)) { - dragging = false; - DOM.releaseCapture(getElement()); - if (moved) { - hideFloatingCopy(); - tHead.removeSlotFocus(); - if (closestSlot != colIndex - && closestSlot != (colIndex + 1)) { - if (closestSlot > colIndex) { - reOrderColumn(cid, closestSlot - 1); - } else { - reOrderColumn(cid, closestSlot); - } - } - } - if (Util.isTouchEvent(event)) { - /* - * Prevent using in e.g. scrolling and prevent generated - * events. - */ - event.preventDefault(); - event.stopPropagation(); - } - } - - if (!moved) { - // mouse event was a click to header -> sort column - if (sortable && Util.isTouchEventOrLeftMouseButton(event)) { - if (sortColumn.equals(cid)) { - // just toggle order - client.updateVariable(paintableId, "sortascending", - !sortAscending, false); - } else { - // set table sorted by this column - client.updateVariable(paintableId, "sortcolumn", - cid, false); - } - // get also cache columns at the same request - scrollBodyPanel.setScrollPosition(0); - firstvisible = 0; - rowRequestHandler.setReqFirstRow(0); - rowRequestHandler.setReqRows((int) (2 * pageLength - * cache_rate + pageLength)); - rowRequestHandler.deferRowFetch(); // some validation + - // defer 250ms - rowRequestHandler.cancel(); // instead of waiting - rowRequestHandler.run(); // run immediately - } - fireHeaderClickedEvent(event); - if (Util.isTouchEvent(event)) { - /* - * Prevent using in e.g. scrolling and prevent generated - * events. - */ - event.preventDefault(); - event.stopPropagation(); - } - break; - } - break; - case Event.ONDBLCLICK: - fireHeaderClickedEvent(event); - break; - case Event.ONTOUCHMOVE: - case Event.ONMOUSEMOVE: - if (dragging && Util.isTouchEventOrLeftMouseButton(event)) { - if (event.getTypeInt() == Event.ONTOUCHMOVE) { - /* - * prevent using this event in e.g. scrolling - */ - event.stopPropagation(); - } - if (!moved) { - createFloatingCopy(); - moved = true; - } - - final int clientX = Util.getTouchOrMouseClientX(event); - final int x = clientX + tHead.hTableWrapper.getScrollLeft(); - int slotX = headerX; - closestSlot = colIndex; - int closestDistance = -1; - int start = 0; - if (showRowHeaders) { - start++; - } - final int visibleCellCount = tHead.getVisibleCellCount(); - for (int i = start; i <= visibleCellCount; i++) { - if (i > 0) { - final String colKey = getColKeyByIndex(i - 1); - slotX += getColWidth(colKey); - } - final int dist = Math.abs(x - slotX); - if (closestDistance == -1 || dist < closestDistance) { - closestDistance = dist; - closestSlot = i; - } - } - tHead.focusSlot(closestSlot); - - updateFloatingCopysPosition(clientX, -1); - } - break; - default: - break; - } - } - - private void onResizeEvent(Event event) { - switch (DOM.eventGetType(event)) { - case Event.ONMOUSEDOWN: - if (!Util.isTouchEventOrLeftMouseButton(event)) { - return; - } - isResizing = true; - DOM.setCapture(getElement()); - dragStartX = DOM.eventGetClientX(event); - colIndex = getColIndexByKey(cid); - originalWidth = getWidth(); - DOM.eventPreventDefault(event); - break; - case Event.ONMOUSEUP: - if (!Util.isTouchEventOrLeftMouseButton(event)) { - return; - } - isResizing = false; - DOM.releaseCapture(getElement()); - tHead.disableAutoColumnWidthCalculation(this); - - // Ensure last header cell is taking into account possible - // column selector - HeaderCell lastCell = tHead.getHeaderCell(tHead - .getVisibleCellCount() - 1); - tHead.resizeCaptionContainer(lastCell); - triggerLazyColumnAdjustment(true); - - fireColumnResizeEvent(cid, originalWidth, getColWidth(cid)); - break; - case Event.ONMOUSEMOVE: - if (!Util.isTouchEventOrLeftMouseButton(event)) { - return; - } - if (isResizing) { - final int deltaX = DOM.eventGetClientX(event) - dragStartX; - if (deltaX == 0) { - return; - } - tHead.disableAutoColumnWidthCalculation(this); - - int newWidth = originalWidth + deltaX; - if (newWidth < getMinWidth()) { - newWidth = getMinWidth(); - } - setColWidth(colIndex, newWidth, true); - triggerLazyColumnAdjustment(false); - forceRealignColumnHeaders(); - } - break; - default: - break; - } - } - - public int getMinWidth() { - int cellExtraWidth = 0; - if (scrollBody != null) { - cellExtraWidth += scrollBody.getCellExtraWidth(); - } - return cellExtraWidth + sortIndicator.getOffsetWidth(); - } - - public String getCaption() { - return DOM.getInnerText(captionContainer); - } - - public boolean isEnabled() { - return getParent() != null; - } - - public void setAlign(char c) { - final String ALIGN_PREFIX = CLASSNAME + "-caption-container-align-"; - if (align != c) { - captionContainer.removeClassName(ALIGN_PREFIX + "center"); - captionContainer.removeClassName(ALIGN_PREFIX + "right"); - captionContainer.removeClassName(ALIGN_PREFIX + "left"); - switch (c) { - case ALIGN_CENTER: - captionContainer.addClassName(ALIGN_PREFIX + "center"); - break; - case ALIGN_RIGHT: - captionContainer.addClassName(ALIGN_PREFIX + "right"); - break; - default: - captionContainer.addClassName(ALIGN_PREFIX + "left"); - break; - } - } - align = c; - } - - public char getAlign() { - return align; - } - - /** - * Detects the natural minimum width for the column of this header cell. - * If column is resized by user or the width is defined by server the - * actual width is returned. Else the natural min width is returned. - * - * @param columnIndex - * column index hint, if -1 (unknown) it will be detected - * - * @return - */ - public int getNaturalColumnWidth(int columnIndex) { - if (isDefinedWidth()) { - return width; - } else { - if (naturalWidth < 0) { - // This is recently revealed column. Try to detect a proper - // value (greater of header and data - // cols) - - int hw = captionContainer.getOffsetWidth() - + scrollBody.getCellExtraWidth(); - if (BrowserInfo.get().isGecko()) { - hw += sortIndicator.getOffsetWidth(); - } - if (columnIndex < 0) { - columnIndex = 0; - for (Iterator it = tHead.iterator(); it - .hasNext(); columnIndex++) { - if (it.next() == this) { - break; - } - } - } - final int cw = scrollBody.getColWidth(columnIndex); - naturalWidth = (hw > cw ? hw : cw); - } - return naturalWidth; - } - } - - public void setExpandRatio(float floatAttribute) { - if (floatAttribute != expandRatio) { - triggerLazyColumnAdjustment(false); - } - expandRatio = floatAttribute; - } - - public float getExpandRatio() { - return expandRatio; - } - - public boolean isSorted() { - return sorted; - } - } - - /** - * HeaderCell that is header cell for row headers. - * - * Reordering disabled and clicking on it resets sorting. - */ - public class RowHeadersHeaderCell extends HeaderCell { - - RowHeadersHeaderCell() { - super(ROW_HEADER_COLUMN_KEY, ""); - this.setStyleName(CLASSNAME + "-header-cell-rowheader"); - } - - @Override - protected void handleCaptionEvent(Event event) { - // NOP: RowHeaders cannot be reordered - // TODO It'd be nice to reset sorting here - } - } - - public class TableHead extends Panel implements ActionOwner { - - private static final int WRAPPER_WIDTH = 900000; - - ArrayList visibleCells = new ArrayList(); - - HashMap availableCells = new HashMap(); - - Element div = DOM.createDiv(); - Element hTableWrapper = DOM.createDiv(); - Element hTableContainer = DOM.createDiv(); - Element table = DOM.createTable(); - Element headerTableBody = DOM.createTBody(); - Element tr = DOM.createTR(); - - private final Element columnSelector = DOM.createDiv(); - - private int focusedSlot = -1; - - public TableHead() { - if (BrowserInfo.get().isIE()) { - table.setPropertyInt("cellSpacing", 0); - } - - DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden"); - DOM.setElementProperty(hTableWrapper, "className", CLASSNAME - + "-header"); - - // TODO move styles to CSS - DOM.setElementProperty(columnSelector, "className", CLASSNAME - + "-column-selector"); - DOM.setStyleAttribute(columnSelector, "display", "none"); - - DOM.appendChild(table, headerTableBody); - DOM.appendChild(headerTableBody, tr); - DOM.appendChild(hTableContainer, table); - DOM.appendChild(hTableWrapper, hTableContainer); - DOM.appendChild(div, hTableWrapper); - DOM.appendChild(div, columnSelector); - setElement(div); - - setStyleName(CLASSNAME + "-header-wrap"); - - DOM.sinkEvents(columnSelector, Event.ONCLICK); - - availableCells.put(ROW_HEADER_COLUMN_KEY, - new RowHeadersHeaderCell()); - } - - public void resizeCaptionContainer(HeaderCell cell) { - HeaderCell lastcell = getHeaderCell(visibleCells.size() - 1); - - // Measure column widths - int columnTotalWidth = 0; - for (Widget w : visibleCells) { - columnTotalWidth += w.getOffsetWidth(); - } - - if (cell == lastcell - && columnSelector.getOffsetWidth() > 0 - && columnTotalWidth >= div.getOffsetWidth() - - columnSelector.getOffsetWidth() - && !hasVerticalScrollbar()) { - // Ensure column caption is visible when placed under the column - // selector widget by shifting and resizing the caption. - int offset = 0; - int diff = div.getOffsetWidth() - columnTotalWidth; - if (diff < columnSelector.getOffsetWidth() && diff > 0) { - // If the difference is less than the column selectors width - // then just offset by the - // difference - offset = columnSelector.getOffsetWidth() - diff; - } else { - // Else offset by the whole column selector - offset = columnSelector.getOffsetWidth(); - } - lastcell.resizeCaptionContainer(offset); - } else { - cell.resizeCaptionContainer(0); - } - } - - @Override - public void clear() { - for (String cid : availableCells.keySet()) { - removeCell(cid); - } - availableCells.clear(); - availableCells.put(ROW_HEADER_COLUMN_KEY, - new RowHeadersHeaderCell()); - } - - public void updateCellsFromUIDL(UIDL uidl) { - Iterator it = uidl.getChildIterator(); - HashSet updated = new HashSet(); - boolean refreshContentWidths = false; - while (it.hasNext()) { - final UIDL col = (UIDL) it.next(); - final String cid = col.getStringAttribute("cid"); - updated.add(cid); - - String caption = buildCaptionHtmlSnippet(col); - HeaderCell c = getHeaderCell(cid); - if (c == null) { - c = new HeaderCell(cid, caption); - availableCells.put(cid, c); - if (initializedAndAttached) { - // we will need a column width recalculation - initializedAndAttached = false; - initialContentReceived = false; - isNewBody = true; - } - } else { - c.setText(caption); - } - - if (col.hasAttribute("sortable")) { - c.setSortable(true); - if (cid.equals(sortColumn)) { - c.setSorted(true); - } else { - c.setSorted(false); - } - } else { - c.setSortable(false); - } - - if (col.hasAttribute("align")) { - c.setAlign(col.getStringAttribute("align").charAt(0)); - } else { - c.setAlign(ALIGN_LEFT); - - } - if (col.hasAttribute("width")) { - final String widthStr = col.getStringAttribute("width"); - // Make sure to accomodate for the sort indicator if - // necessary. - int width = Integer.parseInt(widthStr); - if (width < c.getMinWidth()) { - width = c.getMinWidth(); - } - if (width != c.getWidth() && scrollBody != null) { - // Do a more thorough update if a column is resized from - // the server *after* the header has been properly - // initialized - final int colIx = getColIndexByKey(c.cid); - final int newWidth = width; - Scheduler.get().scheduleDeferred( - new ScheduledCommand() { - public void execute() { - setColWidth(colIx, newWidth, true); - } - }); - refreshContentWidths = true; - } else { - c.setWidth(width, true); - } - } else if (recalcWidths) { - c.setUndefinedWidth(); - } - if (col.hasAttribute("er")) { - c.setExpandRatio(col.getFloatAttribute("er")); - } - if (col.hasAttribute("collapsed")) { - // ensure header is properly removed from parent (case when - // collapsing happens via servers side api) - if (c.isAttached()) { - c.removeFromParent(); - headerChangedDuringUpdate = true; - } - } - } - - if (refreshContentWidths) { - // Recalculate the column sizings if any column has changed - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - public void execute() { - triggerLazyColumnAdjustment(true); - } - }); - } - - // check for orphaned header cells - for (Iterator cit = availableCells.keySet().iterator(); cit - .hasNext();) { - String cid = cit.next(); - if (!updated.contains(cid)) { - removeCell(cid); - cit.remove(); - // we will need a column width recalculation, since columns - // with expand ratios should expand to fill the void. - initializedAndAttached = false; - initialContentReceived = false; - isNewBody = true; - } - } - } - - public void enableColumn(String cid, int index) { - final HeaderCell c = getHeaderCell(cid); - if (!c.isEnabled() || getHeaderCell(index) != c) { - setHeaderCell(index, c); - if (initializedAndAttached) { - headerChangedDuringUpdate = true; - } - } - } - - public int getVisibleCellCount() { - return visibleCells.size(); - } - - public void setHorizontalScrollPosition(int scrollLeft) { - hTableWrapper.setScrollLeft(scrollLeft); - } - - public void setColumnCollapsingAllowed(boolean cc) { - if (cc) { - columnSelector.getStyle().setDisplay(Display.BLOCK); - } else { - columnSelector.getStyle().setDisplay(Display.NONE); - } - } - - public void disableBrowserIntelligence() { - hTableContainer.getStyle().setWidth(WRAPPER_WIDTH, Unit.PX); - } - - public void enableBrowserIntelligence() { - hTableContainer.getStyle().clearWidth(); - } - - public void setHeaderCell(int index, HeaderCell cell) { - if (cell.isEnabled()) { - // we're moving the cell - DOM.removeChild(tr, cell.getElement()); - orphan(cell); - visibleCells.remove(cell); - } - if (index < visibleCells.size()) { - // insert to right slot - DOM.insertChild(tr, cell.getElement(), index); - adopt(cell); - visibleCells.add(index, cell); - } else if (index == visibleCells.size()) { - // simply append - DOM.appendChild(tr, cell.getElement()); - adopt(cell); - visibleCells.add(cell); - } else { - throw new RuntimeException( - "Header cells must be appended in order"); - } - } - - public HeaderCell getHeaderCell(int index) { - if (index >= 0 && index < visibleCells.size()) { - return (HeaderCell) visibleCells.get(index); - } else { - return null; - } - } - - /** - * Get's HeaderCell by it's column Key. - * - * Note that this returns HeaderCell even if it is currently collapsed. - * - * @param cid - * Column key of accessed HeaderCell - * @return HeaderCell - */ - public HeaderCell getHeaderCell(String cid) { - return availableCells.get(cid); - } - - public void moveCell(int oldIndex, int newIndex) { - final HeaderCell hCell = getHeaderCell(oldIndex); - final Element cell = hCell.getElement(); - - visibleCells.remove(oldIndex); - DOM.removeChild(tr, cell); - - DOM.insertChild(tr, cell, newIndex); - visibleCells.add(newIndex, hCell); - } - - public Iterator iterator() { - return visibleCells.iterator(); - } - - @Override - public boolean remove(Widget w) { - if (visibleCells.contains(w)) { - visibleCells.remove(w); - orphan(w); - DOM.removeChild(DOM.getParent(w.getElement()), w.getElement()); - return true; - } - return false; - } - - public void removeCell(String colKey) { - final HeaderCell c = getHeaderCell(colKey); - remove(c); - } - - private void focusSlot(int index) { - removeSlotFocus(); - if (index > 0) { - DOM.setElementProperty( - DOM.getFirstChild(DOM.getChild(tr, index - 1)), - "className", CLASSNAME + "-resizer " + CLASSNAME - + "-focus-slot-right"); - } else { - DOM.setElementProperty( - DOM.getFirstChild(DOM.getChild(tr, index)), - "className", CLASSNAME + "-resizer " + CLASSNAME - + "-focus-slot-left"); - } - focusedSlot = index; - } - - private void removeSlotFocus() { - if (focusedSlot < 0) { - return; - } - if (focusedSlot == 0) { - DOM.setElementProperty( - DOM.getFirstChild(DOM.getChild(tr, focusedSlot)), - "className", CLASSNAME + "-resizer"); - } else if (focusedSlot > 0) { - DOM.setElementProperty( - DOM.getFirstChild(DOM.getChild(tr, focusedSlot - 1)), - "className", CLASSNAME + "-resizer"); - } - focusedSlot = -1; - } - - @Override - public void onBrowserEvent(Event event) { - if (enabled) { - if (event.getEventTarget().cast() == columnSelector) { - final int left = DOM.getAbsoluteLeft(columnSelector); - final int top = DOM.getAbsoluteTop(columnSelector) - + DOM.getElementPropertyInt(columnSelector, - "offsetHeight"); - client.getContextMenu().showAt(this, left, top); - } - } - } - - @Override - protected void onDetach() { - super.onDetach(); - if (client != null) { - client.getContextMenu().ensureHidden(this); - } - } - - class VisibleColumnAction extends Action { - - String colKey; - private boolean collapsed; - private VScrollTableRow currentlyFocusedRow; - - public VisibleColumnAction(String colKey) { - super(VScrollTable.TableHead.this); - this.colKey = colKey; - caption = tHead.getHeaderCell(colKey).getCaption(); - currentlyFocusedRow = focusedRow; - } - - @Override - public void execute() { - client.getContextMenu().hide(); - // toggle selected column - if (collapsedColumns.contains(colKey)) { - collapsedColumns.remove(colKey); - } else { - tHead.removeCell(colKey); - collapsedColumns.add(colKey); - triggerLazyColumnAdjustment(true); - } - - // update variable to server - client.updateVariable(paintableId, "collapsedcolumns", - collapsedColumns.toArray(new String[collapsedColumns - .size()]), false); - // let rowRequestHandler determine proper rows - rowRequestHandler.refreshContent(); - lazyRevertFocusToRow(currentlyFocusedRow); - } - - public void setCollapsed(boolean b) { - collapsed = b; - } - - /** - * Override default method to distinguish on/off columns - */ - @Override - public String getHTML() { - final StringBuffer buf = new StringBuffer(); - if (collapsed) { - buf.append(""); - } else { - buf.append(""); - } - buf.append(super.getHTML()); - buf.append(""); - - return buf.toString(); - } - - } - - /* - * Returns columns as Action array for column select popup - */ - public Action[] getActions() { - Object[] cols; - if (columnReordering && columnOrder != null) { - cols = columnOrder; - } else { - // if columnReordering is disabled, we need different way to get - // all available columns - cols = visibleColOrder; - cols = new Object[visibleColOrder.length - + collapsedColumns.size()]; - int i; - for (i = 0; i < visibleColOrder.length; i++) { - cols[i] = visibleColOrder[i]; - } - for (final Iterator it = collapsedColumns.iterator(); it - .hasNext();) { - cols[i++] = it.next(); - } - } - final Action[] actions = new Action[cols.length]; - - for (int i = 0; i < cols.length; i++) { - final String cid = (String) cols[i]; - final HeaderCell c = getHeaderCell(cid); - final VisibleColumnAction a = new VisibleColumnAction( - c.getColKey()); - a.setCaption(c.getCaption()); - if (!c.isEnabled()) { - a.setCollapsed(true); - } - actions[i] = a; - } - return actions; - } - - public ApplicationConnection getClient() { - return client; - } - - public String getPaintableId() { - return paintableId; - } - - /** - * Returns column alignments for visible columns - */ - public char[] getColumnAlignments() { - final Iterator it = visibleCells.iterator(); - final char[] aligns = new char[visibleCells.size()]; - int colIndex = 0; - while (it.hasNext()) { - aligns[colIndex++] = ((HeaderCell) it.next()).getAlign(); - } - return aligns; - } - - /** - * Disables the automatic calculation of all column widths by forcing - * the widths to be "defined" thus turning off expand ratios and such. - */ - public void disableAutoColumnWidthCalculation(HeaderCell source) { - for (HeaderCell cell : availableCells.values()) { - cell.disableAutoWidthCalculation(); - } - // fire column resize events for all columns but the source of the - // resize action, since an event will fire separately for this. - ArrayList columns = new ArrayList( - availableCells.values()); - columns.remove(source); - sendColumnWidthUpdates(columns); - forceRealignColumnHeaders(); - } - } - - /** - * A cell in the footer - */ - public class FooterCell extends Widget { - private final Element td = DOM.createTD(); - private final Element captionContainer = DOM.createDiv(); - private char align = ALIGN_LEFT; - private int width = -1; - private float expandRatio = 0; - private final String cid; - boolean definedWidth = false; - private int naturalWidth = -1; - - public FooterCell(String colId, String headerText) { - cid = colId; - - setText(headerText); - - DOM.setElementProperty(captionContainer, "className", CLASSNAME - + "-footer-container"); - - // ensure no clipping initially (problem on column additions) - DOM.setStyleAttribute(captionContainer, "overflow", "visible"); - - DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS); - - DOM.appendChild(td, captionContainer); - - DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK - | Event.ONCONTEXTMENU); - - setElement(td); - } - - /** - * Sets the text of the footer - * - * @param footerText - * The text in the footer - */ - public void setText(String footerText) { - DOM.setInnerHTML(captionContainer, footerText); - } - - /** - * Set alignment of the text in the cell - * - * @param c - * The alignment which can be ALIGN_CENTER, ALIGN_LEFT, - * ALIGN_RIGHT - */ - public void setAlign(char c) { - if (align != c) { - switch (c) { - case ALIGN_CENTER: - DOM.setStyleAttribute(captionContainer, "textAlign", - "center"); - break; - case ALIGN_RIGHT: - DOM.setStyleAttribute(captionContainer, "textAlign", - "right"); - break; - default: - DOM.setStyleAttribute(captionContainer, "textAlign", ""); - break; - } - } - align = c; - } - - /** - * Get the alignment of the text int the cell - * - * @return Returns either ALIGN_CENTER, ALIGN_LEFT or ALIGN_RIGHT - */ - public char getAlign() { - return align; - } - - /** - * Sets the width of the cell - * - * @param w - * The width of the cell - * @param ensureDefinedWidth - * Ensures the the given width is not recalculated - */ - public void setWidth(int w, boolean ensureDefinedWidth) { - - if (ensureDefinedWidth) { - definedWidth = true; - // on column resize expand ratio becomes zero - expandRatio = 0; - } - if (width == w) { - return; - } - if (width == -1) { - // go to default mode, clip content if necessary - DOM.setStyleAttribute(captionContainer, "overflow", ""); - } - width = w; - if (w == -1) { - DOM.setStyleAttribute(captionContainer, "width", ""); - setWidth(""); - } else { - - /* - * Reduce width with one pixel for the right border since the - * footers does not have any spacers between them. - */ - int borderWidths = 1; - - // Set the container width (check for negative value) - if (w - borderWidths >= 0) { - captionContainer.getStyle().setPropertyPx("width", - w - borderWidths); - } else { - captionContainer.getStyle().setPropertyPx("width", 0); - } - - /* - * if we already have tBody, set the header width properly, if - * not defer it. IE will fail with complex float in table header - * unless TD width is not explicitly set. - */ - if (scrollBody != null) { - /* - * Reduce with one since footer does not have any spacers, - * instead a 1 pixel border. - */ - int tdWidth = width + scrollBody.getCellExtraWidth() - - borderWidths; - setWidth(tdWidth + "px"); - } else { - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - int borderWidths = 1; - int tdWidth = width - + scrollBody.getCellExtraWidth() - - borderWidths; - setWidth(tdWidth + "px"); - } - }); - } - } - } - - /** - * Sets the width to undefined - */ - public void setUndefinedWidth() { - setWidth(-1, false); - } - - /** - * Detects if width is fixed by developer on server side or resized to - * current width by user. - * - * @return true if defined, false if "natural" width - */ - public boolean isDefinedWidth() { - return definedWidth && width >= 0; - } - - /** - * Returns the pixels width of the footer cell - * - * @return The width in pixels - */ - public int getWidth() { - return width; - } - - /** - * Sets the expand ratio of the cell - * - * @param floatAttribute - * The expand ratio - */ - public void setExpandRatio(float floatAttribute) { - expandRatio = floatAttribute; - } - - /** - * Returns the expand ration of the cell - * - * @return The expand ratio - */ - public float getExpandRatio() { - return expandRatio; - } - - /** - * Is the cell enabled? - * - * @return True if enabled else False - */ - public boolean isEnabled() { - return getParent() != null; - } - - /** - * Handle column clicking - */ - - @Override - public void onBrowserEvent(Event event) { - if (enabled && event != null) { - handleCaptionEvent(event); - - if (DOM.eventGetType(event) == Event.ONMOUSEUP) { - scrollBodyPanel.setFocus(true); - } - boolean stopPropagation = true; - if (event.getTypeInt() == Event.ONCONTEXTMENU - && !client.hasEventListeners(VScrollTable.this, - FOOTER_CLICK_EVENT_ID)) { - // Show browser context menu if a footer click listener is - // not present - stopPropagation = false; - } - if (stopPropagation) { - event.stopPropagation(); - event.preventDefault(); - } - } - } - - /** - * Handles a event on the captions - * - * @param event - * The event to handle - */ - protected void handleCaptionEvent(Event event) { - if (event.getTypeInt() == Event.ONMOUSEUP - || event.getTypeInt() == Event.ONDBLCLICK) { - fireFooterClickedEvent(event); - } - } - - /** - * Fires a footer click event after the user has clicked a column footer - * cell - * - * @param event - * The click event - */ - private void fireFooterClickedEvent(Event event) { - if (client.hasEventListeners(VScrollTable.this, - FOOTER_CLICK_EVENT_ID)) { - MouseEventDetails details = MouseEventDetailsBuilder - .buildMouseEventDetails(event); - client.updateVariable(paintableId, "footerClickEvent", - details.toString(), false); - client.updateVariable(paintableId, "footerClickCID", cid, true); - } - } - - /** - * Returns the column key of the column - * - * @return The column key - */ - public String getColKey() { - return cid; - } - - /** - * Detects the natural minimum width for the column of this header cell. - * If column is resized by user or the width is defined by server the - * actual width is returned. Else the natural min width is returned. - * - * @param columnIndex - * column index hint, if -1 (unknown) it will be detected - * - * @return - */ - public int getNaturalColumnWidth(int columnIndex) { - if (isDefinedWidth()) { - return width; - } else { - if (naturalWidth < 0) { - // This is recently revealed column. Try to detect a proper - // value (greater of header and data - // cols) - - final int hw = ((Element) getElement().getLastChild()) - .getOffsetWidth() + scrollBody.getCellExtraWidth(); - if (columnIndex < 0) { - columnIndex = 0; - for (Iterator it = tHead.iterator(); it - .hasNext(); columnIndex++) { - if (it.next() == this) { - break; - } - } - } - final int cw = scrollBody.getColWidth(columnIndex); - naturalWidth = (hw > cw ? hw : cw); - } - return naturalWidth; - } - } - - public void setNaturalMinimumColumnWidth(int w) { - naturalWidth = w; - } - } - - /** - * HeaderCell that is header cell for row headers. - * - * Reordering disabled and clicking on it resets sorting. - */ - public class RowHeadersFooterCell extends FooterCell { - - RowHeadersFooterCell() { - super(ROW_HEADER_COLUMN_KEY, ""); - } - - @Override - protected void handleCaptionEvent(Event event) { - // NOP: RowHeaders cannot be reordered - // TODO It'd be nice to reset sorting here - } - } - - /** - * The footer of the table which can be seen in the bottom of the Table. - */ - public class TableFooter extends Panel { - - private static final int WRAPPER_WIDTH = 900000; - - ArrayList visibleCells = new ArrayList(); - HashMap availableCells = new HashMap(); - - Element div = DOM.createDiv(); - Element hTableWrapper = DOM.createDiv(); - Element hTableContainer = DOM.createDiv(); - Element table = DOM.createTable(); - Element headerTableBody = DOM.createTBody(); - Element tr = DOM.createTR(); - - public TableFooter() { - - DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden"); - DOM.setElementProperty(hTableWrapper, "className", CLASSNAME - + "-footer"); - - DOM.appendChild(table, headerTableBody); - DOM.appendChild(headerTableBody, tr); - DOM.appendChild(hTableContainer, table); - DOM.appendChild(hTableWrapper, hTableContainer); - DOM.appendChild(div, hTableWrapper); - setElement(div); - - setStyleName(CLASSNAME + "-footer-wrap"); - - availableCells.put(ROW_HEADER_COLUMN_KEY, - new RowHeadersFooterCell()); - } - - @Override - public void clear() { - for (String cid : availableCells.keySet()) { - removeCell(cid); - } - availableCells.clear(); - availableCells.put(ROW_HEADER_COLUMN_KEY, - new RowHeadersFooterCell()); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.Panel#remove(com.google.gwt.user.client - * .ui.Widget) - */ - @Override - public boolean remove(Widget w) { - if (visibleCells.contains(w)) { - visibleCells.remove(w); - orphan(w); - DOM.removeChild(DOM.getParent(w.getElement()), w.getElement()); - return true; - } - return false; - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.HasWidgets#iterator() - */ - public Iterator iterator() { - return visibleCells.iterator(); - } - - /** - * Gets a footer cell which represents the given columnId - * - * @param cid - * The columnId - * - * @return The cell - */ - public FooterCell getFooterCell(String cid) { - return availableCells.get(cid); - } - - /** - * Gets a footer cell by using a column index - * - * @param index - * The index of the column - * @return The Cell - */ - public FooterCell getFooterCell(int index) { - if (index < visibleCells.size()) { - return (FooterCell) visibleCells.get(index); - } else { - return null; - } - } - - /** - * Updates the cells contents when updateUIDL request is received - * - * @param uidl - * The UIDL - */ - public void updateCellsFromUIDL(UIDL uidl) { - Iterator columnIterator = uidl.getChildIterator(); - HashSet updated = new HashSet(); - while (columnIterator.hasNext()) { - final UIDL col = (UIDL) columnIterator.next(); - final String cid = col.getStringAttribute("cid"); - updated.add(cid); - - String caption = col.hasAttribute("fcaption") ? col - .getStringAttribute("fcaption") : ""; - FooterCell c = getFooterCell(cid); - if (c == null) { - c = new FooterCell(cid, caption); - availableCells.put(cid, c); - if (initializedAndAttached) { - // we will need a column width recalculation - initializedAndAttached = false; - initialContentReceived = false; - isNewBody = true; - } - } else { - c.setText(caption); - } - - if (col.hasAttribute("align")) { - c.setAlign(col.getStringAttribute("align").charAt(0)); - } else { - c.setAlign(ALIGN_LEFT); - - } - if (col.hasAttribute("width")) { - if (scrollBody == null) { - // Already updated by setColWidth called from - // TableHeads.updateCellsFromUIDL in case of a server - // side resize - final String width = col.getStringAttribute("width"); - c.setWidth(Integer.parseInt(width), true); - } - } else if (recalcWidths) { - c.setUndefinedWidth(); - } - if (col.hasAttribute("er")) { - c.setExpandRatio(col.getFloatAttribute("er")); - } - if (col.hasAttribute("collapsed")) { - // ensure header is properly removed from parent (case when - // collapsing happens via servers side api) - if (c.isAttached()) { - c.removeFromParent(); - headerChangedDuringUpdate = true; - } - } - } - - // check for orphaned header cells - for (Iterator cit = availableCells.keySet().iterator(); cit - .hasNext();) { - String cid = cit.next(); - if (!updated.contains(cid)) { - removeCell(cid); - cit.remove(); - } - } - } - - /** - * Set a footer cell for a specified column index - * - * @param index - * The index - * @param cell - * The footer cell - */ - public void setFooterCell(int index, FooterCell cell) { - if (cell.isEnabled()) { - // we're moving the cell - DOM.removeChild(tr, cell.getElement()); - orphan(cell); - visibleCells.remove(cell); - } - if (index < visibleCells.size()) { - // insert to right slot - DOM.insertChild(tr, cell.getElement(), index); - adopt(cell); - visibleCells.add(index, cell); - } else if (index == visibleCells.size()) { - // simply append - DOM.appendChild(tr, cell.getElement()); - adopt(cell); - visibleCells.add(cell); - } else { - throw new RuntimeException( - "Header cells must be appended in order"); - } - } - - /** - * Remove a cell by using the columnId - * - * @param colKey - * The columnId to remove - */ - public void removeCell(String colKey) { - final FooterCell c = getFooterCell(colKey); - remove(c); - } - - /** - * Enable a column (Sets the footer cell) - * - * @param cid - * The columnId - * @param index - * The index of the column - */ - public void enableColumn(String cid, int index) { - final FooterCell c = getFooterCell(cid); - if (!c.isEnabled() || getFooterCell(index) != c) { - setFooterCell(index, c); - if (initializedAndAttached) { - headerChangedDuringUpdate = true; - } - } - } - - /** - * Disable browser measurement of the table width - */ - public void disableBrowserIntelligence() { - DOM.setStyleAttribute(hTableContainer, "width", WRAPPER_WIDTH - + "px"); - } - - /** - * Enable browser measurement of the table width - */ - public void enableBrowserIntelligence() { - DOM.setStyleAttribute(hTableContainer, "width", ""); - } - - /** - * Set the horizontal position in the cell in the footer. This is done - * when a horizontal scrollbar is present. - * - * @param scrollLeft - * The value of the leftScroll - */ - public void setHorizontalScrollPosition(int scrollLeft) { - hTableWrapper.setScrollLeft(scrollLeft); - } - - /** - * Swap cells when the column are dragged - * - * @param oldIndex - * The old index of the cell - * @param newIndex - * The new index of the cell - */ - public void moveCell(int oldIndex, int newIndex) { - final FooterCell hCell = getFooterCell(oldIndex); - final Element cell = hCell.getElement(); - - visibleCells.remove(oldIndex); - DOM.removeChild(tr, cell); - - DOM.insertChild(tr, cell, newIndex); - visibleCells.add(newIndex, hCell); - } - } - - /** - * This Panel can only contain VScrollTableRow type of widgets. This - * "simulates" very large table, keeping spacers which take room of - * unrendered rows. - * - */ - public class VScrollTableBody extends Panel { - - public static final int DEFAULT_ROW_HEIGHT = 24; - - private double rowHeight = -1; - - private final LinkedList renderedRows = new LinkedList(); - - /** - * Due some optimizations row height measuring is deferred and initial - * set of rows is rendered detached. Flag set on when table body has - * been attached in dom and rowheight has been measured. - */ - private boolean tBodyMeasurementsDone = false; - - Element preSpacer = DOM.createDiv(); - Element postSpacer = DOM.createDiv(); - - Element container = DOM.createDiv(); - - TableSectionElement tBodyElement = Document.get().createTBodyElement(); - Element table = DOM.createTable(); - - private int firstRendered; - private int lastRendered; - - private char[] aligns; - - protected VScrollTableBody() { - constructDOM(); - setElement(container); - } - - public VScrollTableRow getRowByRowIndex(int indexInTable) { - int internalIndex = indexInTable - firstRendered; - if (internalIndex >= 0 && internalIndex < renderedRows.size()) { - return (VScrollTableRow) renderedRows.get(internalIndex); - } else { - return null; - } - } - - /** - * @return the height of scrollable body, subpixels ceiled. - */ - public int getRequiredHeight() { - return preSpacer.getOffsetHeight() + postSpacer.getOffsetHeight() - + Util.getRequiredHeight(table); - } - - private void constructDOM() { - DOM.setElementProperty(table, "className", CLASSNAME + "-table"); - if (BrowserInfo.get().isIE()) { - table.setPropertyInt("cellSpacing", 0); - } - DOM.setElementProperty(preSpacer, "className", CLASSNAME - + "-row-spacer"); - DOM.setElementProperty(postSpacer, "className", CLASSNAME - + "-row-spacer"); - - table.appendChild(tBodyElement); - DOM.appendChild(container, preSpacer); - DOM.appendChild(container, table); - DOM.appendChild(container, postSpacer); - - } - - public int getAvailableWidth() { - int availW = scrollBodyPanel.getOffsetWidth() - getBorderWidth(); - return availW; - } - - public void renderInitialRows(UIDL rowData, int firstIndex, int rows) { - firstRendered = firstIndex; - lastRendered = firstIndex + rows - 1; - final Iterator it = rowData.getChildIterator(); - aligns = tHead.getColumnAlignments(); - while (it.hasNext()) { - final VScrollTableRow row = createRow((UIDL) it.next(), aligns); - addRow(row); - } - if (isAttached()) { - fixSpacers(); - } - } - - public void renderRows(UIDL rowData, int firstIndex, int rows) { - // FIXME REVIEW - aligns = tHead.getColumnAlignments(); - final Iterator it = rowData.getChildIterator(); - if (firstIndex == lastRendered + 1) { - while (it.hasNext()) { - final VScrollTableRow row = prepareRow((UIDL) it.next()); - addRow(row); - lastRendered++; - } - fixSpacers(); - } else if (firstIndex + rows == firstRendered) { - final VScrollTableRow[] rowArray = new VScrollTableRow[rows]; - int i = rows; - while (it.hasNext()) { - i--; - rowArray[i] = prepareRow((UIDL) it.next()); - } - for (i = 0; i < rows; i++) { - addRowBeforeFirstRendered(rowArray[i]); - firstRendered--; - } - } else { - // completely new set of rows - while (lastRendered + 1 > firstRendered) { - unlinkRow(false); - } - final VScrollTableRow row = prepareRow((UIDL) it.next()); - firstRendered = firstIndex; - lastRendered = firstIndex - 1; - addRow(row); - lastRendered++; - setContainerHeight(); - fixSpacers(); - while (it.hasNext()) { - addRow(prepareRow((UIDL) it.next())); - lastRendered++; - } - fixSpacers(); - } - - // this may be a new set of rows due content change, - // ensure we have proper cache rows - ensureCacheFilled(); - } - - /** - * Ensure we have the correct set of rows on client side, e.g. if the - * content on the server side has changed, or the client scroll position - * has changed since the last request. - */ - protected void ensureCacheFilled() { - int reactFirstRow = (int) (firstRowInViewPort - pageLength - * cache_react_rate); - int reactLastRow = (int) (firstRowInViewPort + pageLength + pageLength - * cache_react_rate); - if (reactFirstRow < 0) { - reactFirstRow = 0; - } - if (reactLastRow >= totalRows) { - reactLastRow = totalRows - 1; - } - if (lastRendered < reactFirstRow || firstRendered > reactLastRow) { - /* - * #8040 - scroll position is completely changed since the - * latest request, so request a new set of rows. - * - * TODO: We should probably check whether the fetched rows match - * the current scroll position right when they arrive, so as to - * not waste time rendering a set of rows that will never be - * visible... - */ - rowRequestHandler.setReqFirstRow(reactFirstRow); - rowRequestHandler.setReqRows(reactLastRow - reactFirstRow + 1); - rowRequestHandler.deferRowFetch(1); - } else if (lastRendered < reactLastRow) { - // get some cache rows below visible area - rowRequestHandler.setReqFirstRow(lastRendered + 1); - rowRequestHandler.setReqRows(reactLastRow - lastRendered); - rowRequestHandler.deferRowFetch(1); - } else if (firstRendered > reactFirstRow) { - /* - * Branch for fetching cache above visible area. - * - * If cache needed for both before and after visible area, this - * will be rendered after-cache is received and rendered. So in - * some rare situations the table may make two cache visits to - * server. - */ - rowRequestHandler.setReqFirstRow(reactFirstRow); - rowRequestHandler.setReqRows(firstRendered - reactFirstRow); - rowRequestHandler.deferRowFetch(1); - } - } - - /** - * Inserts rows as provided in the rowData starting at firstIndex. - * - * @param rowData - * @param firstIndex - * @param rows - * the number of rows - * @return a list of the rows added. - */ - protected List insertRows(UIDL rowData, - int firstIndex, int rows) { - aligns = tHead.getColumnAlignments(); - final Iterator it = rowData.getChildIterator(); - List insertedRows = new ArrayList(); - - if (firstIndex == lastRendered + 1) { - while (it.hasNext()) { - final VScrollTableRow row = prepareRow((UIDL) it.next()); - addRow(row); - insertedRows.add(row); - lastRendered++; - } - fixSpacers(); - } else if (firstIndex + rows == firstRendered) { - final VScrollTableRow[] rowArray = new VScrollTableRow[rows]; - int i = rows; - while (it.hasNext()) { - i--; - rowArray[i] = prepareRow((UIDL) it.next()); - } - for (i = 0; i < rows; i++) { - addRowBeforeFirstRendered(rowArray[i]); - insertedRows.add(rowArray[i]); - firstRendered--; - } - } else { - // insert in the middle - int ix = firstIndex; - while (it.hasNext()) { - VScrollTableRow row = prepareRow((UIDL) it.next()); - insertRowAt(row, ix); - insertedRows.add(row); - lastRendered++; - ix++; - } - fixSpacers(); - } - return insertedRows; - } - - protected List insertAndReindexRows(UIDL rowData, - int firstIndex, int rows) { - List inserted = insertRows(rowData, firstIndex, - rows); - int actualIxOfFirstRowAfterInserted = firstIndex + rows - - firstRendered; - for (int ix = actualIxOfFirstRowAfterInserted; ix < renderedRows - .size(); ix++) { - VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix); - r.setIndex(r.getIndex() + rows); - } - setContainerHeight(); - return inserted; - } - - protected void insertRowsDeleteBelow(UIDL rowData, int firstIndex, - int rows) { - unlinkAllRowsStartingAt(firstIndex); - insertRows(rowData, firstIndex, rows); - setContainerHeight(); - } - - /** - * This method is used to instantiate new rows for this table. It - * automatically sets correct widths to rows cells and assigns correct - * client reference for child widgets. - * - * This method can be called only after table has been initialized - * - * @param uidl - */ - private VScrollTableRow prepareRow(UIDL uidl) { - final VScrollTableRow row = createRow(uidl, aligns); - row.initCellWidths(); - return row; - } - - protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) { - if (uidl.hasAttribute("gen_html")) { - // This is a generated row. - return new VScrollTableGeneratedRow(uidl, aligns2); - } - return new VScrollTableRow(uidl, aligns2); - } - - private void addRowBeforeFirstRendered(VScrollTableRow row) { - row.setIndex(firstRendered - 1); - if (row.isSelected()) { - row.addStyleName("v-selected"); - } - tBodyElement.insertBefore(row.getElement(), - tBodyElement.getFirstChild()); - adopt(row); - renderedRows.add(0, row); - } - - private void addRow(VScrollTableRow row) { - row.setIndex(firstRendered + renderedRows.size()); - if (row.isSelected()) { - row.addStyleName("v-selected"); - } - tBodyElement.appendChild(row.getElement()); - adopt(row); - renderedRows.add(row); - } - - private void insertRowAt(VScrollTableRow row, int index) { - row.setIndex(index); - if (row.isSelected()) { - row.addStyleName("v-selected"); - } - if (index > 0) { - VScrollTableRow sibling = getRowByRowIndex(index - 1); - tBodyElement - .insertAfter(row.getElement(), sibling.getElement()); - } else { - VScrollTableRow sibling = getRowByRowIndex(index); - tBodyElement.insertBefore(row.getElement(), - sibling.getElement()); - } - adopt(row); - int actualIx = index - firstRendered; - renderedRows.add(actualIx, row); - } - - public Iterator iterator() { - return renderedRows.iterator(); - } - - /** - * @return false if couldn't remove row - */ - protected boolean unlinkRow(boolean fromBeginning) { - if (lastRendered - firstRendered < 0) { - return false; - } - int actualIx; - if (fromBeginning) { - actualIx = 0; - firstRendered++; - } else { - actualIx = renderedRows.size() - 1; - lastRendered--; - } - if (actualIx >= 0) { - unlinkRowAtActualIndex(actualIx); - fixSpacers(); - return true; - } - return false; - } - - protected void unlinkRows(int firstIndex, int count) { - if (count < 1) { - return; - } - if (firstRendered > firstIndex - && firstRendered < firstIndex + count) { - firstIndex = firstRendered; - } - int lastIndex = firstIndex + count - 1; - if (lastRendered < lastIndex) { - lastIndex = lastRendered; - } - for (int ix = lastIndex; ix >= firstIndex; ix--) { - unlinkRowAtActualIndex(actualIndex(ix)); - lastRendered--; - } - fixSpacers(); - } - - protected void unlinkAndReindexRows(int firstIndex, int count) { - unlinkRows(firstIndex, count); - int actualFirstIx = firstIndex - firstRendered; - for (int ix = actualFirstIx; ix < renderedRows.size(); ix++) { - VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix); - r.setIndex(r.getIndex() - count); - } - setContainerHeight(); - } - - protected void unlinkAllRowsStartingAt(int index) { - if (firstRendered > index) { - index = firstRendered; - } - for (int ix = renderedRows.size() - 1; ix >= index; ix--) { - unlinkRowAtActualIndex(actualIndex(ix)); - lastRendered--; - } - fixSpacers(); - } - - private int actualIndex(int index) { - return index - firstRendered; - } - - private void unlinkRowAtActualIndex(int index) { - final VScrollTableRow toBeRemoved = (VScrollTableRow) renderedRows - .get(index); - // Unregister row tooltip - client.registerTooltip(VScrollTable.this, toBeRemoved.getElement(), - null); - for (int i = 0; i < toBeRemoved.getElement().getChildCount(); i++) { - // Unregister cell tooltips - Element td = toBeRemoved.getElement().getChild(i).cast(); - client.registerTooltip(VScrollTable.this, td, null); - } - tBodyElement.removeChild(toBeRemoved.getElement()); - orphan(toBeRemoved); - renderedRows.remove(index); - } - - @Override - public boolean remove(Widget w) { - throw new UnsupportedOperationException(); - } - - /** - * Fix container blocks height according to totalRows to avoid - * "bouncing" when scrolling - */ - private void setContainerHeight() { - fixSpacers(); - DOM.setStyleAttribute(container, "height", - measureRowHeightOffset(totalRows) + "px"); - } - - private void fixSpacers() { - int prepx = measureRowHeightOffset(firstRendered); - if (prepx < 0) { - prepx = 0; - } - preSpacer.getStyle().setPropertyPx("height", prepx); - int postpx = measureRowHeightOffset(totalRows - 1) - - measureRowHeightOffset(lastRendered); - if (postpx < 0) { - postpx = 0; - } - postSpacer.getStyle().setPropertyPx("height", postpx); - } - - public double getRowHeight() { - return getRowHeight(false); - } - - public double getRowHeight(boolean forceUpdate) { - if (tBodyMeasurementsDone && !forceUpdate) { - return rowHeight; - } else { - - if (tBodyElement.getRows().getLength() > 0) { - int tableHeight = getTableHeight(); - int rowCount = tBodyElement.getRows().getLength(); - rowHeight = tableHeight / (double) rowCount; - } else { - if (isAttached()) { - // measure row height by adding a dummy row - VScrollTableRow scrollTableRow = new VScrollTableRow(); - tBodyElement.appendChild(scrollTableRow.getElement()); - getRowHeight(forceUpdate); - tBodyElement.removeChild(scrollTableRow.getElement()); - } else { - // TODO investigate if this can never happen anymore - return DEFAULT_ROW_HEIGHT; - } - } - tBodyMeasurementsDone = true; - return rowHeight; - } - } - - public int getTableHeight() { - return table.getOffsetHeight(); - } - - /** - * Returns the width available for column content. - * - * @param columnIndex - * @return - */ - public int getColWidth(int columnIndex) { - if (tBodyMeasurementsDone) { - if (renderedRows.isEmpty()) { - // no rows yet rendered - return 0; - } - for (Widget row : renderedRows) { - if (!(row instanceof VScrollTableGeneratedRow)) { - TableRowElement tr = row.getElement().cast(); - Element wrapperdiv = tr.getCells().getItem(columnIndex) - .getFirstChildElement().cast(); - return wrapperdiv.getOffsetWidth(); - } - } - return 0; - } else { - return 0; - } - } - - /** - * Sets the content width of a column. - * - * Due IE limitation, we must set the width to a wrapper elements inside - * table cells (with overflow hidden, which does not work on td - * elements). - * - * To get this work properly crossplatform, we will also set the width - * of td. - * - * @param colIndex - * @param w - */ - public void setColWidth(int colIndex, int w) { - for (Widget row : renderedRows) { - ((VScrollTableRow) row).setCellWidth(colIndex, w); - } - } - - private int cellExtraWidth = -1; - - /** - * Method to return the space used for cell paddings + border. - */ - private int getCellExtraWidth() { - if (cellExtraWidth < 0) { - detectExtrawidth(); - } - return cellExtraWidth; - } - - private void detectExtrawidth() { - NodeList rows = tBodyElement.getRows(); - if (rows.getLength() == 0) { - /* need to temporary add empty row and detect */ - VScrollTableRow scrollTableRow = new VScrollTableRow(); - tBodyElement.appendChild(scrollTableRow.getElement()); - detectExtrawidth(); - tBodyElement.removeChild(scrollTableRow.getElement()); - } else { - boolean noCells = false; - TableRowElement item = rows.getItem(0); - TableCellElement firstTD = item.getCells().getItem(0); - if (firstTD == null) { - // content is currently empty, we need to add a fake cell - // for measuring - noCells = true; - VScrollTableRow next = (VScrollTableRow) iterator().next(); - boolean sorted = tHead.getHeaderCell(0) != null ? tHead - .getHeaderCell(0).isSorted() : false; - next.addCell(null, "", ALIGN_LEFT, "", true, sorted); - firstTD = item.getCells().getItem(0); - } - com.google.gwt.dom.client.Element wrapper = firstTD - .getFirstChildElement(); - cellExtraWidth = firstTD.getOffsetWidth() - - wrapper.getOffsetWidth(); - if (noCells) { - firstTD.getParentElement().removeChild(firstTD); - } - } - } - - private void reLayoutComponents() { - for (Widget w : this) { - VScrollTableRow r = (VScrollTableRow) w; - for (Widget widget : r) { - client.handleComponentRelativeSize(widget); - } - } - } - - public int getLastRendered() { - return lastRendered; - } - - public int getFirstRendered() { - return firstRendered; - } - - public void moveCol(int oldIndex, int newIndex) { - - // loop all rows and move given index to its new place - final Iterator rows = iterator(); - while (rows.hasNext()) { - final VScrollTableRow row = (VScrollTableRow) rows.next(); - - final Element td = DOM.getChild(row.getElement(), oldIndex); - if (td != null) { - DOM.removeChild(row.getElement(), td); - - DOM.insertChild(row.getElement(), td, newIndex); - } - } - - } - - /** - * Restore row visibility which is set to "none" when the row is - * rendered (due a performance optimization). - */ - private void restoreRowVisibility() { - for (Widget row : renderedRows) { - row.getElement().getStyle().setProperty("visibility", ""); - } - } - - public class VScrollTableRow extends Panel implements ActionOwner { - - private static final int TOUCHSCROLL_TIMEOUT = 70; - private static final int DRAGMODE_MULTIROW = 2; - protected ArrayList childWidgets = new ArrayList(); - private boolean selected = false; - protected final int rowKey; - - private String[] actionKeys = null; - private final TableRowElement rowElement; - private boolean mDown; - private int index; - private Event touchStart; - private static final String ROW_CLASSNAME_EVEN = CLASSNAME + "-row"; - private static final String ROW_CLASSNAME_ODD = CLASSNAME - + "-row-odd"; - private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500; - private Timer contextTouchTimeout; - private int touchStartY; - private int touchStartX; - - private VScrollTableRow(int rowKey) { - this.rowKey = rowKey; - rowElement = Document.get().createTRElement(); - setElement(rowElement); - DOM.sinkEvents(getElement(), Event.MOUSEEVENTS - | Event.TOUCHEVENTS | Event.ONDBLCLICK - | Event.ONCONTEXTMENU | VTooltip.TOOLTIP_EVENTS); - } - - public VScrollTableRow(UIDL uidl, char[] aligns) { - this(uidl.getIntAttribute("key")); - - /* - * Rendering the rows as hidden improves Firefox and Safari - * performance drastically. - */ - getElement().getStyle().setProperty("visibility", "hidden"); - - String rowStyle = uidl.getStringAttribute("rowstyle"); - if (rowStyle != null) { - addStyleName(CLASSNAME + "-row-" + rowStyle); - } - - String rowDescription = uidl.getStringAttribute("rowdescr"); - if (rowDescription != null && !rowDescription.equals("")) { - TooltipInfo info = new TooltipInfo(rowDescription); - client.registerTooltip(VScrollTable.this, rowElement, info); - } else { - // Remove possibly previously set tooltip - client.registerTooltip(VScrollTable.this, rowElement, null); - } - - tHead.getColumnAlignments(); - int col = 0; - int visibleColumnIndex = -1; - - // row header - if (showRowHeaders) { - boolean sorted = tHead.getHeaderCell(col).isSorted(); - addCell(uidl, buildCaptionHtmlSnippet(uidl), aligns[col++], - "rowheader", true, sorted); - visibleColumnIndex++; - } - - if (uidl.hasAttribute("al")) { - actionKeys = uidl.getStringArrayAttribute("al"); - } - - addCellsFromUIDL(uidl, aligns, col, visibleColumnIndex); - - if (uidl.hasAttribute("selected") && !isSelected()) { - toggleSelection(); - } - } - - /** - * Add a dummy row, used for measurements if Table is empty. - */ - public VScrollTableRow() { - this(0); - addStyleName(CLASSNAME + "-row"); - addCell(null, "_", 'b', "", true, false); - } - - protected void initCellWidths() { - final int cells = tHead.getVisibleCellCount(); - for (int i = 0; i < cells; i++) { - int w = VScrollTable.this.getColWidth(getColKeyByIndex(i)); - if (w < 0) { - w = 0; - } - setCellWidth(i, w); - } - } - - protected void setCellWidth(int cellIx, int width) { - final Element cell = DOM.getChild(getElement(), cellIx); - cell.getFirstChildElement().getStyle() - .setPropertyPx("width", width); - cell.getStyle().setPropertyPx("width", width); - } - - protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col, - int visibleColumnIndex) { - final Iterator cells = uidl.getChildIterator(); - while (cells.hasNext()) { - final Object cell = cells.next(); - visibleColumnIndex++; - - String columnId = visibleColOrder[visibleColumnIndex]; - - String style = ""; - if (uidl.hasAttribute("style-" + columnId)) { - style = uidl.getStringAttribute("style-" + columnId); - } - - String description = null; - if (uidl.hasAttribute("descr-" + columnId)) { - description = uidl.getStringAttribute("descr-" - + columnId); - } - - boolean sorted = tHead.getHeaderCell(col).isSorted(); - if (cell instanceof String) { - addCell(uidl, cell.toString(), aligns[col++], style, - isRenderHtmlInCells(), sorted, description); - } else { - final ComponentConnector cellContent = client - .getPaintable((UIDL) cell); - - addCell(uidl, cellContent.getWidget(), aligns[col++], - style, sorted); - } - } - } - - /** - * Overriding this and returning true causes all text cells to be - * rendered as HTML. - * - * @return always returns false in the default implementation - */ - protected boolean isRenderHtmlInCells() { - return false; - } - - /** - * Detects whether row is visible in tables viewport. - * - * @return - */ - public boolean isInViewPort() { - int absoluteTop = getAbsoluteTop(); - int scrollPosition = scrollBodyPanel.getScrollPosition(); - if (absoluteTop < scrollPosition) { - return false; - } - int maxVisible = scrollPosition - + scrollBodyPanel.getOffsetHeight() - getOffsetHeight(); - if (absoluteTop > maxVisible) { - return false; - } - return true; - } - - /** - * Makes a check based on indexes whether the row is before the - * compared row. - * - * @param row1 - * @return true if this rows index is smaller than in the row1 - */ - public boolean isBefore(VScrollTableRow row1) { - return getIndex() < row1.getIndex(); - } - - /** - * Sets the index of the row in the whole table. Currently used just - * to set even/odd classname - * - * @param indexInWholeTable - */ - private void setIndex(int indexInWholeTable) { - index = indexInWholeTable; - boolean isOdd = indexInWholeTable % 2 == 0; - // Inverted logic to be backwards compatible with earlier 6.4. - // It is very strange because rows 1,3,5 are considered "even" - // and 2,4,6 "odd". - // - // First remove any old styles so that both styles aren't - // applied when indexes are updated. - removeStyleName(ROW_CLASSNAME_ODD); - removeStyleName(ROW_CLASSNAME_EVEN); - if (!isOdd) { - addStyleName(ROW_CLASSNAME_ODD); - } else { - addStyleName(ROW_CLASSNAME_EVEN); - } - } - - public int getIndex() { - return index; - } - - @Override - protected void onDetach() { - super.onDetach(); - client.getContextMenu().ensureHidden(this); - } - - public String getKey() { - return String.valueOf(rowKey); - } - - public void addCell(UIDL rowUidl, String text, char align, - String style, boolean textIsHTML, boolean sorted) { - addCell(rowUidl, text, align, style, textIsHTML, sorted, null); - } - - public void addCell(UIDL rowUidl, String text, char align, - String style, boolean textIsHTML, boolean sorted, - String description) { - // String only content is optimized by not using Label widget - final TableCellElement td = DOM.createTD().cast(); - initCellWithText(text, align, style, textIsHTML, sorted, - description, td); - } - - protected void initCellWithText(String text, char align, - String style, boolean textIsHTML, boolean sorted, - String description, final TableCellElement td) { - final Element container = DOM.createDiv(); - String className = CLASSNAME + "-cell-content"; - if (style != null && !style.equals("")) { - className += " " + CLASSNAME + "-cell-content-" + style; - } - if (sorted) { - className += " " + CLASSNAME + "-cell-content-sorted"; - } - td.setClassName(className); - container.setClassName(CLASSNAME + "-cell-wrapper"); - if (textIsHTML) { - container.setInnerHTML(text); - } else { - container.setInnerText(text); - } - if (align != ALIGN_LEFT) { - switch (align) { - case ALIGN_CENTER: - container.getStyle().setProperty("textAlign", "center"); - break; - case ALIGN_RIGHT: - default: - container.getStyle().setProperty("textAlign", "right"); - break; - } - } - - if (description != null && !description.equals("")) { - TooltipInfo info = new TooltipInfo(description); - client.registerTooltip(VScrollTable.this, td, info); - } else { - // Remove possibly previously set tooltip - client.registerTooltip(VScrollTable.this, td, null); - } - - td.appendChild(container); - getElement().appendChild(td); - } - - public void addCell(UIDL rowUidl, Widget w, char align, - String style, boolean sorted) { - final TableCellElement td = DOM.createTD().cast(); - initCellWithWidget(w, align, style, sorted, td); - } - - protected void initCellWithWidget(Widget w, char align, - String style, boolean sorted, final TableCellElement td) { - final Element container = DOM.createDiv(); - String className = CLASSNAME + "-cell-content"; - if (style != null && !style.equals("")) { - className += " " + CLASSNAME + "-cell-content-" + style; - } - if (sorted) { - className += " " + CLASSNAME + "-cell-content-sorted"; - } - td.setClassName(className); - container.setClassName(CLASSNAME + "-cell-wrapper"); - // TODO most components work with this, but not all (e.g. - // Select) - // Old comment: make widget cells respect align. - // text-align:center for IE, margin: auto for others - if (align != ALIGN_LEFT) { - switch (align) { - case ALIGN_CENTER: - container.getStyle().setProperty("textAlign", "center"); - break; - case ALIGN_RIGHT: - default: - container.getStyle().setProperty("textAlign", "right"); - break; - } - } - td.appendChild(container); - getElement().appendChild(td); - // ensure widget not attached to another element (possible tBody - // change) - w.removeFromParent(); - container.appendChild(w.getElement()); - adopt(w); - childWidgets.add(w); - } - - public Iterator iterator() { - return childWidgets.iterator(); - } - - @Override - public boolean remove(Widget w) { - if (childWidgets.contains(w)) { - orphan(w); - DOM.removeChild(DOM.getParent(w.getElement()), - w.getElement()); - childWidgets.remove(w); - return true; - } else { - return false; - } - } - - /** - * If there are registered click listeners, sends a click event and - * returns true. Otherwise, does nothing and returns false. - * - * @param event - * @param targetTdOrTr - * @param immediate - * Whether the event is sent immediately - * @return Whether a click event was sent - */ - private boolean handleClickEvent(Event event, Element targetTdOrTr, - boolean immediate) { - if (!client.hasEventListeners(VScrollTable.this, - ITEM_CLICK_EVENT_ID)) { - // Don't send an event if nobody is listening - return false; - } - - // This row was clicked - client.updateVariable(paintableId, "clickedKey", "" + rowKey, - false); - - if (getElement() == targetTdOrTr.getParentElement()) { - // A specific column was clicked - int childIndex = DOM.getChildIndex(getElement(), - targetTdOrTr); - String colKey = null; - colKey = tHead.getHeaderCell(childIndex).getColKey(); - client.updateVariable(paintableId, "clickedColKey", colKey, - false); - } - - MouseEventDetails details = MouseEventDetailsBuilder - .buildMouseEventDetails(event); - - client.updateVariable(paintableId, "clickEvent", - details.toString(), immediate); - - return true; - } - - private void handleTooltips(final Event event, Element target) { - if (target.hasTagName("TD")) { - // Table cell (td) - Element container = target.getFirstChildElement().cast(); - Element widget = container.getFirstChildElement().cast(); - - boolean containsWidget = false; - for (Widget w : childWidgets) { - if (widget == w.getElement()) { - containsWidget = true; - break; - } - } - - if (!containsWidget) { - // Only text nodes has tooltips - if (ConnectorMap.get(client).getWidgetTooltipInfo( - VScrollTable.this, target) != null) { - // Cell has description, use it - client.handleTooltipEvent(event, VScrollTable.this, - target); - } else { - // Cell might have row description, use row - // description - client.handleTooltipEvent(event, VScrollTable.this, - target.getParentElement()); - } - } - - } else { - // Table row (tr) - client.handleTooltipEvent(event, VScrollTable.this, target); - } - } - - /* - * React on click that occur on content cells only - */ - @Override - public void onBrowserEvent(final Event event) { - if (enabled) { - final int type = event.getTypeInt(); - final Element targetTdOrTr = getEventTargetTdOrTr(event); - if (type == Event.ONCONTEXTMENU) { - showContextMenu(event); - if (enabled - && (actionKeys != null || client - .hasEventListeners(VScrollTable.this, - ITEM_CLICK_EVENT_ID))) { - /* - * Prevent browser context menu only if there are - * action handlers or item click listeners - * registered - */ - event.stopPropagation(); - event.preventDefault(); - } - return; - } - - boolean targetCellOrRowFound = targetTdOrTr != null; - if (targetCellOrRowFound) { - handleTooltips(event, targetTdOrTr); - } - - switch (type) { - case Event.ONDBLCLICK: - if (targetCellOrRowFound) { - handleClickEvent(event, targetTdOrTr, true); - } - break; - case Event.ONMOUSEUP: - if (targetCellOrRowFound) { - mDown = false; - /* - * Queue here, send at the same time as the - * corresponding value change event - see #7127 - */ - boolean clickEventSent = handleClickEvent(event, - targetTdOrTr, false); - - if (event.getButton() == Event.BUTTON_LEFT - && isSelectable()) { - - // Ctrl+Shift click - if ((event.getCtrlKey() || event.getMetaKey()) - && event.getShiftKey() - && isMultiSelectModeDefault()) { - toggleShiftSelection(false); - setRowFocus(this); - - // Ctrl click - } else if ((event.getCtrlKey() || event - .getMetaKey()) - && isMultiSelectModeDefault()) { - boolean wasSelected = isSelected(); - toggleSelection(); - setRowFocus(this); - /* - * next possible range select must start on - * this row - */ - selectionRangeStart = this; - if (wasSelected) { - removeRowFromUnsentSelectionRanges(this); - } - - } else if ((event.getCtrlKey() || event - .getMetaKey()) && isSingleSelectMode()) { - // Ctrl (or meta) click (Single selection) - if (!isSelected() - || (isSelected() && nullSelectionAllowed)) { - - if (!isSelected()) { - deselectAll(); - } - - toggleSelection(); - setRowFocus(this); - } - - } else if (event.getShiftKey() - && isMultiSelectModeDefault()) { - // Shift click - toggleShiftSelection(true); - - } else { - // click - boolean currentlyJustThisRowSelected = selectedRowKeys - .size() == 1 - && selectedRowKeys - .contains(getKey()); - - if (!currentlyJustThisRowSelected) { - if (isSingleSelectMode() - || isMultiSelectModeDefault()) { - /* - * For default multi select mode - * (ctrl/shift) and for single - * select mode we need to clear the - * previous selection before - * selecting a new one when the user - * clicks on a row. Only in - * multiselect/simple mode the old - * selection should remain after a - * normal click. - */ - deselectAll(); - } - toggleSelection(); - } else if ((isSingleSelectMode() || isMultiSelectModeSimple()) - && nullSelectionAllowed) { - toggleSelection(); - }/* - * else NOP to avoid excessive server - * visits (selection is removed with - * CTRL/META click) - */ - - selectionRangeStart = this; - setRowFocus(this); - } - - // Remove IE text selection hack - if (BrowserInfo.get().isIE()) { - ((Element) event.getEventTarget().cast()) - .setPropertyJSO("onselectstart", - null); - } - // Queue value change - sendSelectedRows(false); - } - /* - * Send queued click and value change events if any - * If a click event is sent, send value change with - * it regardless of the immediate flag, see #7127 - */ - if (immediate || clickEventSent) { - client.sendPendingVariableChanges(); - } - } - break; - case Event.ONTOUCHEND: - case Event.ONTOUCHCANCEL: - if (touchStart != null) { - /* - * Touch has not been handled as neither context or - * drag start, handle it as a click. - */ - Util.simulateClickFromTouchEvent(touchStart, this); - touchStart = null; - } - if (contextTouchTimeout != null) { - contextTouchTimeout.cancel(); - } - break; - case Event.ONTOUCHMOVE: - if (isSignificantMove(event)) { - /* - * TODO figure out scroll delegate don't eat events - * if row is selected. Null check for active - * delegate is as a workaround. - */ - if (dragmode != 0 - && touchStart != null - && (TouchScrollDelegate - .getActiveScrollDelegate() == null)) { - startRowDrag(touchStart, type, targetTdOrTr); - } - if (contextTouchTimeout != null) { - contextTouchTimeout.cancel(); - } - /* - * Avoid clicks and drags by clearing touch start - * flag. - */ - touchStart = null; - } - - break; - case Event.ONTOUCHSTART: - touchStart = event; - Touch touch = event.getChangedTouches().get(0); - // save position to fields, touches in events are same - // isntance during the operation. - touchStartX = touch.getClientX(); - touchStartY = touch.getClientY(); - /* - * Prevent simulated mouse events. - */ - touchStart.preventDefault(); - if (dragmode != 0 || actionKeys != null) { - new Timer() { - @Override - public void run() { - TouchScrollDelegate activeScrollDelegate = TouchScrollDelegate - .getActiveScrollDelegate(); - if (activeScrollDelegate != null - && !activeScrollDelegate.isMoved()) { - /* - * scrolling hasn't started. Cancel - * scrolling and let row handle this as - * drag start or context menu. - */ - activeScrollDelegate.stopScrolling(); - } else { - /* - * Scrolled or scrolling, clear touch - * start to indicate that row shouldn't - * handle touch move/end events. - */ - touchStart = null; - } - } - }.schedule(TOUCHSCROLL_TIMEOUT); - - if (contextTouchTimeout == null - && actionKeys != null) { - contextTouchTimeout = new Timer() { - @Override - public void run() { - if (touchStart != null) { - showContextMenu(touchStart); - touchStart = null; - } - } - }; - } - contextTouchTimeout.cancel(); - contextTouchTimeout - .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); - } - break; - case Event.ONMOUSEDOWN: - if (targetCellOrRowFound) { - setRowFocus(this); - ensureFocus(); - if (dragmode != 0 - && (event.getButton() == NativeEvent.BUTTON_LEFT)) { - startRowDrag(event, type, targetTdOrTr); - - } else if (event.getCtrlKey() - || event.getShiftKey() - || event.getMetaKey() - && isMultiSelectModeDefault()) { - - // Prevent default text selection in Firefox - event.preventDefault(); - - // Prevent default text selection in IE - if (BrowserInfo.get().isIE()) { - ((Element) event.getEventTarget().cast()) - .setPropertyJSO( - "onselectstart", - getPreventTextSelectionIEHack()); - } - - event.stopPropagation(); - } - } - break; - case Event.ONMOUSEOUT: - if (targetCellOrRowFound) { - mDown = false; - } - break; - default: - break; - } - } - super.onBrowserEvent(event); - } - - private boolean isSignificantMove(Event event) { - if (touchStart == null) { - // no touch start - return false; - } - /* - * TODO calculate based on real distance instead of separate - * axis checks - */ - Touch touch = event.getChangedTouches().get(0); - if (Math.abs(touch.getClientX() - touchStartX) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) { - return true; - } - if (Math.abs(touch.getClientY() - touchStartY) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) { - return true; - } - return false; - } - - protected void startRowDrag(Event event, final int type, - Element targetTdOrTr) { - mDown = true; - VTransferable transferable = new VTransferable(); - transferable.setDragSource(ConnectorMap.get(client) - .getConnector(VScrollTable.this)); - transferable.setData("itemId", "" + rowKey); - NodeList cells = rowElement.getCells(); - for (int i = 0; i < cells.getLength(); i++) { - if (cells.getItem(i).isOrHasChild(targetTdOrTr)) { - HeaderCell headerCell = tHead.getHeaderCell(i); - transferable.setData("propertyId", headerCell.cid); - break; - } - } - - VDragEvent ev = VDragAndDropManager.get().startDrag( - transferable, event, true); - if (dragmode == DRAGMODE_MULTIROW && isMultiSelectModeAny() - && selectedRowKeys.contains("" + rowKey)) { - ev.createDragImage( - (Element) scrollBody.tBodyElement.cast(), true); - Element dragImage = ev.getDragImage(); - int i = 0; - for (Iterator iterator = scrollBody.iterator(); iterator - .hasNext();) { - VScrollTableRow next = (VScrollTableRow) iterator - .next(); - Element child = (Element) dragImage.getChild(i++); - if (!selectedRowKeys.contains("" + next.rowKey)) { - child.getStyle().setVisibility(Visibility.HIDDEN); - } - } - } else { - ev.createDragImage(getElement(), true); - } - if (type == Event.ONMOUSEDOWN) { - event.preventDefault(); - } - event.stopPropagation(); - } - - /** - * Finds the TD that the event interacts with. Returns null if the - * target of the event should not be handled. If the event target is - * the row directly this method returns the TR element instead of - * the TD. - * - * @param event - * @return TD or TR element that the event targets (the actual event - * target is this element or a child of it) - */ - private Element getEventTargetTdOrTr(Event event) { - final Element eventTarget = event.getEventTarget().cast(); - Widget widget = Util.findWidget(eventTarget, null); - final Element thisTrElement = getElement(); - - if (widget != this) { - /* - * This is a workaround to make Labels, read only TextFields - * and Embedded in a Table clickable (see #2688). It is - * really not a fix as it does not work with a custom read - * only components (not extending VLabel/VEmbedded). - */ - while (widget != null && widget.getParent() != this) { - widget = widget.getParent(); - } - - if (!(widget instanceof VLabel) - && !(widget instanceof VEmbedded) - && !(widget instanceof VTextField && ((VTextField) widget) - .isReadOnly())) { - return null; - } - } - if (eventTarget == thisTrElement) { - // This was a click on the TR element - return thisTrElement; - } - - // Iterate upwards until we find the TR element - Element element = eventTarget; - while (element != null - && element.getParentElement().cast() != thisTrElement) { - element = element.getParentElement().cast(); - } - return element; - } - - public void showContextMenu(Event event) { - if (enabled && actionKeys != null) { - // Show context menu if there are registered action handlers - int left = Util.getTouchOrMouseClientX(event); - int top = Util.getTouchOrMouseClientY(event); - top += Window.getScrollTop(); - left += Window.getScrollLeft(); - contextMenu = new ContextMenuDetails(getKey(), left, top); - client.getContextMenu().showAt(this, left, top); - } - } - - /** - * Has the row been selected? - * - * @return Returns true if selected, else false - */ - public boolean isSelected() { - return selected; - } - - /** - * Toggle the selection of the row - */ - public void toggleSelection() { - selected = !selected; - selectionChanged = true; - if (selected) { - selectedRowKeys.add(String.valueOf(rowKey)); - addStyleName("v-selected"); - } else { - removeStyleName("v-selected"); - selectedRowKeys.remove(String.valueOf(rowKey)); - } - } - - /** - * Is called when a user clicks an item when holding SHIFT key down. - * This will select a new range from the last focused row - * - * @param deselectPrevious - * Should the previous selected range be deselected - */ - private void toggleShiftSelection(boolean deselectPrevious) { - - /* - * Ensures that we are in multiselect mode and that we have a - * previous selection which was not a deselection - */ - if (isSingleSelectMode()) { - // No previous selection found - deselectAll(); - toggleSelection(); - return; - } - - // Set the selectable range - VScrollTableRow endRow = this; - VScrollTableRow startRow = selectionRangeStart; - if (startRow == null) { - startRow = focusedRow; - // If start row is null then we have a multipage selection - // from - // above - if (startRow == null) { - startRow = (VScrollTableRow) scrollBody.iterator() - .next(); - setRowFocus(endRow); - } - } - // Deselect previous items if so desired - if (deselectPrevious) { - deselectAll(); - } - - // we'll ensure GUI state from top down even though selection - // was the opposite way - if (!startRow.isBefore(endRow)) { - VScrollTableRow tmp = startRow; - startRow = endRow; - endRow = tmp; - } - SelectionRange range = new SelectionRange(startRow, endRow); - - for (Widget w : scrollBody) { - VScrollTableRow row = (VScrollTableRow) w; - if (range.inRange(row)) { - if (!row.isSelected()) { - row.toggleSelection(); - } - selectedRowKeys.add(row.getKey()); - } - } - - // Add range - if (startRow != endRow) { - selectedRowRanges.add(range); - } - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.gwt.client.ui.IActionOwner#getActions () - */ - public Action[] getActions() { - if (actionKeys == null) { - return new Action[] {}; - } - final Action[] actions = new Action[actionKeys.length]; - for (int i = 0; i < actions.length; i++) { - final String actionKey = actionKeys[i]; - final TreeAction a = new TreeAction(this, - String.valueOf(rowKey), actionKey) { - @Override - public void execute() { - super.execute(); - lazyRevertFocusToRow(VScrollTableRow.this); - } - }; - a.setCaption(getActionCaption(actionKey)); - a.setIconUrl(getActionIcon(actionKey)); - actions[i] = a; - } - return actions; - } - - public ApplicationConnection getClient() { - return client; - } - - public String getPaintableId() { - return paintableId; - } - - private int getColIndexOf(Widget child) { - com.google.gwt.dom.client.Element widgetCell = child - .getElement().getParentElement().getParentElement(); - NodeList cells = rowElement.getCells(); - for (int i = 0; i < cells.getLength(); i++) { - if (cells.getItem(i) == widgetCell) { - return i; - } - } - return -1; - } - - public Widget getWidgetForPaintable() { - return this; - } - } - - protected class VScrollTableGeneratedRow extends VScrollTableRow { - - private boolean spanColumns; - private boolean htmlContentAllowed; - - public VScrollTableGeneratedRow(UIDL uidl, char[] aligns) { - super(uidl, aligns); - addStyleName("v-table-generated-row"); - } - - public boolean isSpanColumns() { - return spanColumns; - } - - @Override - protected void initCellWidths() { - if (spanColumns) { - setSpannedColumnWidthAfterDOMFullyInited(); - } else { - super.initCellWidths(); - } - } - - private void setSpannedColumnWidthAfterDOMFullyInited() { - // Defer setting width on spanned columns to make sure that - // they are added to the DOM before trying to calculate - // widths. - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - - public void execute() { - if (showRowHeaders) { - setCellWidth(0, tHead.getHeaderCell(0).getWidth()); - calcAndSetSpanWidthOnCell(1); - } else { - calcAndSetSpanWidthOnCell(0); - } - } - }); - } - - @Override - protected boolean isRenderHtmlInCells() { - return htmlContentAllowed; - } - - @Override - protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col, - int visibleColumnIndex) { - htmlContentAllowed = uidl.getBooleanAttribute("gen_html"); - spanColumns = uidl.getBooleanAttribute("gen_span"); - - final Iterator cells = uidl.getChildIterator(); - if (spanColumns) { - int colCount = uidl.getChildCount(); - if (cells.hasNext()) { - final Object cell = cells.next(); - if (cell instanceof String) { - addSpannedCell(uidl, cell.toString(), aligns[0], - "", htmlContentAllowed, false, null, - colCount); - } else { - addSpannedCell(uidl, (Widget) cell, aligns[0], "", - false, colCount); - } - } - } else { - super.addCellsFromUIDL(uidl, aligns, col, - visibleColumnIndex); - } - } - - private void addSpannedCell(UIDL rowUidl, Widget w, char align, - String style, boolean sorted, int colCount) { - TableCellElement td = DOM.createTD().cast(); - td.setColSpan(colCount); - initCellWithWidget(w, align, style, sorted, td); - } - - private void addSpannedCell(UIDL rowUidl, String text, char align, - String style, boolean textIsHTML, boolean sorted, - String description, int colCount) { - // String only content is optimized by not using Label widget - final TableCellElement td = DOM.createTD().cast(); - td.setColSpan(colCount); - initCellWithText(text, align, style, textIsHTML, sorted, - description, td); - } - - @Override - protected void setCellWidth(int cellIx, int width) { - if (isSpanColumns()) { - if (showRowHeaders) { - if (cellIx == 0) { - super.setCellWidth(0, width); - } else { - // We need to recalculate the spanning TDs width for - // every cellIx in order to support column resizing. - calcAndSetSpanWidthOnCell(1); - } - } else { - // Same as above. - calcAndSetSpanWidthOnCell(0); - } - } else { - super.setCellWidth(cellIx, width); - } - } - - private void calcAndSetSpanWidthOnCell(final int cellIx) { - int spanWidth = 0; - for (int ix = (showRowHeaders ? 1 : 0); ix < tHead - .getVisibleCellCount(); ix++) { - spanWidth += tHead.getHeaderCell(ix).getOffsetWidth(); - } - Util.setWidthExcludingPaddingAndBorder((Element) getElement() - .getChild(cellIx), spanWidth, 13, false); - } - } - - /** - * Ensure the component has a focus. - * - * TODO the current implementation simply always calls focus for the - * component. In case the Table at some point implements focus/blur - * listeners, this method needs to be evolved to conditionally call - * focus only if not currently focused. - */ - protected void ensureFocus() { - if (!hasFocus) { - scrollBodyPanel.setFocus(true); - } - - } - - } - - /** - * Deselects all items - */ - public void deselectAll() { - for (Widget w : scrollBody) { - VScrollTableRow row = (VScrollTableRow) w; - if (row.isSelected()) { - row.toggleSelection(); - } - } - // still ensure all selects are removed from (not necessary rendered) - selectedRowKeys.clear(); - selectedRowRanges.clear(); - // also notify server that it clears all previous selections (the client - // side does not know about the invisible ones) - instructServerToForgetPreviousSelections(); - } - - /** - * Used in multiselect mode when the client side knows that all selections - * are in the next request. - */ - private void instructServerToForgetPreviousSelections() { - client.updateVariable(paintableId, "clearSelections", true, false); - } - - /** - * Determines the pagelength when the table height is fixed. - */ - public void updatePageLength() { - // Only update if visible and enabled - if (!isVisible() || !enabled) { - return; - } - - if (scrollBody == null) { - return; - } - - if (isDynamicHeight()) { - return; - } - - int rowHeight = (int) Math.round(scrollBody.getRowHeight()); - int bodyH = scrollBodyPanel.getOffsetHeight(); - int rowsAtOnce = bodyH / rowHeight; - boolean anotherPartlyVisible = ((bodyH % rowHeight) != 0); - if (anotherPartlyVisible) { - rowsAtOnce++; - } - if (pageLength != rowsAtOnce) { - pageLength = rowsAtOnce; - client.updateVariable(paintableId, "pagelength", pageLength, false); - - if (!rendering) { - int currentlyVisible = scrollBody.lastRendered - - scrollBody.firstRendered; - if (currentlyVisible < pageLength - && currentlyVisible < totalRows) { - // shake scrollpanel to fill empty space - scrollBodyPanel.setScrollPosition(scrollTop + 1); - scrollBodyPanel.setScrollPosition(scrollTop - 1); - } - - sizeNeedsInit = true; - } - } - - } - - void updateWidth() { - if (!isVisible()) { - /* - * Do not update size when the table is hidden as all column widths - * will be set to zero and they won't be recalculated when the table - * is set visible again (until the size changes again) - */ - return; - } - - if (!isDynamicWidth()) { - int innerPixels = getOffsetWidth() - getBorderWidth(); - if (innerPixels < 0) { - innerPixels = 0; - } - setContentWidth(innerPixels); - - // readjust undefined width columns - triggerLazyColumnAdjustment(false); - - } else { - - sizeNeedsInit = true; - - // readjust undefined width columns - triggerLazyColumnAdjustment(false); - } - - /* - * setting width may affect wheter the component has scrollbars -> needs - * scrolling or not - */ - setProperTabIndex(); - } - - private static final int LAZY_COLUMN_ADJUST_TIMEOUT = 300; - - private final Timer lazyAdjustColumnWidths = new Timer() { - /** - * Check for column widths, and available width, to see if we can fix - * column widths "optimally". Doing this lazily to avoid expensive - * calculation when resizing is not yet finished. - */ - @Override - public void run() { - if (scrollBody == null) { - // Try again later if we get here before scrollBody has been - // initalized - triggerLazyColumnAdjustment(false); - return; - } - - Iterator headCells = tHead.iterator(); - int usedMinimumWidth = 0; - int totalExplicitColumnsWidths = 0; - float expandRatioDivider = 0; - int colIndex = 0; - while (headCells.hasNext()) { - final HeaderCell hCell = (HeaderCell) headCells.next(); - if (hCell.isDefinedWidth()) { - totalExplicitColumnsWidths += hCell.getWidth(); - usedMinimumWidth += hCell.getWidth(); - } else { - usedMinimumWidth += hCell.getNaturalColumnWidth(colIndex); - expandRatioDivider += hCell.getExpandRatio(); - } - colIndex++; - } - - int availW = scrollBody.getAvailableWidth(); - // Hey IE, are you really sure about this? - availW = scrollBody.getAvailableWidth(); - int visibleCellCount = tHead.getVisibleCellCount(); - availW -= scrollBody.getCellExtraWidth() * visibleCellCount; - if (willHaveScrollbars()) { - availW -= Util.getNativeScrollbarSize(); - } - - int extraSpace = availW - usedMinimumWidth; - if (extraSpace < 0) { - extraSpace = 0; - } - - int totalUndefinedNaturalWidths = usedMinimumWidth - - totalExplicitColumnsWidths; - - // we have some space that can be divided optimally - HeaderCell hCell; - colIndex = 0; - headCells = tHead.iterator(); - int checksum = 0; - while (headCells.hasNext()) { - hCell = (HeaderCell) headCells.next(); - if (!hCell.isDefinedWidth()) { - int w = hCell.getNaturalColumnWidth(colIndex); - int newSpace; - if (expandRatioDivider > 0) { - // divide excess space by expand ratios - newSpace = Math.round((w + extraSpace - * hCell.getExpandRatio() / expandRatioDivider)); - } else { - if (totalUndefinedNaturalWidths != 0) { - // divide relatively to natural column widths - newSpace = Math.round(w + (float) extraSpace - * (float) w / totalUndefinedNaturalWidths); - } else { - newSpace = w; - } - } - checksum += newSpace; - setColWidth(colIndex, newSpace, false); - } else { - checksum += hCell.getWidth(); - } - colIndex++; - } - - if (extraSpace > 0 && checksum != availW) { - /* - * There might be in some cases a rounding error of 1px when - * extra space is divided so if there is one then we give the - * first undefined column 1 more pixel - */ - headCells = tHead.iterator(); - colIndex = 0; - while (headCells.hasNext()) { - HeaderCell hc = (HeaderCell) headCells.next(); - if (!hc.isDefinedWidth()) { - setColWidth(colIndex, - hc.getWidth() + availW - checksum, false); - break; - } - colIndex++; - } - } - - if (isDynamicHeight() && totalRows == pageLength) { - // fix body height (may vary if lazy loading is offhorizontal - // scrollbar appears/disappears) - int bodyHeight = scrollBody.getRequiredHeight(); - boolean needsSpaceForHorizontalScrollbar = (availW < usedMinimumWidth); - if (needsSpaceForHorizontalScrollbar) { - bodyHeight += Util.getNativeScrollbarSize(); - } - int heightBefore = getOffsetHeight(); - scrollBodyPanel.setHeight(bodyHeight + "px"); - if (heightBefore != getOffsetHeight()) { - Util.notifyParentOfSizeChange(VScrollTable.this, false); - } - } - scrollBody.reLayoutComponents(); - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); - } - }); - - forceRealignColumnHeaders(); - } - - }; - - private void forceRealignColumnHeaders() { - if (BrowserInfo.get().isIE()) { - /* - * IE does not fire onscroll event if scroll position is reverted to - * 0 due to the content element size growth. Ensure headers are in - * sync with content manually. Safe to use null event as we don't - * actually use the event object in listener. - */ - onScroll(null); - } - } - - /** - * helper to set pixel size of head and body part - * - * @param pixels - */ - private void setContentWidth(int pixels) { - tHead.setWidth(pixels + "px"); - scrollBodyPanel.setWidth(pixels + "px"); - tFoot.setWidth(pixels + "px"); - } - - private int borderWidth = -1; - - /** - * @return border left + border right - */ - private int getBorderWidth() { - if (borderWidth < 0) { - borderWidth = Util.measureHorizontalPaddingAndBorder( - scrollBodyPanel.getElement(), 2); - if (borderWidth < 0) { - borderWidth = 0; - } - } - return borderWidth; - } - - /** - * Ensures scrollable area is properly sized. This method is used when fixed - * size is used. - */ - private int containerHeight; - - private void setContainerHeight() { - if (!isDynamicHeight()) { - containerHeight = getOffsetHeight(); - containerHeight -= showColHeaders ? tHead.getOffsetHeight() : 0; - containerHeight -= tFoot.getOffsetHeight(); - containerHeight -= getContentAreaBorderHeight(); - if (containerHeight < 0) { - containerHeight = 0; - } - scrollBodyPanel.setHeight(containerHeight + "px"); - } - } - - private int contentAreaBorderHeight = -1; - private int scrollLeft; - private int scrollTop; - VScrollTableDropHandler dropHandler; - private boolean navKeyDown; - boolean multiselectPending; - - /** - * @return border top + border bottom of the scrollable area of table - */ - private int getContentAreaBorderHeight() { - if (contentAreaBorderHeight < 0) { - - DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow", - "hidden"); - int oh = scrollBodyPanel.getOffsetHeight(); - int ch = scrollBodyPanel.getElement() - .getPropertyInt("clientHeight"); - contentAreaBorderHeight = oh - ch; - DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow", - "auto"); - } - return contentAreaBorderHeight; - } - - @Override - public void setHeight(String height) { - if (height.length() == 0 - && getElement().getStyle().getHeight().length() != 0) { - /* - * Changing from defined to undefined size -> should do a size init - * to take page length into account again - */ - sizeNeedsInit = true; - } - super.setHeight(height); - } - - void updateHeight() { - setContainerHeight(); - - updatePageLength(); - - if (!rendering) { - // Webkit may sometimes get an odd rendering bug (white space - // between header and body), see bug #3875. Running - // overflow hack here to shake body element a bit. - Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); - } - - /* - * setting height may affect wheter the component has scrollbars -> - * needs scrolling or not - */ - setProperTabIndex(); - - } - - /* - * Overridden due Table might not survive of visibility change (scroll pos - * lost). Example ITabPanel just set contained components invisible and back - * when changing tabs. - */ - @Override - public void setVisible(boolean visible) { - if (isVisible() != visible) { - super.setVisible(visible); - if (initializedAndAttached) { - if (visible) { - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - scrollBodyPanel - .setScrollPosition(measureRowHeightOffset(firstRowInViewPort)); - } - }); - } - } - } - } - - /** - * Helper function to build html snippet for column or row headers - * - * @param uidl - * possibly with values caption and icon - * @return html snippet containing possibly an icon + caption text - */ - protected String buildCaptionHtmlSnippet(UIDL uidl) { - String s = uidl.hasAttribute("caption") ? uidl - .getStringAttribute("caption") : ""; - if (uidl.hasAttribute("icon")) { - s = "\"icon\"" + s; - } - return s; - } - - /** - * This method has logic which rows needs to be requested from server when - * user scrolls - */ - public void onScroll(ScrollEvent event) { - scrollLeft = scrollBodyPanel.getElement().getScrollLeft(); - scrollTop = scrollBodyPanel.getScrollPosition(); - /* - * #6970 - IE sometimes fires scroll events for a detached table. - * - * FIXME initializedAndAttached should probably be renamed - its name - * doesn't seem to reflect its semantics. onDetach() doesn't set it to - * false, and changing that might break something else, so we need to - * check isAttached() separately. - */ - if (!initializedAndAttached || !isAttached()) { - return; - } - if (!enabled) { - scrollBodyPanel - .setScrollPosition(measureRowHeightOffset(firstRowInViewPort)); - return; - } - - rowRequestHandler.cancel(); - - if (BrowserInfo.get().isSafari() && event != null && scrollTop == 0) { - // due to the webkitoverflowworkaround, top may sometimes report 0 - // for webkit, although it really is not. Expecting to have the - // correct - // value available soon. - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - onScroll(null); - } - }); - return; - } - - // fix headers horizontal scrolling - tHead.setHorizontalScrollPosition(scrollLeft); - - // fix footers horizontal scrolling - tFoot.setHorizontalScrollPosition(scrollLeft); - - firstRowInViewPort = calcFirstRowInViewPort(); - if (firstRowInViewPort > totalRows - pageLength) { - firstRowInViewPort = totalRows - pageLength; - } - - int postLimit = (int) (firstRowInViewPort + (pageLength - 1) + pageLength - * cache_react_rate); - if (postLimit > totalRows - 1) { - postLimit = totalRows - 1; - } - int preLimit = (int) (firstRowInViewPort - pageLength - * cache_react_rate); - if (preLimit < 0) { - preLimit = 0; - } - final int lastRendered = scrollBody.getLastRendered(); - final int firstRendered = scrollBody.getFirstRendered(); - - if (postLimit <= lastRendered && preLimit >= firstRendered) { - // we're within no-react area, no need to request more rows - // remember which firstvisible we requested, in case the server has - // a differing opinion - lastRequestedFirstvisible = firstRowInViewPort; - client.updateVariable(paintableId, "firstvisible", - firstRowInViewPort, false); - return; - } - - if (firstRowInViewPort - pageLength * cache_rate > lastRendered - || firstRowInViewPort + pageLength + pageLength * cache_rate < firstRendered) { - // need a totally new set of rows - rowRequestHandler - .setReqFirstRow((firstRowInViewPort - (int) (pageLength * cache_rate))); - int last = firstRowInViewPort + (int) (cache_rate * pageLength) - + pageLength - 1; - if (last >= totalRows) { - last = totalRows - 1; - } - rowRequestHandler.setReqRows(last - - rowRequestHandler.getReqFirstRow() + 1); - rowRequestHandler.deferRowFetch(); - return; - } - if (preLimit < firstRendered) { - // need some rows to the beginning of the rendered area - rowRequestHandler - .setReqFirstRow((int) (firstRowInViewPort - pageLength - * cache_rate)); - rowRequestHandler.setReqRows(firstRendered - - rowRequestHandler.getReqFirstRow()); - rowRequestHandler.deferRowFetch(); - - return; - } - if (postLimit > lastRendered) { - // need some rows to the end of the rendered area - rowRequestHandler.setReqFirstRow(lastRendered + 1); - rowRequestHandler.setReqRows((int) ((firstRowInViewPort - + pageLength + pageLength * cache_rate) - lastRendered)); - rowRequestHandler.deferRowFetch(); - } - } - - protected int calcFirstRowInViewPort() { - return (int) Math.ceil(scrollTop / scrollBody.getRowHeight()); - } - - public VScrollTableDropHandler getDropHandler() { - return dropHandler; - } - - private static class TableDDDetails { - int overkey = -1; - VerticalDropLocation dropLocation; - String colkey; - - @Override - public boolean equals(Object obj) { - if (obj instanceof TableDDDetails) { - TableDDDetails other = (TableDDDetails) obj; - return dropLocation == other.dropLocation - && overkey == other.overkey - && ((colkey != null && colkey.equals(other.colkey)) || (colkey == null && other.colkey == null)); - } - return false; - } - - // @Override - // public int hashCode() { - // return overkey; - // } - } - - public class VScrollTableDropHandler extends VAbstractDropHandler { - - private static final String ROWSTYLEBASE = "v-table-row-drag-"; - private TableDDDetails dropDetails; - private TableDDDetails lastEmphasized; - - @Override - public void dragEnter(VDragEvent drag) { - updateDropDetails(drag); - super.dragEnter(drag); - } - - private void updateDropDetails(VDragEvent drag) { - dropDetails = new TableDDDetails(); - Element elementOver = drag.getElementOver(); - - VScrollTableRow row = Util.findWidget(elementOver, getRowClass()); - if (row != null) { - dropDetails.overkey = row.rowKey; - Element tr = row.getElement(); - Element element = elementOver; - while (element != null && element.getParentElement() != tr) { - element = (Element) element.getParentElement(); - } - int childIndex = DOM.getChildIndex(tr, element); - dropDetails.colkey = tHead.getHeaderCell(childIndex) - .getColKey(); - dropDetails.dropLocation = DDUtil.getVerticalDropLocation( - row.getElement(), drag.getCurrentGwtEvent(), 0.2); - } - - drag.getDropDetails().put("itemIdOver", dropDetails.overkey + ""); - drag.getDropDetails().put( - "detail", - dropDetails.dropLocation != null ? dropDetails.dropLocation - .toString() : null); - - } - - private Class getRowClass() { - // get the row type this way to make dd work in derived - // implementations - return scrollBody.iterator().next().getClass(); - } - - @Override - public void dragOver(VDragEvent drag) { - TableDDDetails oldDetails = dropDetails; - updateDropDetails(drag); - if (!oldDetails.equals(dropDetails)) { - deEmphasis(); - final TableDDDetails newDetails = dropDetails; - VAcceptCallback cb = new VAcceptCallback() { - public void accepted(VDragEvent event) { - if (newDetails.equals(dropDetails)) { - dragAccepted(event); - } - /* - * Else new target slot already defined, ignore - */ - } - }; - validate(cb, drag); - } - } - - @Override - public void dragLeave(VDragEvent drag) { - deEmphasis(); - super.dragLeave(drag); - } - - @Override - public boolean drop(VDragEvent drag) { - deEmphasis(); - return super.drop(drag); - } - - private void deEmphasis() { - UIObject.setStyleName(getElement(), CLASSNAME + "-drag", false); - if (lastEmphasized == null) { - return; - } - for (Widget w : scrollBody.renderedRows) { - VScrollTableRow row = (VScrollTableRow) w; - if (lastEmphasized != null - && row.rowKey == lastEmphasized.overkey) { - String stylename = ROWSTYLEBASE - + lastEmphasized.dropLocation.toString() - .toLowerCase(); - VScrollTableRow.setStyleName(row.getElement(), stylename, - false); - lastEmphasized = null; - return; - } - } - } - - /** - * TODO needs different drop modes ?? (on cells, on rows), now only - * supports rows - */ - private void emphasis(TableDDDetails details) { - deEmphasis(); - UIObject.setStyleName(getElement(), CLASSNAME + "-drag", true); - // iterate old and new emphasized row - for (Widget w : scrollBody.renderedRows) { - VScrollTableRow row = (VScrollTableRow) w; - if (details != null && details.overkey == row.rowKey) { - String stylename = ROWSTYLEBASE - + details.dropLocation.toString().toLowerCase(); - VScrollTableRow.setStyleName(row.getElement(), stylename, - true); - lastEmphasized = details; - return; - } - } - } - - @Override - protected void dragAccepted(VDragEvent drag) { - emphasis(dropDetails); - } - - @Override - public ComponentConnector getConnector() { - return ConnectorMap.get(client).getConnector(VScrollTable.this); - } - - public ApplicationConnection getApplicationConnection() { - return client; - } - - } - - protected VScrollTableRow getFocusedRow() { - return focusedRow; - } - - /** - * Moves the selection head to a specific row - * - * @param row - * The row to where the selection head should move - * @return Returns true if focus was moved successfully, else false - */ - protected boolean setRowFocus(VScrollTableRow row) { - - if (!isSelectable()) { - return false; - } - - // Remove previous selection - if (focusedRow != null && focusedRow != row) { - focusedRow.removeStyleName(CLASSNAME_SELECTION_FOCUS); - } - - if (row != null) { - - // Apply focus style to new selection - row.addStyleName(CLASSNAME_SELECTION_FOCUS); - - /* - * Trying to set focus on already focused row - */ - if (row == focusedRow) { - return false; - } - - // Set new focused row - focusedRow = row; - - ensureRowIsVisible(row); - - return true; - } - - return false; - } - - /** - * Ensures that the row is visible - * - * @param row - * The row to ensure is visible - */ - private void ensureRowIsVisible(VScrollTableRow row) { - Util.scrollIntoViewVertically(row.getElement()); - } - - /** - * Handles the keyboard events handled by the table - * - * @param event - * The keyboard event received - * @return true iff the navigation event was handled - */ - protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { - if (keycode == KeyCodes.KEY_TAB || keycode == KeyCodes.KEY_SHIFT) { - // Do not handle tab key - return false; - } - - // Down navigation - if (!isSelectable() && keycode == getNavigationDownKey()) { - scrollBodyPanel.setScrollPosition(scrollBodyPanel - .getScrollPosition() + scrollingVelocity); - return true; - } else if (keycode == getNavigationDownKey()) { - if (isMultiSelectModeAny() && moveFocusDown()) { - selectFocusedRow(ctrl, shift); - - } else if (isSingleSelectMode() && !shift && moveFocusDown()) { - selectFocusedRow(ctrl, shift); - } - return true; - } - - // Up navigation - if (!isSelectable() && keycode == getNavigationUpKey()) { - scrollBodyPanel.setScrollPosition(scrollBodyPanel - .getScrollPosition() - scrollingVelocity); - return true; - } else if (keycode == getNavigationUpKey()) { - if (isMultiSelectModeAny() && moveFocusUp()) { - selectFocusedRow(ctrl, shift); - } else if (isSingleSelectMode() && !shift && moveFocusUp()) { - selectFocusedRow(ctrl, shift); - } - return true; - } - - if (keycode == getNavigationLeftKey()) { - // Left navigation - scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel - .getHorizontalScrollPosition() - scrollingVelocity); - return true; - - } else if (keycode == getNavigationRightKey()) { - // Right navigation - scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel - .getHorizontalScrollPosition() + scrollingVelocity); - return true; - } - - // Select navigation - if (isSelectable() && keycode == getNavigationSelectKey()) { - if (isSingleSelectMode()) { - boolean wasSelected = focusedRow.isSelected(); - deselectAll(); - if (!wasSelected || !nullSelectionAllowed) { - focusedRow.toggleSelection(); - } - } else { - focusedRow.toggleSelection(); - removeRowFromUnsentSelectionRanges(focusedRow); - } - - sendSelectedRows(); - return true; - } - - // Page Down navigation - if (keycode == getNavigationPageDownKey()) { - if (isSelectable()) { - /* - * If selectable we plagiate MSW behaviour: first scroll to the - * end of current view. If at the end, scroll down one page - * length and keep the selected row in the bottom part of - * visible area. - */ - if (!isFocusAtTheEndOfTable()) { - VScrollTableRow lastVisibleRowInViewPort = scrollBody - .getRowByRowIndex(firstRowInViewPort - + getFullyVisibleRowCount() - 1); - if (lastVisibleRowInViewPort != null - && lastVisibleRowInViewPort != focusedRow) { - // focused row is not at the end of the table, move - // focus and select the last visible row - setRowFocus(lastVisibleRowInViewPort); - selectFocusedRow(ctrl, shift); - sendSelectedRows(); - } else { - int indexOfToBeFocused = focusedRow.getIndex() - + getFullyVisibleRowCount(); - if (indexOfToBeFocused >= totalRows) { - indexOfToBeFocused = totalRows - 1; - } - VScrollTableRow toBeFocusedRow = scrollBody - .getRowByRowIndex(indexOfToBeFocused); - - if (toBeFocusedRow != null) { - /* - * if the next focused row is rendered - */ - setRowFocus(toBeFocusedRow); - selectFocusedRow(ctrl, shift); - // TODO needs scrollintoview ? - sendSelectedRows(); - } else { - // scroll down by pixels and return, to wait for - // new rows, then select the last item in the - // viewport - selectLastItemInNextRender = true; - multiselectPending = shift; - scrollByPagelenght(1); - } - } - } - } else { - /* No selections, go page down by scrolling */ - scrollByPagelenght(1); - } - return true; - } - - // Page Up navigation - if (keycode == getNavigationPageUpKey()) { - if (isSelectable()) { - /* - * If selectable we plagiate MSW behaviour: first scroll to the - * end of current view. If at the end, scroll down one page - * length and keep the selected row in the bottom part of - * visible area. - */ - if (!isFocusAtTheBeginningOfTable()) { - VScrollTableRow firstVisibleRowInViewPort = scrollBody - .getRowByRowIndex(firstRowInViewPort); - if (firstVisibleRowInViewPort != null - && firstVisibleRowInViewPort != focusedRow) { - // focus is not at the beginning of the table, move - // focus and select the first visible row - setRowFocus(firstVisibleRowInViewPort); - selectFocusedRow(ctrl, shift); - sendSelectedRows(); - } else { - int indexOfToBeFocused = focusedRow.getIndex() - - getFullyVisibleRowCount(); - if (indexOfToBeFocused < 0) { - indexOfToBeFocused = 0; - } - VScrollTableRow toBeFocusedRow = scrollBody - .getRowByRowIndex(indexOfToBeFocused); - - if (toBeFocusedRow != null) { // if the next focused row - // is rendered - setRowFocus(toBeFocusedRow); - selectFocusedRow(ctrl, shift); - // TODO needs scrollintoview ? - sendSelectedRows(); - } else { - // unless waiting for the next rowset already - // scroll down by pixels and return, to wait for - // new rows, then select the last item in the - // viewport - selectFirstItemInNextRender = true; - multiselectPending = shift; - scrollByPagelenght(-1); - } - } - } - } else { - /* No selections, go page up by scrolling */ - scrollByPagelenght(-1); - } - - return true; - } - - // Goto start navigation - if (keycode == getNavigationStartKey()) { - scrollBodyPanel.setScrollPosition(0); - if (isSelectable()) { - if (focusedRow != null && focusedRow.getIndex() == 0) { - return false; - } else { - VScrollTableRow rowByRowIndex = (VScrollTableRow) scrollBody - .iterator().next(); - if (rowByRowIndex.getIndex() == 0) { - setRowFocus(rowByRowIndex); - selectFocusedRow(ctrl, shift); - sendSelectedRows(); - } else { - // first row of table will come in next row fetch - if (ctrl) { - focusFirstItemInNextRender = true; - } else { - selectFirstItemInNextRender = true; - multiselectPending = shift; - } - } - } - } - return true; - } - - // Goto end navigation - if (keycode == getNavigationEndKey()) { - scrollBodyPanel.setScrollPosition(scrollBody.getOffsetHeight()); - if (isSelectable()) { - final int lastRendered = scrollBody.getLastRendered(); - if (lastRendered + 1 == totalRows) { - VScrollTableRow rowByRowIndex = scrollBody - .getRowByRowIndex(lastRendered); - if (focusedRow != rowByRowIndex) { - setRowFocus(rowByRowIndex); - selectFocusedRow(ctrl, shift); - sendSelectedRows(); - } - } else { - if (ctrl) { - focusLastItemInNextRender = true; - } else { - selectLastItemInNextRender = true; - multiselectPending = shift; - } - } - } - return true; - } - - return false; - } - - private boolean isFocusAtTheBeginningOfTable() { - return focusedRow.getIndex() == 0; - } - - private boolean isFocusAtTheEndOfTable() { - return focusedRow.getIndex() + 1 >= totalRows; - } - - private int getFullyVisibleRowCount() { - return (int) (scrollBodyPanel.getOffsetHeight() / scrollBody - .getRowHeight()); - } - - private void scrollByPagelenght(int i) { - int pixels = i * scrollBodyPanel.getOffsetHeight(); - int newPixels = scrollBodyPanel.getScrollPosition() + pixels; - if (newPixels < 0) { - newPixels = 0; - } // else if too high, NOP (all know browsers accept illegally big - // values here) - scrollBodyPanel.setScrollPosition(newPixels); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event - * .dom.client.FocusEvent) - */ - public void onFocus(FocusEvent event) { - if (isFocusable()) { - hasFocus = true; - - // Focus a row if no row is in focus - if (focusedRow == null) { - focusRowFromBody(); - } else { - setRowFocus(focusedRow); - } - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event - * .dom.client.BlurEvent) - */ - public void onBlur(BlurEvent event) { - hasFocus = false; - navKeyDown = false; - - if (BrowserInfo.get().isIE()) { - // IE sometimes moves focus to a clicked table cell... - Element focusedElement = Util.getIEFocusedElement(); - if (Util.getConnectorForElement(client, getParent(), focusedElement) == this) { - // ..in that case, steal the focus back to the focus handler - // but not if focus is in a child component instead (#7965) - focus(); - return; - } - } - - if (isFocusable()) { - // Unfocus any row - setRowFocus(null); - } - } - - /** - * Removes a key from a range if the key is found in a selected range - * - * @param key - * The key to remove - */ - private void removeRowFromUnsentSelectionRanges(VScrollTableRow row) { - Collection newRanges = null; - for (Iterator iterator = selectedRowRanges.iterator(); iterator - .hasNext();) { - SelectionRange range = iterator.next(); - if (range.inRange(row)) { - // Split the range if given row is in range - Collection splitranges = range.split(row); - if (newRanges == null) { - newRanges = new ArrayList(); - } - newRanges.addAll(splitranges); - iterator.remove(); - } - } - if (newRanges != null) { - selectedRowRanges.addAll(newRanges); - } - } - - /** - * Can the Table be focused? - * - * @return True if the table can be focused, else false - */ - public boolean isFocusable() { - if (scrollBody != null && enabled) { - return !(!hasHorizontalScrollbar() && !hasVerticalScrollbar() && !isSelectable()); - } - return false; - } - - private boolean hasHorizontalScrollbar() { - return scrollBody.getOffsetWidth() > scrollBodyPanel.getOffsetWidth(); - } - - private boolean hasVerticalScrollbar() { - return scrollBody.getOffsetHeight() > scrollBodyPanel.getOffsetHeight(); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.terminal.gwt.client.Focusable#focus() - */ - public void focus() { - if (isFocusable()) { - scrollBodyPanel.focus(); - } - } - - /** - * Sets the proper tabIndex for scrollBodyPanel (the focusable elemen in the - * component). - * - * If the component has no explicit tabIndex a zero is given (default - * tabbing order based on dom hierarchy) or -1 if the component does not - * need to gain focus. The component needs no focus if it has no scrollabars - * (not scrollable) and not selectable. Note that in the future shortcut - * actions may need focus. - * - */ - void setProperTabIndex() { - int storedScrollTop = 0; - int storedScrollLeft = 0; - - if (BrowserInfo.get().getOperaVersion() >= 11) { - // Workaround for Opera scroll bug when changing tabIndex (#6222) - storedScrollTop = scrollBodyPanel.getScrollPosition(); - storedScrollLeft = scrollBodyPanel.getHorizontalScrollPosition(); - } - - if (tabIndex == 0 && !isFocusable()) { - scrollBodyPanel.setTabIndex(-1); - } else { - scrollBodyPanel.setTabIndex(tabIndex); - } - - if (BrowserInfo.get().getOperaVersion() >= 11) { - // Workaround for Opera scroll bug when changing tabIndex (#6222) - scrollBodyPanel.setScrollPosition(storedScrollTop); - scrollBodyPanel.setHorizontalScrollPosition(storedScrollLeft); - } - } - - public void startScrollingVelocityTimer() { - if (scrollingVelocityTimer == null) { - scrollingVelocityTimer = new Timer() { - @Override - public void run() { - scrollingVelocity++; - } - }; - scrollingVelocityTimer.scheduleRepeating(100); - } - } - - public void cancelScrollingVelocityTimer() { - if (scrollingVelocityTimer != null) { - // Remove velocityTimer if it exists and the Table is disabled - scrollingVelocityTimer.cancel(); - scrollingVelocityTimer = null; - scrollingVelocity = 10; - } - } - - /** - * - * @param keyCode - * @return true if the given keyCode is used by the table for navigation - */ - private boolean isNavigationKey(int keyCode) { - return keyCode == getNavigationUpKey() - || keyCode == getNavigationLeftKey() - || keyCode == getNavigationRightKey() - || keyCode == getNavigationDownKey() - || keyCode == getNavigationPageUpKey() - || keyCode == getNavigationPageDownKey() - || keyCode == getNavigationEndKey() - || keyCode == getNavigationStartKey(); - } - - public void lazyRevertFocusToRow(final VScrollTableRow currentlyFocusedRow) { - Scheduler.get().scheduleFinally(new ScheduledCommand() { - public void execute() { - if (currentlyFocusedRow != null) { - setRowFocus(currentlyFocusedRow); - } else { - VConsole.log("no row?"); - focusRowFromBody(); - } - scrollBody.ensureFocus(); - } - }); - } - - public Action[] getActions() { - if (bodyActionKeys == null) { - return new Action[] {}; - } - final Action[] actions = new Action[bodyActionKeys.length]; - for (int i = 0; i < actions.length; i++) { - final String actionKey = bodyActionKeys[i]; - Action bodyAction = new TreeAction(this, null, actionKey); - bodyAction.setCaption(getActionCaption(actionKey)); - bodyAction.setIconUrl(getActionIcon(actionKey)); - actions[i] = bodyAction; - } - return actions; - } - - public ApplicationConnection getClient() { - return client; - } - - public String getPaintableId() { - return paintableId; - } - - /** - * Add this to the element mouse down event by using element.setPropertyJSO - * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again - * when the mouse is depressed in the mouse up event. - * - * @return Returns the JSO preventing text selection - */ - private static native JavaScriptObject getPreventTextSelectionIEHack() - /*-{ - return function(){ return false; }; - }-*/; - - protected void triggerLazyColumnAdjustment(boolean now) { - lazyAdjustColumnWidths.cancel(); - if (now) { - lazyAdjustColumnWidths.run(); - } else { - lazyAdjustColumnWidths.schedule(LAZY_COLUMN_ADJUST_TIMEOUT); - } - } - - private boolean isDynamicWidth() { - ComponentConnector paintable = ConnectorMap.get(client).getConnector( - this); - return paintable.isUndefinedWidth(); - } - - private boolean isDynamicHeight() { - ComponentConnector paintable = ConnectorMap.get(client).getConnector( - this); - if (paintable == null) { - // This should be refactored. As isDynamicHeight can be called from - // a timer it is possible that the connector has been unregistered - // when this method is called, causing getConnector to return null. - return false; - } - return paintable.isUndefinedHeight(); - } - - private void debug(String msg) { - if (enableDebug) { - VConsole.error(msg); - } - } - - public Widget getWidgetForPaintable() { - return this; - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VSlider.java b/src/com/vaadin/terminal/gwt/client/ui/VSlider.java deleted file mode 100644 index 09cdcaf324..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VSlider.java +++ /dev/null @@ -1,512 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -// -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.user.client.Command; -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.Window; -import com.google.gwt.user.client.ui.HTML; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.ContainerResizedListener; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VConsole; - -public class VSlider extends SimpleFocusablePanel implements Field, - ContainerResizedListener { - - public static final String CLASSNAME = "v-slider"; - - /** - * Minimum size (width or height, depending on orientation) of the slider - * base. - */ - private static final int MIN_SIZE = 50; - - ApplicationConnection client; - - String id; - - boolean immediate; - boolean disabled; - boolean readonly; - - private int acceleration = 1; - double min; - double max; - int resolution; - Double value; - boolean vertical; - - private final HTML feedback = new HTML("", false); - private final VOverlay feedbackPopup = new VOverlay(true, false, true) { - @Override - public void show() { - super.show(); - updateFeedbackPosition(); - } - }; - - /* DOM element for slider's base */ - private final Element base; - private final int BASE_BORDER_WIDTH = 1; - - /* DOM element for slider's handle */ - private final Element handle; - - /* DOM element for decrement arrow */ - private final Element smaller; - - /* DOM element for increment arrow */ - private final Element bigger; - - /* Temporary dragging/animation variables */ - private boolean dragging = false; - - private VLazyExecutor delayedValueUpdater = new VLazyExecutor(100, - new ScheduledCommand() { - - public void execute() { - updateValueToServer(); - acceleration = 1; - } - }); - - public VSlider() { - super(); - - base = DOM.createDiv(); - handle = DOM.createDiv(); - smaller = DOM.createDiv(); - bigger = DOM.createDiv(); - - setStyleName(CLASSNAME); - DOM.setElementProperty(base, "className", CLASSNAME + "-base"); - DOM.setElementProperty(handle, "className", CLASSNAME + "-handle"); - DOM.setElementProperty(smaller, "className", CLASSNAME + "-smaller"); - DOM.setElementProperty(bigger, "className", CLASSNAME + "-bigger"); - - DOM.appendChild(getElement(), bigger); - DOM.appendChild(getElement(), smaller); - DOM.appendChild(getElement(), base); - DOM.appendChild(base, handle); - - // Hide initially - DOM.setStyleAttribute(smaller, "display", "none"); - DOM.setStyleAttribute(bigger, "display", "none"); - DOM.setStyleAttribute(handle, "visibility", "hidden"); - - sinkEvents(Event.MOUSEEVENTS | Event.ONMOUSEWHEEL | Event.KEYEVENTS - | Event.FOCUSEVENTS | Event.TOUCHEVENTS); - - feedbackPopup.addStyleName(CLASSNAME + "-feedback"); - feedbackPopup.setWidget(feedback); - } - - void setFeedbackValue(double value) { - String currentValue = "" + value; - if (resolution == 0) { - currentValue = "" + new Double(value).intValue(); - } - feedback.setText(currentValue); - } - - private void updateFeedbackPosition() { - if (vertical) { - feedbackPopup.setPopupPosition( - DOM.getAbsoluteLeft(handle) + handle.getOffsetWidth(), - DOM.getAbsoluteTop(handle) + handle.getOffsetHeight() / 2 - - feedbackPopup.getOffsetHeight() / 2); - } else { - feedbackPopup.setPopupPosition( - DOM.getAbsoluteLeft(handle) + handle.getOffsetWidth() / 2 - - feedbackPopup.getOffsetWidth() / 2, - DOM.getAbsoluteTop(handle) - - feedbackPopup.getOffsetHeight()); - } - } - - void buildBase() { - final String styleAttribute = vertical ? "height" : "width"; - final String domProperty = vertical ? "offsetHeight" : "offsetWidth"; - - final Element p = DOM.getParent(getElement()); - if (DOM.getElementPropertyInt(p, domProperty) > 50) { - if (vertical) { - setHeight(); - } else { - DOM.setStyleAttribute(base, styleAttribute, ""); - } - } else { - // Set minimum size and adjust after all components have - // (supposedly) been drawn completely. - DOM.setStyleAttribute(base, styleAttribute, MIN_SIZE + "px"); - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - final Element p = DOM.getParent(getElement()); - if (DOM.getElementPropertyInt(p, domProperty) > (MIN_SIZE + 5)) { - if (vertical) { - setHeight(); - } else { - DOM.setStyleAttribute(base, styleAttribute, ""); - } - // Ensure correct position - setValue(value, false); - } - } - }); - } - - // TODO attach listeners for focusing and arrow keys - } - - void buildHandle() { - final String handleAttribute = vertical ? "marginTop" : "marginLeft"; - - DOM.setStyleAttribute(handle, handleAttribute, "0"); - - // Restore visibility - DOM.setStyleAttribute(handle, "visibility", "visible"); - - } - - void setValue(Double value, boolean updateToServer) { - if (value == null) { - return; - } - - if (value < min) { - value = min; - } else if (value > max) { - value = max; - } - - // Update handle position - final String styleAttribute = vertical ? "marginTop" : "marginLeft"; - final String domProperty = vertical ? "offsetHeight" : "offsetWidth"; - final int handleSize = Integer.parseInt(DOM.getElementProperty(handle, - domProperty)); - final int baseSize = Integer.parseInt(DOM.getElementProperty(base, - domProperty)) - (2 * BASE_BORDER_WIDTH); - - final int range = baseSize - handleSize; - double v = value.doubleValue(); - - // Round value to resolution - if (resolution > 0) { - v = Math.round(v * Math.pow(10, resolution)); - v = v / Math.pow(10, resolution); - } else { - v = Math.round(v); - } - final double valueRange = max - min; - double p = 0; - if (valueRange > 0) { - p = range * ((v - min) / valueRange); - } - if (p < 0) { - p = 0; - } - if (vertical) { - p = range - p; - } - final double pos = p; - - DOM.setStyleAttribute(handle, styleAttribute, (Math.round(pos)) + "px"); - - // Update value - this.value = new Double(v); - setFeedbackValue(v); - - if (updateToServer) { - updateValueToServer(); - } - } - - @Override - public void onBrowserEvent(Event event) { - if (disabled || readonly) { - return; - } - final Element targ = DOM.eventGetTarget(event); - - if (DOM.eventGetType(event) == Event.ONMOUSEWHEEL) { - processMouseWheelEvent(event); - } else if (dragging || targ == handle) { - processHandleEvent(event); - } else if (targ == smaller) { - decreaseValue(true); - } else if (targ == bigger) { - increaseValue(true); - } else if (DOM.eventGetType(event) == Event.MOUSEEVENTS) { - processBaseEvent(event); - } else if ((BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYPRESS) - || (!BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYDOWN)) { - - if (handleNavigation(event.getKeyCode(), event.getCtrlKey(), - event.getShiftKey())) { - - feedbackPopup.show(); - - delayedValueUpdater.trigger(); - - DOM.eventPreventDefault(event); - DOM.eventCancelBubble(event, true); - } - } else if (targ.equals(getElement()) - && DOM.eventGetType(event) == Event.ONFOCUS) { - feedbackPopup.show(); - } else if (targ.equals(getElement()) - && DOM.eventGetType(event) == Event.ONBLUR) { - feedbackPopup.hide(); - } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) { - feedbackPopup.show(); - } - if (Util.isTouchEvent(event)) { - event.preventDefault(); // avoid simulated events - event.stopPropagation(); - } - } - - private void processMouseWheelEvent(final Event event) { - final int dir = DOM.eventGetMouseWheelVelocityY(event); - - if (dir < 0) { - increaseValue(false); - } else { - decreaseValue(false); - } - - delayedValueUpdater.trigger(); - - DOM.eventPreventDefault(event); - DOM.eventCancelBubble(event, true); - } - - private void processHandleEvent(Event event) { - switch (DOM.eventGetType(event)) { - case Event.ONMOUSEDOWN: - case Event.ONTOUCHSTART: - if (!disabled && !readonly) { - focus(); - feedbackPopup.show(); - dragging = true; - DOM.setElementProperty(handle, "className", CLASSNAME - + "-handle " + CLASSNAME + "-handle-active"); - DOM.setCapture(getElement()); - DOM.eventPreventDefault(event); // prevent selecting text - DOM.eventCancelBubble(event, true); - event.stopPropagation(); - VConsole.log("Slider move start"); - } - break; - case Event.ONMOUSEMOVE: - case Event.ONTOUCHMOVE: - if (dragging) { - VConsole.log("Slider move"); - setValueByEvent(event, false); - updateFeedbackPosition(); - event.stopPropagation(); - } - break; - case Event.ONTOUCHEND: - feedbackPopup.hide(); - case Event.ONMOUSEUP: - // feedbackPopup.hide(); - VConsole.log("Slider move end"); - dragging = false; - DOM.setElementProperty(handle, "className", CLASSNAME + "-handle"); - DOM.releaseCapture(getElement()); - setValueByEvent(event, true); - event.stopPropagation(); - break; - default: - break; - } - } - - private void processBaseEvent(Event event) { - if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) { - if (!disabled && !readonly && !dragging) { - setValueByEvent(event, true); - DOM.eventCancelBubble(event, true); - } - } - } - - private void decreaseValue(boolean updateToServer) { - setValue(new Double(value.doubleValue() - Math.pow(10, -resolution)), - updateToServer); - } - - private void increaseValue(boolean updateToServer) { - setValue(new Double(value.doubleValue() + Math.pow(10, -resolution)), - updateToServer); - } - - private void setValueByEvent(Event event, boolean updateToServer) { - double v = min; // Fallback to min - - final int coord = getEventPosition(event); - - final int handleSize, baseSize, baseOffset; - if (vertical) { - handleSize = handle.getOffsetHeight(); - baseSize = base.getOffsetHeight(); - baseOffset = base.getAbsoluteTop() - Window.getScrollTop() - - handleSize / 2; - } else { - handleSize = handle.getOffsetWidth(); - baseSize = base.getOffsetWidth(); - baseOffset = base.getAbsoluteLeft() - Window.getScrollLeft() - + handleSize / 2; - } - - if (vertical) { - v = ((baseSize - (coord - baseOffset)) / (double) (baseSize - handleSize)) - * (max - min) + min; - } else { - v = ((coord - baseOffset) / (double) (baseSize - handleSize)) - * (max - min) + min; - } - - if (v < min) { - v = min; - } else if (v > max) { - v = max; - } - - setValue(v, updateToServer); - } - - /** - * TODO consider extracting touches support to an impl class specific for - * webkit (only browser that really supports touches). - * - * @param event - * @return - */ - protected int getEventPosition(Event event) { - if (vertical) { - return Util.getTouchOrMouseClientY(event); - } else { - return Util.getTouchOrMouseClientX(event); - } - } - - public void iLayout() { - if (vertical) { - setHeight(); - } - // Update handle position - setValue(value, false); - } - - private void setHeight() { - // Calculate decoration size - DOM.setStyleAttribute(base, "height", "0"); - DOM.setStyleAttribute(base, "overflow", "hidden"); - int h = DOM.getElementPropertyInt(getElement(), "offsetHeight"); - if (h < MIN_SIZE) { - h = MIN_SIZE; - } - DOM.setStyleAttribute(base, "height", h + "px"); - DOM.setStyleAttribute(base, "overflow", ""); - } - - private void updateValueToServer() { - client.updateVariable(id, "value", value.doubleValue(), immediate); - } - - /** - * Handles the keyboard events handled by the Slider - * - * @param event - * The keyboard event received - * @return true iff the navigation event was handled - */ - public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { - - // No support for ctrl moving - if (ctrl) { - return false; - } - - if ((keycode == getNavigationUpKey() && vertical) - || (keycode == getNavigationRightKey() && !vertical)) { - if (shift) { - for (int a = 0; a < acceleration; a++) { - increaseValue(false); - } - acceleration++; - } else { - increaseValue(false); - } - return true; - } else if (keycode == getNavigationDownKey() && vertical - || (keycode == getNavigationLeftKey() && !vertical)) { - if (shift) { - for (int a = 0; a < acceleration; a++) { - decreaseValue(false); - } - acceleration++; - } else { - decreaseValue(false); - } - return true; - } - - return false; - } - - /** - * Get the key that increases the vertical slider. By default it is the up - * arrow key but by overriding this you can change the key to whatever you - * want. - * - * @return The keycode of the key - */ - protected int getNavigationUpKey() { - return KeyCodes.KEY_UP; - } - - /** - * Get the key that decreases the vertical slider. By default it is the down - * arrow key but by overriding this you can change the key to whatever you - * want. - * - * @return The keycode of the key - */ - protected int getNavigationDownKey() { - return KeyCodes.KEY_DOWN; - } - - /** - * Get the key that decreases the horizontal slider. By default it is the - * left arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationLeftKey() { - return KeyCodes.KEY_LEFT; - } - - /** - * Get the key that increases the horizontal slider. By default it is the - * right arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationRightKey() { - return KeyCodes.KEY_RIGHT; - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelHorizontal.java b/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelHorizontal.java deleted file mode 100644 index 3902f064a5..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelHorizontal.java +++ /dev/null @@ -1,12 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -public class VSplitPanelHorizontal extends VAbstractSplitPanel { - - public VSplitPanelHorizontal() { - super(VAbstractSplitPanel.ORIENTATION_HORIZONTAL); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelVertical.java b/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelVertical.java deleted file mode 100644 index e61f8cf5e5..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VSplitPanelVertical.java +++ /dev/null @@ -1,12 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -public class VSplitPanelVertical extends VAbstractSplitPanel { - - public VSplitPanelVertical() { - super(VAbstractSplitPanel.ORIENTATION_VERTICAL); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTabsheet.java b/src/com/vaadin/terminal/gwt/client/ui/VTabsheet.java deleted file mode 100644 index 2cdc041955..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VTabsheet.java +++ /dev/null @@ -1,1219 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Iterator; -import java.util.List; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Style; -import com.google.gwt.dom.client.Style.Visibility; -import com.google.gwt.dom.client.TableCellElement; -import com.google.gwt.dom.client.TableElement; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.HasBlurHandlers; -import com.google.gwt.event.dom.client.HasFocusHandlers; -import com.google.gwt.event.dom.client.HasKeyDownHandlers; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Command; -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.ComplexPanel; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.user.client.ui.impl.FocusImpl; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.ComponentState; -import com.vaadin.terminal.gwt.client.ConnectorMap; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.Focusable; -import com.vaadin.terminal.gwt.client.TooltipInfo; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VCaption; -import com.vaadin.terminal.gwt.client.ui.label.VLabel; - -public class VTabsheet extends VTabsheetBase implements Focusable, - FocusHandler, BlurHandler, KeyDownHandler { - - private static class VCloseEvent { - private Tab tab; - - VCloseEvent(Tab tab) { - this.tab = tab; - } - - public Tab getTab() { - return tab; - } - - } - - private interface VCloseHandler { - public void onClose(VCloseEvent event); - } - - /** - * Representation of a single "tab" shown in the TabBar - * - */ - private static class Tab extends SimplePanel implements HasFocusHandlers, - HasBlurHandlers, HasKeyDownHandlers { - private static final String TD_CLASSNAME = CLASSNAME + "-tabitemcell"; - private static final String TD_FIRST_CLASSNAME = TD_CLASSNAME - + "-first"; - private static final String TD_SELECTED_CLASSNAME = TD_CLASSNAME - + "-selected"; - private static final String TD_SELECTED_FIRST_CLASSNAME = TD_SELECTED_CLASSNAME - + "-first"; - private static final String TD_DISABLED_CLASSNAME = TD_CLASSNAME - + "-disabled"; - - private static final String DIV_CLASSNAME = CLASSNAME + "-tabitem"; - private static final String DIV_SELECTED_CLASSNAME = DIV_CLASSNAME - + "-selected"; - - private TabCaption tabCaption; - Element td = getElement(); - private VCloseHandler closeHandler; - - private boolean enabledOnServer = true; - private Element div; - private TabBar tabBar; - private boolean hiddenOnServer = false; - - private String styleName; - - private Tab(TabBar tabBar) { - super(DOM.createTD()); - this.tabBar = tabBar; - setStyleName(td, TD_CLASSNAME); - - div = DOM.createDiv(); - focusImpl.setTabIndex(td, -1); - setStyleName(div, DIV_CLASSNAME); - - DOM.appendChild(td, div); - - tabCaption = new TabCaption(this, getTabsheet() - .getApplicationConnection()); - add(tabCaption); - - addFocusHandler(getTabsheet()); - addBlurHandler(getTabsheet()); - addKeyDownHandler(getTabsheet()); - } - - public boolean isHiddenOnServer() { - return hiddenOnServer; - } - - public void setHiddenOnServer(boolean hiddenOnServer) { - this.hiddenOnServer = hiddenOnServer; - } - - @Override - protected Element getContainerElement() { - // Attach caption element to div, not td - return div; - } - - public boolean isEnabledOnServer() { - return enabledOnServer; - } - - public void setEnabledOnServer(boolean enabled) { - enabledOnServer = enabled; - setStyleName(td, TD_DISABLED_CLASSNAME, !enabled); - if (!enabled) { - focusImpl.setTabIndex(td, -1); - } - } - - public void addClickHandler(ClickHandler handler) { - tabCaption.addClickHandler(handler); - } - - public void setCloseHandler(VCloseHandler closeHandler) { - this.closeHandler = closeHandler; - } - - /** - * Toggles the style names for the Tab - * - * @param selected - * true if the Tab is selected - * @param first - * true if the Tab is the first visible Tab - */ - public void setStyleNames(boolean selected, boolean first) { - setStyleName(td, TD_FIRST_CLASSNAME, first); - setStyleName(td, TD_SELECTED_CLASSNAME, selected); - setStyleName(td, TD_SELECTED_FIRST_CLASSNAME, selected && first); - setStyleName(div, DIV_SELECTED_CLASSNAME, selected); - } - - public void setTabulatorIndex(int tabIndex) { - focusImpl.setTabIndex(td, tabIndex); - } - - public boolean isClosable() { - return tabCaption.isClosable(); - } - - public void onClose() { - closeHandler.onClose(new VCloseEvent(this)); - } - - public VTabsheet getTabsheet() { - return tabBar.getTabsheet(); - } - - public void updateFromUIDL(UIDL tabUidl) { - tabCaption.updateCaption(tabUidl); - - // Apply the styleName set for the tab - String newStyleName = tabUidl.getStringAttribute(TAB_STYLE_NAME); - // Find the nth td element - if (newStyleName != null && newStyleName.length() != 0) { - if (!newStyleName.equals(styleName)) { - // If we have a new style name - if (styleName != null && styleName.length() != 0) { - // Remove old style name if present - td.removeClassName(TD_CLASSNAME + "-" + styleName); - } - // Set new style name - td.addClassName(TD_CLASSNAME + "-" + newStyleName); - styleName = newStyleName; - } - } else if (styleName != null) { - // Remove the set stylename if no stylename is present in the - // uidl - td.removeClassName(TD_CLASSNAME + "-" + styleName); - styleName = null; - } - } - - public void recalculateCaptionWidth() { - tabCaption.setWidth(tabCaption.getRequiredWidth() + "px"); - } - - public HandlerRegistration addFocusHandler(FocusHandler handler) { - return addDomHandler(handler, FocusEvent.getType()); - } - - public HandlerRegistration addBlurHandler(BlurHandler handler) { - return addDomHandler(handler, BlurEvent.getType()); - } - - public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) { - return addDomHandler(handler, KeyDownEvent.getType()); - } - - public void focus() { - focusImpl.focus(td); - } - - public void blur() { - focusImpl.blur(td); - } - } - - private static class TabCaption extends VCaption { - - private boolean closable = false; - private Element closeButton; - private Tab tab; - private ApplicationConnection client; - - TabCaption(Tab tab, ApplicationConnection client) { - super(client); - this.client = client; - this.tab = tab; - } - - public boolean updateCaption(UIDL uidl) { - if (uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DESCRIPTION)) { - TooltipInfo tooltipInfo = new TooltipInfo(); - tooltipInfo - .setTitle(uidl - .getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DESCRIPTION)); - tooltipInfo - .setErrorMessage(uidl - .getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_ERROR_MESSAGE)); - client.registerTooltip(getTabsheet(), getElement(), tooltipInfo); - } else { - client.registerTooltip(getTabsheet(), getElement(), null); - } - - // TODO need to call this instead of super because the caption does - // not have an owner - boolean ret = updateCaptionWithoutOwner( - uidl.getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_CAPTION), - uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DISABLED), - uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DESCRIPTION), - uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_ERROR_MESSAGE), - uidl.getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_ICON)); - - setClosable(uidl.hasAttribute("closable")); - - return ret; - } - - private VTabsheet getTabsheet() { - return tab.getTabsheet(); - } - - @Override - public void onBrowserEvent(Event event) { - if (closable && event.getTypeInt() == Event.ONCLICK - && event.getEventTarget().cast() == closeButton) { - tab.onClose(); - event.stopPropagation(); - event.preventDefault(); - } - - super.onBrowserEvent(event); - - if (event.getTypeInt() == Event.ONLOAD) { - getTabsheet().tabSizeMightHaveChanged(getTab()); - } - client.handleTooltipEvent(event, getTabsheet(), getElement()); - } - - public Tab getTab() { - return tab; - } - - public void setClosable(boolean closable) { - this.closable = closable; - if (closable && closeButton == null) { - closeButton = DOM.createSpan(); - closeButton.setInnerHTML("×"); - closeButton - .setClassName(VTabsheet.CLASSNAME + "-caption-close"); - getElement().insertBefore(closeButton, - getElement().getLastChild()); - } else if (!closable && closeButton != null) { - getElement().removeChild(closeButton); - closeButton = null; - } - if (closable) { - addStyleDependentName("closable"); - } else { - removeStyleDependentName("closable"); - } - } - - public boolean isClosable() { - return closable; - } - - @Override - public int getRequiredWidth() { - int width = super.getRequiredWidth(); - if (closeButton != null) { - width += Util.getRequiredWidth(closeButton); - } - return width; - } - } - - static class TabBar extends ComplexPanel implements ClickHandler, - VCloseHandler { - - private final Element tr = DOM.createTR(); - - private final Element spacerTd = DOM.createTD(); - - private Tab selected; - - private VTabsheet tabsheet; - - TabBar(VTabsheet tabsheet) { - this.tabsheet = tabsheet; - - Element el = DOM.createTable(); - Element tbody = DOM.createTBody(); - DOM.appendChild(el, tbody); - DOM.appendChild(tbody, tr); - setStyleName(spacerTd, CLASSNAME + "-spacertd"); - DOM.appendChild(tr, spacerTd); - DOM.appendChild(spacerTd, DOM.createDiv()); - setElement(el); - } - - public void onClose(VCloseEvent event) { - Tab tab = event.getTab(); - if (!tab.isEnabledOnServer()) { - return; - } - int tabIndex = getWidgetIndex(tab); - getTabsheet().sendTabClosedEvent(tabIndex); - } - - protected Element getContainerElement() { - return tr; - } - - public int getTabCount() { - return getWidgetCount(); - } - - public Tab addTab() { - Tab t = new Tab(this); - int tabIndex = getTabCount(); - - // Logical attach - insert(t, tr, tabIndex, true); - - if (tabIndex == 0) { - // Set the "first" style - t.setStyleNames(false, true); - } - - t.addClickHandler(this); - t.setCloseHandler(this); - - return t; - } - - public void onClick(ClickEvent event) { - Widget caption = (Widget) event.getSource(); - int index = getWidgetIndex(caption.getParent()); - // IE needs explicit focus() - if (BrowserInfo.get().isIE()) { - getTabsheet().focus(); - } - getTabsheet().onTabSelected(index); - } - - public VTabsheet getTabsheet() { - return tabsheet; - } - - public Tab getTab(int index) { - if (index < 0 || index >= getTabCount()) { - return null; - } - return (Tab) super.getWidget(index); - } - - public void selectTab(int index) { - final Tab newSelected = getTab(index); - final Tab oldSelected = selected; - - newSelected.setStyleNames(true, isFirstVisibleTab(index)); - newSelected.setTabulatorIndex(getTabsheet().tabulatorIndex); - - if (oldSelected != null && oldSelected != newSelected) { - oldSelected.setStyleNames(false, - isFirstVisibleTab(getWidgetIndex(oldSelected))); - oldSelected.setTabulatorIndex(-1); - } - - // Update the field holding the currently selected tab - selected = newSelected; - - // The selected tab might need more (or less) space - newSelected.recalculateCaptionWidth(); - getTab(tabsheet.activeTabIndex).recalculateCaptionWidth(); - } - - public void removeTab(int i) { - Tab tab = getTab(i); - if (tab == null) { - return; - } - - remove(tab); - - /* - * If this widget was selected we need to unmark it as the last - * selected - */ - if (tab == selected) { - selected = null; - } - - // FIXME: Shouldn't something be selected instead? - } - - private boolean isFirstVisibleTab(int index) { - return getFirstVisibleTab() == index; - } - - /** - * Returns the index of the first visible tab - * - * @return - */ - private int getFirstVisibleTab() { - return getNextVisibleTab(-1); - } - - /** - * Find the next visible tab. Returns -1 if none is found. - * - * @param i - * @return - */ - private int getNextVisibleTab(int i) { - int tabs = getTabCount(); - do { - i++; - } while (i < tabs && getTab(i).isHiddenOnServer()); - - if (i == tabs) { - return -1; - } else { - return i; - } - } - - /** - * Find the previous visible tab. Returns -1 if none is found. - * - * @param i - * @return - */ - private int getPreviousVisibleTab(int i) { - do { - i--; - } while (i >= 0 && getTab(i).isHiddenOnServer()); - - return i; - - } - - public int scrollLeft(int currentFirstVisible) { - int prevVisible = getPreviousVisibleTab(currentFirstVisible); - if (prevVisible == -1) { - return -1; - } - - Tab newFirst = getTab(prevVisible); - newFirst.setVisible(true); - newFirst.recalculateCaptionWidth(); - - return prevVisible; - } - - public int scrollRight(int currentFirstVisible) { - int nextVisible = getNextVisibleTab(currentFirstVisible); - if (nextVisible == -1) { - return -1; - } - Tab currentFirst = getTab(currentFirstVisible); - currentFirst.setVisible(false); - currentFirst.recalculateCaptionWidth(); - return nextVisible; - } - } - - public static final String CLASSNAME = "v-tabsheet"; - - public static final String TABS_CLASSNAME = "v-tabsheet-tabcontainer"; - public static final String SCROLLER_CLASSNAME = "v-tabsheet-scroller"; - - // Can't use "style" as it's already in use - public static final String TAB_STYLE_NAME = "tabstyle"; - - final Element tabs; // tabbar and 'scroller' container - Tab focusedTab; - /** - * The tabindex property (position in the browser's focus cycle.) Named like - * this to avoid confusion with activeTabIndex. - */ - int tabulatorIndex = 0; - - private static final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel(); - - private final Element scroller; // tab-scroller element - private final Element scrollerNext; // tab-scroller next button element - private final Element scrollerPrev; // tab-scroller prev button element - - /** - * The index of the first visible tab (when scrolled) - */ - private int scrollerIndex = 0; - - final TabBar tb = new TabBar(this); - final VTabsheetPanel tp = new VTabsheetPanel(); - final Element contentNode; - - private final Element deco; - - boolean waitingForResponse; - - private String currentStyle; - - /** - * @return Whether the tab could be selected or not. - */ - private boolean onTabSelected(final int tabIndex) { - Tab tab = tb.getTab(tabIndex); - if (client == null || disabled || waitingForResponse) { - return false; - } - if (!tab.isEnabledOnServer() || tab.isHiddenOnServer()) { - return false; - } - if (activeTabIndex != tabIndex) { - tb.selectTab(tabIndex); - - // If this TabSheet already has focus, set the new selected tab - // as focused. - if (focusedTab != null) { - focusedTab = tab; - } - - addStyleDependentName("loading"); - // Hide the current contents so a loading indicator can be shown - // instead - Widget currentlyDisplayedWidget = tp.getWidget(tp - .getVisibleWidget()); - currentlyDisplayedWidget.getElement().getParentElement().getStyle() - .setVisibility(Visibility.HIDDEN); - client.updateVariable(id, "selected", tabKeys.get(tabIndex) - .toString(), true); - waitingForResponse = true; - } - // Note that we return true when tabIndex == activeTabIndex; the active - // tab could be selected, it's just a no-op. - return true; - } - - public ApplicationConnection getApplicationConnection() { - return client; - } - - public void tabSizeMightHaveChanged(Tab tab) { - // icon onloads may change total width of tabsheet - if (isDynamicWidth()) { - updateDynamicWidth(); - } - updateTabScroller(); - - } - - void sendTabClosedEvent(int tabIndex) { - client.updateVariable(id, "close", tabKeys.get(tabIndex), true); - } - - boolean isDynamicWidth() { - ComponentConnector paintable = ConnectorMap.get(client).getConnector( - this); - return paintable.isUndefinedWidth(); - } - - boolean isDynamicHeight() { - ComponentConnector paintable = ConnectorMap.get(client).getConnector( - this); - return paintable.isUndefinedHeight(); - } - - public VTabsheet() { - super(CLASSNAME); - - addHandler(this, FocusEvent.getType()); - addHandler(this, BlurEvent.getType()); - - // Tab scrolling - DOM.setStyleAttribute(getElement(), "overflow", "hidden"); - tabs = DOM.createDiv(); - DOM.setElementProperty(tabs, "className", TABS_CLASSNAME); - scroller = DOM.createDiv(); - - DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME); - scrollerPrev = DOM.createButton(); - DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME - + "Prev"); - DOM.sinkEvents(scrollerPrev, Event.ONCLICK); - scrollerNext = DOM.createButton(); - DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME - + "Next"); - DOM.sinkEvents(scrollerNext, Event.ONCLICK); - DOM.appendChild(getElement(), tabs); - - // Tabs - tp.setStyleName(CLASSNAME + "-tabsheetpanel"); - contentNode = DOM.createDiv(); - - deco = DOM.createDiv(); - - addStyleDependentName("loading"); // Indicate initial progress - tb.setStyleName(CLASSNAME + "-tabs"); - DOM.setElementProperty(contentNode, "className", CLASSNAME + "-content"); - DOM.setElementProperty(deco, "className", CLASSNAME + "-deco"); - - add(tb, tabs); - DOM.appendChild(scroller, scrollerPrev); - DOM.appendChild(scroller, scrollerNext); - - DOM.appendChild(getElement(), contentNode); - add(tp, contentNode); - DOM.appendChild(getElement(), deco); - - DOM.appendChild(tabs, scroller); - - // TODO Use for Safari only. Fix annoying 1px first cell in TabBar. - // DOM.setStyleAttribute(DOM.getFirstChild(DOM.getFirstChild(DOM - // .getFirstChild(tb.getElement()))), "display", "none"); - - } - - @Override - public void onBrowserEvent(Event event) { - - // Tab scrolling - if (isScrolledTabs() && DOM.eventGetTarget(event) == scrollerPrev) { - int newFirstIndex = tb.scrollLeft(scrollerIndex); - if (newFirstIndex != -1) { - scrollerIndex = newFirstIndex; - updateTabScroller(); - } - } else if (isClippedTabs() && DOM.eventGetTarget(event) == scrollerNext) { - int newFirstIndex = tb.scrollRight(scrollerIndex); - - if (newFirstIndex != -1) { - scrollerIndex = newFirstIndex; - updateTabScroller(); - } - } else { - super.onBrowserEvent(event); - } - } - - /** - * Checks if the tab with the selected index has been scrolled out of the - * view (on the left side). - * - * @param index - * @return - */ - private boolean scrolledOutOfView(int index) { - return scrollerIndex > index; - } - - void handleStyleNames(UIDL uidl, ComponentState state) { - // Add proper stylenames for all elements (easier to prevent unwanted - // style inheritance) - if (state.hasStyles()) { - final List styles = state.getStyles(); - if (!currentStyle.equals(styles.toString())) { - currentStyle = styles.toString(); - final String tabsBaseClass = TABS_CLASSNAME; - String tabsClass = tabsBaseClass; - final String contentBaseClass = CLASSNAME + "-content"; - String contentClass = contentBaseClass; - final String decoBaseClass = CLASSNAME + "-deco"; - String decoClass = decoBaseClass; - for (String style : styles) { - tb.addStyleDependentName(style); - tabsClass += " " + tabsBaseClass + "-" + style; - contentClass += " " + contentBaseClass + "-" + style; - decoClass += " " + decoBaseClass + "-" + style; - } - DOM.setElementProperty(tabs, "className", tabsClass); - DOM.setElementProperty(contentNode, "className", contentClass); - DOM.setElementProperty(deco, "className", decoClass); - borderW = -1; - } - } else { - tb.setStyleName(CLASSNAME + "-tabs"); - DOM.setElementProperty(tabs, "className", TABS_CLASSNAME); - DOM.setElementProperty(contentNode, "className", CLASSNAME - + "-content"); - DOM.setElementProperty(deco, "className", CLASSNAME + "-deco"); - } - - if (uidl.hasAttribute("hidetabs")) { - tb.setVisible(false); - addStyleName(CLASSNAME + "-hidetabs"); - } else { - tb.setVisible(true); - removeStyleName(CLASSNAME + "-hidetabs"); - } - } - - void updateDynamicWidth() { - // Find width consumed by tabs - TableCellElement spacerCell = ((TableElement) tb.getElement().cast()) - .getRows().getItem(0).getCells().getItem(tb.getTabCount()); - - int spacerWidth = spacerCell.getOffsetWidth(); - DivElement div = (DivElement) spacerCell.getFirstChildElement(); - - int spacerMinWidth = spacerCell.getOffsetWidth() - div.getOffsetWidth(); - - int tabsWidth = tb.getOffsetWidth() - spacerWidth + spacerMinWidth; - - // Find content width - Style style = tp.getElement().getStyle(); - String overflow = style.getProperty("overflow"); - style.setProperty("overflow", "hidden"); - style.setPropertyPx("width", tabsWidth); - - boolean hasTabs = tp.getWidgetCount() > 0; - - Style wrapperstyle = null; - if (hasTabs) { - wrapperstyle = tp.getWidget(tp.getVisibleWidget()).getElement() - .getParentElement().getStyle(); - wrapperstyle.setPropertyPx("width", tabsWidth); - } - // Get content width from actual widget - - int contentWidth = 0; - if (hasTabs) { - contentWidth = tp.getWidget(tp.getVisibleWidget()).getOffsetWidth(); - } - style.setProperty("overflow", overflow); - - // Set widths to max(tabs,content) - if (tabsWidth < contentWidth) { - tabsWidth = contentWidth; - } - - int outerWidth = tabsWidth + getContentAreaBorderWidth(); - - tabs.getStyle().setPropertyPx("width", outerWidth); - style.setPropertyPx("width", tabsWidth); - if (hasTabs) { - wrapperstyle.setPropertyPx("width", tabsWidth); - } - - contentNode.getStyle().setPropertyPx("width", tabsWidth); - super.setWidth(outerWidth + "px"); - updateOpenTabSize(); - } - - @Override - protected void renderTab(final UIDL tabUidl, int index, boolean selected, - boolean hidden) { - Tab tab = tb.getTab(index); - if (tab == null) { - tab = tb.addTab(); - } - tab.updateFromUIDL(tabUidl); - tab.setEnabledOnServer((!disabledTabKeys.contains(tabKeys.get(index)))); - tab.setHiddenOnServer(hidden); - - if (scrolledOutOfView(index)) { - // Should not set tabs visible if they are scrolled out of view - hidden = true; - } - // Set the current visibility of the tab (in the browser) - tab.setVisible(!hidden); - - /* - * Force the width of the caption container so the content will not wrap - * and tabs won't be too narrow in certain browsers - */ - tab.recalculateCaptionWidth(); - - UIDL tabContentUIDL = null; - ComponentConnector tabContentPaintable = null; - Widget tabContentWidget = null; - if (tabUidl.getChildCount() > 0) { - tabContentUIDL = tabUidl.getChildUIDL(0); - tabContentPaintable = client.getPaintable(tabContentUIDL); - tabContentWidget = tabContentPaintable.getWidget(); - } - - if (tabContentPaintable != null) { - /* This is a tab with content information */ - - int oldIndex = tp.getWidgetIndex(tabContentWidget); - if (oldIndex != -1 && oldIndex != index) { - /* - * The tab has previously been rendered in another position so - * we must move the cached content to correct position - */ - tp.insert(tabContentWidget, index); - } - } else { - /* A tab whose content has not yet been loaded */ - - /* - * Make sure there is a corresponding empty tab in tp. The same - * operation as the moving above but for not-loaded tabs. - */ - if (index < tp.getWidgetCount()) { - Widget oldWidget = tp.getWidget(index); - if (!(oldWidget instanceof PlaceHolder)) { - tp.insert(new PlaceHolder(), index); - } - } - - } - - if (selected) { - renderContent(tabContentUIDL); - tb.selectTab(index); - } else { - if (tabContentUIDL != null) { - // updating a drawn child on hidden tab - if (tp.getWidgetIndex(tabContentWidget) < 0) { - tp.insert(tabContentWidget, index); - } - } else if (tp.getWidgetCount() <= index) { - tp.add(new PlaceHolder()); - } - } - } - - public class PlaceHolder extends VLabel { - public PlaceHolder() { - super(""); - } - } - - @Override - protected void selectTab(int index, final UIDL contentUidl) { - if (index != activeTabIndex) { - activeTabIndex = index; - tb.selectTab(activeTabIndex); - } - renderContent(contentUidl); - } - - private void renderContent(final UIDL contentUIDL) { - final ComponentConnector content = client.getPaintable(contentUIDL); - Widget newWidget = content.getWidget(); - if (tp.getWidgetCount() > activeTabIndex) { - Widget old = tp.getWidget(activeTabIndex); - if (old != newWidget) { - tp.remove(activeTabIndex); - ConnectorMap paintableMap = ConnectorMap.get(client); - if (paintableMap.isConnector(old)) { - paintableMap.unregisterConnector(paintableMap - .getConnector(old)); - } - tp.insert(content.getWidget(), activeTabIndex); - } - } else { - tp.add(content.getWidget()); - } - - tp.showWidget(activeTabIndex); - - VTabsheet.this.iLayout(); - /* - * The size of a cached, relative sized component must be updated to - * report correct size to updateOpenTabSize(). - */ - if (contentUIDL.getBooleanAttribute("cached")) { - client.handleComponentRelativeSize(content.getWidget()); - } - updateOpenTabSize(); - VTabsheet.this.removeStyleDependentName("loading"); - } - - void updateContentNodeHeight() { - if (!isDynamicHeight()) { - int contentHeight = getOffsetHeight(); - contentHeight -= DOM.getElementPropertyInt(deco, "offsetHeight"); - contentHeight -= tb.getOffsetHeight(); - if (contentHeight < 0) { - contentHeight = 0; - } - - // Set proper values for content element - DOM.setStyleAttribute(contentNode, "height", contentHeight + "px"); - } else { - DOM.setStyleAttribute(contentNode, "height", ""); - } - } - - public void iLayout() { - updateTabScroller(); - tp.runWebkitOverflowAutoFix(); - } - - /** - * Sets the size of the visible tab (component). As the tab is set to - * position: absolute (to work around a firefox flickering bug) we must keep - * this up-to-date by hand. - */ - void updateOpenTabSize() { - /* - * The overflow=auto element must have a height specified, otherwise it - * will be just as high as the contents and no scrollbars will appear - */ - int height = -1; - int width = -1; - int minWidth = 0; - - if (!isDynamicHeight()) { - height = contentNode.getOffsetHeight(); - } - if (!isDynamicWidth()) { - width = contentNode.getOffsetWidth() - getContentAreaBorderWidth(); - } else { - /* - * If the tabbar is wider than the content we need to use the tabbar - * width as minimum width so scrollbars get placed correctly (at the - * right edge). - */ - minWidth = tb.getOffsetWidth() - getContentAreaBorderWidth(); - } - tp.fixVisibleTabSize(width, height, minWidth); - - } - - /** - * Layouts the tab-scroller elements, and applies styles. - */ - private void updateTabScroller() { - if (!isDynamicWidth()) { - ComponentConnector paintable = ConnectorMap.get(client) - .getConnector(this); - DOM.setStyleAttribute(tabs, "width", paintable.getState() - .getWidth()); - } - - // Make sure scrollerIndex is valid - if (scrollerIndex < 0 || scrollerIndex > tb.getTabCount()) { - scrollerIndex = tb.getFirstVisibleTab(); - } else if (tb.getTabCount() > 0 - && tb.getTab(scrollerIndex).isHiddenOnServer()) { - scrollerIndex = tb.getNextVisibleTab(scrollerIndex); - } - - boolean scrolled = isScrolledTabs(); - boolean clipped = isClippedTabs(); - if (tb.getTabCount() > 0 && tb.isVisible() && (scrolled || clipped)) { - DOM.setStyleAttribute(scroller, "display", ""); - DOM.setElementProperty(scrollerPrev, "className", - SCROLLER_CLASSNAME + (scrolled ? "Prev" : "Prev-disabled")); - DOM.setElementProperty(scrollerNext, "className", - SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled")); - } else { - DOM.setStyleAttribute(scroller, "display", "none"); - } - - if (BrowserInfo.get().isSafari()) { - // fix tab height for safari, bugs sometimes if tabs contain icons - String property = tabs.getStyle().getProperty("height"); - if (property == null || property.equals("")) { - tabs.getStyle().setPropertyPx("height", tb.getOffsetHeight()); - } - /* - * another hack for webkits. tabscroller sometimes drops without - * "shaking it" reproducable in - * com.vaadin.tests.components.tabsheet.TabSheetIcons - */ - final Style style = scroller.getStyle(); - style.setProperty("whiteSpace", "normal"); - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - style.setProperty("whiteSpace", ""); - } - }); - } - - } - - void showAllTabs() { - scrollerIndex = tb.getFirstVisibleTab(); - for (int i = 0; i < tb.getTabCount(); i++) { - Tab t = tb.getTab(i); - if (!t.isHiddenOnServer()) { - t.setVisible(true); - } - } - } - - private boolean isScrolledTabs() { - return scrollerIndex > tb.getFirstVisibleTab(); - } - - private boolean isClippedTabs() { - return (tb.getOffsetWidth() - DOM.getElementPropertyInt((Element) tb - .getContainerElement().getLastChild().cast(), "offsetWidth")) > getOffsetWidth() - - (isScrolledTabs() ? scroller.getOffsetWidth() : 0); - } - - private boolean isClipped(Tab tab) { - return tab.getAbsoluteLeft() + tab.getOffsetWidth() > getAbsoluteLeft() - + getOffsetWidth() - scroller.getOffsetWidth(); - } - - @Override - protected void clearPaintables() { - - int i = tb.getTabCount(); - while (i > 0) { - tb.removeTab(--i); - } - tp.clear(); - - } - - @Override - protected Iterator getWidgetIterator() { - return tp.iterator(); - } - - private int borderW = -1; - - int getContentAreaBorderWidth() { - if (borderW < 0) { - borderW = Util.measureHorizontalBorder(contentNode); - } - return borderW; - } - - @Override - protected int getTabCount() { - return tb.getTabCount(); - } - - @Override - protected ComponentConnector getTab(int index) { - if (tp.getWidgetCount() > index) { - Widget widget = tp.getWidget(index); - return ConnectorMap.get(client).getConnector(widget); - } - return null; - } - - @Override - protected void removeTab(int index) { - tb.removeTab(index); - /* - * This must be checked because renderTab automatically removes the - * active tab content when it changes - */ - if (tp.getWidgetCount() > index) { - tp.remove(index); - } - } - - public void onBlur(BlurEvent event) { - if (focusedTab != null && event.getSource() instanceof Tab) { - focusedTab = null; - if (client.hasEventListeners(this, EventId.BLUR)) { - client.updateVariable(id, EventId.BLUR, "", true); - } - } - } - - public void onFocus(FocusEvent event) { - if (focusedTab == null && event.getSource() instanceof Tab) { - focusedTab = (Tab) event.getSource(); - if (client.hasEventListeners(this, EventId.FOCUS)) { - client.updateVariable(id, EventId.FOCUS, "", true); - } - } - } - - public void focus() { - tb.getTab(activeTabIndex).focus(); - } - - public void blur() { - tb.getTab(activeTabIndex).blur(); - } - - public void onKeyDown(KeyDownEvent event) { - if (event.getSource() instanceof Tab) { - int keycode = event.getNativeEvent().getKeyCode(); - - if (keycode == getPreviousTabKey()) { - selectPreviousTab(); - } else if (keycode == getNextTabKey()) { - selectNextTab(); - } else if (keycode == getCloseTabKey()) { - Tab tab = tb.getTab(activeTabIndex); - if (tab.isClosable()) { - tab.onClose(); - } - } - } - } - - /** - * @return The key code of the keyboard shortcut that selects the previous - * tab in a focused tabsheet. - */ - protected int getPreviousTabKey() { - return KeyCodes.KEY_LEFT; - } - - /** - * @return The key code of the keyboard shortcut that selects the next tab - * in a focused tabsheet. - */ - protected int getNextTabKey() { - return KeyCodes.KEY_RIGHT; - } - - /** - * @return The key code of the keyboard shortcut that closes the currently - * selected tab in a focused tabsheet. - */ - protected int getCloseTabKey() { - return KeyCodes.KEY_DELETE; - } - - private void selectPreviousTab() { - int newTabIndex = activeTabIndex; - // Find the previous visible and enabled tab if any. - do { - newTabIndex--; - } while (newTabIndex >= 0 && !onTabSelected(newTabIndex)); - - if (newTabIndex >= 0) { - activeTabIndex = newTabIndex; - if (isScrolledTabs()) { - // Scroll until the new active tab is visible - int newScrollerIndex = scrollerIndex; - while (tb.getTab(activeTabIndex).getAbsoluteLeft() < getAbsoluteLeft() - && newScrollerIndex != -1) { - newScrollerIndex = tb.scrollLeft(newScrollerIndex); - } - scrollerIndex = newScrollerIndex; - updateTabScroller(); - } - } - } - - private void selectNextTab() { - int newTabIndex = activeTabIndex; - // Find the next visible and enabled tab if any. - do { - newTabIndex++; - } while (newTabIndex < getTabCount() && !onTabSelected(newTabIndex)); - - if (newTabIndex < getTabCount()) { - activeTabIndex = newTabIndex; - if (isClippedTabs()) { - // Scroll until the new active tab is completely visible - int newScrollerIndex = scrollerIndex; - while (isClipped(tb.getTab(activeTabIndex)) - && newScrollerIndex != -1) { - newScrollerIndex = tb.scrollRight(newScrollerIndex); - } - scrollerIndex = newScrollerIndex; - updateTabScroller(); - } - } - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTabsheetBase.java b/src/com/vaadin/terminal/gwt/client/ui/VTabsheetBase.java deleted file mode 100644 index a499a35460..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VTabsheetBase.java +++ /dev/null @@ -1,75 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.ui.ComplexPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.UIDL; - -abstract class VTabsheetBase extends ComplexPanel { - - String id; - ApplicationConnection client; - - protected final ArrayList tabKeys = new ArrayList(); - protected int activeTabIndex = 0; - protected boolean disabled; - protected boolean readonly; - protected Set disabledTabKeys = new HashSet(); - - public VTabsheetBase(String classname) { - setElement(DOM.createDiv()); - setStyleName(classname); - } - - /** - * @return a list of currently shown Widgets - */ - abstract protected Iterator getWidgetIterator(); - - /** - * Clears current tabs and contents - */ - abstract protected void clearPaintables(); - - /** - * Implement in extending classes. This method should render needed elements - * and set the visibility of the tab according to the 'selected' parameter. - */ - protected abstract void renderTab(final UIDL tabUidl, int index, - boolean selected, boolean hidden); - - /** - * Implement in extending classes. This method should render any previously - * non-cached content and set the activeTabIndex property to the specified - * index. - */ - protected abstract void selectTab(int index, final UIDL contentUidl); - - /** - * Implement in extending classes. This method should return the number of - * tabs currently rendered. - */ - protected abstract int getTabCount(); - - /** - * Implement in extending classes. This method should return the Paintable - * corresponding to the given index. - */ - protected abstract ComponentConnector getTab(int index); - - /** - * Implement in extending classes. This method should remove the rendered - * tab with the specified index. - */ - protected abstract void removeTab(int index); -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTabsheetPanel.java b/src/com/vaadin/terminal/gwt/client/ui/VTabsheetPanel.java deleted file mode 100644 index 68270da85e..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VTabsheetPanel.java +++ /dev/null @@ -1,217 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.dom.client.Node; -import com.google.gwt.dom.client.NodeList; -import com.google.gwt.event.dom.client.TouchStartEvent; -import com.google.gwt.event.dom.client.TouchStartHandler; -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.ComplexPanel; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.Util; - -/** - * A panel that displays all of its child widgets in a 'deck', where only one - * can be visible at a time. It is used by - * {@link com.vaadin.terminal.gwt.client.ui.VTabsheet}. - * - * This class has the same basic functionality as the GWT DeckPanel - * {@link com.google.gwt.user.client.ui.DeckPanel}, with the exception that it - * doesn't manipulate the child widgets' width and height attributes. - */ -public class VTabsheetPanel extends ComplexPanel { - - private Widget visibleWidget; - private TouchScrollDelegate touchScrollDelegate; - - /** - * Creates an empty tabsheet panel. - */ - public VTabsheetPanel() { - setElement(DOM.createDiv()); - sinkEvents(Event.TOUCHEVENTS); - addDomHandler(new TouchStartHandler() { - public void onTouchStart(TouchStartEvent event) { - /* - * All container elements needs to be scrollable by one finger. - * Update the scrollable element list of touch delegate on each - * touch start. - */ - NodeList childNodes = getElement().getChildNodes(); - Element[] elements = new Element[childNodes.getLength()]; - for (int i = 0; i < elements.length; i++) { - elements[i] = (Element) childNodes.getItem(i); - } - getTouchScrollDelegate().setElements(elements); - getTouchScrollDelegate().onTouchStart(event); - } - }, TouchStartEvent.getType()); - } - - protected TouchScrollDelegate getTouchScrollDelegate() { - if (touchScrollDelegate == null) { - touchScrollDelegate = new TouchScrollDelegate(); - } - return touchScrollDelegate; - - } - - /** - * Adds the specified widget to the deck. - * - * @param w - * the widget to be added - */ - @Override - public void add(Widget w) { - Element el = createContainerElement(); - DOM.appendChild(getElement(), el); - super.add(w, el); - } - - private Element createContainerElement() { - Element el = DOM.createDiv(); - DOM.setStyleAttribute(el, "position", "absolute"); - DOM.setStyleAttribute(el, "overflow", "auto"); - hide(el); - return el; - } - - /** - * Gets the index of the currently-visible widget. - * - * @return the visible widget's index - */ - public int getVisibleWidget() { - return getWidgetIndex(visibleWidget); - } - - /** - * Inserts a widget before the specified index. - * - * @param w - * the widget to be inserted - * @param beforeIndex - * the index before which it will be inserted - * @throws IndexOutOfBoundsException - * if beforeIndex is out of range - */ - public void insert(Widget w, int beforeIndex) { - Element el = createContainerElement(); - DOM.insertChild(getElement(), el, beforeIndex); - super.insert(w, el, beforeIndex, false); - } - - @Override - public boolean remove(Widget w) { - Element child = w.getElement(); - Element parent = null; - if (child != null) { - parent = DOM.getParent(child); - } - final boolean removed = super.remove(w); - if (removed) { - if (visibleWidget == w) { - visibleWidget = null; - } - if (parent != null) { - DOM.removeChild(getElement(), parent); - } - } - return removed; - } - - /** - * Shows the widget at the specified index. This causes the currently- - * visible widget to be hidden. - * - * @param index - * the index of the widget to be shown - */ - public void showWidget(int index) { - checkIndexBoundsForAccess(index); - Widget newVisible = getWidget(index); - if (visibleWidget != newVisible) { - if (visibleWidget != null) { - hide(DOM.getParent(visibleWidget.getElement())); - } - visibleWidget = newVisible; - } - // Always ensure the selected tab is visible. If server prevents a tab - // change we might end up here with visibleWidget == newVisible but its - // parent is still hidden. - unHide(DOM.getParent(visibleWidget.getElement())); - } - - private void hide(Element e) { - DOM.setStyleAttribute(e, "visibility", "hidden"); - DOM.setStyleAttribute(e, "top", "-100000px"); - DOM.setStyleAttribute(e, "left", "-100000px"); - } - - private void unHide(Element e) { - DOM.setStyleAttribute(e, "top", "0px"); - DOM.setStyleAttribute(e, "left", "0px"); - DOM.setStyleAttribute(e, "visibility", ""); - } - - public void fixVisibleTabSize(int width, int height, int minWidth) { - if (visibleWidget == null) { - return; - } - - boolean dynamicHeight = false; - - if (height < 0) { - height = visibleWidget.getOffsetHeight(); - dynamicHeight = true; - } - if (width < 0) { - width = visibleWidget.getOffsetWidth(); - } - if (width < minWidth) { - width = minWidth; - } - - Element wrapperDiv = (Element) visibleWidget.getElement() - .getParentElement(); - - // width first - getElement().getStyle().setPropertyPx("width", width); - wrapperDiv.getStyle().setPropertyPx("width", width); - - if (dynamicHeight) { - // height of widget might have changed due wrapping - height = visibleWidget.getOffsetHeight(); - } - // v-tabsheet-tabsheetpanel height - getElement().getStyle().setPropertyPx("height", height); - - // widget wrapper height - wrapperDiv.getStyle().setPropertyPx("height", height); - runWebkitOverflowAutoFix(); - } - - public void runWebkitOverflowAutoFix() { - if (visibleWidget != null) { - Util.runWebkitOverflowAutoFix(DOM.getParent(visibleWidget - .getElement())); - } - - } - - public void replaceComponent(Widget oldComponent, Widget newComponent) { - boolean isVisible = (visibleWidget == oldComponent); - int widgetIndex = getWidgetIndex(oldComponent); - remove(oldComponent); - insert(newComponent, widgetIndex); - if (isVisible) { - showWidget(widgetIndex); - } - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTextArea.java b/src/com/vaadin/terminal/gwt/client/ui/VTextArea.java deleted file mode 100644 index 2c8ed24693..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VTextArea.java +++ /dev/null @@ -1,63 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.Event; - -/** - * This class represents a multiline textfield (textarea). - * - * TODO consider replacing this with a RichTextArea based implementation. IE - * does not support CSS height for textareas in Strict mode :-( - * - * @author Vaadin Ltd. - * - */ -public class VTextArea extends VTextField { - public static final String CLASSNAME = "v-textarea"; - - public VTextArea() { - super(DOM.createTextArea()); - setStyleName(CLASSNAME); - } - - public void setRows(int rows) { - setRows(getElement(), rows); - } - - private native void setRows(Element e, int r) - /*-{ - try { - if(e.tagName.toLowerCase() == "textarea") - e.rows = r; - } catch (e) {} - }-*/; - - @Override - public void onBrowserEvent(Event event) { - if (getMaxLength() >= 0 && event.getTypeInt() == Event.ONKEYUP) { - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - if (getText().length() > getMaxLength()) { - setText(getText().substring(0, getMaxLength())); - } - } - }); - } - super.onBrowserEvent(event); - } - - @Override - public int getCursorPos() { - // This is needed so that TextBoxImplIE6 is used to return the correct - // position for old Internet Explorer versions where it has to be - // detected in a different way. - return getImpl().getTextAreaCursorPos(getElement()); - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTextField.java b/src/com/vaadin/terminal/gwt/client/ui/VTextField.java deleted file mode 100644 index 5936a96565..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VTextField.java +++ /dev/null @@ -1,441 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -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.Timer; -import com.google.gwt.user.client.ui.TextBoxBase; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VTooltip; - -/** - * This class represents a basic text input field with one row. - * - * @author Vaadin Ltd. - * - */ -public class VTextField extends TextBoxBase implements Field, ChangeHandler, - FocusHandler, BlurHandler, KeyDownHandler { - - public static final String VAR_CUR_TEXT = "curText"; - - public static final String ATTR_NO_VALUE_CHANGE_BETWEEN_PAINTS = "nvc"; - /** - * The input node CSS classname. - */ - public static final String CLASSNAME = "v-textfield"; - /** - * This CSS classname is added to the input node on hover. - */ - public static final String CLASSNAME_FOCUS = "focus"; - - protected String paintableId; - - protected ApplicationConnection client; - - protected String valueBeforeEdit = null; - - /** - * Set to false if a text change event has been sent since the last value - * change event. This means that {@link #valueBeforeEdit} should not be - * trusted when determining whether a text change even should be sent. - */ - private boolean valueBeforeEditIsSynced = true; - - protected boolean immediate = false; - private int maxLength = -1; - - private static final String CLASSNAME_PROMPT = "prompt"; - protected static final String ATTR_INPUTPROMPT = "prompt"; - public static final String ATTR_TEXTCHANGE_TIMEOUT = "iet"; - public static final String VAR_CURSOR = "c"; - public static final String ATTR_TEXTCHANGE_EVENTMODE = "iem"; - protected static final String TEXTCHANGE_MODE_EAGER = "EAGER"; - private static final String TEXTCHANGE_MODE_TIMEOUT = "TIMEOUT"; - - protected String inputPrompt = null; - private boolean prompting = false; - private int lastCursorPos = -1; - private boolean wordwrap = true; - - public VTextField() { - this(DOM.createInputText()); - } - - protected VTextField(Element node) { - super(node); - setStyleName(CLASSNAME); - addChangeHandler(this); - if (BrowserInfo.get().isIE()) { - // IE does not send change events when pressing enter in a text - // input so we handle it using a key listener instead - addKeyDownHandler(this); - } - addFocusHandler(this); - addBlurHandler(this); - sinkEvents(VTooltip.TOOLTIP_EVENTS); - } - - /* - * TODO When GWT adds ONCUT, add it there and remove workaround. See - * http://code.google.com/p/google-web-toolkit/issues/detail?id=4030 - * - * Also note that the cut/paste are not totally crossbrowsers compatible. - * E.g. in Opera mac works via context menu, but on via File->Paste/Cut. - * Opera might need the polling method for 100% working textchanceevents. - * Eager polling for a change is bit dum and heavy operation, so I guess we - * should first try to survive without. - */ - protected static final int TEXTCHANGE_EVENTS = Event.ONPASTE - | Event.KEYEVENTS | Event.ONMOUSEUP; - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (client != null) { - client.handleTooltipEvent(event, this); - } - - if (listenTextChangeEvents - && (event.getTypeInt() & TEXTCHANGE_EVENTS) == event - .getTypeInt()) { - deferTextChangeEvent(); - } - - } - - /* - * TODO optimize this so that only changes are sent + make the value change - * event just a flag that moves the current text to value - */ - private String lastTextChangeString = null; - - private String getLastCommunicatedString() { - return lastTextChangeString; - } - - private void communicateTextValueToServer() { - String text = getText(); - if (prompting) { - // Input prompt visible, text is actually "" - text = ""; - } - if (!text.equals(getLastCommunicatedString())) { - if (valueBeforeEditIsSynced && text.equals(valueBeforeEdit)) { - /* - * Value change for the current text has been enqueued since the - * last text change event was sent, but we can't know that it - * has been sent to the server. Ensure that all pending changes - * are sent now. Sending a value change without a text change - * will simulate a TextChangeEvent on the server. - */ - client.sendPendingVariableChanges(); - } else { - // Default case - just send an immediate text change message - client.updateVariable(paintableId, VAR_CUR_TEXT, text, true); - - // Shouldn't investigate valueBeforeEdit to avoid duplicate text - // change events as the states are not in sync any more - valueBeforeEditIsSynced = false; - } - lastTextChangeString = text; - } - } - - private Timer textChangeEventTrigger = new Timer() { - - @Override - public void run() { - if (isAttached()) { - updateCursorPosition(); - communicateTextValueToServer(); - scheduled = false; - } - } - }; - private boolean scheduled = false; - protected boolean listenTextChangeEvents; - protected String textChangeEventMode; - protected int textChangeEventTimeout; - - private void deferTextChangeEvent() { - if (textChangeEventMode.equals(TEXTCHANGE_MODE_TIMEOUT) && scheduled) { - return; - } else { - textChangeEventTrigger.cancel(); - } - textChangeEventTrigger.schedule(getTextChangeEventTimeout()); - scheduled = true; - } - - private int getTextChangeEventTimeout() { - return textChangeEventTimeout; - } - - @Override - public void setReadOnly(boolean readOnly) { - boolean wasReadOnly = isReadOnly(); - - if (readOnly) { - setTabIndex(-1); - } else if (wasReadOnly && !readOnly && getTabIndex() == -1) { - /* - * Need to manually set tab index to 0 since server will not send - * the tab index if it is 0. - */ - setTabIndex(0); - } - - super.setReadOnly(readOnly); - } - - protected void updateFieldContent(final String text) { - setPrompting(inputPrompt != null && focusedTextField != this - && (text.equals(""))); - - String fieldValue; - if (prompting) { - fieldValue = isReadOnly() ? "" : inputPrompt; - addStyleDependentName(CLASSNAME_PROMPT); - } else { - fieldValue = text; - removeStyleDependentName(CLASSNAME_PROMPT); - } - setText(fieldValue); - - lastTextChangeString = valueBeforeEdit = text; - valueBeforeEditIsSynced = true; - } - - protected void onCut() { - if (listenTextChangeEvents) { - deferTextChangeEvent(); - } - } - - protected native void attachCutEventListener(Element el) - /*-{ - var me = this; - el.oncut = function() { - me.@com.vaadin.terminal.gwt.client.ui.VTextField::onCut()(); - }; - }-*/; - - protected native void detachCutEventListener(Element el) - /*-{ - el.oncut = null; - }-*/; - - @Override - protected void onDetach() { - super.onDetach(); - detachCutEventListener(getElement()); - if (focusedTextField == this) { - focusedTextField = null; - } - } - - @Override - protected void onAttach() { - super.onAttach(); - if (listenTextChangeEvents) { - detachCutEventListener(getElement()); - } - } - - protected void setMaxLength(int newMaxLength) { - if (newMaxLength >= 0) { - maxLength = newMaxLength; - if (getElement().getTagName().toLowerCase().equals("textarea")) { - // NOP no maxlength property for textarea - } else { - getElement().setPropertyInt("maxLength", maxLength); - } - } else if (maxLength != -1) { - if (getElement().getTagName().toLowerCase().equals("textarea")) { - // NOP no maxlength property for textarea - } else { - getElement().removeAttribute("maxLength"); - } - maxLength = -1; - } - - } - - protected int getMaxLength() { - return maxLength; - } - - public void onChange(ChangeEvent event) { - valueChange(false); - } - - /** - * Called when the field value might have changed and/or the field was - * blurred. These are combined so the blur event is sent in the same batch - * as a possible value change event (these are often connected). - * - * @param blurred - * true if the field was blurred - */ - public void valueChange(boolean blurred) { - if (client != null && paintableId != null) { - boolean sendBlurEvent = false; - boolean sendValueChange = false; - - if (blurred && client.hasEventListeners(this, EventId.BLUR)) { - sendBlurEvent = true; - client.updateVariable(paintableId, EventId.BLUR, "", false); - } - - String newText = getText(); - if (!prompting && newText != null - && !newText.equals(valueBeforeEdit)) { - sendValueChange = immediate; - client.updateVariable(paintableId, "text", getText(), false); - valueBeforeEdit = newText; - valueBeforeEditIsSynced = true; - } - - /* - * also send cursor position, no public api yet but for easier - * extension - */ - updateCursorPosition(); - - if (sendBlurEvent || sendValueChange) { - /* - * Avoid sending text change event as we will simulate it on the - * server side before value change events. - */ - textChangeEventTrigger.cancel(); - scheduled = false; - client.sendPendingVariableChanges(); - } - } - } - - /** - * Updates the cursor position variable if it has changed since the last - * update. - * - * @return true iff the value was updated - */ - protected boolean updateCursorPosition() { - if (Util.isAttachedAndDisplayed(this)) { - int cursorPos = getCursorPos(); - if (lastCursorPos != cursorPos) { - client.updateVariable(paintableId, VAR_CURSOR, cursorPos, false); - lastCursorPos = cursorPos; - return true; - } - } - return false; - } - - private static VTextField focusedTextField; - - public static void flushChangesFromFocusedTextField() { - if (focusedTextField != null) { - focusedTextField.onChange(null); - } - } - - public void onFocus(FocusEvent event) { - addStyleDependentName(CLASSNAME_FOCUS); - if (prompting) { - setText(""); - removeStyleDependentName(CLASSNAME_PROMPT); - setPrompting(false); - } - focusedTextField = this; - if (client.hasEventListeners(this, EventId.FOCUS)) { - client.updateVariable(paintableId, EventId.FOCUS, "", true); - } - } - - public void onBlur(BlurEvent event) { - removeStyleDependentName(CLASSNAME_FOCUS); - focusedTextField = null; - String text = getText(); - setPrompting(inputPrompt != null && (text == null || "".equals(text))); - if (prompting) { - setText(isReadOnly() ? "" : inputPrompt); - addStyleDependentName(CLASSNAME_PROMPT); - } - - valueChange(true); - } - - private void setPrompting(boolean prompting) { - this.prompting = prompting; - } - - public void setColumns(int columns) { - setColumns(getElement(), columns); - } - - private native void setColumns(Element e, int c) - /*-{ - try { - switch(e.tagName.toLowerCase()) { - case "input": - //e.size = c; - e.style.width = c+"em"; - break; - case "textarea": - //e.cols = c; - e.style.width = c+"em"; - break; - default:; - } - } catch (e) {} - }-*/; - - // Here for backward compatibility; to be moved to TextArea - public void setWordwrap(boolean enabled) { - if (enabled == wordwrap) { - return; // No change - } - - if (enabled) { - getElement().removeAttribute("wrap"); - getElement().getStyle().clearOverflow(); - } else { - getElement().setAttribute("wrap", "off"); - getElement().getStyle().setOverflow(Overflow.AUTO); - } - if (BrowserInfo.get().isSafari4()) { - // Force redraw as Safari 4 does not properly update the screen - Util.forceWebkitRedraw(getElement()); - } else if (BrowserInfo.get().isOpera()) { - // Opera fails to dynamically update the wrap attribute so we detach - // and reattach the whole TextArea. - Util.detachAttach(getElement()); - } - wordwrap = enabled; - } - - public void onKeyDown(KeyDownEvent event) { - if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) { - valueChange(false); - } - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java b/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java deleted file mode 100644 index 01e5103c2e..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VTextualDate.java +++ /dev/null @@ -1,331 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.Date; - -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ChangeEvent; -import com.google.gwt.event.dom.client.ChangeHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.TextBox; -import com.vaadin.terminal.gwt.client.EventId; -import com.vaadin.terminal.gwt.client.Focusable; -import com.vaadin.terminal.gwt.client.LocaleNotLoadedException; -import com.vaadin.terminal.gwt.client.LocaleService; -import com.vaadin.terminal.gwt.client.VConsole; - -public class VTextualDate extends VDateField implements Field, ChangeHandler, - Focusable, SubPartAware { - - private static final String PARSE_ERROR_CLASSNAME = CLASSNAME - + "-parseerror"; - - protected final TextBox text; - - protected String formatStr; - - protected boolean lenient; - - private static final String CLASSNAME_PROMPT = "prompt"; - protected static final String ATTR_INPUTPROMPT = "prompt"; - protected String inputPrompt = ""; - private boolean prompting = false; - - public VTextualDate() { - - super(); - text = new TextBox(); - // use normal textfield styles as a basis - text.setStyleName(VTextField.CLASSNAME); - // add datefield spesific style name also - text.addStyleName(CLASSNAME + "-textfield"); - text.addChangeHandler(this); - text.addFocusHandler(new FocusHandler() { - public void onFocus(FocusEvent event) { - text.addStyleName(VTextField.CLASSNAME + "-" - + VTextField.CLASSNAME_FOCUS); - if (prompting) { - text.setText(""); - setPrompting(false); - } - if (getClient() != null - && getClient().hasEventListeners(VTextualDate.this, - EventId.FOCUS)) { - getClient() - .updateVariable(getId(), EventId.FOCUS, "", true); - } - } - }); - text.addBlurHandler(new BlurHandler() { - public void onBlur(BlurEvent event) { - text.removeStyleName(VTextField.CLASSNAME + "-" - + VTextField.CLASSNAME_FOCUS); - String value = getText(); - setPrompting(inputPrompt != null - && (value == null || "".equals(value))); - if (prompting) { - text.setText(readonly ? "" : inputPrompt); - } - if (getClient() != null - && getClient().hasEventListeners(VTextualDate.this, - EventId.BLUR)) { - getClient().updateVariable(getId(), EventId.BLUR, "", true); - } - } - }); - add(text); - } - - protected String getFormatString() { - if (formatStr == null) { - if (currentResolution == RESOLUTION_YEAR) { - formatStr = "yyyy"; // force full year - } else { - - try { - String frmString = LocaleService - .getDateFormat(currentLocale); - frmString = cleanFormat(frmString); - // String delim = LocaleService - // .getClockDelimiter(currentLocale); - - if (currentResolution >= RESOLUTION_HOUR) { - if (dts.isTwelveHourClock()) { - frmString += " hh"; - } else { - frmString += " HH"; - } - if (currentResolution >= RESOLUTION_MIN) { - frmString += ":mm"; - if (currentResolution >= RESOLUTION_SEC) { - frmString += ":ss"; - } - } - if (dts.isTwelveHourClock()) { - frmString += " aaa"; - } - - } - - formatStr = frmString; - } catch (LocaleNotLoadedException e) { - // TODO should die instead? Can the component survive - // without format string? - VConsole.error(e); - } - } - } - return formatStr; - } - - /** - * Updates the text field according to the current date (provided by - * {@link #getDate()}). Takes care of updating text, enabling and disabling - * the field, setting/removing readonly status and updating readonly styles. - * - * TODO: Split part of this into a method that only updates the text as this - * is what usually is needed except for updateFromUIDL. - */ - protected void buildDate() { - removeStyleName(PARSE_ERROR_CLASSNAME); - // Create the initial text for the textfield - String dateText; - Date currentDate = getDate(); - if (currentDate != null) { - dateText = getDateTimeService().formatDate(currentDate, - getFormatString()); - } else { - dateText = ""; - } - - setText(dateText); - text.setEnabled(enabled); - text.setReadOnly(readonly); - - if (readonly) { - text.addStyleName("v-readonly"); - } else { - text.removeStyleName("v-readonly"); - } - - } - - protected void setPrompting(boolean prompting) { - this.prompting = prompting; - if (prompting) { - addStyleDependentName(CLASSNAME_PROMPT); - } else { - removeStyleDependentName(CLASSNAME_PROMPT); - } - } - - @SuppressWarnings("deprecation") - public void onChange(ChangeEvent event) { - if (!text.getText().equals("")) { - try { - String enteredDate = text.getText(); - - setDate(getDateTimeService().parseDate(enteredDate, - getFormatString(), lenient)); - - if (lenient) { - // If date value was leniently parsed, normalize text - // presentation. - // FIXME: Add a description/example here of when this is - // needed - text.setValue( - getDateTimeService().formatDate(getDate(), - getFormatString()), false); - } - - // remove possibly added invalid value indication - removeStyleName(PARSE_ERROR_CLASSNAME); - } catch (final Exception e) { - VConsole.log(e); - - addStyleName(PARSE_ERROR_CLASSNAME); - // this is a hack that may eventually be removed - getClient().updateVariable(getId(), "lastInvalidDateString", - text.getText(), false); - setDate(null); - } - } else { - setDate(null); - // remove possibly added invalid value indication - removeStyleName(PARSE_ERROR_CLASSNAME); - } - // always send the date string - getClient() - .updateVariable(getId(), "dateString", text.getText(), false); - - // Update variables - // (only the smallest defining resolution needs to be - // immediate) - Date currentDate = getDate(); - getClient().updateVariable(getId(), "year", - currentDate != null ? currentDate.getYear() + 1900 : -1, - currentResolution == VDateField.RESOLUTION_YEAR && immediate); - if (currentResolution >= VDateField.RESOLUTION_MONTH) { - getClient().updateVariable( - getId(), - "month", - currentDate != null ? currentDate.getMonth() + 1 : -1, - currentResolution == VDateField.RESOLUTION_MONTH - && immediate); - } - if (currentResolution >= VDateField.RESOLUTION_DAY) { - getClient() - .updateVariable( - getId(), - "day", - currentDate != null ? currentDate.getDate() : -1, - currentResolution == VDateField.RESOLUTION_DAY - && immediate); - } - if (currentResolution >= VDateField.RESOLUTION_HOUR) { - getClient().updateVariable( - getId(), - "hour", - currentDate != null ? currentDate.getHours() : -1, - currentResolution == VDateField.RESOLUTION_HOUR - && immediate); - } - if (currentResolution >= VDateField.RESOLUTION_MIN) { - getClient() - .updateVariable( - getId(), - "min", - currentDate != null ? currentDate.getMinutes() : -1, - currentResolution == VDateField.RESOLUTION_MIN - && immediate); - } - if (currentResolution >= VDateField.RESOLUTION_SEC) { - getClient() - .updateVariable( - getId(), - "sec", - currentDate != null ? currentDate.getSeconds() : -1, - currentResolution == VDateField.RESOLUTION_SEC - && immediate); - } - - } - - private String cleanFormat(String format) { - // Remove unnecessary d & M if resolution is too low - if (currentResolution < VDateField.RESOLUTION_DAY) { - format = format.replaceAll("d", ""); - } - if (currentResolution < VDateField.RESOLUTION_MONTH) { - format = format.replaceAll("M", ""); - } - - // Remove unsupported patterns - // TODO support for 'G', era designator (used at least in Japan) - format = format.replaceAll("[GzZwWkK]", ""); - - // Remove extra delimiters ('/' and '.') - while (format.startsWith("/") || format.startsWith(".") - || format.startsWith("-")) { - format = format.substring(1); - } - while (format.endsWith("/") || format.endsWith(".") - || format.endsWith("-")) { - format = format.substring(0, format.length() - 1); - } - - // Remove duplicate delimiters - format = format.replaceAll("//", "/"); - format = format.replaceAll("\\.\\.", "."); - format = format.replaceAll("--", "-"); - - return format.trim(); - } - - public void focus() { - text.setFocus(true); - } - - protected String getText() { - if (prompting) { - return ""; - } - return text.getText(); - } - - protected void setText(String text) { - if (inputPrompt != null && (text == null || "".equals(text))) { - text = readonly ? "" : inputPrompt; - setPrompting(true); - } else { - setPrompting(false); - } - - this.text.setText(text); - } - - private final String TEXTFIELD_ID = "field"; - - public Element getSubPartElement(String subPart) { - if (subPart.equals(TEXTFIELD_ID)) { - return text.getElement(); - } - - return null; - } - - public String getSubPartName(Element subElement) { - if (text.getElement().isOrHasChild(subElement)) { - return TEXTFIELD_ID; - } - - return null; - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTree.java b/src/com/vaadin/terminal/gwt/client/ui/VTree.java deleted file mode 100644 index 49c6a2a582..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VTree.java +++ /dev/null @@ -1,2095 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.Node; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ContextMenuEvent; -import com.google.gwt.event.dom.client.ContextMenuHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.KeyPressEvent; -import com.google.gwt.event.dom.client.KeyPressHandler; -import com.google.gwt.user.client.Command; -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.Window; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.UIObject; -import com.google.gwt.user.client.ui.Widget; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.ComponentConnector; -import com.vaadin.terminal.gwt.client.ConnectorMap; -import com.vaadin.terminal.gwt.client.MouseEventDetails; -import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.VTooltip; -import com.vaadin.terminal.gwt.client.ui.dd.DDUtil; -import com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler; -import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCallback; -import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager; -import com.vaadin.terminal.gwt.client.ui.dd.VDragEvent; -import com.vaadin.terminal.gwt.client.ui.dd.VDropHandler; -import com.vaadin.terminal.gwt.client.ui.dd.VHasDropHandler; -import com.vaadin.terminal.gwt.client.ui.dd.VTransferable; -import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; - -/** - * - */ -public class VTree extends FocusElementPanel implements VHasDropHandler, - FocusHandler, BlurHandler, KeyPressHandler, KeyDownHandler, - SubPartAware, ActionOwner { - - public static final String CLASSNAME = "v-tree"; - - public static final String ITEM_CLICK_EVENT_ID = "itemClick"; - - /** - * Click selects the current node, ctrl/shift toggles multi selection - */ - public static final int MULTISELECT_MODE_DEFAULT = 0; - - /** - * Click/touch on node toggles its selected status - */ - public static final int MULTISELECT_MODE_SIMPLE = 1; - - private static final int CHARCODE_SPACE = 32; - - final FlowPanel body = new FlowPanel(); - - Set selectedIds = new HashSet(); - ApplicationConnection client; - String paintableId; - boolean selectable; - boolean isMultiselect; - private String currentMouseOverKey; - TreeNode lastSelection; - TreeNode focusedNode; - int multiSelectMode = MULTISELECT_MODE_DEFAULT; - - private final HashMap keyToNode = new HashMap(); - - /** - * This map contains captions and icon urls for actions like: * "33_c" -> - * "Edit" * "33_i" -> "http://dom.com/edit.png" - */ - private final HashMap actionMap = new HashMap(); - - boolean immediate; - - boolean isNullSelectionAllowed = true; - - boolean disabled = false; - - boolean readonly; - - boolean rendering; - - private VAbstractDropHandler dropHandler; - - int dragMode; - - private boolean selectionHasChanged = false; - - String[] bodyActionKeys; - - public VLazyExecutor iconLoaded = new VLazyExecutor(50, - new ScheduledCommand() { - - public void execute() { - Util.notifyParentOfSizeChange(VTree.this, true); - } - - }); - - public VTree() { - super(); - setStyleName(CLASSNAME); - add(body); - - addFocusHandler(this); - addBlurHandler(this); - - /* - * Listen to context menu events on the empty space in the tree - */ - sinkEvents(Event.ONCONTEXTMENU); - addDomHandler(new ContextMenuHandler() { - public void onContextMenu(ContextMenuEvent event) { - handleBodyContextMenu(event); - } - }, ContextMenuEvent.getType()); - - /* - * Firefox auto-repeat works correctly only if we use a key press - * handler, other browsers handle it correctly when using a key down - * handler - */ - if (BrowserInfo.get().isGecko() || BrowserInfo.get().isOpera()) { - addKeyPressHandler(this); - } else { - addKeyDownHandler(this); - } - - /* - * We need to use the sinkEvents method to catch the keyUp events so we - * can cache a single shift. KeyUpHandler cannot do this. At the same - * time we catch the mouse down and up events so we can apply the text - * selection patch in IE - */ - sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONKEYUP); - - /* - * Re-set the tab index to make sure that the FocusElementPanel's - * (super) focus element gets the tab index and not the element - * containing the tree. - */ - setTabIndex(0); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user - * .client.Event) - */ - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (event.getTypeInt() == Event.ONMOUSEDOWN) { - // Prevent default text selection in IE - if (BrowserInfo.get().isIE()) { - ((Element) event.getEventTarget().cast()).setPropertyJSO( - "onselectstart", applyDisableTextSelectionIEHack()); - } - } else if (event.getTypeInt() == Event.ONMOUSEUP) { - // Remove IE text selection hack - if (BrowserInfo.get().isIE()) { - ((Element) event.getEventTarget().cast()).setPropertyJSO( - "onselectstart", null); - } - } else if (event.getTypeInt() == Event.ONKEYUP) { - if (selectionHasChanged) { - if (event.getKeyCode() == getNavigationDownKey() - && !event.getShiftKey()) { - sendSelectionToServer(); - event.preventDefault(); - } else if (event.getKeyCode() == getNavigationUpKey() - && !event.getShiftKey()) { - sendSelectionToServer(); - event.preventDefault(); - } else if (event.getKeyCode() == KeyCodes.KEY_SHIFT) { - sendSelectionToServer(); - event.preventDefault(); - } else if (event.getKeyCode() == getNavigationSelectKey()) { - sendSelectionToServer(); - event.preventDefault(); - } - } - } - } - - public String getActionCaption(String actionKey) { - return actionMap.get(actionKey + "_c"); - } - - public String getActionIcon(String actionKey) { - return actionMap.get(actionKey + "_i"); - } - - /** - * Returns the first root node of the tree or null if there are no root - * nodes. - * - * @return The first root {@link TreeNode} - */ - protected TreeNode getFirstRootNode() { - if (body.getWidgetCount() == 0) { - return null; - } - return (TreeNode) body.getWidget(0); - } - - /** - * Returns the last root node of the tree or null if there are no root - * nodes. - * - * @return The last root {@link TreeNode} - */ - protected TreeNode getLastRootNode() { - if (body.getWidgetCount() == 0) { - return null; - } - return (TreeNode) body.getWidget(body.getWidgetCount() - 1); - } - - /** - * Returns a list of all root nodes in the Tree in the order they appear in - * the tree. - * - * @return A list of all root {@link TreeNode}s. - */ - protected List getRootNodes() { - ArrayList rootNodes = new ArrayList(); - for (int i = 0; i < body.getWidgetCount(); i++) { - rootNodes.add((TreeNode) body.getWidget(i)); - } - return rootNodes; - } - - private void updateTreeRelatedDragData(VDragEvent drag) { - - currentMouseOverKey = findCurrentMouseOverKey(drag.getElementOver()); - - drag.getDropDetails().put("itemIdOver", currentMouseOverKey); - if (currentMouseOverKey != null) { - TreeNode treeNode = getNodeByKey(currentMouseOverKey); - VerticalDropLocation detail = treeNode.getDropDetail(drag - .getCurrentGwtEvent()); - Boolean overTreeNode = null; - if (treeNode != null && !treeNode.isLeaf() - && detail == VerticalDropLocation.MIDDLE) { - overTreeNode = true; - } - drag.getDropDetails().put("itemIdOverIsNode", overTreeNode); - drag.getDropDetails().put("detail", detail); - } else { - drag.getDropDetails().put("itemIdOverIsNode", null); - drag.getDropDetails().put("detail", null); - } - - } - - private String findCurrentMouseOverKey(Element elementOver) { - TreeNode treeNode = Util.findWidget(elementOver, TreeNode.class); - return treeNode == null ? null : treeNode.key; - } - - void updateDropHandler(UIDL childUidl) { - if (dropHandler == null) { - dropHandler = new VAbstractDropHandler() { - - @Override - public void dragEnter(VDragEvent drag) { - } - - @Override - protected void dragAccepted(final VDragEvent drag) { - - } - - @Override - public void dragOver(final VDragEvent currentDrag) { - final Object oldIdOver = currentDrag.getDropDetails().get( - "itemIdOver"); - final VerticalDropLocation oldDetail = (VerticalDropLocation) currentDrag - .getDropDetails().get("detail"); - - updateTreeRelatedDragData(currentDrag); - final VerticalDropLocation detail = (VerticalDropLocation) currentDrag - .getDropDetails().get("detail"); - boolean nodeHasChanged = (currentMouseOverKey != null && currentMouseOverKey != oldIdOver) - || (currentMouseOverKey == null && oldIdOver != null); - boolean detailHasChanded = (detail != null && detail != oldDetail) - || (detail == null && oldDetail != null); - - if (nodeHasChanged || detailHasChanded) { - final String newKey = currentMouseOverKey; - TreeNode treeNode = keyToNode.get(oldIdOver); - if (treeNode != null) { - // clear old styles - treeNode.emphasis(null); - } - if (newKey != null) { - validate(new VAcceptCallback() { - public void accepted(VDragEvent event) { - VerticalDropLocation curDetail = (VerticalDropLocation) event - .getDropDetails().get("detail"); - if (curDetail == detail - && newKey.equals(currentMouseOverKey)) { - getNodeByKey(newKey).emphasis(detail); - } - /* - * Else drag is already on a different - * node-detail pair, new criteria check is - * going on - */ - } - }, currentDrag); - - } - } - - } - - @Override - public void dragLeave(VDragEvent drag) { - cleanUp(); - } - - private void cleanUp() { - if (currentMouseOverKey != null) { - getNodeByKey(currentMouseOverKey).emphasis(null); - currentMouseOverKey = null; - } - } - - @Override - public boolean drop(VDragEvent drag) { - cleanUp(); - return super.drop(drag); - } - - @Override - public ComponentConnector getConnector() { - return ConnectorMap.get(client).getConnector(VTree.this); - } - - public ApplicationConnection getApplicationConnection() { - return client; - } - - }; - } - dropHandler.updateAcceptRules(childUidl); - } - - public void setSelected(TreeNode treeNode, boolean selected) { - if (selected) { - if (!isMultiselect) { - while (selectedIds.size() > 0) { - final String id = selectedIds.iterator().next(); - final TreeNode oldSelection = getNodeByKey(id); - if (oldSelection != null) { - // can be null if the node is not visible (parent - // collapsed) - oldSelection.setSelected(false); - } - selectedIds.remove(id); - } - } - treeNode.setSelected(true); - selectedIds.add(treeNode.key); - } else { - if (!isNullSelectionAllowed) { - if (!isMultiselect || selectedIds.size() == 1) { - return; - } - } - selectedIds.remove(treeNode.key); - treeNode.setSelected(false); - } - - sendSelectionToServer(); - } - - /** - * Sends the selection to the server - */ - private void sendSelectionToServer() { - Command command = new Command() { - public void execute() { - client.updateVariable(paintableId, "selected", - selectedIds.toArray(new String[selectedIds.size()]), - immediate); - selectionHasChanged = false; - } - }; - - /* - * Delaying the sending of the selection in webkit to ensure the - * selection is always sent when the tree has focus and after click - * events have been processed. This is due to the focusing - * implementation in FocusImplSafari which uses timeouts when focusing - * and blurring. - */ - if (BrowserInfo.get().isWebkit()) { - Scheduler.get().scheduleDeferred(command); - } else { - command.execute(); - } - } - - /** - * Is a node selected in the tree - * - * @param treeNode - * The node to check - * @return - */ - public boolean isSelected(TreeNode treeNode) { - return selectedIds.contains(treeNode.key); - } - - public class TreeNode extends SimplePanel implements ActionOwner { - - public static final String CLASSNAME = "v-tree-node"; - public static final String CLASSNAME_FOCUSED = CLASSNAME + "-focused"; - - public String key; - - String[] actionKeys = null; - - boolean childrenLoaded; - - Element nodeCaptionDiv; - - protected Element nodeCaptionSpan; - - FlowPanel childNodeContainer; - - private boolean open; - - private Icon icon; - - private Event mouseDownEvent; - - private int cachedHeight = -1; - - private boolean focused = false; - - public TreeNode() { - constructDom(); - sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS - | Event.TOUCHEVENTS | Event.ONCONTEXTMENU); - } - - public VerticalDropLocation getDropDetail(NativeEvent currentGwtEvent) { - if (cachedHeight < 0) { - /* - * Height is cached to avoid flickering (drop hints may change - * the reported offsetheight -> would change the drop detail) - */ - cachedHeight = nodeCaptionDiv.getOffsetHeight(); - } - VerticalDropLocation verticalDropLocation = DDUtil - .getVerticalDropLocation(nodeCaptionDiv, cachedHeight, - currentGwtEvent, 0.15); - return verticalDropLocation; - } - - protected void emphasis(VerticalDropLocation detail) { - String base = "v-tree-node-drag-"; - UIObject.setStyleName(getElement(), base + "top", - VerticalDropLocation.TOP == detail); - UIObject.setStyleName(getElement(), base + "bottom", - VerticalDropLocation.BOTTOM == detail); - UIObject.setStyleName(getElement(), base + "center", - VerticalDropLocation.MIDDLE == detail); - base = "v-tree-node-caption-drag-"; - UIObject.setStyleName(nodeCaptionDiv, base + "top", - VerticalDropLocation.TOP == detail); - UIObject.setStyleName(nodeCaptionDiv, base + "bottom", - VerticalDropLocation.BOTTOM == detail); - UIObject.setStyleName(nodeCaptionDiv, base + "center", - VerticalDropLocation.MIDDLE == detail); - - // also add classname to "folder node" into which the drag is - // targeted - - TreeNode folder = null; - /* Possible parent of this TreeNode will be stored here */ - TreeNode parentFolder = getParentNode(); - - // TODO fix my bugs - if (isLeaf()) { - folder = parentFolder; - // note, parent folder may be null if this is root node => no - // folder target exists - } else { - if (detail == VerticalDropLocation.TOP) { - folder = parentFolder; - } else { - folder = this; - } - // ensure we remove the dragfolder classname from the previous - // folder node - setDragFolderStyleName(this, false); - setDragFolderStyleName(parentFolder, false); - } - if (folder != null) { - setDragFolderStyleName(folder, detail != null); - } - - } - - private TreeNode getParentNode() { - Widget parent2 = getParent().getParent(); - if (parent2 instanceof TreeNode) { - return (TreeNode) parent2; - } - return null; - } - - private void setDragFolderStyleName(TreeNode folder, boolean add) { - if (folder != null) { - UIObject.setStyleName(folder.getElement(), - "v-tree-node-dragfolder", add); - UIObject.setStyleName(folder.nodeCaptionDiv, - "v-tree-node-caption-dragfolder", add); - } - } - - /** - * Handles mouse selection - * - * @param ctrl - * Was the ctrl-key pressed - * @param shift - * Was the shift-key pressed - * @return Returns true if event was handled, else false - */ - private boolean handleClickSelection(final boolean ctrl, - final boolean shift) { - - // always when clicking an item, focus it - setFocusedNode(this, false); - - if (!BrowserInfo.get().isOpera()) { - /* - * Ensure that the tree's focus element also gains focus - * (TreeNodes focus is faked using FocusElementPanel in browsers - * other than Opera). - */ - focus(); - } - - ScheduledCommand command = new ScheduledCommand() { - public void execute() { - - if (multiSelectMode == MULTISELECT_MODE_SIMPLE - || !isMultiselect) { - toggleSelection(); - lastSelection = TreeNode.this; - } else if (multiSelectMode == MULTISELECT_MODE_DEFAULT) { - // Handle ctrl+click - if (isMultiselect && ctrl && !shift) { - toggleSelection(); - lastSelection = TreeNode.this; - - // Handle shift+click - } else if (isMultiselect && !ctrl && shift) { - deselectAll(); - selectNodeRange(lastSelection.key, key); - sendSelectionToServer(); - - // Handle ctrl+shift click - } else if (isMultiselect && ctrl && shift) { - selectNodeRange(lastSelection.key, key); - - // Handle click - } else { - // TODO should happen only if this alone not yet - // selected, - // now sending excess server calls - deselectAll(); - toggleSelection(); - lastSelection = TreeNode.this; - } - } - } - }; - - if (BrowserInfo.get().isWebkit() && !treeHasFocus) { - /* - * Safari may need to wait for focus. See FocusImplSafari. - */ - // VConsole.log("Deferring click handling to let webkit gain focus..."); - Scheduler.get().scheduleDeferred(command); - } else { - command.execute(); - } - - return true; - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt - * .user.client.Event) - */ - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - final int type = DOM.eventGetType(event); - final Element target = DOM.eventGetTarget(event); - - if (type == Event.ONLOAD && target == icon.getElement()) { - iconLoaded.trigger(); - } - - if (disabled) { - return; - } - - if (target == nodeCaptionSpan) { - client.handleTooltipEvent(event, VTree.this, key); - } - - final boolean inCaption = target == nodeCaptionSpan - || (icon != null && target == icon.getElement()); - if (inCaption - && client - .hasEventListeners(VTree.this, ITEM_CLICK_EVENT_ID) - - && (type == Event.ONDBLCLICK || type == Event.ONMOUSEUP)) { - fireClick(event); - } - if (type == Event.ONCLICK) { - if (getElement() == target) { - // state change - toggleState(); - } else if (!readonly && inCaption) { - if (selectable) { - // caption click = selection change && possible click - // event - if (handleClickSelection( - event.getCtrlKey() || event.getMetaKey(), - event.getShiftKey())) { - event.preventDefault(); - } - } else { - // Not selectable, only focus the node. - setFocusedNode(this); - } - } - event.stopPropagation(); - } else if (type == Event.ONCONTEXTMENU) { - showContextMenu(event); - } - - if (dragMode != 0 || dropHandler != null) { - if (type == Event.ONMOUSEDOWN || type == Event.ONTOUCHSTART) { - if (nodeCaptionDiv.isOrHasChild((Node) event - .getEventTarget().cast())) { - if (dragMode > 0 - && (type == Event.ONTOUCHSTART || event - .getButton() == NativeEvent.BUTTON_LEFT)) { - mouseDownEvent = event; // save event for possible - // dd operation - if (type == Event.ONMOUSEDOWN) { - event.preventDefault(); // prevent text - // selection - } else { - /* - * FIXME We prevent touch start event to be used - * as a scroll start event. Note that we cannot - * easily distinguish whether the user wants to - * drag or scroll. The same issue is in table - * that has scrollable area and has drag and - * drop enable. Some kind of timer might be used - * to resolve the issue. - */ - event.stopPropagation(); - } - } - } - } else if (type == Event.ONMOUSEMOVE - || type == Event.ONMOUSEOUT - || type == Event.ONTOUCHMOVE) { - - if (mouseDownEvent != null) { - // start actual drag on slight move when mouse is down - VTransferable t = new VTransferable(); - t.setDragSource(ConnectorMap.get(client).getConnector( - VTree.this)); - t.setData("itemId", key); - VDragEvent drag = VDragAndDropManager.get().startDrag( - t, mouseDownEvent, true); - - drag.createDragImage(nodeCaptionDiv, true); - event.stopPropagation(); - - mouseDownEvent = null; - } - } else if (type == Event.ONMOUSEUP) { - mouseDownEvent = null; - } - if (type == Event.ONMOUSEOVER) { - mouseDownEvent = null; - currentMouseOverKey = key; - event.stopPropagation(); - } - - } else if (type == Event.ONMOUSEDOWN - && event.getButton() == NativeEvent.BUTTON_LEFT) { - event.preventDefault(); // text selection - } - } - - private void fireClick(final Event evt) { - /* - * Ensure we have focus in tree before sending variables. Otherwise - * previously modified field may contain dirty variables. - */ - if (!treeHasFocus) { - if (BrowserInfo.get().isOpera()) { - if (focusedNode == null) { - getNodeByKey(key).setFocused(true); - } else { - focusedNode.setFocused(true); - } - } else { - focus(); - } - } - final MouseEventDetails details = MouseEventDetailsBuilder - .buildMouseEventDetails(evt); - ScheduledCommand command = new ScheduledCommand() { - public void execute() { - // Determine if we should send the event immediately to the - // server. We do not want to send the event if there is a - // selection event happening after this. In all other cases - // we want to send it immediately. - boolean sendClickEventNow = true; - - if (details.getButton() == NativeEvent.BUTTON_LEFT - && immediate && selectable) { - // Probably a selection that will cause a value change - // event to be sent - sendClickEventNow = false; - - // The exception is that user clicked on the - // currently selected row and null selection is not - // allowed == no selection event - if (isSelected() && selectedIds.size() == 1 - && !isNullSelectionAllowed) { - sendClickEventNow = true; - } - } - - client.updateVariable(paintableId, "clickedKey", key, false); - client.updateVariable(paintableId, "clickEvent", - details.toString(), sendClickEventNow); - } - }; - if (treeHasFocus) { - command.execute(); - } else { - /* - * Webkits need a deferring due to FocusImplSafari uses timeout - */ - Scheduler.get().scheduleDeferred(command); - } - } - - private void toggleSelection() { - if (selectable) { - VTree.this.setSelected(this, !isSelected()); - } - } - - private void toggleState() { - setState(!getState(), true); - } - - protected void constructDom() { - addStyleName(CLASSNAME); - - nodeCaptionDiv = DOM.createDiv(); - DOM.setElementProperty(nodeCaptionDiv, "className", CLASSNAME - + "-caption"); - Element wrapper = DOM.createDiv(); - nodeCaptionSpan = DOM.createSpan(); - DOM.sinkEvents(nodeCaptionSpan, VTooltip.TOOLTIP_EVENTS); - DOM.appendChild(getElement(), nodeCaptionDiv); - DOM.appendChild(nodeCaptionDiv, wrapper); - DOM.appendChild(wrapper, nodeCaptionSpan); - - if (BrowserInfo.get().isOpera()) { - /* - * Focus the caption div of the node to get keyboard navigation - * to work without scrolling up or down when focusing a node. - */ - nodeCaptionDiv.setTabIndex(-1); - } - - childNodeContainer = new FlowPanel(); - childNodeContainer.setStyleName(CLASSNAME + "-children"); - setWidget(childNodeContainer); - } - - public boolean isLeaf() { - String[] styleNames = getStyleName().split(" "); - for (String styleName : styleNames) { - if (styleName.equals(CLASSNAME + "-leaf")) { - return true; - } - } - return false; - } - - void setState(boolean state, boolean notifyServer) { - if (open == state) { - return; - } - if (state) { - if (!childrenLoaded && notifyServer) { - client.updateVariable(paintableId, "requestChildTree", - true, false); - } - if (notifyServer) { - client.updateVariable(paintableId, "expand", - new String[] { key }, true); - } - addStyleName(CLASSNAME + "-expanded"); - childNodeContainer.setVisible(true); - - } else { - removeStyleName(CLASSNAME + "-expanded"); - childNodeContainer.setVisible(false); - if (notifyServer) { - client.updateVariable(paintableId, "collapse", - new String[] { key }, true); - } - } - open = state; - - if (!rendering) { - Util.notifyParentOfSizeChange(VTree.this, false); - } - } - - boolean getState() { - return open; - } - - void setText(String text) { - DOM.setInnerText(nodeCaptionSpan, text); - } - - public boolean isChildrenLoaded() { - return childrenLoaded; - } - - /** - * Returns the children of the node - * - * @return A set of tree nodes - */ - public List getChildren() { - List nodes = new LinkedList(); - - if (!isLeaf() && isChildrenLoaded()) { - Iterator iter = childNodeContainer.iterator(); - while (iter.hasNext()) { - TreeNode node = (TreeNode) iter.next(); - nodes.add(node); - } - } - return nodes; - } - - public Action[] getActions() { - if (actionKeys == null) { - return new Action[] {}; - } - final Action[] actions = new Action[actionKeys.length]; - for (int i = 0; i < actions.length; i++) { - final String actionKey = actionKeys[i]; - final TreeAction a = new TreeAction(this, String.valueOf(key), - actionKey); - a.setCaption(getActionCaption(actionKey)); - a.setIconUrl(getActionIcon(actionKey)); - actions[i] = a; - } - return actions; - } - - public ApplicationConnection getClient() { - return client; - } - - public String getPaintableId() { - return paintableId; - } - - /** - * Adds/removes Vaadin specific style name. This method ought to be - * called only from VTree. - * - * @param selected - */ - protected void setSelected(boolean selected) { - // add style name to caption dom structure only, not to subtree - setStyleName(nodeCaptionDiv, "v-tree-node-selected", selected); - } - - protected boolean isSelected() { - return VTree.this.isSelected(this); - } - - /** - * Travels up the hierarchy looking for this node - * - * @param child - * The child which grandparent this is or is not - * @return True if this is a grandparent of the child node - */ - public boolean isGrandParentOf(TreeNode child) { - TreeNode currentNode = child; - boolean isGrandParent = false; - while (currentNode != null) { - currentNode = currentNode.getParentNode(); - if (currentNode == this) { - isGrandParent = true; - break; - } - } - return isGrandParent; - } - - public boolean isSibling(TreeNode node) { - return node.getParentNode() == getParentNode(); - } - - public void showContextMenu(Event event) { - if (!readonly && !disabled) { - if (actionKeys != null) { - int left = event.getClientX(); - int top = event.getClientY(); - top += Window.getScrollTop(); - left += Window.getScrollLeft(); - client.getContextMenu().showAt(this, left, top); - } - event.stopPropagation(); - event.preventDefault(); - } - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.Widget#onDetach() - */ - @Override - protected void onDetach() { - super.onDetach(); - client.getContextMenu().ensureHidden(this); - } - - /* - * (non-Javadoc) - * - * @see com.google.gwt.user.client.ui.UIObject#toString() - */ - @Override - public String toString() { - return nodeCaptionSpan.getInnerText(); - } - - /** - * Is the node focused? - * - * @param focused - * True if focused, false if not - */ - public void setFocused(boolean focused) { - if (!this.focused && focused) { - nodeCaptionDiv.addClassName(CLASSNAME_FOCUSED); - - this.focused = focused; - if (BrowserInfo.get().isOpera()) { - nodeCaptionDiv.focus(); - } - treeHasFocus = true; - } else if (this.focused && !focused) { - nodeCaptionDiv.removeClassName(CLASSNAME_FOCUSED); - this.focused = focused; - treeHasFocus = false; - } - } - - /** - * Scrolls the caption into view - */ - public void scrollIntoView() { - Util.scrollIntoViewVertically(nodeCaptionDiv); - } - - public void setIcon(String iconUrl) { - if (iconUrl != null) { - // Add icon if not present - if (icon == null) { - icon = new Icon(client); - DOM.insertBefore(DOM.getFirstChild(nodeCaptionDiv), - icon.getElement(), nodeCaptionSpan); - } - icon.setUri(iconUrl); - } else { - // Remove icon if present - if (icon != null) { - DOM.removeChild(DOM.getFirstChild(nodeCaptionDiv), - icon.getElement()); - icon = null; - } - } - } - - public void setNodeStyleName(String styleName) { - addStyleName(TreeNode.CLASSNAME + "-" + styleName); - setStyleName(nodeCaptionDiv, TreeNode.CLASSNAME + "-caption-" - + styleName, true); - childNodeContainer.addStyleName(TreeNode.CLASSNAME + "-children-" - + styleName); - - } - - } - - public VDropHandler getDropHandler() { - return dropHandler; - } - - public TreeNode getNodeByKey(String key) { - return keyToNode.get(key); - } - - /** - * Deselects all items in the tree - */ - public void deselectAll() { - for (String key : selectedIds) { - TreeNode node = keyToNode.get(key); - if (node != null) { - node.setSelected(false); - } - } - selectedIds.clear(); - selectionHasChanged = true; - } - - /** - * Selects a range of nodes - * - * @param startNodeKey - * The start node key - * @param endNodeKey - * The end node key - */ - private void selectNodeRange(String startNodeKey, String endNodeKey) { - - TreeNode startNode = keyToNode.get(startNodeKey); - TreeNode endNode = keyToNode.get(endNodeKey); - - // The nodes have the same parent - if (startNode.getParent() == endNode.getParent()) { - doSiblingSelection(startNode, endNode); - - // The start node is a grandparent of the end node - } else if (startNode.isGrandParentOf(endNode)) { - doRelationSelection(startNode, endNode); - - // The end node is a grandparent of the start node - } else if (endNode.isGrandParentOf(startNode)) { - doRelationSelection(endNode, startNode); - - } else { - doNoRelationSelection(startNode, endNode); - } - } - - /** - * Selects a node and deselect all other nodes - * - * @param node - * The node to select - */ - private void selectNode(TreeNode node, boolean deselectPrevious) { - if (deselectPrevious) { - deselectAll(); - } - - if (node != null) { - node.setSelected(true); - selectedIds.add(node.key); - lastSelection = node; - } - selectionHasChanged = true; - } - - /** - * Deselects a node - * - * @param node - * The node to deselect - */ - private void deselectNode(TreeNode node) { - node.setSelected(false); - selectedIds.remove(node.key); - selectionHasChanged = true; - } - - /** - * Selects all the open children to a node - * - * @param node - * The parent node - */ - private void selectAllChildren(TreeNode node, boolean includeRootNode) { - if (includeRootNode) { - node.setSelected(true); - selectedIds.add(node.key); - } - - for (TreeNode child : node.getChildren()) { - if (!child.isLeaf() && child.getState()) { - selectAllChildren(child, true); - } else { - child.setSelected(true); - selectedIds.add(child.key); - } - } - selectionHasChanged = true; - } - - /** - * Selects all children until a stop child is reached - * - * @param root - * The root not to start from - * @param stopNode - * The node to finish with - * @param includeRootNode - * Should the root node be selected - * @param includeStopNode - * Should the stop node be selected - * - * @return Returns false if the stop child was found, else true if all - * children was selected - */ - private boolean selectAllChildrenUntil(TreeNode root, TreeNode stopNode, - boolean includeRootNode, boolean includeStopNode) { - if (includeRootNode) { - root.setSelected(true); - selectedIds.add(root.key); - } - if (root.getState() && root != stopNode) { - for (TreeNode child : root.getChildren()) { - if (!child.isLeaf() && child.getState() && child != stopNode) { - if (!selectAllChildrenUntil(child, stopNode, true, - includeStopNode)) { - return false; - } - } else if (child == stopNode) { - if (includeStopNode) { - child.setSelected(true); - selectedIds.add(child.key); - } - return false; - } else { - child.setSelected(true); - selectedIds.add(child.key); - } - } - } - selectionHasChanged = true; - - return true; - } - - /** - * Select a range between two nodes which have no relation to each other - * - * @param startNode - * The start node to start the selection from - * @param endNode - * The end node to end the selection to - */ - private void doNoRelationSelection(TreeNode startNode, TreeNode endNode) { - - TreeNode commonParent = getCommonGrandParent(startNode, endNode); - TreeNode startBranch = null, endBranch = null; - - // Find the children of the common parent - List children; - if (commonParent != null) { - children = commonParent.getChildren(); - } else { - children = getRootNodes(); - } - - // Find the start and end branches - for (TreeNode node : children) { - if (nodeIsInBranch(startNode, node)) { - startBranch = node; - } - if (nodeIsInBranch(endNode, node)) { - endBranch = node; - } - } - - // Swap nodes if necessary - if (children.indexOf(startBranch) > children.indexOf(endBranch)) { - TreeNode temp = startBranch; - startBranch = endBranch; - endBranch = temp; - - temp = startNode; - startNode = endNode; - endNode = temp; - } - - // Select all children under the start node - selectAllChildren(startNode, true); - TreeNode startParent = startNode.getParentNode(); - TreeNode currentNode = startNode; - while (startParent != null && startParent != commonParent) { - List startChildren = startParent.getChildren(); - for (int i = startChildren.indexOf(currentNode) + 1; i < startChildren - .size(); i++) { - selectAllChildren(startChildren.get(i), true); - } - - currentNode = startParent; - startParent = startParent.getParentNode(); - } - - // Select nodes until the end node is reached - for (int i = children.indexOf(startBranch) + 1; i <= children - .indexOf(endBranch); i++) { - selectAllChildrenUntil(children.get(i), endNode, true, true); - } - - // Ensure end node was selected - endNode.setSelected(true); - selectedIds.add(endNode.key); - selectionHasChanged = true; - } - - /** - * Examines the children of the branch node and returns true if a node is in - * that branch - * - * @param node - * The node to search for - * @param branch - * The branch to search in - * @return True if found, false if not found - */ - private boolean nodeIsInBranch(TreeNode node, TreeNode branch) { - if (node == branch) { - return true; - } - for (TreeNode child : branch.getChildren()) { - if (child == node) { - return true; - } - if (!child.isLeaf() && child.getState()) { - if (nodeIsInBranch(node, child)) { - return true; - } - } - } - return false; - } - - /** - * Selects a range of items which are in direct relation with each other.
- * NOTE: The start node MUST be before the end node! - * - * @param startNode - * - * @param endNode - */ - private void doRelationSelection(TreeNode startNode, TreeNode endNode) { - TreeNode currentNode = endNode; - while (currentNode != startNode) { - currentNode.setSelected(true); - selectedIds.add(currentNode.key); - - // Traverse children above the selection - List subChildren = currentNode.getParentNode() - .getChildren(); - if (subChildren.size() > 1) { - selectNodeRange(subChildren.iterator().next().key, - currentNode.key); - } else if (subChildren.size() == 1) { - TreeNode n = subChildren.get(0); - n.setSelected(true); - selectedIds.add(n.key); - } - - currentNode = currentNode.getParentNode(); - } - startNode.setSelected(true); - selectedIds.add(startNode.key); - selectionHasChanged = true; - } - - /** - * Selects a range of items which have the same parent. - * - * @param startNode - * The start node - * @param endNode - * The end node - */ - private void doSiblingSelection(TreeNode startNode, TreeNode endNode) { - TreeNode parent = startNode.getParentNode(); - - List children; - if (parent == null) { - // Topmost parent - children = getRootNodes(); - } else { - children = parent.getChildren(); - } - - // Swap start and end point if needed - if (children.indexOf(startNode) > children.indexOf(endNode)) { - TreeNode temp = startNode; - startNode = endNode; - endNode = temp; - } - - Iterator childIter = children.iterator(); - boolean startFound = false; - while (childIter.hasNext()) { - TreeNode node = childIter.next(); - if (node == startNode) { - startFound = true; - } - - if (startFound && node != endNode && node.getState()) { - selectAllChildren(node, true); - } else if (startFound && node != endNode) { - node.setSelected(true); - selectedIds.add(node.key); - } - - if (node == endNode) { - node.setSelected(true); - selectedIds.add(node.key); - break; - } - } - selectionHasChanged = true; - } - - /** - * Returns the first common parent of two nodes - * - * @param node1 - * The first node - * @param node2 - * The second node - * @return The common parent or null - */ - public TreeNode getCommonGrandParent(TreeNode node1, TreeNode node2) { - // If either one does not have a grand parent then return null - if (node1.getParentNode() == null || node2.getParentNode() == null) { - return null; - } - - // If the nodes are parents of each other then return null - if (node1.isGrandParentOf(node2) || node2.isGrandParentOf(node1)) { - return null; - } - - // Get parents of node1 - List parents1 = new ArrayList(); - TreeNode parent1 = node1.getParentNode(); - while (parent1 != null) { - parents1.add(parent1); - parent1 = parent1.getParentNode(); - } - - // Get parents of node2 - List parents2 = new ArrayList(); - TreeNode parent2 = node2.getParentNode(); - while (parent2 != null) { - parents2.add(parent2); - parent2 = parent2.getParentNode(); - } - - // Search the parents for the first common parent - for (int i = 0; i < parents1.size(); i++) { - parent1 = parents1.get(i); - for (int j = 0; j < parents2.size(); j++) { - parent2 = parents2.get(j); - if (parent1 == parent2) { - return parent1; - } - } - } - - return null; - } - - /** - * Sets the node currently in focus - * - * @param node - * The node to focus or null to remove the focus completely - * @param scrollIntoView - * Scroll the node into view - */ - public void setFocusedNode(TreeNode node, boolean scrollIntoView) { - // Unfocus previously focused node - if (focusedNode != null) { - focusedNode.setFocused(false); - } - - if (node != null) { - node.setFocused(true); - } - - focusedNode = node; - - if (node != null && scrollIntoView) { - /* - * Delay scrolling the focused node into view if we are still - * rendering. #5396 - */ - if (!rendering) { - node.scrollIntoView(); - } else { - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - focusedNode.scrollIntoView(); - } - }); - } - } - } - - /** - * Focuses a node and scrolls it into view - * - * @param node - * The node to focus - */ - public void setFocusedNode(TreeNode node) { - setFocusedNode(node, true); - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event - * .dom.client.FocusEvent) - */ - public void onFocus(FocusEvent event) { - treeHasFocus = true; - // If no node has focus, focus the first item in the tree - if (focusedNode == null && lastSelection == null && selectable) { - setFocusedNode(getFirstRootNode(), false); - } else if (focusedNode != null && selectable) { - setFocusedNode(focusedNode, false); - } else if (lastSelection != null && selectable) { - setFocusedNode(lastSelection, false); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event - * .dom.client.BlurEvent) - */ - public void onBlur(BlurEvent event) { - treeHasFocus = false; - if (focusedNode != null) { - focusedNode.setFocused(false); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google - * .gwt.event.dom.client.KeyPressEvent) - */ - public void onKeyPress(KeyPressEvent event) { - NativeEvent nativeEvent = event.getNativeEvent(); - int keyCode = nativeEvent.getKeyCode(); - if (keyCode == 0 && nativeEvent.getCharCode() == ' ') { - // Provide a keyCode for space to be compatible with FireFox - // keypress event - keyCode = CHARCODE_SPACE; - } - if (handleKeyNavigation(keyCode, - event.isControlKeyDown() || event.isMetaKeyDown(), - event.isShiftKeyDown())) { - event.preventDefault(); - event.stopPropagation(); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt - * .event.dom.client.KeyDownEvent) - */ - public void onKeyDown(KeyDownEvent event) { - if (handleKeyNavigation(event.getNativeEvent().getKeyCode(), - event.isControlKeyDown() || event.isMetaKeyDown(), - event.isShiftKeyDown())) { - event.preventDefault(); - event.stopPropagation(); - } - } - - /** - * Handles the keyboard navigation - * - * @param keycode - * The keycode of the pressed key - * @param ctrl - * Was ctrl pressed - * @param shift - * Was shift pressed - * @return Returns true if the key was handled, else false - */ - protected boolean handleKeyNavigation(int keycode, boolean ctrl, - boolean shift) { - // Navigate down - if (keycode == getNavigationDownKey()) { - TreeNode node = null; - // If node is open and has children then move in to the children - if (!focusedNode.isLeaf() && focusedNode.getState() - && focusedNode.getChildren().size() > 0) { - node = focusedNode.getChildren().get(0); - } - - // Else move down to the next sibling - else { - node = getNextSibling(focusedNode); - if (node == null) { - // Else jump to the parent and try to select the next - // sibling there - TreeNode current = focusedNode; - while (node == null && current.getParentNode() != null) { - node = getNextSibling(current.getParentNode()); - current = current.getParentNode(); - } - } - } - - if (node != null) { - setFocusedNode(node); - if (selectable) { - if (!ctrl && !shift) { - selectNode(node, true); - } else if (shift && isMultiselect) { - deselectAll(); - selectNodeRange(lastSelection.key, node.key); - } else if (shift) { - selectNode(node, true); - } - } - } - return true; - } - - // Navigate up - if (keycode == getNavigationUpKey()) { - TreeNode prev = getPreviousSibling(focusedNode); - TreeNode node = null; - if (prev != null) { - node = getLastVisibleChildInTree(prev); - } else if (focusedNode.getParentNode() != null) { - node = focusedNode.getParentNode(); - } - if (node != null) { - setFocusedNode(node); - if (selectable) { - if (!ctrl && !shift) { - selectNode(node, true); - } else if (shift && isMultiselect) { - deselectAll(); - selectNodeRange(lastSelection.key, node.key); - } else if (shift) { - selectNode(node, true); - } - } - } - return true; - } - - // Navigate left (close branch) - if (keycode == getNavigationLeftKey()) { - if (!focusedNode.isLeaf() && focusedNode.getState()) { - focusedNode.setState(false, true); - } else if (focusedNode.getParentNode() != null - && (focusedNode.isLeaf() || !focusedNode.getState())) { - - if (ctrl || !selectable) { - setFocusedNode(focusedNode.getParentNode()); - } else if (shift) { - doRelationSelection(focusedNode.getParentNode(), - focusedNode); - setFocusedNode(focusedNode.getParentNode()); - } else { - focusAndSelectNode(focusedNode.getParentNode()); - } - } - return true; - } - - // Navigate right (open branch) - if (keycode == getNavigationRightKey()) { - if (!focusedNode.isLeaf() && !focusedNode.getState()) { - focusedNode.setState(true, true); - } else if (!focusedNode.isLeaf()) { - if (ctrl || !selectable) { - setFocusedNode(focusedNode.getChildren().get(0)); - } else if (shift) { - setSelected(focusedNode, true); - setFocusedNode(focusedNode.getChildren().get(0)); - setSelected(focusedNode, true); - } else { - focusAndSelectNode(focusedNode.getChildren().get(0)); - } - } - return true; - } - - // Selection - if (keycode == getNavigationSelectKey()) { - if (!focusedNode.isSelected()) { - selectNode( - focusedNode, - (!isMultiselect || multiSelectMode == MULTISELECT_MODE_SIMPLE) - && selectable); - } else { - deselectNode(focusedNode); - } - return true; - } - - // Home selection - if (keycode == getNavigationStartKey()) { - TreeNode node = getFirstRootNode(); - if (ctrl || !selectable) { - setFocusedNode(node); - } else if (shift) { - deselectAll(); - selectNodeRange(focusedNode.key, node.key); - } else { - selectNode(node, true); - } - sendSelectionToServer(); - return true; - } - - // End selection - if (keycode == getNavigationEndKey()) { - TreeNode lastNode = getLastRootNode(); - TreeNode node = getLastVisibleChildInTree(lastNode); - if (ctrl || !selectable) { - setFocusedNode(node); - } else if (shift) { - deselectAll(); - selectNodeRange(focusedNode.key, node.key); - } else { - selectNode(node, true); - } - sendSelectionToServer(); - return true; - } - - return false; - } - - private void focusAndSelectNode(TreeNode node) { - /* - * Keyboard navigation doesn't work reliably if the tree is in - * multiselect mode as well as isNullSelectionAllowed = false. It first - * tries to deselect the old focused node, which fails since there must - * be at least one selection. After this the newly focused node is - * selected and we've ended up with two selected nodes even though we - * only navigated with the arrow keys. - * - * Because of this, we first select the next node and later de-select - * the old one. - */ - TreeNode oldFocusedNode = focusedNode; - setFocusedNode(node); - setSelected(focusedNode, true); - setSelected(oldFocusedNode, false); - } - - /** - * Traverses the tree to the bottom most child - * - * @param root - * The root of the tree - * @return The bottom most child - */ - private TreeNode getLastVisibleChildInTree(TreeNode root) { - if (root.isLeaf() || !root.getState() || root.getChildren().size() == 0) { - return root; - } - List children = root.getChildren(); - return getLastVisibleChildInTree(children.get(children.size() - 1)); - } - - /** - * Gets the next sibling in the tree - * - * @param node - * The node to get the sibling for - * @return The sibling node or null if the node is the last sibling - */ - private TreeNode getNextSibling(TreeNode node) { - TreeNode parent = node.getParentNode(); - List children; - if (parent == null) { - children = getRootNodes(); - } else { - children = parent.getChildren(); - } - - int idx = children.indexOf(node); - if (idx < children.size() - 1) { - return children.get(idx + 1); - } - - return null; - } - - /** - * Returns the previous sibling in the tree - * - * @param node - * The node to get the sibling for - * @return The sibling node or null if the node is the first sibling - */ - private TreeNode getPreviousSibling(TreeNode node) { - TreeNode parent = node.getParentNode(); - List children; - if (parent == null) { - children = getRootNodes(); - } else { - children = parent.getChildren(); - } - - int idx = children.indexOf(node); - if (idx > 0) { - return children.get(idx - 1); - } - - return null; - } - - /** - * Add this to the element mouse down event by using element.setPropertyJSO - * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again - * when the mouse is depressed in the mouse up event. - * - * @return Returns the JSO preventing text selection - */ - private native JavaScriptObject applyDisableTextSelectionIEHack() - /*-{ - return function(){ return false; }; - }-*/; - - /** - * Get the key that moves the selection head upwards. By default it is the - * up arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationUpKey() { - return KeyCodes.KEY_UP; - } - - /** - * Get the key that moves the selection head downwards. By default it is the - * down arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationDownKey() { - return KeyCodes.KEY_DOWN; - } - - /** - * Get the key that scrolls to the left in the table. By default it is the - * left arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationLeftKey() { - return KeyCodes.KEY_LEFT; - } - - /** - * Get the key that scroll to the right on the table. By default it is the - * right arrow key but by overriding this you can change the key to whatever - * you want. - * - * @return The keycode of the key - */ - protected int getNavigationRightKey() { - return KeyCodes.KEY_RIGHT; - } - - /** - * Get the key that selects an item in the table. By default it is the space - * bar key but by overriding this you can change the key to whatever you - * want. - * - * @return - */ - protected int getNavigationSelectKey() { - return CHARCODE_SPACE; - } - - /** - * Get the key the moves the selection one page up in the table. By default - * this is the Page Up key but by overriding this you can change the key to - * whatever you want. - * - * @return - */ - protected int getNavigationPageUpKey() { - return KeyCodes.KEY_PAGEUP; - } - - /** - * Get the key the moves the selection one page down in the table. By - * default this is the Page Down key but by overriding this you can change - * the key to whatever you want. - * - * @return - */ - protected int getNavigationPageDownKey() { - return KeyCodes.KEY_PAGEDOWN; - } - - /** - * Get the key the moves the selection to the beginning of the table. By - * default this is the Home key but by overriding this you can change the - * key to whatever you want. - * - * @return - */ - protected int getNavigationStartKey() { - return KeyCodes.KEY_HOME; - } - - /** - * Get the key the moves the selection to the end of the table. By default - * this is the End key but by overriding this you can change the key to - * whatever you want. - * - * @return - */ - protected int getNavigationEndKey() { - return KeyCodes.KEY_END; - } - - private final String SUBPART_NODE_PREFIX = "n"; - private final String EXPAND_IDENTIFIER = "expand"; - - /* - * In webkit, focus may have been requested for this component but not yet - * gained. Use this to trac if tree has gained the focus on webkit. See - * FocusImplSafari and #6373 - */ - private boolean treeHasFocus; - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.terminal.gwt.client.ui.SubPartAware#getSubPartElement(java - * .lang.String) - */ - public Element getSubPartElement(String subPart) { - if ("fe".equals(subPart)) { - if (BrowserInfo.get().isOpera() && focusedNode != null) { - return focusedNode.getElement(); - } - return getFocusElement(); - } - - if (subPart.startsWith(SUBPART_NODE_PREFIX + "[")) { - boolean expandCollapse = false; - - // Node - String[] nodes = subPart.split("/"); - TreeNode treeNode = null; - try { - for (String node : nodes) { - if (node.startsWith(SUBPART_NODE_PREFIX)) { - - // skip SUBPART_NODE_PREFIX"[" - node = node.substring(SUBPART_NODE_PREFIX.length() + 1); - // skip "]" - node = node.substring(0, node.length() - 1); - int position = Integer.parseInt(node); - if (treeNode == null) { - treeNode = getRootNodes().get(position); - } else { - treeNode = treeNode.getChildren().get(position); - } - } else if (node.startsWith(EXPAND_IDENTIFIER)) { - expandCollapse = true; - } - } - - if (expandCollapse) { - return treeNode.getElement(); - } else { - return treeNode.nodeCaptionSpan; - } - } catch (Exception e) { - // Invalid locator string or node could not be found - return null; - } - } - return null; - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.terminal.gwt.client.ui.SubPartAware#getSubPartName(com.google - * .gwt.user.client.Element) - */ - public String getSubPartName(Element subElement) { - // Supported identifiers: - // - // n[index]/n[index]/n[index]{/expand} - // - // Ends with "/expand" if the target is expand/collapse indicator, - // otherwise ends with the node - - boolean isExpandCollapse = false; - - if (!getElement().isOrHasChild(subElement)) { - return null; - } - - if (subElement == getFocusElement()) { - return "fe"; - } - - TreeNode treeNode = Util.findWidget(subElement, TreeNode.class); - if (treeNode == null) { - // Did not click on a node, let somebody else take care of the - // locator string - return null; - } - - if (subElement == treeNode.getElement()) { - // Targets expand/collapse arrow - isExpandCollapse = true; - } - - ArrayList positions = new ArrayList(); - while (treeNode.getParentNode() != null) { - positions.add(0, - treeNode.getParentNode().getChildren().indexOf(treeNode)); - treeNode = treeNode.getParentNode(); - } - positions.add(0, getRootNodes().indexOf(treeNode)); - - String locator = ""; - for (Integer i : positions) { - locator += SUBPART_NODE_PREFIX + "[" + i + "]/"; - } - - locator = locator.substring(0, locator.length() - 1); - if (isExpandCollapse) { - locator += "/" + EXPAND_IDENTIFIER; - } - return locator; - } - - public Action[] getActions() { - if (bodyActionKeys == null) { - return new Action[] {}; - } - final Action[] actions = new Action[bodyActionKeys.length]; - for (int i = 0; i < actions.length; i++) { - final String actionKey = bodyActionKeys[i]; - final TreeAction a = new TreeAction(this, null, actionKey); - a.setCaption(getActionCaption(actionKey)); - a.setIconUrl(getActionIcon(actionKey)); - actions[i] = a; - } - return actions; - } - - public ApplicationConnection getClient() { - return client; - } - - public String getPaintableId() { - return paintableId; - } - - private void handleBodyContextMenu(ContextMenuEvent event) { - if (!readonly && !disabled) { - if (bodyActionKeys != null) { - int left = event.getNativeEvent().getClientX(); - int top = event.getNativeEvent().getClientY(); - top += Window.getScrollTop(); - left += Window.getScrollLeft(); - client.getContextMenu().showAt(this, left, top); - } - event.stopPropagation(); - event.preventDefault(); - } - } - - public void registerAction(String key, String caption, String iconUrl) { - actionMap.put(key + "_c", caption); - if (iconUrl != null) { - actionMap.put(key + "_i", iconUrl); - } else { - actionMap.remove(key + "_i"); - } - - } - - public void registerNode(TreeNode treeNode) { - keyToNode.put(treeNode.key, treeNode); - } - - public void clearNodeToKeyMap() { - keyToNode.clear(); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java b/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java deleted file mode 100644 index 34c2a8b866..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VTreeTable.java +++ /dev/null @@ -1,804 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; - -import com.google.gwt.animation.client.Animation; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.ImageElement; -import com.google.gwt.dom.client.SpanElement; -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.dom.client.Style.Visibility; -import com.google.gwt.dom.client.TableCellElement; -import com.google.gwt.event.dom.client.KeyCodes; -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.Widget; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.ComputedStyle; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; -import com.vaadin.terminal.gwt.client.ui.VTreeTable.VTreeTableScrollBody.VTreeTableRow; - -public class VTreeTable extends VScrollTable { - - static class PendingNavigationEvent { - final int keycode; - final boolean ctrl; - final boolean shift; - - public PendingNavigationEvent(int keycode, boolean ctrl, boolean shift) { - this.keycode = keycode; - this.ctrl = ctrl; - this.shift = shift; - } - - @Override - public String toString() { - String string = "Keyboard event: " + keycode; - if (ctrl) { - string += " + ctrl"; - } - if (shift) { - string += " + shift"; - } - return string; - } - } - - boolean collapseRequest; - private boolean selectionPending; - int colIndexOfHierarchy; - String collapsedRowKey; - VTreeTableScrollBody scrollBody; - boolean animationsEnabled; - LinkedList pendingNavigationEvents = new LinkedList(); - boolean focusParentResponsePending; - - @Override - protected VScrollTableBody createScrollBody() { - scrollBody = new VTreeTableScrollBody(); - return scrollBody; - } - - /* - * Overridden to allow animation of expands and collapses of nodes. - */ - @Override - protected void addAndRemoveRows(UIDL partialRowAdditions) { - if (partialRowAdditions == null) { - return; - } - - if (animationsEnabled && browserSupportsAnimation()) { - if (partialRowAdditions.hasAttribute("hide")) { - scrollBody.unlinkRowsAnimatedAndUpdateCacheWhenFinished( - partialRowAdditions.getIntAttribute("firstprowix"), - partialRowAdditions.getIntAttribute("numprows")); - } else { - scrollBody.insertRowsAnimated(partialRowAdditions, - partialRowAdditions.getIntAttribute("firstprowix"), - partialRowAdditions.getIntAttribute("numprows")); - discardRowsOutsideCacheWindow(); - } - } else { - super.addAndRemoveRows(partialRowAdditions); - } - } - - private boolean browserSupportsAnimation() { - BrowserInfo bi = BrowserInfo.get(); - return !(bi.isSafari4()); - } - - class VTreeTableScrollBody extends VScrollTable.VScrollTableBody { - private int identWidth = -1; - - VTreeTableScrollBody() { - super(); - } - - @Override - protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) { - if (uidl.hasAttribute("gen_html")) { - // This is a generated row. - return new VTreeTableGeneratedRow(uidl, aligns2); - } - return new VTreeTableRow(uidl, aligns2); - } - - class VTreeTableRow extends - VScrollTable.VScrollTableBody.VScrollTableRow { - - private boolean isTreeCellAdded = false; - private SpanElement treeSpacer; - private boolean open; - private int depth; - private boolean canHaveChildren; - protected Widget widgetInHierarchyColumn; - - public VTreeTableRow(UIDL uidl, char[] aligns2) { - super(uidl, aligns2); - } - - @Override - public void addCell(UIDL rowUidl, String text, char align, - String style, boolean textIsHTML, boolean isSorted, - String description) { - super.addCell(rowUidl, text, align, style, textIsHTML, - isSorted, description); - - addTreeSpacer(rowUidl); - } - - protected boolean addTreeSpacer(UIDL rowUidl) { - if (cellShowsTreeHierarchy(getElement().getChildCount() - 1)) { - Element container = (Element) getElement().getLastChild() - .getFirstChild(); - - if (rowUidl.hasAttribute("icon")) { - // icons are in first content cell in TreeTable - ImageElement icon = Document.get().createImageElement(); - icon.setClassName("v-icon"); - icon.setAlt("icon"); - icon.setSrc(client.translateVaadinUri(rowUidl - .getStringAttribute("icon"))); - container.insertFirst(icon); - } - - String classname = "v-treetable-treespacer"; - if (rowUidl.getBooleanAttribute("ca")) { - canHaveChildren = true; - open = rowUidl.getBooleanAttribute("open"); - classname += open ? " v-treetable-node-open" - : " v-treetable-node-closed"; - } - - treeSpacer = Document.get().createSpanElement(); - - treeSpacer.setClassName(classname); - container.insertFirst(treeSpacer); - depth = rowUidl.hasAttribute("depth") ? rowUidl - .getIntAttribute("depth") : 0; - setIdent(); - isTreeCellAdded = true; - return true; - } - return false; - } - - private boolean cellShowsTreeHierarchy(int curColIndex) { - if (isTreeCellAdded) { - return false; - } - return curColIndex == colIndexOfHierarchy - + (showRowHeaders ? 1 : 0); - } - - @Override - public void onBrowserEvent(Event event) { - if (event.getEventTarget().cast() == treeSpacer - && treeSpacer.getClassName().contains("node")) { - if (event.getTypeInt() == Event.ONMOUSEUP) { - sendToggleCollapsedUpdate(getKey()); - } - return; - } - super.onBrowserEvent(event); - } - - @Override - public void addCell(UIDL rowUidl, Widget w, char align, - String style, boolean isSorted) { - super.addCell(rowUidl, w, align, style, isSorted); - if (addTreeSpacer(rowUidl)) { - widgetInHierarchyColumn = w; - } - - } - - private void setIdent() { - if (getIdentWidth() > 0 && depth != 0) { - treeSpacer.getStyle().setWidth( - (depth + 1) * getIdentWidth(), Unit.PX); - } - } - - @Override - protected void onAttach() { - super.onAttach(); - if (getIdentWidth() < 0) { - detectIdent(this); - } - } - - private int getHierarchyAndIconWidth() { - int consumedSpace = treeSpacer.getOffsetWidth(); - if (treeSpacer.getParentElement().getChildCount() > 2) { - // icon next to tree spacer - consumedSpace += ((com.google.gwt.dom.client.Element) treeSpacer - .getNextSibling()).getOffsetWidth(); - } - return consumedSpace; - } - - } - - protected class VTreeTableGeneratedRow extends VTreeTableRow { - private boolean spanColumns; - private boolean htmlContentAllowed; - - public VTreeTableGeneratedRow(UIDL uidl, char[] aligns) { - super(uidl, aligns); - addStyleName("v-table-generated-row"); - } - - public boolean isSpanColumns() { - return spanColumns; - } - - @Override - protected void initCellWidths() { - if (spanColumns) { - setSpannedColumnWidthAfterDOMFullyInited(); - } else { - super.initCellWidths(); - } - } - - private void setSpannedColumnWidthAfterDOMFullyInited() { - // Defer setting width on spanned columns to make sure that - // they are added to the DOM before trying to calculate - // widths. - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - - public void execute() { - if (showRowHeaders) { - setCellWidth(0, tHead.getHeaderCell(0).getWidth()); - calcAndSetSpanWidthOnCell(1); - } else { - calcAndSetSpanWidthOnCell(0); - } - } - }); - } - - @Override - protected boolean isRenderHtmlInCells() { - return htmlContentAllowed; - } - - @Override - protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col, - int visibleColumnIndex) { - htmlContentAllowed = uidl.getBooleanAttribute("gen_html"); - spanColumns = uidl.getBooleanAttribute("gen_span"); - - final Iterator cells = uidl.getChildIterator(); - if (spanColumns) { - int colCount = uidl.getChildCount(); - if (cells.hasNext()) { - final Object cell = cells.next(); - if (cell instanceof String) { - addSpannedCell(uidl, cell.toString(), aligns[0], - "", htmlContentAllowed, false, null, - colCount); - } else { - addSpannedCell(uidl, (Widget) cell, aligns[0], "", - false, colCount); - } - } - } else { - super.addCellsFromUIDL(uidl, aligns, col, - visibleColumnIndex); - } - } - - private void addSpannedCell(UIDL rowUidl, Widget w, char align, - String style, boolean sorted, int colCount) { - TableCellElement td = DOM.createTD().cast(); - td.setColSpan(colCount); - initCellWithWidget(w, align, style, sorted, td); - td.getStyle().setHeight(getRowHeight(), Unit.PX); - if (addTreeSpacer(rowUidl)) { - widgetInHierarchyColumn = w; - } - } - - private void addSpannedCell(UIDL rowUidl, String text, char align, - String style, boolean textIsHTML, boolean sorted, - String description, int colCount) { - // String only content is optimized by not using Label widget - final TableCellElement td = DOM.createTD().cast(); - td.setColSpan(colCount); - initCellWithText(text, align, style, textIsHTML, sorted, - description, td); - td.getStyle().setHeight(getRowHeight(), Unit.PX); - addTreeSpacer(rowUidl); - } - - @Override - protected void setCellWidth(int cellIx, int width) { - if (isSpanColumns()) { - if (showRowHeaders) { - if (cellIx == 0) { - super.setCellWidth(0, width); - } else { - // We need to recalculate the spanning TDs width for - // every cellIx in order to support column resizing. - calcAndSetSpanWidthOnCell(1); - } - } else { - // Same as above. - calcAndSetSpanWidthOnCell(0); - } - } else { - super.setCellWidth(cellIx, width); - } - } - - private void calcAndSetSpanWidthOnCell(final int cellIx) { - int spanWidth = 0; - for (int ix = (showRowHeaders ? 1 : 0); ix < tHead - .getVisibleCellCount(); ix++) { - spanWidth += tHead.getHeaderCell(ix).getOffsetWidth(); - } - Util.setWidthExcludingPaddingAndBorder((Element) getElement() - .getChild(cellIx), spanWidth, 13, false); - } - } - - private int getIdentWidth() { - return identWidth; - } - - private void detectIdent(VTreeTableRow vTreeTableRow) { - identWidth = vTreeTableRow.treeSpacer.getOffsetWidth(); - if (identWidth == 0) { - identWidth = -1; - return; - } - Iterator iterator = iterator(); - while (iterator.hasNext()) { - VTreeTableRow next = (VTreeTableRow) iterator.next(); - next.setIdent(); - } - } - - protected void unlinkRowsAnimatedAndUpdateCacheWhenFinished( - final int firstIndex, final int rows) { - List rowsToDelete = new ArrayList(); - for (int ix = firstIndex; ix < firstIndex + rows; ix++) { - VScrollTableRow row = getRowByRowIndex(ix); - if (row != null) { - rowsToDelete.add(row); - } - } - RowCollapseAnimation anim = new RowCollapseAnimation(rowsToDelete) { - @Override - protected void onComplete() { - super.onComplete(); - // Actually unlink the rows and update the cache after the - // animation is done. - unlinkAndReindexRows(firstIndex, rows); - discardRowsOutsideCacheWindow(); - ensureCacheFilled(); - } - }; - anim.run(150); - } - - protected List insertRowsAnimated(UIDL rowData, - int firstIndex, int rows) { - List insertedRows = insertAndReindexRows(rowData, - firstIndex, rows); - RowExpandAnimation anim = new RowExpandAnimation(insertedRows); - anim.run(150); - return insertedRows; - } - - /** - * Prepares the table for animation by copying the background colors of - * all TR elements to their respective TD elements if the TD element is - * transparent. This is needed, since if TDs have transparent - * backgrounds, the rows sliding behind them are visible. - */ - private class AnimationPreparator { - private final int lastItemIx; - - public AnimationPreparator(int lastItemIx) { - this.lastItemIx = lastItemIx; - } - - public void prepareTableForAnimation() { - int ix = lastItemIx; - VScrollTableRow row = null; - while ((row = getRowByRowIndex(ix)) != null) { - copyTRBackgroundsToTDs(row); - --ix; - } - } - - private void copyTRBackgroundsToTDs(VScrollTableRow row) { - Element tr = row.getElement(); - ComputedStyle cs = new ComputedStyle(tr); - String backgroundAttachment = cs - .getProperty("backgroundAttachment"); - String backgroundClip = cs.getProperty("backgroundClip"); - String backgroundColor = cs.getProperty("backgroundColor"); - String backgroundImage = cs.getProperty("backgroundImage"); - String backgroundOrigin = cs.getProperty("backgroundOrigin"); - for (int ix = 0; ix < tr.getChildCount(); ix++) { - Element td = tr.getChild(ix).cast(); - if (!elementHasBackground(td)) { - td.getStyle().setProperty("backgroundAttachment", - backgroundAttachment); - td.getStyle().setProperty("backgroundClip", - backgroundClip); - td.getStyle().setProperty("backgroundColor", - backgroundColor); - td.getStyle().setProperty("backgroundImage", - backgroundImage); - td.getStyle().setProperty("backgroundOrigin", - backgroundOrigin); - } - } - } - - private boolean elementHasBackground(Element element) { - ComputedStyle cs = new ComputedStyle(element); - String clr = cs.getProperty("backgroundColor"); - String img = cs.getProperty("backgroundImage"); - return !("rgba(0, 0, 0, 0)".equals(clr.trim()) - || "transparent".equals(clr.trim()) || img == null); - } - - public void restoreTableAfterAnimation() { - int ix = lastItemIx; - VScrollTableRow row = null; - while ((row = getRowByRowIndex(ix)) != null) { - restoreStyleForTDsInRow(row); - - --ix; - } - } - - private void restoreStyleForTDsInRow(VScrollTableRow row) { - Element tr = row.getElement(); - for (int ix = 0; ix < tr.getChildCount(); ix++) { - Element td = tr.getChild(ix).cast(); - td.getStyle().clearProperty("backgroundAttachment"); - td.getStyle().clearProperty("backgroundClip"); - td.getStyle().clearProperty("backgroundColor"); - td.getStyle().clearProperty("backgroundImage"); - td.getStyle().clearProperty("backgroundOrigin"); - } - } - } - - /** - * Animates row expansion using the GWT animation framework. - * - * The idea is as follows: - * - * 1. Insert all rows normally - * - * 2. Insert a newly created DIV containing a new TABLE element below - * the DIV containing the actual scroll table body. - * - * 3. Clone the rows that were inserted in step 1 and attach the clones - * to the new TABLE element created in step 2. - * - * 4. The new DIV from step 2 is absolutely positioned so that the last - * inserted row is just behind the row that was expanded. - * - * 5. Hide the contents of the originally inserted rows by setting the - * DIV.v-table-cell-wrapper to display:none;. - * - * 6. Set the height of the originally inserted rows to 0. - * - * 7. The animation loop slides the DIV from step 2 downwards, while at - * the same pace growing the height of each of the inserted rows from 0 - * to full height. The first inserted row grows from 0 to full and after - * this the second row grows from 0 to full, etc until all rows are full - * height. - * - * 8. Remove the DIV from step 2 - * - * 9. Restore display:block; to the DIV.v-table-cell-wrapper elements. - * - * 10. DONE - */ - private class RowExpandAnimation extends Animation { - - private final List rows; - private Element cloneDiv; - private Element cloneTable; - private AnimationPreparator preparator; - - public RowExpandAnimation(List rows) { - this.rows = rows; - buildAndInsertAnimatingDiv(); - preparator = new AnimationPreparator(rows.get(0).getIndex() - 1); - preparator.prepareTableForAnimation(); - for (VScrollTableRow row : rows) { - cloneAndAppendRow(row); - row.addStyleName("v-table-row-animating"); - setCellWrapperDivsToDisplayNone(row); - row.setHeight(getInitialHeight()); - } - } - - protected String getInitialHeight() { - return "0px"; - } - - private void cloneAndAppendRow(VScrollTableRow row) { - Element clonedTR = null; - clonedTR = row.getElement().cloneNode(true).cast(); - clonedTR.getStyle().setVisibility(Visibility.VISIBLE); - cloneTable.appendChild(clonedTR); - } - - protected double getBaseOffset() { - return rows.get(0).getAbsoluteTop() - - rows.get(0).getParent().getAbsoluteTop() - - rows.size() * getRowHeight(); - } - - private void buildAndInsertAnimatingDiv() { - cloneDiv = DOM.createDiv(); - cloneDiv.addClassName("v-treetable-animation-clone-wrapper"); - cloneTable = DOM.createTable(); - cloneTable.addClassName("v-treetable-animation-clone"); - cloneDiv.appendChild(cloneTable); - insertAnimatingDiv(); - } - - private void insertAnimatingDiv() { - Element tableBody = getElement().cast(); - Element tableBodyParent = tableBody.getParentElement().cast(); - tableBodyParent.insertAfter(cloneDiv, tableBody); - } - - @Override - protected void onUpdate(double progress) { - animateDiv(progress); - animateRowHeights(progress); - } - - private void animateDiv(double progress) { - double offset = calculateDivOffset(progress, getRowHeight()); - - cloneDiv.getStyle().setTop(getBaseOffset() + offset, Unit.PX); - } - - private void animateRowHeights(double progress) { - double rh = getRowHeight(); - double vlh = calculateHeightOfAllVisibleLines(progress, rh); - int ix = 0; - - while (ix < rows.size()) { - double height = vlh < rh ? vlh : rh; - rows.get(ix).setHeight(height + "px"); - vlh -= height; - ix++; - } - } - - protected double calculateHeightOfAllVisibleLines(double progress, - double rh) { - return rows.size() * rh * progress; - } - - protected double calculateDivOffset(double progress, double rh) { - return progress * rows.size() * rh; - } - - @Override - protected void onComplete() { - preparator.restoreTableAfterAnimation(); - for (VScrollTableRow row : rows) { - resetCellWrapperDivsDisplayProperty(row); - row.removeStyleName("v-table-row-animating"); - } - Element tableBodyParent = (Element) getElement() - .getParentElement(); - tableBodyParent.removeChild(cloneDiv); - } - - private void setCellWrapperDivsToDisplayNone(VScrollTableRow row) { - Element tr = row.getElement(); - for (int ix = 0; ix < tr.getChildCount(); ix++) { - getWrapperDiv(tr, ix).getStyle().setDisplay(Display.NONE); - } - } - - private Element getWrapperDiv(Element tr, int tdIx) { - Element td = tr.getChild(tdIx).cast(); - return td.getChild(0).cast(); - } - - private void resetCellWrapperDivsDisplayProperty(VScrollTableRow row) { - Element tr = row.getElement(); - for (int ix = 0; ix < tr.getChildCount(); ix++) { - getWrapperDiv(tr, ix).getStyle().clearProperty("display"); - } - } - - } - - /** - * This is the inverse of the RowExpandAnimation and is implemented by - * extending it and overriding the calculation of offsets and heights. - */ - private class RowCollapseAnimation extends RowExpandAnimation { - - private final List rows; - - public RowCollapseAnimation(List rows) { - super(rows); - this.rows = rows; - } - - @Override - protected String getInitialHeight() { - return getRowHeight() + "px"; - } - - @Override - protected double getBaseOffset() { - return getRowHeight(); - } - - @Override - protected double calculateHeightOfAllVisibleLines(double progress, - double rh) { - return rows.size() * rh * (1 - progress); - } - - @Override - protected double calculateDivOffset(double progress, double rh) { - return -super.calculateDivOffset(progress, rh); - } - } - } - - /** - * Icons rendered into first actual column in TreeTable, not to row header - * cell - */ - @Override - protected String buildCaptionHtmlSnippet(UIDL uidl) { - if (uidl.getTag().equals("column")) { - return super.buildCaptionHtmlSnippet(uidl); - } else { - String s = uidl.getStringAttribute("caption"); - return s; - } - } - - @Override - protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { - if (collapseRequest || focusParentResponsePending) { - // Enqueue the event if there might be pending content changes from - // the server - if (pendingNavigationEvents.size() < 10) { - // Only keep 10 keyboard events in the queue - PendingNavigationEvent pendingNavigationEvent = new PendingNavigationEvent( - keycode, ctrl, shift); - pendingNavigationEvents.add(pendingNavigationEvent); - } - return true; - } - - VTreeTableRow focusedRow = (VTreeTableRow) getFocusedRow(); - if (focusedRow != null) { - if (focusedRow.canHaveChildren - && ((keycode == KeyCodes.KEY_RIGHT && !focusedRow.open) || (keycode == KeyCodes.KEY_LEFT && focusedRow.open))) { - if (!ctrl) { - client.updateVariable(paintableId, "selectCollapsed", true, - false); - } - sendSelectedRows(false); - sendToggleCollapsedUpdate(focusedRow.getKey()); - return true; - } else if (keycode == KeyCodes.KEY_RIGHT && focusedRow.open) { - // already expanded, move selection down if next is on a deeper - // level (is-a-child) - VTreeTableScrollBody body = (VTreeTableScrollBody) focusedRow - .getParent(); - Iterator iterator = body.iterator(); - VTreeTableRow next = null; - while (iterator.hasNext()) { - next = (VTreeTableRow) iterator.next(); - if (next == focusedRow) { - next = (VTreeTableRow) iterator.next(); - break; - } - } - if (next != null) { - if (next.depth > focusedRow.depth) { - selectionPending = true; - return super.handleNavigation(getNavigationDownKey(), - ctrl, shift); - } - } else { - // Note, a minor change here for a bit false behavior if - // cache rows is disabled + last visible row + no childs for - // the node - selectionPending = true; - return super.handleNavigation(getNavigationDownKey(), ctrl, - shift); - } - } else if (keycode == KeyCodes.KEY_LEFT) { - // already collapsed move selection up to parent node - // do on the server side as the parent is not necessary - // rendered on the client, could check if parent is visible if - // a performance issue arises - - client.updateVariable(paintableId, "focusParent", - focusedRow.getKey(), true); - - // Set flag that we should enqueue navigation events until we - // get a response to this request - focusParentResponsePending = true; - - return true; - } - } - return super.handleNavigation(keycode, ctrl, shift); - } - - private void sendToggleCollapsedUpdate(String rowKey) { - collapsedRowKey = rowKey; - collapseRequest = true; - client.updateVariable(paintableId, "toggleCollapsed", rowKey, true); - } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (event.getTypeInt() == Event.ONKEYUP && selectionPending) { - sendSelectedRows(); - } - } - - @Override - protected void sendSelectedRows(boolean immediately) { - super.sendSelectedRows(immediately); - selectionPending = false; - } - - @Override - protected void reOrderColumn(String columnKey, int newIndex) { - super.reOrderColumn(columnKey, newIndex); - // current impl not intelligent enough to survive without visiting the - // server to redraw content - client.sendPendingVariableChanges(); - } - - @Override - public void setStyleName(String style) { - super.setStyleName(style + " v-treetable"); - } - - @Override - protected void updateTotalRows(UIDL uidl) { - // Make sure that initializedAndAttached & al are not reset when the - // totalrows are updated on expand/collapse requests. - int newTotalRows = uidl.getIntAttribute("totalrows"); - setTotalRows(newTotalRows); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTwinColSelect.java b/src/com/vaadin/terminal/gwt/client/ui/VTwinColSelect.java deleted file mode 100644 index 7f12a04574..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VTwinColSelect.java +++ /dev/null @@ -1,597 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.DoubleClickEvent; -import com.google.gwt.event.dom.client.DoubleClickHandler; -import com.google.gwt.event.dom.client.HasDoubleClickHandlers; -import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyDownEvent; -import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.MouseDownEvent; -import com.google.gwt.event.dom.client.MouseDownHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.ListBox; -import com.google.gwt.user.client.ui.Panel; -import com.vaadin.terminal.gwt.client.UIDL; -import com.vaadin.terminal.gwt.client.Util; - -public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, - MouseDownHandler, DoubleClickHandler, SubPartAware { - - private static final String CLASSNAME = "v-select-twincol"; - public static final String ATTRIBUTE_LEFT_CAPTION = "lc"; - public static final String ATTRIBUTE_RIGHT_CAPTION = "rc"; - - private static final int VISIBLE_COUNT = 10; - - private static final int DEFAULT_COLUMN_COUNT = 10; - - private final DoubleClickListBox options; - - private final DoubleClickListBox selections; - - FlowPanel captionWrapper; - - private HTML optionsCaption = null; - - private HTML selectionsCaption = null; - - private final VButton add; - - private final VButton remove; - - private final FlowPanel buttons; - - private final Panel panel; - - /** - * A ListBox which catches double clicks - * - */ - public class DoubleClickListBox extends ListBox implements - HasDoubleClickHandlers { - public DoubleClickListBox(boolean isMultipleSelect) { - super(isMultipleSelect); - } - - public DoubleClickListBox() { - super(); - } - - @Override - public HandlerRegistration addDoubleClickHandler( - DoubleClickHandler handler) { - return addDomHandler(handler, DoubleClickEvent.getType()); - } - } - - public VTwinColSelect() { - super(CLASSNAME); - - captionWrapper = new FlowPanel(); - - options = new DoubleClickListBox(); - options.addClickHandler(this); - options.addDoubleClickHandler(this); - options.setVisibleItemCount(VISIBLE_COUNT); - options.setStyleName(CLASSNAME + "-options"); - - selections = new DoubleClickListBox(); - selections.addClickHandler(this); - selections.addDoubleClickHandler(this); - selections.setVisibleItemCount(VISIBLE_COUNT); - selections.setStyleName(CLASSNAME + "-selections"); - - buttons = new FlowPanel(); - buttons.setStyleName(CLASSNAME + "-buttons"); - add = new VButton(); - add.setText(">>"); - add.addClickHandler(this); - remove = new VButton(); - remove.setText("<<"); - remove.addClickHandler(this); - - panel = ((Panel) optionsContainer); - - panel.add(captionWrapper); - captionWrapper.getElement().getStyle().setOverflow(Overflow.HIDDEN); - // Hide until there actually is a caption to prevent IE from rendering - // extra empty space - captionWrapper.setVisible(false); - - panel.add(options); - buttons.add(add); - final HTML br = new HTML(""); - br.setStyleName(CLASSNAME + "-deco"); - buttons.add(br); - buttons.add(remove); - panel.add(buttons); - panel.add(selections); - - options.addKeyDownHandler(this); - options.addMouseDownHandler(this); - - selections.addMouseDownHandler(this); - selections.addKeyDownHandler(this); - } - - public HTML getOptionsCaption() { - if (optionsCaption == null) { - optionsCaption = new HTML(); - optionsCaption.setStyleName(CLASSNAME + "-caption-left"); - optionsCaption.getElement().getStyle() - .setFloat(com.google.gwt.dom.client.Style.Float.LEFT); - captionWrapper.add(optionsCaption); - } - - return optionsCaption; - } - - public HTML getSelectionsCaption() { - if (selectionsCaption == null) { - selectionsCaption = new HTML(); - selectionsCaption.setStyleName(CLASSNAME + "-caption-right"); - selectionsCaption.getElement().getStyle() - .setFloat(com.google.gwt.dom.client.Style.Float.RIGHT); - captionWrapper.add(selectionsCaption); - } - - return selectionsCaption; - } - - protected void updateCaptions(UIDL uidl) { - String leftCaption = (uidl.hasAttribute(ATTRIBUTE_LEFT_CAPTION) ? uidl - .getStringAttribute(ATTRIBUTE_LEFT_CAPTION) : null); - String rightCaption = (uidl.hasAttribute(ATTRIBUTE_RIGHT_CAPTION) ? uidl - .getStringAttribute(ATTRIBUTE_RIGHT_CAPTION) : null); - - boolean hasCaptions = (leftCaption != null || rightCaption != null); - - if (leftCaption == null) { - removeOptionsCaption(); - } else { - getOptionsCaption().setText(leftCaption); - - } - - if (rightCaption == null) { - removeSelectionsCaption(); - } else { - getSelectionsCaption().setText(rightCaption); - } - - captionWrapper.setVisible(hasCaptions); - } - - private void removeOptionsCaption() { - if (optionsCaption == null) { - return; - } - - if (optionsCaption.getParent() != null) { - captionWrapper.remove(optionsCaption); - } - - optionsCaption = null; - } - - private void removeSelectionsCaption() { - if (selectionsCaption == null) { - return; - } - - if (selectionsCaption.getParent() != null) { - captionWrapper.remove(selectionsCaption); - } - - selectionsCaption = null; - } - - @Override - protected void buildOptions(UIDL uidl) { - final boolean enabled = !isDisabled() && !isReadonly(); - options.setMultipleSelect(isMultiselect()); - selections.setMultipleSelect(isMultiselect()); - options.setEnabled(enabled); - selections.setEnabled(enabled); - add.setEnabled(enabled); - remove.setEnabled(enabled); - options.clear(); - selections.clear(); - for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { - final UIDL optionUidl = (UIDL) i.next(); - if (optionUidl.hasAttribute("selected")) { - selections.addItem(optionUidl.getStringAttribute("caption"), - optionUidl.getStringAttribute("key")); - } else { - options.addItem(optionUidl.getStringAttribute("caption"), - optionUidl.getStringAttribute("key")); - } - } - - if (getRows() > 0) { - options.setVisibleItemCount(getRows()); - selections.setVisibleItemCount(getRows()); - - } - - } - - @Override - protected String[] getSelectedItems() { - final ArrayList selectedItemKeys = new ArrayList(); - for (int i = 0; i < selections.getItemCount(); i++) { - selectedItemKeys.add(selections.getValue(i)); - } - return selectedItemKeys.toArray(new String[selectedItemKeys.size()]); - } - - private boolean[] getSelectionBitmap(ListBox listBox) { - final boolean[] selectedIndexes = new boolean[listBox.getItemCount()]; - for (int i = 0; i < listBox.getItemCount(); i++) { - if (listBox.isItemSelected(i)) { - selectedIndexes[i] = true; - } else { - selectedIndexes[i] = false; - } - } - return selectedIndexes; - } - - private void addItem() { - Set movedItems = moveSelectedItems(options, selections); - selectedKeys.addAll(movedItems); - - client.updateVariable(paintableId, "selected", - selectedKeys.toArray(new String[selectedKeys.size()]), - isImmediate()); - } - - private void removeItem() { - Set movedItems = moveSelectedItems(selections, options); - selectedKeys.removeAll(movedItems); - - client.updateVariable(paintableId, "selected", - selectedKeys.toArray(new String[selectedKeys.size()]), - isImmediate()); - } - - private Set moveSelectedItems(ListBox source, ListBox target) { - final boolean[] sel = getSelectionBitmap(source); - final Set movedItems = new HashSet(); - int lastSelected = 0; - for (int i = 0; i < sel.length; i++) { - if (sel[i]) { - final int optionIndex = i - - (sel.length - source.getItemCount()); - movedItems.add(source.getValue(optionIndex)); - - // Move selection to another column - final String text = source.getItemText(optionIndex); - final String value = source.getValue(optionIndex); - target.addItem(text, value); - target.setItemSelected(target.getItemCount() - 1, true); - source.removeItem(optionIndex); - - if (source.getItemCount() > 0) { - lastSelected = optionIndex > 0 ? optionIndex - 1 : 0; - } - } - } - - if (source.getItemCount() > 0) { - source.setSelectedIndex(lastSelected); - } - - // If no items are left move the focus to the selections - if (source.getItemCount() == 0) { - target.setFocus(true); - } else { - source.setFocus(true); - } - - return movedItems; - } - - @Override - public void onClick(ClickEvent event) { - super.onClick(event); - if (event.getSource() == add) { - addItem(); - - } else if (event.getSource() == remove) { - removeItem(); - - } else if (event.getSource() == options) { - // unselect all in other list, to avoid mistakes (i.e wrong button) - final int c = selections.getItemCount(); - for (int i = 0; i < c; i++) { - selections.setItemSelected(i, false); - } - } else if (event.getSource() == selections) { - // unselect all in other list, to avoid mistakes (i.e wrong button) - final int c = options.getItemCount(); - for (int i = 0; i < c; i++) { - options.setItemSelected(i, false); - } - } - } - - void clearInternalHeights() { - selections.setHeight(""); - options.setHeight(""); - } - - void setInternalHeights() { - int captionHeight = Util.getRequiredHeight(captionWrapper); - int totalHeight = getOffsetHeight(); - - String selectHeight = (totalHeight - captionHeight) + "px"; - - selections.setHeight(selectHeight); - options.setHeight(selectHeight); - - } - - void clearInternalWidths() { - int cols = -1; - if (getColumns() > 0) { - cols = getColumns(); - } else { - cols = DEFAULT_COLUMN_COUNT; - } - - if (cols >= 0) { - String colWidth = cols + "em"; - String containerWidth = (2 * cols + 4) + "em"; - // Caption wrapper width == optionsSelect + buttons + - // selectionsSelect - String captionWrapperWidth = (2 * cols + 4 - 0.5) + "em"; - - options.setWidth(colWidth); - if (optionsCaption != null) { - optionsCaption.setWidth(colWidth); - } - selections.setWidth(colWidth); - if (selectionsCaption != null) { - selectionsCaption.setWidth(colWidth); - } - buttons.setWidth("3.5em"); - optionsContainer.setWidth(containerWidth); - captionWrapper.setWidth(captionWrapperWidth); - } - } - - void setInternalWidths() { - DOM.setStyleAttribute(getElement(), "position", "relative"); - int bordersAndPaddings = Util.measureHorizontalPaddingAndBorder( - buttons.getElement(), 0); - - int buttonWidth = Util.getRequiredWidth(buttons); - int totalWidth = getOffsetWidth(); - - int spaceForSelect = (totalWidth - buttonWidth - bordersAndPaddings) / 2; - - options.setWidth(spaceForSelect + "px"); - if (optionsCaption != null) { - optionsCaption.setWidth(spaceForSelect + "px"); - } - - selections.setWidth(spaceForSelect + "px"); - if (selectionsCaption != null) { - selectionsCaption.setWidth(spaceForSelect + "px"); - } - captionWrapper.setWidth("100%"); - } - - @Override - protected void setTabIndex(int tabIndex) { - options.setTabIndex(tabIndex); - selections.setTabIndex(tabIndex); - add.setTabIndex(tabIndex); - remove.setTabIndex(tabIndex); - } - - public void focus() { - options.setFocus(true); - } - - /** - * Get the key that selects an item in the table. By default it is the Enter - * key but by overriding this you can change the key to whatever you want. - * - * @return - */ - protected int getNavigationSelectKey() { - return KeyCodes.KEY_ENTER; - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt - * .event.dom.client.KeyDownEvent) - */ - public void onKeyDown(KeyDownEvent event) { - int keycode = event.getNativeKeyCode(); - - // Catch tab and move between select:s - if (keycode == KeyCodes.KEY_TAB && event.getSource() == options) { - // Prevent default behavior - event.preventDefault(); - - // Remove current selections - for (int i = 0; i < options.getItemCount(); i++) { - options.setItemSelected(i, false); - } - - // Focus selections - selections.setFocus(true); - } - - if (keycode == KeyCodes.KEY_TAB && event.isShiftKeyDown() - && event.getSource() == selections) { - // Prevent default behavior - event.preventDefault(); - - // Remove current selections - for (int i = 0; i < selections.getItemCount(); i++) { - selections.setItemSelected(i, false); - } - - // Focus options - options.setFocus(true); - } - - if (keycode == getNavigationSelectKey()) { - // Prevent default behavior - event.preventDefault(); - - // Decide which select the selection was made in - if (event.getSource() == options) { - // Prevents the selection to become a single selection when - // using Enter key - // as the selection key (default) - options.setFocus(false); - - addItem(); - - } else if (event.getSource() == selections) { - // Prevents the selection to become a single selection when - // using Enter key - // as the selection key (default) - selections.setFocus(false); - - removeItem(); - } - } - - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google - * .gwt.event.dom.client.MouseDownEvent) - */ - public void onMouseDown(MouseDownEvent event) { - // Ensure that items are deselected when selecting - // from a different source. See #3699 for details. - if (event.getSource() == options) { - for (int i = 0; i < selections.getItemCount(); i++) { - selections.setItemSelected(i, false); - } - } else if (event.getSource() == selections) { - for (int i = 0; i < options.getItemCount(); i++) { - options.setItemSelected(i, false); - } - } - - } - - /* - * (non-Javadoc) - * - * @see - * com.google.gwt.event.dom.client.DoubleClickHandler#onDoubleClick(com. - * google.gwt.event.dom.client.DoubleClickEvent) - */ - public void onDoubleClick(DoubleClickEvent event) { - if (event.getSource() == options) { - addItem(); - options.setSelectedIndex(-1); - options.setFocus(false); - } else if (event.getSource() == selections) { - removeItem(); - selections.setSelectedIndex(-1); - selections.setFocus(false); - } - - } - - private static final String SUBPART_OPTION_SELECT = "leftSelect"; - private static final String SUBPART_OPTION_SELECT_ITEM = SUBPART_OPTION_SELECT - + "-item"; - private static final String SUBPART_SELECTION_SELECT = "rightSelect"; - private static final String SUBPART_SELECTION_SELECT_ITEM = SUBPART_SELECTION_SELECT - + "-item"; - private static final String SUBPART_LEFT_CAPTION = "leftCaption"; - private static final String SUBPART_RIGHT_CAPTION = "rightCaption"; - private static final String SUBPART_ADD_BUTTON = "add"; - private static final String SUBPART_REMOVE_BUTTON = "remove"; - - public Element getSubPartElement(String subPart) { - if (SUBPART_OPTION_SELECT.equals(subPart)) { - return options.getElement(); - } else if (subPart.startsWith(SUBPART_OPTION_SELECT_ITEM)) { - String idx = subPart.substring(SUBPART_OPTION_SELECT_ITEM.length()); - return (Element) options.getElement().getChild( - Integer.parseInt(idx)); - } else if (SUBPART_SELECTION_SELECT.equals(subPart)) { - return selections.getElement(); - } else if (subPart.startsWith(SUBPART_SELECTION_SELECT_ITEM)) { - String idx = subPart.substring(SUBPART_SELECTION_SELECT_ITEM - .length()); - return (Element) selections.getElement().getChild( - Integer.parseInt(idx)); - } else if (optionsCaption != null - && SUBPART_LEFT_CAPTION.equals(subPart)) { - return optionsCaption.getElement(); - } else if (selectionsCaption != null - && SUBPART_RIGHT_CAPTION.equals(subPart)) { - return selectionsCaption.getElement(); - } else if (SUBPART_ADD_BUTTON.equals(subPart)) { - return add.getElement(); - } else if (SUBPART_REMOVE_BUTTON.equals(subPart)) { - return remove.getElement(); - } - - return null; - } - - public String getSubPartName(Element subElement) { - if (optionsCaption != null - && optionsCaption.getElement().isOrHasChild(subElement)) { - return SUBPART_LEFT_CAPTION; - } else if (selectionsCaption != null - && selectionsCaption.getElement().isOrHasChild(subElement)) { - return SUBPART_RIGHT_CAPTION; - } else if (options.getElement().isOrHasChild(subElement)) { - if (options.getElement() == subElement) { - return SUBPART_OPTION_SELECT; - } else { - int idx = Util.getChildElementIndex(subElement); - return SUBPART_OPTION_SELECT_ITEM + idx; - } - } else if (selections.getElement().isOrHasChild(subElement)) { - if (selections.getElement() == subElement) { - return SUBPART_SELECTION_SELECT; - } else { - int idx = Util.getChildElementIndex(subElement); - return SUBPART_SELECTION_SELECT_ITEM + idx; - } - } else if (add.getElement().isOrHasChild(subElement)) { - return SUBPART_ADD_BUTTON; - } else if (remove.getElement().isOrHasChild(subElement)) { - return SUBPART_REMOVE_BUTTON; - } - - return null; - } -} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VUpload.java b/src/com/vaadin/terminal/gwt/client/ui/VUpload.java deleted file mode 100644 index f7fb5878d5..0000000000 --- a/src/com/vaadin/terminal/gwt/client/ui/VUpload.java +++ /dev/null @@ -1,315 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal.gwt.client.ui; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.FormElement; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.ui.FileUpload; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.FormPanel; -import com.google.gwt.user.client.ui.Hidden; -import com.google.gwt.user.client.ui.Panel; -import com.google.gwt.user.client.ui.SimplePanel; -import com.vaadin.terminal.gwt.client.ApplicationConnection; -import com.vaadin.terminal.gwt.client.BrowserInfo; -import com.vaadin.terminal.gwt.client.VConsole; -import com.vaadin.terminal.gwt.client.VTooltip; - -/** - * - * Note, we are not using GWT FormPanel as we want to listen submitcomplete - * events even though the upload component is already detached. - * - */ -public class VUpload extends SimplePanel { - - private final class MyFileUpload extends FileUpload { - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - if (event.getTypeInt() == Event.ONCHANGE) { - if (immediate && fu.getFilename() != null - && !"".equals(fu.getFilename())) { - submit(); - } - } else if (BrowserInfo.get().isIE() - && event.getTypeInt() == Event.ONFOCUS) { - // IE and user has clicked on hidden textarea part of upload - // field. Manually open file selector, other browsers do it by - // default. - fireNativeClick(fu.getElement()); - // also remove focus to enable hack if user presses cancel - // button - fireNativeBlur(fu.getElement()); - } - } - } - - public static final String CLASSNAME = "v-upload"; - - /** - * FileUpload component that opens native OS dialog to select file. - */ - FileUpload fu = new MyFileUpload(); - - Panel panel = new FlowPanel(); - - UploadIFrameOnloadStrategy onloadstrategy = GWT - .create(UploadIFrameOnloadStrategy.class); - - ApplicationConnection client; - - protected String paintableId; - - /** - * Button that initiates uploading - */ - protected final VButton submitButton; - - /** - * When expecting big files, programmer may initiate some UI changes when - * uploading the file starts. Bit after submitting file we'll visit the - * server to check possible changes. - */ - protected Timer t; - - /** - * some browsers tries to send form twice if submit is called in button - * click handler, some don't submit at all without it, so we need to track - * if form is already being submitted - */ - private boolean submitted = false; - - private boolean enabled = true; - - private boolean immediate; - - private Hidden maxfilesize = new Hidden(); - - protected FormElement element; - - private com.google.gwt.dom.client.Element synthesizedFrame; - - protected int nextUploadId; - - public VUpload() { - super(com.google.gwt.dom.client.Document.get().createFormElement()); - - element = getElement().cast(); - setEncoding(getElement(), FormPanel.ENCODING_MULTIPART); - element.setMethod(FormPanel.METHOD_POST); - - setWidget(panel); - panel.add(maxfilesize); - panel.add(fu); - submitButton = new VButton(); - submitButton.addClickHandler(new ClickHandler() { - public void onClick(ClickEvent event) { - if (immediate) { - // fire click on upload (eg. focused button and hit space) - fireNativeClick(fu.getElement()); - } else { - submit(); - } - } - }); - panel.add(submitButton); - - setStyleName(CLASSNAME); - - sinkEvents(VTooltip.TOOLTIP_EVENTS); - } - - @Override - public void onBrowserEvent(Event event) { - if ((event.getTypeInt() & VTooltip.TOOLTIP_EVENTS) > 0) { - client.handleTooltipEvent(event, this); - } - super.onBrowserEvent(event); - } - - private static native void setEncoding(Element form, String encoding) - /*-{ - form.enctype = encoding; - }-*/; - - protected void setImmediate(boolean booleanAttribute) { - if (immediate != booleanAttribute) { - immediate = booleanAttribute; - if (immediate) { - fu.sinkEvents(Event.ONCHANGE); - fu.sinkEvents(Event.ONFOCUS); - } - } - setStyleName(getElement(), CLASSNAME + "-immediate", immediate); - } - - private static native void fireNativeClick(Element element) - /*-{ - element.click(); - }-*/; - - private static native void fireNativeBlur(Element element) - /*-{ - element.blur(); - }-*/; - - protected void disableUpload() { - submitButton.setEnabled(false); - if (!submitted) { - // Cannot disable the fileupload while submitting or the file won't - // be submitted at all - fu.getElement().setPropertyBoolean("disabled", true); - } - enabled = false; - } - - protected void enableUpload() { - submitButton.setEnabled(true); - fu.getElement().setPropertyBoolean("disabled", false); - enabled = true; - if (submitted) { - /* - * An old request is still in progress (most likely cancelled), - * ditching that target frame to make it possible to send a new - * file. A new target frame is created later." - */ - cleanTargetFrame(); - submitted = false; - } - } - - /** - * Re-creates file input field and populates panel. This is needed as we - * want to clear existing values from our current file input field. - */ - private void rebuildPanel() { - panel.remove(submitButton); - panel.remove(fu); - fu = new MyFileUpload(); - fu.setName(paintableId + "_file"); - fu.getElement().setPropertyBoolean("disabled", !enabled); - panel.add(fu); - panel.add(submitButton); - if (immediate) { - fu.sinkEvents(Event.ONCHANGE); - } - } - - /** - * Called by JSNI (hooked via {@link #onloadstrategy}) - */ - private void onSubmitComplete() { - /* Needs to be run dereferred to avoid various browser issues. */ - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - if (submitted) { - if (client != null) { - if (t != null) { - t.cancel(); - } - VConsole.log("VUpload:Submit complete"); - client.sendPendingVariableChanges(); - } - - rebuildPanel(); - - submitted = false; - enableUpload(); - if (!isAttached()) { - /* - * Upload is complete when upload is already abandoned. - */ - cleanTargetFrame(); - } - } - } - }); - } - - protected void submit() { - if (fu.getFilename().length() == 0 || submitted || !enabled) { - VConsole.log("Submit cancelled (disabled, no file or already submitted)"); - return; - } - // flush possibly pending variable changes, so they will be handled - // before upload - client.sendPendingVariableChanges(); - - element.submit(); - submitted = true; - VConsole.log("Submitted form"); - - disableUpload(); - - /* - * Visit server a moment after upload has started to see possible - * changes from UploadStarted event. Will be cleared on complete. - */ - t = new Timer() { - @Override - public void run() { - VConsole.log("Visiting server to see if upload started event changed UI."); - client.updateVariable(paintableId, "pollForStart", - nextUploadId, true); - } - }; - t.schedule(800); - } - - @Override - protected void onAttach() { - super.onAttach(); - if (client != null) { - ensureTargetFrame(); - } - } - - protected void ensureTargetFrame() { - if (synthesizedFrame == null) { - // Attach a hidden IFrame to the form. This is the target iframe to - // which the form will be submitted. We have to create the iframe - // using innerHTML, because setting an iframe's 'name' property - // dynamically doesn't work on most browsers. - DivElement dummy = Document.get().createDivElement(); - dummy.setInnerHTML(""); + getWidget().browserElement = DOM.getFirstChild(getWidget() + .getElement()); + } + DOM.setElementAttribute(getWidget().browserElement, "src", + getWidget().getSrc(uidl, client)); + clearBrowserElement = false; + } else { + VConsole.log("Unknown Embedded type '" + getWidget().type + "'"); + } + } else if (uidl.hasAttribute("mimetype")) { + final String mime = uidl.getStringAttribute("mimetype"); + if (mime.equals("application/x-shockwave-flash")) { + // Handle embedding of Flash + getWidget().addStyleName(VEmbedded.CLASSNAME + "-flash"); + getWidget().setHTML(getWidget().createFlashEmbed(uidl)); + + } else if (mime.equals("image/svg+xml")) { + getWidget().addStyleName(VEmbedded.CLASSNAME + "-svg"); + String data; + Map parameters = VEmbedded.getParameters(uidl); + if (parameters.get("data") == null) { + data = getWidget().getSrc(uidl, client); + } else { + data = "data:image/svg+xml," + parameters.get("data"); + } + getWidget().setHTML(""); + ObjectElement obj = Document.get().createObjectElement(); + obj.setType(mime); + obj.setData(data); + if (!isUndefinedWidth()) { + obj.getStyle().setProperty("width", "100%"); + } + if (!isUndefinedHeight()) { + obj.getStyle().setProperty("height", "100%"); + } + if (uidl.hasAttribute("classid")) { + obj.setAttribute("classid", + uidl.getStringAttribute("classid")); + } + if (uidl.hasAttribute("codebase")) { + obj.setAttribute("codebase", + uidl.getStringAttribute("codebase")); + } + if (uidl.hasAttribute("codetype")) { + obj.setAttribute("codetype", + uidl.getStringAttribute("codetype")); + } + if (uidl.hasAttribute("archive")) { + obj.setAttribute("archive", + uidl.getStringAttribute("archive")); + } + if (uidl.hasAttribute("standby")) { + obj.setAttribute("standby", + uidl.getStringAttribute("standby")); + } + getWidget().getElement().appendChild(obj); + if (uidl.hasAttribute(ALTERNATE_TEXT)) { + obj.setInnerText(uidl.getStringAttribute(ALTERNATE_TEXT)); + } + } else { + VConsole.log("Unknown Embedded mimetype '" + mime + "'"); + } + } else { + VConsole.log("Unknown Embedded; no type or mimetype attribute"); + } + + if (clearBrowserElement) { + getWidget().browserElement = null; + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VEmbedded.class); + } + + @Override + public VEmbedded getWidget() { + return (VEmbedded) super.getWidget(); + } + + protected final ClickEventHandler clickEventHandler = new ClickEventHandler( + this) { + + @Override + protected void fireClick(NativeEvent event, + MouseEventDetails mouseDetails) { + rpc.click(mouseDetails); + } + + }; + +} \ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/embedded/VEmbedded.java b/src/com/vaadin/terminal/gwt/client/ui/embedded/VEmbedded.java new file mode 100644 index 0000000000..203e7362f3 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/embedded/VEmbedded.java @@ -0,0 +1,239 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.embedded; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +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.HTML; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VConsole; + +public class VEmbedded extends HTML { + public static String CLASSNAME = "v-embedded"; + + protected Element browserElement; + + protected String type; + + protected ApplicationConnection client; + + public VEmbedded() { + setStyleName(CLASSNAME); + } + + /** + * Creates the Object and Embed tags for the Flash plugin so it works + * cross-browser + * + * @param uidl + * The UIDL + * @return Tags concatenated into a string + */ + protected String createFlashEmbed(UIDL uidl) { + /* + * To ensure cross-browser compatibility we are using the twice-cooked + * method to embed flash i.e. we add a OBJECT tag for IE ActiveX and + * inside it a EMBED for all other browsers. + */ + + StringBuilder html = new StringBuilder(); + + // Start the object tag + html.append(""); + + // Ensure we have an movie parameter + Map parameters = getParameters(uidl); + if (parameters.get("movie") == null) { + parameters.put("movie", getSrc(uidl, client)); + } + + // Add parameters to OBJECT + for (String name : parameters.keySet()) { + html.append(""); + } + + // Build inner EMBED tag + html.append(""); + + if (uidl.hasAttribute(EmbeddedConnector.ALTERNATE_TEXT)) { + html.append(uidl + .getStringAttribute(EmbeddedConnector.ALTERNATE_TEXT)); + } + + // End object tag + html.append(""); + + return html.toString(); + } + + /** + * Returns a map (name -> value) of all parameters in the UIDL. + * + * @param uidl + * @return + */ + protected static Map getParameters(UIDL uidl) { + Map parameters = new HashMap(); + + Iterator childIterator = uidl.getChildIterator(); + while (childIterator.hasNext()) { + + Object child = childIterator.next(); + if (child instanceof UIDL) { + + UIDL childUIDL = (UIDL) child; + if (childUIDL.getTag().equals("embeddedparam")) { + String name = childUIDL.getStringAttribute("name"); + String value = childUIDL.getStringAttribute("value"); + parameters.put(name, value); + } + } + + } + + return parameters; + } + + /** + * Helper to return translated src-attribute from embedded's UIDL + * + * @param uidl + * @param client + * @return + */ + protected String getSrc(UIDL uidl, ApplicationConnection client) { + String url = client.translateVaadinUri(uidl.getStringAttribute("src")); + if (url == null) { + return ""; + } + return url; + } + + @Override + protected void onDetach() { + if (BrowserInfo.get().isIE()) { + // Force browser to fire unload event when component is detached + // from the view (IE doesn't do this automatically) + if (browserElement != null) { + /* + * src was previously set to javascript:false, but this was not + * enough to overcome a bug when detaching an iframe with a pdf + * loaded in IE9. about:blank seems to cause the adobe reader + * plugin to unload properly before the iframe is removed. See + * #7855 + */ + DOM.setElementAttribute(browserElement, "src", "about:blank"); + } + } + super.onDetach(); + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (DOM.eventGetType(event) == Event.ONLOAD) { + VConsole.log("Embeddable onload"); + Util.notifyParentOfSizeChange(this, true); + } + + client.handleTooltipEvent(event, this); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/form/FormConnector.java b/src/com/vaadin/terminal/gwt/client/ui/form/FormConnector.java new file mode 100644 index 0000000000..34d8461b20 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/form/FormConnector.java @@ -0,0 +1,187 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.form; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.LayoutManager; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; +import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout; +import com.vaadin.ui.Form; + +@Component(Form.class) +public class FormConnector extends AbstractComponentContainerConnector + implements Paintable, SimpleManagedLayout { + + @Override + public void init() { + VForm form = getWidget(); + getLayoutManager().registerDependency(this, form.footerContainer); + } + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidget().client = client; + getWidget().id = uidl.getId(); + + if (!isRealUpdate(uidl)) { + return; + } + + boolean legendEmpty = true; + if (getState().getCaption() != null) { + getWidget().caption.setInnerText(getState().getCaption()); + legendEmpty = false; + } else { + getWidget().caption.setInnerText(""); + } + if (getState().getIcon() != null) { + if (getWidget().icon == null) { + getWidget().icon = new Icon(client); + getWidget().legend.insertFirst(getWidget().icon.getElement()); + } + getWidget().icon.setUri(getState().getIcon().getURL()); + legendEmpty = false; + } else { + if (getWidget().icon != null) { + getWidget().legend.removeChild(getWidget().icon.getElement()); + } + } + if (legendEmpty) { + getWidget().addStyleDependentName("nocaption"); + } else { + getWidget().removeStyleDependentName("nocaption"); + } + + if (null != getState().getErrorMessage()) { + getWidget().errorMessage + .updateMessage(getState().getErrorMessage()); + getWidget().errorMessage.setVisible(true); + } else { + getWidget().errorMessage.setVisible(false); + } + + if (getState().hasDescription()) { + getWidget().desc.setInnerHTML(getState().getDescription()); + if (getWidget().desc.getParentElement() == null) { + getWidget().fieldSet.insertAfter(getWidget().desc, + getWidget().legend); + } + } else { + getWidget().desc.setInnerHTML(""); + if (getWidget().desc.getParentElement() != null) { + getWidget().fieldSet.removeChild(getWidget().desc); + } + } + + // first render footer so it will be easier to handle relative height of + // main layout + if (getState().getFooter() != null) { + // render footer + ComponentConnector newFooter = (ComponentConnector) getState() + .getFooter(); + Widget newFooterWidget = newFooter.getWidget(); + if (getWidget().footer == null) { + getWidget().add(newFooter.getWidget(), + getWidget().footerContainer); + getWidget().footer = newFooterWidget; + } else if (newFooter != getWidget().footer) { + getWidget().remove(getWidget().footer); + getWidget().add(newFooter.getWidget(), + getWidget().footerContainer); + } + getWidget().footer = newFooterWidget; + } else { + if (getWidget().footer != null) { + getWidget().remove(getWidget().footer); + } + } + + ComponentConnector newLayout = (ComponentConnector) getState() + .getLayout(); + Widget newLayoutWidget = newLayout.getWidget(); + if (getWidget().lo == null) { + // Layout not rendered before + getWidget().lo = newLayoutWidget; + getWidget().add(newLayoutWidget, getWidget().fieldContainer); + } else if (getWidget().lo != newLayoutWidget) { + // Layout has changed + getWidget().remove(getWidget().lo); + getWidget().lo = newLayoutWidget; + getWidget().add(newLayoutWidget, getWidget().fieldContainer); + } + + // also recalculates size of the footer if undefined size form - see + // #3710 + client.runDescendentsLayout(getWidget()); + + // We may have actions attached + if (uidl.getChildCount() >= 1) { + UIDL childUidl = uidl.getChildByTagName("actions"); + if (childUidl != null) { + if (getWidget().shortcutHandler == null) { + getWidget().shortcutHandler = new ShortcutActionHandler( + getConnectorId(), client); + getWidget().keyDownRegistration = getWidget() + .addDomHandler(getWidget(), KeyDownEvent.getType()); + } + getWidget().shortcutHandler.updateActionMap(childUidl); + } + } else if (getWidget().shortcutHandler != null) { + getWidget().keyDownRegistration.removeHandler(); + getWidget().shortcutHandler = null; + getWidget().keyDownRegistration = null; + } + } + + public void updateCaption(ComponentConnector component) { + // NOP form don't render caption for neither field layout nor footer + // layout + } + + @Override + public VForm getWidget() { + return (VForm) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VForm.class); + } + + public void layout() { + VForm form = getWidget(); + + LayoutManager lm = getLayoutManager(); + int footerHeight = lm.getOuterHeight(form.footerContainer) + - lm.getMarginTop(form.footerContainer); + + form.fieldContainer.getStyle().setPaddingBottom(footerHeight, Unit.PX); + form.footerContainer.getStyle().setMarginTop(-footerHeight, Unit.PX); + } + + @Override + public boolean isReadOnly() { + return super.isReadOnly() || getState().isPropertyReadOnly(); + } + + @Override + public FormState getState() { + return (FormState) super.getState(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/form/FormState.java b/src/com/vaadin/terminal/gwt/client/ui/form/FormState.java new file mode 100644 index 0000000000..ef48593eb5 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/form/FormState.java @@ -0,0 +1,26 @@ +package com.vaadin.terminal.gwt.client.ui.form; + +import com.vaadin.terminal.gwt.client.AbstractFieldState; +import com.vaadin.terminal.gwt.client.Connector; + +public class FormState extends AbstractFieldState { + private Connector layout; + private Connector footer; + + public Connector getLayout() { + return layout; + } + + public void setLayout(Connector layout) { + this.layout = layout; + } + + public Connector getFooter() { + return footer; + } + + public void setFooter(Connector footer) { + this.footer = footer; + } + +} \ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/form/VForm.java b/src/com/vaadin/terminal/gwt/client/ui/form/VForm.java new file mode 100644 index 0000000000..e3a0c9b321 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/form/VForm.java @@ -0,0 +1,80 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.form; + +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.shared.HandlerRegistration; +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.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.VErrorMessage; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; + +public class VForm extends ComplexPanel implements KeyDownHandler { + + protected String id; + + public static final String CLASSNAME = "v-form"; + + Widget lo; + Element legend = DOM.createLegend(); + Element caption = DOM.createSpan(); + private Element errorIndicatorElement = DOM.createDiv(); + Element desc = DOM.createDiv(); + Icon icon; + VErrorMessage errorMessage = new VErrorMessage(); + + Element fieldContainer = DOM.createDiv(); + + Element footerContainer = DOM.createDiv(); + + Element fieldSet = DOM.createFieldSet(); + + Widget footer; + + ApplicationConnection client; + + ShortcutActionHandler shortcutHandler; + + HandlerRegistration keyDownRegistration; + + public VForm() { + setElement(DOM.createDiv()); + getElement().appendChild(fieldSet); + setStyleName(CLASSNAME); + fieldSet.appendChild(legend); + legend.appendChild(caption); + errorIndicatorElement.setClassName("v-errorindicator"); + errorIndicatorElement.getStyle().setDisplay(Display.NONE); + errorIndicatorElement.setInnerText(" "); // needed for IE + desc.setClassName("v-form-description"); + fieldSet.appendChild(desc); // Adding description for initial padding + // measurements, removed later if no + // description is set + fieldContainer.setClassName(CLASSNAME + "-content"); + fieldSet.appendChild(fieldContainer); + errorMessage.setVisible(false); + errorMessage.setStyleName(CLASSNAME + "-errormessage"); + fieldSet.appendChild(errorMessage.getElement()); + fieldSet.appendChild(footerContainer); + } + + public void onKeyDown(KeyDownEvent event) { + shortcutHandler.handleKeyboardEvent(Event.as(event.getNativeEvent())); + } + + @Override + protected void add(Widget child, Element container) { + // Overridden to allow VFormPaintable to call this. Should be removed + // once functionality from VFormPaintable is moved to VForm. + super.add(child, container); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/formlayout/FormLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/formlayout/FormLayoutConnector.java new file mode 100644 index 0000000000..aa076784e1 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/formlayout/FormLayoutConnector.java @@ -0,0 +1,105 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.formlayout; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; +import com.vaadin.terminal.gwt.client.ui.AbstractLayoutConnector; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.VMarginInfo; +import com.vaadin.terminal.gwt.client.ui.formlayout.VFormLayout.Caption; +import com.vaadin.terminal.gwt.client.ui.formlayout.VFormLayout.ErrorFlag; +import com.vaadin.terminal.gwt.client.ui.formlayout.VFormLayout.VFormLayoutTable; +import com.vaadin.terminal.gwt.client.ui.orderedlayout.AbstractOrderedLayoutConnector.AbstractOrderedLayoutState; +import com.vaadin.ui.FormLayout; + +@Component(FormLayout.class) +public class FormLayoutConnector extends AbstractLayoutConnector { + + @Override + public AbstractOrderedLayoutState getState() { + return (AbstractOrderedLayoutState) super.getState(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + VFormLayoutTable formLayoutTable = getWidget().table; + + formLayoutTable.setMargins(new VMarginInfo(getState() + .getMarginsBitmask())); + formLayoutTable.setSpacing(getState().isSpacing()); + + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + + VFormLayout formLayout = getWidget(); + VFormLayoutTable formLayoutTable = getWidget().table; + + int childId = 0; + + formLayoutTable.setRowCount(getChildren().size()); + + for (ComponentConnector child : getChildren()) { + Widget childWidget = child.getWidget(); + + Caption caption = formLayoutTable.getCaption(childWidget); + if (caption == null) { + caption = formLayout.new Caption(child); + caption.addClickHandler(formLayoutTable); + } + + ErrorFlag error = formLayoutTable.getError(childWidget); + if (error == null) { + error = formLayout.new ErrorFlag(child); + } + + formLayoutTable.setChild(childId, childWidget, caption, error); + childId++; + } + + for (ComponentConnector oldChild : event.getOldChildren()) { + if (oldChild.getParent() == this) { + continue; + } + + formLayoutTable.cleanReferences(oldChild.getWidget()); + } + + } + + public void updateCaption(ComponentConnector component) { + getWidget().table.updateCaption(component.getWidget(), + component.getState(), component.isEnabled()); + boolean hideErrors = false; + + // FIXME This incorrectly depends on AbstractFieldConnector + if (component instanceof AbstractFieldConnector) { + hideErrors = ((AbstractFieldConnector) component).getState() + .isHideErrors(); + } + + getWidget().table.updateError(component.getWidget(), component + .getState().getErrorMessage(), hideErrors); + } + + @Override + public VFormLayout getWidget() { + return (VFormLayout) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VFormLayout.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/formlayout/VFormLayout.java b/src/com/vaadin/terminal/gwt/client/ui/formlayout/VFormLayout.java new file mode 100644 index 0000000000..85584755a6 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/formlayout/VFormLayout.java @@ -0,0 +1,379 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.formlayout; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +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.FlexTable; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.Focusable; +import com.vaadin.terminal.gwt.client.StyleConstants; +import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.VMarginInfo; + +/** + * Two col Layout that places caption on left col and field on right col + */ +public class VFormLayout extends SimplePanel { + + private final static String CLASSNAME = "v-formlayout"; + + ApplicationConnection client; + VFormLayoutTable table; + + public VFormLayout() { + super(); + setStyleName(CLASSNAME); + table = new VFormLayoutTable(); + setWidget(table); + } + + /** + * Parses the stylenames from shared state + * + * @param state + * shared state of the component + * @param enabled + * @return An array of stylenames + */ + private String[] getStylesFromState(ComponentState state, boolean enabled) { + List styles = new ArrayList(); + if (state.hasStyles()) { + for (String name : state.getStyles()) { + styles.add(name); + } + } + + if (!enabled) { + styles.add(ApplicationConnection.DISABLED_CLASSNAME); + } + + return styles.toArray(new String[styles.size()]); + } + + public class VFormLayoutTable extends FlexTable implements ClickHandler { + + private static final int COLUMN_CAPTION = 0; + private static final int COLUMN_ERRORFLAG = 1; + private static final int COLUMN_WIDGET = 2; + + private HashMap widgetToCaption = new HashMap(); + private HashMap widgetToError = new HashMap(); + + public VFormLayoutTable() { + DOM.setElementProperty(getElement(), "cellPadding", "0"); + DOM.setElementProperty(getElement(), "cellSpacing", "0"); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt + * .event.dom.client.ClickEvent) + */ + public void onClick(ClickEvent event) { + Caption caption = (Caption) event.getSource(); + if (caption.getOwner() != null) { + if (caption.getOwner() instanceof Focusable) { + ((Focusable) caption.getOwner()).focus(); + } else if (caption.getOwner() instanceof com.google.gwt.user.client.ui.Focusable) { + ((com.google.gwt.user.client.ui.Focusable) caption + .getOwner()).setFocus(true); + } + } + } + + public void setMargins(VMarginInfo margins) { + Element margin = getElement(); + setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_TOP, + margins.hasTop()); + setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_RIGHT, + margins.hasRight()); + setStyleName(margin, + CLASSNAME + "-" + StyleConstants.MARGIN_BOTTOM, + margins.hasBottom()); + setStyleName(margin, CLASSNAME + "-" + StyleConstants.MARGIN_LEFT, + margins.hasLeft()); + + } + + public void setSpacing(boolean spacing) { + setStyleName(getElement(), CLASSNAME + "-" + "spacing", spacing); + + } + + public void setRowCount(int rowNr) { + for (int i = 0; i < rowNr; i++) { + prepareCell(i, COLUMN_CAPTION); + getCellFormatter().setStyleName(i, COLUMN_CAPTION, + CLASSNAME + "-captioncell"); + + prepareCell(i, 1); + getCellFormatter().setStyleName(i, COLUMN_ERRORFLAG, + CLASSNAME + "-errorcell"); + + prepareCell(i, 2); + getCellFormatter().setStyleName(i, COLUMN_WIDGET, + CLASSNAME + "-contentcell"); + + String rowstyles = CLASSNAME + "-row"; + if (i == 0) { + rowstyles += " " + CLASSNAME + "-firstrow"; + } + if (i == rowNr - 1) { + rowstyles += " " + CLASSNAME + "-lastrow"; + } + + getRowFormatter().setStyleName(i, rowstyles); + + } + while (getRowCount() != rowNr) { + removeRow(rowNr); + } + } + + public void setChild(int rowNr, Widget childWidget, Caption caption, + ErrorFlag error) { + setWidget(rowNr, COLUMN_WIDGET, childWidget); + setWidget(rowNr, COLUMN_CAPTION, caption); + setWidget(rowNr, COLUMN_ERRORFLAG, error); + + widgetToCaption.put(childWidget, caption); + widgetToError.put(childWidget, error); + + } + + public Caption getCaption(Widget childWidget) { + return widgetToCaption.get(childWidget); + } + + public ErrorFlag getError(Widget childWidget) { + return widgetToError.get(childWidget); + } + + public void cleanReferences(Widget oldChildWidget) { + widgetToError.remove(oldChildWidget); + widgetToCaption.remove(oldChildWidget); + + } + + public void updateCaption(Widget widget, ComponentState state, + boolean enabled) { + final Caption c = widgetToCaption.get(widget); + if (c != null) { + c.updateCaption(state, enabled); + } + } + + public void updateError(Widget widget, String errorMessage, + boolean hideErrors) { + final ErrorFlag e = widgetToError.get(widget); + if (e != null) { + e.updateError(errorMessage, hideErrors); + } + + } + + } + + // TODO why duplicated here? + public class Caption extends HTML { + + public static final String CLASSNAME = "v-caption"; + + private final ComponentConnector owner; + + private Element requiredFieldIndicator; + + private Icon icon; + + private Element captionText; + + /** + * + * @param component + * optional owner of caption. If not set, getOwner will + * return null + */ + public Caption(ComponentConnector component) { + super(); + owner = component; + + sinkEvents(VTooltip.TOOLTIP_EVENTS); + } + + private void setStyles(String[] styles) { + String styleName = CLASSNAME; + + if (styles != null) { + for (String style : styles) { + if (ApplicationConnection.DISABLED_CLASSNAME.equals(style)) { + // Add v-disabled also without classname prefix so + // generic v-disabled CSS rules work + styleName += " " + style; + } + + styleName += " " + CLASSNAME + "-" + style; + } + } + + setStyleName(styleName); + } + + public void updateCaption(ComponentState state, boolean enabled) { + // Update styles as they might have changed when the caption changed + setStyles(getStylesFromState(state, enabled)); + + boolean isEmpty = true; + + if (state.getIcon() != null) { + if (icon == null) { + icon = new Icon(owner.getConnection()); + + DOM.insertChild(getElement(), icon.getElement(), 0); + } + icon.setUri(state.getIcon().getURL()); + isEmpty = false; + } else { + if (icon != null) { + DOM.removeChild(getElement(), icon.getElement()); + icon = null; + } + + } + + if (state.getCaption() != null) { + if (captionText == null) { + captionText = DOM.createSpan(); + DOM.insertChild(getElement(), captionText, icon == null ? 0 + : 1); + } + String c = state.getCaption(); + if (c == null) { + c = ""; + } else { + isEmpty = false; + } + DOM.setInnerText(captionText, c); + } else { + // TODO should span also be removed + } + + if (state.hasDescription() && captionText != null) { + addStyleDependentName("hasdescription"); + } else { + removeStyleDependentName("hasdescription"); + } + + boolean required = owner instanceof AbstractFieldConnector + && ((AbstractFieldConnector) owner).isRequired(); + if (required) { + if (requiredFieldIndicator == null) { + requiredFieldIndicator = DOM.createSpan(); + DOM.setInnerText(requiredFieldIndicator, "*"); + DOM.setElementProperty(requiredFieldIndicator, "className", + "v-required-field-indicator"); + DOM.appendChild(getElement(), requiredFieldIndicator); + } + } else { + if (requiredFieldIndicator != null) { + DOM.removeChild(getElement(), requiredFieldIndicator); + requiredFieldIndicator = null; + } + } + + // Workaround for IE weirdness, sometimes returns bad height in some + // circumstances when Caption is empty. See #1444 + // IE7 bugs more often. I wonder what happens when IE8 arrives... + // FIXME: This could be unnecessary for IE8+ + if (BrowserInfo.get().isIE()) { + if (isEmpty) { + setHeight("0px"); + DOM.setStyleAttribute(getElement(), "overflow", "hidden"); + } else { + setHeight(""); + DOM.setStyleAttribute(getElement(), "overflow", ""); + } + + } + + } + + /** + * Returns Paintable for which this Caption belongs to. + * + * @return owner Widget + */ + public ComponentConnector getOwner() { + return owner; + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + owner.getConnection().handleTooltipEvent(event, owner); + } + } + + class ErrorFlag extends HTML { + private static final String CLASSNAME = VFormLayout.CLASSNAME + + "-error-indicator"; + Element errorIndicatorElement; + + private ComponentConnector owner; + + public ErrorFlag(ComponentConnector owner) { + setStyleName(CLASSNAME); + sinkEvents(VTooltip.TOOLTIP_EVENTS); + this.owner = owner; + } + + public void updateError(String errorMessage, boolean hideErrors) { + boolean showError = null != errorMessage; + if (hideErrors) { + showError = false; + } + + if (showError) { + if (errorIndicatorElement == null) { + errorIndicatorElement = DOM.createDiv(); + DOM.setInnerHTML(errorIndicatorElement, " "); + DOM.setElementProperty(errorIndicatorElement, "className", + "v-errorindicator"); + DOM.appendChild(getElement(), errorIndicatorElement); + } + + } else if (errorIndicatorElement != null) { + DOM.removeChild(getElement(), errorIndicatorElement); + errorIndicatorElement = null; + } + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (owner != null) { + client.handleTooltipEvent(event, owner); + } + } + + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutConnector.java new file mode 100644 index 0000000000..c96c2f7b63 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/GridLayoutConnector.java @@ -0,0 +1,262 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.gridlayout; + +import java.util.Iterator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.DirectionalManagedLayout; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ui.AbstractLayoutConnector.AbstractLayoutState; +import com.vaadin.terminal.gwt.client.ui.AlignmentInfo; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.VMarginInfo; +import com.vaadin.terminal.gwt.client.ui.gridlayout.VGridLayout.Cell; +import com.vaadin.terminal.gwt.client.ui.layout.VLayoutSlot; +import com.vaadin.ui.GridLayout; + +@Component(GridLayout.class) +public class GridLayoutConnector extends AbstractComponentContainerConnector + implements Paintable, DirectionalManagedLayout { + + public static class GridLayoutState extends AbstractLayoutState { + private boolean spacing = false; + private int rows = 0; + private int columns = 0; + + public boolean isSpacing() { + return spacing; + } + + public void setSpacing(boolean spacing) { + this.spacing = spacing; + } + + public int getRows() { + return rows; + } + + public void setRows(int rows) { + this.rows = rows; + } + + public int getColumns() { + return columns; + } + + public void setColumns(int cols) { + columns = cols; + } + + } + + private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( + this) { + + @Override + protected ComponentConnector getChildComponent(Element element) { + return getWidget().getComponent(element); + } + + @Override + protected LayoutClickRPC getLayoutClickRPC() { + return rpc; + }; + + }; + + public interface GridLayoutServerRPC extends LayoutClickRPC, ServerRpc { + + } + + private GridLayoutServerRPC rpc; + private boolean needCaptionUpdate = false; + + @Override + public void init() { + rpc = RpcProxy.create(GridLayoutServerRPC.class, this); + getLayoutManager().registerDependency(this, + getWidget().spacingMeasureElement); + } + + @Override + public GridLayoutState getState() { + return (GridLayoutState) super.getState(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + clickEventHandler.handleEventHandlerRegistration(); + + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + VGridLayout layout = getWidget(); + layout.client = client; + + if (!isRealUpdate(uidl)) { + return; + } + + int cols = getState().getColumns(); + int rows = getState().getRows(); + + layout.columnWidths = new int[cols]; + layout.rowHeights = new int[rows]; + + layout.setSize(rows, cols); + + final int[] alignments = uidl.getIntArrayAttribute("alignments"); + int alignmentIndex = 0; + + for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { + final UIDL r = (UIDL) i.next(); + if ("gr".equals(r.getTag())) { + for (final Iterator j = r.getChildIterator(); j.hasNext();) { + final UIDL cellUidl = (UIDL) j.next(); + if ("gc".equals(cellUidl.getTag())) { + int row = cellUidl.getIntAttribute("y"); + int col = cellUidl.getIntAttribute("x"); + + Widget previousWidget = null; + + Cell cell = layout.getCell(row, col); + if (cell != null && cell.slot != null) { + // This is an update. Track if the widget changes + // and update the caption if that happens. This + // workaround can be removed once the DOM update is + // done in onContainerHierarchyChange + previousWidget = cell.slot.getWidget(); + } + + cell = layout.createCell(row, col); + + cell.updateFromUidl(cellUidl); + + if (cell.hasContent()) { + cell.setAlignment(new AlignmentInfo( + alignments[alignmentIndex++])); + if (cell.slot.getWidget() != previousWidget) { + // Widget changed or widget moved from another + // slot. Update its caption as the widget might + // have called updateCaption when the widget was + // still in its old slot. This workaround can be + // removed once the DOM update + // is done in onContainerHierarchyChange + updateCaption(ConnectorMap.get(getConnection()) + .getConnector(cell.slot.getWidget())); + } + } + } + } + } + } + + layout.colExpandRatioArray = uidl.getIntArrayAttribute("colExpand"); + layout.rowExpandRatioArray = uidl.getIntArrayAttribute("rowExpand"); + + layout.updateMarginStyleNames(new VMarginInfo(getState() + .getMarginsBitmask())); + + layout.updateSpacingStyleName(getState().isSpacing()); + + if (needCaptionUpdate) { + needCaptionUpdate = false; + + for (ComponentConnector child : getChildren()) { + updateCaption(child); + } + } + getLayoutManager().setNeedsUpdate(this); + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + + VGridLayout layout = getWidget(); + + // clean non rendered components + for (ComponentConnector oldChild : event.getOldChildren()) { + if (oldChild.getParent() == this) { + continue; + } + + Widget childWidget = oldChild.getWidget(); + layout.remove(childWidget); + + Cell cell = layout.widgetToCell.remove(childWidget); + cell.slot.setCaption(null); + cell.slot.getWrapperElement().removeFromParent(); + cell.slot = null; + } + + } + + public void updateCaption(ComponentConnector childConnector) { + if (!childConnector.delegateCaptionHandling()) { + // Check not required by interface but by workarounds in this class + // when updateCaption is explicitly called for all children. + return; + } + + VGridLayout layout = getWidget(); + Cell cell = layout.widgetToCell.get(childConnector.getWidget()); + if (cell == null) { + // workaround before updateFromUidl is removed. We currently update + // the captions at the end of updateFromUidl instead of immediately + // because the DOM has not been set up at this point (as it is done + // in updateFromUidl) + needCaptionUpdate = true; + return; + } + if (VCaption.isNeeded(childConnector.getState())) { + VLayoutSlot layoutSlot = cell.slot; + VCaption caption = layoutSlot.getCaption(); + if (caption == null) { + caption = new VCaption(childConnector, getConnection()); + + Widget widget = childConnector.getWidget(); + + layout.setCaption(widget, caption); + } + caption.updateCaption(); + } else { + layout.setCaption(childConnector.getWidget(), null); + } + } + + @Override + public VGridLayout getWidget() { + return (VGridLayout) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VGridLayout.class); + } + + public void layoutVertically() { + getWidget().updateHeight(); + } + + public void layoutHorizontally() { + getWidget().updateWidth(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/gridlayout/VGridLayout.java b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/VGridLayout.java new file mode 100644 index 0000000000..1949cb191c --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/gridlayout/VGridLayout.java @@ -0,0 +1,676 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.gridlayout; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.LayoutManager; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.ui.AlignmentInfo; +import com.vaadin.terminal.gwt.client.ui.VMarginInfo; +import com.vaadin.terminal.gwt.client.ui.layout.ComponentConnectorLayoutSlot; +import com.vaadin.terminal.gwt.client.ui.layout.VLayoutSlot; + +public class VGridLayout extends ComplexPanel { + + public static final String CLASSNAME = "v-gridlayout"; + + ApplicationConnection client; + + HashMap widgetToCell = new HashMap(); + + int[] columnWidths; + int[] rowHeights; + + int[] colExpandRatioArray; + + int[] rowExpandRatioArray; + + int[] minColumnWidths; + + private int[] minRowHeights; + + DivElement spacingMeasureElement; + + public VGridLayout() { + super(); + setElement(Document.get().createDivElement()); + + spacingMeasureElement = Document.get().createDivElement(); + Style spacingStyle = spacingMeasureElement.getStyle(); + spacingStyle.setPosition(Position.ABSOLUTE); + getElement().appendChild(spacingMeasureElement); + + setStyleName(CLASSNAME); + } + + private GridLayoutConnector getConnector() { + return (GridLayoutConnector) ConnectorMap.get(client) + .getConnector(this); + } + + /** + * Returns the column widths measured in pixels + * + * @return + */ + protected int[] getColumnWidths() { + return columnWidths; + } + + /** + * Returns the row heights measured in pixels + * + * @return + */ + protected int[] getRowHeights() { + return rowHeights; + } + + /** + * Returns the spacing between the cells horizontally in pixels + * + * @return + */ + protected int getHorizontalSpacing() { + return LayoutManager.get(client).getOuterWidth(spacingMeasureElement); + } + + /** + * Returns the spacing between the cells vertically in pixels + * + * @return + */ + protected int getVerticalSpacing() { + return LayoutManager.get(client).getOuterHeight(spacingMeasureElement); + } + + static int[] cloneArray(int[] toBeCloned) { + int[] clone = new int[toBeCloned.length]; + for (int i = 0; i < clone.length; i++) { + clone[i] = toBeCloned[i] * 1; + } + return clone; + } + + void expandRows() { + if (!isUndefinedHeight()) { + int usedSpace = minRowHeights[0]; + int verticalSpacing = getVerticalSpacing(); + for (int i = 1; i < minRowHeights.length; i++) { + usedSpace += verticalSpacing + minRowHeights[i]; + } + int availableSpace = LayoutManager.get(client).getInnerHeight( + getElement()); + int excessSpace = availableSpace - usedSpace; + int distributed = 0; + if (excessSpace > 0) { + for (int i = 0; i < rowHeights.length; i++) { + int ew = excessSpace * rowExpandRatioArray[i] / 1000; + rowHeights[i] = minRowHeights[i] + ew; + distributed += ew; + } + excessSpace -= distributed; + int c = 0; + while (excessSpace > 0) { + rowHeights[c % rowHeights.length]++; + excessSpace--; + c++; + } + } + } + } + + void updateHeight() { + // Detect minimum heights & calculate spans + detectRowHeights(); + + // Expand + expandRows(); + + // Position + layoutCellsVertically(); + } + + void updateWidth() { + // Detect widths & calculate spans + detectColWidths(); + // Expand + expandColumns(); + // Position + layoutCellsHorizontally(); + + } + + void expandColumns() { + if (!isUndefinedWidth()) { + int usedSpace = minColumnWidths[0]; + int horizontalSpacing = getHorizontalSpacing(); + for (int i = 1; i < minColumnWidths.length; i++) { + usedSpace += horizontalSpacing + minColumnWidths[i]; + } + + int availableSpace = LayoutManager.get(client).getInnerWidth( + getElement()); + int excessSpace = availableSpace - usedSpace; + int distributed = 0; + if (excessSpace > 0) { + for (int i = 0; i < columnWidths.length; i++) { + int ew = excessSpace * colExpandRatioArray[i] / 1000; + columnWidths[i] = minColumnWidths[i] + ew; + distributed += ew; + } + excessSpace -= distributed; + int c = 0; + while (excessSpace > 0) { + columnWidths[c % columnWidths.length]++; + excessSpace--; + c++; + } + } + } + } + + void layoutCellsVertically() { + int verticalSpacing = getVerticalSpacing(); + LayoutManager layoutManager = LayoutManager.get(client); + Element element = getElement(); + int paddingTop = layoutManager.getPaddingTop(element); + int y = paddingTop; + + for (int i = 0; i < cells.length; i++) { + y = paddingTop; + for (int j = 0; j < cells[i].length; j++) { + Cell cell = cells[i][j]; + if (cell != null) { + cell.layoutVertically(y); + } + y += rowHeights[j] + verticalSpacing; + } + } + + if (isUndefinedHeight()) { + int outerHeight = y - verticalSpacing + + layoutManager.getPaddingBottom(element) + + layoutManager.getBorderHeight(element); + element.getStyle().setHeight(outerHeight, Unit.PX); + } + } + + void layoutCellsHorizontally() { + LayoutManager layoutManager = LayoutManager.get(client); + Element element = getElement(); + int x = layoutManager.getPaddingLeft(element); + int horizontalSpacing = getHorizontalSpacing(); + for (int i = 0; i < cells.length; i++) { + for (int j = 0; j < cells[i].length; j++) { + Cell cell = cells[i][j]; + if (cell != null) { + cell.layoutHorizontally(x); + } + } + x += columnWidths[i] + horizontalSpacing; + } + + if (isUndefinedWidth()) { + int outerWidth = x - horizontalSpacing + + layoutManager.getPaddingRight(element) + + layoutManager.getBorderWidth(element); + element.getStyle().setWidth(outerWidth, Unit.PX); + } + } + + private boolean isUndefinedHeight() { + return getConnector().isUndefinedHeight(); + } + + private boolean isUndefinedWidth() { + return getConnector().isUndefinedWidth(); + } + + private void detectRowHeights() { + for (int i = 0; i < rowHeights.length; i++) { + rowHeights[i] = 0; + } + + // collect min rowheight from non-rowspanned cells + for (int i = 0; i < cells.length; i++) { + for (int j = 0; j < cells[i].length; j++) { + Cell cell = cells[i][j]; + if (cell != null) { + if (cell.rowspan == 1) { + if (!cell.hasRelativeHeight() + && rowHeights[j] < cell.getHeight()) { + rowHeights[j] = cell.getHeight(); + } + } else { + storeRowSpannedCell(cell); + } + } + } + } + + distributeRowSpanHeights(); + + minRowHeights = cloneArray(rowHeights); + } + + private void detectColWidths() { + // collect min colwidths from non-colspanned cells + for (int i = 0; i < columnWidths.length; i++) { + columnWidths[i] = 0; + } + + for (int i = 0; i < cells.length; i++) { + for (int j = 0; j < cells[i].length; j++) { + Cell cell = cells[i][j]; + if (cell != null) { + if (cell.colspan == 1) { + if (!cell.hasRelativeWidth() + && columnWidths[i] < cell.getWidth()) { + columnWidths[i] = cell.getWidth(); + } + } else { + storeColSpannedCell(cell); + } + } + } + } + + distributeColSpanWidths(); + + minColumnWidths = cloneArray(columnWidths); + } + + private void storeRowSpannedCell(Cell cell) { + SpanList l = null; + for (SpanList list : rowSpans) { + if (list.span < cell.rowspan) { + continue; + } else { + // insert before this + l = list; + break; + } + } + if (l == null) { + l = new SpanList(cell.rowspan); + rowSpans.add(l); + } else if (l.span != cell.rowspan) { + SpanList newL = new SpanList(cell.rowspan); + rowSpans.add(rowSpans.indexOf(l), newL); + l = newL; + } + l.cells.add(cell); + } + + /** + * Iterates colspanned cells, ensures cols have enough space to accommodate + * them + */ + void distributeColSpanWidths() { + for (SpanList list : colSpans) { + for (Cell cell : list.cells) { + // cells with relative content may return non 0 here if on + // subsequent renders + int width = cell.hasRelativeWidth() ? 0 : cell.getWidth(); + distributeSpanSize(columnWidths, cell.col, cell.colspan, + getHorizontalSpacing(), width, colExpandRatioArray); + } + } + } + + /** + * Iterates rowspanned cells, ensures rows have enough space to accommodate + * them + */ + private void distributeRowSpanHeights() { + for (SpanList list : rowSpans) { + for (Cell cell : list.cells) { + // cells with relative content may return non 0 here if on + // subsequent renders + int height = cell.hasRelativeHeight() ? 0 : cell.getHeight(); + distributeSpanSize(rowHeights, cell.row, cell.rowspan, + getVerticalSpacing(), height, rowExpandRatioArray); + } + } + } + + private static void distributeSpanSize(int[] dimensions, + int spanStartIndex, int spanSize, int spacingSize, int size, + int[] expansionRatios) { + int allocated = dimensions[spanStartIndex]; + for (int i = 1; i < spanSize; i++) { + allocated += spacingSize + dimensions[spanStartIndex + i]; + } + if (allocated < size) { + // dimensions needs to be expanded due spanned cell + int neededExtraSpace = size - allocated; + int allocatedExtraSpace = 0; + + // Divide space according to expansion ratios if any span has a + // ratio + int totalExpansion = 0; + for (int i = 0; i < spanSize; i++) { + int itemIndex = spanStartIndex + i; + totalExpansion += expansionRatios[itemIndex]; + } + + for (int i = 0; i < spanSize; i++) { + int itemIndex = spanStartIndex + i; + int expansion; + if (totalExpansion == 0) { + // Divide equally among all cells if there are no + // expansion ratios + expansion = neededExtraSpace / spanSize; + } else { + expansion = neededExtraSpace * expansionRatios[itemIndex] + / totalExpansion; + } + dimensions[itemIndex] += expansion; + allocatedExtraSpace += expansion; + } + + // We might still miss a couple of pixels because of + // rounding errors... + if (neededExtraSpace > allocatedExtraSpace) { + for (int i = 0; i < spanSize; i++) { + // Add one pixel to every cell until we have + // compensated for any rounding error + int itemIndex = spanStartIndex + i; + dimensions[itemIndex] += 1; + allocatedExtraSpace += 1; + if (neededExtraSpace == allocatedExtraSpace) { + break; + } + } + } + } + } + + private LinkedList colSpans = new LinkedList(); + private LinkedList rowSpans = new LinkedList(); + + private class SpanList { + final int span; + List cells = new LinkedList(); + + public SpanList(int span) { + this.span = span; + } + } + + void storeColSpannedCell(Cell cell) { + SpanList l = null; + for (SpanList list : colSpans) { + if (list.span < cell.colspan) { + continue; + } else { + // insert before this + l = list; + break; + } + } + if (l == null) { + l = new SpanList(cell.colspan); + colSpans.add(l); + } else if (l.span != cell.colspan) { + + SpanList newL = new SpanList(cell.colspan); + colSpans.add(colSpans.indexOf(l), newL); + l = newL; + } + l.cells.add(cell); + } + + Cell[][] cells; + + /** + * Private helper class. + */ + class Cell { + public Cell(int row, int col) { + this.row = row; + this.col = col; + } + + public boolean hasContent() { + return hasContent; + } + + public boolean hasRelativeHeight() { + if (slot != null) { + return slot.getChild().isRelativeHeight(); + } else { + return true; + } + } + + /** + * @return total of spanned cols + */ + private int getAvailableWidth() { + int width = columnWidths[col]; + for (int i = 1; i < colspan; i++) { + width += getHorizontalSpacing() + columnWidths[col + i]; + } + return width; + } + + /** + * @return total of spanned rows + */ + private int getAvailableHeight() { + int height = rowHeights[row]; + for (int i = 1; i < rowspan; i++) { + height += getVerticalSpacing() + rowHeights[row + i]; + } + return height; + } + + public void layoutHorizontally(int x) { + if (slot != null) { + slot.positionHorizontally(x, getAvailableWidth()); + } + } + + public void layoutVertically(int y) { + if (slot != null) { + slot.positionVertically(y, getAvailableHeight()); + } + } + + public int getWidth() { + if (slot != null) { + return slot.getUsedWidth(); + } else { + return 0; + } + } + + public int getHeight() { + if (slot != null) { + return slot.getUsedHeight(); + } else { + return 0; + } + } + + protected boolean hasRelativeWidth() { + if (slot != null) { + return slot.getChild().isRelativeWidth(); + } else { + return true; + } + } + + final int row; + final int col; + int colspan = 1; + int rowspan = 1; + + private boolean hasContent; + + private AlignmentInfo alignment; + + ComponentConnectorLayoutSlot slot; + + public void updateFromUidl(UIDL cellUidl) { + // Set cell width + colspan = cellUidl.hasAttribute("w") ? cellUidl + .getIntAttribute("w") : 1; + // Set cell height + rowspan = cellUidl.hasAttribute("h") ? cellUidl + .getIntAttribute("h") : 1; + // ensure we will lose reference to old cells, now overlapped by + // this cell + for (int i = 0; i < colspan; i++) { + for (int j = 0; j < rowspan; j++) { + if (i > 0 || j > 0) { + cells[col + i][row + j] = null; + } + } + } + + UIDL childUidl = cellUidl.getChildUIDL(0); // we are interested + // about childUidl + hasContent = childUidl != null; + if (hasContent) { + ComponentConnector childConnector = client + .getPaintable(childUidl); + + if (slot == null || slot.getChild() != childConnector) { + slot = new ComponentConnectorLayoutSlot(CLASSNAME, + childConnector, getConnector()); + Element slotWrapper = slot.getWrapperElement(); + getElement().appendChild(slotWrapper); + + Widget widget = childConnector.getWidget(); + insert(widget, slotWrapper, getWidgetCount(), false); + Cell oldCell = widgetToCell.put(widget, this); + if (oldCell != null) { + oldCell.slot.getWrapperElement().removeFromParent(); + oldCell.slot = null; + } + } + + } + } + + public void setAlignment(AlignmentInfo alignmentInfo) { + slot.setAlignment(alignmentInfo); + } + } + + Cell getCell(int row, int col) { + return cells[col][row]; + } + + /** + * Creates a new Cell with the given coordinates. If an existing cell was + * found, returns that one. + * + * @param row + * @param col + * @return + */ + Cell createCell(int row, int col) { + Cell cell = getCell(row, col); + if (cell == null) { + cell = new Cell(row, col); + cells[col][row] = cell; + } + return cell; + } + + /** + * Returns the deepest nested child component which contains "element". The + * child component is also returned if "element" is part of its caption. + * + * @param element + * An element that is a nested sub element of the root element in + * this layout + * @return The Paintable which the element is a part of. Null if the element + * belongs to the layout and not to a child. + */ + ComponentConnector getComponent(Element element) { + return Util.getConnectorForElement(client, this, element); + } + + void setCaption(Widget widget, VCaption caption) { + VLayoutSlot slot = widgetToCell.get(widget).slot; + + if (caption != null) { + // Logical attach. + getChildren().add(caption); + } + + // Physical attach if not null, also removes old caption + slot.setCaption(caption); + + if (caption != null) { + // Adopt. + adopt(caption); + } + } + + private void togglePrefixedStyleName(String name, boolean enabled) { + if (enabled) { + addStyleDependentName(name); + } else { + removeStyleDependentName(name); + } + } + + void updateMarginStyleNames(VMarginInfo marginInfo) { + togglePrefixedStyleName("margin-top", marginInfo.hasTop()); + togglePrefixedStyleName("margin-right", marginInfo.hasRight()); + togglePrefixedStyleName("margin-bottom", marginInfo.hasBottom()); + togglePrefixedStyleName("margin-left", marginInfo.hasLeft()); + } + + void updateSpacingStyleName(boolean spacingEnabled) { + String styleName = getStylePrimaryName(); + if (spacingEnabled) { + spacingMeasureElement.addClassName(styleName + "-spacing-on"); + spacingMeasureElement.removeClassName(styleName + "-spacing-off"); + } else { + spacingMeasureElement.removeClassName(styleName + "-spacing-on"); + spacingMeasureElement.addClassName(styleName + "-spacing-off"); + } + } + + public void setSize(int rows, int cols) { + if (cells == null) { + cells = new Cell[cols][rows]; + } else if (cells.length != cols || cells[0].length != rows) { + Cell[][] newCells = new Cell[cols][rows]; + for (int i = 0; i < cells.length; i++) { + for (int j = 0; j < cells[i].length; j++) { + if (i < cols && j < rows) { + newCells[i][j] = cells[i][j]; + } + } + } + cells = newCells; + } + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/link/LinkConnector.java b/src/com/vaadin/terminal/gwt/client/ui/link/LinkConnector.java new file mode 100644 index 0000000000..1c083d9566 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/link/LinkConnector.java @@ -0,0 +1,99 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.link; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.ui.Link; + +@Component(Link.class) +public class LinkConnector extends AbstractComponentConnector implements + Paintable { + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().client = client; + + getWidget().enabled = isEnabled(); + + if (uidl.hasAttribute("name")) { + getWidget().target = uidl.getStringAttribute("name"); + getWidget().anchor.setAttribute("target", getWidget().target); + } + if (uidl.hasAttribute("src")) { + getWidget().src = client.translateVaadinUri(uidl + .getStringAttribute("src")); + getWidget().anchor.setAttribute("href", getWidget().src); + } + + if (uidl.hasAttribute("border")) { + if ("none".equals(uidl.getStringAttribute("border"))) { + getWidget().borderStyle = VLink.BORDER_STYLE_NONE; + } else { + getWidget().borderStyle = VLink.BORDER_STYLE_MINIMAL; + } + } else { + getWidget().borderStyle = VLink.BORDER_STYLE_DEFAULT; + } + + getWidget().targetHeight = uidl.hasAttribute("targetHeight") ? uidl + .getIntAttribute("targetHeight") : -1; + getWidget().targetWidth = uidl.hasAttribute("targetWidth") ? uidl + .getIntAttribute("targetWidth") : -1; + + // Set link caption + getWidget().captionElement.setInnerText(getState().getCaption()); + + // handle error + if (null != getState().getErrorMessage()) { + if (getWidget().errorIndicatorElement == null) { + getWidget().errorIndicatorElement = DOM.createDiv(); + DOM.setElementProperty(getWidget().errorIndicatorElement, + "className", "v-errorindicator"); + } + DOM.insertChild(getWidget().getElement(), + getWidget().errorIndicatorElement, 0); + } else if (getWidget().errorIndicatorElement != null) { + DOM.setStyleAttribute(getWidget().errorIndicatorElement, "display", + "none"); + } + + if (getState().getIcon() != null) { + if (getWidget().icon == null) { + getWidget().icon = new Icon(client); + getWidget().anchor.insertBefore(getWidget().icon.getElement(), + getWidget().captionElement); + } + getWidget().icon.setUri(getState().getIcon().getURL()); + } + + } + + @Override + protected Widget createWidget() { + return GWT.create(VLink.class); + } + + @Override + public VLink getWidget() { + return (VLink) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/link/VLink.java b/src/com/vaadin/terminal/gwt/client/ui/link/VLink.java new file mode 100644 index 0000000000..68fe5d9292 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/link/VLink.java @@ -0,0 +1,117 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.link; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +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.Window; +import com.google.gwt.user.client.ui.HTML; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.Icon; + +public class VLink extends HTML implements ClickHandler { + + public static final String CLASSNAME = "v-link"; + + protected static final int BORDER_STYLE_DEFAULT = 0; + protected static final int BORDER_STYLE_MINIMAL = 1; + protected static final int BORDER_STYLE_NONE = 2; + + protected String src; + + protected String target; + + protected int borderStyle = BORDER_STYLE_DEFAULT; + + protected boolean enabled; + + protected int targetWidth; + + protected int targetHeight; + + protected Element errorIndicatorElement; + + protected final Element anchor = DOM.createAnchor(); + + protected final Element captionElement = DOM.createSpan(); + + protected Icon icon; + + protected ApplicationConnection client; + + public VLink() { + super(); + getElement().appendChild(anchor); + anchor.appendChild(captionElement); + addClickHandler(this); + sinkEvents(VTooltip.TOOLTIP_EVENTS); + setStyleName(CLASSNAME); + } + + public void onClick(ClickEvent event) { + if (enabled) { + if (target == null) { + target = "_self"; + } + String features; + switch (borderStyle) { + case BORDER_STYLE_NONE: + features = "menubar=no,location=no,status=no"; + break; + case BORDER_STYLE_MINIMAL: + features = "menubar=yes,location=no,status=no"; + break; + default: + features = ""; + break; + } + + if (targetWidth > 0) { + features += (features.length() > 0 ? "," : "") + "width=" + + targetWidth; + } + if (targetHeight > 0) { + features += (features.length() > 0 ? "," : "") + "height=" + + targetHeight; + } + + if (features.length() > 0) { + // if 'special features' are set, use window.open(), unless + // a modifier key is held (ctrl to open in new tab etc) + Event e = DOM.eventGetCurrentEvent(); + if (!e.getCtrlKey() && !e.getAltKey() && !e.getShiftKey() + && !e.getMetaKey()) { + Window.open(src, target, features); + e.preventDefault(); + } + } + } + } + + @Override + public void onBrowserEvent(Event event) { + final Element target = DOM.eventGetTarget(event); + if (event.getTypeInt() == Event.ONLOAD) { + Util.notifyParentOfSizeChange(this, true); + } + if (client != null) { + client.handleTooltipEvent(event, this); + } + if (target == captionElement || target == anchor + || (icon != null && target == icon.getElement())) { + super.onBrowserEvent(event); + } + if (!enabled) { + event.preventDefault(); + } + + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/listselect/ListSelectConnector.java b/src/com/vaadin/terminal/gwt/client/ui/listselect/ListSelectConnector.java new file mode 100644 index 0000000000..0be533f00e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/listselect/ListSelectConnector.java @@ -0,0 +1,25 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.listselect; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.optiongroup.OptionGroupBaseConnector; +import com.vaadin.ui.ListSelect; + +@Component(ListSelect.class) +public class ListSelectConnector extends OptionGroupBaseConnector { + + @Override + protected Widget createWidget() { + return GWT.create(VListSelect.class); + } + + @Override + public VListSelect getWidget() { + return (VListSelect) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/listselect/TooltipListBox.java b/src/com/vaadin/terminal/gwt/client/ui/listselect/TooltipListBox.java new file mode 100644 index 0000000000..7d0c805e05 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/listselect/TooltipListBox.java @@ -0,0 +1,38 @@ +package com.vaadin.terminal.gwt.client.ui.listselect; + +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.VTooltip; + +/** + * Extended ListBox to listen tooltip events and forward them to generic + * handler. + */ +public class TooltipListBox extends ListBox { + private ApplicationConnection client; + private Widget widget; + + public TooltipListBox(boolean isMultiselect) { + super(isMultiselect); + sinkEvents(VTooltip.TOOLTIP_EVENTS); + } + + public void setClient(ApplicationConnection client) { + this.client = client; + } + + public void setSelect(Widget widget) { + this.widget = widget; + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (client != null) { + client.handleTooltipEvent(event, widget); + } + } + +} \ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/listselect/VListSelect.java b/src/com/vaadin/terminal/gwt/client/ui/listselect/VListSelect.java new file mode 100644 index 0000000000..e338897841 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/listselect/VListSelect.java @@ -0,0 +1,108 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.listselect; + +import java.util.ArrayList; +import java.util.Iterator; + +import com.google.gwt.event.dom.client.ChangeEvent; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.optiongroup.VOptionGroupBase; + +public class VListSelect extends VOptionGroupBase { + + public static final String CLASSNAME = "v-select"; + + private static final int VISIBLE_COUNT = 10; + + protected TooltipListBox select; + + private int lastSelectedIndex = -1; + + public VListSelect() { + super(new TooltipListBox(true), CLASSNAME); + select = (TooltipListBox) optionsContainer; + select.setSelect(this); + select.addChangeHandler(this); + select.addClickHandler(this); + select.setStyleName(CLASSNAME + "-select"); + select.setVisibleItemCount(VISIBLE_COUNT); + } + + @Override + protected void buildOptions(UIDL uidl) { + select.setClient(client); + select.setMultipleSelect(isMultiselect()); + select.setEnabled(!isDisabled() && !isReadonly()); + select.clear(); + if (!isMultiselect() && isNullSelectionAllowed() + && !isNullSelectionItemAvailable()) { + // can't unselect last item in singleselect mode + select.addItem("", (String) null); + } + for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { + final UIDL optionUidl = (UIDL) i.next(); + select.addItem(optionUidl.getStringAttribute("caption"), + optionUidl.getStringAttribute("key")); + if (optionUidl.hasAttribute("selected")) { + int itemIndex = select.getItemCount() - 1; + select.setItemSelected(itemIndex, true); + lastSelectedIndex = itemIndex; + } + } + if (getRows() > 0) { + select.setVisibleItemCount(getRows()); + } + } + + @Override + protected String[] getSelectedItems() { + final ArrayList selectedItemKeys = new ArrayList(); + for (int i = 0; i < select.getItemCount(); i++) { + if (select.isItemSelected(i)) { + selectedItemKeys.add(select.getValue(i)); + } + } + return selectedItemKeys.toArray(new String[selectedItemKeys.size()]); + } + + @Override + public void onChange(ChangeEvent event) { + final int si = select.getSelectedIndex(); + if (si == -1 && !isNullSelectionAllowed()) { + select.setSelectedIndex(lastSelectedIndex); + } else { + lastSelectedIndex = si; + if (isMultiselect()) { + client.updateVariable(paintableId, "selected", + getSelectedItems(), isImmediate()); + } else { + client.updateVariable(paintableId, "selected", + new String[] { "" + getSelectedItem() }, isImmediate()); + } + } + } + + @Override + public void setHeight(String height) { + select.setHeight(height); + super.setHeight(height); + } + + @Override + public void setWidth(String width) { + select.setWidth(width); + super.setWidth(width); + } + + @Override + protected void setTabIndex(int tabIndex) { + ((TooltipListBox) optionsContainer).setTabIndex(tabIndex); + } + + public void focus() { + select.setFocus(true); + } +} \ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/menubar/MenuBar.java b/src/com/vaadin/terminal/gwt/client/ui/menubar/MenuBar.java new file mode 100644 index 0000000000..7bee870387 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/menubar/MenuBar.java @@ -0,0 +1,519 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.menubar; + +/* + * Copyright 2007 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. + */ + +// COPIED HERE DUE package privates in GWT +import java.util.ArrayList; +import java.util.List; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.user.client.Command; +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.PopupListener; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ui.VOverlay; + +/** + * A standard menu bar widget. A menu bar can contain any number of menu items, + * each of which can either fire a {@link com.google.gwt.user.client.Command} or + * open a cascaded menu bar. + * + *

+ * + *

+ * + *

CSS Style Rules

+ *
    + *
  • .gwt-MenuBar { the menu bar itself }
  • + *
  • .gwt-MenuBar .gwt-MenuItem { menu items }
  • + *
  • + * .gwt-MenuBar .gwt-MenuItem-selected { selected menu items }
  • + *
+ * + *

+ *

Example

+ * {@example com.google.gwt.examples.MenuBarExample} + *

+ * + * @deprecated + */ +@Deprecated +public class MenuBar extends Widget implements PopupListener { + + private final Element body; + private final ArrayList items = new ArrayList(); + private MenuBar parentMenu; + private PopupPanel popup; + private MenuItem selectedItem; + private MenuBar shownChildMenu; + private final boolean vertical; + private boolean autoOpen; + + /** + * Creates an empty horizontal menu bar. + */ + public MenuBar() { + this(false); + } + + /** + * Creates an empty menu bar. + * + * @param vertical + * true to orient the menu bar vertically + */ + public MenuBar(boolean vertical) { + super(); + + final Element table = DOM.createTable(); + body = DOM.createTBody(); + DOM.appendChild(table, body); + + if (!vertical) { + final Element tr = DOM.createTR(); + DOM.appendChild(body, tr); + } + + this.vertical = vertical; + + final Element outer = DOM.createDiv(); + DOM.appendChild(outer, table); + setElement(outer); + + sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT); + setStyleName("gwt-MenuBar"); + } + + /** + * Adds a menu item to the bar. + * + * @param item + * the item to be added + */ + public void addItem(MenuItem item) { + Element tr; + if (vertical) { + tr = DOM.createTR(); + DOM.appendChild(body, tr); + } else { + tr = DOM.getChild(body, 0); + } + + DOM.appendChild(tr, item.getElement()); + + item.setParentMenu(this); + item.setSelectionStyle(false); + items.add(item); + } + + /** + * Adds a menu item to the bar, that will fire the given command when it is + * selected. + * + * @param text + * the item's text + * @param asHTML + * true to treat the specified text as html + * @param cmd + * the command to be fired + * @return the {@link MenuItem} object created + */ + public MenuItem addItem(String text, boolean asHTML, Command cmd) { + final MenuItem item = new MenuItem(text, asHTML, cmd); + addItem(item); + return item; + } + + /** + * Adds a menu item to the bar, that will open the specified menu when it is + * selected. + * + * @param text + * the item's text + * @param asHTML + * true to treat the specified text as html + * @param popup + * the menu to be cascaded from it + * @return the {@link MenuItem} object created + */ + public MenuItem addItem(String text, boolean asHTML, MenuBar popup) { + final MenuItem item = new MenuItem(text, asHTML, popup); + addItem(item); + return item; + } + + /** + * Adds a menu item to the bar, that will fire the given command when it is + * selected. + * + * @param text + * the item's text + * @param cmd + * the command to be fired + * @return the {@link MenuItem} object created + */ + public MenuItem addItem(String text, Command cmd) { + final MenuItem item = new MenuItem(text, cmd); + addItem(item); + return item; + } + + /** + * Adds a menu item to the bar, that will open the specified menu when it is + * selected. + * + * @param text + * the item's text + * @param popup + * the menu to be cascaded from it + * @return the {@link MenuItem} object created + */ + public MenuItem addItem(String text, MenuBar popup) { + final MenuItem item = new MenuItem(text, popup); + addItem(item); + return item; + } + + /** + * Removes all menu items from this menu bar. + */ + public void clearItems() { + final Element container = getItemContainerElement(); + while (DOM.getChildCount(container) > 0) { + DOM.removeChild(container, DOM.getChild(container, 0)); + } + items.clear(); + } + + /** + * Gets whether this menu bar's child menus will open when the mouse is + * moved over it. + * + * @return true if child menus will auto-open + */ + public boolean getAutoOpen() { + return autoOpen; + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + + final MenuItem item = findItem(DOM.eventGetTarget(event)); + switch (DOM.eventGetType(event)) { + case Event.ONCLICK: { + // Fire an item's command when the user clicks on it. + if (item != null) { + doItemAction(item, true); + } + break; + } + + case Event.ONMOUSEOVER: { + if (item != null) { + itemOver(item); + } + break; + } + + case Event.ONMOUSEOUT: { + if (item != null) { + itemOver(null); + } + break; + } + } + } + + public void onPopupClosed(PopupPanel sender, boolean autoClosed) { + // If the menu popup was auto-closed, close all of its parents as well. + if (autoClosed) { + closeAllParents(); + } + + // When the menu popup closes, remember that no item is + // currently showing a popup menu. + onHide(); + shownChildMenu = null; + popup = null; + } + + /** + * Removes the specified menu item from the bar. + * + * @param item + * the item to be removed + */ + public void removeItem(MenuItem item) { + final int idx = items.indexOf(item); + if (idx == -1) { + return; + } + + final Element container = getItemContainerElement(); + DOM.removeChild(container, DOM.getChild(container, idx)); + items.remove(idx); + } + + /** + * Sets whether this menu bar's child menus will open when the mouse is + * moved over it. + * + * @param autoOpen + * true to cause child menus to auto-open + */ + public void setAutoOpen(boolean autoOpen) { + this.autoOpen = autoOpen; + } + + /** + * Returns a list containing the MenuItem objects in the menu + * bar. If there are no items in the menu bar, then an empty + * List object will be returned. + * + * @return a list containing the MenuItem objects in the menu + * bar + */ + public List getItems() { + return items; + } + + /** + * Returns the MenuItem that is currently selected + * (highlighted) by the user. If none of the items in the menu are currently + * selected, then null will be returned. + * + * @return the MenuItem that is currently selected, or + * null if no items are currently selected + */ + public MenuItem getSelectedItem() { + return selectedItem; + } + + @Override + protected void onDetach() { + // When the menu is detached, make sure to close all of its children. + if (popup != null) { + popup.hide(); + } + + super.onDetach(); + } + + /* + * Closes all parent menu popups. + */ + void closeAllParents() { + MenuBar curMenu = this; + while (curMenu != null) { + curMenu.close(); + + if ((curMenu.parentMenu == null) && (curMenu.selectedItem != null)) { + curMenu.selectedItem.setSelectionStyle(false); + curMenu.selectedItem = null; + } + + curMenu = curMenu.parentMenu; + } + } + + /* + * Performs the action associated with the given menu item. If the item has + * a popup associated with it, the popup will be shown. If it has a command + * associated with it, and 'fireCommand' is true, then the command will be + * fired. Popups associated with other items will be hidden. + * + * @param item the item whose popup is to be shown. @param fireCommand + * true if the item's command should be fired, + * false otherwise. + */ + protected void doItemAction(final MenuItem item, boolean fireCommand) { + // If the given item is already showing its menu, we're done. + if ((shownChildMenu != null) && (item.getSubMenu() == shownChildMenu)) { + return; + } + + // If another item is showing its menu, then hide it. + if (shownChildMenu != null) { + shownChildMenu.onHide(); + popup.hide(); + } + + // If the item has no popup, optionally fire its command. + if (item.getSubMenu() == null) { + if (fireCommand) { + // Close this menu and all of its parents. + closeAllParents(); + + // Fire the item's command. + final Command cmd = item.getCommand(); + if (cmd != null) { + Scheduler.get().scheduleDeferred(cmd); + } + } + return; + } + + // Ensure that the item is selected. + selectItem(item); + + // Create a new popup for this item, and position it next to + // the item (below if this is a horizontal menu bar, to the + // right if it's a vertical bar). + popup = new VOverlay(true) { + { + setWidget(item.getSubMenu()); + item.getSubMenu().onShow(); + } + + @Override + public boolean onEventPreview(Event event) { + // Hook the popup panel's event preview. We use this to keep it + // from + // auto-hiding when the parent menu is clicked. + switch (DOM.eventGetType(event)) { + case Event.ONCLICK: + // If the event target is part of the parent menu, suppress + // the + // event altogether. + final Element target = DOM.eventGetTarget(event); + final Element parentMenuElement = item.getParentMenu() + .getElement(); + if (DOM.isOrHasChild(parentMenuElement, target)) { + return false; + } + break; + } + + return super.onEventPreview(event); + } + }; + popup.addPopupListener(this); + + if (vertical) { + popup.setPopupPosition( + item.getAbsoluteLeft() + item.getOffsetWidth(), + item.getAbsoluteTop()); + } else { + popup.setPopupPosition(item.getAbsoluteLeft(), + item.getAbsoluteTop() + item.getOffsetHeight()); + } + + shownChildMenu = item.getSubMenu(); + item.getSubMenu().parentMenu = this; + + // Show the popup, ensuring that the menubar's event preview remains on + // top + // of the popup's. + popup.show(); + } + + void itemOver(MenuItem item) { + if (item == null) { + // Don't clear selection if the currently selected item's menu is + // showing. + if ((selectedItem != null) + && (shownChildMenu == selectedItem.getSubMenu())) { + return; + } + } + + // Style the item selected when the mouse enters. + selectItem(item); + + // If child menus are being shown, or this menu is itself + // a child menu, automatically show an item's child menu + // when the mouse enters. + if (item != null) { + if ((shownChildMenu != null) || (parentMenu != null) || autoOpen) { + doItemAction(item, false); + } + } + } + + public void selectItem(MenuItem item) { + if (item == selectedItem) { + return; + } + + if (selectedItem != null) { + selectedItem.setSelectionStyle(false); + } + + if (item != null) { + item.setSelectionStyle(true); + } + + selectedItem = item; + } + + /** + * Closes this menu (if it is a popup). + */ + private void close() { + if (parentMenu != null) { + parentMenu.popup.hide(); + } + } + + private MenuItem findItem(Element hItem) { + for (int i = 0; i < items.size(); ++i) { + final MenuItem item = items.get(i); + if (DOM.isOrHasChild(item.getElement(), hItem)) { + return item; + } + } + + return null; + } + + private Element getItemContainerElement() { + if (vertical) { + return body; + } else { + return DOM.getChild(body, 0); + } + } + + /* + * This method is called when a menu bar is hidden, so that it can hide any + * child popups that are currently being shown. + */ + private void onHide() { + if (shownChildMenu != null) { + shownChildMenu.onHide(); + popup.hide(); + } + } + + /* + * This method is called when a menu bar is shown. + */ + private void onShow() { + // Select the first item when a menu is shown. + if (items.size() > 0) { + selectItem(items.get(0)); + } + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/menubar/MenuBarConnector.java b/src/com/vaadin/terminal/gwt/client/ui/menubar/MenuBarConnector.java new file mode 100644 index 0000000000..ee23e88647 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/menubar/MenuBarConnector.java @@ -0,0 +1,169 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.menubar; + +import java.util.Iterator; +import java.util.Stack; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout; +import com.vaadin.terminal.gwt.client.ui.menubar.VMenuBar.CustomMenuItem; + +@Component(value = com.vaadin.ui.MenuBar.class, loadStyle = LoadStyle.LAZY) +public class MenuBarConnector extends AbstractComponentConnector implements + Paintable, SimpleManagedLayout { + /** + * This method must be implemented to update the client-side component from + * UIDL data received from server. + * + * This method is called when the page is loaded for the first time, and + * every time UI changes in the component are received from the server. + */ + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().htmlContentAllowed = uidl + .hasAttribute(VMenuBar.HTML_CONTENT_ALLOWED); + + getWidget().openRootOnHover = uidl + .getBooleanAttribute(VMenuBar.OPEN_ROOT_MENU_ON_HOWER); + + getWidget().enabled = isEnabled(); + + // For future connections + getWidget().client = client; + getWidget().uidlId = uidl.getId(); + + // Empty the menu every time it receives new information + if (!getWidget().getItems().isEmpty()) { + getWidget().clearItems(); + } + + UIDL options = uidl.getChildUIDL(0); + + if (null != getState() && !getState().isUndefinedWidth()) { + UIDL moreItemUIDL = options.getChildUIDL(0); + StringBuffer itemHTML = new StringBuffer(); + + if (moreItemUIDL.hasAttribute("icon")) { + itemHTML.append("\"\""); + } + + String moreItemText = moreItemUIDL.getStringAttribute("text"); + if ("".equals(moreItemText)) { + moreItemText = "►"; + } + itemHTML.append(moreItemText); + + getWidget().moreItem = GWT.create(CustomMenuItem.class); + getWidget().moreItem.setHTML(itemHTML.toString()); + getWidget().moreItem.setCommand(VMenuBar.emptyCommand); + + getWidget().collapsedRootItems = new VMenuBar(true, getWidget()); + getWidget().moreItem.setSubMenu(getWidget().collapsedRootItems); + getWidget().moreItem.addStyleName(VMenuBar.CLASSNAME + + "-more-menuitem"); + } + + UIDL uidlItems = uidl.getChildUIDL(1); + Iterator itr = uidlItems.getChildIterator(); + Stack> iteratorStack = new Stack>(); + Stack menuStack = new Stack(); + VMenuBar currentMenu = getWidget(); + + while (itr.hasNext()) { + UIDL item = (UIDL) itr.next(); + CustomMenuItem currentItem = null; + + final int itemId = item.getIntAttribute("id"); + + boolean itemHasCommand = item.hasAttribute("command"); + boolean itemIsCheckable = item + .hasAttribute(VMenuBar.ATTRIBUTE_CHECKED); + + String itemHTML = getWidget().buildItemHTML(item); + + Command cmd = null; + if (!item.hasAttribute("separator")) { + if (itemHasCommand || itemIsCheckable) { + // Construct a command that fires onMenuClick(int) with the + // item's id-number + cmd = new Command() { + public void execute() { + getWidget().hostReference.onMenuClick(itemId); + } + }; + } + } + + currentItem = currentMenu.addItem(itemHTML.toString(), cmd); + currentItem.updateFromUIDL(item, client); + + if (item.getChildCount() > 0) { + menuStack.push(currentMenu); + iteratorStack.push(itr); + itr = item.getChildIterator(); + currentMenu = new VMenuBar(true, currentMenu); + // this is the top-level style that also propagates to items - + // any item specific styles are set above in + // currentItem.updateFromUIDL(item, client) + if (getState().hasStyles()) { + for (String style : getState().getStyles()) { + currentMenu.addStyleDependentName(style); + } + } + currentItem.setSubMenu(currentMenu); + } + + while (!itr.hasNext() && !iteratorStack.empty()) { + boolean hasCheckableItem = false; + for (CustomMenuItem menuItem : currentMenu.getItems()) { + hasCheckableItem = hasCheckableItem + || menuItem.isCheckable(); + } + if (hasCheckableItem) { + currentMenu.addStyleDependentName("check-column"); + } else { + currentMenu.removeStyleDependentName("check-column"); + } + + itr = iteratorStack.pop(); + currentMenu = menuStack.pop(); + } + }// while + + getLayoutManager().setWidthNeedsUpdate(this); + + }// updateFromUIDL + + @Override + protected Widget createWidget() { + return GWT.create(VMenuBar.class); + } + + @Override + public VMenuBar getWidget() { + return (VMenuBar) super.getWidget(); + } + + public void layout() { + getWidget().iLayout(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/menubar/MenuItem.java b/src/com/vaadin/terminal/gwt/client/ui/menubar/MenuItem.java new file mode 100644 index 0000000000..af79ba7c5e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/menubar/MenuItem.java @@ -0,0 +1,189 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.menubar; + +/* + * Copyright 2007 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. + */ + +// COPIED HERE DUE package privates in GWT +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.HasHTML; +import com.google.gwt.user.client.ui.UIObject; + +/** + * A widget that can be placed in a + * {@link com.google.gwt.user.client.ui.MenuBar}. Menu items can either fire a + * {@link com.google.gwt.user.client.Command} when they are clicked, or open a + * cascading sub-menu. + * + * @deprecated + */ +@Deprecated +public class MenuItem extends UIObject implements HasHTML { + + private static final String DEPENDENT_STYLENAME_SELECTED_ITEM = "selected"; + + private Command command; + private MenuBar parentMenu, subMenu; + + /** + * Constructs a new menu item that fires a command when it is selected. + * + * @param text + * the item's text + * @param cmd + * the command to be fired when it is selected + */ + public MenuItem(String text, Command cmd) { + this(text, false); + setCommand(cmd); + } + + /** + * Constructs a new menu item that fires a command when it is selected. + * + * @param text + * the item's text + * @param asHTML + * true to treat the specified text as html + * @param cmd + * the command to be fired when it is selected + */ + public MenuItem(String text, boolean asHTML, Command cmd) { + this(text, asHTML); + setCommand(cmd); + } + + /** + * Constructs a new menu item that cascades to a sub-menu when it is + * selected. + * + * @param text + * the item's text + * @param subMenu + * the sub-menu to be displayed when it is selected + */ + public MenuItem(String text, MenuBar subMenu) { + this(text, false); + setSubMenu(subMenu); + } + + /** + * Constructs a new menu item that cascades to a sub-menu when it is + * selected. + * + * @param text + * the item's text + * @param asHTML + * true to treat the specified text as html + * @param subMenu + * the sub-menu to be displayed when it is selected + */ + public MenuItem(String text, boolean asHTML, MenuBar subMenu) { + this(text, asHTML); + setSubMenu(subMenu); + } + + MenuItem(String text, boolean asHTML) { + setElement(DOM.createTD()); + setSelectionStyle(false); + + if (asHTML) { + setHTML(text); + } else { + setText(text); + } + setStyleName("gwt-MenuItem"); + } + + /** + * Gets the command associated with this item. + * + * @return this item's command, or null if none exists + */ + public Command getCommand() { + return command; + } + + public String getHTML() { + return DOM.getInnerHTML(getElement()); + } + + /** + * Gets the menu that contains this item. + * + * @return the parent menu, or null if none exists. + */ + public MenuBar getParentMenu() { + return parentMenu; + } + + /** + * Gets the sub-menu associated with this item. + * + * @return this item's sub-menu, or null if none exists + */ + public MenuBar getSubMenu() { + return subMenu; + } + + public String getText() { + return DOM.getInnerText(getElement()); + } + + /** + * Sets the command associated with this item. + * + * @param cmd + * the command to be associated with this item + */ + public void setCommand(Command cmd) { + command = cmd; + } + + public void setHTML(String html) { + DOM.setInnerHTML(getElement(), html); + } + + /** + * Sets the sub-menu associated with this item. + * + * @param subMenu + * this item's new sub-menu + */ + public void setSubMenu(MenuBar subMenu) { + this.subMenu = subMenu; + } + + public void setText(String text) { + DOM.setInnerText(getElement(), text); + } + + void setParentMenu(MenuBar parentMenu) { + this.parentMenu = parentMenu; + } + + void setSelectionStyle(boolean selected) { + if (selected) { + addStyleDependentName(DEPENDENT_STYLENAME_SELECTED_ITEM); + } else { + removeStyleDependentName(DEPENDENT_STYLENAME_SELECTED_ITEM); + } + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/menubar/VMenuBar.java b/src/com/vaadin/terminal/gwt/client/ui/menubar/VMenuBar.java new file mode 100644 index 0000000000..e48483cb02 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/menubar/VMenuBar.java @@ -0,0 +1,1438 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.menubar; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.KeyPressEvent; +import com.google.gwt.event.dom.client.KeyPressHandler; +import com.google.gwt.event.logical.shared.CloseEvent; +import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.user.client.Command; +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.Timer; +import com.google.gwt.user.client.ui.HasHTML; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.LayoutManager; +import com.vaadin.terminal.gwt.client.TooltipInfo; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.SimpleFocusablePanel; +import com.vaadin.terminal.gwt.client.ui.SubPartAware; +import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; +import com.vaadin.terminal.gwt.client.ui.VOverlay; + +public class VMenuBar extends SimpleFocusablePanel implements + CloseHandler, KeyPressHandler, KeyDownHandler, + FocusHandler, SubPartAware { + + // The hierarchy of VMenuBar is a bit weird as VMenuBar is the Paintable, + // used for the root menu but also used for the sub menus. + + /** Set the CSS class name to allow styling. */ + public static final String CLASSNAME = "v-menubar"; + + /** For server connections **/ + protected String uidlId; + protected ApplicationConnection client; + + protected final VMenuBar hostReference = this; + protected CustomMenuItem moreItem = null; + + // Only used by the root menu bar + protected VMenuBar collapsedRootItems; + + // Construct an empty command to be used when the item has no command + // associated + protected static final Command emptyCommand = null; + + public static final String OPEN_ROOT_MENU_ON_HOWER = "ormoh"; + + public static final String ATTRIBUTE_CHECKED = "checked"; + public static final String ATTRIBUTE_ITEM_DESCRIPTION = "description"; + public static final String ATTRIBUTE_ITEM_ICON = "icon"; + public static final String ATTRIBUTE_ITEM_DISABLED = "disabled"; + public static final String ATTRIBUTE_ITEM_STYLE = "style"; + + public static final String HTML_CONTENT_ALLOWED = "usehtml"; + + /** Widget fields **/ + protected boolean subMenu; + protected ArrayList items; + protected Element containerElement; + protected VOverlay popup; + protected VMenuBar visibleChildMenu; + protected boolean menuVisible = false; + protected VMenuBar parentMenu; + protected CustomMenuItem selected; + + boolean enabled = true; + + private String width = "notinited"; + + private VLazyExecutor iconLoadedExecutioner = new VLazyExecutor(100, + new ScheduledCommand() { + + public void execute() { + iLayout(true); + } + }); + + boolean openRootOnHover; + + boolean htmlContentAllowed; + + public VMenuBar() { + // Create an empty horizontal menubar + this(false, null); + + // Navigation is only handled by the root bar + addFocusHandler(this); + + /* + * Firefox auto-repeat works correctly only if we use a key press + * handler, other browsers handle it correctly when using a key down + * handler + */ + if (BrowserInfo.get().isGecko()) { + addKeyPressHandler(this); + } else { + addKeyDownHandler(this); + } + } + + public VMenuBar(boolean subMenu, VMenuBar parentMenu) { + + items = new ArrayList(); + popup = null; + visibleChildMenu = null; + + containerElement = getElement(); + + if (!subMenu) { + setStyleName(CLASSNAME); + } else { + setStyleName(CLASSNAME + "-submenu"); + this.parentMenu = parentMenu; + } + this.subMenu = subMenu; + + sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT + | Event.ONLOAD); + + sinkEvents(VTooltip.TOOLTIP_EVENTS); + } + + @Override + protected void onDetach() { + super.onDetach(); + if (!subMenu) { + setSelected(null); + hideChildren(); + menuVisible = false; + } + } + + void updateSize() { + // Take from setWidth + if (!subMenu) { + // Only needed for root level menu + hideChildren(); + setSelected(null); + menuVisible = false; + } + } + + /** + * Build the HTML content for a menu item. + * + * @param item + * @return + */ + protected String buildItemHTML(UIDL item) { + // Construct html from the text and the optional icon + StringBuffer itemHTML = new StringBuffer(); + if (item.hasAttribute("separator")) { + itemHTML.append("---"); + } else { + // Add submenu indicator + if (item.getChildCount() > 0) { + String bgStyle = ""; + itemHTML.append(""); + } + + itemHTML.append(""); + if (item.hasAttribute("icon")) { + itemHTML.append("\"\""); + } + String itemText = item.getStringAttribute("text"); + if (!htmlContentAllowed) { + itemText = Util.escapeHTML(itemText); + } + itemHTML.append(itemText); + itemHTML.append(""); + } + return itemHTML.toString(); + } + + /** + * This is called by the items in the menu and it communicates the + * information to the server + * + * @param clickedItemId + * id of the item that was clicked + */ + public void onMenuClick(int clickedItemId) { + // Updating the state to the server can not be done before + // the server connection is known, i.e., before updateFromUIDL() + // has been called. + if (uidlId != null && client != null) { + // Communicate the user interaction parameters to server. This call + // will initiate an AJAX request to the server. + client.updateVariable(uidlId, "clickedId", clickedItemId, true); + } + } + + /** Widget methods **/ + + /** + * Returns a list of items in this menu + */ + public List getItems() { + return items; + } + + /** + * Remove all the items in this menu + */ + public void clearItems() { + Element e = getContainerElement(); + while (DOM.getChildCount(e) > 0) { + DOM.removeChild(e, DOM.getChild(e, 0)); + } + items.clear(); + } + + /** + * Returns the containing element of the menu + * + * @return + */ + @Override + public Element getContainerElement() { + return containerElement; + } + + /** + * Add a new item to this menu + * + * @param html + * items text + * @param cmd + * items command + * @return the item created + */ + public CustomMenuItem addItem(String html, Command cmd) { + CustomMenuItem item = GWT.create(CustomMenuItem.class); + item.setHTML(html); + item.setCommand(cmd); + + addItem(item); + return item; + } + + /** + * Add a new item to this menu + * + * @param item + */ + public void addItem(CustomMenuItem item) { + if (items.contains(item)) { + return; + } + DOM.appendChild(getContainerElement(), item.getElement()); + item.setParentMenu(this); + item.setSelected(false); + items.add(item); + } + + public void addItem(CustomMenuItem item, int index) { + if (items.contains(item)) { + return; + } + DOM.insertChild(getContainerElement(), item.getElement(), index); + item.setParentMenu(this); + item.setSelected(false); + items.add(index, item); + } + + /** + * Remove the given item from this menu + * + * @param item + */ + public void removeItem(CustomMenuItem item) { + if (items.contains(item)) { + int index = items.indexOf(item); + + DOM.removeChild(getContainerElement(), + DOM.getChild(getContainerElement(), index)); + items.remove(index); + } + } + + /* + * @see + * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user + * .client.Event) + */ + @Override + public void onBrowserEvent(Event e) { + super.onBrowserEvent(e); + + // Handle onload events (icon loaded, size changes) + if (DOM.eventGetType(e) == Event.ONLOAD) { + VMenuBar parent = getParentMenu(); + if (parent != null) { + // The onload event for an image in a popup should be sent to + // the parent, which owns the popup + parent.iconLoaded(); + } else { + // Onload events for images in the root menu are handled by the + // root menu itself + iconLoaded(); + } + return; + } + + Element targetElement = DOM.eventGetTarget(e); + CustomMenuItem targetItem = null; + for (int i = 0; i < items.size(); i++) { + CustomMenuItem item = items.get(i); + if (DOM.isOrHasChild(item.getElement(), targetElement)) { + targetItem = item; + } + } + + // Handle tooltips + if (targetItem == null && client != null) { + // Handle root menubar tooltips + client.handleTooltipEvent(e, this); + } else if (targetItem != null) { + // Handle item tooltips + targetItem.onBrowserEvent(e); + } + + if (targetItem != null) { + switch (DOM.eventGetType(e)) { + + case Event.ONCLICK: + if (isEnabled() && targetItem.isEnabled()) { + itemClick(targetItem); + } + if (subMenu) { + // Prevent moving keyboard focus to child menus + VMenuBar parent = parentMenu; + while (parent.getParentMenu() != null) { + parent = parent.getParentMenu(); + } + parent.setFocus(true); + } + + break; + + case Event.ONMOUSEOVER: + LazyCloser.cancelClosing(); + + if (isEnabled() && targetItem.isEnabled()) { + itemOver(targetItem); + } + break; + + case Event.ONMOUSEOUT: + itemOut(targetItem); + LazyCloser.schedule(); + break; + } + } else if (subMenu && DOM.eventGetType(e) == Event.ONCLICK && subMenu) { + // Prevent moving keyboard focus to child menus + VMenuBar parent = parentMenu; + while (parent.getParentMenu() != null) { + parent = parent.getParentMenu(); + } + parent.setFocus(true); + } + } + + private boolean isEnabled() { + return enabled; + } + + private void iconLoaded() { + iconLoadedExecutioner.trigger(); + } + + /** + * When an item is clicked + * + * @param item + */ + public void itemClick(CustomMenuItem item) { + if (item.getCommand() != null) { + setSelected(null); + + if (visibleChildMenu != null) { + visibleChildMenu.hideChildren(); + } + + hideParents(true); + menuVisible = false; + Scheduler.get().scheduleDeferred(item.getCommand()); + + } else { + if (item.getSubMenu() != null + && item.getSubMenu() != visibleChildMenu) { + setSelected(item); + showChildMenu(item); + menuVisible = true; + } else if (!subMenu) { + setSelected(null); + hideChildren(); + menuVisible = false; + } + } + } + + /** + * When the user hovers the mouse over the item + * + * @param item + */ + public void itemOver(CustomMenuItem item) { + if ((openRootOnHover || subMenu || menuVisible) && !item.isSeparator()) { + setSelected(item); + if (!subMenu && openRootOnHover && !menuVisible) { + menuVisible = true; // start opening menus + LazyCloser.prepare(this); + } + } + + if (menuVisible && visibleChildMenu != item.getSubMenu() + && popup != null) { + popup.hide(); + } + + if (menuVisible && item.getSubMenu() != null + && visibleChildMenu != item.getSubMenu()) { + showChildMenu(item); + } + } + + /** + * When the mouse is moved away from an item + * + * @param item + */ + public void itemOut(CustomMenuItem item) { + if (visibleChildMenu != item.getSubMenu()) { + hideChildMenu(item); + setSelected(null); + } else if (visibleChildMenu == null) { + setSelected(null); + } + } + + /** + * Used to autoclose submenus when they the menu is in a mode which opens + * root menus on mouse hover. + */ + private static class LazyCloser extends Timer { + static LazyCloser INSTANCE; + private VMenuBar activeRoot; + + @Override + public void run() { + activeRoot.hideChildren(); + activeRoot.setSelected(null); + activeRoot.menuVisible = false; + activeRoot = null; + } + + public static void cancelClosing() { + if (INSTANCE != null) { + INSTANCE.cancel(); + } + } + + public static void prepare(VMenuBar vMenuBar) { + if (INSTANCE == null) { + INSTANCE = new LazyCloser(); + } + if (INSTANCE.activeRoot == vMenuBar) { + INSTANCE.cancel(); + } else if (INSTANCE.activeRoot != null) { + INSTANCE.cancel(); + INSTANCE.run(); + } + INSTANCE.activeRoot = vMenuBar; + } + + public static void schedule() { + if (INSTANCE != null && INSTANCE.activeRoot != null) { + INSTANCE.schedule(750); + } + } + + } + + /** + * Shows the child menu of an item. The caller must ensure that the item has + * a submenu. + * + * @param item + */ + public void showChildMenu(CustomMenuItem item) { + + int left = 0; + int top = 0; + if (subMenu) { + left = item.getParentMenu().getAbsoluteLeft() + + item.getParentMenu().getOffsetWidth(); + top = item.getAbsoluteTop(); + } else { + left = item.getAbsoluteLeft(); + top = item.getParentMenu().getAbsoluteTop() + + item.getParentMenu().getOffsetHeight(); + } + showChildMenuAt(item, top, left); + } + + protected void showChildMenuAt(CustomMenuItem item, int top, int left) { + final int shadowSpace = 10; + + popup = new VOverlay(true, false, true); + popup.setStyleName(CLASSNAME + "-popup"); + popup.setWidget(item.getSubMenu()); + popup.addCloseHandler(this); + popup.addAutoHidePartner(item.getElement()); + + // at 0,0 because otherwise IE7 add extra scrollbars (#5547) + popup.setPopupPosition(0, 0); + + item.getSubMenu().onShow(); + visibleChildMenu = item.getSubMenu(); + item.getSubMenu().setParentMenu(this); + + popup.show(); + + if (left + popup.getOffsetWidth() >= RootPanel.getBodyElement() + .getOffsetWidth() - shadowSpace) { + if (subMenu) { + left = item.getParentMenu().getAbsoluteLeft() + - popup.getOffsetWidth() - shadowSpace; + } else { + left = RootPanel.getBodyElement().getOffsetWidth() + - popup.getOffsetWidth() - shadowSpace; + } + // Accommodate space for shadow + if (left < shadowSpace) { + left = shadowSpace; + } + } + + top = adjustPopupHeight(top, shadowSpace); + + popup.setPopupPosition(left, top); + + } + + private int adjustPopupHeight(int top, final int shadowSpace) { + // Check that the popup will fit the screen + int availableHeight = RootPanel.getBodyElement().getOffsetHeight() + - top - shadowSpace; + int missingHeight = popup.getOffsetHeight() - availableHeight; + if (missingHeight > 0) { + // First move the top of the popup to get more space + // Don't move above top of screen, don't move more than needed + int moveUpBy = Math.min(top - shadowSpace, missingHeight); + + // Update state + top -= moveUpBy; + missingHeight -= moveUpBy; + availableHeight += moveUpBy; + + if (missingHeight > 0) { + int contentWidth = visibleChildMenu.getOffsetWidth(); + + // If there's still not enough room, limit height to fit and add + // a scroll bar + Style style = popup.getElement().getStyle(); + style.setHeight(availableHeight, Unit.PX); + style.setOverflowY(Overflow.SCROLL); + + // Make room for the scroll bar by adjusting the width of the + // popup + style.setWidth(contentWidth + Util.getNativeScrollbarSize(), + Unit.PX); + popup.updateShadowSizeAndPosition(); + } + } + return top; + } + + /** + * Hides the submenu of an item + * + * @param item + */ + public void hideChildMenu(CustomMenuItem item) { + if (visibleChildMenu != null + && !(visibleChildMenu == item.getSubMenu())) { + popup.hide(); + } + } + + /** + * When the menu is shown. + */ + public void onShow() { + // remove possible previous selection + if (selected != null) { + selected.setSelected(false); + selected = null; + } + menuVisible = true; + } + + /** + * Listener method, fired when this menu is closed + */ + public void onClose(CloseEvent event) { + hideChildren(); + if (event.isAutoClosed()) { + hideParents(true); + menuVisible = false; + } + visibleChildMenu = null; + popup = null; + } + + /** + * Recursively hide all child menus + */ + public void hideChildren() { + if (visibleChildMenu != null) { + visibleChildMenu.hideChildren(); + popup.hide(); + } + } + + /** + * Recursively hide all parent menus + */ + public void hideParents(boolean autoClosed) { + if (visibleChildMenu != null) { + popup.hide(); + setSelected(null); + menuVisible = !autoClosed; + } + + if (getParentMenu() != null) { + getParentMenu().hideParents(autoClosed); + } + } + + /** + * Returns the parent menu of this menu, or null if this is the top-level + * menu + * + * @return + */ + public VMenuBar getParentMenu() { + return parentMenu; + } + + /** + * Set the parent menu of this menu + * + * @param parent + */ + public void setParentMenu(VMenuBar parent) { + parentMenu = parent; + } + + /** + * Returns the currently selected item of this menu, or null if nothing is + * selected + * + * @return + */ + public CustomMenuItem getSelected() { + return selected; + } + + /** + * Set the currently selected item of this menu + * + * @param item + */ + public void setSelected(CustomMenuItem item) { + // If we had something selected, unselect + if (item != selected && selected != null) { + selected.setSelected(false); + } + // If we have a valid selection, select it + if (item != null) { + item.setSelected(true); + } + + selected = item; + } + + /** + * + * A class to hold information on menu items + * + */ + protected static class CustomMenuItem extends Widget implements HasHTML { + + private ApplicationConnection client; + + protected String html = null; + protected Command command = null; + protected VMenuBar subMenu = null; + protected VMenuBar parentMenu = null; + protected boolean enabled = true; + protected boolean isSeparator = false; + protected boolean checkable = false; + protected boolean checked = false; + + /** + * Default menu item {@link Widget} constructor for GWT.create(). + * + * Use {@link #setHTML(String)} and {@link #setCommand(Command)} after + * constructing a menu item. + */ + public CustomMenuItem() { + this("", null); + } + + /** + * Creates a menu item {@link Widget}. + * + * @param html + * @param cmd + * @deprecated use the default constructor and {@link #setHTML(String)} + * and {@link #setCommand(Command)} instead + */ + @Deprecated + public CustomMenuItem(String html, Command cmd) { + // We need spans to allow inline-block in IE + setElement(DOM.createSpan()); + + setHTML(html); + setCommand(cmd); + setSelected(false); + setStyleName(CLASSNAME + "-menuitem"); + + sinkEvents(VTooltip.TOOLTIP_EVENTS); + } + + public void setSelected(boolean selected) { + if (selected && isSelectable()) { + addStyleDependentName("selected"); + // needed for IE6 to have a single style name to match for an + // element + // TODO Can be optimized now that IE6 is not supported any more + if (checkable) { + if (checked) { + removeStyleDependentName("selected-unchecked"); + addStyleDependentName("selected-checked"); + } else { + removeStyleDependentName("selected-checked"); + addStyleDependentName("selected-unchecked"); + } + } + } else { + removeStyleDependentName("selected"); + // needed for IE6 to have a single style name to match for an + // element + removeStyleDependentName("selected-checked"); + removeStyleDependentName("selected-unchecked"); + } + } + + public void setChecked(boolean checked) { + if (checkable && !isSeparator) { + this.checked = checked; + + if (checked) { + addStyleDependentName("checked"); + removeStyleDependentName("unchecked"); + } else { + addStyleDependentName("unchecked"); + removeStyleDependentName("checked"); + } + } else { + this.checked = false; + } + } + + public boolean isChecked() { + return checked; + } + + public void setCheckable(boolean checkable) { + if (checkable && !isSeparator) { + this.checkable = true; + } else { + setChecked(false); + this.checkable = false; + } + } + + public boolean isCheckable() { + return checkable; + } + + /* + * setters and getters for the fields + */ + + public void setSubMenu(VMenuBar subMenu) { + this.subMenu = subMenu; + } + + public VMenuBar getSubMenu() { + return subMenu; + } + + public void setParentMenu(VMenuBar parentMenu) { + this.parentMenu = parentMenu; + } + + public VMenuBar getParentMenu() { + return parentMenu; + } + + public void setCommand(Command command) { + this.command = command; + } + + public Command getCommand() { + return command; + } + + public String getHTML() { + return html; + } + + public void setHTML(String html) { + this.html = html; + DOM.setInnerHTML(getElement(), html); + + // Sink the onload event for any icons. The onload + // events are handled by the parent VMenuBar. + Util.sinkOnloadForImages(getElement()); + } + + public String getText() { + return html; + } + + public void setText(String text) { + setHTML(Util.escapeHTML(text)); + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + if (enabled) { + removeStyleDependentName("disabled"); + } else { + addStyleDependentName("disabled"); + } + } + + public boolean isEnabled() { + return enabled; + } + + private void setSeparator(boolean separator) { + isSeparator = separator; + if (separator) { + setStyleName(CLASSNAME + "-separator"); + } else { + setStyleName(CLASSNAME + "-menuitem"); + setEnabled(enabled); + } + } + + public boolean isSeparator() { + return isSeparator; + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + this.client = client; + setSeparator(uidl.hasAttribute("separator")); + setEnabled(!uidl.hasAttribute(ATTRIBUTE_ITEM_DISABLED)); + + if (!isSeparator() && uidl.hasAttribute(ATTRIBUTE_CHECKED)) { + // if the selected attribute is present (either true or false), + // the item is selectable + setCheckable(true); + setChecked(uidl.getBooleanAttribute(ATTRIBUTE_CHECKED)); + } else { + setCheckable(false); + } + + if (uidl.hasAttribute(ATTRIBUTE_ITEM_STYLE)) { + String itemStyle = uidl + .getStringAttribute(ATTRIBUTE_ITEM_STYLE); + addStyleDependentName(itemStyle); + } + + if (uidl.hasAttribute(ATTRIBUTE_ITEM_DESCRIPTION)) { + String description = uidl + .getStringAttribute(ATTRIBUTE_ITEM_DESCRIPTION); + TooltipInfo info = new TooltipInfo(description); + + VMenuBar root = findRootMenu(); + client.registerTooltip(root, this, info); + } + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (client != null) { + client.handleTooltipEvent(event, findRootMenu(), this); + } + } + + private VMenuBar findRootMenu() { + VMenuBar menubar = getParentMenu(); + + // Traverse up until root menu is found + while (menubar.getParentMenu() != null) { + menubar = menubar.getParentMenu(); + } + + return menubar; + } + + /** + * Checks if the item can be selected. + * + * @return true if it is possible to select this item, false otherwise + */ + public boolean isSelectable() { + return !isSeparator() && isEnabled(); + } + + } + + /** + * @author Jouni Koivuviita / Vaadin Ltd. + */ + public void iLayout() { + iLayout(false); + updateSize(); + } + + public void iLayout(boolean iconLoadEvent) { + // Only collapse if there is more than one item in the root menu and the + // menu has an explicit size + if ((getItems().size() > 1 || (collapsedRootItems != null && collapsedRootItems + .getItems().size() > 0)) + && getElement().getStyle().getProperty("width") != null + && moreItem != null) { + + // Measure the width of the "more" item + final boolean morePresent = getItems().contains(moreItem); + addItem(moreItem); + final int moreItemWidth = moreItem.getOffsetWidth(); + if (!morePresent) { + removeItem(moreItem); + } + + int availableWidth = LayoutManager.get(client).getInnerWidth( + getElement()); + + // Used width includes the "more" item if present + int usedWidth = getConsumedWidth(); + int diff = availableWidth - usedWidth; + removeItem(moreItem); + + if (diff < 0) { + // Too many items: collapse last items from root menu + int widthNeeded = usedWidth - availableWidth; + if (!morePresent) { + widthNeeded += moreItemWidth; + } + int widthReduced = 0; + + while (widthReduced < widthNeeded && getItems().size() > 0) { + // Move last root menu item to collapsed menu + CustomMenuItem collapse = getItems().get( + getItems().size() - 1); + widthReduced += collapse.getOffsetWidth(); + removeItem(collapse); + collapsedRootItems.addItem(collapse, 0); + } + } else if (collapsedRootItems.getItems().size() > 0) { + // Space available for items: expand first items from collapsed + // menu + int widthAvailable = diff + moreItemWidth; + int widthGrowth = 0; + + while (widthAvailable > widthGrowth + && collapsedRootItems.getItems().size() > 0) { + // Move first item from collapsed menu to the root menu + CustomMenuItem expand = collapsedRootItems.getItems() + .get(0); + collapsedRootItems.removeItem(expand); + addItem(expand); + widthGrowth += expand.getOffsetWidth(); + if (collapsedRootItems.getItems().size() > 0) { + widthAvailable -= moreItemWidth; + } + if (widthGrowth > widthAvailable) { + removeItem(expand); + collapsedRootItems.addItem(expand, 0); + } else { + widthAvailable = diff + moreItemWidth; + } + } + } + if (collapsedRootItems.getItems().size() > 0) { + addItem(moreItem); + } + } + + // If a popup is open we might need to adjust the shadow as well if an + // icon shown in that popup was loaded + if (popup != null) { + // Forces a recalculation of the shadow size + popup.show(); + } + if (iconLoadEvent) { + // Size have changed if the width is undefined + Util.notifyParentOfSizeChange(this, false); + } + } + + private int getConsumedWidth() { + int w = 0; + for (CustomMenuItem item : getItems()) { + if (!collapsedRootItems.getItems().contains(item)) { + w += item.getOffsetWidth(); + } + } + return w; + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google + * .gwt.event.dom.client.KeyPressEvent) + */ + public void onKeyPress(KeyPressEvent event) { + if (handleNavigation(event.getNativeEvent().getKeyCode(), + event.isControlKeyDown() || event.isMetaKeyDown(), + event.isShiftKeyDown())) { + event.preventDefault(); + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt + * .event.dom.client.KeyDownEvent) + */ + public void onKeyDown(KeyDownEvent event) { + if (handleNavigation(event.getNativeEvent().getKeyCode(), + event.isControlKeyDown() || event.isMetaKeyDown(), + event.isShiftKeyDown())) { + event.preventDefault(); + } + } + + /** + * Get the key that moves the selection upwards. By default it is the up + * arrow key but by overriding this you can change the key to whatever you + * want. + * + * @return The keycode of the key + */ + protected int getNavigationUpKey() { + return KeyCodes.KEY_UP; + } + + /** + * Get the key that moves the selection downwards. By default it is the down + * arrow key but by overriding this you can change the key to whatever you + * want. + * + * @return The keycode of the key + */ + protected int getNavigationDownKey() { + return KeyCodes.KEY_DOWN; + } + + /** + * Get the key that moves the selection left. By default it is the left + * arrow key but by overriding this you can change the key to whatever you + * want. + * + * @return The keycode of the key + */ + protected int getNavigationLeftKey() { + return KeyCodes.KEY_LEFT; + } + + /** + * Get the key that moves the selection right. By default it is the right + * arrow key but by overriding this you can change the key to whatever you + * want. + * + * @return The keycode of the key + */ + protected int getNavigationRightKey() { + return KeyCodes.KEY_RIGHT; + } + + /** + * Get the key that selects a menu item. By default it is the Enter key but + * by overriding this you can change the key to whatever you want. + * + * @return + */ + protected int getNavigationSelectKey() { + return KeyCodes.KEY_ENTER; + } + + /** + * Get the key that closes the menu. By default it is the escape key but by + * overriding this yoy can change the key to whatever you want. + * + * @return + */ + protected int getCloseMenuKey() { + return KeyCodes.KEY_ESCAPE; + } + + /** + * Handles the keyboard events handled by the MenuBar + * + * @param event + * The keyboard event received + * @return true iff the navigation event was handled + */ + public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { + + // If tab or shift+tab close menus + if (keycode == KeyCodes.KEY_TAB) { + setSelected(null); + hideChildren(); + menuVisible = false; + return false; + } + + if (ctrl || shift || !isEnabled()) { + // Do not handle tab key, nor ctrl keys + return false; + } + + if (keycode == getNavigationLeftKey()) { + if (getSelected() == null) { + // If nothing is selected then select the last item + setSelected(items.get(items.size() - 1)); + if (!getSelected().isSelectable()) { + handleNavigation(keycode, ctrl, shift); + } + } else if (visibleChildMenu == null && getParentMenu() == null) { + // If this is the root menu then move to the left + int idx = items.indexOf(getSelected()); + if (idx > 0) { + setSelected(items.get(idx - 1)); + } else { + setSelected(items.get(items.size() - 1)); + } + + if (!getSelected().isSelectable()) { + handleNavigation(keycode, ctrl, shift); + } + } else if (visibleChildMenu != null) { + // Redirect all navigation to the submenu + visibleChildMenu.handleNavigation(keycode, ctrl, shift); + + } else if (getParentMenu().getParentMenu() == null) { + // Inside a sub menu, whose parent is a root menu item + VMenuBar root = getParentMenu(); + + root.getSelected().getSubMenu().setSelected(null); + root.hideChildren(); + + // Get the root menus items and select the previous one + int idx = root.getItems().indexOf(root.getSelected()); + idx = idx > 0 ? idx : root.getItems().size(); + CustomMenuItem selected = root.getItems().get(--idx); + + while (selected.isSeparator() || !selected.isEnabled()) { + idx = idx > 0 ? idx : root.getItems().size(); + selected = root.getItems().get(--idx); + } + + root.setSelected(selected); + openMenuAndFocusFirstIfPossible(selected); + } else { + getParentMenu().getSelected().getSubMenu().setSelected(null); + getParentMenu().hideChildren(); + } + + return true; + + } else if (keycode == getNavigationRightKey()) { + + if (getSelected() == null) { + // If nothing is selected then select the first item + setSelected(items.get(0)); + if (!getSelected().isSelectable()) { + handleNavigation(keycode, ctrl, shift); + } + } else if (visibleChildMenu == null && getParentMenu() == null) { + // If this is the root menu then move to the right + int idx = items.indexOf(getSelected()); + + if (idx < items.size() - 1) { + setSelected(items.get(idx + 1)); + } else { + setSelected(items.get(0)); + } + + if (!getSelected().isSelectable()) { + handleNavigation(keycode, ctrl, shift); + } + } else if (visibleChildMenu == null + && getSelected().getSubMenu() != null) { + // If the item has a submenu then show it and move the selection + // there + showChildMenu(getSelected()); + menuVisible = true; + visibleChildMenu.handleNavigation(keycode, ctrl, shift); + } else if (visibleChildMenu == null) { + + // Get the root menu + VMenuBar root = getParentMenu(); + while (root.getParentMenu() != null) { + root = root.getParentMenu(); + } + + // Hide the submenu + root.hideChildren(); + + // Get the root menus items and select the next one + int idx = root.getItems().indexOf(root.getSelected()); + idx = idx < root.getItems().size() - 1 ? idx : -1; + CustomMenuItem selected = root.getItems().get(++idx); + + while (selected.isSeparator() || !selected.isEnabled()) { + idx = idx < root.getItems().size() - 1 ? idx : -1; + selected = root.getItems().get(++idx); + } + + root.setSelected(selected); + openMenuAndFocusFirstIfPossible(selected); + } else if (visibleChildMenu != null) { + // Redirect all navigation to the submenu + visibleChildMenu.handleNavigation(keycode, ctrl, shift); + } + + return true; + + } else if (keycode == getNavigationUpKey()) { + + if (getSelected() == null) { + // If nothing is selected then select the last item + setSelected(items.get(items.size() - 1)); + if (!getSelected().isSelectable()) { + handleNavigation(keycode, ctrl, shift); + } + } else if (visibleChildMenu != null) { + // Redirect all navigation to the submenu + visibleChildMenu.handleNavigation(keycode, ctrl, shift); + } else { + // Select the previous item if possible or loop to the last item + int idx = items.indexOf(getSelected()); + if (idx > 0) { + setSelected(items.get(idx - 1)); + } else { + setSelected(items.get(items.size() - 1)); + } + + if (!getSelected().isSelectable()) { + handleNavigation(keycode, ctrl, shift); + } + } + + return true; + + } else if (keycode == getNavigationDownKey()) { + + if (getSelected() == null) { + // If nothing is selected then select the first item + selectFirstItem(); + } else if (visibleChildMenu == null && getParentMenu() == null) { + // If this is the root menu the show the child menu with arrow + // down, if there is a child menu + openMenuAndFocusFirstIfPossible(getSelected()); + } else if (visibleChildMenu != null) { + // Redirect all navigation to the submenu + visibleChildMenu.handleNavigation(keycode, ctrl, shift); + } else { + // Select the next item if possible or loop to the first item + int idx = items.indexOf(getSelected()); + if (idx < items.size() - 1) { + setSelected(items.get(idx + 1)); + } else { + setSelected(items.get(0)); + } + + if (!getSelected().isSelectable()) { + handleNavigation(keycode, ctrl, shift); + } + } + return true; + + } else if (keycode == getCloseMenuKey()) { + setSelected(null); + hideChildren(); + menuVisible = false; + + } else if (keycode == getNavigationSelectKey()) { + if (getSelected() == null) { + // If nothing is selected then select the first item + selectFirstItem(); + } else if (visibleChildMenu != null) { + // Redirect all navigation to the submenu + visibleChildMenu.handleNavigation(keycode, ctrl, shift); + menuVisible = false; + } else if (visibleChildMenu == null + && getSelected().getSubMenu() != null) { + // If the item has a sub menu then show it and move the + // selection there + openMenuAndFocusFirstIfPossible(getSelected()); + } else { + Command command = getSelected().getCommand(); + if (command != null) { + command.execute(); + } + + setSelected(null); + hideParents(true); + } + } + + return false; + } + + private void selectFirstItem() { + for (int i = 0; i < items.size(); i++) { + CustomMenuItem item = items.get(i); + if (item.isSelectable()) { + setSelected(item); + break; + } + } + } + + private void openMenuAndFocusFirstIfPossible(CustomMenuItem menuItem) { + VMenuBar subMenu = menuItem.getSubMenu(); + if (subMenu == null) { + // No child menu? Nothing to do + return; + } + + VMenuBar parentMenu = menuItem.getParentMenu(); + parentMenu.showChildMenu(menuItem); + + menuVisible = true; + // Select the first item in the newly open submenu + subMenu.selectFirstItem(); + + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event + * .dom.client.FocusEvent) + */ + public void onFocus(FocusEvent event) { + + } + + private final String SUBPART_PREFIX = "item"; + + public Element getSubPartElement(String subPart) { + int index = Integer + .parseInt(subPart.substring(SUBPART_PREFIX.length())); + CustomMenuItem item = getItems().get(index); + + return item.getElement(); + } + + public String getSubPartName(Element subElement) { + if (!getElement().isOrHasChild(subElement)) { + return null; + } + + Element menuItemRoot = subElement; + while (menuItemRoot != null && menuItemRoot.getParentElement() != null + && menuItemRoot.getParentElement() != getElement()) { + menuItemRoot = menuItemRoot.getParentElement().cast(); + } + // "menuItemRoot" is now the root of the menu item + + final int itemCount = getItems().size(); + for (int i = 0; i < itemCount; i++) { + if (getItems().get(i).getElement() == menuItemRoot) { + String name = SUBPART_PREFIX + i; + return name; + } + } + return null; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/nativebutton/NativeButtonConnector.java b/src/com/vaadin/terminal/gwt/client/ui/nativebutton/NativeButtonConnector.java new file mode 100644 index 0000000000..93e1172369 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/nativebutton/NativeButtonConnector.java @@ -0,0 +1,125 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.nativebutton; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.EventHelper; +import com.vaadin.terminal.gwt.client.communication.FieldRpc.FocusAndBlurServerRpc; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.button.ButtonConnector.ButtonServerRpc; +import com.vaadin.terminal.gwt.client.ui.button.ButtonState; +import com.vaadin.ui.NativeButton; + +@Component(NativeButton.class) +public class NativeButtonConnector extends AbstractComponentConnector implements + BlurHandler, FocusHandler { + + private HandlerRegistration focusHandlerRegistration; + private HandlerRegistration blurHandlerRegistration; + + private FocusAndBlurServerRpc focusBlurRpc = RpcProxy.create( + FocusAndBlurServerRpc.class, this); + + @Override + public void init() { + super.init(); + + getWidget().buttonRpcProxy = RpcProxy.create(ButtonServerRpc.class, + this); + getWidget().client = getConnection(); + getWidget().paintableId = getConnectorId(); + } + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().disableOnClick = getState().isDisableOnClick(); + focusHandlerRegistration = EventHelper.updateFocusHandler(this, + focusHandlerRegistration); + blurHandlerRegistration = EventHelper.updateBlurHandler(this, + blurHandlerRegistration); + + // Set text + getWidget().setText(getState().getCaption()); + + // handle error + if (null != getState().getErrorMessage()) { + if (getWidget().errorIndicatorElement == null) { + getWidget().errorIndicatorElement = DOM.createSpan(); + getWidget().errorIndicatorElement + .setClassName("v-errorindicator"); + } + getWidget().getElement().insertBefore( + getWidget().errorIndicatorElement, + getWidget().captionElement); + + } else if (getWidget().errorIndicatorElement != null) { + getWidget().getElement().removeChild( + getWidget().errorIndicatorElement); + getWidget().errorIndicatorElement = null; + } + + if (getState().getIcon() != null) { + if (getWidget().icon == null) { + getWidget().icon = new Icon(getConnection()); + getWidget().getElement().insertBefore( + getWidget().icon.getElement(), + getWidget().captionElement); + } + getWidget().icon.setUri(getState().getIcon().getURL()); + } else { + if (getWidget().icon != null) { + getWidget().getElement().removeChild( + getWidget().icon.getElement()); + getWidget().icon = null; + } + } + + } + + @Override + protected Widget createWidget() { + return GWT.create(VNativeButton.class); + } + + @Override + public VNativeButton getWidget() { + return (VNativeButton) super.getWidget(); + } + + @Override + public ButtonState getState() { + return (ButtonState) super.getState(); + } + + public void onFocus(FocusEvent event) { + // EventHelper.updateFocusHandler ensures that this is called only when + // there is a listener on server side + focusBlurRpc.focus(); + } + + public void onBlur(BlurEvent event) { + // EventHelper.updateFocusHandler ensures that this is called only when + // there is a listener on server side + focusBlurRpc.blur(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java b/src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java new file mode 100644 index 0000000000..cca8fc14bf --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/nativebutton/VNativeButton.java @@ -0,0 +1,132 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.nativebutton; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Button; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.button.ButtonConnector.ButtonServerRpc; + +public class VNativeButton extends Button implements ClickHandler { + + public static final String CLASSNAME = "v-nativebutton"; + + protected String width = null; + + protected String paintableId; + + protected ApplicationConnection client; + + ButtonServerRpc buttonRpcProxy; + + protected Element errorIndicatorElement; + + protected final Element captionElement = DOM.createSpan(); + + protected Icon icon; + + /** + * Helper flag to handle special-case where the button is moved from under + * mouse while clicking it. In this case mouse leaves the button without + * moving. + */ + private boolean clickPending; + + protected boolean disableOnClick = false; + + public VNativeButton() { + setStyleName(CLASSNAME); + + getElement().appendChild(captionElement); + captionElement.setClassName(getStyleName() + "-caption"); + + addClickHandler(this); + + sinkEvents(VTooltip.TOOLTIP_EVENTS); + sinkEvents(Event.ONMOUSEDOWN); + sinkEvents(Event.ONMOUSEUP); + } + + @Override + public void setText(String text) { + captionElement.setInnerText(text); + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + + if (DOM.eventGetType(event) == Event.ONLOAD) { + Util.notifyParentOfSizeChange(this, true); + + } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN + && event.getButton() == Event.BUTTON_LEFT) { + clickPending = true; + } else if (DOM.eventGetType(event) == Event.ONMOUSEMOVE) { + clickPending = false; + } else if (DOM.eventGetType(event) == Event.ONMOUSEOUT) { + if (clickPending) { + click(); + } + clickPending = false; + } + + if (client != null) { + client.handleTooltipEvent(event, this); + } + } + + @Override + public void setWidth(String width) { + this.width = width; + super.setWidth(width); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event + * .dom.client.ClickEvent) + */ + public void onClick(ClickEvent event) { + if (paintableId == null || client == null) { + return; + } + + if (BrowserInfo.get().isSafari()) { + VNativeButton.this.setFocus(true); + } + if (disableOnClick) { + setEnabled(false); + buttonRpcProxy.disableOnClick(); + } + + // Add mouse details + MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(event.getNativeEvent(), getElement()); + buttonRpcProxy.click(details); + + clickPending = false; + } + + @Override + public void setEnabled(boolean enabled) { + if (isEnabled() != enabled) { + super.setEnabled(enabled); + setStyleName(ApplicationConnection.DISABLED_CLASSNAME, !enabled); + } + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/nativeselect/NativeSelectConnector.java b/src/com/vaadin/terminal/gwt/client/ui/nativeselect/NativeSelectConnector.java new file mode 100644 index 0000000000..0bc43f3e41 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/nativeselect/NativeSelectConnector.java @@ -0,0 +1,25 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.nativeselect; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.optiongroup.OptionGroupBaseConnector; +import com.vaadin.ui.NativeSelect; + +@Component(NativeSelect.class) +public class NativeSelectConnector extends OptionGroupBaseConnector { + + @Override + protected Widget createWidget() { + return GWT.create(VNativeSelect.class); + } + + @Override + public VNativeSelect getWidget() { + return (VNativeSelect) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/nativeselect/VNativeSelect.java b/src/com/vaadin/terminal/gwt/client/ui/nativeselect/VNativeSelect.java new file mode 100644 index 0000000000..54f5e9aff5 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/nativeselect/VNativeSelect.java @@ -0,0 +1,112 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.nativeselect; + +import java.util.ArrayList; +import java.util.Iterator; + +import com.google.gwt.event.dom.client.ChangeEvent; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Field; +import com.vaadin.terminal.gwt.client.ui.listselect.TooltipListBox; +import com.vaadin.terminal.gwt.client.ui.optiongroup.VOptionGroupBase; + +public class VNativeSelect extends VOptionGroupBase implements Field { + + public static final String CLASSNAME = "v-select"; + + protected TooltipListBox select; + + private boolean firstValueIsTemporaryNullItem = false; + + public VNativeSelect() { + super(new TooltipListBox(false), CLASSNAME); + select = (TooltipListBox) optionsContainer; + select.setSelect(this); + select.setVisibleItemCount(1); + select.addChangeHandler(this); + select.setStyleName(CLASSNAME + "-select"); + + } + + @Override + protected void buildOptions(UIDL uidl) { + select.setClient(client); + select.setEnabled(!isDisabled() && !isReadonly()); + select.clear(); + firstValueIsTemporaryNullItem = false; + + if (isNullSelectionAllowed() && !isNullSelectionItemAvailable()) { + // can't unselect last item in singleselect mode + select.addItem("", (String) null); + } + boolean selected = false; + for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { + final UIDL optionUidl = (UIDL) i.next(); + select.addItem(optionUidl.getStringAttribute("caption"), + optionUidl.getStringAttribute("key")); + if (optionUidl.hasAttribute("selected")) { + select.setItemSelected(select.getItemCount() - 1, true); + selected = true; + } + } + if (!selected && !isNullSelectionAllowed()) { + // null-select not allowed, but value not selected yet; add null and + // remove when something is selected + select.insertItem("", (String) null, 0); + select.setItemSelected(0, true); + firstValueIsTemporaryNullItem = true; + } + } + + @Override + protected String[] getSelectedItems() { + final ArrayList selectedItemKeys = new ArrayList(); + for (int i = 0; i < select.getItemCount(); i++) { + if (select.isItemSelected(i)) { + selectedItemKeys.add(select.getValue(i)); + } + } + return selectedItemKeys.toArray(new String[selectedItemKeys.size()]); + } + + @Override + public void onChange(ChangeEvent event) { + + if (select.isMultipleSelect()) { + client.updateVariable(paintableId, "selected", getSelectedItems(), + isImmediate()); + } else { + client.updateVariable(paintableId, "selected", new String[] { "" + + getSelectedItem() }, isImmediate()); + } + if (firstValueIsTemporaryNullItem) { + // remove temporary empty item + select.removeItem(0); + firstValueIsTemporaryNullItem = false; + } + } + + @Override + public void setHeight(String height) { + select.setHeight(height); + super.setHeight(height); + } + + @Override + public void setWidth(String width) { + select.setWidth(width); + super.setWidth(width); + } + + @Override + protected void setTabIndex(int tabIndex) { + ((TooltipListBox) optionsContainer).setTabIndex(tabIndex); + } + + public void focus() { + select.setFocus(true); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java b/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java new file mode 100644 index 0000000000..eb97160f52 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/notification/VNotification.java @@ -0,0 +1,431 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.notification; + +import java.util.ArrayList; +import java.util.Date; +import java.util.EventObject; +import java.util.Iterator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.KeyCodes; +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.Timer; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.VOverlay; +import com.vaadin.terminal.gwt.client.ui.root.VRoot; + +public class VNotification extends VOverlay { + + public static final int CENTERED = 1; + public static final int CENTERED_TOP = 2; + public static final int CENTERED_BOTTOM = 3; + public static final int TOP_LEFT = 4; + public static final int TOP_RIGHT = 5; + public static final int BOTTOM_LEFT = 6; + public static final int BOTTOM_RIGHT = 7; + + public static final int DELAY_FOREVER = -1; + public static final int DELAY_NONE = 0; + + private static final String STYLENAME = "v-Notification"; + private static final int mouseMoveThreshold = 7; + private static final int Z_INDEX_BASE = 20000; + public static final String STYLE_SYSTEM = "system"; + private static final int FADE_ANIMATION_INTERVAL = 50; // == 20 fps + + private int startOpacity = 90; + private int fadeMsec = 400; + private int delayMsec = 1000; + + private Timer fader; + private Timer delay; + + private int x = -1; + private int y = -1; + + private String temporaryStyle; + + private ArrayList listeners; + private static final int TOUCH_DEVICE_IDLE_DELAY = 1000; + + public static final String ATTRIBUTE_NOTIFICATION_STYLE = "style"; + public static final String ATTRIBUTE_NOTIFICATION_CAPTION = "caption"; + public static final String ATTRIBUTE_NOTIFICATION_MESSAGE = "message"; + public static final String ATTRIBUTE_NOTIFICATION_ICON = "icon"; + public static final String ATTRIBUTE_NOTIFICATION_POSITION = "position"; + public static final String ATTRIBUTE_NOTIFICATION_DELAY = "delay"; + + /** + * Default constructor. You should use GWT.create instead. + */ + public VNotification() { + setStyleName(STYLENAME); + sinkEvents(Event.ONCLICK); + DOM.setStyleAttribute(getElement(), "zIndex", "" + Z_INDEX_BASE); + } + + /** + * @deprecated Use static {@link #createNotification(int)} instead to enable + * GWT deferred binding. + * + * @param delayMsec + */ + @Deprecated + public VNotification(int delayMsec) { + this(); + this.delayMsec = delayMsec; + if (BrowserInfo.get().isTouchDevice()) { + new Timer() { + @Override + public void run() { + if (isAttached()) { + fade(); + } + } + }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY); + } + } + + /** + * @deprecated Use static {@link #createNotification(int, int, int)} instead + * to enable GWT deferred binding. + * + * @param delayMsec + * @param fadeMsec + * @param startOpacity + */ + @Deprecated + public VNotification(int delayMsec, int fadeMsec, int startOpacity) { + this(delayMsec); + this.fadeMsec = fadeMsec; + this.startOpacity = startOpacity; + } + + public void startDelay() { + DOM.removeEventPreview(this); + if (delayMsec > 0) { + if (delay == null) { + delay = new Timer() { + @Override + public void run() { + fade(); + } + }; + delay.schedule(delayMsec); + } + } else if (delayMsec == 0) { + fade(); + } + } + + @Override + public void show() { + show(CENTERED); + } + + public void show(String style) { + show(CENTERED, style); + } + + public void show(int position) { + show(position, null); + } + + public void show(Widget widget, int position, String style) { + setWidget(widget); + show(position, style); + } + + public void show(String html, int position, String style) { + setWidget(new HTML(html)); + show(position, style); + } + + public void show(int position, String style) { + setOpacity(getElement(), startOpacity); + if (style != null) { + temporaryStyle = style; + addStyleName(style); + addStyleDependentName(style); + } + super.show(); + setPosition(position); + } + + @Override + public void hide() { + DOM.removeEventPreview(this); + cancelDelay(); + cancelFade(); + if (temporaryStyle != null) { + removeStyleName(temporaryStyle); + removeStyleDependentName(temporaryStyle); + temporaryStyle = null; + } + super.hide(); + fireEvent(new HideEvent(this)); + } + + public void fade() { + DOM.removeEventPreview(this); + cancelDelay(); + if (fader == null) { + fader = new Timer() { + private final long start = new Date().getTime(); + + @Override + public void run() { + /* + * To make animation smooth, don't count that event happens + * on time. Reduce opacity according to the actual time + * spent instead of fixed decrement. + */ + long now = new Date().getTime(); + long timeEplaced = now - start; + float remainingFraction = 1 - timeEplaced + / (float) fadeMsec; + int opacity = (int) (startOpacity * remainingFraction); + if (opacity <= 0) { + cancel(); + hide(); + if (BrowserInfo.get().isOpera()) { + // tray notification on opera needs to explicitly + // define + // size, reset it + DOM.setStyleAttribute(getElement(), "width", ""); + DOM.setStyleAttribute(getElement(), "height", ""); + } + } else { + setOpacity(getElement(), opacity); + } + } + }; + fader.scheduleRepeating(FADE_ANIMATION_INTERVAL); + } + } + + public void setPosition(int position) { + final Element el = getElement(); + DOM.setStyleAttribute(el, "top", ""); + DOM.setStyleAttribute(el, "left", ""); + DOM.setStyleAttribute(el, "bottom", ""); + DOM.setStyleAttribute(el, "right", ""); + switch (position) { + case TOP_LEFT: + DOM.setStyleAttribute(el, "top", "0px"); + DOM.setStyleAttribute(el, "left", "0px"); + break; + case TOP_RIGHT: + DOM.setStyleAttribute(el, "top", "0px"); + DOM.setStyleAttribute(el, "right", "0px"); + break; + case BOTTOM_RIGHT: + DOM.setStyleAttribute(el, "position", "absolute"); + if (BrowserInfo.get().isOpera()) { + // tray notification on opera needs explicitly defined size + DOM.setStyleAttribute(el, "width", getOffsetWidth() + "px"); + DOM.setStyleAttribute(el, "height", getOffsetHeight() + "px"); + } + DOM.setStyleAttribute(el, "bottom", "0px"); + DOM.setStyleAttribute(el, "right", "0px"); + break; + case BOTTOM_LEFT: + DOM.setStyleAttribute(el, "bottom", "0px"); + DOM.setStyleAttribute(el, "left", "0px"); + break; + case CENTERED_TOP: + center(); + DOM.setStyleAttribute(el, "top", "0px"); + break; + case CENTERED_BOTTOM: + center(); + DOM.setStyleAttribute(el, "top", ""); + DOM.setStyleAttribute(el, "bottom", "0px"); + break; + default: + case CENTERED: + center(); + break; + } + } + + private void cancelFade() { + if (fader != null) { + fader.cancel(); + fader = null; + } + } + + private void cancelDelay() { + if (delay != null) { + delay.cancel(); + delay = null; + } + } + + private void setOpacity(Element el, int opacity) { + DOM.setStyleAttribute(el, "opacity", "" + (opacity / 100.0)); + if (BrowserInfo.get().isIE()) { + DOM.setStyleAttribute(el, "filter", "Alpha(opacity=" + opacity + + ")"); + } + } + + @Override + public void onBrowserEvent(Event event) { + DOM.removeEventPreview(this); + if (fader == null) { + fade(); + } + } + + @Override + public boolean onEventPreview(Event event) { + int type = DOM.eventGetType(event); + // "modal" + if (delayMsec == -1 || temporaryStyle == STYLE_SYSTEM) { + if (type == Event.ONCLICK) { + if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) { + fade(); + return false; + } + } else if (type == Event.ONKEYDOWN + && event.getKeyCode() == KeyCodes.KEY_ESCAPE) { + fade(); + return false; + } + if (temporaryStyle == STYLE_SYSTEM) { + return true; + } else { + return false; + } + } + // default + switch (type) { + case Event.ONMOUSEMOVE: + + if (x < 0) { + x = DOM.eventGetClientX(event); + y = DOM.eventGetClientY(event); + } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold + || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) { + startDelay(); + } + break; + case Event.ONMOUSEDOWN: + case Event.ONMOUSEWHEEL: + case Event.ONSCROLL: + startDelay(); + break; + case Event.ONKEYDOWN: + if (event.getRepeat()) { + return true; + } + startDelay(); + break; + default: + break; + } + return true; + } + + public void addEventListener(EventListener listener) { + if (listeners == null) { + listeners = new ArrayList(); + } + listeners.add(listener); + } + + public void removeEventListener(EventListener listener) { + if (listeners == null) { + return; + } + listeners.remove(listener); + } + + private void fireEvent(HideEvent event) { + if (listeners != null) { + for (Iterator it = listeners.iterator(); it + .hasNext();) { + EventListener l = it.next(); + l.notificationHidden(event); + } + } + } + + public static void showNotification(ApplicationConnection client, + final UIDL notification) { + boolean onlyPlainText = notification + .hasAttribute(VRoot.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED); + String html = ""; + if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_ICON)) { + final String parsedUri = client.translateVaadinUri(notification + .getStringAttribute(ATTRIBUTE_NOTIFICATION_ICON)); + html += ""; + } + if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_CAPTION)) { + String caption = notification + .getStringAttribute(ATTRIBUTE_NOTIFICATION_CAPTION); + if (onlyPlainText) { + caption = Util.escapeHTML(caption); + caption = caption.replaceAll("\\n", "
"); + } + html += "

" + caption + "

"; + } + if (notification.hasAttribute(ATTRIBUTE_NOTIFICATION_MESSAGE)) { + String message = notification + .getStringAttribute(ATTRIBUTE_NOTIFICATION_MESSAGE); + if (onlyPlainText) { + message = Util.escapeHTML(message); + message = message.replaceAll("\\n", "
"); + } + html += "

" + message + "

"; + } + + final String style = notification + .hasAttribute(ATTRIBUTE_NOTIFICATION_STYLE) ? notification + .getStringAttribute(ATTRIBUTE_NOTIFICATION_STYLE) : null; + final int position = notification + .getIntAttribute(ATTRIBUTE_NOTIFICATION_POSITION); + final int delay = notification + .getIntAttribute(ATTRIBUTE_NOTIFICATION_DELAY); + createNotification(delay).show(html, position, style); + } + + public static VNotification createNotification(int delayMsec) { + final VNotification notification = GWT.create(VNotification.class); + notification.delayMsec = delayMsec; + if (BrowserInfo.get().isTouchDevice()) { + new Timer() { + @Override + public void run() { + if (notification.isAttached()) { + notification.fade(); + } + } + }.schedule(notification.delayMsec + TOUCH_DEVICE_IDLE_DELAY); + } + return notification; + } + + public class HideEvent extends EventObject { + + public HideEvent(Object source) { + super(source); + } + } + + public interface EventListener extends java.util.EventListener { + public void notificationHidden(HideEvent event); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/optiongroup/OptionGroupBaseConnector.java b/src/com/vaadin/terminal/gwt/client/ui/optiongroup/OptionGroupBaseConnector.java new file mode 100644 index 0000000000..3658126a97 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/optiongroup/OptionGroupBaseConnector.java @@ -0,0 +1,93 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.optiongroup; + +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; +import com.vaadin.terminal.gwt.client.ui.nativebutton.VNativeButton; +import com.vaadin.terminal.gwt.client.ui.textfield.VTextField; + +public abstract class OptionGroupBaseConnector extends AbstractFieldConnector + implements Paintable { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + // Save details + getWidget().client = client; + getWidget().paintableId = uidl.getId(); + + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().selectedKeys = uidl.getStringArrayVariableAsSet("selected"); + + getWidget().readonly = isReadOnly(); + getWidget().disabled = !isEnabled(); + getWidget().multiselect = "multi".equals(uidl + .getStringAttribute("selectmode")); + getWidget().immediate = getState().isImmediate(); + getWidget().nullSelectionAllowed = uidl + .getBooleanAttribute("nullselect"); + getWidget().nullSelectionItemAvailable = uidl + .getBooleanAttribute("nullselectitem"); + + if (uidl.hasAttribute("cols")) { + getWidget().cols = uidl.getIntAttribute("cols"); + } + if (uidl.hasAttribute("rows")) { + getWidget().rows = uidl.getIntAttribute("rows"); + } + + final UIDL ops = uidl.getChildUIDL(0); + + if (getWidget().getColumns() > 0) { + getWidget().container.setWidth(getWidget().getColumns() + "em"); + if (getWidget().container != getWidget().optionsContainer) { + getWidget().optionsContainer.setWidth("100%"); + } + } + + getWidget().buildOptions(ops); + + if (uidl.getBooleanAttribute("allownewitem")) { + if (getWidget().newItemField == null) { + getWidget().newItemButton = new VNativeButton(); + getWidget().newItemButton.setText("+"); + getWidget().newItemButton.addClickHandler(getWidget()); + getWidget().newItemField = new VTextField(); + getWidget().newItemField.addKeyPressHandler(getWidget()); + } + getWidget().newItemField.setEnabled(!getWidget().disabled + && !getWidget().readonly); + getWidget().newItemButton.setEnabled(!getWidget().disabled + && !getWidget().readonly); + + if (getWidget().newItemField == null + || getWidget().newItemField.getParent() != getWidget().container) { + getWidget().container.add(getWidget().newItemField); + getWidget().container.add(getWidget().newItemButton); + final int w = getWidget().container.getOffsetWidth() + - getWidget().newItemButton.getOffsetWidth(); + getWidget().newItemField.setWidth(Math.max(w, 0) + "px"); + } + } else if (getWidget().newItemField != null) { + getWidget().container.remove(getWidget().newItemField); + getWidget().container.remove(getWidget().newItemButton); + } + + getWidget().setTabIndex( + uidl.hasAttribute("tabindex") ? uidl + .getIntAttribute("tabindex") : 0); + + } + + @Override + public VOptionGroupBase getWidget() { + return (VOptionGroupBase) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/optiongroup/OptionGroupConnector.java b/src/com/vaadin/terminal/gwt/client/ui/optiongroup/OptionGroupConnector.java new file mode 100644 index 0000000000..57c98978e7 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/optiongroup/OptionGroupConnector.java @@ -0,0 +1,73 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.optiongroup; + +import java.util.ArrayList; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.EventId; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.ui.OptionGroup; + +@Component(OptionGroup.class) +public class OptionGroupConnector extends OptionGroupBaseConnector { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidget().htmlContentAllowed = uidl + .hasAttribute(VOptionGroup.HTML_CONTENT_ALLOWED); + + super.updateFromUIDL(uidl, client); + + getWidget().sendFocusEvents = client.hasEventListeners(this, + EventId.FOCUS); + getWidget().sendBlurEvents = client.hasEventListeners(this, + EventId.BLUR); + + if (getWidget().focusHandlers != null) { + for (HandlerRegistration reg : getWidget().focusHandlers) { + reg.removeHandler(); + } + getWidget().focusHandlers.clear(); + getWidget().focusHandlers = null; + + for (HandlerRegistration reg : getWidget().blurHandlers) { + reg.removeHandler(); + } + getWidget().blurHandlers.clear(); + getWidget().blurHandlers = null; + } + + if (getWidget().sendFocusEvents || getWidget().sendBlurEvents) { + getWidget().focusHandlers = new ArrayList(); + getWidget().blurHandlers = new ArrayList(); + + // add focus and blur handlers to checkboxes / radio buttons + for (Widget wid : getWidget().panel) { + if (wid instanceof CheckBox) { + getWidget().focusHandlers.add(((CheckBox) wid) + .addFocusHandler(getWidget())); + getWidget().blurHandlers.add(((CheckBox) wid) + .addBlurHandler(getWidget())); + } + } + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VOptionGroup.class); + } + + @Override + public VOptionGroup getWidget() { + return (VOptionGroup) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/optiongroup/VOptionGroup.java b/src/com/vaadin/terminal/gwt/client/ui/optiongroup/VOptionGroup.java new file mode 100644 index 0000000000..d6e6949242 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/optiongroup/VOptionGroup.java @@ -0,0 +1,197 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.optiongroup; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.LoadEvent; +import com.google.gwt.event.dom.client.LoadHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.FocusWidget; +import com.google.gwt.user.client.ui.Focusable; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.RadioButton; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.EventId; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.checkbox.VCheckBox; + +public class VOptionGroup extends VOptionGroupBase implements FocusHandler, + BlurHandler { + + public static final String HTML_CONTENT_ALLOWED = "usehtml"; + + public static final String CLASSNAME = "v-select-optiongroup"; + + public static final String ATTRIBUTE_OPTION_DISABLED = "disabled"; + + protected final Panel panel; + + private final Map optionsToKeys; + + protected boolean sendFocusEvents = false; + protected boolean sendBlurEvents = false; + protected List focusHandlers = null; + protected List blurHandlers = null; + + private final LoadHandler iconLoadHandler = new LoadHandler() { + public void onLoad(LoadEvent event) { + Util.notifyParentOfSizeChange(VOptionGroup.this, true); + } + }; + + /** + * used to check whether a blur really was a blur of the complete + * optiongroup: if a control inside this optiongroup gains focus right after + * blur of another control inside this optiongroup (meaning: if onFocus + * fires after onBlur has fired), the blur and focus won't be sent to the + * server side as only a focus change inside this optiongroup occured + */ + private boolean blurOccured = false; + + protected boolean htmlContentAllowed = false; + + public VOptionGroup() { + super(CLASSNAME); + panel = (Panel) optionsContainer; + optionsToKeys = new HashMap(); + } + + /* + * Return true if no elements were changed, false otherwise. + */ + @Override + protected void buildOptions(UIDL uidl) { + panel.clear(); + for (final Iterator it = uidl.getChildIterator(); it.hasNext();) { + final UIDL opUidl = (UIDL) it.next(); + CheckBox op; + + String itemHtml = opUidl.getStringAttribute("caption"); + if (!htmlContentAllowed) { + itemHtml = Util.escapeHTML(itemHtml); + } + + String icon = opUidl.getStringAttribute("icon"); + if (icon != null && icon.length() != 0) { + String iconUrl = client.translateVaadinUri(icon); + itemHtml = "\"\"" + itemHtml; + } + + if (isMultiselect()) { + op = new VCheckBox(); + op.setHTML(itemHtml); + } else { + op = new RadioButton(paintableId, itemHtml, true); + op.setStyleName("v-radiobutton"); + } + + if (icon != null && icon.length() != 0) { + Util.sinkOnloadForImages(op.getElement()); + op.addHandler(iconLoadHandler, LoadEvent.getType()); + } + + op.addStyleName(CLASSNAME_OPTION); + op.setValue(opUidl.getBooleanAttribute("selected")); + boolean enabled = !opUidl + .getBooleanAttribute(ATTRIBUTE_OPTION_DISABLED) + && !isReadonly() && !isDisabled(); + op.setEnabled(enabled); + setStyleName(op.getElement(), + ApplicationConnection.DISABLED_CLASSNAME, !enabled); + op.addClickHandler(this); + optionsToKeys.put(op, opUidl.getStringAttribute("key")); + panel.add(op); + } + } + + @Override + protected String[] getSelectedItems() { + return selectedKeys.toArray(new String[selectedKeys.size()]); + } + + @Override + public void onClick(ClickEvent event) { + super.onClick(event); + if (event.getSource() instanceof CheckBox) { + final boolean selected = ((CheckBox) event.getSource()).getValue(); + final String key = optionsToKeys.get(event.getSource()); + if (!isMultiselect()) { + selectedKeys.clear(); + } + if (selected) { + selectedKeys.add(key); + } else { + selectedKeys.remove(key); + } + client.updateVariable(paintableId, "selected", getSelectedItems(), + isImmediate()); + } + } + + @Override + protected void setTabIndex(int tabIndex) { + for (Iterator iterator = panel.iterator(); iterator.hasNext();) { + FocusWidget widget = (FocusWidget) iterator.next(); + widget.setTabIndex(tabIndex); + } + } + + public void focus() { + Iterator iterator = panel.iterator(); + if (iterator.hasNext()) { + ((Focusable) iterator.next()).setFocus(true); + } + } + + public void onFocus(FocusEvent arg0) { + if (!blurOccured) { + // no blur occured before this focus event + // panel was blurred => fire the event to the server side if + // requested by server side + if (sendFocusEvents) { + client.updateVariable(paintableId, EventId.FOCUS, "", true); + } + } else { + // blur occured before this focus event + // another control inside the panel (checkbox / radio box) was + // blurred => do not fire the focus and set blurOccured to false, so + // blur will not be fired, too + blurOccured = false; + } + } + + public void onBlur(BlurEvent arg0) { + blurOccured = true; + if (sendBlurEvents) { + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + // check whether blurOccured still is true and then send the + // event out to the server + if (blurOccured) { + client.updateVariable(paintableId, EventId.BLUR, "", + true); + blurOccured = false; + } + } + }); + } + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/optiongroup/VOptionGroupBase.java b/src/com/vaadin/terminal/gwt/client/ui/optiongroup/VOptionGroupBase.java new file mode 100644 index 0000000000..a512f024b8 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/optiongroup/VOptionGroupBase.java @@ -0,0 +1,168 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.optiongroup; + +import java.util.Set; + +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyPressEvent; +import com.google.gwt.event.dom.client.KeyPressHandler; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Focusable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Field; +import com.vaadin.terminal.gwt.client.ui.nativebutton.VNativeButton; +import com.vaadin.terminal.gwt.client.ui.textfield.VTextField; + +public abstract class VOptionGroupBase extends Composite implements Field, + ClickHandler, ChangeHandler, KeyPressHandler, Focusable { + + public static final String CLASSNAME_OPTION = "v-select-option"; + + protected ApplicationConnection client; + + protected String paintableId; + + protected Set selectedKeys; + + protected boolean immediate; + + protected boolean multiselect; + + protected boolean disabled; + + protected boolean readonly; + + protected int cols = 0; + + protected int rows = 0; + + protected boolean nullSelectionAllowed = true; + + protected boolean nullSelectionItemAvailable = false; + + /** + * Widget holding the different options (e.g. ListBox or Panel for radio + * buttons) (optional, fallbacks to container Panel) + */ + protected Widget optionsContainer; + + /** + * Panel containing the component + */ + protected final Panel container; + + protected VTextField newItemField; + + protected VNativeButton newItemButton; + + public VOptionGroupBase(String classname) { + container = new FlowPanel(); + initWidget(container); + optionsContainer = container; + container.setStyleName(classname); + immediate = false; + multiselect = false; + } + + /* + * Call this if you wish to specify your own container for the option + * elements (e.g. SELECT) + */ + public VOptionGroupBase(Widget w, String classname) { + this(classname); + optionsContainer = w; + container.add(optionsContainer); + } + + protected boolean isImmediate() { + return immediate; + } + + protected boolean isMultiselect() { + return multiselect; + } + + protected boolean isDisabled() { + return disabled; + } + + protected boolean isReadonly() { + return readonly; + } + + protected boolean isNullSelectionAllowed() { + return nullSelectionAllowed; + } + + protected boolean isNullSelectionItemAvailable() { + return nullSelectionItemAvailable; + } + + /** + * @return "cols" specified in uidl, 0 if not specified + */ + protected int getColumns() { + return cols; + } + + /** + * @return "rows" specified in uidl, 0 if not specified + */ + + protected int getRows() { + return rows; + } + + abstract protected void setTabIndex(int tabIndex); + + public void onClick(ClickEvent event) { + if (event.getSource() == newItemButton + && !newItemField.getText().equals("")) { + client.updateVariable(paintableId, "newitem", + newItemField.getText(), true); + newItemField.setText(""); + } + } + + public void onChange(ChangeEvent event) { + if (multiselect) { + client.updateVariable(paintableId, "selected", getSelectedItems(), + immediate); + } else { + client.updateVariable(paintableId, "selected", new String[] { "" + + getSelectedItem() }, immediate); + } + } + + public void onKeyPress(KeyPressEvent event) { + if (event.getSource() == newItemField + && event.getCharCode() == KeyCodes.KEY_ENTER) { + newItemButton.click(); + } + } + + protected abstract void buildOptions(UIDL uidl); + + protected abstract String[] getSelectedItems(); + + protected String getSelectedItem() { + final String[] sel = getSelectedItems(); + if (sel.length > 0) { + return sel[0]; + } else { + return null; + } + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java new file mode 100644 index 0000000000..f3d817b586 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java @@ -0,0 +1,308 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + +import java.util.List; + +import com.google.gwt.dom.client.Style; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.DirectionalManagedLayout; +import com.vaadin.terminal.gwt.client.LayoutManager; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.ValueMap; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.ui.AbstractLayoutConnector; +import com.vaadin.terminal.gwt.client.ui.AlignmentInfo; +import com.vaadin.terminal.gwt.client.ui.LayoutClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.VMarginInfo; +import com.vaadin.terminal.gwt.client.ui.layout.ComponentConnectorLayoutSlot; +import com.vaadin.terminal.gwt.client.ui.layout.VLayoutSlot; + +public abstract class AbstractOrderedLayoutConnector extends + AbstractLayoutConnector implements Paintable, DirectionalManagedLayout { + + public static class AbstractOrderedLayoutState extends AbstractLayoutState { + private boolean spacing = false; + + public boolean isSpacing() { + return spacing; + } + + public void setSpacing(boolean spacing) { + this.spacing = spacing; + } + + } + + public interface AbstractOrderedLayoutServerRPC extends LayoutClickRPC, + ServerRpc { + + } + + AbstractOrderedLayoutServerRPC rpc; + + private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( + this) { + + @Override + protected ComponentConnector getChildComponent(Element element) { + return Util.getConnectorForElement(getConnection(), getWidget(), + element); + } + + @Override + protected LayoutClickRPC getLayoutClickRPC() { + return rpc; + }; + + }; + + @Override + public void init() { + rpc = RpcProxy.create(AbstractOrderedLayoutServerRPC.class, this); + getLayoutManager().registerDependency(this, + getWidget().spacingMeasureElement); + } + + @Override + public AbstractOrderedLayoutState getState() { + return (AbstractOrderedLayoutState) super.getState(); + } + + public void updateCaption(ComponentConnector component) { + VMeasuringOrderedLayout layout = getWidget(); + if (VCaption.isNeeded(component.getState())) { + VLayoutSlot layoutSlot = layout.getSlotForChild(component + .getWidget()); + VCaption caption = layoutSlot.getCaption(); + if (caption == null) { + caption = new VCaption(component, getConnection()); + + Widget widget = component.getWidget(); + + layout.setCaption(widget, caption); + } + caption.updateCaption(); + } else { + layout.setCaption(component.getWidget(), null); + getLayoutManager().setNeedsUpdate(this); + } + } + + @Override + public VMeasuringOrderedLayout getWidget() { + return (VMeasuringOrderedLayout) super.getWidget(); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + clickEventHandler.handleEventHandlerRegistration(); + + VMeasuringOrderedLayout layout = getWidget(); + + ValueMap expandRatios = uidl.getMapAttribute("expandRatios"); + ValueMap alignments = uidl.getMapAttribute("alignments"); + + for (ComponentConnector child : getChildren()) { + VLayoutSlot slot = layout.getSlotForChild(child.getWidget()); + String pid = child.getConnectorId(); + + AlignmentInfo alignment; + if (alignments.containsKey(pid)) { + alignment = new AlignmentInfo(alignments.getInt(pid)); + } else { + alignment = AlignmentInfo.TOP_LEFT; + } + slot.setAlignment(alignment); + + double expandRatio; + if (expandRatios.containsKey(pid)) { + expandRatio = expandRatios.getRawNumber(pid); + } else { + expandRatio = 0; + } + slot.setExpandRatio(expandRatio); + } + + layout.updateMarginStyleNames(new VMarginInfo(getState() + .getMarginsBitmask())); + + layout.updateSpacingStyleName(getState().isSpacing()); + + getLayoutManager().setNeedsUpdate(this); + } + + private int getSizeForInnerSize(int size, boolean isVertical) { + LayoutManager layoutManager = getLayoutManager(); + Element element = getWidget().getElement(); + if (isVertical) { + return size + layoutManager.getBorderHeight(element) + + layoutManager.getPaddingHeight(element); + } else { + return size + layoutManager.getBorderWidth(element) + + layoutManager.getPaddingWidth(element); + } + } + + private static String getSizeProperty(boolean isVertical) { + return isVertical ? "height" : "width"; + } + + private boolean isUndefinedInDirection(boolean isVertical) { + if (isVertical) { + return isUndefinedHeight(); + } else { + return isUndefinedWidth(); + } + } + + private int getInnerSizeInDirection(boolean isVertical) { + if (isVertical) { + return getLayoutManager().getInnerHeight(getWidget().getElement()); + } else { + return getLayoutManager().getInnerWidth(getWidget().getElement()); + } + } + + private void layoutPrimaryDirection() { + VMeasuringOrderedLayout layout = getWidget(); + boolean isVertical = layout.isVertical; + boolean isUndefined = isUndefinedInDirection(isVertical); + + int startPadding = getStartPadding(isVertical); + int spacingSize = getSpacingInDirection(isVertical); + int allocatedSize; + + if (isUndefined) { + allocatedSize = -1; + } else { + allocatedSize = getInnerSizeInDirection(isVertical); + } + + allocatedSize = layout.layoutPrimaryDirection(spacingSize, + allocatedSize, startPadding); + + Style ownStyle = getWidget().getElement().getStyle(); + if (isUndefined) { + ownStyle.setPropertyPx(getSizeProperty(isVertical), + getSizeForInnerSize(allocatedSize, isVertical)); + } else { + ownStyle.setProperty(getSizeProperty(isVertical), + getDefinedSize(isVertical)); + } + } + + private int getSpacingInDirection(boolean isVertical) { + if (isVertical) { + return getLayoutManager().getOuterHeight( + getWidget().spacingMeasureElement); + } else { + return getLayoutManager().getOuterWidth( + getWidget().spacingMeasureElement); + } + } + + private void layoutSecondaryDirection() { + VMeasuringOrderedLayout layout = getWidget(); + boolean isVertical = layout.isVertical; + boolean isUndefined = isUndefinedInDirection(!isVertical); + + int startPadding = getStartPadding(!isVertical); + + int allocatedSize; + if (isUndefined) { + allocatedSize = -1; + } else { + allocatedSize = getInnerSizeInDirection(!isVertical); + } + + allocatedSize = layout.layoutSecondaryDirection(allocatedSize, + startPadding); + + Style ownStyle = getWidget().getElement().getStyle(); + + if (isUndefined) { + ownStyle.setPropertyPx(getSizeProperty(!getWidget().isVertical), + getSizeForInnerSize(allocatedSize, !getWidget().isVertical)); + } else { + ownStyle.setProperty(getSizeProperty(!getWidget().isVertical), + getDefinedSize(!getWidget().isVertical)); + } + } + + private String getDefinedSize(boolean isVertical) { + if (isVertical) { + return getState().getHeight(); + } else { + return getState().getWidth(); + } + } + + private int getStartPadding(boolean isVertical) { + if (isVertical) { + return getLayoutManager().getPaddingTop(getWidget().getElement()); + } else { + return getLayoutManager().getPaddingLeft(getWidget().getElement()); + } + } + + public void layoutHorizontally() { + if (getWidget().isVertical) { + layoutSecondaryDirection(); + } else { + layoutPrimaryDirection(); + } + } + + public void layoutVertically() { + if (getWidget().isVertical) { + layoutPrimaryDirection(); + } else { + layoutSecondaryDirection(); + } + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + List previousChildren = event.getOldChildren(); + int currentIndex = 0; + VMeasuringOrderedLayout layout = getWidget(); + + for (ComponentConnector child : getChildren()) { + Widget childWidget = child.getWidget(); + VLayoutSlot slot = layout.getSlotForChild(childWidget); + + if (childWidget.getParent() != layout) { + // If the child widget was previously attached to another + // AbstractOrderedLayout a slot might be found that belongs to + // another AbstractOrderedLayout. In this case we discard it and + // create a new slot. + slot = new ComponentConnectorLayoutSlot(getWidget() + .getStylePrimaryName(), child, this); + } + layout.addOrMove(slot, currentIndex++); + } + + for (ComponentConnector child : previousChildren) { + if (child.getParent() != this) { + // Remove slot if the connector is no longer a child of this + // layout + layout.removeSlotForWidget(child.getWidget()); + } + } + + }; + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/HorizontalLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/HorizontalLayoutConnector.java new file mode 100644 index 0000000000..74f5906142 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/HorizontalLayoutConnector.java @@ -0,0 +1,24 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + +import com.google.gwt.core.client.GWT; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; +import com.vaadin.ui.HorizontalLayout; + +@Component(value = HorizontalLayout.class, loadStyle = LoadStyle.EAGER) +public class HorizontalLayoutConnector extends AbstractOrderedLayoutConnector { + + @Override + public VHorizontalLayout getWidget() { + return (VHorizontalLayout) super.getWidget(); + } + + @Override + protected VHorizontalLayout createWidget() { + return GWT.create(VHorizontalLayout.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VHorizontalLayout.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VHorizontalLayout.java new file mode 100644 index 0000000000..4520f2be55 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VHorizontalLayout.java @@ -0,0 +1,15 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + + +public class VHorizontalLayout extends VMeasuringOrderedLayout { + + public static final String CLASSNAME = "v-horizontallayout"; + + public VHorizontalLayout() { + super(CLASSNAME, false); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VMeasuringOrderedLayout.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VMeasuringOrderedLayout.java new file mode 100644 index 0000000000..03cf1a5dfb --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VMeasuringOrderedLayout.java @@ -0,0 +1,227 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + +import java.util.HashMap; +import java.util.Map; + +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.ui.VMarginInfo; +import com.vaadin.terminal.gwt.client.ui.layout.VLayoutSlot; + +public class VMeasuringOrderedLayout extends ComplexPanel { + + final boolean isVertical; + + final DivElement spacingMeasureElement; + + private Map widgetToSlot = new HashMap(); + + protected VMeasuringOrderedLayout(String className, boolean isVertical) { + DivElement element = Document.get().createDivElement(); + setElement(element); + + spacingMeasureElement = Document.get().createDivElement(); + Style spacingStyle = spacingMeasureElement.getStyle(); + spacingStyle.setPosition(Position.ABSOLUTE); + getElement().appendChild(spacingMeasureElement); + + setStyleName(className); + this.isVertical = isVertical; + } + + public void addOrMove(VLayoutSlot layoutSlot, int index) { + Widget widget = layoutSlot.getWidget(); + Element wrapperElement = layoutSlot.getWrapperElement(); + + Element containerElement = getElement(); + Node childAtIndex = containerElement.getChild(index); + if (childAtIndex != wrapperElement) { + // Insert at correct location not attached or at wrong location + containerElement.insertBefore(wrapperElement, childAtIndex); + insert(widget, wrapperElement, index, false); + } + + widgetToSlot.put(widget, layoutSlot); + } + + private void togglePrefixedStyleName(String name, boolean enabled) { + if (enabled) { + addStyleDependentName(name); + } else { + removeStyleDependentName(name); + } + } + + void updateMarginStyleNames(VMarginInfo marginInfo) { + togglePrefixedStyleName("margin-top", marginInfo.hasTop()); + togglePrefixedStyleName("margin-right", marginInfo.hasRight()); + togglePrefixedStyleName("margin-bottom", marginInfo.hasBottom()); + togglePrefixedStyleName("margin-left", marginInfo.hasLeft()); + } + + void updateSpacingStyleName(boolean spacingEnabled) { + String styleName = getStylePrimaryName(); + if (spacingEnabled) { + spacingMeasureElement.addClassName(styleName + "-spacing-on"); + spacingMeasureElement.removeClassName(styleName + "-spacing-off"); + } else { + spacingMeasureElement.removeClassName(styleName + "-spacing-on"); + spacingMeasureElement.addClassName(styleName + "-spacing-off"); + } + } + + public void removeSlotForWidget(Widget widget) { + VLayoutSlot slot = getSlotForChild(widget); + VCaption caption = slot.getCaption(); + if (caption != null) { + // Must remove using setCaption to ensure dependencies (layout -> + // caption) are unregistered + slot.setCaption(null); + } + + remove(slot.getWidget()); + getElement().removeChild(slot.getWrapperElement()); + widgetToSlot.remove(widget); + } + + public VLayoutSlot getSlotForChild(Widget widget) { + return widgetToSlot.get(widget); + } + + public void setCaption(Widget child, VCaption caption) { + VLayoutSlot slot = getSlotForChild(child); + + if (caption != null) { + // Logical attach. + getChildren().add(caption); + } + + // Physical attach if not null, also removes old caption + slot.setCaption(caption); + + if (caption != null) { + // Adopt. + adopt(caption); + } + } + + public int layoutPrimaryDirection(int spacingSize, int allocatedSize, + int startPadding) { + int actuallyAllocated = 0; + double totalExpand = 0; + + int childCount = 0; + for (Widget child : this) { + if (child instanceof VCaption) { + continue; + } + childCount++; + + VLayoutSlot slot = getSlotForChild(child); + totalExpand += slot.getExpandRatio(); + + if (!slot.isRelativeInDirection(isVertical)) { + actuallyAllocated += slot.getUsedSizeInDirection(isVertical); + } + } + + actuallyAllocated += spacingSize * (childCount - 1); + + if (allocatedSize == -1) { + allocatedSize = actuallyAllocated; + } + + double unallocatedSpace = Math + .max(0, allocatedSize - actuallyAllocated); + + double currentLocation = startPadding; + + for (Widget child : this) { + if (child instanceof VCaption) { + continue; + } + + VLayoutSlot slot = getSlotForChild(child); + + double childExpandRatio; + if (totalExpand == 0) { + childExpandRatio = 1d / childCount; + } else { + childExpandRatio = slot.getExpandRatio() / totalExpand; + } + + double extraPixels = unallocatedSpace * childExpandRatio; + double endLocation = currentLocation + extraPixels; + if (!slot.isRelativeInDirection(isVertical)) { + endLocation += slot.getUsedSizeInDirection(isVertical); + } + + /* + * currentLocation and allocatedSpace are used with full precision + * to avoid missing pixels in the end. The pixel dimensions passed + * to the DOM are still rounded. Otherwise e.g. 10.5px start + * position + 10.5px space might be cause the component to go 1px + * beyond the edge as the effect of the browser's rounding may cause + * something similar to 11px + 11px. + * + * It's most efficient to use doubles all the way because native + * javascript emulates other number types using doubles. + */ + double roundedLocation = Math.round(currentLocation); + + /* + * Space is calculated as the difference between rounded start and + * end locations. Just rounding the space would cause e.g. 10.5px + + * 10.5px = 21px -> 11px + 11px = 22px but in this way we get 11px + + * 10px = 21px. + */ + double roundedSpace = Math.round(endLocation) - roundedLocation; + + slot.positionInDirection(roundedLocation, roundedSpace, isVertical); + + currentLocation = endLocation + spacingSize; + } + + return allocatedSize; + } + + public int layoutSecondaryDirection(int allocatedSize, int startPadding) { + int maxSize = 0; + for (Widget child : this) { + if (child instanceof VCaption) { + continue; + } + + VLayoutSlot slot = getSlotForChild(child); + if (!slot.isRelativeInDirection(!isVertical)) { + maxSize = Math.max(maxSize, + slot.getUsedSizeInDirection(!isVertical)); + } + } + + if (allocatedSize == -1) { + allocatedSize = maxSize; + } + + for (Widget child : this) { + if (child instanceof VCaption) { + continue; + } + + VLayoutSlot slot = getSlotForChild(child); + slot.positionInDirection(startPadding, allocatedSize, !isVertical); + } + + return allocatedSize; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VVerticalLayout.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VVerticalLayout.java new file mode 100644 index 0000000000..ba5f24fd67 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VVerticalLayout.java @@ -0,0 +1,15 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + + +public class VVerticalLayout extends VMeasuringOrderedLayout { + + public static final String CLASSNAME = "v-verticallayout"; + + public VVerticalLayout() { + super(CLASSNAME, true); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VerticalLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VerticalLayoutConnector.java new file mode 100644 index 0000000000..8e3677ca5f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/orderedlayout/VerticalLayoutConnector.java @@ -0,0 +1,24 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.orderedlayout; + +import com.google.gwt.core.client.GWT; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; +import com.vaadin.ui.VerticalLayout; + +@Component(value = VerticalLayout.class, loadStyle = LoadStyle.EAGER) +public class VerticalLayoutConnector extends AbstractOrderedLayoutConnector { + + @Override + public VVerticalLayout getWidget() { + return (VVerticalLayout) super.getWidget(); + } + + @Override + protected VVerticalLayout createWidget() { + return GWT.create(VVerticalLayout.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/panel/PanelConnector.java b/src/com/vaadin/terminal/gwt/client/ui/panel/PanelConnector.java new file mode 100644 index 0000000000..7f2afaeb7f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/panel/PanelConnector.java @@ -0,0 +1,269 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.panel; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.LayoutManager; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ui.ClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.PostLayoutListener; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; +import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout; +import com.vaadin.ui.Panel; + +@Component(Panel.class) +public class PanelConnector extends AbstractComponentContainerConnector + implements Paintable, SimpleManagedLayout, PostLayoutListener { + + public interface PanelServerRPC extends ClickRPC, ServerRpc { + + } + + public static class PanelState extends ComponentState { + private int tabIndex; + private int scrollLeft, scrollTop; + + public int getTabIndex() { + return tabIndex; + } + + public void setTabIndex(int tabIndex) { + this.tabIndex = tabIndex; + } + + public int getScrollLeft() { + return scrollLeft; + } + + public void setScrollLeft(int scrollLeft) { + this.scrollLeft = scrollLeft; + } + + public int getScrollTop() { + return scrollTop; + } + + public void setScrollTop(int scrollTop) { + this.scrollTop = scrollTop; + } + + } + + private Integer uidlScrollTop; + + private ClickEventHandler clickEventHandler = new ClickEventHandler(this) { + + @Override + protected void fireClick(NativeEvent event, + MouseEventDetails mouseDetails) { + rpc.click(mouseDetails); + } + }; + + private Integer uidlScrollLeft; + + private PanelServerRPC rpc; + + @Override + public void init() { + rpc = RpcProxy.create(PanelServerRPC.class, this); + VPanel panel = getWidget(); + LayoutManager layoutManager = getLayoutManager(); + + layoutManager.registerDependency(this, panel.captionNode); + layoutManager.registerDependency(this, panel.bottomDecoration); + layoutManager.registerDependency(this, panel.contentNode); + } + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (isRealUpdate(uidl)) { + + // Handle caption displaying and style names, prior generics. + // Affects size calculations + + // Restore default stylenames + getWidget().contentNode.setClassName(VPanel.CLASSNAME + "-content"); + getWidget().bottomDecoration.setClassName(VPanel.CLASSNAME + + "-deco"); + getWidget().captionNode.setClassName(VPanel.CLASSNAME + "-caption"); + boolean hasCaption = false; + if (getState().getCaption() != null + && !"".equals(getState().getCaption())) { + getWidget().setCaption(getState().getCaption()); + hasCaption = true; + } else { + getWidget().setCaption(""); + getWidget().captionNode.setClassName(VPanel.CLASSNAME + + "-nocaption"); + } + + // Add proper stylenames for all elements. This way we can prevent + // unwanted CSS selector inheritance. + final String captionBaseClass = VPanel.CLASSNAME + + (hasCaption ? "-caption" : "-nocaption"); + final String contentBaseClass = VPanel.CLASSNAME + "-content"; + final String decoBaseClass = VPanel.CLASSNAME + "-deco"; + String captionClass = captionBaseClass; + String contentClass = contentBaseClass; + String decoClass = decoBaseClass; + if (getState().hasStyles()) { + for (String style : getState().getStyles()) { + captionClass += " " + captionBaseClass + "-" + style; + contentClass += " " + contentBaseClass + "-" + style; + decoClass += " " + decoBaseClass + "-" + style; + } + } + getWidget().captionNode.setClassName(captionClass); + getWidget().contentNode.setClassName(contentClass); + getWidget().bottomDecoration.setClassName(decoClass); + } + + if (!isRealUpdate(uidl)) { + return; + } + + clickEventHandler.handleEventHandlerRegistration(); + + getWidget().client = client; + getWidget().id = uidl.getId(); + + if (getState().getIcon() != null) { + getWidget().setIconUri(getState().getIcon().getURL(), client); + } else { + getWidget().setIconUri(null, client); + } + + getWidget().setErrorIndicatorVisible( + null != getState().getErrorMessage()); + + // We may have actions attached to this panel + if (uidl.getChildCount() > 0) { + final int cnt = uidl.getChildCount(); + for (int i = 0; i < cnt; i++) { + UIDL childUidl = uidl.getChildUIDL(i); + if (childUidl.getTag().equals("actions")) { + if (getWidget().shortcutHandler == null) { + getWidget().shortcutHandler = new ShortcutActionHandler( + getConnectorId(), client); + } + getWidget().shortcutHandler.updateActionMap(childUidl); + } + } + } + + if (getState().getScrollTop() != getWidget().scrollTop) { + // Sizes are not yet up to date, so changing the scroll position + // is deferred to after the layout phase + uidlScrollTop = getState().getScrollTop(); + } + + if (getState().getScrollLeft() != getWidget().scrollLeft) { + // Sizes are not yet up to date, so changing the scroll position + // is deferred to after the layout phase + uidlScrollLeft = getState().getScrollLeft(); + } + + // And apply tab index + getWidget().contentNode.setTabIndex(getState().getTabIndex()); + } + + public void updateCaption(ComponentConnector component) { + // NOP: layouts caption, errors etc not rendered in Panel + } + + @Override + public VPanel getWidget() { + return (VPanel) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VPanel.class); + } + + public void layout() { + updateSizes(); + } + + void updateSizes() { + VPanel panel = getWidget(); + + LayoutManager layoutManager = getLayoutManager(); + int top = layoutManager.getInnerHeight(panel.captionNode); + int bottom = layoutManager.getInnerHeight(panel.bottomDecoration); + + Style style = panel.getElement().getStyle(); + panel.captionNode.getStyle().setMarginTop(-top, Unit.PX); + panel.bottomDecoration.getStyle().setMarginBottom(-bottom, Unit.PX); + style.setPaddingTop(top, Unit.PX); + style.setPaddingBottom(bottom, Unit.PX); + + // Update scroll positions + panel.contentNode.setScrollTop(panel.scrollTop); + panel.contentNode.setScrollLeft(panel.scrollLeft); + // Read actual value back to ensure update logic is correct + panel.scrollTop = panel.contentNode.getScrollTop(); + panel.scrollLeft = panel.contentNode.getScrollLeft(); + + Util.runWebkitOverflowAutoFix(panel.contentNode); + } + + public void postLayout() { + VPanel panel = getWidget(); + if (uidlScrollTop != null) { + panel.contentNode.setScrollTop(uidlScrollTop.intValue()); + // Read actual value back to ensure update logic is correct + // TODO Does this trigger reflows? + panel.scrollTop = panel.contentNode.getScrollTop(); + uidlScrollTop = null; + } + + if (uidlScrollLeft != null) { + panel.contentNode.setScrollLeft(uidlScrollLeft.intValue()); + // Read actual value back to ensure update logic is correct + // TODO Does this trigger reflows? + panel.scrollLeft = panel.contentNode.getScrollLeft(); + uidlScrollLeft = null; + } + } + + @Override + public PanelState getState() { + return (PanelState) super.getState(); + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + // We always have 1 child, unless the child is hidden + Widget newChildWidget = null; + if (getChildren().size() == 1) { + ComponentConnector newChild = getChildren().get(0); + newChildWidget = newChild.getWidget(); + } + + getWidget().setWidget(newChildWidget); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/panel/VPanel.java b/src/com/vaadin/terminal/gwt/client/ui/panel/VPanel.java new file mode 100644 index 0000000000..e2d3d443a0 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/panel/VPanel.java @@ -0,0 +1,189 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.panel; + +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.event.dom.client.TouchStartEvent; +import com.google.gwt.event.dom.client.TouchStartHandler; +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.SimplePanel; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Focusable; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; + +public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, + Focusable { + + public static final String CLASSNAME = "v-panel"; + + ApplicationConnection client; + + String id; + + final Element captionNode = DOM.createDiv(); + + private final Element captionText = DOM.createSpan(); + + private Icon icon; + + final Element bottomDecoration = DOM.createDiv(); + + final Element contentNode = DOM.createDiv(); + + private Element errorIndicatorElement; + + ShortcutActionHandler shortcutHandler; + + int scrollTop; + + int scrollLeft; + + private TouchScrollDelegate touchScrollDelegate; + + public VPanel() { + super(); + DivElement captionWrap = Document.get().createDivElement(); + captionWrap.appendChild(captionNode); + captionNode.appendChild(captionText); + + captionWrap.setClassName(CLASSNAME + "-captionwrap"); + captionNode.setClassName(CLASSNAME + "-caption"); + contentNode.setClassName(CLASSNAME + "-content"); + bottomDecoration.setClassName(CLASSNAME + "-deco"); + + getElement().appendChild(captionWrap); + + /* + * Make contentNode focusable only by using the setFocus() method. This + * behaviour can be changed by invoking setTabIndex() in the serverside + * implementation + */ + contentNode.setTabIndex(-1); + + getElement().appendChild(contentNode); + + getElement().appendChild(bottomDecoration); + setStyleName(CLASSNAME); + DOM.sinkEvents(getElement(), Event.ONKEYDOWN); + DOM.sinkEvents(contentNode, Event.ONSCROLL | Event.TOUCHEVENTS); + addHandler(new TouchStartHandler() { + public void onTouchStart(TouchStartEvent event) { + getTouchScrollDelegate().onTouchStart(event); + } + }, TouchStartEvent.getType()); + } + + /** + * Sets the keyboard focus on the Panel + * + * @param focus + * Should the panel have focus or not. + */ + public void setFocus(boolean focus) { + if (focus) { + getContainerElement().focus(); + } else { + getContainerElement().blur(); + } + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.Focusable#focus() + */ + public void focus() { + setFocus(true); + + } + + @Override + protected Element getContainerElement() { + return contentNode; + } + + void setCaption(String text) { + DOM.setInnerHTML(captionText, text); + } + + void setErrorIndicatorVisible(boolean showError) { + if (showError) { + if (errorIndicatorElement == null) { + errorIndicatorElement = DOM.createSpan(); + DOM.setElementProperty(errorIndicatorElement, "className", + "v-errorindicator"); + DOM.sinkEvents(errorIndicatorElement, Event.MOUSEEVENTS); + sinkEvents(Event.MOUSEEVENTS); + } + DOM.insertBefore(captionNode, errorIndicatorElement, captionText); + } else if (errorIndicatorElement != null) { + DOM.removeChild(captionNode, errorIndicatorElement); + errorIndicatorElement = null; + } + } + + void setIconUri(String iconUri, ApplicationConnection client) { + if (iconUri == null) { + if (icon != null) { + DOM.removeChild(captionNode, icon.getElement()); + icon = null; + } + } else { + if (icon == null) { + icon = new Icon(client); + DOM.insertChild(captionNode, icon.getElement(), 0); + } + icon.setUri(iconUri); + } + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + + final Element target = DOM.eventGetTarget(event); + final int type = DOM.eventGetType(event); + if (type == Event.ONKEYDOWN && shortcutHandler != null) { + shortcutHandler.handleKeyboardEvent(event); + return; + } + if (type == Event.ONSCROLL) { + int newscrollTop = DOM.getElementPropertyInt(contentNode, + "scrollTop"); + int newscrollLeft = DOM.getElementPropertyInt(contentNode, + "scrollLeft"); + if (client != null + && (newscrollLeft != scrollLeft || newscrollTop != scrollTop)) { + scrollLeft = newscrollLeft; + scrollTop = newscrollTop; + client.updateVariable(id, "scrollTop", scrollTop, false); + client.updateVariable(id, "scrollLeft", scrollLeft, false); + } + } else if (captionNode.isOrHasChild(target)) { + if (client != null) { + client.handleTooltipEvent(event, this); + } + } + } + + protected TouchScrollDelegate getTouchScrollDelegate() { + if (touchScrollDelegate == null) { + touchScrollDelegate = new TouchScrollDelegate(contentNode); + } + return touchScrollDelegate; + + } + + public ShortcutActionHandler getShortcutActionHandler() { + return shortcutHandler; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/passwordfield/PasswordFieldConnector.java b/src/com/vaadin/terminal/gwt/client/ui/passwordfield/PasswordFieldConnector.java new file mode 100644 index 0000000000..088e83c9cc --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/passwordfield/PasswordFieldConnector.java @@ -0,0 +1,32 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.passwordfield; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.textfield.TextFieldConnector; +import com.vaadin.ui.PasswordField; + +@Component(PasswordField.class) +public class PasswordFieldConnector extends TextFieldConnector { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + super.updateFromUIDL(uidl, client); + } + + @Override + protected Widget createWidget() { + return GWT.create(VPasswordField.class); + } + + @Override + public VPasswordField getWidget() { + return (VPasswordField) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/passwordfield/VPasswordField.java b/src/com/vaadin/terminal/gwt/client/ui/passwordfield/VPasswordField.java new file mode 100644 index 0000000000..c160322de5 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/passwordfield/VPasswordField.java @@ -0,0 +1,22 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.passwordfield; + +import com.google.gwt.user.client.DOM; +import com.vaadin.terminal.gwt.client.ui.textfield.VTextField; + +/** + * This class represents a password field. + * + * @author Vaadin Ltd. + * + */ +public class VPasswordField extends VTextField { + + public VPasswordField() { + super(DOM.createInputPassword()); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/popupview/PopupViewConnector.java b/src/com/vaadin/terminal/gwt/client/ui/popupview/PopupViewConnector.java new file mode 100644 index 0000000000..cd3b1d9f47 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/popupview/PopupViewConnector.java @@ -0,0 +1,121 @@ +/* + @VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.popupview; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.VCaptionWrapper; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.PostLayoutListener; +import com.vaadin.ui.PopupView; + +@Component(PopupView.class) +public class PopupViewConnector extends AbstractComponentContainerConnector + implements Paintable, PostLayoutListener { + + private boolean centerAfterLayout = false; + + @Override + public boolean delegateCaptionHandling() { + return false; + } + + /** + * + * + * @see com.vaadin.terminal.gwt.client.ComponentConnector#updateFromUIDL(com.vaadin.terminal.gwt.client.UIDL, + * com.vaadin.terminal.gwt.client.ApplicationConnection) + */ + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + // These are for future server connections + getWidget().client = client; + getWidget().uidlId = uidl.getId(); + + getWidget().hostPopupVisible = uidl + .getBooleanVariable("popupVisibility"); + + getWidget().setHTML(uidl.getStringAttribute("html")); + + if (uidl.hasAttribute("hideOnMouseOut")) { + getWidget().popup.setHideOnMouseOut(uidl + .getBooleanAttribute("hideOnMouseOut")); + } + + // Render the popup if visible and show it. + if (getWidget().hostPopupVisible) { + UIDL popupUIDL = uidl.getChildUIDL(0); + + // showPopupOnTop(popup, hostReference); + getWidget().preparePopup(getWidget().popup); + getWidget().popup.updateFromUIDL(popupUIDL, client); + if (getState().hasStyles()) { + final StringBuffer styleBuf = new StringBuffer(); + final String primaryName = getWidget().popup + .getStylePrimaryName(); + styleBuf.append(primaryName); + for (String style : getState().getStyles()) { + styleBuf.append(" "); + styleBuf.append(primaryName); + styleBuf.append("-"); + styleBuf.append(style); + } + getWidget().popup.setStyleName(styleBuf.toString()); + } else { + getWidget().popup.setStyleName(getWidget().popup + .getStylePrimaryName()); + } + getWidget().showPopup(getWidget().popup); + centerAfterLayout = true; + + // The popup shouldn't be visible, try to hide it. + } else { + getWidget().popup.hide(); + } + }// updateFromUIDL + + public void updateCaption(ComponentConnector component) { + if (VCaption.isNeeded(component.getState())) { + if (getWidget().popup.captionWrapper != null) { + getWidget().popup.captionWrapper.updateCaption(); + } else { + getWidget().popup.captionWrapper = new VCaptionWrapper( + component, getConnection()); + getWidget().popup.setWidget(getWidget().popup.captionWrapper); + getWidget().popup.captionWrapper.updateCaption(); + } + } else { + if (getWidget().popup.captionWrapper != null) { + getWidget().popup + .setWidget(getWidget().popup.popupComponentWidget); + } + } + } + + @Override + public VPopupView getWidget() { + return (VPopupView) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VPopupView.class); + } + + public void postLayout() { + if (centerAfterLayout) { + centerAfterLayout = false; + getWidget().center(); + } + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/popupview/VPopupView.java b/src/com/vaadin/terminal/gwt/client/ui/popupview/VPopupView.java new file mode 100644 index 0000000000..da48975726 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/popupview/VPopupView.java @@ -0,0 +1,348 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.popupview; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.logical.shared.CloseEvent; +import com.google.gwt.event.logical.shared.CloseHandler; +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.Focusable; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.HasWidgets; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.VCaptionWrapper; +import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.VOverlay; +import com.vaadin.terminal.gwt.client.ui.richtextarea.VRichTextArea; + +public class VPopupView extends HTML { + + public static final String CLASSNAME = "v-popupview"; + + /** For server-client communication */ + String uidlId; + ApplicationConnection client; + + /** This variable helps to communicate popup visibility to the server */ + boolean hostPopupVisible; + + final CustomPopup popup; + private final Label loading = new Label(); + + /** + * loading constructor + */ + public VPopupView() { + super(); + popup = new CustomPopup(); + + setStyleName(CLASSNAME); + popup.setStyleName(CLASSNAME + "-popup"); + loading.setStyleName(CLASSNAME + "-loading"); + + setHTML(""); + popup.setWidget(loading); + + // When we click to open the popup... + addClickHandler(new ClickHandler() { + public void onClick(ClickEvent event) { + updateState(true); + } + }); + + // ..and when we close it + popup.addCloseHandler(new CloseHandler() { + public void onClose(CloseEvent event) { + updateState(false); + } + }); + + popup.setAnimationEnabled(true); + sinkEvents(VTooltip.TOOLTIP_EVENTS); + } + + /** + * Update popup visibility to server + * + * @param visibility + */ + private void updateState(boolean visible) { + // If we know the server connection + // then update the current situation + if (uidlId != null && client != null && isAttached()) { + client.updateVariable(uidlId, "popupVisibility", visible, true); + } + } + + void preparePopup(final CustomPopup popup) { + popup.setVisible(false); + popup.show(); + } + + /** + * Determines the correct position for a popup and displays the popup at + * that position. + * + * By default, the popup is shown centered relative to its host component, + * ensuring it is visible on the screen if possible. + * + * Can be overridden to customize the popup position. + * + * @param popup + */ + protected void showPopup(final CustomPopup popup) { + popup.setPopupPosition(0, 0); + + popup.setVisible(true); + } + + void center() { + int windowTop = RootPanel.get().getAbsoluteTop(); + int windowLeft = RootPanel.get().getAbsoluteLeft(); + int windowRight = windowLeft + RootPanel.get().getOffsetWidth(); + int windowBottom = windowTop + RootPanel.get().getOffsetHeight(); + + int offsetWidth = popup.getOffsetWidth(); + int offsetHeight = popup.getOffsetHeight(); + + int hostHorizontalCenter = VPopupView.this.getAbsoluteLeft() + + VPopupView.this.getOffsetWidth() / 2; + int hostVerticalCenter = VPopupView.this.getAbsoluteTop() + + VPopupView.this.getOffsetHeight() / 2; + + int left = hostHorizontalCenter - offsetWidth / 2; + int top = hostVerticalCenter - offsetHeight / 2; + + // Don't show the popup outside the screen. + if ((left + offsetWidth) > windowRight) { + left -= (left + offsetWidth) - windowRight; + } + + if ((top + offsetHeight) > windowBottom) { + top -= (top + offsetHeight) - windowBottom; + } + + if (left < 0) { + left = 0; + } + + if (top < 0) { + top = 0; + } + + popup.setPopupPosition(left, top); + } + + /** + * Make sure that we remove the popup when the main widget is removed. + * + * @see com.google.gwt.user.client.ui.Widget#onUnload() + */ + @Override + protected void onDetach() { + popup.hide(); + super.onDetach(); + } + + private static native void nativeBlur(Element e) + /*-{ + if(e && e.blur) { + e.blur(); + } + }-*/; + + /** + * This class is only protected to enable overriding showPopup, and is + * currently not intended to be extended or otherwise used directly. Its API + * (other than it being a VOverlay) is to be considered private and + * potentially subject to change. + */ + protected class CustomPopup extends VOverlay { + + private ComponentConnector popupComponentPaintable = null; + Widget popupComponentWidget = null; + VCaptionWrapper captionWrapper = null; + + private boolean hasHadMouseOver = false; + private boolean hideOnMouseOut = true; + private final Set activeChildren = new HashSet(); + private boolean hiding = false; + + public CustomPopup() { + super(true, false, true); // autoHide, not modal, dropshadow + } + + // For some reason ONMOUSEOUT events are not always received, so we have + // to use ONMOUSEMOVE that doesn't target the popup + @Override + public boolean onEventPreview(Event event) { + Element target = DOM.eventGetTarget(event); + boolean eventTargetsPopup = DOM.isOrHasChild(getElement(), target); + int type = DOM.eventGetType(event); + + // Catch children that use keyboard, so we can unfocus them when + // hiding + if (eventTargetsPopup && type == Event.ONKEYPRESS) { + activeChildren.add(target); + } + + if (eventTargetsPopup && type == Event.ONMOUSEMOVE) { + hasHadMouseOver = true; + } + + if (!eventTargetsPopup && type == Event.ONMOUSEMOVE) { + if (hasHadMouseOver && hideOnMouseOut) { + hide(); + return true; + } + } + + // Was the TAB key released outside of our popup? + if (!eventTargetsPopup && type == Event.ONKEYUP + && event.getKeyCode() == KeyCodes.KEY_TAB) { + // Should we hide on focus out (mouse out)? + if (hideOnMouseOut) { + hide(); + return true; + } + } + + return super.onEventPreview(event); + } + + @Override + public void hide(boolean autoClosed) { + hiding = true; + syncChildren(); + if (popupComponentWidget != null && popupComponentWidget != loading) { + remove(popupComponentWidget); + } + hasHadMouseOver = false; + super.hide(autoClosed); + } + + @Override + public void show() { + hiding = false; + super.show(); + } + + /** + * Try to sync all known active child widgets to server + */ + public void syncChildren() { + // Notify children with focus + if ((popupComponentWidget instanceof Focusable)) { + ((Focusable) popupComponentWidget).setFocus(false); + } else { + + checkForRTE(popupComponentWidget); + } + + // Notify children that have used the keyboard + for (Element e : activeChildren) { + try { + nativeBlur(e); + } catch (Exception ignored) { + } + } + activeChildren.clear(); + } + + private void checkForRTE(Widget popupComponentWidget2) { + if (popupComponentWidget2 instanceof VRichTextArea) { + ((VRichTextArea) popupComponentWidget2) + .synchronizeContentToServer(); + } else if (popupComponentWidget2 instanceof HasWidgets) { + HasWidgets hw = (HasWidgets) popupComponentWidget2; + Iterator iterator = hw.iterator(); + while (iterator.hasNext()) { + checkForRTE(iterator.next()); + } + } + } + + @Override + public boolean remove(Widget w) { + + popupComponentPaintable = null; + popupComponentWidget = null; + captionWrapper = null; + + return super.remove(w); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + ComponentConnector newPopupComponent = client.getPaintable(uidl + .getChildUIDL(0)); + + if (newPopupComponent != popupComponentPaintable) { + Widget newWidget = newPopupComponent.getWidget(); + setWidget(newWidget); + popupComponentWidget = newWidget; + popupComponentPaintable = newPopupComponent; + } + + } + + public void setHideOnMouseOut(boolean hideOnMouseOut) { + this.hideOnMouseOut = hideOnMouseOut; + } + + /* + * + * We need a hack make popup act as a child of VPopupView in Vaadin's + * component tree, but work in default GWT manner when closing or + * opening. + * + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.Widget#getParent() + */ + @Override + public Widget getParent() { + if (!isAttached() || hiding) { + return super.getParent(); + } else { + return VPopupView.this; + } + } + + @Override + protected void onDetach() { + super.onDetach(); + hiding = false; + } + + @Override + public Element getContainerElement() { + return super.getContainerElement(); + } + + }// class CustomPopup + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (client != null) { + client.handleTooltipEvent(event, this); + } + } + +}// class VPopupView diff --git a/src/com/vaadin/terminal/gwt/client/ui/progressindicator/ProgressIndicatorConnector.java b/src/com/vaadin/terminal/gwt/client/ui/progressindicator/ProgressIndicatorConnector.java new file mode 100644 index 0000000000..2727c0e305 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/progressindicator/ProgressIndicatorConnector.java @@ -0,0 +1,66 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.progressindicator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.ui.ProgressIndicator; + +@Component(ProgressIndicator.class) +public class ProgressIndicatorConnector extends AbstractFieldConnector + implements Paintable { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + if (!isRealUpdate(uidl)) { + return; + } + + // Save details + getWidget().client = client; + + getWidget().indeterminate = uidl.getBooleanAttribute("indeterminate"); + + if (getWidget().indeterminate) { + String basename = VProgressIndicator.CLASSNAME + "-indeterminate"; + getWidget().addStyleName(basename); + if (!isEnabled()) { + getWidget().addStyleName(basename + "-disabled"); + } else { + getWidget().removeStyleName(basename + "-disabled"); + } + } else { + try { + final float f = Float.parseFloat(uidl + .getStringAttribute("state")); + final int size = Math.round(100 * f); + DOM.setStyleAttribute(getWidget().indicator, "width", size + + "%"); + } catch (final Exception e) { + } + } + + if (isEnabled()) { + getWidget().interval = uidl.getIntAttribute("pollinginterval"); + getWidget().poller.scheduleRepeating(getWidget().interval); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VProgressIndicator.class); + } + + @Override + public VProgressIndicator getWidget() { + return (VProgressIndicator) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/progressindicator/VProgressIndicator.java b/src/com/vaadin/terminal/gwt/client/ui/progressindicator/VProgressIndicator.java new file mode 100644 index 0000000000..bc64efb60a --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/progressindicator/VProgressIndicator.java @@ -0,0 +1,71 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.progressindicator; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Util; + +public class VProgressIndicator extends Widget { + + public static final String CLASSNAME = "v-progressindicator"; + Element wrapper = DOM.createDiv(); + Element indicator = DOM.createDiv(); + protected ApplicationConnection client; + protected final Poller poller; + protected boolean indeterminate = false; + private boolean pollerSuspendedDueDetach; + protected int interval; + + public VProgressIndicator() { + setElement(DOM.createDiv()); + getElement().appendChild(wrapper); + setStyleName(CLASSNAME); + wrapper.appendChild(indicator); + indicator.setClassName(CLASSNAME + "-indicator"); + wrapper.setClassName(CLASSNAME + "-wrapper"); + poller = new Poller(); + } + + @Override + protected void onAttach() { + super.onAttach(); + if (pollerSuspendedDueDetach) { + poller.scheduleRepeating(interval); + } + } + + @Override + protected void onDetach() { + super.onDetach(); + if (interval > 0) { + poller.cancel(); + pollerSuspendedDueDetach = true; + } + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (!visible) { + poller.cancel(); + } + } + + class Poller extends Timer { + + @Override + public void run() { + if (!client.hasActiveRequest() + && Util.isAttachedAndDisplayed(VProgressIndicator.this)) { + client.sendPendingVariableChanges(); + } + } + + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java b/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java index 04bfbf28bd..32691b53ec 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java @@ -39,8 +39,8 @@ import com.vaadin.terminal.gwt.client.ui.ClickEventHandler; import com.vaadin.terminal.gwt.client.ui.Component; import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; -import com.vaadin.terminal.gwt.client.ui.VNotification; -import com.vaadin.terminal.gwt.client.ui.WindowConnector; +import com.vaadin.terminal.gwt.client.ui.notification.VNotification; +import com.vaadin.terminal.gwt.client.ui.window.WindowConnector; import com.vaadin.ui.Root; @Component(value = Root.class, loadStyle = LoadStyle.EAGER) diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/RootServerRPC.java b/src/com/vaadin/terminal/gwt/client/ui/root/RootServerRPC.java index 734c624aa1..26cf619766 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/root/RootServerRPC.java +++ b/src/com/vaadin/terminal/gwt/client/ui/root/RootServerRPC.java @@ -1,7 +1,6 @@ package com.vaadin.terminal.gwt.client.ui.root; import com.vaadin.terminal.gwt.client.communication.ServerRpc; -import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector.ClickRPC; public interface RootServerRPC extends ClickRPC, ServerRpc { diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java b/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java index 20263531a3..8182753ab2 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java +++ b/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java @@ -26,7 +26,7 @@ import com.vaadin.terminal.gwt.client.VConsole; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler; import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; -import com.vaadin.terminal.gwt.client.ui.VTextField; +import com.vaadin.terminal.gwt.client.ui.textfield.VTextField; /** * diff --git a/src/com/vaadin/terminal/gwt/client/ui/slider/SliderConnector.java b/src/com/vaadin/terminal/gwt/client/ui/slider/SliderConnector.java new file mode 100644 index 0000000000..8b2e6501df --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/slider/SliderConnector.java @@ -0,0 +1,77 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.slider; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.ui.Slider; + +@Component(Slider.class) +public class SliderConnector extends AbstractFieldConnector implements + Paintable { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + getWidget().client = client; + getWidget().id = uidl.getId(); + + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().immediate = getState().isImmediate(); + getWidget().disabled = !isEnabled(); + getWidget().readonly = isReadOnly(); + + getWidget().vertical = uidl.hasAttribute("vertical"); + + // TODO should style names be used? + + if (getWidget().vertical) { + getWidget().addStyleName(VSlider.CLASSNAME + "-vertical"); + } else { + getWidget().removeStyleName(VSlider.CLASSNAME + "-vertical"); + } + + getWidget().min = uidl.getDoubleAttribute("min"); + getWidget().max = uidl.getDoubleAttribute("max"); + getWidget().resolution = uidl.getIntAttribute("resolution"); + getWidget().value = new Double(uidl.getDoubleVariable("value")); + + getWidget().setFeedbackValue(getWidget().value); + + getWidget().buildBase(); + + if (!getWidget().vertical) { + // Draw handle with a delay to allow base to gain maximum width + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + getWidget().buildHandle(); + getWidget().setValue(getWidget().value, false); + } + }); + } else { + getWidget().buildHandle(); + getWidget().setValue(getWidget().value, false); + } + } + + @Override + public VSlider getWidget() { + return (VSlider) super.getWidget(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VSlider.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/slider/VSlider.java b/src/com/vaadin/terminal/gwt/client/ui/slider/VSlider.java new file mode 100644 index 0000000000..9ff614252d --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/slider/VSlider.java @@ -0,0 +1,516 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +// +package com.vaadin.terminal.gwt.client.ui.slider; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.user.client.Command; +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.Window; +import com.google.gwt.user.client.ui.HTML; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ContainerResizedListener; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.ui.Field; +import com.vaadin.terminal.gwt.client.ui.SimpleFocusablePanel; +import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; +import com.vaadin.terminal.gwt.client.ui.VOverlay; + +public class VSlider extends SimpleFocusablePanel implements Field, + ContainerResizedListener { + + public static final String CLASSNAME = "v-slider"; + + /** + * Minimum size (width or height, depending on orientation) of the slider + * base. + */ + private static final int MIN_SIZE = 50; + + ApplicationConnection client; + + String id; + + boolean immediate; + boolean disabled; + boolean readonly; + + private int acceleration = 1; + double min; + double max; + int resolution; + Double value; + boolean vertical; + + private final HTML feedback = new HTML("", false); + private final VOverlay feedbackPopup = new VOverlay(true, false, true) { + @Override + public void show() { + super.show(); + updateFeedbackPosition(); + } + }; + + /* DOM element for slider's base */ + private final Element base; + private final int BASE_BORDER_WIDTH = 1; + + /* DOM element for slider's handle */ + private final Element handle; + + /* DOM element for decrement arrow */ + private final Element smaller; + + /* DOM element for increment arrow */ + private final Element bigger; + + /* Temporary dragging/animation variables */ + private boolean dragging = false; + + private VLazyExecutor delayedValueUpdater = new VLazyExecutor(100, + new ScheduledCommand() { + + public void execute() { + updateValueToServer(); + acceleration = 1; + } + }); + + public VSlider() { + super(); + + base = DOM.createDiv(); + handle = DOM.createDiv(); + smaller = DOM.createDiv(); + bigger = DOM.createDiv(); + + setStyleName(CLASSNAME); + DOM.setElementProperty(base, "className", CLASSNAME + "-base"); + DOM.setElementProperty(handle, "className", CLASSNAME + "-handle"); + DOM.setElementProperty(smaller, "className", CLASSNAME + "-smaller"); + DOM.setElementProperty(bigger, "className", CLASSNAME + "-bigger"); + + DOM.appendChild(getElement(), bigger); + DOM.appendChild(getElement(), smaller); + DOM.appendChild(getElement(), base); + DOM.appendChild(base, handle); + + // Hide initially + DOM.setStyleAttribute(smaller, "display", "none"); + DOM.setStyleAttribute(bigger, "display", "none"); + DOM.setStyleAttribute(handle, "visibility", "hidden"); + + sinkEvents(Event.MOUSEEVENTS | Event.ONMOUSEWHEEL | Event.KEYEVENTS + | Event.FOCUSEVENTS | Event.TOUCHEVENTS); + + feedbackPopup.addStyleName(CLASSNAME + "-feedback"); + feedbackPopup.setWidget(feedback); + } + + void setFeedbackValue(double value) { + String currentValue = "" + value; + if (resolution == 0) { + currentValue = "" + new Double(value).intValue(); + } + feedback.setText(currentValue); + } + + private void updateFeedbackPosition() { + if (vertical) { + feedbackPopup.setPopupPosition( + DOM.getAbsoluteLeft(handle) + handle.getOffsetWidth(), + DOM.getAbsoluteTop(handle) + handle.getOffsetHeight() / 2 + - feedbackPopup.getOffsetHeight() / 2); + } else { + feedbackPopup.setPopupPosition( + DOM.getAbsoluteLeft(handle) + handle.getOffsetWidth() / 2 + - feedbackPopup.getOffsetWidth() / 2, + DOM.getAbsoluteTop(handle) + - feedbackPopup.getOffsetHeight()); + } + } + + void buildBase() { + final String styleAttribute = vertical ? "height" : "width"; + final String domProperty = vertical ? "offsetHeight" : "offsetWidth"; + + final Element p = DOM.getParent(getElement()); + if (DOM.getElementPropertyInt(p, domProperty) > 50) { + if (vertical) { + setHeight(); + } else { + DOM.setStyleAttribute(base, styleAttribute, ""); + } + } else { + // Set minimum size and adjust after all components have + // (supposedly) been drawn completely. + DOM.setStyleAttribute(base, styleAttribute, MIN_SIZE + "px"); + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + final Element p = DOM.getParent(getElement()); + if (DOM.getElementPropertyInt(p, domProperty) > (MIN_SIZE + 5)) { + if (vertical) { + setHeight(); + } else { + DOM.setStyleAttribute(base, styleAttribute, ""); + } + // Ensure correct position + setValue(value, false); + } + } + }); + } + + // TODO attach listeners for focusing and arrow keys + } + + void buildHandle() { + final String handleAttribute = vertical ? "marginTop" : "marginLeft"; + + DOM.setStyleAttribute(handle, handleAttribute, "0"); + + // Restore visibility + DOM.setStyleAttribute(handle, "visibility", "visible"); + + } + + void setValue(Double value, boolean updateToServer) { + if (value == null) { + return; + } + + if (value < min) { + value = min; + } else if (value > max) { + value = max; + } + + // Update handle position + final String styleAttribute = vertical ? "marginTop" : "marginLeft"; + final String domProperty = vertical ? "offsetHeight" : "offsetWidth"; + final int handleSize = Integer.parseInt(DOM.getElementProperty(handle, + domProperty)); + final int baseSize = Integer.parseInt(DOM.getElementProperty(base, + domProperty)) - (2 * BASE_BORDER_WIDTH); + + final int range = baseSize - handleSize; + double v = value.doubleValue(); + + // Round value to resolution + if (resolution > 0) { + v = Math.round(v * Math.pow(10, resolution)); + v = v / Math.pow(10, resolution); + } else { + v = Math.round(v); + } + final double valueRange = max - min; + double p = 0; + if (valueRange > 0) { + p = range * ((v - min) / valueRange); + } + if (p < 0) { + p = 0; + } + if (vertical) { + p = range - p; + } + final double pos = p; + + DOM.setStyleAttribute(handle, styleAttribute, (Math.round(pos)) + "px"); + + // Update value + this.value = new Double(v); + setFeedbackValue(v); + + if (updateToServer) { + updateValueToServer(); + } + } + + @Override + public void onBrowserEvent(Event event) { + if (disabled || readonly) { + return; + } + final Element targ = DOM.eventGetTarget(event); + + if (DOM.eventGetType(event) == Event.ONMOUSEWHEEL) { + processMouseWheelEvent(event); + } else if (dragging || targ == handle) { + processHandleEvent(event); + } else if (targ == smaller) { + decreaseValue(true); + } else if (targ == bigger) { + increaseValue(true); + } else if (DOM.eventGetType(event) == Event.MOUSEEVENTS) { + processBaseEvent(event); + } else if ((BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYPRESS) + || (!BrowserInfo.get().isGecko() && DOM.eventGetType(event) == Event.ONKEYDOWN)) { + + if (handleNavigation(event.getKeyCode(), event.getCtrlKey(), + event.getShiftKey())) { + + feedbackPopup.show(); + + delayedValueUpdater.trigger(); + + DOM.eventPreventDefault(event); + DOM.eventCancelBubble(event, true); + } + } else if (targ.equals(getElement()) + && DOM.eventGetType(event) == Event.ONFOCUS) { + feedbackPopup.show(); + } else if (targ.equals(getElement()) + && DOM.eventGetType(event) == Event.ONBLUR) { + feedbackPopup.hide(); + } else if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) { + feedbackPopup.show(); + } + if (Util.isTouchEvent(event)) { + event.preventDefault(); // avoid simulated events + event.stopPropagation(); + } + } + + private void processMouseWheelEvent(final Event event) { + final int dir = DOM.eventGetMouseWheelVelocityY(event); + + if (dir < 0) { + increaseValue(false); + } else { + decreaseValue(false); + } + + delayedValueUpdater.trigger(); + + DOM.eventPreventDefault(event); + DOM.eventCancelBubble(event, true); + } + + private void processHandleEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONMOUSEDOWN: + case Event.ONTOUCHSTART: + if (!disabled && !readonly) { + focus(); + feedbackPopup.show(); + dragging = true; + DOM.setElementProperty(handle, "className", CLASSNAME + + "-handle " + CLASSNAME + "-handle-active"); + DOM.setCapture(getElement()); + DOM.eventPreventDefault(event); // prevent selecting text + DOM.eventCancelBubble(event, true); + event.stopPropagation(); + VConsole.log("Slider move start"); + } + break; + case Event.ONMOUSEMOVE: + case Event.ONTOUCHMOVE: + if (dragging) { + VConsole.log("Slider move"); + setValueByEvent(event, false); + updateFeedbackPosition(); + event.stopPropagation(); + } + break; + case Event.ONTOUCHEND: + feedbackPopup.hide(); + case Event.ONMOUSEUP: + // feedbackPopup.hide(); + VConsole.log("Slider move end"); + dragging = false; + DOM.setElementProperty(handle, "className", CLASSNAME + "-handle"); + DOM.releaseCapture(getElement()); + setValueByEvent(event, true); + event.stopPropagation(); + break; + default: + break; + } + } + + private void processBaseEvent(Event event) { + if (DOM.eventGetType(event) == Event.ONMOUSEDOWN) { + if (!disabled && !readonly && !dragging) { + setValueByEvent(event, true); + DOM.eventCancelBubble(event, true); + } + } + } + + private void decreaseValue(boolean updateToServer) { + setValue(new Double(value.doubleValue() - Math.pow(10, -resolution)), + updateToServer); + } + + private void increaseValue(boolean updateToServer) { + setValue(new Double(value.doubleValue() + Math.pow(10, -resolution)), + updateToServer); + } + + private void setValueByEvent(Event event, boolean updateToServer) { + double v = min; // Fallback to min + + final int coord = getEventPosition(event); + + final int handleSize, baseSize, baseOffset; + if (vertical) { + handleSize = handle.getOffsetHeight(); + baseSize = base.getOffsetHeight(); + baseOffset = base.getAbsoluteTop() - Window.getScrollTop() + - handleSize / 2; + } else { + handleSize = handle.getOffsetWidth(); + baseSize = base.getOffsetWidth(); + baseOffset = base.getAbsoluteLeft() - Window.getScrollLeft() + + handleSize / 2; + } + + if (vertical) { + v = ((baseSize - (coord - baseOffset)) / (double) (baseSize - handleSize)) + * (max - min) + min; + } else { + v = ((coord - baseOffset) / (double) (baseSize - handleSize)) + * (max - min) + min; + } + + if (v < min) { + v = min; + } else if (v > max) { + v = max; + } + + setValue(v, updateToServer); + } + + /** + * TODO consider extracting touches support to an impl class specific for + * webkit (only browser that really supports touches). + * + * @param event + * @return + */ + protected int getEventPosition(Event event) { + if (vertical) { + return Util.getTouchOrMouseClientY(event); + } else { + return Util.getTouchOrMouseClientX(event); + } + } + + public void iLayout() { + if (vertical) { + setHeight(); + } + // Update handle position + setValue(value, false); + } + + private void setHeight() { + // Calculate decoration size + DOM.setStyleAttribute(base, "height", "0"); + DOM.setStyleAttribute(base, "overflow", "hidden"); + int h = DOM.getElementPropertyInt(getElement(), "offsetHeight"); + if (h < MIN_SIZE) { + h = MIN_SIZE; + } + DOM.setStyleAttribute(base, "height", h + "px"); + DOM.setStyleAttribute(base, "overflow", ""); + } + + private void updateValueToServer() { + client.updateVariable(id, "value", value.doubleValue(), immediate); + } + + /** + * Handles the keyboard events handled by the Slider + * + * @param event + * The keyboard event received + * @return true iff the navigation event was handled + */ + public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { + + // No support for ctrl moving + if (ctrl) { + return false; + } + + if ((keycode == getNavigationUpKey() && vertical) + || (keycode == getNavigationRightKey() && !vertical)) { + if (shift) { + for (int a = 0; a < acceleration; a++) { + increaseValue(false); + } + acceleration++; + } else { + increaseValue(false); + } + return true; + } else if (keycode == getNavigationDownKey() && vertical + || (keycode == getNavigationLeftKey() && !vertical)) { + if (shift) { + for (int a = 0; a < acceleration; a++) { + decreaseValue(false); + } + acceleration++; + } else { + decreaseValue(false); + } + return true; + } + + return false; + } + + /** + * Get the key that increases the vertical slider. By default it is the up + * arrow key but by overriding this you can change the key to whatever you + * want. + * + * @return The keycode of the key + */ + protected int getNavigationUpKey() { + return KeyCodes.KEY_UP; + } + + /** + * Get the key that decreases the vertical slider. By default it is the down + * arrow key but by overriding this you can change the key to whatever you + * want. + * + * @return The keycode of the key + */ + protected int getNavigationDownKey() { + return KeyCodes.KEY_DOWN; + } + + /** + * Get the key that decreases the horizontal slider. By default it is the + * left arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationLeftKey() { + return KeyCodes.KEY_LEFT; + } + + /** + * Get the key that increases the horizontal slider. By default it is the + * right arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationRightKey() { + return KeyCodes.KEY_RIGHT; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelConnector.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelConnector.java new file mode 100644 index 0000000000..c480e7fb70 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/AbstractSplitPanelConnector.java @@ -0,0 +1,284 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.splitpanel; + +import java.util.LinkedList; + +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.DomEvent; +import com.google.gwt.event.dom.client.DomEvent.Type; +import com.google.gwt.event.shared.EventHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.Connector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ui.ClickEventHandler; +import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout; +import com.vaadin.terminal.gwt.client.ui.splitpanel.VAbstractSplitPanel.SplitterMoveHandler; +import com.vaadin.terminal.gwt.client.ui.splitpanel.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent; + +public abstract class AbstractSplitPanelConnector extends + AbstractComponentContainerConnector implements SimpleManagedLayout { + + public interface AbstractSplitPanelRPC extends ServerRpc { + + /** + * Called when the position has been updated by the user. + * + * @param position + * The new position in % if the current unit is %, in px + * otherwise + */ + public void setSplitterPosition(float position); + + /** + * Called when a click event has occurred on the splitter. + * + * @param mouseDetails + * Details about the mouse when the event took place + */ + public void splitterClick(MouseEventDetails mouseDetails); + + } + + public static class SplitterState { + private float position; + private String positionUnit; + private boolean positionReversed = false; + private boolean locked = false; + + public float getPosition() { + return position; + } + + public void setPosition(float position) { + this.position = position; + } + + public String getPositionUnit() { + return positionUnit; + } + + public void setPositionUnit(String positionUnit) { + this.positionUnit = positionUnit; + } + + public boolean isPositionReversed() { + return positionReversed; + } + + public void setPositionReversed(boolean positionReversed) { + this.positionReversed = positionReversed; + } + + public boolean isLocked() { + return locked; + } + + public void setLocked(boolean locked) { + this.locked = locked; + } + + } + + public static class AbstractSplitPanelState extends ComponentState { + private Connector firstChild = null; + private Connector secondChild = null; + private SplitterState splitterState = new SplitterState(); + + public boolean hasFirstChild() { + return firstChild != null; + } + + public boolean hasSecondChild() { + return secondChild != null; + } + + public Connector getFirstChild() { + return firstChild; + } + + public void setFirstChild(Connector firstChild) { + this.firstChild = firstChild; + } + + public Connector getSecondChild() { + return secondChild; + } + + public void setSecondChild(Connector secondChild) { + this.secondChild = secondChild; + } + + public SplitterState getSplitterState() { + return splitterState; + } + + public void setSplitterState(SplitterState splitterState) { + this.splitterState = splitterState; + } + + } + + private AbstractSplitPanelRPC rpc; + + @Override + protected void init() { + super.init(); + rpc = RpcProxy.create(AbstractSplitPanelRPC.class, this); + // TODO Remove + getWidget().client = getConnection(); + + getWidget().addHandler(new SplitterMoveHandler() { + + public void splitterMoved(SplitterMoveEvent event) { + String position = getWidget().getSplitterPosition(); + float pos = 0; + if (position.indexOf("%") > 0) { + // Send % values as a fraction to avoid that the splitter + // "jumps" when server responds with the integer pct value + // (e.g. dragged 16.6% -> should not jump to 17%) + pos = Float.valueOf(position.substring(0, + position.length() - 1)); + } else { + pos = Integer.parseInt(position.substring(0, + position.length() - 2)); + } + + rpc.setSplitterPosition(pos); + } + + }, SplitterMoveEvent.TYPE); + } + + public void updateCaption(ComponentConnector component) { + // TODO Implement caption handling + } + + ClickEventHandler clickEventHandler = new ClickEventHandler(this) { + + @Override + protected HandlerRegistration registerHandler( + H handler, Type type) { + if ((Event.getEventsSunk(getWidget().splitter) & Event + .getTypeInt(type.getName())) != 0) { + // If we are already sinking the event for the splitter we do + // not want to additionally sink it for the root element + return getWidget().addHandler(handler, type); + } else { + return getWidget().addDomHandler(handler, type); + } + } + + @Override + protected boolean shouldFireEvent(DomEvent event) { + Element target = event.getNativeEvent().getEventTarget().cast(); + if (!getWidget().splitter.isOrHasChild(target)) { + return false; + } + + return super.shouldFireEvent(event); + }; + + @Override + protected Element getRelativeToElement() { + return getWidget().splitter; + }; + + @Override + protected void fireClick(NativeEvent event, + MouseEventDetails mouseDetails) { + rpc.splitterClick(mouseDetails); + } + + }; + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().immediate = getState().isImmediate(); + + getWidget().setEnabled(isEnabled()); + + clickEventHandler.handleEventHandlerRegistration(); + + if (getState().hasStyles()) { + getWidget().componentStyleNames = getState().getStyles(); + } else { + getWidget().componentStyleNames = new LinkedList(); + } + + // Splitter updates + SplitterState splitterState = getState().getSplitterState(); + + getWidget().setLocked(splitterState.isLocked()); + getWidget().setPositionReversed(splitterState.isPositionReversed()); + + getWidget().setStylenames(); + + getWidget().position = splitterState.getPosition() + + splitterState.getPositionUnit(); + + // This is needed at least for cases like #3458 to take + // appearing/disappearing scrollbars into account. + getConnection().runDescendentsLayout(getWidget()); + + getLayoutManager().setNeedsUpdate(this); + + } + + public void layout() { + VAbstractSplitPanel splitPanel = getWidget(); + splitPanel.setSplitPosition(splitPanel.position); + splitPanel.updateSizes(); + } + + @Override + public VAbstractSplitPanel getWidget() { + return (VAbstractSplitPanel) super.getWidget(); + } + + @Override + protected abstract VAbstractSplitPanel createWidget(); + + @Override + public AbstractSplitPanelState getState() { + return (AbstractSplitPanelState) super.getState(); + } + + private ComponentConnector getFirstChild() { + return (ComponentConnector) getState().getFirstChild(); + } + + private ComponentConnector getSecondChild() { + return (ComponentConnector) getState().getSecondChild(); + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + + Widget newFirstChildWidget = null; + if (getFirstChild() != null) { + newFirstChildWidget = getFirstChild().getWidget(); + } + getWidget().setFirstWidget(newFirstChildWidget); + + Widget newSecondChildWidget = null; + if (getSecondChild() != null) { + newSecondChildWidget = getSecondChild().getWidget(); + } + getWidget().setSecondWidget(newSecondChildWidget); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/HorizontalSplitPanelConnector.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/HorizontalSplitPanelConnector.java new file mode 100644 index 0000000000..d1a87874dc --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/HorizontalSplitPanelConnector.java @@ -0,0 +1,19 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.splitpanel; + +import com.google.gwt.core.client.GWT; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; +import com.vaadin.ui.HorizontalSplitPanel; + +@Component(value = HorizontalSplitPanel.class, loadStyle = LoadStyle.EAGER) +public class HorizontalSplitPanelConnector extends AbstractSplitPanelConnector { + + @Override + protected VAbstractSplitPanel createWidget() { + return GWT.create(VSplitPanelHorizontal.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VAbstractSplitPanel.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VAbstractSplitPanel.java new file mode 100644 index 0000000000..6aad93dc5c --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VAbstractSplitPanel.java @@ -0,0 +1,651 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.splitpanel; + +import java.util.List; + +import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.Style; +import com.google.gwt.event.dom.client.TouchCancelEvent; +import com.google.gwt.event.dom.client.TouchCancelHandler; +import com.google.gwt.event.dom.client.TouchEndEvent; +import com.google.gwt.event.dom.client.TouchEndHandler; +import com.google.gwt.event.dom.client.TouchMoveEvent; +import com.google.gwt.event.dom.client.TouchMoveHandler; +import com.google.gwt.event.dom.client.TouchStartEvent; +import com.google.gwt.event.dom.client.TouchStartHandler; +import com.google.gwt.event.shared.EventHandler; +import com.google.gwt.event.shared.GwtEvent; +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.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; +import com.vaadin.terminal.gwt.client.ui.VOverlay; +import com.vaadin.terminal.gwt.client.ui.splitpanel.VAbstractSplitPanel.SplitterMoveHandler.SplitterMoveEvent; + +public class VAbstractSplitPanel extends ComplexPanel { + + private boolean enabled = false; + + public static final String CLASSNAME = "v-splitpanel"; + + public static final int ORIENTATION_HORIZONTAL = 0; + + public static final int ORIENTATION_VERTICAL = 1; + + private static final int MIN_SIZE = 30; + + private int orientation = ORIENTATION_HORIZONTAL; + + Widget firstChild; + + Widget secondChild; + + private final Element wrapper = DOM.createDiv(); + + private final Element firstContainer = DOM.createDiv(); + + private final Element secondContainer = DOM.createDiv(); + + final Element splitter = DOM.createDiv(); + + private boolean resizing; + + private boolean resized = false; + + private int origX; + + private int origY; + + private int origMouseX; + + private int origMouseY; + + private boolean locked = false; + + private boolean positionReversed = false; + + List componentStyleNames; + + private Element draggingCurtain; + + ApplicationConnection client; + + boolean immediate; + + /* The current position of the split handle in either percentages or pixels */ + String position; + + protected Element scrolledContainer; + + protected int origScrollTop; + + private TouchScrollDelegate touchScrollDelegate; + + public VAbstractSplitPanel() { + this(ORIENTATION_HORIZONTAL); + } + + public VAbstractSplitPanel(int orientation) { + setElement(DOM.createDiv()); + switch (orientation) { + case ORIENTATION_HORIZONTAL: + setStyleName(CLASSNAME + "-horizontal"); + break; + case ORIENTATION_VERTICAL: + default: + setStyleName(CLASSNAME + "-vertical"); + break; + } + // size below will be overridden in update from uidl, initial size + // needed to keep IE alive + setWidth(MIN_SIZE + "px"); + setHeight(MIN_SIZE + "px"); + constructDom(); + setOrientation(orientation); + sinkEvents(Event.MOUSEEVENTS); + + addDomHandler(new TouchCancelHandler() { + public void onTouchCancel(TouchCancelEvent event) { + // TODO When does this actually happen?? + VConsole.log("TOUCH CANCEL"); + } + }, TouchCancelEvent.getType()); + addDomHandler(new TouchStartHandler() { + public void onTouchStart(TouchStartEvent event) { + Node target = event.getTouches().get(0).getTarget().cast(); + if (splitter.isOrHasChild(target)) { + onMouseDown(Event.as(event.getNativeEvent())); + } else { + getTouchScrollDelegate().onTouchStart(event); + } + } + + }, TouchStartEvent.getType()); + addDomHandler(new TouchMoveHandler() { + public void onTouchMove(TouchMoveEvent event) { + if (resizing) { + onMouseMove(Event.as(event.getNativeEvent())); + } + } + }, TouchMoveEvent.getType()); + addDomHandler(new TouchEndHandler() { + public void onTouchEnd(TouchEndEvent event) { + if (resizing) { + onMouseUp(Event.as(event.getNativeEvent())); + } + } + }, TouchEndEvent.getType()); + + } + + private TouchScrollDelegate getTouchScrollDelegate() { + if (touchScrollDelegate == null) { + touchScrollDelegate = new TouchScrollDelegate(firstContainer, + secondContainer); + } + return touchScrollDelegate; + } + + protected void constructDom() { + DOM.appendChild(splitter, DOM.createDiv()); // for styling + DOM.appendChild(getElement(), wrapper); + DOM.setStyleAttribute(wrapper, "position", "relative"); + DOM.setStyleAttribute(wrapper, "width", "100%"); + DOM.setStyleAttribute(wrapper, "height", "100%"); + + DOM.appendChild(wrapper, secondContainer); + DOM.appendChild(wrapper, firstContainer); + DOM.appendChild(wrapper, splitter); + + DOM.setStyleAttribute(splitter, "position", "absolute"); + DOM.setStyleAttribute(secondContainer, "position", "absolute"); + + DOM.setStyleAttribute(firstContainer, "overflow", "auto"); + DOM.setStyleAttribute(secondContainer, "overflow", "auto"); + + } + + private void setOrientation(int orientation) { + this.orientation = orientation; + if (orientation == ORIENTATION_HORIZONTAL) { + DOM.setStyleAttribute(splitter, "height", "100%"); + DOM.setStyleAttribute(splitter, "top", "0"); + DOM.setStyleAttribute(firstContainer, "height", "100%"); + DOM.setStyleAttribute(secondContainer, "height", "100%"); + } else { + DOM.setStyleAttribute(splitter, "width", "100%"); + DOM.setStyleAttribute(splitter, "left", "0"); + DOM.setStyleAttribute(firstContainer, "width", "100%"); + DOM.setStyleAttribute(secondContainer, "width", "100%"); + } + + DOM.setElementProperty(firstContainer, "className", CLASSNAME + + "-first-container"); + DOM.setElementProperty(secondContainer, "className", CLASSNAME + + "-second-container"); + } + + @Override + public boolean remove(Widget w) { + boolean removed = super.remove(w); + if (removed) { + if (firstChild == w) { + firstChild = null; + } else { + secondChild = null; + } + } + return removed; + } + + void setLocked(boolean newValue) { + if (locked != newValue) { + locked = newValue; + splitterSize = -1; + setStylenames(); + } + } + + void setPositionReversed(boolean reversed) { + if (positionReversed != reversed) { + if (orientation == ORIENTATION_HORIZONTAL) { + DOM.setStyleAttribute(splitter, "right", ""); + DOM.setStyleAttribute(splitter, "left", ""); + } else if (orientation == ORIENTATION_VERTICAL) { + DOM.setStyleAttribute(splitter, "top", ""); + DOM.setStyleAttribute(splitter, "bottom", ""); + } + + positionReversed = reversed; + } + } + + void setSplitPosition(String pos) { + if (pos == null) { + return; + } + + // Convert percentage values to pixels + if (pos.indexOf("%") > 0) { + int size = orientation == ORIENTATION_HORIZONTAL ? getOffsetWidth() + : getOffsetHeight(); + float percentage = Float.parseFloat(pos.substring(0, + pos.length() - 1)); + pos = percentage / 100 * size + "px"; + } + + String attributeName; + if (orientation == ORIENTATION_HORIZONTAL) { + if (positionReversed) { + attributeName = "right"; + } else { + attributeName = "left"; + } + } else { + if (positionReversed) { + attributeName = "bottom"; + } else { + attributeName = "top"; + } + } + + Style style = splitter.getStyle(); + if (!pos.equals(style.getProperty(attributeName))) { + style.setProperty(attributeName, pos); + updateSizes(); + } + } + + void updateSizes() { + if (!isAttached()) { + return; + } + + int wholeSize; + int pixelPosition; + + switch (orientation) { + case ORIENTATION_HORIZONTAL: + wholeSize = DOM.getElementPropertyInt(wrapper, "clientWidth"); + pixelPosition = DOM.getElementPropertyInt(splitter, "offsetLeft"); + + // reposition splitter in case it is out of box + if ((pixelPosition > 0 && pixelPosition + getSplitterSize() > wholeSize) + || (positionReversed && pixelPosition < 0)) { + pixelPosition = wholeSize - getSplitterSize(); + if (pixelPosition < 0) { + pixelPosition = 0; + } + setSplitPosition(pixelPosition + "px"); + return; + } + + DOM.setStyleAttribute(firstContainer, "width", pixelPosition + "px"); + int secondContainerWidth = (wholeSize - pixelPosition - getSplitterSize()); + if (secondContainerWidth < 0) { + secondContainerWidth = 0; + } + DOM.setStyleAttribute(secondContainer, "width", + secondContainerWidth + "px"); + DOM.setStyleAttribute(secondContainer, "left", + (pixelPosition + getSplitterSize()) + "px"); + + break; + case ORIENTATION_VERTICAL: + wholeSize = DOM.getElementPropertyInt(wrapper, "clientHeight"); + pixelPosition = DOM.getElementPropertyInt(splitter, "offsetTop"); + + // reposition splitter in case it is out of box + if ((pixelPosition > 0 && pixelPosition + getSplitterSize() > wholeSize) + || (positionReversed && pixelPosition < 0)) { + pixelPosition = wholeSize - getSplitterSize(); + if (pixelPosition < 0) { + pixelPosition = 0; + } + setSplitPosition(pixelPosition + "px"); + return; + } + + DOM.setStyleAttribute(firstContainer, "height", pixelPosition + + "px"); + int secondContainerHeight = (wholeSize - pixelPosition - getSplitterSize()); + if (secondContainerHeight < 0) { + secondContainerHeight = 0; + } + DOM.setStyleAttribute(secondContainer, "height", + secondContainerHeight + "px"); + DOM.setStyleAttribute(secondContainer, "top", + (pixelPosition + getSplitterSize()) + "px"); + + break; + } + + } + + void setFirstWidget(Widget w) { + if (firstChild != null) { + firstChild.removeFromParent(); + } + if (w != null) { + super.add(w, firstContainer); + } + firstChild = w; + } + + void setSecondWidget(Widget w) { + if (secondChild != null) { + secondChild.removeFromParent(); + } + if (w != null) { + super.add(w, secondContainer); + } + secondChild = w; + } + + @Override + public void onBrowserEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONMOUSEMOVE: + // case Event.ONTOUCHMOVE: + if (resizing) { + onMouseMove(event); + } + break; + case Event.ONMOUSEDOWN: + // case Event.ONTOUCHSTART: + onMouseDown(event); + break; + case Event.ONMOUSEOUT: + // Dragging curtain interferes with click events if added in + // mousedown so we add it only when needed i.e., if the mouse moves + // outside the splitter. + if (resizing) { + showDraggingCurtain(); + } + break; + case Event.ONMOUSEUP: + // case Event.ONTOUCHEND: + if (resizing) { + onMouseUp(event); + } + break; + case Event.ONCLICK: + resizing = false; + break; + } + // Only fire click event listeners if the splitter isn't moved + if (Util.isTouchEvent(event) || !resized) { + super.onBrowserEvent(event); + } else if (DOM.eventGetType(event) == Event.ONMOUSEUP) { + // Reset the resized flag after a mouseup has occured so the next + // mousedown/mouseup can be interpreted as a click. + resized = false; + } + } + + public void onMouseDown(Event event) { + if (locked || !isEnabled()) { + return; + } + final Element trg = event.getEventTarget().cast(); + if (trg == splitter || trg == DOM.getChild(splitter, 0)) { + resizing = true; + DOM.setCapture(getElement()); + origX = DOM.getElementPropertyInt(splitter, "offsetLeft"); + origY = DOM.getElementPropertyInt(splitter, "offsetTop"); + origMouseX = Util.getTouchOrMouseClientX(event); + origMouseY = Util.getTouchOrMouseClientY(event); + event.stopPropagation(); + event.preventDefault(); + } + } + + public void onMouseMove(Event event) { + switch (orientation) { + case ORIENTATION_HORIZONTAL: + final int x = Util.getTouchOrMouseClientX(event); + onHorizontalMouseMove(x); + break; + case ORIENTATION_VERTICAL: + default: + final int y = Util.getTouchOrMouseClientY(event); + onVerticalMouseMove(y); + break; + } + + } + + private void onHorizontalMouseMove(int x) { + int newX = origX + x - origMouseX; + if (newX < 0) { + newX = 0; + } + if (newX + getSplitterSize() > getOffsetWidth()) { + newX = getOffsetWidth() - getSplitterSize(); + } + + if (position.indexOf("%") > 0) { + float pos = newX; + // 100% needs special handling + if (newX + getSplitterSize() >= getOffsetWidth()) { + pos = getOffsetWidth(); + } + // Reversed position + if (positionReversed) { + pos = getOffsetWidth() - pos - getSplitterSize(); + } + position = (pos / getOffsetWidth() * 100) + "%"; + } else { + // Reversed position + if (positionReversed) { + position = (getOffsetWidth() - newX - getSplitterSize()) + "px"; + } else { + position = newX + "px"; + } + } + + if (origX != newX) { + resized = true; + } + + // Reversed position + if (positionReversed) { + newX = getOffsetWidth() - newX - getSplitterSize(); + } + + setSplitPosition(newX + "px"); + client.doLayout(false); + } + + private void onVerticalMouseMove(int y) { + int newY = origY + y - origMouseY; + if (newY < 0) { + newY = 0; + } + + if (newY + getSplitterSize() > getOffsetHeight()) { + newY = getOffsetHeight() - getSplitterSize(); + } + + if (position.indexOf("%") > 0) { + float pos = newY; + // 100% needs special handling + if (newY + getSplitterSize() >= getOffsetHeight()) { + pos = getOffsetHeight(); + } + // Reversed position + if (positionReversed) { + pos = getOffsetHeight() - pos - getSplitterSize(); + } + position = pos / getOffsetHeight() * 100 + "%"; + } else { + // Reversed position + if (positionReversed) { + position = (getOffsetHeight() - newY - getSplitterSize()) + + "px"; + } else { + position = newY + "px"; + } + } + + if (origY != newY) { + resized = true; + } + + // Reversed position + if (positionReversed) { + newY = getOffsetHeight() - newY - getSplitterSize(); + } + + setSplitPosition(newY + "px"); + client.doLayout(false); + } + + public void onMouseUp(Event event) { + DOM.releaseCapture(getElement()); + hideDraggingCurtain(); + resizing = false; + if (!Util.isTouchEvent(event)) { + onMouseMove(event); + } + fireEvent(new SplitterMoveEvent(this)); + } + + public interface SplitterMoveHandler extends EventHandler { + public void splitterMoved(SplitterMoveEvent event); + + public static class SplitterMoveEvent extends + GwtEvent { + + public static final Type TYPE = new Type(); + + private Widget splitPanel; + + public SplitterMoveEvent(Widget splitPanel) { + this.splitPanel = splitPanel; + } + + @Override + public com.google.gwt.event.shared.GwtEvent.Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(SplitterMoveHandler handler) { + handler.splitterMoved(this); + } + + } + } + + String getSplitterPosition() { + return position; + } + + /** + * Used in FF to avoid losing mouse capture when pointer is moved on an + * iframe. + */ + private void showDraggingCurtain() { + if (!isDraggingCurtainRequired()) { + return; + } + if (draggingCurtain == null) { + draggingCurtain = DOM.createDiv(); + DOM.setStyleAttribute(draggingCurtain, "position", "absolute"); + DOM.setStyleAttribute(draggingCurtain, "top", "0px"); + DOM.setStyleAttribute(draggingCurtain, "left", "0px"); + DOM.setStyleAttribute(draggingCurtain, "width", "100%"); + DOM.setStyleAttribute(draggingCurtain, "height", "100%"); + DOM.setStyleAttribute(draggingCurtain, "zIndex", "" + + VOverlay.Z_INDEX); + + DOM.appendChild(wrapper, draggingCurtain); + } + } + + /** + * A dragging curtain is required in Gecko and Webkit. + * + * @return true if the browser requires a dragging curtain + */ + private boolean isDraggingCurtainRequired() { + return (BrowserInfo.get().isGecko() || BrowserInfo.get().isWebkit()); + } + + /** + * Hides dragging curtain + */ + private void hideDraggingCurtain() { + if (draggingCurtain != null) { + DOM.removeChild(wrapper, draggingCurtain); + draggingCurtain = null; + } + } + + private int splitterSize = -1; + + private int getSplitterSize() { + if (splitterSize < 0) { + if (isAttached()) { + switch (orientation) { + case ORIENTATION_HORIZONTAL: + splitterSize = DOM.getElementPropertyInt(splitter, + "offsetWidth"); + break; + + default: + splitterSize = DOM.getElementPropertyInt(splitter, + "offsetHeight"); + break; + } + } + } + return splitterSize; + } + + void setStylenames() { + final String splitterSuffix = (orientation == ORIENTATION_HORIZONTAL ? "-hsplitter" + : "-vsplitter"); + final String firstContainerSuffix = "-first-container"; + final String secondContainerSuffix = "-second-container"; + String lockedSuffix = ""; + + String splitterStyle = CLASSNAME + splitterSuffix; + String firstStyle = CLASSNAME + firstContainerSuffix; + String secondStyle = CLASSNAME + secondContainerSuffix; + + if (locked) { + splitterStyle = CLASSNAME + splitterSuffix + "-locked"; + lockedSuffix = "-locked"; + } + for (String style : componentStyleNames) { + splitterStyle += " " + CLASSNAME + splitterSuffix + "-" + style + + lockedSuffix; + firstStyle += " " + CLASSNAME + firstContainerSuffix + "-" + style; + secondStyle += " " + CLASSNAME + secondContainerSuffix + "-" + + style; + } + DOM.setElementProperty(splitter, "className", splitterStyle); + DOM.setElementProperty(firstContainer, "className", firstStyle); + DOM.setElementProperty(secondContainer, "className", secondStyle); + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VSplitPanelHorizontal.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VSplitPanelHorizontal.java new file mode 100644 index 0000000000..e19bc6418b --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VSplitPanelHorizontal.java @@ -0,0 +1,13 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.splitpanel; + + +public class VSplitPanelHorizontal extends VAbstractSplitPanel { + + public VSplitPanelHorizontal() { + super(VAbstractSplitPanel.ORIENTATION_HORIZONTAL); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VSplitPanelVertical.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VSplitPanelVertical.java new file mode 100644 index 0000000000..0fcb638630 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VSplitPanelVertical.java @@ -0,0 +1,13 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.splitpanel; + + +public class VSplitPanelVertical extends VAbstractSplitPanel { + + public VSplitPanelVertical() { + super(VAbstractSplitPanel.ORIENTATION_VERTICAL); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VerticalSplitPanelConnector.java b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VerticalSplitPanelConnector.java new file mode 100644 index 0000000000..a9b3e81a1b --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/splitpanel/VerticalSplitPanelConnector.java @@ -0,0 +1,19 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.splitpanel; + +import com.google.gwt.core.client.GWT; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; +import com.vaadin.ui.VerticalSplitPanel; + +@Component(value = VerticalSplitPanel.class, loadStyle = LoadStyle.EAGER) +public class VerticalSplitPanelConnector extends AbstractSplitPanelConnector { + + @Override + protected VAbstractSplitPanel createWidget() { + return GWT.create(VSplitPanelVertical.class); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/table/TableConnector.java b/src/com/vaadin/terminal/gwt/client/ui/table/TableConnector.java new file mode 100644 index 0000000000..76c6c21571 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/table/TableConnector.java @@ -0,0 +1,319 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.table; + +import java.util.Iterator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.AbstractFieldState; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.DirectionalManagedLayout; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.PostLayoutListener; +import com.vaadin.terminal.gwt.client.ui.table.VScrollTable.ContextMenuDetails; +import com.vaadin.terminal.gwt.client.ui.table.VScrollTable.VScrollTableBody.VScrollTableRow; + +@Component(com.vaadin.ui.Table.class) +public class TableConnector extends AbstractComponentContainerConnector + implements Paintable, DirectionalManagedLayout, PostLayoutListener { + + @Override + protected void init() { + super.init(); + getWidget().init(getConnection()); + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.terminal.gwt.client.Paintable#updateFromUIDL(com.vaadin.terminal + * .gwt.client.UIDL, com.vaadin.terminal.gwt.client.ApplicationConnection) + */ + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidget().rendering = true; + + // If a row has an open context menu, it will be closed as the row is + // detached. Retain a reference here so we can restore the menu if + // required. + ContextMenuDetails contextMenuBeforeUpdate = getWidget().contextMenu; + + if (uidl.hasAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_FIRST)) { + getWidget().serverCacheFirst = uidl + .getIntAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_FIRST); + getWidget().serverCacheLast = uidl + .getIntAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_LAST); + } else { + getWidget().serverCacheFirst = -1; + getWidget().serverCacheLast = -1; + } + /* + * We need to do this before updateComponent since updateComponent calls + * this.setHeight() which will calculate a new body height depending on + * the space available. + */ + if (uidl.hasAttribute("colfooters")) { + getWidget().showColFooters = uidl.getBooleanAttribute("colfooters"); + } + + getWidget().tFoot.setVisible(getWidget().showColFooters); + + if (!isRealUpdate(uidl)) { + getWidget().rendering = false; + return; + } + + getWidget().enabled = isEnabled(); + + if (BrowserInfo.get().isIE8() && !getWidget().enabled) { + /* + * The disabled shim will not cover the table body if it is relative + * in IE8. See #7324 + */ + getWidget().scrollBodyPanel.getElement().getStyle() + .setPosition(Position.STATIC); + } else if (BrowserInfo.get().isIE8()) { + getWidget().scrollBodyPanel.getElement().getStyle() + .setPosition(Position.RELATIVE); + } + + getWidget().paintableId = uidl.getStringAttribute("id"); + getWidget().immediate = getState().isImmediate(); + + int previousTotalRows = getWidget().totalRows; + getWidget().updateTotalRows(uidl); + boolean totalRowsChanged = (getWidget().totalRows != previousTotalRows); + + getWidget().updateDragMode(uidl); + + getWidget().updateSelectionProperties(uidl, getState(), isReadOnly()); + + if (uidl.hasAttribute("alb")) { + getWidget().bodyActionKeys = uidl.getStringArrayAttribute("alb"); + } else { + // Need to clear the actions if the action handlers have been + // removed + getWidget().bodyActionKeys = null; + } + + getWidget().setCacheRateFromUIDL(uidl); + + getWidget().recalcWidths = uidl.hasAttribute("recalcWidths"); + if (getWidget().recalcWidths) { + getWidget().tHead.clear(); + getWidget().tFoot.clear(); + } + + getWidget().updatePageLength(uidl); + + getWidget().updateFirstVisibleAndScrollIfNeeded(uidl); + + getWidget().showRowHeaders = uidl.getBooleanAttribute("rowheaders"); + getWidget().showColHeaders = uidl.getBooleanAttribute("colheaders"); + + getWidget().updateSortingProperties(uidl); + + boolean keyboardSelectionOverRowFetchInProgress = getWidget() + .selectSelectedRows(uidl); + + getWidget().updateActionMap(uidl); + + getWidget().updateColumnProperties(uidl); + + UIDL ac = uidl.getChildByTagName("-ac"); + if (ac == null) { + if (getWidget().dropHandler != null) { + // remove dropHandler if not present anymore + getWidget().dropHandler = null; + } + } else { + if (getWidget().dropHandler == null) { + getWidget().dropHandler = getWidget().new VScrollTableDropHandler(); + } + getWidget().dropHandler.updateAcceptRules(ac); + } + + UIDL partialRowAdditions = uidl.getChildByTagName("prows"); + UIDL partialRowUpdates = uidl.getChildByTagName("urows"); + if (partialRowUpdates != null || partialRowAdditions != null) { + // we may have pending cache row fetch, cancel it. See #2136 + getWidget().rowRequestHandler.cancel(); + + getWidget().updateRowsInBody(partialRowUpdates); + getWidget().addAndRemoveRows(partialRowAdditions); + } else { + UIDL rowData = uidl.getChildByTagName("rows"); + if (rowData != null) { + // we may have pending cache row fetch, cancel it. See #2136 + getWidget().rowRequestHandler.cancel(); + + if (!getWidget().recalcWidths + && getWidget().initializedAndAttached) { + getWidget().updateBody(rowData, + uidl.getIntAttribute("firstrow"), + uidl.getIntAttribute("rows")); + if (getWidget().headerChangedDuringUpdate) { + getWidget().triggerLazyColumnAdjustment(true); + } else if (!getWidget().isScrollPositionVisible() + || totalRowsChanged + || getWidget().lastRenderedHeight != getWidget().scrollBody + .getOffsetHeight()) { + // webkits may still bug with their disturbing scrollbar + // bug, see #3457 + // Run overflow fix for the scrollable area + // #6698 - If there's a scroll going on, don't abort it + // by changing overflows as the length of the contents + // *shouldn't* have changed (unless the number of rows + // or the height of the widget has also changed) + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + Util.runWebkitOverflowAutoFix(getWidget().scrollBodyPanel + .getElement()); + } + }); + } + } else { + getWidget().initializeRows(uidl, rowData); + } + } + } + + // If a row had an open context menu before the update, and after the + // update there's a row with the same key as that row, restore the + // context menu. See #8526. + showSavedContextMenu(contextMenuBeforeUpdate); + + if (!getWidget().isSelectable()) { + getWidget().scrollBody.addStyleName(VScrollTable.CLASSNAME + + "-body-noselection"); + } else { + getWidget().scrollBody.removeStyleName(VScrollTable.CLASSNAME + + "-body-noselection"); + } + + getWidget().hideScrollPositionAnnotation(); + + // selection is no in sync with server, avoid excessive server visits by + // clearing to flag used during the normal operation + if (!keyboardSelectionOverRowFetchInProgress) { + getWidget().selectionChanged = false; + } + + /* + * This is called when the Home or page up button has been pressed in + * selectable mode and the next selected row was not yet rendered in the + * client + */ + if (getWidget().selectFirstItemInNextRender + || getWidget().focusFirstItemInNextRender) { + getWidget().selectFirstRenderedRowInViewPort( + getWidget().focusFirstItemInNextRender); + getWidget().selectFirstItemInNextRender = getWidget().focusFirstItemInNextRender = false; + } + + /* + * This is called when the page down or end button has been pressed in + * selectable mode and the next selected row was not yet rendered in the + * client + */ + if (getWidget().selectLastItemInNextRender + || getWidget().focusLastItemInNextRender) { + getWidget().selectLastRenderedRowInViewPort( + getWidget().focusLastItemInNextRender); + getWidget().selectLastItemInNextRender = getWidget().focusLastItemInNextRender = false; + } + getWidget().multiselectPending = false; + + if (getWidget().focusedRow != null) { + if (!getWidget().focusedRow.isAttached() + && !getWidget().rowRequestHandler.isRunning()) { + // focused row has been orphaned, can't focus + getWidget().focusRowFromBody(); + } + } + + getWidget().tabIndex = uidl.hasAttribute("tabindex") ? uidl + .getIntAttribute("tabindex") : 0; + getWidget().setProperTabIndex(); + + getWidget().resizeSortedColumnForSortIndicator(); + + // Remember this to detect situations where overflow hack might be + // needed during scrolling + getWidget().lastRenderedHeight = getWidget().scrollBody + .getOffsetHeight(); + + getWidget().rendering = false; + getWidget().headerChangedDuringUpdate = false; + + } + + @Override + protected Widget createWidget() { + return GWT.create(VScrollTable.class); + } + + @Override + public VScrollTable getWidget() { + return (VScrollTable) super.getWidget(); + } + + public void updateCaption(ComponentConnector component) { + // NOP, not rendered + } + + public void layoutVertically() { + getWidget().updateHeight(); + } + + public void layoutHorizontally() { + getWidget().updateWidth(); + } + + public void postLayout() { + getWidget().sizeInit(); + } + + @Override + public boolean isReadOnly() { + return super.isReadOnly() || getState().isPropertyReadOnly(); + } + + @Override + public AbstractFieldState getState() { + return (AbstractFieldState) super.getState(); + } + + /** + * Shows a saved row context menu if the row for the context menu is still + * visible. Does nothing if a context menu has not been saved. + * + * @param savedContextMenu + */ + public void showSavedContextMenu(ContextMenuDetails savedContextMenu) { + if (isEnabled() && savedContextMenu != null) { + Iterator iterator = getWidget().scrollBody.iterator(); + while (iterator.hasNext()) { + Widget w = iterator.next(); + VScrollTableRow row = (VScrollTableRow) w; + if (row.getKey().equals(savedContextMenu.rowKey)) { + getWidget().contextMenu = savedContextMenu; + getConnection().getContextMenu().showAt(row, + savedContextMenu.left, savedContextMenu.top); + } + } + } + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java new file mode 100644 index 0000000000..fbe1ef2f27 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/table/VScrollTable.java @@ -0,0 +1,6683 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.table; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.NodeList; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.dom.client.TableCellElement; +import com.google.gwt.dom.client.TableRowElement; +import com.google.gwt.dom.client.TableSectionElement; +import com.google.gwt.dom.client.Touch; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.ContextMenuEvent; +import com.google.gwt.event.dom.client.ContextMenuHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.KeyPressEvent; +import com.google.gwt.event.dom.client.KeyPressHandler; +import com.google.gwt.event.dom.client.KeyUpEvent; +import com.google.gwt.event.dom.client.KeyUpHandler; +import com.google.gwt.event.dom.client.ScrollEvent; +import com.google.gwt.event.dom.client.ScrollHandler; +import com.google.gwt.event.dom.client.TouchStartEvent; +import com.google.gwt.event.dom.client.TouchStartHandler; +import com.google.gwt.event.logical.shared.CloseEvent; +import com.google.gwt.event.logical.shared.CloseHandler; +import com.google.gwt.user.client.Command; +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.Timer; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HasWidgets; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.UIObject; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.Focusable; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; +import com.vaadin.terminal.gwt.client.TooltipInfo; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.Action; +import com.vaadin.terminal.gwt.client.ui.ActionOwner; +import com.vaadin.terminal.gwt.client.ui.FocusableScrollPanel; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; +import com.vaadin.terminal.gwt.client.ui.TreeAction; +import com.vaadin.terminal.gwt.client.ui.dd.DDUtil; +import com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler; +import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCallback; +import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager; +import com.vaadin.terminal.gwt.client.ui.dd.VDragEvent; +import com.vaadin.terminal.gwt.client.ui.dd.VHasDropHandler; +import com.vaadin.terminal.gwt.client.ui.dd.VTransferable; +import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; +import com.vaadin.terminal.gwt.client.ui.embedded.VEmbedded; +import com.vaadin.terminal.gwt.client.ui.label.VLabel; +import com.vaadin.terminal.gwt.client.ui.table.VScrollTable.VScrollTableBody.VScrollTableRow; +import com.vaadin.terminal.gwt.client.ui.textfield.VTextField; + +/** + * VScrollTable + * + * VScrollTable is a FlowPanel having two widgets in it: * TableHead component * + * ScrollPanel + * + * TableHead contains table's header and widgets + logic for resizing, + * reordering and hiding columns. + * + * ScrollPanel contains VScrollTableBody object which handles content. To save + * some bandwidth and to improve clients responsiveness with loads of data, in + * VScrollTableBody all rows are not necessary rendered. There are "spacers" in + * VScrollTableBody to use the exact same space as non-rendered rows would use. + * This way we can use seamlessly traditional scrollbars and scrolling to fetch + * more rows instead of "paging". + * + * In VScrollTable we listen to scroll events. On horizontal scrolling we also + * update TableHeads scroll position which has its scrollbars hidden. On + * vertical scroll events we will check if we are reaching the end of area where + * we have rows rendered and + * + * TODO implement unregistering for child components in Cells + */ +public class VScrollTable extends FlowPanel implements HasWidgets, + ScrollHandler, VHasDropHandler, FocusHandler, BlurHandler, Focusable, + ActionOwner { + + public enum SelectMode { + NONE(0), SINGLE(1), MULTI(2); + private int id; + + private SelectMode(int id) { + this.id = id; + } + + public int getId() { + return id; + } + } + + private static final String ROW_HEADER_COLUMN_KEY = "0"; + + public static final String CLASSNAME = "v-table"; + public static final String CLASSNAME_SELECTION_FOCUS = CLASSNAME + "-focus"; + + public static final String ATTRIBUTE_PAGEBUFFER_FIRST = "pb-ft"; + public static final String ATTRIBUTE_PAGEBUFFER_LAST = "pb-l"; + + public static final String ITEM_CLICK_EVENT_ID = "itemClick"; + public static final String HEADER_CLICK_EVENT_ID = "handleHeaderClick"; + public static final String FOOTER_CLICK_EVENT_ID = "handleFooterClick"; + public static final String COLUMN_RESIZE_EVENT_ID = "columnResize"; + public static final String COLUMN_REORDER_EVENT_ID = "columnReorder"; + + private static final double CACHE_RATE_DEFAULT = 2; + + /** + * The default multi select mode where simple left clicks only selects one + * item, CTRL+left click selects multiple items and SHIFT-left click selects + * a range of items. + */ + private static final int MULTISELECT_MODE_DEFAULT = 0; + + /** + * The simple multiselect mode is what the table used to have before + * ctrl/shift selections were added. That is that when this is set clicking + * on an item selects/deselects the item and no ctrl/shift selections are + * available. + */ + private static final int MULTISELECT_MODE_SIMPLE = 1; + + /** + * multiple of pagelength which component will cache when requesting more + * rows + */ + private double cache_rate = CACHE_RATE_DEFAULT; + /** + * fraction of pageLenght which can be scrolled without making new request + */ + private double cache_react_rate = 0.75 * cache_rate; + + public static final char ALIGN_CENTER = 'c'; + public static final char ALIGN_LEFT = 'b'; + public static final char ALIGN_RIGHT = 'e'; + private static final int CHARCODE_SPACE = 32; + private int firstRowInViewPort = 0; + private int pageLength = 15; + private int lastRequestedFirstvisible = 0; // to detect "serverside scroll" + + protected boolean showRowHeaders = false; + + private String[] columnOrder; + + protected ApplicationConnection client; + protected String paintableId; + + boolean immediate; + private boolean nullSelectionAllowed = true; + + private SelectMode selectMode = SelectMode.NONE; + + private final HashSet selectedRowKeys = new HashSet(); + + /* + * When scrolling and selecting at the same time, the selections are not in + * sync with the server while retrieving new rows (until key is released). + */ + private HashSet unSyncedselectionsBeforeRowFetch; + + /* + * These are used when jumping between pages when pressing Home and End + */ + boolean selectLastItemInNextRender = false; + boolean selectFirstItemInNextRender = false; + boolean focusFirstItemInNextRender = false; + boolean focusLastItemInNextRender = false; + + /* + * The currently focused row + */ + VScrollTableRow focusedRow; + + /* + * Helper to store selection range start in when using the keyboard + */ + private VScrollTableRow selectionRangeStart; + + /* + * Flag for notifying when the selection has changed and should be sent to + * the server + */ + boolean selectionChanged = false; + + /* + * The speed (in pixels) which the scrolling scrolls vertically/horizontally + */ + private int scrollingVelocity = 10; + + private Timer scrollingVelocityTimer = null; + + String[] bodyActionKeys; + + private boolean enableDebug = false; + + /** + * Represents a select range of rows + */ + private class SelectionRange { + private VScrollTableRow startRow; + private final int length; + + /** + * Constuctor. + */ + public SelectionRange(VScrollTableRow row1, VScrollTableRow row2) { + VScrollTableRow endRow; + if (row2.isBefore(row1)) { + startRow = row2; + endRow = row1; + } else { + startRow = row1; + endRow = row2; + } + length = endRow.getIndex() - startRow.getIndex() + 1; + } + + public SelectionRange(VScrollTableRow row, int length) { + startRow = row; + this.length = length; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return startRow.getKey() + "-" + length; + } + + private boolean inRange(VScrollTableRow row) { + return row.getIndex() >= startRow.getIndex() + && row.getIndex() < startRow.getIndex() + length; + } + + public Collection split(VScrollTableRow row) { + assert row.isAttached(); + ArrayList ranges = new ArrayList(2); + + int endOfFirstRange = row.getIndex() - 1; + if (!(endOfFirstRange - startRow.getIndex() < 0)) { + // create range of first part unless its length is < 1 + ranges.add(new SelectionRange(startRow, endOfFirstRange + - startRow.getIndex() + 1)); + } + int startOfSecondRange = row.getIndex() + 1; + if (!(getEndIndex() - startOfSecondRange < 0)) { + // create range of second part unless its length is < 1 + VScrollTableRow startOfRange = scrollBody + .getRowByRowIndex(startOfSecondRange); + ranges.add(new SelectionRange(startOfRange, getEndIndex() + - startOfSecondRange + 1)); + } + return ranges; + } + + private int getEndIndex() { + return startRow.getIndex() + length - 1; + } + + }; + + private final HashSet selectedRowRanges = new HashSet(); + + boolean initializedAndAttached = false; + + /** + * Flag to indicate if a column width recalculation is needed due update. + */ + boolean headerChangedDuringUpdate = false; + + protected final TableHead tHead = new TableHead(); + + final TableFooter tFoot = new TableFooter(); + + final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel(true); + + private KeyPressHandler navKeyPressHandler = new KeyPressHandler() { + public void onKeyPress(KeyPressEvent keyPressEvent) { + // This is used for Firefox only, since Firefox auto-repeat + // works correctly only if we use a key press handler, other + // browsers handle it correctly when using a key down handler + if (!BrowserInfo.get().isGecko()) { + return; + } + + NativeEvent event = keyPressEvent.getNativeEvent(); + if (!enabled) { + // Cancel default keyboard events on a disabled Table + // (prevents scrolling) + event.preventDefault(); + } else if (hasFocus) { + // Key code in Firefox/onKeyPress is present only for + // special keys, otherwise 0 is returned + int keyCode = event.getKeyCode(); + if (keyCode == 0 && event.getCharCode() == ' ') { + // Provide a keyCode for space to be compatible with + // FireFox keypress event + keyCode = CHARCODE_SPACE; + } + + if (handleNavigation(keyCode, + event.getCtrlKey() || event.getMetaKey(), + event.getShiftKey())) { + event.preventDefault(); + } + + startScrollingVelocityTimer(); + } + } + + }; + + private KeyUpHandler navKeyUpHandler = new KeyUpHandler() { + + public void onKeyUp(KeyUpEvent keyUpEvent) { + NativeEvent event = keyUpEvent.getNativeEvent(); + int keyCode = event.getKeyCode(); + + if (!isFocusable()) { + cancelScrollingVelocityTimer(); + } else if (isNavigationKey(keyCode)) { + if (keyCode == getNavigationDownKey() + || keyCode == getNavigationUpKey()) { + /* + * in multiselect mode the server may still have value from + * previous page. Clear it unless doing multiselection or + * just moving focus. + */ + if (!event.getShiftKey() && !event.getCtrlKey()) { + instructServerToForgetPreviousSelections(); + } + sendSelectedRows(); + } + cancelScrollingVelocityTimer(); + navKeyDown = false; + } + } + }; + + private KeyDownHandler navKeyDownHandler = new KeyDownHandler() { + + public void onKeyDown(KeyDownEvent keyDownEvent) { + NativeEvent event = keyDownEvent.getNativeEvent(); + // This is not used for Firefox + if (BrowserInfo.get().isGecko()) { + return; + } + + if (!enabled) { + // Cancel default keyboard events on a disabled Table + // (prevents scrolling) + event.preventDefault(); + } else if (hasFocus) { + if (handleNavigation(event.getKeyCode(), event.getCtrlKey() + || event.getMetaKey(), event.getShiftKey())) { + navKeyDown = true; + event.preventDefault(); + } + + startScrollingVelocityTimer(); + } + } + }; + int totalRows; + + private Set collapsedColumns; + + final RowRequestHandler rowRequestHandler; + VScrollTableBody scrollBody; + private int firstvisible = 0; + private boolean sortAscending; + private String sortColumn; + private String oldSortColumn; + private boolean columnReordering; + + /** + * This map contains captions and icon urls for actions like: * "33_c" -> + * "Edit" * "33_i" -> "http://dom.com/edit.png" + */ + private final HashMap actionMap = new HashMap(); + private String[] visibleColOrder; + private boolean initialContentReceived = false; + private Element scrollPositionElement; + boolean enabled; + boolean showColHeaders; + boolean showColFooters; + + /** flag to indicate that table body has changed */ + private boolean isNewBody = true; + + /* + * Read from the "recalcWidths" -attribute. When it is true, the table will + * recalculate the widths for columns - desirable in some cases. For #1983, + * marked experimental. + */ + boolean recalcWidths = false; + + boolean rendering = false; + private boolean hasFocus = false; + private int dragmode; + + private int multiselectmode; + int tabIndex; + private TouchScrollDelegate touchScrollDelegate; + + int lastRenderedHeight; + + /** + * Values (serverCacheFirst+serverCacheLast) sent by server that tells which + * rows (indexes) are in the server side cache (page buffer). -1 means + * unknown. The server side cache row MUST MATCH the client side cache rows. + * + * If the client side cache contains additional rows with e.g. buttons, it + * will cause out of sync when such a button is pressed. + * + * If the server side cache contains additional rows with e.g. buttons, + * scrolling in the client will cause empty buttons to be rendered + * (cached=true request for non-existing components) + */ + int serverCacheFirst = -1; + int serverCacheLast = -1; + + private boolean sizeNeedsInit = true; + + /** + * Used to recall the position of an open context menu if we need to close + * and reopen it during a row update. + */ + class ContextMenuDetails { + String rowKey; + int left; + int top; + + ContextMenuDetails(String rowKey, int left, int top) { + this.rowKey = rowKey; + this.left = left; + this.top = top; + } + } + + protected ContextMenuDetails contextMenu = null; + + public VScrollTable() { + setMultiSelectMode(MULTISELECT_MODE_DEFAULT); + + scrollBodyPanel.setStyleName(CLASSNAME + "-body-wrapper"); + scrollBodyPanel.addFocusHandler(this); + scrollBodyPanel.addBlurHandler(this); + + scrollBodyPanel.addScrollHandler(this); + scrollBodyPanel.setStyleName(CLASSNAME + "-body"); + + /* + * Firefox auto-repeat works correctly only if we use a key press + * handler, other browsers handle it correctly when using a key down + * handler + */ + if (BrowserInfo.get().isGecko()) { + scrollBodyPanel.addKeyPressHandler(navKeyPressHandler); + } else { + scrollBodyPanel.addKeyDownHandler(navKeyDownHandler); + } + scrollBodyPanel.addKeyUpHandler(navKeyUpHandler); + + scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS); + scrollBodyPanel.addDomHandler(new TouchStartHandler() { + public void onTouchStart(TouchStartEvent event) { + getTouchScrollDelegate().onTouchStart(event); + } + }, TouchStartEvent.getType()); + + scrollBodyPanel.sinkEvents(Event.ONCONTEXTMENU); + scrollBodyPanel.addDomHandler(new ContextMenuHandler() { + public void onContextMenu(ContextMenuEvent event) { + handleBodyContextMenu(event); + } + }, ContextMenuEvent.getType()); + + setStyleName(CLASSNAME); + + add(tHead); + add(scrollBodyPanel); + add(tFoot); + + rowRequestHandler = new RowRequestHandler(); + } + + public void init(ApplicationConnection client) { + this.client = client; + // Add a handler to clear saved context menu details when the menu + // closes. See #8526. + client.getContextMenu().addCloseHandler(new CloseHandler() { + public void onClose(CloseEvent event) { + contextMenu = null; + } + }); + } + + protected TouchScrollDelegate getTouchScrollDelegate() { + if (touchScrollDelegate == null) { + touchScrollDelegate = new TouchScrollDelegate( + scrollBodyPanel.getElement()); + } + return touchScrollDelegate; + + } + + private void handleBodyContextMenu(ContextMenuEvent event) { + if (enabled && bodyActionKeys != null) { + int left = Util.getTouchOrMouseClientX(event.getNativeEvent()); + int top = Util.getTouchOrMouseClientY(event.getNativeEvent()); + top += Window.getScrollTop(); + left += Window.getScrollLeft(); + client.getContextMenu().showAt(this, left, top); + + // Only prevent browser context menu if there are action handlers + // registered + event.stopPropagation(); + event.preventDefault(); + } + } + + /** + * Fires a column resize event which sends the resize information to the + * server. + * + * @param columnId + * The columnId of the column which was resized + * @param originalWidth + * The width in pixels of the column before the resize event + * @param newWidth + * The width in pixels of the column after the resize event + */ + private void fireColumnResizeEvent(String columnId, int originalWidth, + int newWidth) { + client.updateVariable(paintableId, "columnResizeEventColumn", columnId, + false); + client.updateVariable(paintableId, "columnResizeEventPrev", + originalWidth, false); + client.updateVariable(paintableId, "columnResizeEventCurr", newWidth, + immediate); + + } + + /** + * Non-immediate variable update of column widths for a collection of + * columns. + * + * @param columns + * the columns to trigger the events for. + */ + private void sendColumnWidthUpdates(Collection columns) { + String[] newSizes = new String[columns.size()]; + int ix = 0; + for (HeaderCell cell : columns) { + newSizes[ix++] = cell.getColKey() + ":" + cell.getWidth(); + } + client.updateVariable(paintableId, "columnWidthUpdates", newSizes, + false); + } + + /** + * Moves the focus one step down + * + * @return Returns true if succeeded + */ + private boolean moveFocusDown() { + return moveFocusDown(0); + } + + /** + * Moves the focus down by 1+offset rows + * + * @return Returns true if succeeded, else false if the selection could not + * be move downwards + */ + private boolean moveFocusDown(int offset) { + if (isSelectable()) { + if (focusedRow == null && scrollBody.iterator().hasNext()) { + // FIXME should focus first visible from top, not first rendered + // ?? + return setRowFocus((VScrollTableRow) scrollBody.iterator() + .next()); + } else { + VScrollTableRow next = getNextRow(focusedRow, offset); + if (next != null) { + return setRowFocus(next); + } + } + } + + return false; + } + + /** + * Moves the selection one step up + * + * @return Returns true if succeeded + */ + private boolean moveFocusUp() { + return moveFocusUp(0); + } + + /** + * Moves the focus row upwards + * + * @return Returns true if succeeded, else false if the selection could not + * be move upwards + * + */ + private boolean moveFocusUp(int offset) { + if (isSelectable()) { + if (focusedRow == null && scrollBody.iterator().hasNext()) { + // FIXME logic is exactly the same as in moveFocusDown, should + // be the opposite?? + return setRowFocus((VScrollTableRow) scrollBody.iterator() + .next()); + } else { + VScrollTableRow prev = getPreviousRow(focusedRow, offset); + if (prev != null) { + return setRowFocus(prev); + } else { + VConsole.log("no previous available"); + } + } + } + + return false; + } + + /** + * Selects a row where the current selection head is + * + * @param ctrlSelect + * Is the selection a ctrl+selection + * @param shiftSelect + * Is the selection a shift+selection + * @return Returns truw + */ + private void selectFocusedRow(boolean ctrlSelect, boolean shiftSelect) { + if (focusedRow != null) { + // Arrows moves the selection and clears previous selections + if (isSelectable() && !ctrlSelect && !shiftSelect) { + deselectAll(); + focusedRow.toggleSelection(); + selectionRangeStart = focusedRow; + } else if (isSelectable() && ctrlSelect && !shiftSelect) { + // Ctrl+arrows moves selection head + selectionRangeStart = focusedRow; + // No selection, only selection head is moved + } else if (isMultiSelectModeAny() && !ctrlSelect && shiftSelect) { + // Shift+arrows selection selects a range + focusedRow.toggleShiftSelection(shiftSelect); + } + } + } + + /** + * Sends the selection to the server if changed since the last update/visit. + */ + protected void sendSelectedRows() { + sendSelectedRows(immediate); + } + + /** + * Sends the selection to the server if it has been changed since the last + * update/visit. + * + * @param immediately + * set to true to immediately send the rows + */ + protected void sendSelectedRows(boolean immediately) { + // Don't send anything if selection has not changed + if (!selectionChanged) { + return; + } + + // Reset selection changed flag + selectionChanged = false; + + // Note: changing the immediateness of this might require changes to + // "clickEvent" immediateness also. + if (isMultiSelectModeDefault()) { + // Convert ranges to a set of strings + Set ranges = new HashSet(); + for (SelectionRange range : selectedRowRanges) { + ranges.add(range.toString()); + } + + // Send the selected row ranges + client.updateVariable(paintableId, "selectedRanges", + ranges.toArray(new String[selectedRowRanges.size()]), false); + + // clean selectedRowKeys so that they don't contain excess values + for (Iterator iterator = selectedRowKeys.iterator(); iterator + .hasNext();) { + String key = iterator.next(); + VScrollTableRow renderedRowByKey = getRenderedRowByKey(key); + if (renderedRowByKey != null) { + for (SelectionRange range : selectedRowRanges) { + if (range.inRange(renderedRowByKey)) { + iterator.remove(); + } + } + } else { + // orphaned selected key, must be in a range, ignore + iterator.remove(); + } + + } + } + + // Send the selected rows + client.updateVariable(paintableId, "selected", + selectedRowKeys.toArray(new String[selectedRowKeys.size()]), + immediately); + + } + + /** + * Get the key that moves the selection head upwards. By default it is the + * up arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationUpKey() { + return KeyCodes.KEY_UP; + } + + /** + * Get the key that moves the selection head downwards. By default it is the + * down arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationDownKey() { + return KeyCodes.KEY_DOWN; + } + + /** + * Get the key that scrolls to the left in the table. By default it is the + * left arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationLeftKey() { + return KeyCodes.KEY_LEFT; + } + + /** + * Get the key that scroll to the right on the table. By default it is the + * right arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationRightKey() { + return KeyCodes.KEY_RIGHT; + } + + /** + * Get the key that selects an item in the table. By default it is the space + * bar key but by overriding this you can change the key to whatever you + * want. + * + * @return + */ + protected int getNavigationSelectKey() { + return CHARCODE_SPACE; + } + + /** + * Get the key the moves the selection one page up in the table. By default + * this is the Page Up key but by overriding this you can change the key to + * whatever you want. + * + * @return + */ + protected int getNavigationPageUpKey() { + return KeyCodes.KEY_PAGEUP; + } + + /** + * Get the key the moves the selection one page down in the table. By + * default this is the Page Down key but by overriding this you can change + * the key to whatever you want. + * + * @return + */ + protected int getNavigationPageDownKey() { + return KeyCodes.KEY_PAGEDOWN; + } + + /** + * Get the key the moves the selection to the beginning of the table. By + * default this is the Home key but by overriding this you can change the + * key to whatever you want. + * + * @return + */ + protected int getNavigationStartKey() { + return KeyCodes.KEY_HOME; + } + + /** + * Get the key the moves the selection to the end of the table. By default + * this is the End key but by overriding this you can change the key to + * whatever you want. + * + * @return + */ + protected int getNavigationEndKey() { + return KeyCodes.KEY_END; + } + + void initializeRows(UIDL uidl, UIDL rowData) { + if (scrollBody != null) { + scrollBody.removeFromParent(); + } + scrollBody = createScrollBody(); + + scrollBody.renderInitialRows(rowData, uidl.getIntAttribute("firstrow"), + uidl.getIntAttribute("rows")); + scrollBodyPanel.add(scrollBody); + + // New body starts scrolled to the left, make sure the header and footer + // are also scrolled to the left + tHead.setHorizontalScrollPosition(0); + tFoot.setHorizontalScrollPosition(0); + + initialContentReceived = true; + sizeNeedsInit = true; + scrollBody.restoreRowVisibility(); + } + + void updateColumnProperties(UIDL uidl) { + updateColumnOrder(uidl); + + updateCollapsedColumns(uidl); + + UIDL vc = uidl.getChildByTagName("visiblecolumns"); + if (vc != null) { + tHead.updateCellsFromUIDL(vc); + tFoot.updateCellsFromUIDL(vc); + } + + updateHeader(uidl.getStringArrayAttribute("vcolorder")); + updateFooter(uidl.getStringArrayAttribute("vcolorder")); + } + + private void updateCollapsedColumns(UIDL uidl) { + if (uidl.hasVariable("collapsedcolumns")) { + tHead.setColumnCollapsingAllowed(true); + collapsedColumns = uidl + .getStringArrayVariableAsSet("collapsedcolumns"); + } else { + tHead.setColumnCollapsingAllowed(false); + } + } + + private void updateColumnOrder(UIDL uidl) { + if (uidl.hasVariable("columnorder")) { + columnReordering = true; + columnOrder = uidl.getStringArrayVariable("columnorder"); + } else { + columnReordering = false; + columnOrder = null; + } + } + + boolean selectSelectedRows(UIDL uidl) { + boolean keyboardSelectionOverRowFetchInProgress = false; + + if (uidl.hasVariable("selected")) { + final Set selectedKeys = uidl + .getStringArrayVariableAsSet("selected"); + if (scrollBody != null) { + Iterator iterator = scrollBody.iterator(); + while (iterator.hasNext()) { + /* + * Make the focus reflect to the server side state unless we + * are currently selecting multiple rows with keyboard. + */ + VScrollTableRow row = (VScrollTableRow) iterator.next(); + boolean selected = selectedKeys.contains(row.getKey()); + if (!selected + && unSyncedselectionsBeforeRowFetch != null + && unSyncedselectionsBeforeRowFetch.contains(row + .getKey())) { + selected = true; + keyboardSelectionOverRowFetchInProgress = true; + } + if (selected != row.isSelected()) { + row.toggleSelection(); + if (!isSingleSelectMode() && !selected) { + // Update selection range in case a row is + // unselected from the middle of a range - #8076 + removeRowFromUnsentSelectionRanges(row); + } + } + } + } + } + unSyncedselectionsBeforeRowFetch = null; + return keyboardSelectionOverRowFetchInProgress; + } + + void updateSortingProperties(UIDL uidl) { + oldSortColumn = sortColumn; + if (uidl.hasVariable("sortascending")) { + sortAscending = uidl.getBooleanVariable("sortascending"); + sortColumn = uidl.getStringVariable("sortcolumn"); + } + } + + void resizeSortedColumnForSortIndicator() { + // Force recalculation of the captionContainer element inside the header + // cell to accomodate for the size of the sort arrow. + HeaderCell sortedHeader = tHead.getHeaderCell(sortColumn); + if (sortedHeader != null) { + tHead.resizeCaptionContainer(sortedHeader); + } + // Also recalculate the width of the captionContainer element in the + // previously sorted header, since this now has more room. + HeaderCell oldSortedHeader = tHead.getHeaderCell(oldSortColumn); + if (oldSortedHeader != null) { + tHead.resizeCaptionContainer(oldSortedHeader); + } + } + + void updateFirstVisibleAndScrollIfNeeded(UIDL uidl) { + firstvisible = uidl.hasVariable("firstvisible") ? uidl + .getIntVariable("firstvisible") : 0; + if (firstvisible != lastRequestedFirstvisible && scrollBody != null) { + // received 'surprising' firstvisible from server: scroll there + firstRowInViewPort = firstvisible; + scrollBodyPanel + .setScrollPosition(measureRowHeightOffset(firstvisible)); + } + } + + protected int measureRowHeightOffset(int rowIx) { + return (int) (rowIx * scrollBody.getRowHeight()); + } + + void updatePageLength(UIDL uidl) { + int oldPageLength = pageLength; + if (uidl.hasAttribute("pagelength")) { + pageLength = uidl.getIntAttribute("pagelength"); + } else { + // pagelenght is "0" meaning scrolling is turned off + pageLength = totalRows; + } + + if (oldPageLength != pageLength && initializedAndAttached) { + // page length changed, need to update size + sizeNeedsInit = true; + } + } + + void updateSelectionProperties(UIDL uidl, ComponentState state, + boolean readOnly) { + setMultiSelectMode(uidl.hasAttribute("multiselectmode") ? uidl + .getIntAttribute("multiselectmode") : MULTISELECT_MODE_DEFAULT); + + nullSelectionAllowed = uidl.hasAttribute("nsa") ? uidl + .getBooleanAttribute("nsa") : true; + + if (uidl.hasAttribute("selectmode")) { + if (readOnly) { + selectMode = SelectMode.NONE; + } else if (uidl.getStringAttribute("selectmode").equals("multi")) { + selectMode = SelectMode.MULTI; + } else if (uidl.getStringAttribute("selectmode").equals("single")) { + selectMode = SelectMode.SINGLE; + } else { + selectMode = SelectMode.NONE; + } + } + } + + void updateDragMode(UIDL uidl) { + dragmode = uidl.hasAttribute("dragmode") ? uidl + .getIntAttribute("dragmode") : 0; + if (BrowserInfo.get().isIE()) { + if (dragmode > 0) { + getElement().setPropertyJSO("onselectstart", + getPreventTextSelectionIEHack()); + } else { + getElement().setPropertyJSO("onselectstart", null); + } + } + } + + protected void updateTotalRows(UIDL uidl) { + int newTotalRows = uidl.getIntAttribute("totalrows"); + if (newTotalRows != getTotalRows()) { + if (scrollBody != null) { + if (getTotalRows() == 0) { + tHead.clear(); + tFoot.clear(); + } + initializedAndAttached = false; + initialContentReceived = false; + isNewBody = true; + } + setTotalRows(newTotalRows); + } + } + + protected void setTotalRows(int newTotalRows) { + totalRows = newTotalRows; + } + + public int getTotalRows() { + return totalRows; + } + + void focusRowFromBody() { + if (selectedRowKeys.size() == 1) { + // try to focus a row currently selected and in viewport + String selectedRowKey = selectedRowKeys.iterator().next(); + if (selectedRowKey != null) { + VScrollTableRow renderedRow = getRenderedRowByKey(selectedRowKey); + if (renderedRow == null || !renderedRow.isInViewPort()) { + setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort)); + } else { + setRowFocus(renderedRow); + } + } + } else { + // multiselect mode + setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort)); + } + } + + protected VScrollTableBody createScrollBody() { + return new VScrollTableBody(); + } + + /** + * Selects the last row visible in the table + * + * @param focusOnly + * Should the focus only be moved to the last row + */ + void selectLastRenderedRowInViewPort(boolean focusOnly) { + int index = firstRowInViewPort + getFullyVisibleRowCount(); + VScrollTableRow lastRowInViewport = scrollBody.getRowByRowIndex(index); + if (lastRowInViewport == null) { + // this should not happen in normal situations (white space at the + // end of viewport). Select the last rendered as a fallback. + lastRowInViewport = scrollBody.getRowByRowIndex(scrollBody + .getLastRendered()); + if (lastRowInViewport == null) { + return; // empty table + } + } + setRowFocus(lastRowInViewport); + if (!focusOnly) { + selectFocusedRow(false, multiselectPending); + sendSelectedRows(); + } + } + + /** + * Selects the first row visible in the table + * + * @param focusOnly + * Should the focus only be moved to the first row + */ + void selectFirstRenderedRowInViewPort(boolean focusOnly) { + int index = firstRowInViewPort; + VScrollTableRow firstInViewport = scrollBody.getRowByRowIndex(index); + if (firstInViewport == null) { + // this should not happen in normal situations + return; + } + setRowFocus(firstInViewport); + if (!focusOnly) { + selectFocusedRow(false, multiselectPending); + sendSelectedRows(); + } + } + + void setCacheRateFromUIDL(UIDL uidl) { + setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr") + : CACHE_RATE_DEFAULT); + } + + private void setCacheRate(double d) { + if (cache_rate != d) { + cache_rate = d; + cache_react_rate = 0.75 * d; + } + } + + void updateActionMap(UIDL mainUidl) { + UIDL actionsUidl = mainUidl.getChildByTagName("actions"); + if (actionsUidl == null) { + return; + } + + final Iterator it = actionsUidl.getChildIterator(); + while (it.hasNext()) { + final UIDL action = (UIDL) it.next(); + final String key = action.getStringAttribute("key"); + final String caption = action.getStringAttribute("caption"); + actionMap.put(key + "_c", caption); + if (action.hasAttribute("icon")) { + // TODO need some uri handling ?? + actionMap.put(key + "_i", client.translateVaadinUri(action + .getStringAttribute("icon"))); + } else { + actionMap.remove(key + "_i"); + } + } + + } + + public String getActionCaption(String actionKey) { + return actionMap.get(actionKey + "_c"); + } + + public String getActionIcon(String actionKey) { + return actionMap.get(actionKey + "_i"); + } + + private void updateHeader(String[] strings) { + if (strings == null) { + return; + } + + int visibleCols = strings.length; + int colIndex = 0; + if (showRowHeaders) { + tHead.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex); + visibleCols++; + visibleColOrder = new String[visibleCols]; + visibleColOrder[colIndex] = ROW_HEADER_COLUMN_KEY; + colIndex++; + } else { + visibleColOrder = new String[visibleCols]; + tHead.removeCell(ROW_HEADER_COLUMN_KEY); + } + + int i; + for (i = 0; i < strings.length; i++) { + final String cid = strings[i]; + visibleColOrder[colIndex] = cid; + tHead.enableColumn(cid, colIndex); + colIndex++; + } + + tHead.setVisible(showColHeaders); + setContainerHeight(); + + } + + /** + * Updates footers. + *

+ * Update headers whould be called before this method is called! + *

+ * + * @param strings + */ + private void updateFooter(String[] strings) { + if (strings == null) { + return; + } + + // Add dummy column if row headers are present + int colIndex = 0; + if (showRowHeaders) { + tFoot.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex); + colIndex++; + } else { + tFoot.removeCell(ROW_HEADER_COLUMN_KEY); + } + + int i; + for (i = 0; i < strings.length; i++) { + final String cid = strings[i]; + tFoot.enableColumn(cid, colIndex); + colIndex++; + } + + tFoot.setVisible(showColFooters); + } + + /** + * @param uidl + * which contains row data + * @param firstRow + * first row in data set + * @param reqRows + * amount of rows in data set + */ + void updateBody(UIDL uidl, int firstRow, int reqRows) { + if (uidl == null || reqRows < 1) { + // container is empty, remove possibly existing rows + if (firstRow <= 0) { + while (scrollBody.getLastRendered() > scrollBody.firstRendered) { + scrollBody.unlinkRow(false); + } + scrollBody.unlinkRow(false); + } + return; + } + + scrollBody.renderRows(uidl, firstRow, reqRows); + + discardRowsOutsideCacheWindow(); + } + + void updateRowsInBody(UIDL partialRowUpdates) { + if (partialRowUpdates == null) { + return; + } + int firstRowIx = partialRowUpdates.getIntAttribute("firsturowix"); + int count = partialRowUpdates.getIntAttribute("numurows"); + scrollBody.unlinkRows(firstRowIx, count); + scrollBody.insertRows(partialRowUpdates, firstRowIx, count); + } + + /** + * Updates the internal cache by unlinking rows that fall outside of the + * caching window. + */ + protected void discardRowsOutsideCacheWindow() { + int firstRowToKeep = (int) (firstRowInViewPort - pageLength + * cache_rate); + int lastRowToKeep = (int) (firstRowInViewPort + pageLength + pageLength + * cache_rate); + debug("Client side calculated cache rows to keep: " + firstRowToKeep + + "-" + lastRowToKeep); + + if (serverCacheFirst != -1) { + firstRowToKeep = serverCacheFirst; + lastRowToKeep = serverCacheLast; + debug("Server cache rows that override: " + serverCacheFirst + "-" + + serverCacheLast); + if (firstRowToKeep < scrollBody.getFirstRendered() + || lastRowToKeep > scrollBody.getLastRendered()) { + debug("*** Server wants us to keep " + serverCacheFirst + "-" + + serverCacheLast + " but we only have rows " + + scrollBody.getFirstRendered() + "-" + + scrollBody.getLastRendered() + " rendered!"); + } + } + discardRowsOutsideOf(firstRowToKeep, lastRowToKeep); + + scrollBody.fixSpacers(); + + scrollBody.restoreRowVisibility(); + } + + private void discardRowsOutsideOf(int optimalFirstRow, int optimalLastRow) { + /* + * firstDiscarded and lastDiscarded are only calculated for debug + * purposes + */ + int firstDiscarded = -1, lastDiscarded = -1; + boolean cont = true; + while (cont && scrollBody.getLastRendered() > optimalFirstRow + && scrollBody.getFirstRendered() < optimalFirstRow) { + if (firstDiscarded == -1) { + firstDiscarded = scrollBody.getFirstRendered(); + } + + // removing row from start + cont = scrollBody.unlinkRow(true); + } + if (firstDiscarded != -1) { + lastDiscarded = scrollBody.getFirstRendered() - 1; + debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded); + } + firstDiscarded = lastDiscarded = -1; + + cont = true; + while (cont && scrollBody.getLastRendered() > optimalLastRow) { + if (lastDiscarded == -1) { + lastDiscarded = scrollBody.getLastRendered(); + } + + // removing row from the end + cont = scrollBody.unlinkRow(false); + } + if (lastDiscarded != -1) { + firstDiscarded = scrollBody.getLastRendered() + 1; + debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded); + } + + debug("Now in cache: " + scrollBody.getFirstRendered() + "-" + + scrollBody.getLastRendered()); + } + + /** + * Inserts rows in the table body or removes them from the table body based + * on the commands in the UIDL. + * + * @param partialRowAdditions + * the UIDL containing row updates. + */ + protected void addAndRemoveRows(UIDL partialRowAdditions) { + if (partialRowAdditions == null) { + return; + } + if (partialRowAdditions.hasAttribute("hide")) { + scrollBody.unlinkAndReindexRows( + partialRowAdditions.getIntAttribute("firstprowix"), + partialRowAdditions.getIntAttribute("numprows")); + scrollBody.ensureCacheFilled(); + } else { + if (partialRowAdditions.hasAttribute("delbelow")) { + scrollBody.insertRowsDeleteBelow(partialRowAdditions, + partialRowAdditions.getIntAttribute("firstprowix"), + partialRowAdditions.getIntAttribute("numprows")); + } else { + scrollBody.insertAndReindexRows(partialRowAdditions, + partialRowAdditions.getIntAttribute("firstprowix"), + partialRowAdditions.getIntAttribute("numprows")); + } + } + + discardRowsOutsideCacheWindow(); + } + + /** + * Gives correct column index for given column key ("cid" in UIDL). + * + * @param colKey + * @return column index of visible columns, -1 if column not visible + */ + private int getColIndexByKey(String colKey) { + // return 0 if asked for rowHeaders + if (ROW_HEADER_COLUMN_KEY.equals(colKey)) { + return 0; + } + for (int i = 0; i < visibleColOrder.length; i++) { + if (visibleColOrder[i].equals(colKey)) { + return i; + } + } + return -1; + } + + private boolean isMultiSelectModeSimple() { + return selectMode == SelectMode.MULTI + && multiselectmode == MULTISELECT_MODE_SIMPLE; + } + + private boolean isSingleSelectMode() { + return selectMode == SelectMode.SINGLE; + } + + private boolean isMultiSelectModeAny() { + return selectMode == SelectMode.MULTI; + } + + private boolean isMultiSelectModeDefault() { + return selectMode == SelectMode.MULTI + && multiselectmode == MULTISELECT_MODE_DEFAULT; + } + + private void setMultiSelectMode(int multiselectmode) { + if (BrowserInfo.get().isTouchDevice()) { + // Always use the simple mode for touch devices that do not have + // shift/ctrl keys + this.multiselectmode = MULTISELECT_MODE_SIMPLE; + } else { + this.multiselectmode = multiselectmode; + } + + } + + protected boolean isSelectable() { + return selectMode.getId() > SelectMode.NONE.getId(); + } + + private boolean isCollapsedColumn(String colKey) { + if (collapsedColumns == null) { + return false; + } + if (collapsedColumns.contains(colKey)) { + return true; + } + return false; + } + + private String getColKeyByIndex(int index) { + return tHead.getHeaderCell(index).getColKey(); + } + + private void setColWidth(int colIndex, int w, boolean isDefinedWidth) { + final HeaderCell hcell = tHead.getHeaderCell(colIndex); + + // Make sure that the column grows to accommodate the sort indicator if + // necessary. + if (w < hcell.getMinWidth()) { + w = hcell.getMinWidth(); + } + + // Set header column width + hcell.setWidth(w, isDefinedWidth); + + // Ensure indicators have been taken into account + tHead.resizeCaptionContainer(hcell); + + // Set body column width + scrollBody.setColWidth(colIndex, w); + + // Set footer column width + FooterCell fcell = tFoot.getFooterCell(colIndex); + fcell.setWidth(w, isDefinedWidth); + } + + private int getColWidth(String colKey) { + return tHead.getHeaderCell(colKey).getWidth(); + } + + /** + * Get a rendered row by its key + * + * @param key + * The key to search with + * @return + */ + public VScrollTableRow getRenderedRowByKey(String key) { + if (scrollBody != null) { + final Iterator it = scrollBody.iterator(); + VScrollTableRow r = null; + while (it.hasNext()) { + r = (VScrollTableRow) it.next(); + if (r.getKey().equals(key)) { + return r; + } + } + } + return null; + } + + /** + * Returns the next row to the given row + * + * @param row + * The row to calculate from + * + * @return The next row or null if no row exists + */ + private VScrollTableRow getNextRow(VScrollTableRow row, int offset) { + final Iterator it = scrollBody.iterator(); + VScrollTableRow r = null; + while (it.hasNext()) { + r = (VScrollTableRow) it.next(); + if (r == row) { + r = null; + while (offset >= 0 && it.hasNext()) { + r = (VScrollTableRow) it.next(); + offset--; + } + return r; + } + } + + return null; + } + + /** + * Returns the previous row from the given row + * + * @param row + * The row to calculate from + * @return The previous row or null if no row exists + */ + private VScrollTableRow getPreviousRow(VScrollTableRow row, int offset) { + final Iterator it = scrollBody.iterator(); + final Iterator offsetIt = scrollBody.iterator(); + VScrollTableRow r = null; + VScrollTableRow prev = null; + while (it.hasNext()) { + r = (VScrollTableRow) it.next(); + if (offset < 0) { + prev = (VScrollTableRow) offsetIt.next(); + } + if (r == row) { + return prev; + } + offset--; + } + + return null; + } + + protected void reOrderColumn(String columnKey, int newIndex) { + + final int oldIndex = getColIndexByKey(columnKey); + + // Change header order + tHead.moveCell(oldIndex, newIndex); + + // Change body order + scrollBody.moveCol(oldIndex, newIndex); + + // Change footer order + tFoot.moveCell(oldIndex, newIndex); + + /* + * Build new columnOrder and update it to server Note that columnOrder + * also contains collapsed columns so we cannot directly build it from + * cells vector Loop the old columnOrder and append in order to new + * array unless on moved columnKey. On new index also put the moved key + * i == index on columnOrder, j == index on newOrder + */ + final String oldKeyOnNewIndex = visibleColOrder[newIndex]; + if (showRowHeaders) { + newIndex--; // columnOrder don't have rowHeader + } + // add back hidden rows, + for (int i = 0; i < columnOrder.length; i++) { + if (columnOrder[i].equals(oldKeyOnNewIndex)) { + break; // break loop at target + } + if (isCollapsedColumn(columnOrder[i])) { + newIndex++; + } + } + // finally we can build the new columnOrder for server + final String[] newOrder = new String[columnOrder.length]; + for (int i = 0, j = 0; j < newOrder.length; i++) { + if (j == newIndex) { + newOrder[j] = columnKey; + j++; + } + if (i == columnOrder.length) { + break; + } + if (columnOrder[i].equals(columnKey)) { + continue; + } + newOrder[j] = columnOrder[i]; + j++; + } + columnOrder = newOrder; + // also update visibleColumnOrder + int i = showRowHeaders ? 1 : 0; + for (int j = 0; j < newOrder.length; j++) { + final String cid = newOrder[j]; + if (!isCollapsedColumn(cid)) { + visibleColOrder[i++] = cid; + } + } + client.updateVariable(paintableId, "columnorder", columnOrder, false); + if (client.hasEventListeners(this, COLUMN_REORDER_EVENT_ID)) { + client.sendPendingVariableChanges(); + } + } + + @Override + protected void onDetach() { + rowRequestHandler.cancel(); + super.onDetach(); + // ensure that scrollPosElement will be detached + if (scrollPositionElement != null) { + final Element parent = DOM.getParent(scrollPositionElement); + if (parent != null) { + DOM.removeChild(parent, scrollPositionElement); + } + } + } + + /** + * Run only once when component is attached and received its initial + * content. This function: + * + * * Syncs headers and bodys "natural widths and saves the values. + * + * * Sets proper width and height + * + * * Makes deferred request to get some cache rows + */ + void sizeInit() { + if (!sizeNeedsInit) { + return; + } + sizeNeedsInit = false; + + scrollBody.setContainerHeight(); + + /* + * We will use browsers table rendering algorithm to find proper column + * widths. If content and header take less space than available, we will + * divide extra space relatively to each column which has not width set. + * + * Overflow pixels are added to last column. + */ + + Iterator headCells = tHead.iterator(); + Iterator footCells = tFoot.iterator(); + int i = 0; + int totalExplicitColumnsWidths = 0; + int total = 0; + float expandRatioDivider = 0; + + final int[] widths = new int[tHead.visibleCells.size()]; + + tHead.enableBrowserIntelligence(); + tFoot.enableBrowserIntelligence(); + + // first loop: collect natural widths + while (headCells.hasNext()) { + final HeaderCell hCell = (HeaderCell) headCells.next(); + final FooterCell fCell = (FooterCell) footCells.next(); + int w = hCell.getWidth(); + if (hCell.isDefinedWidth()) { + // server has defined column width explicitly + totalExplicitColumnsWidths += w; + } else { + if (hCell.getExpandRatio() > 0) { + expandRatioDivider += hCell.getExpandRatio(); + w = 0; + } else { + // get and store greater of header width and column width, + // and + // store it as a minimumn natural col width + int headerWidth = hCell.getNaturalColumnWidth(i); + int footerWidth = fCell.getNaturalColumnWidth(i); + w = headerWidth > footerWidth ? headerWidth : footerWidth; + } + hCell.setNaturalMinimumColumnWidth(w); + fCell.setNaturalMinimumColumnWidth(w); + } + widths[i] = w; + total += w; + i++; + } + + tHead.disableBrowserIntelligence(); + tFoot.disableBrowserIntelligence(); + + boolean willHaveScrollbarz = willHaveScrollbars(); + + // fix "natural" width if width not set + if (isDynamicWidth()) { + int w = total; + w += scrollBody.getCellExtraWidth() * visibleColOrder.length; + if (willHaveScrollbarz) { + w += Util.getNativeScrollbarSize(); + } + setContentWidth(w); + } + + int availW = scrollBody.getAvailableWidth(); + if (BrowserInfo.get().isIE()) { + // Hey IE, are you really sure about this? + availW = scrollBody.getAvailableWidth(); + } + availW -= scrollBody.getCellExtraWidth() * visibleColOrder.length; + + if (willHaveScrollbarz) { + availW -= Util.getNativeScrollbarSize(); + } + + // TODO refactor this code to be the same as in resize timer + boolean needsReLayout = false; + + if (availW > total) { + // natural size is smaller than available space + final int extraSpace = availW - total; + final int totalWidthR = total - totalExplicitColumnsWidths; + int checksum = 0; + needsReLayout = true; + + if (extraSpace == 1) { + // We cannot divide one single pixel so we give it the first + // undefined column + headCells = tHead.iterator(); + i = 0; + checksum = availW; + while (headCells.hasNext()) { + HeaderCell hc = (HeaderCell) headCells.next(); + if (!hc.isDefinedWidth()) { + widths[i]++; + break; + } + i++; + } + + } else if (expandRatioDivider > 0) { + // visible columns have some active expand ratios, excess + // space is divided according to them + headCells = tHead.iterator(); + i = 0; + while (headCells.hasNext()) { + HeaderCell hCell = (HeaderCell) headCells.next(); + if (hCell.getExpandRatio() > 0) { + int w = widths[i]; + final int newSpace = Math.round((extraSpace * (hCell + .getExpandRatio() / expandRatioDivider))); + w += newSpace; + widths[i] = w; + } + checksum += widths[i]; + i++; + } + } else if (totalWidthR > 0) { + // no expand ratios defined, we will share extra space + // relatively to "natural widths" among those without + // explicit width + headCells = tHead.iterator(); + i = 0; + while (headCells.hasNext()) { + HeaderCell hCell = (HeaderCell) headCells.next(); + if (!hCell.isDefinedWidth()) { + int w = widths[i]; + final int newSpace = Math.round((float) extraSpace + * (float) w / totalWidthR); + w += newSpace; + widths[i] = w; + } + checksum += widths[i]; + i++; + } + } + + if (extraSpace > 0 && checksum != availW) { + /* + * There might be in some cases a rounding error of 1px when + * extra space is divided so if there is one then we give the + * first undefined column 1 more pixel + */ + headCells = tHead.iterator(); + i = 0; + while (headCells.hasNext()) { + HeaderCell hc = (HeaderCell) headCells.next(); + if (!hc.isDefinedWidth()) { + widths[i] += availW - checksum; + break; + } + i++; + } + } + + } else { + // bodys size will be more than available and scrollbar will appear + } + + // last loop: set possibly modified values or reset if new tBody + i = 0; + headCells = tHead.iterator(); + while (headCells.hasNext()) { + final HeaderCell hCell = (HeaderCell) headCells.next(); + if (isNewBody || hCell.getWidth() == -1) { + final int w = widths[i]; + setColWidth(i, w, false); + } + i++; + } + + initializedAndAttached = true; + + if (needsReLayout) { + scrollBody.reLayoutComponents(); + } + + updatePageLength(); + + /* + * Fix "natural" height if height is not set. This must be after width + * fixing so the components' widths have been adjusted. + */ + if (isDynamicHeight()) { + /* + * We must force an update of the row height as this point as it + * might have been (incorrectly) calculated earlier + */ + + int bodyHeight; + if (pageLength == totalRows) { + /* + * A hack to support variable height rows when paging is off. + * Generally this is not supported by scrolltable. We want to + * show all rows so the bodyHeight should be equal to the table + * height. + */ + // int bodyHeight = scrollBody.getOffsetHeight(); + bodyHeight = scrollBody.getRequiredHeight(); + } else { + bodyHeight = (int) Math.round(scrollBody.getRowHeight(true) + * pageLength); + } + boolean needsSpaceForHorizontalSrollbar = (total > availW); + if (needsSpaceForHorizontalSrollbar) { + bodyHeight += Util.getNativeScrollbarSize(); + } + scrollBodyPanel.setHeight(bodyHeight + "px"); + Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); + } + + isNewBody = false; + + if (firstvisible > 0) { + scrollBodyPanel + .setScrollPosition(measureRowHeightOffset(firstvisible)); + firstRowInViewPort = firstvisible; + } + + if (enabled) { + // Do we need cache rows + if (scrollBody.getLastRendered() + 1 < firstRowInViewPort + + pageLength + (int) cache_react_rate * pageLength) { + if (totalRows - 1 > scrollBody.getLastRendered()) { + // fetch cache rows + int firstInNewSet = scrollBody.getLastRendered() + 1; + rowRequestHandler.setReqFirstRow(firstInNewSet); + int lastInNewSet = (int) (firstRowInViewPort + pageLength + cache_rate + * pageLength); + if (lastInNewSet > totalRows - 1) { + lastInNewSet = totalRows - 1; + } + rowRequestHandler.setReqRows(lastInNewSet - firstInNewSet + + 1); + rowRequestHandler.deferRowFetch(1); + } + } + } + + /* + * Ensures the column alignments are correct at initial loading.
+ * (child components widths are correct) + */ + scrollBody.reLayoutComponents(); + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); + } + }); + + client.doLayout(true); + } + + /** + * Note, this method is not official api although declared as protected. + * Extend at you own risk. + * + * @return true if content area will have scrollbars visible. + */ + protected boolean willHaveScrollbars() { + if (isDynamicHeight()) { + if (pageLength < totalRows) { + return true; + } + } else { + int fakeheight = (int) Math.round(scrollBody.getRowHeight() + * totalRows); + int availableHeight = scrollBodyPanel.getElement().getPropertyInt( + "clientHeight"); + if (fakeheight > availableHeight) { + return true; + } + } + return false; + } + + private void announceScrollPosition() { + if (scrollPositionElement == null) { + scrollPositionElement = DOM.createDiv(); + scrollPositionElement.setClassName(CLASSNAME + "-scrollposition"); + scrollPositionElement.getStyle().setPosition(Position.ABSOLUTE); + scrollPositionElement.getStyle().setDisplay(Display.NONE); + getElement().appendChild(scrollPositionElement); + } + + Style style = scrollPositionElement.getStyle(); + style.setMarginLeft(getElement().getOffsetWidth() / 2 - 80, Unit.PX); + style.setMarginTop(-scrollBodyPanel.getOffsetHeight(), Unit.PX); + + // indexes go from 1-totalRows, as rowheaders in index-mode indicate + int last = (firstRowInViewPort + pageLength); + if (last > totalRows) { + last = totalRows; + } + scrollPositionElement.setInnerHTML("" + (firstRowInViewPort + 1) + + " – " + (last) + "..." + ""); + style.setDisplay(Display.BLOCK); + } + + void hideScrollPositionAnnotation() { + if (scrollPositionElement != null) { + DOM.setStyleAttribute(scrollPositionElement, "display", "none"); + } + } + + boolean isScrollPositionVisible() { + return scrollPositionElement != null + && !scrollPositionElement.getStyle().getDisplay() + .equals(Display.NONE.toString()); + } + + class RowRequestHandler extends Timer { + + private int reqFirstRow = 0; + private int reqRows = 0; + private boolean isRunning = false; + + public void deferRowFetch() { + deferRowFetch(250); + } + + public boolean isRunning() { + return isRunning; + } + + public void deferRowFetch(int msec) { + isRunning = true; + if (reqRows > 0 && reqFirstRow < totalRows) { + schedule(msec); + + // tell scroll position to user if currently "visible" rows are + // not rendered + if (totalRows > pageLength + && ((firstRowInViewPort + pageLength > scrollBody + .getLastRendered()) || (firstRowInViewPort < scrollBody + .getFirstRendered()))) { + announceScrollPosition(); + } else { + hideScrollPositionAnnotation(); + } + } + } + + public void setReqFirstRow(int reqFirstRow) { + if (reqFirstRow < 0) { + reqFirstRow = 0; + } else if (reqFirstRow >= totalRows) { + reqFirstRow = totalRows - 1; + } + this.reqFirstRow = reqFirstRow; + } + + public void setReqRows(int reqRows) { + this.reqRows = reqRows; + } + + @Override + public void run() { + if (client.hasActiveRequest() || navKeyDown) { + // if client connection is busy, don't bother loading it more + VConsole.log("Postponed rowfetch"); + schedule(250); + } else { + + int firstToBeRendered = scrollBody.firstRendered; + if (reqFirstRow < firstToBeRendered) { + firstToBeRendered = reqFirstRow; + } else if (firstRowInViewPort - (int) (cache_rate * pageLength) > firstToBeRendered) { + firstToBeRendered = firstRowInViewPort + - (int) (cache_rate * pageLength); + if (firstToBeRendered < 0) { + firstToBeRendered = 0; + } + } + + int lastToBeRendered = scrollBody.lastRendered; + + if (reqFirstRow + reqRows - 1 > lastToBeRendered) { + lastToBeRendered = reqFirstRow + reqRows - 1; + } else if (firstRowInViewPort + pageLength + pageLength + * cache_rate < lastToBeRendered) { + lastToBeRendered = (firstRowInViewPort + pageLength + (int) (pageLength * cache_rate)); + if (lastToBeRendered >= totalRows) { + lastToBeRendered = totalRows - 1; + } + // due Safari 3.1 bug (see #2607), verify reqrows, original + // problem unknown, but this should catch the issue + if (reqFirstRow + reqRows - 1 > lastToBeRendered) { + reqRows = lastToBeRendered - reqFirstRow; + } + } + + client.updateVariable(paintableId, "firstToBeRendered", + firstToBeRendered, false); + + client.updateVariable(paintableId, "lastToBeRendered", + lastToBeRendered, false); + // remember which firstvisible we requested, in case the server + // has + // a differing opinion + lastRequestedFirstvisible = firstRowInViewPort; + client.updateVariable(paintableId, "firstvisible", + firstRowInViewPort, false); + client.updateVariable(paintableId, "reqfirstrow", reqFirstRow, + false); + client.updateVariable(paintableId, "reqrows", reqRows, true); + + if (selectionChanged) { + unSyncedselectionsBeforeRowFetch = new HashSet( + selectedRowKeys); + } + isRunning = false; + } + } + + public int getReqFirstRow() { + return reqFirstRow; + } + + /** + * Sends request to refresh content at this position. + */ + public void refreshContent() { + isRunning = true; + int first = (int) (firstRowInViewPort - pageLength * cache_rate); + int reqRows = (int) (2 * pageLength * cache_rate + pageLength); + if (first < 0) { + reqRows = reqRows + first; + first = 0; + } + setReqFirstRow(first); + setReqRows(reqRows); + run(); + } + } + + public class HeaderCell extends Widget { + + Element td = DOM.createTD(); + + Element captionContainer = DOM.createDiv(); + + Element sortIndicator = DOM.createDiv(); + + Element colResizeWidget = DOM.createDiv(); + + Element floatingCopyOfHeaderCell; + + private boolean sortable = false; + private final String cid; + private boolean dragging; + + private int dragStartX; + private int colIndex; + private int originalWidth; + + private boolean isResizing; + + private int headerX; + + private boolean moved; + + private int closestSlot; + + private int width = -1; + + private int naturalWidth = -1; + + private char align = ALIGN_LEFT; + + boolean definedWidth = false; + + private float expandRatio = 0; + + private boolean sorted; + + public void setSortable(boolean b) { + sortable = b; + } + + /** + * Makes room for the sorting indicator in case the column that the + * header cell belongs to is sorted. This is done by resizing the width + * of the caption container element by the correct amount + */ + public void resizeCaptionContainer(int rightSpacing) { + int captionContainerWidth = width + - colResizeWidget.getOffsetWidth() - rightSpacing; + + if (td.getClassName().contains("-asc") + || td.getClassName().contains("-desc")) { + // Leave room for the sort indicator + captionContainerWidth -= sortIndicator.getOffsetWidth(); + } + + if (captionContainerWidth < 0) { + rightSpacing += captionContainerWidth; + captionContainerWidth = 0; + } + + captionContainer.getStyle().setPropertyPx("width", + captionContainerWidth); + + // Apply/Remove spacing if defined + if (rightSpacing > 0) { + colResizeWidget.getStyle().setMarginLeft(rightSpacing, Unit.PX); + } else { + colResizeWidget.getStyle().clearMarginLeft(); + } + } + + public void setNaturalMinimumColumnWidth(int w) { + naturalWidth = w; + } + + public HeaderCell(String colId, String headerText) { + cid = colId; + + DOM.setElementProperty(colResizeWidget, "className", CLASSNAME + + "-resizer"); + + setText(headerText); + + DOM.appendChild(td, colResizeWidget); + + DOM.setElementProperty(sortIndicator, "className", CLASSNAME + + "-sort-indicator"); + DOM.appendChild(td, sortIndicator); + + DOM.setElementProperty(captionContainer, "className", CLASSNAME + + "-caption-container"); + + // ensure no clipping initially (problem on column additions) + DOM.setStyleAttribute(captionContainer, "overflow", "visible"); + + DOM.appendChild(td, captionContainer); + + DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK + | Event.ONCONTEXTMENU | Event.TOUCHEVENTS); + + setElement(td); + + setAlign(ALIGN_LEFT); + } + + public void disableAutoWidthCalculation() { + definedWidth = true; + expandRatio = 0; + } + + public void setWidth(int w, boolean ensureDefinedWidth) { + if (ensureDefinedWidth) { + definedWidth = true; + // on column resize expand ratio becomes zero + expandRatio = 0; + } + if (width == -1) { + // go to default mode, clip content if necessary + DOM.setStyleAttribute(captionContainer, "overflow", ""); + } + width = w; + if (w == -1) { + DOM.setStyleAttribute(captionContainer, "width", ""); + setWidth(""); + } else { + tHead.resizeCaptionContainer(this); + + /* + * if we already have tBody, set the header width properly, if + * not defer it. IE will fail with complex float in table header + * unless TD width is not explicitly set. + */ + if (scrollBody != null) { + int tdWidth = width + scrollBody.getCellExtraWidth(); + setWidth(tdWidth + "px"); + } else { + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + int tdWidth = width + + scrollBody.getCellExtraWidth(); + setWidth(tdWidth + "px"); + } + }); + } + } + } + + public void setUndefinedWidth() { + definedWidth = false; + setWidth(-1, false); + } + + /** + * Detects if width is fixed by developer on server side or resized to + * current width by user. + * + * @return true if defined, false if "natural" width + */ + public boolean isDefinedWidth() { + return definedWidth && width >= 0; + } + + public int getWidth() { + return width; + } + + public void setText(String headerText) { + DOM.setInnerHTML(captionContainer, headerText); + } + + public String getColKey() { + return cid; + } + + private void setSorted(boolean sorted) { + this.sorted = sorted; + if (sorted) { + if (sortAscending) { + this.setStyleName(CLASSNAME + "-header-cell-asc"); + } else { + this.setStyleName(CLASSNAME + "-header-cell-desc"); + } + } else { + this.setStyleName(CLASSNAME + "-header-cell"); + } + } + + /** + * Handle column reordering. + */ + @Override + public void onBrowserEvent(Event event) { + if (enabled && event != null) { + if (isResizing + || event.getEventTarget().cast() == colResizeWidget) { + if (dragging + && (event.getTypeInt() == Event.ONMOUSEUP || event + .getTypeInt() == Event.ONTOUCHEND)) { + // Handle releasing column header on spacer #5318 + handleCaptionEvent(event); + } else { + onResizeEvent(event); + } + } else { + /* + * Ensure focus before handling caption event. Otherwise + * variables changed from caption event may be before + * variables from other components that fire variables when + * they lose focus. + */ + if (event.getTypeInt() == Event.ONMOUSEDOWN + || event.getTypeInt() == Event.ONTOUCHSTART) { + scrollBodyPanel.setFocus(true); + } + handleCaptionEvent(event); + boolean stopPropagation = true; + if (event.getTypeInt() == Event.ONCONTEXTMENU + && !client.hasEventListeners(VScrollTable.this, + HEADER_CLICK_EVENT_ID)) { + // Prevent showing the browser's context menu only when + // there is a header click listener. + stopPropagation = false; + } + if (stopPropagation) { + event.stopPropagation(); + event.preventDefault(); + } + } + } + } + + private void createFloatingCopy() { + floatingCopyOfHeaderCell = DOM.createDiv(); + DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td)); + floatingCopyOfHeaderCell = DOM + .getChild(floatingCopyOfHeaderCell, 2); + DOM.setElementProperty(floatingCopyOfHeaderCell, "className", + CLASSNAME + "-header-drag"); + // otherwise might wrap or be cut if narrow column + DOM.setStyleAttribute(floatingCopyOfHeaderCell, "width", "auto"); + updateFloatingCopysPosition(DOM.getAbsoluteLeft(td), + DOM.getAbsoluteTop(td)); + DOM.appendChild(RootPanel.get().getElement(), + floatingCopyOfHeaderCell); + } + + private void updateFloatingCopysPosition(int x, int y) { + x -= DOM.getElementPropertyInt(floatingCopyOfHeaderCell, + "offsetWidth") / 2; + DOM.setStyleAttribute(floatingCopyOfHeaderCell, "left", x + "px"); + if (y > 0) { + DOM.setStyleAttribute(floatingCopyOfHeaderCell, "top", (y + 7) + + "px"); + } + } + + private void hideFloatingCopy() { + DOM.removeChild(RootPanel.get().getElement(), + floatingCopyOfHeaderCell); + floatingCopyOfHeaderCell = null; + } + + /** + * Fires a header click event after the user has clicked a column header + * cell + * + * @param event + * The click event + */ + private void fireHeaderClickedEvent(Event event) { + if (client.hasEventListeners(VScrollTable.this, + HEADER_CLICK_EVENT_ID)) { + MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(event); + client.updateVariable(paintableId, "headerClickEvent", + details.toString(), false); + client.updateVariable(paintableId, "headerClickCID", cid, true); + } + } + + protected void handleCaptionEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONTOUCHSTART: + case Event.ONMOUSEDOWN: + if (columnReordering + && Util.isTouchEventOrLeftMouseButton(event)) { + if (event.getTypeInt() == Event.ONTOUCHSTART) { + /* + * prevent using this event in e.g. scrolling + */ + event.stopPropagation(); + } + dragging = true; + moved = false; + colIndex = getColIndexByKey(cid); + DOM.setCapture(getElement()); + headerX = tHead.getAbsoluteLeft(); + event.preventDefault(); // prevent selecting text && + // generated touch events + } + break; + case Event.ONMOUSEUP: + case Event.ONTOUCHEND: + case Event.ONTOUCHCANCEL: + if (columnReordering + && Util.isTouchEventOrLeftMouseButton(event)) { + dragging = false; + DOM.releaseCapture(getElement()); + if (moved) { + hideFloatingCopy(); + tHead.removeSlotFocus(); + if (closestSlot != colIndex + && closestSlot != (colIndex + 1)) { + if (closestSlot > colIndex) { + reOrderColumn(cid, closestSlot - 1); + } else { + reOrderColumn(cid, closestSlot); + } + } + } + if (Util.isTouchEvent(event)) { + /* + * Prevent using in e.g. scrolling and prevent generated + * events. + */ + event.preventDefault(); + event.stopPropagation(); + } + } + + if (!moved) { + // mouse event was a click to header -> sort column + if (sortable && Util.isTouchEventOrLeftMouseButton(event)) { + if (sortColumn.equals(cid)) { + // just toggle order + client.updateVariable(paintableId, "sortascending", + !sortAscending, false); + } else { + // set table sorted by this column + client.updateVariable(paintableId, "sortcolumn", + cid, false); + } + // get also cache columns at the same request + scrollBodyPanel.setScrollPosition(0); + firstvisible = 0; + rowRequestHandler.setReqFirstRow(0); + rowRequestHandler.setReqRows((int) (2 * pageLength + * cache_rate + pageLength)); + rowRequestHandler.deferRowFetch(); // some validation + + // defer 250ms + rowRequestHandler.cancel(); // instead of waiting + rowRequestHandler.run(); // run immediately + } + fireHeaderClickedEvent(event); + if (Util.isTouchEvent(event)) { + /* + * Prevent using in e.g. scrolling and prevent generated + * events. + */ + event.preventDefault(); + event.stopPropagation(); + } + break; + } + break; + case Event.ONDBLCLICK: + fireHeaderClickedEvent(event); + break; + case Event.ONTOUCHMOVE: + case Event.ONMOUSEMOVE: + if (dragging && Util.isTouchEventOrLeftMouseButton(event)) { + if (event.getTypeInt() == Event.ONTOUCHMOVE) { + /* + * prevent using this event in e.g. scrolling + */ + event.stopPropagation(); + } + if (!moved) { + createFloatingCopy(); + moved = true; + } + + final int clientX = Util.getTouchOrMouseClientX(event); + final int x = clientX + tHead.hTableWrapper.getScrollLeft(); + int slotX = headerX; + closestSlot = colIndex; + int closestDistance = -1; + int start = 0; + if (showRowHeaders) { + start++; + } + final int visibleCellCount = tHead.getVisibleCellCount(); + for (int i = start; i <= visibleCellCount; i++) { + if (i > 0) { + final String colKey = getColKeyByIndex(i - 1); + slotX += getColWidth(colKey); + } + final int dist = Math.abs(x - slotX); + if (closestDistance == -1 || dist < closestDistance) { + closestDistance = dist; + closestSlot = i; + } + } + tHead.focusSlot(closestSlot); + + updateFloatingCopysPosition(clientX, -1); + } + break; + default: + break; + } + } + + private void onResizeEvent(Event event) { + switch (DOM.eventGetType(event)) { + case Event.ONMOUSEDOWN: + if (!Util.isTouchEventOrLeftMouseButton(event)) { + return; + } + isResizing = true; + DOM.setCapture(getElement()); + dragStartX = DOM.eventGetClientX(event); + colIndex = getColIndexByKey(cid); + originalWidth = getWidth(); + DOM.eventPreventDefault(event); + break; + case Event.ONMOUSEUP: + if (!Util.isTouchEventOrLeftMouseButton(event)) { + return; + } + isResizing = false; + DOM.releaseCapture(getElement()); + tHead.disableAutoColumnWidthCalculation(this); + + // Ensure last header cell is taking into account possible + // column selector + HeaderCell lastCell = tHead.getHeaderCell(tHead + .getVisibleCellCount() - 1); + tHead.resizeCaptionContainer(lastCell); + triggerLazyColumnAdjustment(true); + + fireColumnResizeEvent(cid, originalWidth, getColWidth(cid)); + break; + case Event.ONMOUSEMOVE: + if (!Util.isTouchEventOrLeftMouseButton(event)) { + return; + } + if (isResizing) { + final int deltaX = DOM.eventGetClientX(event) - dragStartX; + if (deltaX == 0) { + return; + } + tHead.disableAutoColumnWidthCalculation(this); + + int newWidth = originalWidth + deltaX; + if (newWidth < getMinWidth()) { + newWidth = getMinWidth(); + } + setColWidth(colIndex, newWidth, true); + triggerLazyColumnAdjustment(false); + forceRealignColumnHeaders(); + } + break; + default: + break; + } + } + + public int getMinWidth() { + int cellExtraWidth = 0; + if (scrollBody != null) { + cellExtraWidth += scrollBody.getCellExtraWidth(); + } + return cellExtraWidth + sortIndicator.getOffsetWidth(); + } + + public String getCaption() { + return DOM.getInnerText(captionContainer); + } + + public boolean isEnabled() { + return getParent() != null; + } + + public void setAlign(char c) { + final String ALIGN_PREFIX = CLASSNAME + "-caption-container-align-"; + if (align != c) { + captionContainer.removeClassName(ALIGN_PREFIX + "center"); + captionContainer.removeClassName(ALIGN_PREFIX + "right"); + captionContainer.removeClassName(ALIGN_PREFIX + "left"); + switch (c) { + case ALIGN_CENTER: + captionContainer.addClassName(ALIGN_PREFIX + "center"); + break; + case ALIGN_RIGHT: + captionContainer.addClassName(ALIGN_PREFIX + "right"); + break; + default: + captionContainer.addClassName(ALIGN_PREFIX + "left"); + break; + } + } + align = c; + } + + public char getAlign() { + return align; + } + + /** + * Detects the natural minimum width for the column of this header cell. + * If column is resized by user or the width is defined by server the + * actual width is returned. Else the natural min width is returned. + * + * @param columnIndex + * column index hint, if -1 (unknown) it will be detected + * + * @return + */ + public int getNaturalColumnWidth(int columnIndex) { + if (isDefinedWidth()) { + return width; + } else { + if (naturalWidth < 0) { + // This is recently revealed column. Try to detect a proper + // value (greater of header and data + // cols) + + int hw = captionContainer.getOffsetWidth() + + scrollBody.getCellExtraWidth(); + if (BrowserInfo.get().isGecko()) { + hw += sortIndicator.getOffsetWidth(); + } + if (columnIndex < 0) { + columnIndex = 0; + for (Iterator it = tHead.iterator(); it + .hasNext(); columnIndex++) { + if (it.next() == this) { + break; + } + } + } + final int cw = scrollBody.getColWidth(columnIndex); + naturalWidth = (hw > cw ? hw : cw); + } + return naturalWidth; + } + } + + public void setExpandRatio(float floatAttribute) { + if (floatAttribute != expandRatio) { + triggerLazyColumnAdjustment(false); + } + expandRatio = floatAttribute; + } + + public float getExpandRatio() { + return expandRatio; + } + + public boolean isSorted() { + return sorted; + } + } + + /** + * HeaderCell that is header cell for row headers. + * + * Reordering disabled and clicking on it resets sorting. + */ + public class RowHeadersHeaderCell extends HeaderCell { + + RowHeadersHeaderCell() { + super(ROW_HEADER_COLUMN_KEY, ""); + this.setStyleName(CLASSNAME + "-header-cell-rowheader"); + } + + @Override + protected void handleCaptionEvent(Event event) { + // NOP: RowHeaders cannot be reordered + // TODO It'd be nice to reset sorting here + } + } + + public class TableHead extends Panel implements ActionOwner { + + private static final int WRAPPER_WIDTH = 900000; + + ArrayList visibleCells = new ArrayList(); + + HashMap availableCells = new HashMap(); + + Element div = DOM.createDiv(); + Element hTableWrapper = DOM.createDiv(); + Element hTableContainer = DOM.createDiv(); + Element table = DOM.createTable(); + Element headerTableBody = DOM.createTBody(); + Element tr = DOM.createTR(); + + private final Element columnSelector = DOM.createDiv(); + + private int focusedSlot = -1; + + public TableHead() { + if (BrowserInfo.get().isIE()) { + table.setPropertyInt("cellSpacing", 0); + } + + DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden"); + DOM.setElementProperty(hTableWrapper, "className", CLASSNAME + + "-header"); + + // TODO move styles to CSS + DOM.setElementProperty(columnSelector, "className", CLASSNAME + + "-column-selector"); + DOM.setStyleAttribute(columnSelector, "display", "none"); + + DOM.appendChild(table, headerTableBody); + DOM.appendChild(headerTableBody, tr); + DOM.appendChild(hTableContainer, table); + DOM.appendChild(hTableWrapper, hTableContainer); + DOM.appendChild(div, hTableWrapper); + DOM.appendChild(div, columnSelector); + setElement(div); + + setStyleName(CLASSNAME + "-header-wrap"); + + DOM.sinkEvents(columnSelector, Event.ONCLICK); + + availableCells.put(ROW_HEADER_COLUMN_KEY, + new RowHeadersHeaderCell()); + } + + public void resizeCaptionContainer(HeaderCell cell) { + HeaderCell lastcell = getHeaderCell(visibleCells.size() - 1); + + // Measure column widths + int columnTotalWidth = 0; + for (Widget w : visibleCells) { + columnTotalWidth += w.getOffsetWidth(); + } + + if (cell == lastcell + && columnSelector.getOffsetWidth() > 0 + && columnTotalWidth >= div.getOffsetWidth() + - columnSelector.getOffsetWidth() + && !hasVerticalScrollbar()) { + // Ensure column caption is visible when placed under the column + // selector widget by shifting and resizing the caption. + int offset = 0; + int diff = div.getOffsetWidth() - columnTotalWidth; + if (diff < columnSelector.getOffsetWidth() && diff > 0) { + // If the difference is less than the column selectors width + // then just offset by the + // difference + offset = columnSelector.getOffsetWidth() - diff; + } else { + // Else offset by the whole column selector + offset = columnSelector.getOffsetWidth(); + } + lastcell.resizeCaptionContainer(offset); + } else { + cell.resizeCaptionContainer(0); + } + } + + @Override + public void clear() { + for (String cid : availableCells.keySet()) { + removeCell(cid); + } + availableCells.clear(); + availableCells.put(ROW_HEADER_COLUMN_KEY, + new RowHeadersHeaderCell()); + } + + public void updateCellsFromUIDL(UIDL uidl) { + Iterator it = uidl.getChildIterator(); + HashSet updated = new HashSet(); + boolean refreshContentWidths = false; + while (it.hasNext()) { + final UIDL col = (UIDL) it.next(); + final String cid = col.getStringAttribute("cid"); + updated.add(cid); + + String caption = buildCaptionHtmlSnippet(col); + HeaderCell c = getHeaderCell(cid); + if (c == null) { + c = new HeaderCell(cid, caption); + availableCells.put(cid, c); + if (initializedAndAttached) { + // we will need a column width recalculation + initializedAndAttached = false; + initialContentReceived = false; + isNewBody = true; + } + } else { + c.setText(caption); + } + + if (col.hasAttribute("sortable")) { + c.setSortable(true); + if (cid.equals(sortColumn)) { + c.setSorted(true); + } else { + c.setSorted(false); + } + } else { + c.setSortable(false); + } + + if (col.hasAttribute("align")) { + c.setAlign(col.getStringAttribute("align").charAt(0)); + } else { + c.setAlign(ALIGN_LEFT); + + } + if (col.hasAttribute("width")) { + final String widthStr = col.getStringAttribute("width"); + // Make sure to accomodate for the sort indicator if + // necessary. + int width = Integer.parseInt(widthStr); + if (width < c.getMinWidth()) { + width = c.getMinWidth(); + } + if (width != c.getWidth() && scrollBody != null) { + // Do a more thorough update if a column is resized from + // the server *after* the header has been properly + // initialized + final int colIx = getColIndexByKey(c.cid); + final int newWidth = width; + Scheduler.get().scheduleDeferred( + new ScheduledCommand() { + public void execute() { + setColWidth(colIx, newWidth, true); + } + }); + refreshContentWidths = true; + } else { + c.setWidth(width, true); + } + } else if (recalcWidths) { + c.setUndefinedWidth(); + } + if (col.hasAttribute("er")) { + c.setExpandRatio(col.getFloatAttribute("er")); + } + if (col.hasAttribute("collapsed")) { + // ensure header is properly removed from parent (case when + // collapsing happens via servers side api) + if (c.isAttached()) { + c.removeFromParent(); + headerChangedDuringUpdate = true; + } + } + } + + if (refreshContentWidths) { + // Recalculate the column sizings if any column has changed + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + public void execute() { + triggerLazyColumnAdjustment(true); + } + }); + } + + // check for orphaned header cells + for (Iterator cit = availableCells.keySet().iterator(); cit + .hasNext();) { + String cid = cit.next(); + if (!updated.contains(cid)) { + removeCell(cid); + cit.remove(); + // we will need a column width recalculation, since columns + // with expand ratios should expand to fill the void. + initializedAndAttached = false; + initialContentReceived = false; + isNewBody = true; + } + } + } + + public void enableColumn(String cid, int index) { + final HeaderCell c = getHeaderCell(cid); + if (!c.isEnabled() || getHeaderCell(index) != c) { + setHeaderCell(index, c); + if (initializedAndAttached) { + headerChangedDuringUpdate = true; + } + } + } + + public int getVisibleCellCount() { + return visibleCells.size(); + } + + public void setHorizontalScrollPosition(int scrollLeft) { + hTableWrapper.setScrollLeft(scrollLeft); + } + + public void setColumnCollapsingAllowed(boolean cc) { + if (cc) { + columnSelector.getStyle().setDisplay(Display.BLOCK); + } else { + columnSelector.getStyle().setDisplay(Display.NONE); + } + } + + public void disableBrowserIntelligence() { + hTableContainer.getStyle().setWidth(WRAPPER_WIDTH, Unit.PX); + } + + public void enableBrowserIntelligence() { + hTableContainer.getStyle().clearWidth(); + } + + public void setHeaderCell(int index, HeaderCell cell) { + if (cell.isEnabled()) { + // we're moving the cell + DOM.removeChild(tr, cell.getElement()); + orphan(cell); + visibleCells.remove(cell); + } + if (index < visibleCells.size()) { + // insert to right slot + DOM.insertChild(tr, cell.getElement(), index); + adopt(cell); + visibleCells.add(index, cell); + } else if (index == visibleCells.size()) { + // simply append + DOM.appendChild(tr, cell.getElement()); + adopt(cell); + visibleCells.add(cell); + } else { + throw new RuntimeException( + "Header cells must be appended in order"); + } + } + + public HeaderCell getHeaderCell(int index) { + if (index >= 0 && index < visibleCells.size()) { + return (HeaderCell) visibleCells.get(index); + } else { + return null; + } + } + + /** + * Get's HeaderCell by it's column Key. + * + * Note that this returns HeaderCell even if it is currently collapsed. + * + * @param cid + * Column key of accessed HeaderCell + * @return HeaderCell + */ + public HeaderCell getHeaderCell(String cid) { + return availableCells.get(cid); + } + + public void moveCell(int oldIndex, int newIndex) { + final HeaderCell hCell = getHeaderCell(oldIndex); + final Element cell = hCell.getElement(); + + visibleCells.remove(oldIndex); + DOM.removeChild(tr, cell); + + DOM.insertChild(tr, cell, newIndex); + visibleCells.add(newIndex, hCell); + } + + public Iterator iterator() { + return visibleCells.iterator(); + } + + @Override + public boolean remove(Widget w) { + if (visibleCells.contains(w)) { + visibleCells.remove(w); + orphan(w); + DOM.removeChild(DOM.getParent(w.getElement()), w.getElement()); + return true; + } + return false; + } + + public void removeCell(String colKey) { + final HeaderCell c = getHeaderCell(colKey); + remove(c); + } + + private void focusSlot(int index) { + removeSlotFocus(); + if (index > 0) { + DOM.setElementProperty( + DOM.getFirstChild(DOM.getChild(tr, index - 1)), + "className", CLASSNAME + "-resizer " + CLASSNAME + + "-focus-slot-right"); + } else { + DOM.setElementProperty( + DOM.getFirstChild(DOM.getChild(tr, index)), + "className", CLASSNAME + "-resizer " + CLASSNAME + + "-focus-slot-left"); + } + focusedSlot = index; + } + + private void removeSlotFocus() { + if (focusedSlot < 0) { + return; + } + if (focusedSlot == 0) { + DOM.setElementProperty( + DOM.getFirstChild(DOM.getChild(tr, focusedSlot)), + "className", CLASSNAME + "-resizer"); + } else if (focusedSlot > 0) { + DOM.setElementProperty( + DOM.getFirstChild(DOM.getChild(tr, focusedSlot - 1)), + "className", CLASSNAME + "-resizer"); + } + focusedSlot = -1; + } + + @Override + public void onBrowserEvent(Event event) { + if (enabled) { + if (event.getEventTarget().cast() == columnSelector) { + final int left = DOM.getAbsoluteLeft(columnSelector); + final int top = DOM.getAbsoluteTop(columnSelector) + + DOM.getElementPropertyInt(columnSelector, + "offsetHeight"); + client.getContextMenu().showAt(this, left, top); + } + } + } + + @Override + protected void onDetach() { + super.onDetach(); + if (client != null) { + client.getContextMenu().ensureHidden(this); + } + } + + class VisibleColumnAction extends Action { + + String colKey; + private boolean collapsed; + private VScrollTableRow currentlyFocusedRow; + + public VisibleColumnAction(String colKey) { + super(VScrollTable.TableHead.this); + this.colKey = colKey; + caption = tHead.getHeaderCell(colKey).getCaption(); + currentlyFocusedRow = focusedRow; + } + + @Override + public void execute() { + client.getContextMenu().hide(); + // toggle selected column + if (collapsedColumns.contains(colKey)) { + collapsedColumns.remove(colKey); + } else { + tHead.removeCell(colKey); + collapsedColumns.add(colKey); + triggerLazyColumnAdjustment(true); + } + + // update variable to server + client.updateVariable(paintableId, "collapsedcolumns", + collapsedColumns.toArray(new String[collapsedColumns + .size()]), false); + // let rowRequestHandler determine proper rows + rowRequestHandler.refreshContent(); + lazyRevertFocusToRow(currentlyFocusedRow); + } + + public void setCollapsed(boolean b) { + collapsed = b; + } + + /** + * Override default method to distinguish on/off columns + */ + @Override + public String getHTML() { + final StringBuffer buf = new StringBuffer(); + if (collapsed) { + buf.append(""); + } else { + buf.append(""); + } + buf.append(super.getHTML()); + buf.append(""); + + return buf.toString(); + } + + } + + /* + * Returns columns as Action array for column select popup + */ + public Action[] getActions() { + Object[] cols; + if (columnReordering && columnOrder != null) { + cols = columnOrder; + } else { + // if columnReordering is disabled, we need different way to get + // all available columns + cols = visibleColOrder; + cols = new Object[visibleColOrder.length + + collapsedColumns.size()]; + int i; + for (i = 0; i < visibleColOrder.length; i++) { + cols[i] = visibleColOrder[i]; + } + for (final Iterator it = collapsedColumns.iterator(); it + .hasNext();) { + cols[i++] = it.next(); + } + } + final Action[] actions = new Action[cols.length]; + + for (int i = 0; i < cols.length; i++) { + final String cid = (String) cols[i]; + final HeaderCell c = getHeaderCell(cid); + final VisibleColumnAction a = new VisibleColumnAction( + c.getColKey()); + a.setCaption(c.getCaption()); + if (!c.isEnabled()) { + a.setCollapsed(true); + } + actions[i] = a; + } + return actions; + } + + public ApplicationConnection getClient() { + return client; + } + + public String getPaintableId() { + return paintableId; + } + + /** + * Returns column alignments for visible columns + */ + public char[] getColumnAlignments() { + final Iterator it = visibleCells.iterator(); + final char[] aligns = new char[visibleCells.size()]; + int colIndex = 0; + while (it.hasNext()) { + aligns[colIndex++] = ((HeaderCell) it.next()).getAlign(); + } + return aligns; + } + + /** + * Disables the automatic calculation of all column widths by forcing + * the widths to be "defined" thus turning off expand ratios and such. + */ + public void disableAutoColumnWidthCalculation(HeaderCell source) { + for (HeaderCell cell : availableCells.values()) { + cell.disableAutoWidthCalculation(); + } + // fire column resize events for all columns but the source of the + // resize action, since an event will fire separately for this. + ArrayList columns = new ArrayList( + availableCells.values()); + columns.remove(source); + sendColumnWidthUpdates(columns); + forceRealignColumnHeaders(); + } + } + + /** + * A cell in the footer + */ + public class FooterCell extends Widget { + private final Element td = DOM.createTD(); + private final Element captionContainer = DOM.createDiv(); + private char align = ALIGN_LEFT; + private int width = -1; + private float expandRatio = 0; + private final String cid; + boolean definedWidth = false; + private int naturalWidth = -1; + + public FooterCell(String colId, String headerText) { + cid = colId; + + setText(headerText); + + DOM.setElementProperty(captionContainer, "className", CLASSNAME + + "-footer-container"); + + // ensure no clipping initially (problem on column additions) + DOM.setStyleAttribute(captionContainer, "overflow", "visible"); + + DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS); + + DOM.appendChild(td, captionContainer); + + DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK + | Event.ONCONTEXTMENU); + + setElement(td); + } + + /** + * Sets the text of the footer + * + * @param footerText + * The text in the footer + */ + public void setText(String footerText) { + DOM.setInnerHTML(captionContainer, footerText); + } + + /** + * Set alignment of the text in the cell + * + * @param c + * The alignment which can be ALIGN_CENTER, ALIGN_LEFT, + * ALIGN_RIGHT + */ + public void setAlign(char c) { + if (align != c) { + switch (c) { + case ALIGN_CENTER: + DOM.setStyleAttribute(captionContainer, "textAlign", + "center"); + break; + case ALIGN_RIGHT: + DOM.setStyleAttribute(captionContainer, "textAlign", + "right"); + break; + default: + DOM.setStyleAttribute(captionContainer, "textAlign", ""); + break; + } + } + align = c; + } + + /** + * Get the alignment of the text int the cell + * + * @return Returns either ALIGN_CENTER, ALIGN_LEFT or ALIGN_RIGHT + */ + public char getAlign() { + return align; + } + + /** + * Sets the width of the cell + * + * @param w + * The width of the cell + * @param ensureDefinedWidth + * Ensures the the given width is not recalculated + */ + public void setWidth(int w, boolean ensureDefinedWidth) { + + if (ensureDefinedWidth) { + definedWidth = true; + // on column resize expand ratio becomes zero + expandRatio = 0; + } + if (width == w) { + return; + } + if (width == -1) { + // go to default mode, clip content if necessary + DOM.setStyleAttribute(captionContainer, "overflow", ""); + } + width = w; + if (w == -1) { + DOM.setStyleAttribute(captionContainer, "width", ""); + setWidth(""); + } else { + + /* + * Reduce width with one pixel for the right border since the + * footers does not have any spacers between them. + */ + int borderWidths = 1; + + // Set the container width (check for negative value) + if (w - borderWidths >= 0) { + captionContainer.getStyle().setPropertyPx("width", + w - borderWidths); + } else { + captionContainer.getStyle().setPropertyPx("width", 0); + } + + /* + * if we already have tBody, set the header width properly, if + * not defer it. IE will fail with complex float in table header + * unless TD width is not explicitly set. + */ + if (scrollBody != null) { + /* + * Reduce with one since footer does not have any spacers, + * instead a 1 pixel border. + */ + int tdWidth = width + scrollBody.getCellExtraWidth() + - borderWidths; + setWidth(tdWidth + "px"); + } else { + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + int borderWidths = 1; + int tdWidth = width + + scrollBody.getCellExtraWidth() + - borderWidths; + setWidth(tdWidth + "px"); + } + }); + } + } + } + + /** + * Sets the width to undefined + */ + public void setUndefinedWidth() { + setWidth(-1, false); + } + + /** + * Detects if width is fixed by developer on server side or resized to + * current width by user. + * + * @return true if defined, false if "natural" width + */ + public boolean isDefinedWidth() { + return definedWidth && width >= 0; + } + + /** + * Returns the pixels width of the footer cell + * + * @return The width in pixels + */ + public int getWidth() { + return width; + } + + /** + * Sets the expand ratio of the cell + * + * @param floatAttribute + * The expand ratio + */ + public void setExpandRatio(float floatAttribute) { + expandRatio = floatAttribute; + } + + /** + * Returns the expand ration of the cell + * + * @return The expand ratio + */ + public float getExpandRatio() { + return expandRatio; + } + + /** + * Is the cell enabled? + * + * @return True if enabled else False + */ + public boolean isEnabled() { + return getParent() != null; + } + + /** + * Handle column clicking + */ + + @Override + public void onBrowserEvent(Event event) { + if (enabled && event != null) { + handleCaptionEvent(event); + + if (DOM.eventGetType(event) == Event.ONMOUSEUP) { + scrollBodyPanel.setFocus(true); + } + boolean stopPropagation = true; + if (event.getTypeInt() == Event.ONCONTEXTMENU + && !client.hasEventListeners(VScrollTable.this, + FOOTER_CLICK_EVENT_ID)) { + // Show browser context menu if a footer click listener is + // not present + stopPropagation = false; + } + if (stopPropagation) { + event.stopPropagation(); + event.preventDefault(); + } + } + } + + /** + * Handles a event on the captions + * + * @param event + * The event to handle + */ + protected void handleCaptionEvent(Event event) { + if (event.getTypeInt() == Event.ONMOUSEUP + || event.getTypeInt() == Event.ONDBLCLICK) { + fireFooterClickedEvent(event); + } + } + + /** + * Fires a footer click event after the user has clicked a column footer + * cell + * + * @param event + * The click event + */ + private void fireFooterClickedEvent(Event event) { + if (client.hasEventListeners(VScrollTable.this, + FOOTER_CLICK_EVENT_ID)) { + MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(event); + client.updateVariable(paintableId, "footerClickEvent", + details.toString(), false); + client.updateVariable(paintableId, "footerClickCID", cid, true); + } + } + + /** + * Returns the column key of the column + * + * @return The column key + */ + public String getColKey() { + return cid; + } + + /** + * Detects the natural minimum width for the column of this header cell. + * If column is resized by user or the width is defined by server the + * actual width is returned. Else the natural min width is returned. + * + * @param columnIndex + * column index hint, if -1 (unknown) it will be detected + * + * @return + */ + public int getNaturalColumnWidth(int columnIndex) { + if (isDefinedWidth()) { + return width; + } else { + if (naturalWidth < 0) { + // This is recently revealed column. Try to detect a proper + // value (greater of header and data + // cols) + + final int hw = ((Element) getElement().getLastChild()) + .getOffsetWidth() + scrollBody.getCellExtraWidth(); + if (columnIndex < 0) { + columnIndex = 0; + for (Iterator it = tHead.iterator(); it + .hasNext(); columnIndex++) { + if (it.next() == this) { + break; + } + } + } + final int cw = scrollBody.getColWidth(columnIndex); + naturalWidth = (hw > cw ? hw : cw); + } + return naturalWidth; + } + } + + public void setNaturalMinimumColumnWidth(int w) { + naturalWidth = w; + } + } + + /** + * HeaderCell that is header cell for row headers. + * + * Reordering disabled and clicking on it resets sorting. + */ + public class RowHeadersFooterCell extends FooterCell { + + RowHeadersFooterCell() { + super(ROW_HEADER_COLUMN_KEY, ""); + } + + @Override + protected void handleCaptionEvent(Event event) { + // NOP: RowHeaders cannot be reordered + // TODO It'd be nice to reset sorting here + } + } + + /** + * The footer of the table which can be seen in the bottom of the Table. + */ + public class TableFooter extends Panel { + + private static final int WRAPPER_WIDTH = 900000; + + ArrayList visibleCells = new ArrayList(); + HashMap availableCells = new HashMap(); + + Element div = DOM.createDiv(); + Element hTableWrapper = DOM.createDiv(); + Element hTableContainer = DOM.createDiv(); + Element table = DOM.createTable(); + Element headerTableBody = DOM.createTBody(); + Element tr = DOM.createTR(); + + public TableFooter() { + + DOM.setStyleAttribute(hTableWrapper, "overflow", "hidden"); + DOM.setElementProperty(hTableWrapper, "className", CLASSNAME + + "-footer"); + + DOM.appendChild(table, headerTableBody); + DOM.appendChild(headerTableBody, tr); + DOM.appendChild(hTableContainer, table); + DOM.appendChild(hTableWrapper, hTableContainer); + DOM.appendChild(div, hTableWrapper); + setElement(div); + + setStyleName(CLASSNAME + "-footer-wrap"); + + availableCells.put(ROW_HEADER_COLUMN_KEY, + new RowHeadersFooterCell()); + } + + @Override + public void clear() { + for (String cid : availableCells.keySet()) { + removeCell(cid); + } + availableCells.clear(); + availableCells.put(ROW_HEADER_COLUMN_KEY, + new RowHeadersFooterCell()); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.user.client.ui.Panel#remove(com.google.gwt.user.client + * .ui.Widget) + */ + @Override + public boolean remove(Widget w) { + if (visibleCells.contains(w)) { + visibleCells.remove(w); + orphan(w); + DOM.removeChild(DOM.getParent(w.getElement()), w.getElement()); + return true; + } + return false; + } + + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.HasWidgets#iterator() + */ + public Iterator iterator() { + return visibleCells.iterator(); + } + + /** + * Gets a footer cell which represents the given columnId + * + * @param cid + * The columnId + * + * @return The cell + */ + public FooterCell getFooterCell(String cid) { + return availableCells.get(cid); + } + + /** + * Gets a footer cell by using a column index + * + * @param index + * The index of the column + * @return The Cell + */ + public FooterCell getFooterCell(int index) { + if (index < visibleCells.size()) { + return (FooterCell) visibleCells.get(index); + } else { + return null; + } + } + + /** + * Updates the cells contents when updateUIDL request is received + * + * @param uidl + * The UIDL + */ + public void updateCellsFromUIDL(UIDL uidl) { + Iterator columnIterator = uidl.getChildIterator(); + HashSet updated = new HashSet(); + while (columnIterator.hasNext()) { + final UIDL col = (UIDL) columnIterator.next(); + final String cid = col.getStringAttribute("cid"); + updated.add(cid); + + String caption = col.hasAttribute("fcaption") ? col + .getStringAttribute("fcaption") : ""; + FooterCell c = getFooterCell(cid); + if (c == null) { + c = new FooterCell(cid, caption); + availableCells.put(cid, c); + if (initializedAndAttached) { + // we will need a column width recalculation + initializedAndAttached = false; + initialContentReceived = false; + isNewBody = true; + } + } else { + c.setText(caption); + } + + if (col.hasAttribute("align")) { + c.setAlign(col.getStringAttribute("align").charAt(0)); + } else { + c.setAlign(ALIGN_LEFT); + + } + if (col.hasAttribute("width")) { + if (scrollBody == null) { + // Already updated by setColWidth called from + // TableHeads.updateCellsFromUIDL in case of a server + // side resize + final String width = col.getStringAttribute("width"); + c.setWidth(Integer.parseInt(width), true); + } + } else if (recalcWidths) { + c.setUndefinedWidth(); + } + if (col.hasAttribute("er")) { + c.setExpandRatio(col.getFloatAttribute("er")); + } + if (col.hasAttribute("collapsed")) { + // ensure header is properly removed from parent (case when + // collapsing happens via servers side api) + if (c.isAttached()) { + c.removeFromParent(); + headerChangedDuringUpdate = true; + } + } + } + + // check for orphaned header cells + for (Iterator cit = availableCells.keySet().iterator(); cit + .hasNext();) { + String cid = cit.next(); + if (!updated.contains(cid)) { + removeCell(cid); + cit.remove(); + } + } + } + + /** + * Set a footer cell for a specified column index + * + * @param index + * The index + * @param cell + * The footer cell + */ + public void setFooterCell(int index, FooterCell cell) { + if (cell.isEnabled()) { + // we're moving the cell + DOM.removeChild(tr, cell.getElement()); + orphan(cell); + visibleCells.remove(cell); + } + if (index < visibleCells.size()) { + // insert to right slot + DOM.insertChild(tr, cell.getElement(), index); + adopt(cell); + visibleCells.add(index, cell); + } else if (index == visibleCells.size()) { + // simply append + DOM.appendChild(tr, cell.getElement()); + adopt(cell); + visibleCells.add(cell); + } else { + throw new RuntimeException( + "Header cells must be appended in order"); + } + } + + /** + * Remove a cell by using the columnId + * + * @param colKey + * The columnId to remove + */ + public void removeCell(String colKey) { + final FooterCell c = getFooterCell(colKey); + remove(c); + } + + /** + * Enable a column (Sets the footer cell) + * + * @param cid + * The columnId + * @param index + * The index of the column + */ + public void enableColumn(String cid, int index) { + final FooterCell c = getFooterCell(cid); + if (!c.isEnabled() || getFooterCell(index) != c) { + setFooterCell(index, c); + if (initializedAndAttached) { + headerChangedDuringUpdate = true; + } + } + } + + /** + * Disable browser measurement of the table width + */ + public void disableBrowserIntelligence() { + DOM.setStyleAttribute(hTableContainer, "width", WRAPPER_WIDTH + + "px"); + } + + /** + * Enable browser measurement of the table width + */ + public void enableBrowserIntelligence() { + DOM.setStyleAttribute(hTableContainer, "width", ""); + } + + /** + * Set the horizontal position in the cell in the footer. This is done + * when a horizontal scrollbar is present. + * + * @param scrollLeft + * The value of the leftScroll + */ + public void setHorizontalScrollPosition(int scrollLeft) { + hTableWrapper.setScrollLeft(scrollLeft); + } + + /** + * Swap cells when the column are dragged + * + * @param oldIndex + * The old index of the cell + * @param newIndex + * The new index of the cell + */ + public void moveCell(int oldIndex, int newIndex) { + final FooterCell hCell = getFooterCell(oldIndex); + final Element cell = hCell.getElement(); + + visibleCells.remove(oldIndex); + DOM.removeChild(tr, cell); + + DOM.insertChild(tr, cell, newIndex); + visibleCells.add(newIndex, hCell); + } + } + + /** + * This Panel can only contain VScrollTableRow type of widgets. This + * "simulates" very large table, keeping spacers which take room of + * unrendered rows. + * + */ + public class VScrollTableBody extends Panel { + + public static final int DEFAULT_ROW_HEIGHT = 24; + + private double rowHeight = -1; + + private final LinkedList renderedRows = new LinkedList(); + + /** + * Due some optimizations row height measuring is deferred and initial + * set of rows is rendered detached. Flag set on when table body has + * been attached in dom and rowheight has been measured. + */ + private boolean tBodyMeasurementsDone = false; + + Element preSpacer = DOM.createDiv(); + Element postSpacer = DOM.createDiv(); + + Element container = DOM.createDiv(); + + TableSectionElement tBodyElement = Document.get().createTBodyElement(); + Element table = DOM.createTable(); + + private int firstRendered; + private int lastRendered; + + private char[] aligns; + + protected VScrollTableBody() { + constructDOM(); + setElement(container); + } + + public VScrollTableRow getRowByRowIndex(int indexInTable) { + int internalIndex = indexInTable - firstRendered; + if (internalIndex >= 0 && internalIndex < renderedRows.size()) { + return (VScrollTableRow) renderedRows.get(internalIndex); + } else { + return null; + } + } + + /** + * @return the height of scrollable body, subpixels ceiled. + */ + public int getRequiredHeight() { + return preSpacer.getOffsetHeight() + postSpacer.getOffsetHeight() + + Util.getRequiredHeight(table); + } + + private void constructDOM() { + DOM.setElementProperty(table, "className", CLASSNAME + "-table"); + if (BrowserInfo.get().isIE()) { + table.setPropertyInt("cellSpacing", 0); + } + DOM.setElementProperty(preSpacer, "className", CLASSNAME + + "-row-spacer"); + DOM.setElementProperty(postSpacer, "className", CLASSNAME + + "-row-spacer"); + + table.appendChild(tBodyElement); + DOM.appendChild(container, preSpacer); + DOM.appendChild(container, table); + DOM.appendChild(container, postSpacer); + + } + + public int getAvailableWidth() { + int availW = scrollBodyPanel.getOffsetWidth() - getBorderWidth(); + return availW; + } + + public void renderInitialRows(UIDL rowData, int firstIndex, int rows) { + firstRendered = firstIndex; + lastRendered = firstIndex + rows - 1; + final Iterator it = rowData.getChildIterator(); + aligns = tHead.getColumnAlignments(); + while (it.hasNext()) { + final VScrollTableRow row = createRow((UIDL) it.next(), aligns); + addRow(row); + } + if (isAttached()) { + fixSpacers(); + } + } + + public void renderRows(UIDL rowData, int firstIndex, int rows) { + // FIXME REVIEW + aligns = tHead.getColumnAlignments(); + final Iterator it = rowData.getChildIterator(); + if (firstIndex == lastRendered + 1) { + while (it.hasNext()) { + final VScrollTableRow row = prepareRow((UIDL) it.next()); + addRow(row); + lastRendered++; + } + fixSpacers(); + } else if (firstIndex + rows == firstRendered) { + final VScrollTableRow[] rowArray = new VScrollTableRow[rows]; + int i = rows; + while (it.hasNext()) { + i--; + rowArray[i] = prepareRow((UIDL) it.next()); + } + for (i = 0; i < rows; i++) { + addRowBeforeFirstRendered(rowArray[i]); + firstRendered--; + } + } else { + // completely new set of rows + while (lastRendered + 1 > firstRendered) { + unlinkRow(false); + } + final VScrollTableRow row = prepareRow((UIDL) it.next()); + firstRendered = firstIndex; + lastRendered = firstIndex - 1; + addRow(row); + lastRendered++; + setContainerHeight(); + fixSpacers(); + while (it.hasNext()) { + addRow(prepareRow((UIDL) it.next())); + lastRendered++; + } + fixSpacers(); + } + + // this may be a new set of rows due content change, + // ensure we have proper cache rows + ensureCacheFilled(); + } + + /** + * Ensure we have the correct set of rows on client side, e.g. if the + * content on the server side has changed, or the client scroll position + * has changed since the last request. + */ + protected void ensureCacheFilled() { + int reactFirstRow = (int) (firstRowInViewPort - pageLength + * cache_react_rate); + int reactLastRow = (int) (firstRowInViewPort + pageLength + pageLength + * cache_react_rate); + if (reactFirstRow < 0) { + reactFirstRow = 0; + } + if (reactLastRow >= totalRows) { + reactLastRow = totalRows - 1; + } + if (lastRendered < reactFirstRow || firstRendered > reactLastRow) { + /* + * #8040 - scroll position is completely changed since the + * latest request, so request a new set of rows. + * + * TODO: We should probably check whether the fetched rows match + * the current scroll position right when they arrive, so as to + * not waste time rendering a set of rows that will never be + * visible... + */ + rowRequestHandler.setReqFirstRow(reactFirstRow); + rowRequestHandler.setReqRows(reactLastRow - reactFirstRow + 1); + rowRequestHandler.deferRowFetch(1); + } else if (lastRendered < reactLastRow) { + // get some cache rows below visible area + rowRequestHandler.setReqFirstRow(lastRendered + 1); + rowRequestHandler.setReqRows(reactLastRow - lastRendered); + rowRequestHandler.deferRowFetch(1); + } else if (firstRendered > reactFirstRow) { + /* + * Branch for fetching cache above visible area. + * + * If cache needed for both before and after visible area, this + * will be rendered after-cache is received and rendered. So in + * some rare situations the table may make two cache visits to + * server. + */ + rowRequestHandler.setReqFirstRow(reactFirstRow); + rowRequestHandler.setReqRows(firstRendered - reactFirstRow); + rowRequestHandler.deferRowFetch(1); + } + } + + /** + * Inserts rows as provided in the rowData starting at firstIndex. + * + * @param rowData + * @param firstIndex + * @param rows + * the number of rows + * @return a list of the rows added. + */ + protected List insertRows(UIDL rowData, + int firstIndex, int rows) { + aligns = tHead.getColumnAlignments(); + final Iterator it = rowData.getChildIterator(); + List insertedRows = new ArrayList(); + + if (firstIndex == lastRendered + 1) { + while (it.hasNext()) { + final VScrollTableRow row = prepareRow((UIDL) it.next()); + addRow(row); + insertedRows.add(row); + lastRendered++; + } + fixSpacers(); + } else if (firstIndex + rows == firstRendered) { + final VScrollTableRow[] rowArray = new VScrollTableRow[rows]; + int i = rows; + while (it.hasNext()) { + i--; + rowArray[i] = prepareRow((UIDL) it.next()); + } + for (i = 0; i < rows; i++) { + addRowBeforeFirstRendered(rowArray[i]); + insertedRows.add(rowArray[i]); + firstRendered--; + } + } else { + // insert in the middle + int ix = firstIndex; + while (it.hasNext()) { + VScrollTableRow row = prepareRow((UIDL) it.next()); + insertRowAt(row, ix); + insertedRows.add(row); + lastRendered++; + ix++; + } + fixSpacers(); + } + return insertedRows; + } + + protected List insertAndReindexRows(UIDL rowData, + int firstIndex, int rows) { + List inserted = insertRows(rowData, firstIndex, + rows); + int actualIxOfFirstRowAfterInserted = firstIndex + rows + - firstRendered; + for (int ix = actualIxOfFirstRowAfterInserted; ix < renderedRows + .size(); ix++) { + VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix); + r.setIndex(r.getIndex() + rows); + } + setContainerHeight(); + return inserted; + } + + protected void insertRowsDeleteBelow(UIDL rowData, int firstIndex, + int rows) { + unlinkAllRowsStartingAt(firstIndex); + insertRows(rowData, firstIndex, rows); + setContainerHeight(); + } + + /** + * This method is used to instantiate new rows for this table. It + * automatically sets correct widths to rows cells and assigns correct + * client reference for child widgets. + * + * This method can be called only after table has been initialized + * + * @param uidl + */ + private VScrollTableRow prepareRow(UIDL uidl) { + final VScrollTableRow row = createRow(uidl, aligns); + row.initCellWidths(); + return row; + } + + protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) { + if (uidl.hasAttribute("gen_html")) { + // This is a generated row. + return new VScrollTableGeneratedRow(uidl, aligns2); + } + return new VScrollTableRow(uidl, aligns2); + } + + private void addRowBeforeFirstRendered(VScrollTableRow row) { + row.setIndex(firstRendered - 1); + if (row.isSelected()) { + row.addStyleName("v-selected"); + } + tBodyElement.insertBefore(row.getElement(), + tBodyElement.getFirstChild()); + adopt(row); + renderedRows.add(0, row); + } + + private void addRow(VScrollTableRow row) { + row.setIndex(firstRendered + renderedRows.size()); + if (row.isSelected()) { + row.addStyleName("v-selected"); + } + tBodyElement.appendChild(row.getElement()); + adopt(row); + renderedRows.add(row); + } + + private void insertRowAt(VScrollTableRow row, int index) { + row.setIndex(index); + if (row.isSelected()) { + row.addStyleName("v-selected"); + } + if (index > 0) { + VScrollTableRow sibling = getRowByRowIndex(index - 1); + tBodyElement + .insertAfter(row.getElement(), sibling.getElement()); + } else { + VScrollTableRow sibling = getRowByRowIndex(index); + tBodyElement.insertBefore(row.getElement(), + sibling.getElement()); + } + adopt(row); + int actualIx = index - firstRendered; + renderedRows.add(actualIx, row); + } + + public Iterator iterator() { + return renderedRows.iterator(); + } + + /** + * @return false if couldn't remove row + */ + protected boolean unlinkRow(boolean fromBeginning) { + if (lastRendered - firstRendered < 0) { + return false; + } + int actualIx; + if (fromBeginning) { + actualIx = 0; + firstRendered++; + } else { + actualIx = renderedRows.size() - 1; + lastRendered--; + } + if (actualIx >= 0) { + unlinkRowAtActualIndex(actualIx); + fixSpacers(); + return true; + } + return false; + } + + protected void unlinkRows(int firstIndex, int count) { + if (count < 1) { + return; + } + if (firstRendered > firstIndex + && firstRendered < firstIndex + count) { + firstIndex = firstRendered; + } + int lastIndex = firstIndex + count - 1; + if (lastRendered < lastIndex) { + lastIndex = lastRendered; + } + for (int ix = lastIndex; ix >= firstIndex; ix--) { + unlinkRowAtActualIndex(actualIndex(ix)); + lastRendered--; + } + fixSpacers(); + } + + protected void unlinkAndReindexRows(int firstIndex, int count) { + unlinkRows(firstIndex, count); + int actualFirstIx = firstIndex - firstRendered; + for (int ix = actualFirstIx; ix < renderedRows.size(); ix++) { + VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix); + r.setIndex(r.getIndex() - count); + } + setContainerHeight(); + } + + protected void unlinkAllRowsStartingAt(int index) { + if (firstRendered > index) { + index = firstRendered; + } + for (int ix = renderedRows.size() - 1; ix >= index; ix--) { + unlinkRowAtActualIndex(actualIndex(ix)); + lastRendered--; + } + fixSpacers(); + } + + private int actualIndex(int index) { + return index - firstRendered; + } + + private void unlinkRowAtActualIndex(int index) { + final VScrollTableRow toBeRemoved = (VScrollTableRow) renderedRows + .get(index); + // Unregister row tooltip + client.registerTooltip(VScrollTable.this, toBeRemoved.getElement(), + null); + for (int i = 0; i < toBeRemoved.getElement().getChildCount(); i++) { + // Unregister cell tooltips + Element td = toBeRemoved.getElement().getChild(i).cast(); + client.registerTooltip(VScrollTable.this, td, null); + } + tBodyElement.removeChild(toBeRemoved.getElement()); + orphan(toBeRemoved); + renderedRows.remove(index); + } + + @Override + public boolean remove(Widget w) { + throw new UnsupportedOperationException(); + } + + /** + * Fix container blocks height according to totalRows to avoid + * "bouncing" when scrolling + */ + private void setContainerHeight() { + fixSpacers(); + DOM.setStyleAttribute(container, "height", + measureRowHeightOffset(totalRows) + "px"); + } + + private void fixSpacers() { + int prepx = measureRowHeightOffset(firstRendered); + if (prepx < 0) { + prepx = 0; + } + preSpacer.getStyle().setPropertyPx("height", prepx); + int postpx = measureRowHeightOffset(totalRows - 1) + - measureRowHeightOffset(lastRendered); + if (postpx < 0) { + postpx = 0; + } + postSpacer.getStyle().setPropertyPx("height", postpx); + } + + public double getRowHeight() { + return getRowHeight(false); + } + + public double getRowHeight(boolean forceUpdate) { + if (tBodyMeasurementsDone && !forceUpdate) { + return rowHeight; + } else { + + if (tBodyElement.getRows().getLength() > 0) { + int tableHeight = getTableHeight(); + int rowCount = tBodyElement.getRows().getLength(); + rowHeight = tableHeight / (double) rowCount; + } else { + if (isAttached()) { + // measure row height by adding a dummy row + VScrollTableRow scrollTableRow = new VScrollTableRow(); + tBodyElement.appendChild(scrollTableRow.getElement()); + getRowHeight(forceUpdate); + tBodyElement.removeChild(scrollTableRow.getElement()); + } else { + // TODO investigate if this can never happen anymore + return DEFAULT_ROW_HEIGHT; + } + } + tBodyMeasurementsDone = true; + return rowHeight; + } + } + + public int getTableHeight() { + return table.getOffsetHeight(); + } + + /** + * Returns the width available for column content. + * + * @param columnIndex + * @return + */ + public int getColWidth(int columnIndex) { + if (tBodyMeasurementsDone) { + if (renderedRows.isEmpty()) { + // no rows yet rendered + return 0; + } + for (Widget row : renderedRows) { + if (!(row instanceof VScrollTableGeneratedRow)) { + TableRowElement tr = row.getElement().cast(); + Element wrapperdiv = tr.getCells().getItem(columnIndex) + .getFirstChildElement().cast(); + return wrapperdiv.getOffsetWidth(); + } + } + return 0; + } else { + return 0; + } + } + + /** + * Sets the content width of a column. + * + * Due IE limitation, we must set the width to a wrapper elements inside + * table cells (with overflow hidden, which does not work on td + * elements). + * + * To get this work properly crossplatform, we will also set the width + * of td. + * + * @param colIndex + * @param w + */ + public void setColWidth(int colIndex, int w) { + for (Widget row : renderedRows) { + ((VScrollTableRow) row).setCellWidth(colIndex, w); + } + } + + private int cellExtraWidth = -1; + + /** + * Method to return the space used for cell paddings + border. + */ + private int getCellExtraWidth() { + if (cellExtraWidth < 0) { + detectExtrawidth(); + } + return cellExtraWidth; + } + + private void detectExtrawidth() { + NodeList rows = tBodyElement.getRows(); + if (rows.getLength() == 0) { + /* need to temporary add empty row and detect */ + VScrollTableRow scrollTableRow = new VScrollTableRow(); + tBodyElement.appendChild(scrollTableRow.getElement()); + detectExtrawidth(); + tBodyElement.removeChild(scrollTableRow.getElement()); + } else { + boolean noCells = false; + TableRowElement item = rows.getItem(0); + TableCellElement firstTD = item.getCells().getItem(0); + if (firstTD == null) { + // content is currently empty, we need to add a fake cell + // for measuring + noCells = true; + VScrollTableRow next = (VScrollTableRow) iterator().next(); + boolean sorted = tHead.getHeaderCell(0) != null ? tHead + .getHeaderCell(0).isSorted() : false; + next.addCell(null, "", ALIGN_LEFT, "", true, sorted); + firstTD = item.getCells().getItem(0); + } + com.google.gwt.dom.client.Element wrapper = firstTD + .getFirstChildElement(); + cellExtraWidth = firstTD.getOffsetWidth() + - wrapper.getOffsetWidth(); + if (noCells) { + firstTD.getParentElement().removeChild(firstTD); + } + } + } + + private void reLayoutComponents() { + for (Widget w : this) { + VScrollTableRow r = (VScrollTableRow) w; + for (Widget widget : r) { + client.handleComponentRelativeSize(widget); + } + } + } + + public int getLastRendered() { + return lastRendered; + } + + public int getFirstRendered() { + return firstRendered; + } + + public void moveCol(int oldIndex, int newIndex) { + + // loop all rows and move given index to its new place + final Iterator rows = iterator(); + while (rows.hasNext()) { + final VScrollTableRow row = (VScrollTableRow) rows.next(); + + final Element td = DOM.getChild(row.getElement(), oldIndex); + if (td != null) { + DOM.removeChild(row.getElement(), td); + + DOM.insertChild(row.getElement(), td, newIndex); + } + } + + } + + /** + * Restore row visibility which is set to "none" when the row is + * rendered (due a performance optimization). + */ + private void restoreRowVisibility() { + for (Widget row : renderedRows) { + row.getElement().getStyle().setProperty("visibility", ""); + } + } + + public class VScrollTableRow extends Panel implements ActionOwner { + + private static final int TOUCHSCROLL_TIMEOUT = 70; + private static final int DRAGMODE_MULTIROW = 2; + protected ArrayList childWidgets = new ArrayList(); + private boolean selected = false; + protected final int rowKey; + + private String[] actionKeys = null; + private final TableRowElement rowElement; + private boolean mDown; + private int index; + private Event touchStart; + private static final String ROW_CLASSNAME_EVEN = CLASSNAME + "-row"; + private static final String ROW_CLASSNAME_ODD = CLASSNAME + + "-row-odd"; + private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500; + private Timer contextTouchTimeout; + private int touchStartY; + private int touchStartX; + + private VScrollTableRow(int rowKey) { + this.rowKey = rowKey; + rowElement = Document.get().createTRElement(); + setElement(rowElement); + DOM.sinkEvents(getElement(), Event.MOUSEEVENTS + | Event.TOUCHEVENTS | Event.ONDBLCLICK + | Event.ONCONTEXTMENU | VTooltip.TOOLTIP_EVENTS); + } + + public VScrollTableRow(UIDL uidl, char[] aligns) { + this(uidl.getIntAttribute("key")); + + /* + * Rendering the rows as hidden improves Firefox and Safari + * performance drastically. + */ + getElement().getStyle().setProperty("visibility", "hidden"); + + String rowStyle = uidl.getStringAttribute("rowstyle"); + if (rowStyle != null) { + addStyleName(CLASSNAME + "-row-" + rowStyle); + } + + String rowDescription = uidl.getStringAttribute("rowdescr"); + if (rowDescription != null && !rowDescription.equals("")) { + TooltipInfo info = new TooltipInfo(rowDescription); + client.registerTooltip(VScrollTable.this, rowElement, info); + } else { + // Remove possibly previously set tooltip + client.registerTooltip(VScrollTable.this, rowElement, null); + } + + tHead.getColumnAlignments(); + int col = 0; + int visibleColumnIndex = -1; + + // row header + if (showRowHeaders) { + boolean sorted = tHead.getHeaderCell(col).isSorted(); + addCell(uidl, buildCaptionHtmlSnippet(uidl), aligns[col++], + "rowheader", true, sorted); + visibleColumnIndex++; + } + + if (uidl.hasAttribute("al")) { + actionKeys = uidl.getStringArrayAttribute("al"); + } + + addCellsFromUIDL(uidl, aligns, col, visibleColumnIndex); + + if (uidl.hasAttribute("selected") && !isSelected()) { + toggleSelection(); + } + } + + /** + * Add a dummy row, used for measurements if Table is empty. + */ + public VScrollTableRow() { + this(0); + addStyleName(CLASSNAME + "-row"); + addCell(null, "_", 'b', "", true, false); + } + + protected void initCellWidths() { + final int cells = tHead.getVisibleCellCount(); + for (int i = 0; i < cells; i++) { + int w = VScrollTable.this.getColWidth(getColKeyByIndex(i)); + if (w < 0) { + w = 0; + } + setCellWidth(i, w); + } + } + + protected void setCellWidth(int cellIx, int width) { + final Element cell = DOM.getChild(getElement(), cellIx); + cell.getFirstChildElement().getStyle() + .setPropertyPx("width", width); + cell.getStyle().setPropertyPx("width", width); + } + + protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col, + int visibleColumnIndex) { + final Iterator cells = uidl.getChildIterator(); + while (cells.hasNext()) { + final Object cell = cells.next(); + visibleColumnIndex++; + + String columnId = visibleColOrder[visibleColumnIndex]; + + String style = ""; + if (uidl.hasAttribute("style-" + columnId)) { + style = uidl.getStringAttribute("style-" + columnId); + } + + String description = null; + if (uidl.hasAttribute("descr-" + columnId)) { + description = uidl.getStringAttribute("descr-" + + columnId); + } + + boolean sorted = tHead.getHeaderCell(col).isSorted(); + if (cell instanceof String) { + addCell(uidl, cell.toString(), aligns[col++], style, + isRenderHtmlInCells(), sorted, description); + } else { + final ComponentConnector cellContent = client + .getPaintable((UIDL) cell); + + addCell(uidl, cellContent.getWidget(), aligns[col++], + style, sorted); + } + } + } + + /** + * Overriding this and returning true causes all text cells to be + * rendered as HTML. + * + * @return always returns false in the default implementation + */ + protected boolean isRenderHtmlInCells() { + return false; + } + + /** + * Detects whether row is visible in tables viewport. + * + * @return + */ + public boolean isInViewPort() { + int absoluteTop = getAbsoluteTop(); + int scrollPosition = scrollBodyPanel.getScrollPosition(); + if (absoluteTop < scrollPosition) { + return false; + } + int maxVisible = scrollPosition + + scrollBodyPanel.getOffsetHeight() - getOffsetHeight(); + if (absoluteTop > maxVisible) { + return false; + } + return true; + } + + /** + * Makes a check based on indexes whether the row is before the + * compared row. + * + * @param row1 + * @return true if this rows index is smaller than in the row1 + */ + public boolean isBefore(VScrollTableRow row1) { + return getIndex() < row1.getIndex(); + } + + /** + * Sets the index of the row in the whole table. Currently used just + * to set even/odd classname + * + * @param indexInWholeTable + */ + private void setIndex(int indexInWholeTable) { + index = indexInWholeTable; + boolean isOdd = indexInWholeTable % 2 == 0; + // Inverted logic to be backwards compatible with earlier 6.4. + // It is very strange because rows 1,3,5 are considered "even" + // and 2,4,6 "odd". + // + // First remove any old styles so that both styles aren't + // applied when indexes are updated. + removeStyleName(ROW_CLASSNAME_ODD); + removeStyleName(ROW_CLASSNAME_EVEN); + if (!isOdd) { + addStyleName(ROW_CLASSNAME_ODD); + } else { + addStyleName(ROW_CLASSNAME_EVEN); + } + } + + public int getIndex() { + return index; + } + + @Override + protected void onDetach() { + super.onDetach(); + client.getContextMenu().ensureHidden(this); + } + + public String getKey() { + return String.valueOf(rowKey); + } + + public void addCell(UIDL rowUidl, String text, char align, + String style, boolean textIsHTML, boolean sorted) { + addCell(rowUidl, text, align, style, textIsHTML, sorted, null); + } + + public void addCell(UIDL rowUidl, String text, char align, + String style, boolean textIsHTML, boolean sorted, + String description) { + // String only content is optimized by not using Label widget + final TableCellElement td = DOM.createTD().cast(); + initCellWithText(text, align, style, textIsHTML, sorted, + description, td); + } + + protected void initCellWithText(String text, char align, + String style, boolean textIsHTML, boolean sorted, + String description, final TableCellElement td) { + final Element container = DOM.createDiv(); + String className = CLASSNAME + "-cell-content"; + if (style != null && !style.equals("")) { + className += " " + CLASSNAME + "-cell-content-" + style; + } + if (sorted) { + className += " " + CLASSNAME + "-cell-content-sorted"; + } + td.setClassName(className); + container.setClassName(CLASSNAME + "-cell-wrapper"); + if (textIsHTML) { + container.setInnerHTML(text); + } else { + container.setInnerText(text); + } + if (align != ALIGN_LEFT) { + switch (align) { + case ALIGN_CENTER: + container.getStyle().setProperty("textAlign", "center"); + break; + case ALIGN_RIGHT: + default: + container.getStyle().setProperty("textAlign", "right"); + break; + } + } + + if (description != null && !description.equals("")) { + TooltipInfo info = new TooltipInfo(description); + client.registerTooltip(VScrollTable.this, td, info); + } else { + // Remove possibly previously set tooltip + client.registerTooltip(VScrollTable.this, td, null); + } + + td.appendChild(container); + getElement().appendChild(td); + } + + public void addCell(UIDL rowUidl, Widget w, char align, + String style, boolean sorted) { + final TableCellElement td = DOM.createTD().cast(); + initCellWithWidget(w, align, style, sorted, td); + } + + protected void initCellWithWidget(Widget w, char align, + String style, boolean sorted, final TableCellElement td) { + final Element container = DOM.createDiv(); + String className = CLASSNAME + "-cell-content"; + if (style != null && !style.equals("")) { + className += " " + CLASSNAME + "-cell-content-" + style; + } + if (sorted) { + className += " " + CLASSNAME + "-cell-content-sorted"; + } + td.setClassName(className); + container.setClassName(CLASSNAME + "-cell-wrapper"); + // TODO most components work with this, but not all (e.g. + // Select) + // Old comment: make widget cells respect align. + // text-align:center for IE, margin: auto for others + if (align != ALIGN_LEFT) { + switch (align) { + case ALIGN_CENTER: + container.getStyle().setProperty("textAlign", "center"); + break; + case ALIGN_RIGHT: + default: + container.getStyle().setProperty("textAlign", "right"); + break; + } + } + td.appendChild(container); + getElement().appendChild(td); + // ensure widget not attached to another element (possible tBody + // change) + w.removeFromParent(); + container.appendChild(w.getElement()); + adopt(w); + childWidgets.add(w); + } + + public Iterator iterator() { + return childWidgets.iterator(); + } + + @Override + public boolean remove(Widget w) { + if (childWidgets.contains(w)) { + orphan(w); + DOM.removeChild(DOM.getParent(w.getElement()), + w.getElement()); + childWidgets.remove(w); + return true; + } else { + return false; + } + } + + /** + * If there are registered click listeners, sends a click event and + * returns true. Otherwise, does nothing and returns false. + * + * @param event + * @param targetTdOrTr + * @param immediate + * Whether the event is sent immediately + * @return Whether a click event was sent + */ + private boolean handleClickEvent(Event event, Element targetTdOrTr, + boolean immediate) { + if (!client.hasEventListeners(VScrollTable.this, + ITEM_CLICK_EVENT_ID)) { + // Don't send an event if nobody is listening + return false; + } + + // This row was clicked + client.updateVariable(paintableId, "clickedKey", "" + rowKey, + false); + + if (getElement() == targetTdOrTr.getParentElement()) { + // A specific column was clicked + int childIndex = DOM.getChildIndex(getElement(), + targetTdOrTr); + String colKey = null; + colKey = tHead.getHeaderCell(childIndex).getColKey(); + client.updateVariable(paintableId, "clickedColKey", colKey, + false); + } + + MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(event); + + client.updateVariable(paintableId, "clickEvent", + details.toString(), immediate); + + return true; + } + + private void handleTooltips(final Event event, Element target) { + if (target.hasTagName("TD")) { + // Table cell (td) + Element container = target.getFirstChildElement().cast(); + Element widget = container.getFirstChildElement().cast(); + + boolean containsWidget = false; + for (Widget w : childWidgets) { + if (widget == w.getElement()) { + containsWidget = true; + break; + } + } + + if (!containsWidget) { + // Only text nodes has tooltips + if (ConnectorMap.get(client).getWidgetTooltipInfo( + VScrollTable.this, target) != null) { + // Cell has description, use it + client.handleTooltipEvent(event, VScrollTable.this, + target); + } else { + // Cell might have row description, use row + // description + client.handleTooltipEvent(event, VScrollTable.this, + target.getParentElement()); + } + } + + } else { + // Table row (tr) + client.handleTooltipEvent(event, VScrollTable.this, target); + } + } + + /* + * React on click that occur on content cells only + */ + @Override + public void onBrowserEvent(final Event event) { + if (enabled) { + final int type = event.getTypeInt(); + final Element targetTdOrTr = getEventTargetTdOrTr(event); + if (type == Event.ONCONTEXTMENU) { + showContextMenu(event); + if (enabled + && (actionKeys != null || client + .hasEventListeners(VScrollTable.this, + ITEM_CLICK_EVENT_ID))) { + /* + * Prevent browser context menu only if there are + * action handlers or item click listeners + * registered + */ + event.stopPropagation(); + event.preventDefault(); + } + return; + } + + boolean targetCellOrRowFound = targetTdOrTr != null; + if (targetCellOrRowFound) { + handleTooltips(event, targetTdOrTr); + } + + switch (type) { + case Event.ONDBLCLICK: + if (targetCellOrRowFound) { + handleClickEvent(event, targetTdOrTr, true); + } + break; + case Event.ONMOUSEUP: + if (targetCellOrRowFound) { + mDown = false; + /* + * Queue here, send at the same time as the + * corresponding value change event - see #7127 + */ + boolean clickEventSent = handleClickEvent(event, + targetTdOrTr, false); + + if (event.getButton() == Event.BUTTON_LEFT + && isSelectable()) { + + // Ctrl+Shift click + if ((event.getCtrlKey() || event.getMetaKey()) + && event.getShiftKey() + && isMultiSelectModeDefault()) { + toggleShiftSelection(false); + setRowFocus(this); + + // Ctrl click + } else if ((event.getCtrlKey() || event + .getMetaKey()) + && isMultiSelectModeDefault()) { + boolean wasSelected = isSelected(); + toggleSelection(); + setRowFocus(this); + /* + * next possible range select must start on + * this row + */ + selectionRangeStart = this; + if (wasSelected) { + removeRowFromUnsentSelectionRanges(this); + } + + } else if ((event.getCtrlKey() || event + .getMetaKey()) && isSingleSelectMode()) { + // Ctrl (or meta) click (Single selection) + if (!isSelected() + || (isSelected() && nullSelectionAllowed)) { + + if (!isSelected()) { + deselectAll(); + } + + toggleSelection(); + setRowFocus(this); + } + + } else if (event.getShiftKey() + && isMultiSelectModeDefault()) { + // Shift click + toggleShiftSelection(true); + + } else { + // click + boolean currentlyJustThisRowSelected = selectedRowKeys + .size() == 1 + && selectedRowKeys + .contains(getKey()); + + if (!currentlyJustThisRowSelected) { + if (isSingleSelectMode() + || isMultiSelectModeDefault()) { + /* + * For default multi select mode + * (ctrl/shift) and for single + * select mode we need to clear the + * previous selection before + * selecting a new one when the user + * clicks on a row. Only in + * multiselect/simple mode the old + * selection should remain after a + * normal click. + */ + deselectAll(); + } + toggleSelection(); + } else if ((isSingleSelectMode() || isMultiSelectModeSimple()) + && nullSelectionAllowed) { + toggleSelection(); + }/* + * else NOP to avoid excessive server + * visits (selection is removed with + * CTRL/META click) + */ + + selectionRangeStart = this; + setRowFocus(this); + } + + // Remove IE text selection hack + if (BrowserInfo.get().isIE()) { + ((Element) event.getEventTarget().cast()) + .setPropertyJSO("onselectstart", + null); + } + // Queue value change + sendSelectedRows(false); + } + /* + * Send queued click and value change events if any + * If a click event is sent, send value change with + * it regardless of the immediate flag, see #7127 + */ + if (immediate || clickEventSent) { + client.sendPendingVariableChanges(); + } + } + break; + case Event.ONTOUCHEND: + case Event.ONTOUCHCANCEL: + if (touchStart != null) { + /* + * Touch has not been handled as neither context or + * drag start, handle it as a click. + */ + Util.simulateClickFromTouchEvent(touchStart, this); + touchStart = null; + } + if (contextTouchTimeout != null) { + contextTouchTimeout.cancel(); + } + break; + case Event.ONTOUCHMOVE: + if (isSignificantMove(event)) { + /* + * TODO figure out scroll delegate don't eat events + * if row is selected. Null check for active + * delegate is as a workaround. + */ + if (dragmode != 0 + && touchStart != null + && (TouchScrollDelegate + .getActiveScrollDelegate() == null)) { + startRowDrag(touchStart, type, targetTdOrTr); + } + if (contextTouchTimeout != null) { + contextTouchTimeout.cancel(); + } + /* + * Avoid clicks and drags by clearing touch start + * flag. + */ + touchStart = null; + } + + break; + case Event.ONTOUCHSTART: + touchStart = event; + Touch touch = event.getChangedTouches().get(0); + // save position to fields, touches in events are same + // isntance during the operation. + touchStartX = touch.getClientX(); + touchStartY = touch.getClientY(); + /* + * Prevent simulated mouse events. + */ + touchStart.preventDefault(); + if (dragmode != 0 || actionKeys != null) { + new Timer() { + @Override + public void run() { + TouchScrollDelegate activeScrollDelegate = TouchScrollDelegate + .getActiveScrollDelegate(); + if (activeScrollDelegate != null + && !activeScrollDelegate.isMoved()) { + /* + * scrolling hasn't started. Cancel + * scrolling and let row handle this as + * drag start or context menu. + */ + activeScrollDelegate.stopScrolling(); + } else { + /* + * Scrolled or scrolling, clear touch + * start to indicate that row shouldn't + * handle touch move/end events. + */ + touchStart = null; + } + } + }.schedule(TOUCHSCROLL_TIMEOUT); + + if (contextTouchTimeout == null + && actionKeys != null) { + contextTouchTimeout = new Timer() { + @Override + public void run() { + if (touchStart != null) { + showContextMenu(touchStart); + touchStart = null; + } + } + }; + } + contextTouchTimeout.cancel(); + contextTouchTimeout + .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); + } + break; + case Event.ONMOUSEDOWN: + if (targetCellOrRowFound) { + setRowFocus(this); + ensureFocus(); + if (dragmode != 0 + && (event.getButton() == NativeEvent.BUTTON_LEFT)) { + startRowDrag(event, type, targetTdOrTr); + + } else if (event.getCtrlKey() + || event.getShiftKey() + || event.getMetaKey() + && isMultiSelectModeDefault()) { + + // Prevent default text selection in Firefox + event.preventDefault(); + + // Prevent default text selection in IE + if (BrowserInfo.get().isIE()) { + ((Element) event.getEventTarget().cast()) + .setPropertyJSO( + "onselectstart", + getPreventTextSelectionIEHack()); + } + + event.stopPropagation(); + } + } + break; + case Event.ONMOUSEOUT: + if (targetCellOrRowFound) { + mDown = false; + } + break; + default: + break; + } + } + super.onBrowserEvent(event); + } + + private boolean isSignificantMove(Event event) { + if (touchStart == null) { + // no touch start + return false; + } + /* + * TODO calculate based on real distance instead of separate + * axis checks + */ + Touch touch = event.getChangedTouches().get(0); + if (Math.abs(touch.getClientX() - touchStartX) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) { + return true; + } + if (Math.abs(touch.getClientY() - touchStartY) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) { + return true; + } + return false; + } + + protected void startRowDrag(Event event, final int type, + Element targetTdOrTr) { + mDown = true; + VTransferable transferable = new VTransferable(); + transferable.setDragSource(ConnectorMap.get(client) + .getConnector(VScrollTable.this)); + transferable.setData("itemId", "" + rowKey); + NodeList cells = rowElement.getCells(); + for (int i = 0; i < cells.getLength(); i++) { + if (cells.getItem(i).isOrHasChild(targetTdOrTr)) { + HeaderCell headerCell = tHead.getHeaderCell(i); + transferable.setData("propertyId", headerCell.cid); + break; + } + } + + VDragEvent ev = VDragAndDropManager.get().startDrag( + transferable, event, true); + if (dragmode == DRAGMODE_MULTIROW && isMultiSelectModeAny() + && selectedRowKeys.contains("" + rowKey)) { + ev.createDragImage( + (Element) scrollBody.tBodyElement.cast(), true); + Element dragImage = ev.getDragImage(); + int i = 0; + for (Iterator iterator = scrollBody.iterator(); iterator + .hasNext();) { + VScrollTableRow next = (VScrollTableRow) iterator + .next(); + Element child = (Element) dragImage.getChild(i++); + if (!selectedRowKeys.contains("" + next.rowKey)) { + child.getStyle().setVisibility(Visibility.HIDDEN); + } + } + } else { + ev.createDragImage(getElement(), true); + } + if (type == Event.ONMOUSEDOWN) { + event.preventDefault(); + } + event.stopPropagation(); + } + + /** + * Finds the TD that the event interacts with. Returns null if the + * target of the event should not be handled. If the event target is + * the row directly this method returns the TR element instead of + * the TD. + * + * @param event + * @return TD or TR element that the event targets (the actual event + * target is this element or a child of it) + */ + private Element getEventTargetTdOrTr(Event event) { + final Element eventTarget = event.getEventTarget().cast(); + Widget widget = Util.findWidget(eventTarget, null); + final Element thisTrElement = getElement(); + + if (widget != this) { + /* + * This is a workaround to make Labels, read only TextFields + * and Embedded in a Table clickable (see #2688). It is + * really not a fix as it does not work with a custom read + * only components (not extending VLabel/VEmbedded). + */ + while (widget != null && widget.getParent() != this) { + widget = widget.getParent(); + } + + if (!(widget instanceof VLabel) + && !(widget instanceof VEmbedded) + && !(widget instanceof VTextField && ((VTextField) widget) + .isReadOnly())) { + return null; + } + } + if (eventTarget == thisTrElement) { + // This was a click on the TR element + return thisTrElement; + } + + // Iterate upwards until we find the TR element + Element element = eventTarget; + while (element != null + && element.getParentElement().cast() != thisTrElement) { + element = element.getParentElement().cast(); + } + return element; + } + + public void showContextMenu(Event event) { + if (enabled && actionKeys != null) { + // Show context menu if there are registered action handlers + int left = Util.getTouchOrMouseClientX(event); + int top = Util.getTouchOrMouseClientY(event); + top += Window.getScrollTop(); + left += Window.getScrollLeft(); + contextMenu = new ContextMenuDetails(getKey(), left, top); + client.getContextMenu().showAt(this, left, top); + } + } + + /** + * Has the row been selected? + * + * @return Returns true if selected, else false + */ + public boolean isSelected() { + return selected; + } + + /** + * Toggle the selection of the row + */ + public void toggleSelection() { + selected = !selected; + selectionChanged = true; + if (selected) { + selectedRowKeys.add(String.valueOf(rowKey)); + addStyleName("v-selected"); + } else { + removeStyleName("v-selected"); + selectedRowKeys.remove(String.valueOf(rowKey)); + } + } + + /** + * Is called when a user clicks an item when holding SHIFT key down. + * This will select a new range from the last focused row + * + * @param deselectPrevious + * Should the previous selected range be deselected + */ + private void toggleShiftSelection(boolean deselectPrevious) { + + /* + * Ensures that we are in multiselect mode and that we have a + * previous selection which was not a deselection + */ + if (isSingleSelectMode()) { + // No previous selection found + deselectAll(); + toggleSelection(); + return; + } + + // Set the selectable range + VScrollTableRow endRow = this; + VScrollTableRow startRow = selectionRangeStart; + if (startRow == null) { + startRow = focusedRow; + // If start row is null then we have a multipage selection + // from + // above + if (startRow == null) { + startRow = (VScrollTableRow) scrollBody.iterator() + .next(); + setRowFocus(endRow); + } + } + // Deselect previous items if so desired + if (deselectPrevious) { + deselectAll(); + } + + // we'll ensure GUI state from top down even though selection + // was the opposite way + if (!startRow.isBefore(endRow)) { + VScrollTableRow tmp = startRow; + startRow = endRow; + endRow = tmp; + } + SelectionRange range = new SelectionRange(startRow, endRow); + + for (Widget w : scrollBody) { + VScrollTableRow row = (VScrollTableRow) w; + if (range.inRange(row)) { + if (!row.isSelected()) { + row.toggleSelection(); + } + selectedRowKeys.add(row.getKey()); + } + } + + // Add range + if (startRow != endRow) { + selectedRowRanges.add(range); + } + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.ui.IActionOwner#getActions () + */ + public Action[] getActions() { + if (actionKeys == null) { + return new Action[] {}; + } + final Action[] actions = new Action[actionKeys.length]; + for (int i = 0; i < actions.length; i++) { + final String actionKey = actionKeys[i]; + final TreeAction a = new TreeAction(this, + String.valueOf(rowKey), actionKey) { + @Override + public void execute() { + super.execute(); + lazyRevertFocusToRow(VScrollTableRow.this); + } + }; + a.setCaption(getActionCaption(actionKey)); + a.setIconUrl(getActionIcon(actionKey)); + actions[i] = a; + } + return actions; + } + + public ApplicationConnection getClient() { + return client; + } + + public String getPaintableId() { + return paintableId; + } + + private int getColIndexOf(Widget child) { + com.google.gwt.dom.client.Element widgetCell = child + .getElement().getParentElement().getParentElement(); + NodeList cells = rowElement.getCells(); + for (int i = 0; i < cells.getLength(); i++) { + if (cells.getItem(i) == widgetCell) { + return i; + } + } + return -1; + } + + public Widget getWidgetForPaintable() { + return this; + } + } + + protected class VScrollTableGeneratedRow extends VScrollTableRow { + + private boolean spanColumns; + private boolean htmlContentAllowed; + + public VScrollTableGeneratedRow(UIDL uidl, char[] aligns) { + super(uidl, aligns); + addStyleName("v-table-generated-row"); + } + + public boolean isSpanColumns() { + return spanColumns; + } + + @Override + protected void initCellWidths() { + if (spanColumns) { + setSpannedColumnWidthAfterDOMFullyInited(); + } else { + super.initCellWidths(); + } + } + + private void setSpannedColumnWidthAfterDOMFullyInited() { + // Defer setting width on spanned columns to make sure that + // they are added to the DOM before trying to calculate + // widths. + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + + public void execute() { + if (showRowHeaders) { + setCellWidth(0, tHead.getHeaderCell(0).getWidth()); + calcAndSetSpanWidthOnCell(1); + } else { + calcAndSetSpanWidthOnCell(0); + } + } + }); + } + + @Override + protected boolean isRenderHtmlInCells() { + return htmlContentAllowed; + } + + @Override + protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col, + int visibleColumnIndex) { + htmlContentAllowed = uidl.getBooleanAttribute("gen_html"); + spanColumns = uidl.getBooleanAttribute("gen_span"); + + final Iterator cells = uidl.getChildIterator(); + if (spanColumns) { + int colCount = uidl.getChildCount(); + if (cells.hasNext()) { + final Object cell = cells.next(); + if (cell instanceof String) { + addSpannedCell(uidl, cell.toString(), aligns[0], + "", htmlContentAllowed, false, null, + colCount); + } else { + addSpannedCell(uidl, (Widget) cell, aligns[0], "", + false, colCount); + } + } + } else { + super.addCellsFromUIDL(uidl, aligns, col, + visibleColumnIndex); + } + } + + private void addSpannedCell(UIDL rowUidl, Widget w, char align, + String style, boolean sorted, int colCount) { + TableCellElement td = DOM.createTD().cast(); + td.setColSpan(colCount); + initCellWithWidget(w, align, style, sorted, td); + } + + private void addSpannedCell(UIDL rowUidl, String text, char align, + String style, boolean textIsHTML, boolean sorted, + String description, int colCount) { + // String only content is optimized by not using Label widget + final TableCellElement td = DOM.createTD().cast(); + td.setColSpan(colCount); + initCellWithText(text, align, style, textIsHTML, sorted, + description, td); + } + + @Override + protected void setCellWidth(int cellIx, int width) { + if (isSpanColumns()) { + if (showRowHeaders) { + if (cellIx == 0) { + super.setCellWidth(0, width); + } else { + // We need to recalculate the spanning TDs width for + // every cellIx in order to support column resizing. + calcAndSetSpanWidthOnCell(1); + } + } else { + // Same as above. + calcAndSetSpanWidthOnCell(0); + } + } else { + super.setCellWidth(cellIx, width); + } + } + + private void calcAndSetSpanWidthOnCell(final int cellIx) { + int spanWidth = 0; + for (int ix = (showRowHeaders ? 1 : 0); ix < tHead + .getVisibleCellCount(); ix++) { + spanWidth += tHead.getHeaderCell(ix).getOffsetWidth(); + } + Util.setWidthExcludingPaddingAndBorder((Element) getElement() + .getChild(cellIx), spanWidth, 13, false); + } + } + + /** + * Ensure the component has a focus. + * + * TODO the current implementation simply always calls focus for the + * component. In case the Table at some point implements focus/blur + * listeners, this method needs to be evolved to conditionally call + * focus only if not currently focused. + */ + protected void ensureFocus() { + if (!hasFocus) { + scrollBodyPanel.setFocus(true); + } + + } + + } + + /** + * Deselects all items + */ + public void deselectAll() { + for (Widget w : scrollBody) { + VScrollTableRow row = (VScrollTableRow) w; + if (row.isSelected()) { + row.toggleSelection(); + } + } + // still ensure all selects are removed from (not necessary rendered) + selectedRowKeys.clear(); + selectedRowRanges.clear(); + // also notify server that it clears all previous selections (the client + // side does not know about the invisible ones) + instructServerToForgetPreviousSelections(); + } + + /** + * Used in multiselect mode when the client side knows that all selections + * are in the next request. + */ + private void instructServerToForgetPreviousSelections() { + client.updateVariable(paintableId, "clearSelections", true, false); + } + + /** + * Determines the pagelength when the table height is fixed. + */ + public void updatePageLength() { + // Only update if visible and enabled + if (!isVisible() || !enabled) { + return; + } + + if (scrollBody == null) { + return; + } + + if (isDynamicHeight()) { + return; + } + + int rowHeight = (int) Math.round(scrollBody.getRowHeight()); + int bodyH = scrollBodyPanel.getOffsetHeight(); + int rowsAtOnce = bodyH / rowHeight; + boolean anotherPartlyVisible = ((bodyH % rowHeight) != 0); + if (anotherPartlyVisible) { + rowsAtOnce++; + } + if (pageLength != rowsAtOnce) { + pageLength = rowsAtOnce; + client.updateVariable(paintableId, "pagelength", pageLength, false); + + if (!rendering) { + int currentlyVisible = scrollBody.lastRendered + - scrollBody.firstRendered; + if (currentlyVisible < pageLength + && currentlyVisible < totalRows) { + // shake scrollpanel to fill empty space + scrollBodyPanel.setScrollPosition(scrollTop + 1); + scrollBodyPanel.setScrollPosition(scrollTop - 1); + } + + sizeNeedsInit = true; + } + } + + } + + void updateWidth() { + if (!isVisible()) { + /* + * Do not update size when the table is hidden as all column widths + * will be set to zero and they won't be recalculated when the table + * is set visible again (until the size changes again) + */ + return; + } + + if (!isDynamicWidth()) { + int innerPixels = getOffsetWidth() - getBorderWidth(); + if (innerPixels < 0) { + innerPixels = 0; + } + setContentWidth(innerPixels); + + // readjust undefined width columns + triggerLazyColumnAdjustment(false); + + } else { + + sizeNeedsInit = true; + + // readjust undefined width columns + triggerLazyColumnAdjustment(false); + } + + /* + * setting width may affect wheter the component has scrollbars -> needs + * scrolling or not + */ + setProperTabIndex(); + } + + private static final int LAZY_COLUMN_ADJUST_TIMEOUT = 300; + + private final Timer lazyAdjustColumnWidths = new Timer() { + /** + * Check for column widths, and available width, to see if we can fix + * column widths "optimally". Doing this lazily to avoid expensive + * calculation when resizing is not yet finished. + */ + @Override + public void run() { + if (scrollBody == null) { + // Try again later if we get here before scrollBody has been + // initalized + triggerLazyColumnAdjustment(false); + return; + } + + Iterator headCells = tHead.iterator(); + int usedMinimumWidth = 0; + int totalExplicitColumnsWidths = 0; + float expandRatioDivider = 0; + int colIndex = 0; + while (headCells.hasNext()) { + final HeaderCell hCell = (HeaderCell) headCells.next(); + if (hCell.isDefinedWidth()) { + totalExplicitColumnsWidths += hCell.getWidth(); + usedMinimumWidth += hCell.getWidth(); + } else { + usedMinimumWidth += hCell.getNaturalColumnWidth(colIndex); + expandRatioDivider += hCell.getExpandRatio(); + } + colIndex++; + } + + int availW = scrollBody.getAvailableWidth(); + // Hey IE, are you really sure about this? + availW = scrollBody.getAvailableWidth(); + int visibleCellCount = tHead.getVisibleCellCount(); + availW -= scrollBody.getCellExtraWidth() * visibleCellCount; + if (willHaveScrollbars()) { + availW -= Util.getNativeScrollbarSize(); + } + + int extraSpace = availW - usedMinimumWidth; + if (extraSpace < 0) { + extraSpace = 0; + } + + int totalUndefinedNaturalWidths = usedMinimumWidth + - totalExplicitColumnsWidths; + + // we have some space that can be divided optimally + HeaderCell hCell; + colIndex = 0; + headCells = tHead.iterator(); + int checksum = 0; + while (headCells.hasNext()) { + hCell = (HeaderCell) headCells.next(); + if (!hCell.isDefinedWidth()) { + int w = hCell.getNaturalColumnWidth(colIndex); + int newSpace; + if (expandRatioDivider > 0) { + // divide excess space by expand ratios + newSpace = Math.round((w + extraSpace + * hCell.getExpandRatio() / expandRatioDivider)); + } else { + if (totalUndefinedNaturalWidths != 0) { + // divide relatively to natural column widths + newSpace = Math.round(w + (float) extraSpace + * (float) w / totalUndefinedNaturalWidths); + } else { + newSpace = w; + } + } + checksum += newSpace; + setColWidth(colIndex, newSpace, false); + } else { + checksum += hCell.getWidth(); + } + colIndex++; + } + + if (extraSpace > 0 && checksum != availW) { + /* + * There might be in some cases a rounding error of 1px when + * extra space is divided so if there is one then we give the + * first undefined column 1 more pixel + */ + headCells = tHead.iterator(); + colIndex = 0; + while (headCells.hasNext()) { + HeaderCell hc = (HeaderCell) headCells.next(); + if (!hc.isDefinedWidth()) { + setColWidth(colIndex, + hc.getWidth() + availW - checksum, false); + break; + } + colIndex++; + } + } + + if (isDynamicHeight() && totalRows == pageLength) { + // fix body height (may vary if lazy loading is offhorizontal + // scrollbar appears/disappears) + int bodyHeight = scrollBody.getRequiredHeight(); + boolean needsSpaceForHorizontalScrollbar = (availW < usedMinimumWidth); + if (needsSpaceForHorizontalScrollbar) { + bodyHeight += Util.getNativeScrollbarSize(); + } + int heightBefore = getOffsetHeight(); + scrollBodyPanel.setHeight(bodyHeight + "px"); + if (heightBefore != getOffsetHeight()) { + Util.notifyParentOfSizeChange(VScrollTable.this, false); + } + } + scrollBody.reLayoutComponents(); + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); + } + }); + + forceRealignColumnHeaders(); + } + + }; + + private void forceRealignColumnHeaders() { + if (BrowserInfo.get().isIE()) { + /* + * IE does not fire onscroll event if scroll position is reverted to + * 0 due to the content element size growth. Ensure headers are in + * sync with content manually. Safe to use null event as we don't + * actually use the event object in listener. + */ + onScroll(null); + } + } + + /** + * helper to set pixel size of head and body part + * + * @param pixels + */ + private void setContentWidth(int pixels) { + tHead.setWidth(pixels + "px"); + scrollBodyPanel.setWidth(pixels + "px"); + tFoot.setWidth(pixels + "px"); + } + + private int borderWidth = -1; + + /** + * @return border left + border right + */ + private int getBorderWidth() { + if (borderWidth < 0) { + borderWidth = Util.measureHorizontalPaddingAndBorder( + scrollBodyPanel.getElement(), 2); + if (borderWidth < 0) { + borderWidth = 0; + } + } + return borderWidth; + } + + /** + * Ensures scrollable area is properly sized. This method is used when fixed + * size is used. + */ + private int containerHeight; + + private void setContainerHeight() { + if (!isDynamicHeight()) { + containerHeight = getOffsetHeight(); + containerHeight -= showColHeaders ? tHead.getOffsetHeight() : 0; + containerHeight -= tFoot.getOffsetHeight(); + containerHeight -= getContentAreaBorderHeight(); + if (containerHeight < 0) { + containerHeight = 0; + } + scrollBodyPanel.setHeight(containerHeight + "px"); + } + } + + private int contentAreaBorderHeight = -1; + private int scrollLeft; + private int scrollTop; + VScrollTableDropHandler dropHandler; + private boolean navKeyDown; + boolean multiselectPending; + + /** + * @return border top + border bottom of the scrollable area of table + */ + private int getContentAreaBorderHeight() { + if (contentAreaBorderHeight < 0) { + + DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow", + "hidden"); + int oh = scrollBodyPanel.getOffsetHeight(); + int ch = scrollBodyPanel.getElement() + .getPropertyInt("clientHeight"); + contentAreaBorderHeight = oh - ch; + DOM.setStyleAttribute(scrollBodyPanel.getElement(), "overflow", + "auto"); + } + return contentAreaBorderHeight; + } + + @Override + public void setHeight(String height) { + if (height.length() == 0 + && getElement().getStyle().getHeight().length() != 0) { + /* + * Changing from defined to undefined size -> should do a size init + * to take page length into account again + */ + sizeNeedsInit = true; + } + super.setHeight(height); + } + + void updateHeight() { + setContainerHeight(); + + updatePageLength(); + + if (!rendering) { + // Webkit may sometimes get an odd rendering bug (white space + // between header and body), see bug #3875. Running + // overflow hack here to shake body element a bit. + Util.runWebkitOverflowAutoFix(scrollBodyPanel.getElement()); + } + + /* + * setting height may affect wheter the component has scrollbars -> + * needs scrolling or not + */ + setProperTabIndex(); + + } + + /* + * Overridden due Table might not survive of visibility change (scroll pos + * lost). Example ITabPanel just set contained components invisible and back + * when changing tabs. + */ + @Override + public void setVisible(boolean visible) { + if (isVisible() != visible) { + super.setVisible(visible); + if (initializedAndAttached) { + if (visible) { + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + scrollBodyPanel + .setScrollPosition(measureRowHeightOffset(firstRowInViewPort)); + } + }); + } + } + } + } + + /** + * Helper function to build html snippet for column or row headers + * + * @param uidl + * possibly with values caption and icon + * @return html snippet containing possibly an icon + caption text + */ + protected String buildCaptionHtmlSnippet(UIDL uidl) { + String s = uidl.hasAttribute("caption") ? uidl + .getStringAttribute("caption") : ""; + if (uidl.hasAttribute("icon")) { + s = "\"icon\"" + s; + } + return s; + } + + /** + * This method has logic which rows needs to be requested from server when + * user scrolls + */ + public void onScroll(ScrollEvent event) { + scrollLeft = scrollBodyPanel.getElement().getScrollLeft(); + scrollTop = scrollBodyPanel.getScrollPosition(); + /* + * #6970 - IE sometimes fires scroll events for a detached table. + * + * FIXME initializedAndAttached should probably be renamed - its name + * doesn't seem to reflect its semantics. onDetach() doesn't set it to + * false, and changing that might break something else, so we need to + * check isAttached() separately. + */ + if (!initializedAndAttached || !isAttached()) { + return; + } + if (!enabled) { + scrollBodyPanel + .setScrollPosition(measureRowHeightOffset(firstRowInViewPort)); + return; + } + + rowRequestHandler.cancel(); + + if (BrowserInfo.get().isSafari() && event != null && scrollTop == 0) { + // due to the webkitoverflowworkaround, top may sometimes report 0 + // for webkit, although it really is not. Expecting to have the + // correct + // value available soon. + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + onScroll(null); + } + }); + return; + } + + // fix headers horizontal scrolling + tHead.setHorizontalScrollPosition(scrollLeft); + + // fix footers horizontal scrolling + tFoot.setHorizontalScrollPosition(scrollLeft); + + firstRowInViewPort = calcFirstRowInViewPort(); + if (firstRowInViewPort > totalRows - pageLength) { + firstRowInViewPort = totalRows - pageLength; + } + + int postLimit = (int) (firstRowInViewPort + (pageLength - 1) + pageLength + * cache_react_rate); + if (postLimit > totalRows - 1) { + postLimit = totalRows - 1; + } + int preLimit = (int) (firstRowInViewPort - pageLength + * cache_react_rate); + if (preLimit < 0) { + preLimit = 0; + } + final int lastRendered = scrollBody.getLastRendered(); + final int firstRendered = scrollBody.getFirstRendered(); + + if (postLimit <= lastRendered && preLimit >= firstRendered) { + // we're within no-react area, no need to request more rows + // remember which firstvisible we requested, in case the server has + // a differing opinion + lastRequestedFirstvisible = firstRowInViewPort; + client.updateVariable(paintableId, "firstvisible", + firstRowInViewPort, false); + return; + } + + if (firstRowInViewPort - pageLength * cache_rate > lastRendered + || firstRowInViewPort + pageLength + pageLength * cache_rate < firstRendered) { + // need a totally new set of rows + rowRequestHandler + .setReqFirstRow((firstRowInViewPort - (int) (pageLength * cache_rate))); + int last = firstRowInViewPort + (int) (cache_rate * pageLength) + + pageLength - 1; + if (last >= totalRows) { + last = totalRows - 1; + } + rowRequestHandler.setReqRows(last + - rowRequestHandler.getReqFirstRow() + 1); + rowRequestHandler.deferRowFetch(); + return; + } + if (preLimit < firstRendered) { + // need some rows to the beginning of the rendered area + rowRequestHandler + .setReqFirstRow((int) (firstRowInViewPort - pageLength + * cache_rate)); + rowRequestHandler.setReqRows(firstRendered + - rowRequestHandler.getReqFirstRow()); + rowRequestHandler.deferRowFetch(); + + return; + } + if (postLimit > lastRendered) { + // need some rows to the end of the rendered area + rowRequestHandler.setReqFirstRow(lastRendered + 1); + rowRequestHandler.setReqRows((int) ((firstRowInViewPort + + pageLength + pageLength * cache_rate) - lastRendered)); + rowRequestHandler.deferRowFetch(); + } + } + + protected int calcFirstRowInViewPort() { + return (int) Math.ceil(scrollTop / scrollBody.getRowHeight()); + } + + public VScrollTableDropHandler getDropHandler() { + return dropHandler; + } + + private static class TableDDDetails { + int overkey = -1; + VerticalDropLocation dropLocation; + String colkey; + + @Override + public boolean equals(Object obj) { + if (obj instanceof TableDDDetails) { + TableDDDetails other = (TableDDDetails) obj; + return dropLocation == other.dropLocation + && overkey == other.overkey + && ((colkey != null && colkey.equals(other.colkey)) || (colkey == null && other.colkey == null)); + } + return false; + } + + // @Override + // public int hashCode() { + // return overkey; + // } + } + + public class VScrollTableDropHandler extends VAbstractDropHandler { + + private static final String ROWSTYLEBASE = "v-table-row-drag-"; + private TableDDDetails dropDetails; + private TableDDDetails lastEmphasized; + + @Override + public void dragEnter(VDragEvent drag) { + updateDropDetails(drag); + super.dragEnter(drag); + } + + private void updateDropDetails(VDragEvent drag) { + dropDetails = new TableDDDetails(); + Element elementOver = drag.getElementOver(); + + VScrollTableRow row = Util.findWidget(elementOver, getRowClass()); + if (row != null) { + dropDetails.overkey = row.rowKey; + Element tr = row.getElement(); + Element element = elementOver; + while (element != null && element.getParentElement() != tr) { + element = (Element) element.getParentElement(); + } + int childIndex = DOM.getChildIndex(tr, element); + dropDetails.colkey = tHead.getHeaderCell(childIndex) + .getColKey(); + dropDetails.dropLocation = DDUtil.getVerticalDropLocation( + row.getElement(), drag.getCurrentGwtEvent(), 0.2); + } + + drag.getDropDetails().put("itemIdOver", dropDetails.overkey + ""); + drag.getDropDetails().put( + "detail", + dropDetails.dropLocation != null ? dropDetails.dropLocation + .toString() : null); + + } + + private Class getRowClass() { + // get the row type this way to make dd work in derived + // implementations + return scrollBody.iterator().next().getClass(); + } + + @Override + public void dragOver(VDragEvent drag) { + TableDDDetails oldDetails = dropDetails; + updateDropDetails(drag); + if (!oldDetails.equals(dropDetails)) { + deEmphasis(); + final TableDDDetails newDetails = dropDetails; + VAcceptCallback cb = new VAcceptCallback() { + public void accepted(VDragEvent event) { + if (newDetails.equals(dropDetails)) { + dragAccepted(event); + } + /* + * Else new target slot already defined, ignore + */ + } + }; + validate(cb, drag); + } + } + + @Override + public void dragLeave(VDragEvent drag) { + deEmphasis(); + super.dragLeave(drag); + } + + @Override + public boolean drop(VDragEvent drag) { + deEmphasis(); + return super.drop(drag); + } + + private void deEmphasis() { + UIObject.setStyleName(getElement(), CLASSNAME + "-drag", false); + if (lastEmphasized == null) { + return; + } + for (Widget w : scrollBody.renderedRows) { + VScrollTableRow row = (VScrollTableRow) w; + if (lastEmphasized != null + && row.rowKey == lastEmphasized.overkey) { + String stylename = ROWSTYLEBASE + + lastEmphasized.dropLocation.toString() + .toLowerCase(); + VScrollTableRow.setStyleName(row.getElement(), stylename, + false); + lastEmphasized = null; + return; + } + } + } + + /** + * TODO needs different drop modes ?? (on cells, on rows), now only + * supports rows + */ + private void emphasis(TableDDDetails details) { + deEmphasis(); + UIObject.setStyleName(getElement(), CLASSNAME + "-drag", true); + // iterate old and new emphasized row + for (Widget w : scrollBody.renderedRows) { + VScrollTableRow row = (VScrollTableRow) w; + if (details != null && details.overkey == row.rowKey) { + String stylename = ROWSTYLEBASE + + details.dropLocation.toString().toLowerCase(); + VScrollTableRow.setStyleName(row.getElement(), stylename, + true); + lastEmphasized = details; + return; + } + } + } + + @Override + protected void dragAccepted(VDragEvent drag) { + emphasis(dropDetails); + } + + @Override + public ComponentConnector getConnector() { + return ConnectorMap.get(client).getConnector(VScrollTable.this); + } + + public ApplicationConnection getApplicationConnection() { + return client; + } + + } + + protected VScrollTableRow getFocusedRow() { + return focusedRow; + } + + /** + * Moves the selection head to a specific row + * + * @param row + * The row to where the selection head should move + * @return Returns true if focus was moved successfully, else false + */ + public boolean setRowFocus(VScrollTableRow row) { + + if (!isSelectable()) { + return false; + } + + // Remove previous selection + if (focusedRow != null && focusedRow != row) { + focusedRow.removeStyleName(CLASSNAME_SELECTION_FOCUS); + } + + if (row != null) { + + // Apply focus style to new selection + row.addStyleName(CLASSNAME_SELECTION_FOCUS); + + /* + * Trying to set focus on already focused row + */ + if (row == focusedRow) { + return false; + } + + // Set new focused row + focusedRow = row; + + ensureRowIsVisible(row); + + return true; + } + + return false; + } + + /** + * Ensures that the row is visible + * + * @param row + * The row to ensure is visible + */ + private void ensureRowIsVisible(VScrollTableRow row) { + Util.scrollIntoViewVertically(row.getElement()); + } + + /** + * Handles the keyboard events handled by the table + * + * @param event + * The keyboard event received + * @return true iff the navigation event was handled + */ + protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { + if (keycode == KeyCodes.KEY_TAB || keycode == KeyCodes.KEY_SHIFT) { + // Do not handle tab key + return false; + } + + // Down navigation + if (!isSelectable() && keycode == getNavigationDownKey()) { + scrollBodyPanel.setScrollPosition(scrollBodyPanel + .getScrollPosition() + scrollingVelocity); + return true; + } else if (keycode == getNavigationDownKey()) { + if (isMultiSelectModeAny() && moveFocusDown()) { + selectFocusedRow(ctrl, shift); + + } else if (isSingleSelectMode() && !shift && moveFocusDown()) { + selectFocusedRow(ctrl, shift); + } + return true; + } + + // Up navigation + if (!isSelectable() && keycode == getNavigationUpKey()) { + scrollBodyPanel.setScrollPosition(scrollBodyPanel + .getScrollPosition() - scrollingVelocity); + return true; + } else if (keycode == getNavigationUpKey()) { + if (isMultiSelectModeAny() && moveFocusUp()) { + selectFocusedRow(ctrl, shift); + } else if (isSingleSelectMode() && !shift && moveFocusUp()) { + selectFocusedRow(ctrl, shift); + } + return true; + } + + if (keycode == getNavigationLeftKey()) { + // Left navigation + scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel + .getHorizontalScrollPosition() - scrollingVelocity); + return true; + + } else if (keycode == getNavigationRightKey()) { + // Right navigation + scrollBodyPanel.setHorizontalScrollPosition(scrollBodyPanel + .getHorizontalScrollPosition() + scrollingVelocity); + return true; + } + + // Select navigation + if (isSelectable() && keycode == getNavigationSelectKey()) { + if (isSingleSelectMode()) { + boolean wasSelected = focusedRow.isSelected(); + deselectAll(); + if (!wasSelected || !nullSelectionAllowed) { + focusedRow.toggleSelection(); + } + } else { + focusedRow.toggleSelection(); + removeRowFromUnsentSelectionRanges(focusedRow); + } + + sendSelectedRows(); + return true; + } + + // Page Down navigation + if (keycode == getNavigationPageDownKey()) { + if (isSelectable()) { + /* + * If selectable we plagiate MSW behaviour: first scroll to the + * end of current view. If at the end, scroll down one page + * length and keep the selected row in the bottom part of + * visible area. + */ + if (!isFocusAtTheEndOfTable()) { + VScrollTableRow lastVisibleRowInViewPort = scrollBody + .getRowByRowIndex(firstRowInViewPort + + getFullyVisibleRowCount() - 1); + if (lastVisibleRowInViewPort != null + && lastVisibleRowInViewPort != focusedRow) { + // focused row is not at the end of the table, move + // focus and select the last visible row + setRowFocus(lastVisibleRowInViewPort); + selectFocusedRow(ctrl, shift); + sendSelectedRows(); + } else { + int indexOfToBeFocused = focusedRow.getIndex() + + getFullyVisibleRowCount(); + if (indexOfToBeFocused >= totalRows) { + indexOfToBeFocused = totalRows - 1; + } + VScrollTableRow toBeFocusedRow = scrollBody + .getRowByRowIndex(indexOfToBeFocused); + + if (toBeFocusedRow != null) { + /* + * if the next focused row is rendered + */ + setRowFocus(toBeFocusedRow); + selectFocusedRow(ctrl, shift); + // TODO needs scrollintoview ? + sendSelectedRows(); + } else { + // scroll down by pixels and return, to wait for + // new rows, then select the last item in the + // viewport + selectLastItemInNextRender = true; + multiselectPending = shift; + scrollByPagelenght(1); + } + } + } + } else { + /* No selections, go page down by scrolling */ + scrollByPagelenght(1); + } + return true; + } + + // Page Up navigation + if (keycode == getNavigationPageUpKey()) { + if (isSelectable()) { + /* + * If selectable we plagiate MSW behaviour: first scroll to the + * end of current view. If at the end, scroll down one page + * length and keep the selected row in the bottom part of + * visible area. + */ + if (!isFocusAtTheBeginningOfTable()) { + VScrollTableRow firstVisibleRowInViewPort = scrollBody + .getRowByRowIndex(firstRowInViewPort); + if (firstVisibleRowInViewPort != null + && firstVisibleRowInViewPort != focusedRow) { + // focus is not at the beginning of the table, move + // focus and select the first visible row + setRowFocus(firstVisibleRowInViewPort); + selectFocusedRow(ctrl, shift); + sendSelectedRows(); + } else { + int indexOfToBeFocused = focusedRow.getIndex() + - getFullyVisibleRowCount(); + if (indexOfToBeFocused < 0) { + indexOfToBeFocused = 0; + } + VScrollTableRow toBeFocusedRow = scrollBody + .getRowByRowIndex(indexOfToBeFocused); + + if (toBeFocusedRow != null) { // if the next focused row + // is rendered + setRowFocus(toBeFocusedRow); + selectFocusedRow(ctrl, shift); + // TODO needs scrollintoview ? + sendSelectedRows(); + } else { + // unless waiting for the next rowset already + // scroll down by pixels and return, to wait for + // new rows, then select the last item in the + // viewport + selectFirstItemInNextRender = true; + multiselectPending = shift; + scrollByPagelenght(-1); + } + } + } + } else { + /* No selections, go page up by scrolling */ + scrollByPagelenght(-1); + } + + return true; + } + + // Goto start navigation + if (keycode == getNavigationStartKey()) { + scrollBodyPanel.setScrollPosition(0); + if (isSelectable()) { + if (focusedRow != null && focusedRow.getIndex() == 0) { + return false; + } else { + VScrollTableRow rowByRowIndex = (VScrollTableRow) scrollBody + .iterator().next(); + if (rowByRowIndex.getIndex() == 0) { + setRowFocus(rowByRowIndex); + selectFocusedRow(ctrl, shift); + sendSelectedRows(); + } else { + // first row of table will come in next row fetch + if (ctrl) { + focusFirstItemInNextRender = true; + } else { + selectFirstItemInNextRender = true; + multiselectPending = shift; + } + } + } + } + return true; + } + + // Goto end navigation + if (keycode == getNavigationEndKey()) { + scrollBodyPanel.setScrollPosition(scrollBody.getOffsetHeight()); + if (isSelectable()) { + final int lastRendered = scrollBody.getLastRendered(); + if (lastRendered + 1 == totalRows) { + VScrollTableRow rowByRowIndex = scrollBody + .getRowByRowIndex(lastRendered); + if (focusedRow != rowByRowIndex) { + setRowFocus(rowByRowIndex); + selectFocusedRow(ctrl, shift); + sendSelectedRows(); + } + } else { + if (ctrl) { + focusLastItemInNextRender = true; + } else { + selectLastItemInNextRender = true; + multiselectPending = shift; + } + } + } + return true; + } + + return false; + } + + private boolean isFocusAtTheBeginningOfTable() { + return focusedRow.getIndex() == 0; + } + + private boolean isFocusAtTheEndOfTable() { + return focusedRow.getIndex() + 1 >= totalRows; + } + + private int getFullyVisibleRowCount() { + return (int) (scrollBodyPanel.getOffsetHeight() / scrollBody + .getRowHeight()); + } + + private void scrollByPagelenght(int i) { + int pixels = i * scrollBodyPanel.getOffsetHeight(); + int newPixels = scrollBodyPanel.getScrollPosition() + pixels; + if (newPixels < 0) { + newPixels = 0; + } // else if too high, NOP (all know browsers accept illegally big + // values here) + scrollBodyPanel.setScrollPosition(newPixels); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event + * .dom.client.FocusEvent) + */ + public void onFocus(FocusEvent event) { + if (isFocusable()) { + hasFocus = true; + + // Focus a row if no row is in focus + if (focusedRow == null) { + focusRowFromBody(); + } else { + setRowFocus(focusedRow); + } + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event + * .dom.client.BlurEvent) + */ + public void onBlur(BlurEvent event) { + hasFocus = false; + navKeyDown = false; + + if (BrowserInfo.get().isIE()) { + // IE sometimes moves focus to a clicked table cell... + Element focusedElement = Util.getIEFocusedElement(); + if (Util.getConnectorForElement(client, getParent(), focusedElement) == this) { + // ..in that case, steal the focus back to the focus handler + // but not if focus is in a child component instead (#7965) + focus(); + return; + } + } + + if (isFocusable()) { + // Unfocus any row + setRowFocus(null); + } + } + + /** + * Removes a key from a range if the key is found in a selected range + * + * @param key + * The key to remove + */ + private void removeRowFromUnsentSelectionRanges(VScrollTableRow row) { + Collection newRanges = null; + for (Iterator iterator = selectedRowRanges.iterator(); iterator + .hasNext();) { + SelectionRange range = iterator.next(); + if (range.inRange(row)) { + // Split the range if given row is in range + Collection splitranges = range.split(row); + if (newRanges == null) { + newRanges = new ArrayList(); + } + newRanges.addAll(splitranges); + iterator.remove(); + } + } + if (newRanges != null) { + selectedRowRanges.addAll(newRanges); + } + } + + /** + * Can the Table be focused? + * + * @return True if the table can be focused, else false + */ + public boolean isFocusable() { + if (scrollBody != null && enabled) { + return !(!hasHorizontalScrollbar() && !hasVerticalScrollbar() && !isSelectable()); + } + return false; + } + + private boolean hasHorizontalScrollbar() { + return scrollBody.getOffsetWidth() > scrollBodyPanel.getOffsetWidth(); + } + + private boolean hasVerticalScrollbar() { + return scrollBody.getOffsetHeight() > scrollBodyPanel.getOffsetHeight(); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.Focusable#focus() + */ + public void focus() { + if (isFocusable()) { + scrollBodyPanel.focus(); + } + } + + /** + * Sets the proper tabIndex for scrollBodyPanel (the focusable elemen in the + * component). + * + * If the component has no explicit tabIndex a zero is given (default + * tabbing order based on dom hierarchy) or -1 if the component does not + * need to gain focus. The component needs no focus if it has no scrollabars + * (not scrollable) and not selectable. Note that in the future shortcut + * actions may need focus. + * + */ + void setProperTabIndex() { + int storedScrollTop = 0; + int storedScrollLeft = 0; + + if (BrowserInfo.get().getOperaVersion() >= 11) { + // Workaround for Opera scroll bug when changing tabIndex (#6222) + storedScrollTop = scrollBodyPanel.getScrollPosition(); + storedScrollLeft = scrollBodyPanel.getHorizontalScrollPosition(); + } + + if (tabIndex == 0 && !isFocusable()) { + scrollBodyPanel.setTabIndex(-1); + } else { + scrollBodyPanel.setTabIndex(tabIndex); + } + + if (BrowserInfo.get().getOperaVersion() >= 11) { + // Workaround for Opera scroll bug when changing tabIndex (#6222) + scrollBodyPanel.setScrollPosition(storedScrollTop); + scrollBodyPanel.setHorizontalScrollPosition(storedScrollLeft); + } + } + + public void startScrollingVelocityTimer() { + if (scrollingVelocityTimer == null) { + scrollingVelocityTimer = new Timer() { + @Override + public void run() { + scrollingVelocity++; + } + }; + scrollingVelocityTimer.scheduleRepeating(100); + } + } + + public void cancelScrollingVelocityTimer() { + if (scrollingVelocityTimer != null) { + // Remove velocityTimer if it exists and the Table is disabled + scrollingVelocityTimer.cancel(); + scrollingVelocityTimer = null; + scrollingVelocity = 10; + } + } + + /** + * + * @param keyCode + * @return true if the given keyCode is used by the table for navigation + */ + private boolean isNavigationKey(int keyCode) { + return keyCode == getNavigationUpKey() + || keyCode == getNavigationLeftKey() + || keyCode == getNavigationRightKey() + || keyCode == getNavigationDownKey() + || keyCode == getNavigationPageUpKey() + || keyCode == getNavigationPageDownKey() + || keyCode == getNavigationEndKey() + || keyCode == getNavigationStartKey(); + } + + public void lazyRevertFocusToRow(final VScrollTableRow currentlyFocusedRow) { + Scheduler.get().scheduleFinally(new ScheduledCommand() { + public void execute() { + if (currentlyFocusedRow != null) { + setRowFocus(currentlyFocusedRow); + } else { + VConsole.log("no row?"); + focusRowFromBody(); + } + scrollBody.ensureFocus(); + } + }); + } + + public Action[] getActions() { + if (bodyActionKeys == null) { + return new Action[] {}; + } + final Action[] actions = new Action[bodyActionKeys.length]; + for (int i = 0; i < actions.length; i++) { + final String actionKey = bodyActionKeys[i]; + Action bodyAction = new TreeAction(this, null, actionKey); + bodyAction.setCaption(getActionCaption(actionKey)); + bodyAction.setIconUrl(getActionIcon(actionKey)); + actions[i] = bodyAction; + } + return actions; + } + + public ApplicationConnection getClient() { + return client; + } + + public String getPaintableId() { + return paintableId; + } + + /** + * Add this to the element mouse down event by using element.setPropertyJSO + * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again + * when the mouse is depressed in the mouse up event. + * + * @return Returns the JSO preventing text selection + */ + private static native JavaScriptObject getPreventTextSelectionIEHack() + /*-{ + return function(){ return false; }; + }-*/; + + public void triggerLazyColumnAdjustment(boolean now) { + lazyAdjustColumnWidths.cancel(); + if (now) { + lazyAdjustColumnWidths.run(); + } else { + lazyAdjustColumnWidths.schedule(LAZY_COLUMN_ADJUST_TIMEOUT); + } + } + + private boolean isDynamicWidth() { + ComponentConnector paintable = ConnectorMap.get(client).getConnector( + this); + return paintable.isUndefinedWidth(); + } + + private boolean isDynamicHeight() { + ComponentConnector paintable = ConnectorMap.get(client).getConnector( + this); + if (paintable == null) { + // This should be refactored. As isDynamicHeight can be called from + // a timer it is possible that the connector has been unregistered + // when this method is called, causing getConnector to return null. + return false; + } + return paintable.isUndefinedHeight(); + } + + private void debug(String msg) { + if (enableDebug) { + VConsole.error(msg); + } + } + + public Widget getWidgetForPaintable() { + return this; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/tabsheet/TabsheetBaseConnector.java b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/TabsheetBaseConnector.java new file mode 100644 index 0000000000..e16e84d112 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/TabsheetBaseConnector.java @@ -0,0 +1,99 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.tabsheet; + +import java.util.ArrayList; +import java.util.Iterator; + +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentContainerConnector; + +public abstract class TabsheetBaseConnector extends + AbstractComponentContainerConnector implements Paintable { + + public static final String ATTRIBUTE_TAB_DISABLED = "disabled"; + public static final String ATTRIBUTE_TAB_DESCRIPTION = "description"; + public static final String ATTRIBUTE_TAB_ERROR_MESSAGE = "error"; + public static final String ATTRIBUTE_TAB_CAPTION = "caption"; + public static final String ATTRIBUTE_TAB_ICON = "icon"; + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + getWidget().client = client; + + if (!isRealUpdate(uidl)) { + return; + } + + // Update member references + getWidget().id = uidl.getId(); + getWidget().disabled = !isEnabled(); + + // Render content + final UIDL tabs = uidl.getChildUIDL(0); + + // Widgets in the TabSheet before update + ArrayList oldWidgets = new ArrayList(); + for (Iterator iterator = getWidget().getWidgetIterator(); iterator + .hasNext();) { + oldWidgets.add(iterator.next()); + } + + // Clear previous values + getWidget().tabKeys.clear(); + getWidget().disabledTabKeys.clear(); + + int index = 0; + for (final Iterator it = tabs.getChildIterator(); it.hasNext();) { + final UIDL tab = (UIDL) it.next(); + final String key = tab.getStringAttribute("key"); + final boolean selected = tab.getBooleanAttribute("selected"); + final boolean hidden = tab.getBooleanAttribute("hidden"); + + if (tab.getBooleanAttribute(ATTRIBUTE_TAB_DISABLED)) { + getWidget().disabledTabKeys.add(key); + } + + getWidget().tabKeys.add(key); + + if (selected) { + getWidget().activeTabIndex = index; + } + getWidget().renderTab(tab, index, selected, hidden); + index++; + } + + int tabCount = getWidget().getTabCount(); + while (tabCount-- > index) { + getWidget().removeTab(index); + } + + for (int i = 0; i < getWidget().getTabCount(); i++) { + ComponentConnector p = getWidget().getTab(i); + // null for PlaceHolder widgets + if (p != null) { + oldWidgets.remove(p.getWidget()); + } + } + + // Detach any old tab widget, should be max 1 + for (Iterator iterator = oldWidgets.iterator(); iterator + .hasNext();) { + Widget oldWidget = iterator.next(); + if (oldWidget.isAttached()) { + oldWidget.removeFromParent(); + } + } + + } + + @Override + public VTabsheetBase getWidget() { + return (VTabsheetBase) super.getWidget(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/tabsheet/TabsheetConnector.java b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/TabsheetConnector.java new file mode 100644 index 0000000000..7423a536f2 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/TabsheetConnector.java @@ -0,0 +1,104 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.tabsheet; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout; +import com.vaadin.ui.TabSheet; + +@Component(TabSheet.class) +public class TabsheetConnector extends TabsheetBaseConnector implements + SimpleManagedLayout { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + + if (isRealUpdate(uidl)) { + // Handle stylename changes before generics (might affect size + // calculations) + getWidget().handleStyleNames(uidl, getState()); + } + + super.updateFromUIDL(uidl, client); + if (!isRealUpdate(uidl)) { + return; + } + + // tabs; push or not + if (!isUndefinedWidth()) { + DOM.setStyleAttribute(getWidget().tabs, "overflow", "hidden"); + } else { + getWidget().showAllTabs(); + DOM.setStyleAttribute(getWidget().tabs, "width", ""); + DOM.setStyleAttribute(getWidget().tabs, "overflow", "visible"); + getWidget().updateDynamicWidth(); + } + + if (!isUndefinedHeight()) { + // Must update height after the styles have been set + getWidget().updateContentNodeHeight(); + getWidget().updateOpenTabSize(); + } + + getWidget().iLayout(); + + // Re run relative size update to ensure optimal scrollbars + // TODO isolate to situation that visible tab has undefined height + try { + client.handleComponentRelativeSize(getWidget().tp + .getWidget(getWidget().tp.getVisibleWidget())); + } catch (Exception e) { + // Ignore, most likely empty tabsheet + } + + getWidget().waitingForResponse = false; + } + + @Override + protected Widget createWidget() { + return GWT.create(VTabsheet.class); + } + + @Override + public VTabsheet getWidget() { + return (VTabsheet) super.getWidget(); + } + + public void updateCaption(ComponentConnector component) { + /* Tabsheet does not render its children's captions */ + } + + public void layout() { + VTabsheet tabsheet = getWidget(); + + tabsheet.updateContentNodeHeight(); + + if (isUndefinedWidth()) { + tabsheet.contentNode.getStyle().setProperty("width", ""); + } else { + int contentWidth = tabsheet.getOffsetWidth() + - tabsheet.getContentAreaBorderWidth(); + if (contentWidth < 0) { + contentWidth = 0; + } + tabsheet.contentNode.getStyle().setProperty("width", + contentWidth + "px"); + } + + tabsheet.updateOpenTabSize(); + if (isUndefinedWidth()) { + tabsheet.updateDynamicWidth(); + } + + tabsheet.iLayout(); + + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheet.java b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheet.java new file mode 100644 index 0000000000..908a984dbb --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheet.java @@ -0,0 +1,1219 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.tabsheet; + +import java.util.Iterator; +import java.util.List; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.dom.client.TableCellElement; +import com.google.gwt.dom.client.TableElement; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.HasBlurHandlers; +import com.google.gwt.event.dom.client.HasFocusHandlers; +import com.google.gwt.event.dom.client.HasKeyDownHandlers; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.Command; +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.ComplexPanel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.user.client.ui.impl.FocusImpl; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.EventId; +import com.vaadin.terminal.gwt.client.Focusable; +import com.vaadin.terminal.gwt.client.TooltipInfo; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VCaption; +import com.vaadin.terminal.gwt.client.ui.label.VLabel; + +public class VTabsheet extends VTabsheetBase implements Focusable, + FocusHandler, BlurHandler, KeyDownHandler { + + private static class VCloseEvent { + private Tab tab; + + VCloseEvent(Tab tab) { + this.tab = tab; + } + + public Tab getTab() { + return tab; + } + + } + + private interface VCloseHandler { + public void onClose(VCloseEvent event); + } + + /** + * Representation of a single "tab" shown in the TabBar + * + */ + private static class Tab extends SimplePanel implements HasFocusHandlers, + HasBlurHandlers, HasKeyDownHandlers { + private static final String TD_CLASSNAME = CLASSNAME + "-tabitemcell"; + private static final String TD_FIRST_CLASSNAME = TD_CLASSNAME + + "-first"; + private static final String TD_SELECTED_CLASSNAME = TD_CLASSNAME + + "-selected"; + private static final String TD_SELECTED_FIRST_CLASSNAME = TD_SELECTED_CLASSNAME + + "-first"; + private static final String TD_DISABLED_CLASSNAME = TD_CLASSNAME + + "-disabled"; + + private static final String DIV_CLASSNAME = CLASSNAME + "-tabitem"; + private static final String DIV_SELECTED_CLASSNAME = DIV_CLASSNAME + + "-selected"; + + private TabCaption tabCaption; + Element td = getElement(); + private VCloseHandler closeHandler; + + private boolean enabledOnServer = true; + private Element div; + private TabBar tabBar; + private boolean hiddenOnServer = false; + + private String styleName; + + private Tab(TabBar tabBar) { + super(DOM.createTD()); + this.tabBar = tabBar; + setStyleName(td, TD_CLASSNAME); + + div = DOM.createDiv(); + focusImpl.setTabIndex(td, -1); + setStyleName(div, DIV_CLASSNAME); + + DOM.appendChild(td, div); + + tabCaption = new TabCaption(this, getTabsheet() + .getApplicationConnection()); + add(tabCaption); + + addFocusHandler(getTabsheet()); + addBlurHandler(getTabsheet()); + addKeyDownHandler(getTabsheet()); + } + + public boolean isHiddenOnServer() { + return hiddenOnServer; + } + + public void setHiddenOnServer(boolean hiddenOnServer) { + this.hiddenOnServer = hiddenOnServer; + } + + @Override + protected Element getContainerElement() { + // Attach caption element to div, not td + return div; + } + + public boolean isEnabledOnServer() { + return enabledOnServer; + } + + public void setEnabledOnServer(boolean enabled) { + enabledOnServer = enabled; + setStyleName(td, TD_DISABLED_CLASSNAME, !enabled); + if (!enabled) { + focusImpl.setTabIndex(td, -1); + } + } + + public void addClickHandler(ClickHandler handler) { + tabCaption.addClickHandler(handler); + } + + public void setCloseHandler(VCloseHandler closeHandler) { + this.closeHandler = closeHandler; + } + + /** + * Toggles the style names for the Tab + * + * @param selected + * true if the Tab is selected + * @param first + * true if the Tab is the first visible Tab + */ + public void setStyleNames(boolean selected, boolean first) { + setStyleName(td, TD_FIRST_CLASSNAME, first); + setStyleName(td, TD_SELECTED_CLASSNAME, selected); + setStyleName(td, TD_SELECTED_FIRST_CLASSNAME, selected && first); + setStyleName(div, DIV_SELECTED_CLASSNAME, selected); + } + + public void setTabulatorIndex(int tabIndex) { + focusImpl.setTabIndex(td, tabIndex); + } + + public boolean isClosable() { + return tabCaption.isClosable(); + } + + public void onClose() { + closeHandler.onClose(new VCloseEvent(this)); + } + + public VTabsheet getTabsheet() { + return tabBar.getTabsheet(); + } + + public void updateFromUIDL(UIDL tabUidl) { + tabCaption.updateCaption(tabUidl); + + // Apply the styleName set for the tab + String newStyleName = tabUidl.getStringAttribute(TAB_STYLE_NAME); + // Find the nth td element + if (newStyleName != null && newStyleName.length() != 0) { + if (!newStyleName.equals(styleName)) { + // If we have a new style name + if (styleName != null && styleName.length() != 0) { + // Remove old style name if present + td.removeClassName(TD_CLASSNAME + "-" + styleName); + } + // Set new style name + td.addClassName(TD_CLASSNAME + "-" + newStyleName); + styleName = newStyleName; + } + } else if (styleName != null) { + // Remove the set stylename if no stylename is present in the + // uidl + td.removeClassName(TD_CLASSNAME + "-" + styleName); + styleName = null; + } + } + + public void recalculateCaptionWidth() { + tabCaption.setWidth(tabCaption.getRequiredWidth() + "px"); + } + + public HandlerRegistration addFocusHandler(FocusHandler handler) { + return addDomHandler(handler, FocusEvent.getType()); + } + + public HandlerRegistration addBlurHandler(BlurHandler handler) { + return addDomHandler(handler, BlurEvent.getType()); + } + + public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) { + return addDomHandler(handler, KeyDownEvent.getType()); + } + + public void focus() { + focusImpl.focus(td); + } + + public void blur() { + focusImpl.blur(td); + } + } + + private static class TabCaption extends VCaption { + + private boolean closable = false; + private Element closeButton; + private Tab tab; + private ApplicationConnection client; + + TabCaption(Tab tab, ApplicationConnection client) { + super(client); + this.client = client; + this.tab = tab; + } + + public boolean updateCaption(UIDL uidl) { + if (uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DESCRIPTION)) { + TooltipInfo tooltipInfo = new TooltipInfo(); + tooltipInfo + .setTitle(uidl + .getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DESCRIPTION)); + tooltipInfo + .setErrorMessage(uidl + .getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_ERROR_MESSAGE)); + client.registerTooltip(getTabsheet(), getElement(), tooltipInfo); + } else { + client.registerTooltip(getTabsheet(), getElement(), null); + } + + // TODO need to call this instead of super because the caption does + // not have an owner + boolean ret = updateCaptionWithoutOwner( + uidl.getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_CAPTION), + uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DISABLED), + uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_DESCRIPTION), + uidl.hasAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_ERROR_MESSAGE), + uidl.getStringAttribute(TabsheetBaseConnector.ATTRIBUTE_TAB_ICON)); + + setClosable(uidl.hasAttribute("closable")); + + return ret; + } + + private VTabsheet getTabsheet() { + return tab.getTabsheet(); + } + + @Override + public void onBrowserEvent(Event event) { + if (closable && event.getTypeInt() == Event.ONCLICK + && event.getEventTarget().cast() == closeButton) { + tab.onClose(); + event.stopPropagation(); + event.preventDefault(); + } + + super.onBrowserEvent(event); + + if (event.getTypeInt() == Event.ONLOAD) { + getTabsheet().tabSizeMightHaveChanged(getTab()); + } + client.handleTooltipEvent(event, getTabsheet(), getElement()); + } + + public Tab getTab() { + return tab; + } + + public void setClosable(boolean closable) { + this.closable = closable; + if (closable && closeButton == null) { + closeButton = DOM.createSpan(); + closeButton.setInnerHTML("×"); + closeButton + .setClassName(VTabsheet.CLASSNAME + "-caption-close"); + getElement().insertBefore(closeButton, + getElement().getLastChild()); + } else if (!closable && closeButton != null) { + getElement().removeChild(closeButton); + closeButton = null; + } + if (closable) { + addStyleDependentName("closable"); + } else { + removeStyleDependentName("closable"); + } + } + + public boolean isClosable() { + return closable; + } + + @Override + public int getRequiredWidth() { + int width = super.getRequiredWidth(); + if (closeButton != null) { + width += Util.getRequiredWidth(closeButton); + } + return width; + } + } + + static class TabBar extends ComplexPanel implements ClickHandler, + VCloseHandler { + + private final Element tr = DOM.createTR(); + + private final Element spacerTd = DOM.createTD(); + + private Tab selected; + + private VTabsheet tabsheet; + + TabBar(VTabsheet tabsheet) { + this.tabsheet = tabsheet; + + Element el = DOM.createTable(); + Element tbody = DOM.createTBody(); + DOM.appendChild(el, tbody); + DOM.appendChild(tbody, tr); + setStyleName(spacerTd, CLASSNAME + "-spacertd"); + DOM.appendChild(tr, spacerTd); + DOM.appendChild(spacerTd, DOM.createDiv()); + setElement(el); + } + + public void onClose(VCloseEvent event) { + Tab tab = event.getTab(); + if (!tab.isEnabledOnServer()) { + return; + } + int tabIndex = getWidgetIndex(tab); + getTabsheet().sendTabClosedEvent(tabIndex); + } + + protected Element getContainerElement() { + return tr; + } + + public int getTabCount() { + return getWidgetCount(); + } + + public Tab addTab() { + Tab t = new Tab(this); + int tabIndex = getTabCount(); + + // Logical attach + insert(t, tr, tabIndex, true); + + if (tabIndex == 0) { + // Set the "first" style + t.setStyleNames(false, true); + } + + t.addClickHandler(this); + t.setCloseHandler(this); + + return t; + } + + public void onClick(ClickEvent event) { + Widget caption = (Widget) event.getSource(); + int index = getWidgetIndex(caption.getParent()); + // IE needs explicit focus() + if (BrowserInfo.get().isIE()) { + getTabsheet().focus(); + } + getTabsheet().onTabSelected(index); + } + + public VTabsheet getTabsheet() { + return tabsheet; + } + + public Tab getTab(int index) { + if (index < 0 || index >= getTabCount()) { + return null; + } + return (Tab) super.getWidget(index); + } + + public void selectTab(int index) { + final Tab newSelected = getTab(index); + final Tab oldSelected = selected; + + newSelected.setStyleNames(true, isFirstVisibleTab(index)); + newSelected.setTabulatorIndex(getTabsheet().tabulatorIndex); + + if (oldSelected != null && oldSelected != newSelected) { + oldSelected.setStyleNames(false, + isFirstVisibleTab(getWidgetIndex(oldSelected))); + oldSelected.setTabulatorIndex(-1); + } + + // Update the field holding the currently selected tab + selected = newSelected; + + // The selected tab might need more (or less) space + newSelected.recalculateCaptionWidth(); + getTab(tabsheet.activeTabIndex).recalculateCaptionWidth(); + } + + public void removeTab(int i) { + Tab tab = getTab(i); + if (tab == null) { + return; + } + + remove(tab); + + /* + * If this widget was selected we need to unmark it as the last + * selected + */ + if (tab == selected) { + selected = null; + } + + // FIXME: Shouldn't something be selected instead? + } + + private boolean isFirstVisibleTab(int index) { + return getFirstVisibleTab() == index; + } + + /** + * Returns the index of the first visible tab + * + * @return + */ + private int getFirstVisibleTab() { + return getNextVisibleTab(-1); + } + + /** + * Find the next visible tab. Returns -1 if none is found. + * + * @param i + * @return + */ + private int getNextVisibleTab(int i) { + int tabs = getTabCount(); + do { + i++; + } while (i < tabs && getTab(i).isHiddenOnServer()); + + if (i == tabs) { + return -1; + } else { + return i; + } + } + + /** + * Find the previous visible tab. Returns -1 if none is found. + * + * @param i + * @return + */ + private int getPreviousVisibleTab(int i) { + do { + i--; + } while (i >= 0 && getTab(i).isHiddenOnServer()); + + return i; + + } + + public int scrollLeft(int currentFirstVisible) { + int prevVisible = getPreviousVisibleTab(currentFirstVisible); + if (prevVisible == -1) { + return -1; + } + + Tab newFirst = getTab(prevVisible); + newFirst.setVisible(true); + newFirst.recalculateCaptionWidth(); + + return prevVisible; + } + + public int scrollRight(int currentFirstVisible) { + int nextVisible = getNextVisibleTab(currentFirstVisible); + if (nextVisible == -1) { + return -1; + } + Tab currentFirst = getTab(currentFirstVisible); + currentFirst.setVisible(false); + currentFirst.recalculateCaptionWidth(); + return nextVisible; + } + } + + public static final String CLASSNAME = "v-tabsheet"; + + public static final String TABS_CLASSNAME = "v-tabsheet-tabcontainer"; + public static final String SCROLLER_CLASSNAME = "v-tabsheet-scroller"; + + // Can't use "style" as it's already in use + public static final String TAB_STYLE_NAME = "tabstyle"; + + final Element tabs; // tabbar and 'scroller' container + Tab focusedTab; + /** + * The tabindex property (position in the browser's focus cycle.) Named like + * this to avoid confusion with activeTabIndex. + */ + int tabulatorIndex = 0; + + private static final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel(); + + private final Element scroller; // tab-scroller element + private final Element scrollerNext; // tab-scroller next button element + private final Element scrollerPrev; // tab-scroller prev button element + + /** + * The index of the first visible tab (when scrolled) + */ + private int scrollerIndex = 0; + + final TabBar tb = new TabBar(this); + final VTabsheetPanel tp = new VTabsheetPanel(); + final Element contentNode; + + private final Element deco; + + boolean waitingForResponse; + + private String currentStyle; + + /** + * @return Whether the tab could be selected or not. + */ + private boolean onTabSelected(final int tabIndex) { + Tab tab = tb.getTab(tabIndex); + if (client == null || disabled || waitingForResponse) { + return false; + } + if (!tab.isEnabledOnServer() || tab.isHiddenOnServer()) { + return false; + } + if (activeTabIndex != tabIndex) { + tb.selectTab(tabIndex); + + // If this TabSheet already has focus, set the new selected tab + // as focused. + if (focusedTab != null) { + focusedTab = tab; + } + + addStyleDependentName("loading"); + // Hide the current contents so a loading indicator can be shown + // instead + Widget currentlyDisplayedWidget = tp.getWidget(tp + .getVisibleWidget()); + currentlyDisplayedWidget.getElement().getParentElement().getStyle() + .setVisibility(Visibility.HIDDEN); + client.updateVariable(id, "selected", tabKeys.get(tabIndex) + .toString(), true); + waitingForResponse = true; + } + // Note that we return true when tabIndex == activeTabIndex; the active + // tab could be selected, it's just a no-op. + return true; + } + + public ApplicationConnection getApplicationConnection() { + return client; + } + + public void tabSizeMightHaveChanged(Tab tab) { + // icon onloads may change total width of tabsheet + if (isDynamicWidth()) { + updateDynamicWidth(); + } + updateTabScroller(); + + } + + void sendTabClosedEvent(int tabIndex) { + client.updateVariable(id, "close", tabKeys.get(tabIndex), true); + } + + boolean isDynamicWidth() { + ComponentConnector paintable = ConnectorMap.get(client).getConnector( + this); + return paintable.isUndefinedWidth(); + } + + boolean isDynamicHeight() { + ComponentConnector paintable = ConnectorMap.get(client).getConnector( + this); + return paintable.isUndefinedHeight(); + } + + public VTabsheet() { + super(CLASSNAME); + + addHandler(this, FocusEvent.getType()); + addHandler(this, BlurEvent.getType()); + + // Tab scrolling + DOM.setStyleAttribute(getElement(), "overflow", "hidden"); + tabs = DOM.createDiv(); + DOM.setElementProperty(tabs, "className", TABS_CLASSNAME); + scroller = DOM.createDiv(); + + DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME); + scrollerPrev = DOM.createButton(); + DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME + + "Prev"); + DOM.sinkEvents(scrollerPrev, Event.ONCLICK); + scrollerNext = DOM.createButton(); + DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME + + "Next"); + DOM.sinkEvents(scrollerNext, Event.ONCLICK); + DOM.appendChild(getElement(), tabs); + + // Tabs + tp.setStyleName(CLASSNAME + "-tabsheetpanel"); + contentNode = DOM.createDiv(); + + deco = DOM.createDiv(); + + addStyleDependentName("loading"); // Indicate initial progress + tb.setStyleName(CLASSNAME + "-tabs"); + DOM.setElementProperty(contentNode, "className", CLASSNAME + "-content"); + DOM.setElementProperty(deco, "className", CLASSNAME + "-deco"); + + add(tb, tabs); + DOM.appendChild(scroller, scrollerPrev); + DOM.appendChild(scroller, scrollerNext); + + DOM.appendChild(getElement(), contentNode); + add(tp, contentNode); + DOM.appendChild(getElement(), deco); + + DOM.appendChild(tabs, scroller); + + // TODO Use for Safari only. Fix annoying 1px first cell in TabBar. + // DOM.setStyleAttribute(DOM.getFirstChild(DOM.getFirstChild(DOM + // .getFirstChild(tb.getElement()))), "display", "none"); + + } + + @Override + public void onBrowserEvent(Event event) { + + // Tab scrolling + if (isScrolledTabs() && DOM.eventGetTarget(event) == scrollerPrev) { + int newFirstIndex = tb.scrollLeft(scrollerIndex); + if (newFirstIndex != -1) { + scrollerIndex = newFirstIndex; + updateTabScroller(); + } + } else if (isClippedTabs() && DOM.eventGetTarget(event) == scrollerNext) { + int newFirstIndex = tb.scrollRight(scrollerIndex); + + if (newFirstIndex != -1) { + scrollerIndex = newFirstIndex; + updateTabScroller(); + } + } else { + super.onBrowserEvent(event); + } + } + + /** + * Checks if the tab with the selected index has been scrolled out of the + * view (on the left side). + * + * @param index + * @return + */ + private boolean scrolledOutOfView(int index) { + return scrollerIndex > index; + } + + void handleStyleNames(UIDL uidl, ComponentState state) { + // Add proper stylenames for all elements (easier to prevent unwanted + // style inheritance) + if (state.hasStyles()) { + final List styles = state.getStyles(); + if (!currentStyle.equals(styles.toString())) { + currentStyle = styles.toString(); + final String tabsBaseClass = TABS_CLASSNAME; + String tabsClass = tabsBaseClass; + final String contentBaseClass = CLASSNAME + "-content"; + String contentClass = contentBaseClass; + final String decoBaseClass = CLASSNAME + "-deco"; + String decoClass = decoBaseClass; + for (String style : styles) { + tb.addStyleDependentName(style); + tabsClass += " " + tabsBaseClass + "-" + style; + contentClass += " " + contentBaseClass + "-" + style; + decoClass += " " + decoBaseClass + "-" + style; + } + DOM.setElementProperty(tabs, "className", tabsClass); + DOM.setElementProperty(contentNode, "className", contentClass); + DOM.setElementProperty(deco, "className", decoClass); + borderW = -1; + } + } else { + tb.setStyleName(CLASSNAME + "-tabs"); + DOM.setElementProperty(tabs, "className", TABS_CLASSNAME); + DOM.setElementProperty(contentNode, "className", CLASSNAME + + "-content"); + DOM.setElementProperty(deco, "className", CLASSNAME + "-deco"); + } + + if (uidl.hasAttribute("hidetabs")) { + tb.setVisible(false); + addStyleName(CLASSNAME + "-hidetabs"); + } else { + tb.setVisible(true); + removeStyleName(CLASSNAME + "-hidetabs"); + } + } + + void updateDynamicWidth() { + // Find width consumed by tabs + TableCellElement spacerCell = ((TableElement) tb.getElement().cast()) + .getRows().getItem(0).getCells().getItem(tb.getTabCount()); + + int spacerWidth = spacerCell.getOffsetWidth(); + DivElement div = (DivElement) spacerCell.getFirstChildElement(); + + int spacerMinWidth = spacerCell.getOffsetWidth() - div.getOffsetWidth(); + + int tabsWidth = tb.getOffsetWidth() - spacerWidth + spacerMinWidth; + + // Find content width + Style style = tp.getElement().getStyle(); + String overflow = style.getProperty("overflow"); + style.setProperty("overflow", "hidden"); + style.setPropertyPx("width", tabsWidth); + + boolean hasTabs = tp.getWidgetCount() > 0; + + Style wrapperstyle = null; + if (hasTabs) { + wrapperstyle = tp.getWidget(tp.getVisibleWidget()).getElement() + .getParentElement().getStyle(); + wrapperstyle.setPropertyPx("width", tabsWidth); + } + // Get content width from actual widget + + int contentWidth = 0; + if (hasTabs) { + contentWidth = tp.getWidget(tp.getVisibleWidget()).getOffsetWidth(); + } + style.setProperty("overflow", overflow); + + // Set widths to max(tabs,content) + if (tabsWidth < contentWidth) { + tabsWidth = contentWidth; + } + + int outerWidth = tabsWidth + getContentAreaBorderWidth(); + + tabs.getStyle().setPropertyPx("width", outerWidth); + style.setPropertyPx("width", tabsWidth); + if (hasTabs) { + wrapperstyle.setPropertyPx("width", tabsWidth); + } + + contentNode.getStyle().setPropertyPx("width", tabsWidth); + super.setWidth(outerWidth + "px"); + updateOpenTabSize(); + } + + @Override + protected void renderTab(final UIDL tabUidl, int index, boolean selected, + boolean hidden) { + Tab tab = tb.getTab(index); + if (tab == null) { + tab = tb.addTab(); + } + tab.updateFromUIDL(tabUidl); + tab.setEnabledOnServer((!disabledTabKeys.contains(tabKeys.get(index)))); + tab.setHiddenOnServer(hidden); + + if (scrolledOutOfView(index)) { + // Should not set tabs visible if they are scrolled out of view + hidden = true; + } + // Set the current visibility of the tab (in the browser) + tab.setVisible(!hidden); + + /* + * Force the width of the caption container so the content will not wrap + * and tabs won't be too narrow in certain browsers + */ + tab.recalculateCaptionWidth(); + + UIDL tabContentUIDL = null; + ComponentConnector tabContentPaintable = null; + Widget tabContentWidget = null; + if (tabUidl.getChildCount() > 0) { + tabContentUIDL = tabUidl.getChildUIDL(0); + tabContentPaintable = client.getPaintable(tabContentUIDL); + tabContentWidget = tabContentPaintable.getWidget(); + } + + if (tabContentPaintable != null) { + /* This is a tab with content information */ + + int oldIndex = tp.getWidgetIndex(tabContentWidget); + if (oldIndex != -1 && oldIndex != index) { + /* + * The tab has previously been rendered in another position so + * we must move the cached content to correct position + */ + tp.insert(tabContentWidget, index); + } + } else { + /* A tab whose content has not yet been loaded */ + + /* + * Make sure there is a corresponding empty tab in tp. The same + * operation as the moving above but for not-loaded tabs. + */ + if (index < tp.getWidgetCount()) { + Widget oldWidget = tp.getWidget(index); + if (!(oldWidget instanceof PlaceHolder)) { + tp.insert(new PlaceHolder(), index); + } + } + + } + + if (selected) { + renderContent(tabContentUIDL); + tb.selectTab(index); + } else { + if (tabContentUIDL != null) { + // updating a drawn child on hidden tab + if (tp.getWidgetIndex(tabContentWidget) < 0) { + tp.insert(tabContentWidget, index); + } + } else if (tp.getWidgetCount() <= index) { + tp.add(new PlaceHolder()); + } + } + } + + public class PlaceHolder extends VLabel { + public PlaceHolder() { + super(""); + } + } + + @Override + protected void selectTab(int index, final UIDL contentUidl) { + if (index != activeTabIndex) { + activeTabIndex = index; + tb.selectTab(activeTabIndex); + } + renderContent(contentUidl); + } + + private void renderContent(final UIDL contentUIDL) { + final ComponentConnector content = client.getPaintable(contentUIDL); + Widget newWidget = content.getWidget(); + if (tp.getWidgetCount() > activeTabIndex) { + Widget old = tp.getWidget(activeTabIndex); + if (old != newWidget) { + tp.remove(activeTabIndex); + ConnectorMap paintableMap = ConnectorMap.get(client); + if (paintableMap.isConnector(old)) { + paintableMap.unregisterConnector(paintableMap + .getConnector(old)); + } + tp.insert(content.getWidget(), activeTabIndex); + } + } else { + tp.add(content.getWidget()); + } + + tp.showWidget(activeTabIndex); + + VTabsheet.this.iLayout(); + /* + * The size of a cached, relative sized component must be updated to + * report correct size to updateOpenTabSize(). + */ + if (contentUIDL.getBooleanAttribute("cached")) { + client.handleComponentRelativeSize(content.getWidget()); + } + updateOpenTabSize(); + VTabsheet.this.removeStyleDependentName("loading"); + } + + void updateContentNodeHeight() { + if (!isDynamicHeight()) { + int contentHeight = getOffsetHeight(); + contentHeight -= DOM.getElementPropertyInt(deco, "offsetHeight"); + contentHeight -= tb.getOffsetHeight(); + if (contentHeight < 0) { + contentHeight = 0; + } + + // Set proper values for content element + DOM.setStyleAttribute(contentNode, "height", contentHeight + "px"); + } else { + DOM.setStyleAttribute(contentNode, "height", ""); + } + } + + public void iLayout() { + updateTabScroller(); + tp.runWebkitOverflowAutoFix(); + } + + /** + * Sets the size of the visible tab (component). As the tab is set to + * position: absolute (to work around a firefox flickering bug) we must keep + * this up-to-date by hand. + */ + void updateOpenTabSize() { + /* + * The overflow=auto element must have a height specified, otherwise it + * will be just as high as the contents and no scrollbars will appear + */ + int height = -1; + int width = -1; + int minWidth = 0; + + if (!isDynamicHeight()) { + height = contentNode.getOffsetHeight(); + } + if (!isDynamicWidth()) { + width = contentNode.getOffsetWidth() - getContentAreaBorderWidth(); + } else { + /* + * If the tabbar is wider than the content we need to use the tabbar + * width as minimum width so scrollbars get placed correctly (at the + * right edge). + */ + minWidth = tb.getOffsetWidth() - getContentAreaBorderWidth(); + } + tp.fixVisibleTabSize(width, height, minWidth); + + } + + /** + * Layouts the tab-scroller elements, and applies styles. + */ + private void updateTabScroller() { + if (!isDynamicWidth()) { + ComponentConnector paintable = ConnectorMap.get(client) + .getConnector(this); + DOM.setStyleAttribute(tabs, "width", paintable.getState() + .getWidth()); + } + + // Make sure scrollerIndex is valid + if (scrollerIndex < 0 || scrollerIndex > tb.getTabCount()) { + scrollerIndex = tb.getFirstVisibleTab(); + } else if (tb.getTabCount() > 0 + && tb.getTab(scrollerIndex).isHiddenOnServer()) { + scrollerIndex = tb.getNextVisibleTab(scrollerIndex); + } + + boolean scrolled = isScrolledTabs(); + boolean clipped = isClippedTabs(); + if (tb.getTabCount() > 0 && tb.isVisible() && (scrolled || clipped)) { + DOM.setStyleAttribute(scroller, "display", ""); + DOM.setElementProperty(scrollerPrev, "className", + SCROLLER_CLASSNAME + (scrolled ? "Prev" : "Prev-disabled")); + DOM.setElementProperty(scrollerNext, "className", + SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled")); + } else { + DOM.setStyleAttribute(scroller, "display", "none"); + } + + if (BrowserInfo.get().isSafari()) { + // fix tab height for safari, bugs sometimes if tabs contain icons + String property = tabs.getStyle().getProperty("height"); + if (property == null || property.equals("")) { + tabs.getStyle().setPropertyPx("height", tb.getOffsetHeight()); + } + /* + * another hack for webkits. tabscroller sometimes drops without + * "shaking it" reproducable in + * com.vaadin.tests.components.tabsheet.TabSheetIcons + */ + final Style style = scroller.getStyle(); + style.setProperty("whiteSpace", "normal"); + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + style.setProperty("whiteSpace", ""); + } + }); + } + + } + + void showAllTabs() { + scrollerIndex = tb.getFirstVisibleTab(); + for (int i = 0; i < tb.getTabCount(); i++) { + Tab t = tb.getTab(i); + if (!t.isHiddenOnServer()) { + t.setVisible(true); + } + } + } + + private boolean isScrolledTabs() { + return scrollerIndex > tb.getFirstVisibleTab(); + } + + private boolean isClippedTabs() { + return (tb.getOffsetWidth() - DOM.getElementPropertyInt((Element) tb + .getContainerElement().getLastChild().cast(), "offsetWidth")) > getOffsetWidth() + - (isScrolledTabs() ? scroller.getOffsetWidth() : 0); + } + + private boolean isClipped(Tab tab) { + return tab.getAbsoluteLeft() + tab.getOffsetWidth() > getAbsoluteLeft() + + getOffsetWidth() - scroller.getOffsetWidth(); + } + + @Override + protected void clearPaintables() { + + int i = tb.getTabCount(); + while (i > 0) { + tb.removeTab(--i); + } + tp.clear(); + + } + + @Override + protected Iterator getWidgetIterator() { + return tp.iterator(); + } + + private int borderW = -1; + + int getContentAreaBorderWidth() { + if (borderW < 0) { + borderW = Util.measureHorizontalBorder(contentNode); + } + return borderW; + } + + @Override + protected int getTabCount() { + return tb.getTabCount(); + } + + @Override + protected ComponentConnector getTab(int index) { + if (tp.getWidgetCount() > index) { + Widget widget = tp.getWidget(index); + return ConnectorMap.get(client).getConnector(widget); + } + return null; + } + + @Override + protected void removeTab(int index) { + tb.removeTab(index); + /* + * This must be checked because renderTab automatically removes the + * active tab content when it changes + */ + if (tp.getWidgetCount() > index) { + tp.remove(index); + } + } + + public void onBlur(BlurEvent event) { + if (focusedTab != null && event.getSource() instanceof Tab) { + focusedTab = null; + if (client.hasEventListeners(this, EventId.BLUR)) { + client.updateVariable(id, EventId.BLUR, "", true); + } + } + } + + public void onFocus(FocusEvent event) { + if (focusedTab == null && event.getSource() instanceof Tab) { + focusedTab = (Tab) event.getSource(); + if (client.hasEventListeners(this, EventId.FOCUS)) { + client.updateVariable(id, EventId.FOCUS, "", true); + } + } + } + + public void focus() { + tb.getTab(activeTabIndex).focus(); + } + + public void blur() { + tb.getTab(activeTabIndex).blur(); + } + + public void onKeyDown(KeyDownEvent event) { + if (event.getSource() instanceof Tab) { + int keycode = event.getNativeEvent().getKeyCode(); + + if (keycode == getPreviousTabKey()) { + selectPreviousTab(); + } else if (keycode == getNextTabKey()) { + selectNextTab(); + } else if (keycode == getCloseTabKey()) { + Tab tab = tb.getTab(activeTabIndex); + if (tab.isClosable()) { + tab.onClose(); + } + } + } + } + + /** + * @return The key code of the keyboard shortcut that selects the previous + * tab in a focused tabsheet. + */ + protected int getPreviousTabKey() { + return KeyCodes.KEY_LEFT; + } + + /** + * @return The key code of the keyboard shortcut that selects the next tab + * in a focused tabsheet. + */ + protected int getNextTabKey() { + return KeyCodes.KEY_RIGHT; + } + + /** + * @return The key code of the keyboard shortcut that closes the currently + * selected tab in a focused tabsheet. + */ + protected int getCloseTabKey() { + return KeyCodes.KEY_DELETE; + } + + private void selectPreviousTab() { + int newTabIndex = activeTabIndex; + // Find the previous visible and enabled tab if any. + do { + newTabIndex--; + } while (newTabIndex >= 0 && !onTabSelected(newTabIndex)); + + if (newTabIndex >= 0) { + activeTabIndex = newTabIndex; + if (isScrolledTabs()) { + // Scroll until the new active tab is visible + int newScrollerIndex = scrollerIndex; + while (tb.getTab(activeTabIndex).getAbsoluteLeft() < getAbsoluteLeft() + && newScrollerIndex != -1) { + newScrollerIndex = tb.scrollLeft(newScrollerIndex); + } + scrollerIndex = newScrollerIndex; + updateTabScroller(); + } + } + } + + private void selectNextTab() { + int newTabIndex = activeTabIndex; + // Find the next visible and enabled tab if any. + do { + newTabIndex++; + } while (newTabIndex < getTabCount() && !onTabSelected(newTabIndex)); + + if (newTabIndex < getTabCount()) { + activeTabIndex = newTabIndex; + if (isClippedTabs()) { + // Scroll until the new active tab is completely visible + int newScrollerIndex = scrollerIndex; + while (isClipped(tb.getTab(activeTabIndex)) + && newScrollerIndex != -1) { + newScrollerIndex = tb.scrollRight(newScrollerIndex); + } + scrollerIndex = newScrollerIndex; + updateTabScroller(); + } + } + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetBase.java b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetBase.java new file mode 100644 index 0000000000..ed9883dd35 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetBase.java @@ -0,0 +1,75 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.tabsheet; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.UIDL; + +public abstract class VTabsheetBase extends ComplexPanel { + + protected String id; + protected ApplicationConnection client; + + protected final ArrayList tabKeys = new ArrayList(); + protected int activeTabIndex = 0; + protected boolean disabled; + protected boolean readonly; + protected Set disabledTabKeys = new HashSet(); + + public VTabsheetBase(String classname) { + setElement(DOM.createDiv()); + setStyleName(classname); + } + + /** + * @return a list of currently shown Widgets + */ + abstract protected Iterator getWidgetIterator(); + + /** + * Clears current tabs and contents + */ + abstract protected void clearPaintables(); + + /** + * Implement in extending classes. This method should render needed elements + * and set the visibility of the tab according to the 'selected' parameter. + */ + protected abstract void renderTab(final UIDL tabUidl, int index, + boolean selected, boolean hidden); + + /** + * Implement in extending classes. This method should render any previously + * non-cached content and set the activeTabIndex property to the specified + * index. + */ + protected abstract void selectTab(int index, final UIDL contentUidl); + + /** + * Implement in extending classes. This method should return the number of + * tabs currently rendered. + */ + protected abstract int getTabCount(); + + /** + * Implement in extending classes. This method should return the Paintable + * corresponding to the given index. + */ + protected abstract ComponentConnector getTab(int index); + + /** + * Implement in extending classes. This method should remove the rendered + * tab with the specified index. + */ + protected abstract void removeTab(int index); +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetPanel.java b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetPanel.java new file mode 100644 index 0000000000..ee0571d3a7 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/tabsheet/VTabsheetPanel.java @@ -0,0 +1,218 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.tabsheet; + +import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.NodeList; +import com.google.gwt.event.dom.client.TouchStartEvent; +import com.google.gwt.event.dom.client.TouchStartHandler; +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.ComplexPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.TouchScrollDelegate; + +/** + * A panel that displays all of its child widgets in a 'deck', where only one + * can be visible at a time. It is used by + * {@link com.vaadin.terminal.gwt.client.ui.tabsheet.VTabsheet}. + * + * This class has the same basic functionality as the GWT DeckPanel + * {@link com.google.gwt.user.client.ui.DeckPanel}, with the exception that it + * doesn't manipulate the child widgets' width and height attributes. + */ +public class VTabsheetPanel extends ComplexPanel { + + private Widget visibleWidget; + private TouchScrollDelegate touchScrollDelegate; + + /** + * Creates an empty tabsheet panel. + */ + public VTabsheetPanel() { + setElement(DOM.createDiv()); + sinkEvents(Event.TOUCHEVENTS); + addDomHandler(new TouchStartHandler() { + public void onTouchStart(TouchStartEvent event) { + /* + * All container elements needs to be scrollable by one finger. + * Update the scrollable element list of touch delegate on each + * touch start. + */ + NodeList childNodes = getElement().getChildNodes(); + Element[] elements = new Element[childNodes.getLength()]; + for (int i = 0; i < elements.length; i++) { + elements[i] = (Element) childNodes.getItem(i); + } + getTouchScrollDelegate().setElements(elements); + getTouchScrollDelegate().onTouchStart(event); + } + }, TouchStartEvent.getType()); + } + + protected TouchScrollDelegate getTouchScrollDelegate() { + if (touchScrollDelegate == null) { + touchScrollDelegate = new TouchScrollDelegate(); + } + return touchScrollDelegate; + + } + + /** + * Adds the specified widget to the deck. + * + * @param w + * the widget to be added + */ + @Override + public void add(Widget w) { + Element el = createContainerElement(); + DOM.appendChild(getElement(), el); + super.add(w, el); + } + + private Element createContainerElement() { + Element el = DOM.createDiv(); + DOM.setStyleAttribute(el, "position", "absolute"); + DOM.setStyleAttribute(el, "overflow", "auto"); + hide(el); + return el; + } + + /** + * Gets the index of the currently-visible widget. + * + * @return the visible widget's index + */ + public int getVisibleWidget() { + return getWidgetIndex(visibleWidget); + } + + /** + * Inserts a widget before the specified index. + * + * @param w + * the widget to be inserted + * @param beforeIndex + * the index before which it will be inserted + * @throws IndexOutOfBoundsException + * if beforeIndex is out of range + */ + public void insert(Widget w, int beforeIndex) { + Element el = createContainerElement(); + DOM.insertChild(getElement(), el, beforeIndex); + super.insert(w, el, beforeIndex, false); + } + + @Override + public boolean remove(Widget w) { + Element child = w.getElement(); + Element parent = null; + if (child != null) { + parent = DOM.getParent(child); + } + final boolean removed = super.remove(w); + if (removed) { + if (visibleWidget == w) { + visibleWidget = null; + } + if (parent != null) { + DOM.removeChild(getElement(), parent); + } + } + return removed; + } + + /** + * Shows the widget at the specified index. This causes the currently- + * visible widget to be hidden. + * + * @param index + * the index of the widget to be shown + */ + public void showWidget(int index) { + checkIndexBoundsForAccess(index); + Widget newVisible = getWidget(index); + if (visibleWidget != newVisible) { + if (visibleWidget != null) { + hide(DOM.getParent(visibleWidget.getElement())); + } + visibleWidget = newVisible; + } + // Always ensure the selected tab is visible. If server prevents a tab + // change we might end up here with visibleWidget == newVisible but its + // parent is still hidden. + unHide(DOM.getParent(visibleWidget.getElement())); + } + + private void hide(Element e) { + DOM.setStyleAttribute(e, "visibility", "hidden"); + DOM.setStyleAttribute(e, "top", "-100000px"); + DOM.setStyleAttribute(e, "left", "-100000px"); + } + + private void unHide(Element e) { + DOM.setStyleAttribute(e, "top", "0px"); + DOM.setStyleAttribute(e, "left", "0px"); + DOM.setStyleAttribute(e, "visibility", ""); + } + + public void fixVisibleTabSize(int width, int height, int minWidth) { + if (visibleWidget == null) { + return; + } + + boolean dynamicHeight = false; + + if (height < 0) { + height = visibleWidget.getOffsetHeight(); + dynamicHeight = true; + } + if (width < 0) { + width = visibleWidget.getOffsetWidth(); + } + if (width < minWidth) { + width = minWidth; + } + + Element wrapperDiv = (Element) visibleWidget.getElement() + .getParentElement(); + + // width first + getElement().getStyle().setPropertyPx("width", width); + wrapperDiv.getStyle().setPropertyPx("width", width); + + if (dynamicHeight) { + // height of widget might have changed due wrapping + height = visibleWidget.getOffsetHeight(); + } + // v-tabsheet-tabsheetpanel height + getElement().getStyle().setPropertyPx("height", height); + + // widget wrapper height + wrapperDiv.getStyle().setPropertyPx("height", height); + runWebkitOverflowAutoFix(); + } + + public void runWebkitOverflowAutoFix() { + if (visibleWidget != null) { + Util.runWebkitOverflowAutoFix(DOM.getParent(visibleWidget + .getElement())); + } + + } + + public void replaceComponent(Widget oldComponent, Widget newComponent) { + boolean isVisible = (visibleWidget == oldComponent); + int widgetIndex = getWidgetIndex(oldComponent); + remove(oldComponent); + insert(newComponent, widgetIndex); + if (isVisible) { + showWidget(widgetIndex); + } + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/textarea/TextAreaConnector.java b/src/com/vaadin/terminal/gwt/client/ui/textarea/TextAreaConnector.java new file mode 100644 index 0000000000..f5ef93a265 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/textarea/TextAreaConnector.java @@ -0,0 +1,42 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.textarea; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.textfield.TextFieldConnector; +import com.vaadin.ui.TextArea; + +@Component(TextArea.class) +public class TextAreaConnector extends TextFieldConnector { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Call parent renderer explicitly + super.updateFromUIDL(uidl, client); + + if (uidl.hasAttribute("rows")) { + getWidget().setRows(uidl.getIntAttribute("rows")); + } + + if (getWidget().getMaxLength() >= 0) { + getWidget().sinkEvents(Event.ONKEYUP); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VTextArea.class); + } + + @Override + public VTextArea getWidget() { + return (VTextArea) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/textarea/VTextArea.java b/src/com/vaadin/terminal/gwt/client/ui/textarea/VTextArea.java new file mode 100644 index 0000000000..c600b2fd1e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/textarea/VTextArea.java @@ -0,0 +1,64 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.textarea; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.vaadin.terminal.gwt.client.ui.textfield.VTextField; + +/** + * This class represents a multiline textfield (textarea). + * + * TODO consider replacing this with a RichTextArea based implementation. IE + * does not support CSS height for textareas in Strict mode :-( + * + * @author Vaadin Ltd. + * + */ +public class VTextArea extends VTextField { + public static final String CLASSNAME = "v-textarea"; + + public VTextArea() { + super(DOM.createTextArea()); + setStyleName(CLASSNAME); + } + + public void setRows(int rows) { + setRows(getElement(), rows); + } + + private native void setRows(Element e, int r) + /*-{ + try { + if(e.tagName.toLowerCase() == "textarea") + e.rows = r; + } catch (e) {} + }-*/; + + @Override + public void onBrowserEvent(Event event) { + if (getMaxLength() >= 0 && event.getTypeInt() == Event.ONKEYUP) { + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + if (getText().length() > getMaxLength()) { + setText(getText().substring(0, getMaxLength())); + } + } + }); + } + super.onBrowserEvent(event); + } + + @Override + public int getCursorPos() { + // This is needed so that TextBoxImplIE6 is used to return the correct + // position for old Internet Explorer versions where it has to be + // detected in a different way. + return getImpl().getTextAreaCursorPos(getElement()); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/textfield/TextFieldConnector.java b/src/com/vaadin/terminal/gwt/client/ui/textfield/TextFieldConnector.java new file mode 100644 index 0000000000..806dc8c5dc --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/textfield/TextFieldConnector.java @@ -0,0 +1,123 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.textfield; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; +import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.BeforeShortcutActionListener; +import com.vaadin.ui.TextField; + +@Component(value = TextField.class, loadStyle = LoadStyle.EAGER) +public class TextFieldConnector extends AbstractFieldConnector implements + Paintable, BeforeShortcutActionListener { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Save details + getWidget().client = client; + getWidget().paintableId = uidl.getId(); + + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().setReadOnly(isReadOnly()); + + getWidget().inputPrompt = uidl + .getStringAttribute(VTextField.ATTR_INPUTPROMPT); + + getWidget().setMaxLength( + uidl.hasAttribute("maxLength") ? uidl + .getIntAttribute("maxLength") : -1); + + getWidget().immediate = getState().isImmediate(); + + getWidget().listenTextChangeEvents = hasEventListener("ie"); + if (getWidget().listenTextChangeEvents) { + getWidget().textChangeEventMode = uidl + .getStringAttribute(VTextField.ATTR_TEXTCHANGE_EVENTMODE); + if (getWidget().textChangeEventMode + .equals(VTextField.TEXTCHANGE_MODE_EAGER)) { + getWidget().textChangeEventTimeout = 1; + } else { + getWidget().textChangeEventTimeout = uidl + .getIntAttribute(VTextField.ATTR_TEXTCHANGE_TIMEOUT); + if (getWidget().textChangeEventTimeout < 1) { + // Sanitize and allow lazy/timeout with timeout set to 0 to + // work as eager + getWidget().textChangeEventTimeout = 1; + } + } + getWidget().sinkEvents(VTextField.TEXTCHANGE_EVENTS); + getWidget().attachCutEventListener(getWidget().getElement()); + } + + if (uidl.hasAttribute("cols")) { + getWidget().setColumns( + new Integer(uidl.getStringAttribute("cols")).intValue()); + } + + final String text = uidl.getStringVariable("text"); + + /* + * We skip the text content update if field has been repainted, but text + * has not been changed. Additional sanity check verifies there is no + * change in the que (in which case we count more on the server side + * value). + */ + if (!(uidl + .getBooleanAttribute(VTextField.ATTR_NO_VALUE_CHANGE_BETWEEN_PAINTS) + && getWidget().valueBeforeEdit != null && text + .equals(getWidget().valueBeforeEdit))) { + getWidget().updateFieldContent(text); + } + + if (uidl.hasAttribute("selpos")) { + final int pos = uidl.getIntAttribute("selpos"); + final int length = uidl.getIntAttribute("sellen"); + /* + * Gecko defers setting the text so we need to defer the selection. + */ + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + getWidget().setSelectionRange(pos, length); + } + }); + } + + // Here for backward compatibility; to be moved to TextArea. + // Optimization: server does not send attribute for the default 'true' + // state. + if (uidl.hasAttribute("wordwrap") + && uidl.getBooleanAttribute("wordwrap") == false) { + getWidget().setWordwrap(false); + } else { + getWidget().setWordwrap(true); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VTextField.class); + } + + @Override + public VTextField getWidget() { + return (VTextField) super.getWidget(); + } + + public void onBeforeShortcutAction(Event e) { + getWidget().valueChange(false); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/textfield/VTextField.java b/src/com/vaadin/terminal/gwt/client/ui/textfield/VTextField.java new file mode 100644 index 0000000000..a7a27a6b27 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/textfield/VTextField.java @@ -0,0 +1,442 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.textfield; + +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +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.Timer; +import com.google.gwt.user.client.ui.TextBoxBase; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.EventId; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.Field; + +/** + * This class represents a basic text input field with one row. + * + * @author Vaadin Ltd. + * + */ +public class VTextField extends TextBoxBase implements Field, ChangeHandler, + FocusHandler, BlurHandler, KeyDownHandler { + + public static final String VAR_CUR_TEXT = "curText"; + + public static final String ATTR_NO_VALUE_CHANGE_BETWEEN_PAINTS = "nvc"; + /** + * The input node CSS classname. + */ + public static final String CLASSNAME = "v-textfield"; + /** + * This CSS classname is added to the input node on hover. + */ + public static final String CLASSNAME_FOCUS = "focus"; + + protected String paintableId; + + protected ApplicationConnection client; + + protected String valueBeforeEdit = null; + + /** + * Set to false if a text change event has been sent since the last value + * change event. This means that {@link #valueBeforeEdit} should not be + * trusted when determining whether a text change even should be sent. + */ + private boolean valueBeforeEditIsSynced = true; + + protected boolean immediate = false; + private int maxLength = -1; + + private static final String CLASSNAME_PROMPT = "prompt"; + protected static final String ATTR_INPUTPROMPT = "prompt"; + public static final String ATTR_TEXTCHANGE_TIMEOUT = "iet"; + public static final String VAR_CURSOR = "c"; + public static final String ATTR_TEXTCHANGE_EVENTMODE = "iem"; + protected static final String TEXTCHANGE_MODE_EAGER = "EAGER"; + private static final String TEXTCHANGE_MODE_TIMEOUT = "TIMEOUT"; + + protected String inputPrompt = null; + private boolean prompting = false; + private int lastCursorPos = -1; + private boolean wordwrap = true; + + public VTextField() { + this(DOM.createInputText()); + } + + protected VTextField(Element node) { + super(node); + setStyleName(CLASSNAME); + addChangeHandler(this); + if (BrowserInfo.get().isIE()) { + // IE does not send change events when pressing enter in a text + // input so we handle it using a key listener instead + addKeyDownHandler(this); + } + addFocusHandler(this); + addBlurHandler(this); + sinkEvents(VTooltip.TOOLTIP_EVENTS); + } + + /* + * TODO When GWT adds ONCUT, add it there and remove workaround. See + * http://code.google.com/p/google-web-toolkit/issues/detail?id=4030 + * + * Also note that the cut/paste are not totally crossbrowsers compatible. + * E.g. in Opera mac works via context menu, but on via File->Paste/Cut. + * Opera might need the polling method for 100% working textchanceevents. + * Eager polling for a change is bit dum and heavy operation, so I guess we + * should first try to survive without. + */ + protected static final int TEXTCHANGE_EVENTS = Event.ONPASTE + | Event.KEYEVENTS | Event.ONMOUSEUP; + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (client != null) { + client.handleTooltipEvent(event, this); + } + + if (listenTextChangeEvents + && (event.getTypeInt() & TEXTCHANGE_EVENTS) == event + .getTypeInt()) { + deferTextChangeEvent(); + } + + } + + /* + * TODO optimize this so that only changes are sent + make the value change + * event just a flag that moves the current text to value + */ + private String lastTextChangeString = null; + + private String getLastCommunicatedString() { + return lastTextChangeString; + } + + private void communicateTextValueToServer() { + String text = getText(); + if (prompting) { + // Input prompt visible, text is actually "" + text = ""; + } + if (!text.equals(getLastCommunicatedString())) { + if (valueBeforeEditIsSynced && text.equals(valueBeforeEdit)) { + /* + * Value change for the current text has been enqueued since the + * last text change event was sent, but we can't know that it + * has been sent to the server. Ensure that all pending changes + * are sent now. Sending a value change without a text change + * will simulate a TextChangeEvent on the server. + */ + client.sendPendingVariableChanges(); + } else { + // Default case - just send an immediate text change message + client.updateVariable(paintableId, VAR_CUR_TEXT, text, true); + + // Shouldn't investigate valueBeforeEdit to avoid duplicate text + // change events as the states are not in sync any more + valueBeforeEditIsSynced = false; + } + lastTextChangeString = text; + } + } + + private Timer textChangeEventTrigger = new Timer() { + + @Override + public void run() { + if (isAttached()) { + updateCursorPosition(); + communicateTextValueToServer(); + scheduled = false; + } + } + }; + private boolean scheduled = false; + protected boolean listenTextChangeEvents; + protected String textChangeEventMode; + protected int textChangeEventTimeout; + + private void deferTextChangeEvent() { + if (textChangeEventMode.equals(TEXTCHANGE_MODE_TIMEOUT) && scheduled) { + return; + } else { + textChangeEventTrigger.cancel(); + } + textChangeEventTrigger.schedule(getTextChangeEventTimeout()); + scheduled = true; + } + + private int getTextChangeEventTimeout() { + return textChangeEventTimeout; + } + + @Override + public void setReadOnly(boolean readOnly) { + boolean wasReadOnly = isReadOnly(); + + if (readOnly) { + setTabIndex(-1); + } else if (wasReadOnly && !readOnly && getTabIndex() == -1) { + /* + * Need to manually set tab index to 0 since server will not send + * the tab index if it is 0. + */ + setTabIndex(0); + } + + super.setReadOnly(readOnly); + } + + protected void updateFieldContent(final String text) { + setPrompting(inputPrompt != null && focusedTextField != this + && (text.equals(""))); + + String fieldValue; + if (prompting) { + fieldValue = isReadOnly() ? "" : inputPrompt; + addStyleDependentName(CLASSNAME_PROMPT); + } else { + fieldValue = text; + removeStyleDependentName(CLASSNAME_PROMPT); + } + setText(fieldValue); + + lastTextChangeString = valueBeforeEdit = text; + valueBeforeEditIsSynced = true; + } + + protected void onCut() { + if (listenTextChangeEvents) { + deferTextChangeEvent(); + } + } + + protected native void attachCutEventListener(Element el) + /*-{ + var me = this; + el.oncut = function() { + me.@com.vaadin.terminal.gwt.client.ui.VTextField::onCut()(); + }; + }-*/; + + protected native void detachCutEventListener(Element el) + /*-{ + el.oncut = null; + }-*/; + + @Override + protected void onDetach() { + super.onDetach(); + detachCutEventListener(getElement()); + if (focusedTextField == this) { + focusedTextField = null; + } + } + + @Override + protected void onAttach() { + super.onAttach(); + if (listenTextChangeEvents) { + detachCutEventListener(getElement()); + } + } + + protected void setMaxLength(int newMaxLength) { + if (newMaxLength >= 0) { + maxLength = newMaxLength; + if (getElement().getTagName().toLowerCase().equals("textarea")) { + // NOP no maxlength property for textarea + } else { + getElement().setPropertyInt("maxLength", maxLength); + } + } else if (maxLength != -1) { + if (getElement().getTagName().toLowerCase().equals("textarea")) { + // NOP no maxlength property for textarea + } else { + getElement().removeAttribute("maxLength"); + } + maxLength = -1; + } + + } + + public int getMaxLength() { + return maxLength; + } + + public void onChange(ChangeEvent event) { + valueChange(false); + } + + /** + * Called when the field value might have changed and/or the field was + * blurred. These are combined so the blur event is sent in the same batch + * as a possible value change event (these are often connected). + * + * @param blurred + * true if the field was blurred + */ + public void valueChange(boolean blurred) { + if (client != null && paintableId != null) { + boolean sendBlurEvent = false; + boolean sendValueChange = false; + + if (blurred && client.hasEventListeners(this, EventId.BLUR)) { + sendBlurEvent = true; + client.updateVariable(paintableId, EventId.BLUR, "", false); + } + + String newText = getText(); + if (!prompting && newText != null + && !newText.equals(valueBeforeEdit)) { + sendValueChange = immediate; + client.updateVariable(paintableId, "text", getText(), false); + valueBeforeEdit = newText; + valueBeforeEditIsSynced = true; + } + + /* + * also send cursor position, no public api yet but for easier + * extension + */ + updateCursorPosition(); + + if (sendBlurEvent || sendValueChange) { + /* + * Avoid sending text change event as we will simulate it on the + * server side before value change events. + */ + textChangeEventTrigger.cancel(); + scheduled = false; + client.sendPendingVariableChanges(); + } + } + } + + /** + * Updates the cursor position variable if it has changed since the last + * update. + * + * @return true iff the value was updated + */ + protected boolean updateCursorPosition() { + if (Util.isAttachedAndDisplayed(this)) { + int cursorPos = getCursorPos(); + if (lastCursorPos != cursorPos) { + client.updateVariable(paintableId, VAR_CURSOR, cursorPos, false); + lastCursorPos = cursorPos; + return true; + } + } + return false; + } + + private static VTextField focusedTextField; + + public static void flushChangesFromFocusedTextField() { + if (focusedTextField != null) { + focusedTextField.onChange(null); + } + } + + public void onFocus(FocusEvent event) { + addStyleDependentName(CLASSNAME_FOCUS); + if (prompting) { + setText(""); + removeStyleDependentName(CLASSNAME_PROMPT); + setPrompting(false); + } + focusedTextField = this; + if (client.hasEventListeners(this, EventId.FOCUS)) { + client.updateVariable(paintableId, EventId.FOCUS, "", true); + } + } + + public void onBlur(BlurEvent event) { + removeStyleDependentName(CLASSNAME_FOCUS); + focusedTextField = null; + String text = getText(); + setPrompting(inputPrompt != null && (text == null || "".equals(text))); + if (prompting) { + setText(isReadOnly() ? "" : inputPrompt); + addStyleDependentName(CLASSNAME_PROMPT); + } + + valueChange(true); + } + + private void setPrompting(boolean prompting) { + this.prompting = prompting; + } + + public void setColumns(int columns) { + setColumns(getElement(), columns); + } + + private native void setColumns(Element e, int c) + /*-{ + try { + switch(e.tagName.toLowerCase()) { + case "input": + //e.size = c; + e.style.width = c+"em"; + break; + case "textarea": + //e.cols = c; + e.style.width = c+"em"; + break; + default:; + } + } catch (e) {} + }-*/; + + // Here for backward compatibility; to be moved to TextArea + public void setWordwrap(boolean enabled) { + if (enabled == wordwrap) { + return; // No change + } + + if (enabled) { + getElement().removeAttribute("wrap"); + getElement().getStyle().clearOverflow(); + } else { + getElement().setAttribute("wrap", "off"); + getElement().getStyle().setOverflow(Overflow.AUTO); + } + if (BrowserInfo.get().isSafari4()) { + // Force redraw as Safari 4 does not properly update the screen + Util.forceWebkitRedraw(getElement()); + } else if (BrowserInfo.get().isOpera()) { + // Opera fails to dynamically update the wrap attribute so we detach + // and reattach the whole TextArea. + Util.detachAttach(getElement()); + } + wordwrap = enabled; + } + + public void onKeyDown(KeyDownEvent event) { + if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) { + valueChange(false); + } + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/tree/TreeConnector.java b/src/com/vaadin/terminal/gwt/client/ui/tree/TreeConnector.java new file mode 100644 index 0000000000..f2f5d76d43 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/tree/TreeConnector.java @@ -0,0 +1,260 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.tree; + +import java.util.Iterator; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.AbstractFieldState; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.TooltipInfo; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.tree.VTree.TreeNode; +import com.vaadin.ui.Tree; + +@Component(Tree.class) +public class TreeConnector extends AbstractComponentConnector implements + Paintable { + + public static final String ATTRIBUTE_NODE_STYLE = "style"; + public static final String ATTRIBUTE_NODE_CAPTION = "caption"; + public static final String ATTRIBUTE_NODE_ICON = "icon"; + + public static final String ATTRIBUTE_ACTION_CAPTION = "caption"; + public static final String ATTRIBUTE_ACTION_ICON = ATTRIBUTE_NODE_ICON; + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + + getWidget().rendering = true; + + getWidget().client = client; + + if (uidl.hasAttribute("partialUpdate")) { + handleUpdate(uidl); + getWidget().rendering = false; + return; + } + + getWidget().paintableId = uidl.getId(); + + getWidget().immediate = getState().isImmediate(); + + getWidget().disabled = !isEnabled(); + getWidget().readonly = isReadOnly(); + + getWidget().dragMode = uidl.hasAttribute("dragMode") ? uidl + .getIntAttribute("dragMode") : 0; + + getWidget().isNullSelectionAllowed = uidl + .getBooleanAttribute("nullselect"); + + if (uidl.hasAttribute("alb")) { + getWidget().bodyActionKeys = uidl.getStringArrayAttribute("alb"); + } + + getWidget().body.clear(); + // clear out any references to nodes that no longer are attached + getWidget().clearNodeToKeyMap(); + TreeNode childTree = null; + UIDL childUidl = null; + for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { + childUidl = (UIDL) i.next(); + if ("actions".equals(childUidl.getTag())) { + updateActionMap(childUidl); + continue; + } else if ("-ac".equals(childUidl.getTag())) { + getWidget().updateDropHandler(childUidl); + continue; + } + childTree = getWidget().new TreeNode(); + updateNodeFromUIDL(childTree, childUidl); + getWidget().body.add(childTree); + childTree.addStyleDependentName("root"); + childTree.childNodeContainer.addStyleDependentName("root"); + } + if (childTree != null && childUidl != null) { + boolean leaf = !childUidl.getTag().equals("node"); + childTree.addStyleDependentName(leaf ? "leaf-last" : "last"); + childTree.childNodeContainer.addStyleDependentName("last"); + } + final String selectMode = uidl.getStringAttribute("selectmode"); + getWidget().selectable = !"none".equals(selectMode); + getWidget().isMultiselect = "multi".equals(selectMode); + + if (getWidget().isMultiselect) { + if (BrowserInfo.get().isTouchDevice()) { + // Always use the simple mode for touch devices that do not have + // shift/ctrl keys (#8595) + getWidget().multiSelectMode = VTree.MULTISELECT_MODE_SIMPLE; + } else { + getWidget().multiSelectMode = uidl + .getIntAttribute("multiselectmode"); + } + } + + getWidget().selectedIds = uidl.getStringArrayVariableAsSet("selected"); + + // Update lastSelection and focusedNode to point to *actual* nodes again + // after the old ones have been cleared from the body. This fixes focus + // and keyboard navigation issues as described in #7057 and other + // tickets. + if (getWidget().lastSelection != null) { + getWidget().lastSelection = getWidget().getNodeByKey( + getWidget().lastSelection.key); + } + if (getWidget().focusedNode != null) { + getWidget().setFocusedNode( + getWidget().getNodeByKey(getWidget().focusedNode.key)); + } + + if (getWidget().lastSelection == null + && getWidget().focusedNode == null + && !getWidget().selectedIds.isEmpty()) { + getWidget().setFocusedNode( + getWidget().getNodeByKey( + getWidget().selectedIds.iterator().next())); + getWidget().focusedNode.setFocused(false); + } + + getWidget().rendering = false; + + } + + @Override + protected Widget createWidget() { + return GWT.create(VTree.class); + } + + @Override + public VTree getWidget() { + return (VTree) super.getWidget(); + } + + private void handleUpdate(UIDL uidl) { + final TreeNode rootNode = getWidget().getNodeByKey( + uidl.getStringAttribute("rootKey")); + if (rootNode != null) { + if (!rootNode.getState()) { + // expanding node happened server side + rootNode.setState(true, false); + } + renderChildNodes(rootNode, (Iterator) uidl.getChildIterator()); + } + } + + /** + * Registers action for the root and also for individual nodes + * + * @param uidl + */ + private void updateActionMap(UIDL uidl) { + final Iterator it = uidl.getChildIterator(); + while (it.hasNext()) { + final UIDL action = (UIDL) it.next(); + final String key = action.getStringAttribute("key"); + final String caption = action + .getStringAttribute(ATTRIBUTE_ACTION_CAPTION); + String iconUrl = null; + if (action.hasAttribute(ATTRIBUTE_ACTION_ICON)) { + iconUrl = getConnection().translateVaadinUri( + action.getStringAttribute(ATTRIBUTE_ACTION_ICON)); + } + getWidget().registerAction(key, caption, iconUrl); + } + + } + + public void updateNodeFromUIDL(TreeNode treeNode, UIDL uidl) { + String nodeKey = uidl.getStringAttribute("key"); + treeNode.setText(uidl.getStringAttribute(ATTRIBUTE_NODE_CAPTION)); + treeNode.key = nodeKey; + + getWidget().registerNode(treeNode); + + if (uidl.hasAttribute("al")) { + treeNode.actionKeys = uidl.getStringArrayAttribute("al"); + } + + if (uidl.getTag().equals("node")) { + if (uidl.getChildCount() == 0) { + treeNode.childNodeContainer.setVisible(false); + } else { + renderChildNodes(treeNode, (Iterator) uidl.getChildIterator()); + treeNode.childrenLoaded = true; + } + } else { + treeNode.addStyleName(TreeNode.CLASSNAME + "-leaf"); + } + if (uidl.hasAttribute(ATTRIBUTE_NODE_STYLE)) { + treeNode.setNodeStyleName(uidl + .getStringAttribute(ATTRIBUTE_NODE_STYLE)); + } + + String description = uidl.getStringAttribute("descr"); + if (description != null && getConnection() != null) { + // Set tooltip + TooltipInfo info = new TooltipInfo(description); + getConnection().registerTooltip(this, nodeKey, info); + } else { + // Remove possible previous tooltip + getConnection().registerTooltip(this, nodeKey, null); + } + + if (uidl.getBooleanAttribute("expanded") && !treeNode.getState()) { + treeNode.setState(true, false); + } + + if (uidl.getBooleanAttribute("selected")) { + treeNode.setSelected(true); + // ensure that identifier is in selectedIds array (this may be a + // partial update) + getWidget().selectedIds.add(nodeKey); + } + + treeNode.setIcon(uidl.getStringAttribute(ATTRIBUTE_NODE_ICON)); + } + + void renderChildNodes(TreeNode containerNode, Iterator i) { + containerNode.childNodeContainer.clear(); + containerNode.childNodeContainer.setVisible(true); + while (i.hasNext()) { + final UIDL childUidl = i.next(); + // actions are in bit weird place, don't mix them with children, + // but current node's actions + if ("actions".equals(childUidl.getTag())) { + updateActionMap(childUidl); + continue; + } + final TreeNode childTree = getWidget().new TreeNode(); + updateNodeFromUIDL(childTree, childUidl); + containerNode.childNodeContainer.add(childTree); + if (!i.hasNext()) { + childTree + .addStyleDependentName(childTree.isLeaf() ? "leaf-last" + : "last"); + childTree.childNodeContainer.addStyleDependentName("last"); + } + } + containerNode.childrenLoaded = true; + } + + @Override + public boolean isReadOnly() { + return super.isReadOnly() || getState().isPropertyReadOnly(); + } + + @Override + public AbstractFieldState getState() { + return (AbstractFieldState) super.getState(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/tree/VTree.java b/src/com/vaadin/terminal/gwt/client/ui/tree/VTree.java new file mode 100644 index 0000000000..6f19cba957 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/tree/VTree.java @@ -0,0 +1,2102 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.tree; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Node; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.ContextMenuEvent; +import com.google.gwt.event.dom.client.ContextMenuHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.KeyPressEvent; +import com.google.gwt.event.dom.client.KeyPressHandler; +import com.google.gwt.user.client.Command; +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.Window; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.UIObject; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorMap; +import com.vaadin.terminal.gwt.client.MouseEventDetails; +import com.vaadin.terminal.gwt.client.MouseEventDetailsBuilder; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.Action; +import com.vaadin.terminal.gwt.client.ui.ActionOwner; +import com.vaadin.terminal.gwt.client.ui.FocusElementPanel; +import com.vaadin.terminal.gwt.client.ui.Icon; +import com.vaadin.terminal.gwt.client.ui.SubPartAware; +import com.vaadin.terminal.gwt.client.ui.TreeAction; +import com.vaadin.terminal.gwt.client.ui.VLazyExecutor; +import com.vaadin.terminal.gwt.client.ui.dd.DDUtil; +import com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler; +import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCallback; +import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager; +import com.vaadin.terminal.gwt.client.ui.dd.VDragEvent; +import com.vaadin.terminal.gwt.client.ui.dd.VDropHandler; +import com.vaadin.terminal.gwt.client.ui.dd.VHasDropHandler; +import com.vaadin.terminal.gwt.client.ui.dd.VTransferable; +import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation; + +/** + * + */ +public class VTree extends FocusElementPanel implements VHasDropHandler, + FocusHandler, BlurHandler, KeyPressHandler, KeyDownHandler, + SubPartAware, ActionOwner { + + public static final String CLASSNAME = "v-tree"; + + public static final String ITEM_CLICK_EVENT_ID = "itemClick"; + + /** + * Click selects the current node, ctrl/shift toggles multi selection + */ + public static final int MULTISELECT_MODE_DEFAULT = 0; + + /** + * Click/touch on node toggles its selected status + */ + public static final int MULTISELECT_MODE_SIMPLE = 1; + + private static final int CHARCODE_SPACE = 32; + + final FlowPanel body = new FlowPanel(); + + Set selectedIds = new HashSet(); + ApplicationConnection client; + String paintableId; + boolean selectable; + boolean isMultiselect; + private String currentMouseOverKey; + TreeNode lastSelection; + TreeNode focusedNode; + int multiSelectMode = MULTISELECT_MODE_DEFAULT; + + private final HashMap keyToNode = new HashMap(); + + /** + * This map contains captions and icon urls for actions like: * "33_c" -> + * "Edit" * "33_i" -> "http://dom.com/edit.png" + */ + private final HashMap actionMap = new HashMap(); + + boolean immediate; + + boolean isNullSelectionAllowed = true; + + boolean disabled = false; + + boolean readonly; + + boolean rendering; + + private VAbstractDropHandler dropHandler; + + int dragMode; + + private boolean selectionHasChanged = false; + + String[] bodyActionKeys; + + public VLazyExecutor iconLoaded = new VLazyExecutor(50, + new ScheduledCommand() { + + public void execute() { + Util.notifyParentOfSizeChange(VTree.this, true); + } + + }); + + public VTree() { + super(); + setStyleName(CLASSNAME); + add(body); + + addFocusHandler(this); + addBlurHandler(this); + + /* + * Listen to context menu events on the empty space in the tree + */ + sinkEvents(Event.ONCONTEXTMENU); + addDomHandler(new ContextMenuHandler() { + public void onContextMenu(ContextMenuEvent event) { + handleBodyContextMenu(event); + } + }, ContextMenuEvent.getType()); + + /* + * Firefox auto-repeat works correctly only if we use a key press + * handler, other browsers handle it correctly when using a key down + * handler + */ + if (BrowserInfo.get().isGecko() || BrowserInfo.get().isOpera()) { + addKeyPressHandler(this); + } else { + addKeyDownHandler(this); + } + + /* + * We need to use the sinkEvents method to catch the keyUp events so we + * can cache a single shift. KeyUpHandler cannot do this. At the same + * time we catch the mouse down and up events so we can apply the text + * selection patch in IE + */ + sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONKEYUP); + + /* + * Re-set the tab index to make sure that the FocusElementPanel's + * (super) focus element gets the tab index and not the element + * containing the tree. + */ + setTabIndex(0); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user + * .client.Event) + */ + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (event.getTypeInt() == Event.ONMOUSEDOWN) { + // Prevent default text selection in IE + if (BrowserInfo.get().isIE()) { + ((Element) event.getEventTarget().cast()).setPropertyJSO( + "onselectstart", applyDisableTextSelectionIEHack()); + } + } else if (event.getTypeInt() == Event.ONMOUSEUP) { + // Remove IE text selection hack + if (BrowserInfo.get().isIE()) { + ((Element) event.getEventTarget().cast()).setPropertyJSO( + "onselectstart", null); + } + } else if (event.getTypeInt() == Event.ONKEYUP) { + if (selectionHasChanged) { + if (event.getKeyCode() == getNavigationDownKey() + && !event.getShiftKey()) { + sendSelectionToServer(); + event.preventDefault(); + } else if (event.getKeyCode() == getNavigationUpKey() + && !event.getShiftKey()) { + sendSelectionToServer(); + event.preventDefault(); + } else if (event.getKeyCode() == KeyCodes.KEY_SHIFT) { + sendSelectionToServer(); + event.preventDefault(); + } else if (event.getKeyCode() == getNavigationSelectKey()) { + sendSelectionToServer(); + event.preventDefault(); + } + } + } + } + + public String getActionCaption(String actionKey) { + return actionMap.get(actionKey + "_c"); + } + + public String getActionIcon(String actionKey) { + return actionMap.get(actionKey + "_i"); + } + + /** + * Returns the first root node of the tree or null if there are no root + * nodes. + * + * @return The first root {@link TreeNode} + */ + protected TreeNode getFirstRootNode() { + if (body.getWidgetCount() == 0) { + return null; + } + return (TreeNode) body.getWidget(0); + } + + /** + * Returns the last root node of the tree or null if there are no root + * nodes. + * + * @return The last root {@link TreeNode} + */ + protected TreeNode getLastRootNode() { + if (body.getWidgetCount() == 0) { + return null; + } + return (TreeNode) body.getWidget(body.getWidgetCount() - 1); + } + + /** + * Returns a list of all root nodes in the Tree in the order they appear in + * the tree. + * + * @return A list of all root {@link TreeNode}s. + */ + protected List getRootNodes() { + ArrayList rootNodes = new ArrayList(); + for (int i = 0; i < body.getWidgetCount(); i++) { + rootNodes.add((TreeNode) body.getWidget(i)); + } + return rootNodes; + } + + private void updateTreeRelatedDragData(VDragEvent drag) { + + currentMouseOverKey = findCurrentMouseOverKey(drag.getElementOver()); + + drag.getDropDetails().put("itemIdOver", currentMouseOverKey); + if (currentMouseOverKey != null) { + TreeNode treeNode = getNodeByKey(currentMouseOverKey); + VerticalDropLocation detail = treeNode.getDropDetail(drag + .getCurrentGwtEvent()); + Boolean overTreeNode = null; + if (treeNode != null && !treeNode.isLeaf() + && detail == VerticalDropLocation.MIDDLE) { + overTreeNode = true; + } + drag.getDropDetails().put("itemIdOverIsNode", overTreeNode); + drag.getDropDetails().put("detail", detail); + } else { + drag.getDropDetails().put("itemIdOverIsNode", null); + drag.getDropDetails().put("detail", null); + } + + } + + private String findCurrentMouseOverKey(Element elementOver) { + TreeNode treeNode = Util.findWidget(elementOver, TreeNode.class); + return treeNode == null ? null : treeNode.key; + } + + void updateDropHandler(UIDL childUidl) { + if (dropHandler == null) { + dropHandler = new VAbstractDropHandler() { + + @Override + public void dragEnter(VDragEvent drag) { + } + + @Override + protected void dragAccepted(final VDragEvent drag) { + + } + + @Override + public void dragOver(final VDragEvent currentDrag) { + final Object oldIdOver = currentDrag.getDropDetails().get( + "itemIdOver"); + final VerticalDropLocation oldDetail = (VerticalDropLocation) currentDrag + .getDropDetails().get("detail"); + + updateTreeRelatedDragData(currentDrag); + final VerticalDropLocation detail = (VerticalDropLocation) currentDrag + .getDropDetails().get("detail"); + boolean nodeHasChanged = (currentMouseOverKey != null && currentMouseOverKey != oldIdOver) + || (currentMouseOverKey == null && oldIdOver != null); + boolean detailHasChanded = (detail != null && detail != oldDetail) + || (detail == null && oldDetail != null); + + if (nodeHasChanged || detailHasChanded) { + final String newKey = currentMouseOverKey; + TreeNode treeNode = keyToNode.get(oldIdOver); + if (treeNode != null) { + // clear old styles + treeNode.emphasis(null); + } + if (newKey != null) { + validate(new VAcceptCallback() { + public void accepted(VDragEvent event) { + VerticalDropLocation curDetail = (VerticalDropLocation) event + .getDropDetails().get("detail"); + if (curDetail == detail + && newKey.equals(currentMouseOverKey)) { + getNodeByKey(newKey).emphasis(detail); + } + /* + * Else drag is already on a different + * node-detail pair, new criteria check is + * going on + */ + } + }, currentDrag); + + } + } + + } + + @Override + public void dragLeave(VDragEvent drag) { + cleanUp(); + } + + private void cleanUp() { + if (currentMouseOverKey != null) { + getNodeByKey(currentMouseOverKey).emphasis(null); + currentMouseOverKey = null; + } + } + + @Override + public boolean drop(VDragEvent drag) { + cleanUp(); + return super.drop(drag); + } + + @Override + public ComponentConnector getConnector() { + return ConnectorMap.get(client).getConnector(VTree.this); + } + + public ApplicationConnection getApplicationConnection() { + return client; + } + + }; + } + dropHandler.updateAcceptRules(childUidl); + } + + public void setSelected(TreeNode treeNode, boolean selected) { + if (selected) { + if (!isMultiselect) { + while (selectedIds.size() > 0) { + final String id = selectedIds.iterator().next(); + final TreeNode oldSelection = getNodeByKey(id); + if (oldSelection != null) { + // can be null if the node is not visible (parent + // collapsed) + oldSelection.setSelected(false); + } + selectedIds.remove(id); + } + } + treeNode.setSelected(true); + selectedIds.add(treeNode.key); + } else { + if (!isNullSelectionAllowed) { + if (!isMultiselect || selectedIds.size() == 1) { + return; + } + } + selectedIds.remove(treeNode.key); + treeNode.setSelected(false); + } + + sendSelectionToServer(); + } + + /** + * Sends the selection to the server + */ + private void sendSelectionToServer() { + Command command = new Command() { + public void execute() { + client.updateVariable(paintableId, "selected", + selectedIds.toArray(new String[selectedIds.size()]), + immediate); + selectionHasChanged = false; + } + }; + + /* + * Delaying the sending of the selection in webkit to ensure the + * selection is always sent when the tree has focus and after click + * events have been processed. This is due to the focusing + * implementation in FocusImplSafari which uses timeouts when focusing + * and blurring. + */ + if (BrowserInfo.get().isWebkit()) { + Scheduler.get().scheduleDeferred(command); + } else { + command.execute(); + } + } + + /** + * Is a node selected in the tree + * + * @param treeNode + * The node to check + * @return + */ + public boolean isSelected(TreeNode treeNode) { + return selectedIds.contains(treeNode.key); + } + + public class TreeNode extends SimplePanel implements ActionOwner { + + public static final String CLASSNAME = "v-tree-node"; + public static final String CLASSNAME_FOCUSED = CLASSNAME + "-focused"; + + public String key; + + String[] actionKeys = null; + + boolean childrenLoaded; + + Element nodeCaptionDiv; + + protected Element nodeCaptionSpan; + + FlowPanel childNodeContainer; + + private boolean open; + + private Icon icon; + + private Event mouseDownEvent; + + private int cachedHeight = -1; + + private boolean focused = false; + + public TreeNode() { + constructDom(); + sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS + | Event.TOUCHEVENTS | Event.ONCONTEXTMENU); + } + + public VerticalDropLocation getDropDetail(NativeEvent currentGwtEvent) { + if (cachedHeight < 0) { + /* + * Height is cached to avoid flickering (drop hints may change + * the reported offsetheight -> would change the drop detail) + */ + cachedHeight = nodeCaptionDiv.getOffsetHeight(); + } + VerticalDropLocation verticalDropLocation = DDUtil + .getVerticalDropLocation(nodeCaptionDiv, cachedHeight, + currentGwtEvent, 0.15); + return verticalDropLocation; + } + + protected void emphasis(VerticalDropLocation detail) { + String base = "v-tree-node-drag-"; + UIObject.setStyleName(getElement(), base + "top", + VerticalDropLocation.TOP == detail); + UIObject.setStyleName(getElement(), base + "bottom", + VerticalDropLocation.BOTTOM == detail); + UIObject.setStyleName(getElement(), base + "center", + VerticalDropLocation.MIDDLE == detail); + base = "v-tree-node-caption-drag-"; + UIObject.setStyleName(nodeCaptionDiv, base + "top", + VerticalDropLocation.TOP == detail); + UIObject.setStyleName(nodeCaptionDiv, base + "bottom", + VerticalDropLocation.BOTTOM == detail); + UIObject.setStyleName(nodeCaptionDiv, base + "center", + VerticalDropLocation.MIDDLE == detail); + + // also add classname to "folder node" into which the drag is + // targeted + + TreeNode folder = null; + /* Possible parent of this TreeNode will be stored here */ + TreeNode parentFolder = getParentNode(); + + // TODO fix my bugs + if (isLeaf()) { + folder = parentFolder; + // note, parent folder may be null if this is root node => no + // folder target exists + } else { + if (detail == VerticalDropLocation.TOP) { + folder = parentFolder; + } else { + folder = this; + } + // ensure we remove the dragfolder classname from the previous + // folder node + setDragFolderStyleName(this, false); + setDragFolderStyleName(parentFolder, false); + } + if (folder != null) { + setDragFolderStyleName(folder, detail != null); + } + + } + + private TreeNode getParentNode() { + Widget parent2 = getParent().getParent(); + if (parent2 instanceof TreeNode) { + return (TreeNode) parent2; + } + return null; + } + + private void setDragFolderStyleName(TreeNode folder, boolean add) { + if (folder != null) { + UIObject.setStyleName(folder.getElement(), + "v-tree-node-dragfolder", add); + UIObject.setStyleName(folder.nodeCaptionDiv, + "v-tree-node-caption-dragfolder", add); + } + } + + /** + * Handles mouse selection + * + * @param ctrl + * Was the ctrl-key pressed + * @param shift + * Was the shift-key pressed + * @return Returns true if event was handled, else false + */ + private boolean handleClickSelection(final boolean ctrl, + final boolean shift) { + + // always when clicking an item, focus it + setFocusedNode(this, false); + + if (!BrowserInfo.get().isOpera()) { + /* + * Ensure that the tree's focus element also gains focus + * (TreeNodes focus is faked using FocusElementPanel in browsers + * other than Opera). + */ + focus(); + } + + ScheduledCommand command = new ScheduledCommand() { + public void execute() { + + if (multiSelectMode == MULTISELECT_MODE_SIMPLE + || !isMultiselect) { + toggleSelection(); + lastSelection = TreeNode.this; + } else if (multiSelectMode == MULTISELECT_MODE_DEFAULT) { + // Handle ctrl+click + if (isMultiselect && ctrl && !shift) { + toggleSelection(); + lastSelection = TreeNode.this; + + // Handle shift+click + } else if (isMultiselect && !ctrl && shift) { + deselectAll(); + selectNodeRange(lastSelection.key, key); + sendSelectionToServer(); + + // Handle ctrl+shift click + } else if (isMultiselect && ctrl && shift) { + selectNodeRange(lastSelection.key, key); + + // Handle click + } else { + // TODO should happen only if this alone not yet + // selected, + // now sending excess server calls + deselectAll(); + toggleSelection(); + lastSelection = TreeNode.this; + } + } + } + }; + + if (BrowserInfo.get().isWebkit() && !treeHasFocus) { + /* + * Safari may need to wait for focus. See FocusImplSafari. + */ + // VConsole.log("Deferring click handling to let webkit gain focus..."); + Scheduler.get().scheduleDeferred(command); + } else { + command.execute(); + } + + return true; + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt + * .user.client.Event) + */ + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + final int type = DOM.eventGetType(event); + final Element target = DOM.eventGetTarget(event); + + if (type == Event.ONLOAD && target == icon.getElement()) { + iconLoaded.trigger(); + } + + if (disabled) { + return; + } + + if (target == nodeCaptionSpan) { + client.handleTooltipEvent(event, VTree.this, key); + } + + final boolean inCaption = target == nodeCaptionSpan + || (icon != null && target == icon.getElement()); + if (inCaption + && client + .hasEventListeners(VTree.this, ITEM_CLICK_EVENT_ID) + + && (type == Event.ONDBLCLICK || type == Event.ONMOUSEUP)) { + fireClick(event); + } + if (type == Event.ONCLICK) { + if (getElement() == target) { + // state change + toggleState(); + } else if (!readonly && inCaption) { + if (selectable) { + // caption click = selection change && possible click + // event + if (handleClickSelection( + event.getCtrlKey() || event.getMetaKey(), + event.getShiftKey())) { + event.preventDefault(); + } + } else { + // Not selectable, only focus the node. + setFocusedNode(this); + } + } + event.stopPropagation(); + } else if (type == Event.ONCONTEXTMENU) { + showContextMenu(event); + } + + if (dragMode != 0 || dropHandler != null) { + if (type == Event.ONMOUSEDOWN || type == Event.ONTOUCHSTART) { + if (nodeCaptionDiv.isOrHasChild((Node) event + .getEventTarget().cast())) { + if (dragMode > 0 + && (type == Event.ONTOUCHSTART || event + .getButton() == NativeEvent.BUTTON_LEFT)) { + mouseDownEvent = event; // save event for possible + // dd operation + if (type == Event.ONMOUSEDOWN) { + event.preventDefault(); // prevent text + // selection + } else { + /* + * FIXME We prevent touch start event to be used + * as a scroll start event. Note that we cannot + * easily distinguish whether the user wants to + * drag or scroll. The same issue is in table + * that has scrollable area and has drag and + * drop enable. Some kind of timer might be used + * to resolve the issue. + */ + event.stopPropagation(); + } + } + } + } else if (type == Event.ONMOUSEMOVE + || type == Event.ONMOUSEOUT + || type == Event.ONTOUCHMOVE) { + + if (mouseDownEvent != null) { + // start actual drag on slight move when mouse is down + VTransferable t = new VTransferable(); + t.setDragSource(ConnectorMap.get(client).getConnector( + VTree.this)); + t.setData("itemId", key); + VDragEvent drag = VDragAndDropManager.get().startDrag( + t, mouseDownEvent, true); + + drag.createDragImage(nodeCaptionDiv, true); + event.stopPropagation(); + + mouseDownEvent = null; + } + } else if (type == Event.ONMOUSEUP) { + mouseDownEvent = null; + } + if (type == Event.ONMOUSEOVER) { + mouseDownEvent = null; + currentMouseOverKey = key; + event.stopPropagation(); + } + + } else if (type == Event.ONMOUSEDOWN + && event.getButton() == NativeEvent.BUTTON_LEFT) { + event.preventDefault(); // text selection + } + } + + private void fireClick(final Event evt) { + /* + * Ensure we have focus in tree before sending variables. Otherwise + * previously modified field may contain dirty variables. + */ + if (!treeHasFocus) { + if (BrowserInfo.get().isOpera()) { + if (focusedNode == null) { + getNodeByKey(key).setFocused(true); + } else { + focusedNode.setFocused(true); + } + } else { + focus(); + } + } + final MouseEventDetails details = MouseEventDetailsBuilder + .buildMouseEventDetails(evt); + ScheduledCommand command = new ScheduledCommand() { + public void execute() { + // Determine if we should send the event immediately to the + // server. We do not want to send the event if there is a + // selection event happening after this. In all other cases + // we want to send it immediately. + boolean sendClickEventNow = true; + + if (details.getButton() == NativeEvent.BUTTON_LEFT + && immediate && selectable) { + // Probably a selection that will cause a value change + // event to be sent + sendClickEventNow = false; + + // The exception is that user clicked on the + // currently selected row and null selection is not + // allowed == no selection event + if (isSelected() && selectedIds.size() == 1 + && !isNullSelectionAllowed) { + sendClickEventNow = true; + } + } + + client.updateVariable(paintableId, "clickedKey", key, false); + client.updateVariable(paintableId, "clickEvent", + details.toString(), sendClickEventNow); + } + }; + if (treeHasFocus) { + command.execute(); + } else { + /* + * Webkits need a deferring due to FocusImplSafari uses timeout + */ + Scheduler.get().scheduleDeferred(command); + } + } + + private void toggleSelection() { + if (selectable) { + VTree.this.setSelected(this, !isSelected()); + } + } + + private void toggleState() { + setState(!getState(), true); + } + + protected void constructDom() { + addStyleName(CLASSNAME); + + nodeCaptionDiv = DOM.createDiv(); + DOM.setElementProperty(nodeCaptionDiv, "className", CLASSNAME + + "-caption"); + Element wrapper = DOM.createDiv(); + nodeCaptionSpan = DOM.createSpan(); + DOM.sinkEvents(nodeCaptionSpan, VTooltip.TOOLTIP_EVENTS); + DOM.appendChild(getElement(), nodeCaptionDiv); + DOM.appendChild(nodeCaptionDiv, wrapper); + DOM.appendChild(wrapper, nodeCaptionSpan); + + if (BrowserInfo.get().isOpera()) { + /* + * Focus the caption div of the node to get keyboard navigation + * to work without scrolling up or down when focusing a node. + */ + nodeCaptionDiv.setTabIndex(-1); + } + + childNodeContainer = new FlowPanel(); + childNodeContainer.setStyleName(CLASSNAME + "-children"); + setWidget(childNodeContainer); + } + + public boolean isLeaf() { + String[] styleNames = getStyleName().split(" "); + for (String styleName : styleNames) { + if (styleName.equals(CLASSNAME + "-leaf")) { + return true; + } + } + return false; + } + + void setState(boolean state, boolean notifyServer) { + if (open == state) { + return; + } + if (state) { + if (!childrenLoaded && notifyServer) { + client.updateVariable(paintableId, "requestChildTree", + true, false); + } + if (notifyServer) { + client.updateVariable(paintableId, "expand", + new String[] { key }, true); + } + addStyleName(CLASSNAME + "-expanded"); + childNodeContainer.setVisible(true); + + } else { + removeStyleName(CLASSNAME + "-expanded"); + childNodeContainer.setVisible(false); + if (notifyServer) { + client.updateVariable(paintableId, "collapse", + new String[] { key }, true); + } + } + open = state; + + if (!rendering) { + Util.notifyParentOfSizeChange(VTree.this, false); + } + } + + boolean getState() { + return open; + } + + void setText(String text) { + DOM.setInnerText(nodeCaptionSpan, text); + } + + public boolean isChildrenLoaded() { + return childrenLoaded; + } + + /** + * Returns the children of the node + * + * @return A set of tree nodes + */ + public List getChildren() { + List nodes = new LinkedList(); + + if (!isLeaf() && isChildrenLoaded()) { + Iterator iter = childNodeContainer.iterator(); + while (iter.hasNext()) { + TreeNode node = (TreeNode) iter.next(); + nodes.add(node); + } + } + return nodes; + } + + public Action[] getActions() { + if (actionKeys == null) { + return new Action[] {}; + } + final Action[] actions = new Action[actionKeys.length]; + for (int i = 0; i < actions.length; i++) { + final String actionKey = actionKeys[i]; + final TreeAction a = new TreeAction(this, String.valueOf(key), + actionKey); + a.setCaption(getActionCaption(actionKey)); + a.setIconUrl(getActionIcon(actionKey)); + actions[i] = a; + } + return actions; + } + + public ApplicationConnection getClient() { + return client; + } + + public String getPaintableId() { + return paintableId; + } + + /** + * Adds/removes Vaadin specific style name. This method ought to be + * called only from VTree. + * + * @param selected + */ + protected void setSelected(boolean selected) { + // add style name to caption dom structure only, not to subtree + setStyleName(nodeCaptionDiv, "v-tree-node-selected", selected); + } + + protected boolean isSelected() { + return VTree.this.isSelected(this); + } + + /** + * Travels up the hierarchy looking for this node + * + * @param child + * The child which grandparent this is or is not + * @return True if this is a grandparent of the child node + */ + public boolean isGrandParentOf(TreeNode child) { + TreeNode currentNode = child; + boolean isGrandParent = false; + while (currentNode != null) { + currentNode = currentNode.getParentNode(); + if (currentNode == this) { + isGrandParent = true; + break; + } + } + return isGrandParent; + } + + public boolean isSibling(TreeNode node) { + return node.getParentNode() == getParentNode(); + } + + public void showContextMenu(Event event) { + if (!readonly && !disabled) { + if (actionKeys != null) { + int left = event.getClientX(); + int top = event.getClientY(); + top += Window.getScrollTop(); + left += Window.getScrollLeft(); + client.getContextMenu().showAt(this, left, top); + } + event.stopPropagation(); + event.preventDefault(); + } + } + + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.Widget#onDetach() + */ + @Override + protected void onDetach() { + super.onDetach(); + client.getContextMenu().ensureHidden(this); + } + + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.UIObject#toString() + */ + @Override + public String toString() { + return nodeCaptionSpan.getInnerText(); + } + + /** + * Is the node focused? + * + * @param focused + * True if focused, false if not + */ + public void setFocused(boolean focused) { + if (!this.focused && focused) { + nodeCaptionDiv.addClassName(CLASSNAME_FOCUSED); + + this.focused = focused; + if (BrowserInfo.get().isOpera()) { + nodeCaptionDiv.focus(); + } + treeHasFocus = true; + } else if (this.focused && !focused) { + nodeCaptionDiv.removeClassName(CLASSNAME_FOCUSED); + this.focused = focused; + treeHasFocus = false; + } + } + + /** + * Scrolls the caption into view + */ + public void scrollIntoView() { + Util.scrollIntoViewVertically(nodeCaptionDiv); + } + + public void setIcon(String iconUrl) { + if (iconUrl != null) { + // Add icon if not present + if (icon == null) { + icon = new Icon(client); + DOM.insertBefore(DOM.getFirstChild(nodeCaptionDiv), + icon.getElement(), nodeCaptionSpan); + } + icon.setUri(iconUrl); + } else { + // Remove icon if present + if (icon != null) { + DOM.removeChild(DOM.getFirstChild(nodeCaptionDiv), + icon.getElement()); + icon = null; + } + } + } + + public void setNodeStyleName(String styleName) { + addStyleName(TreeNode.CLASSNAME + "-" + styleName); + setStyleName(nodeCaptionDiv, TreeNode.CLASSNAME + "-caption-" + + styleName, true); + childNodeContainer.addStyleName(TreeNode.CLASSNAME + "-children-" + + styleName); + + } + + } + + public VDropHandler getDropHandler() { + return dropHandler; + } + + public TreeNode getNodeByKey(String key) { + return keyToNode.get(key); + } + + /** + * Deselects all items in the tree + */ + public void deselectAll() { + for (String key : selectedIds) { + TreeNode node = keyToNode.get(key); + if (node != null) { + node.setSelected(false); + } + } + selectedIds.clear(); + selectionHasChanged = true; + } + + /** + * Selects a range of nodes + * + * @param startNodeKey + * The start node key + * @param endNodeKey + * The end node key + */ + private void selectNodeRange(String startNodeKey, String endNodeKey) { + + TreeNode startNode = keyToNode.get(startNodeKey); + TreeNode endNode = keyToNode.get(endNodeKey); + + // The nodes have the same parent + if (startNode.getParent() == endNode.getParent()) { + doSiblingSelection(startNode, endNode); + + // The start node is a grandparent of the end node + } else if (startNode.isGrandParentOf(endNode)) { + doRelationSelection(startNode, endNode); + + // The end node is a grandparent of the start node + } else if (endNode.isGrandParentOf(startNode)) { + doRelationSelection(endNode, startNode); + + } else { + doNoRelationSelection(startNode, endNode); + } + } + + /** + * Selects a node and deselect all other nodes + * + * @param node + * The node to select + */ + private void selectNode(TreeNode node, boolean deselectPrevious) { + if (deselectPrevious) { + deselectAll(); + } + + if (node != null) { + node.setSelected(true); + selectedIds.add(node.key); + lastSelection = node; + } + selectionHasChanged = true; + } + + /** + * Deselects a node + * + * @param node + * The node to deselect + */ + private void deselectNode(TreeNode node) { + node.setSelected(false); + selectedIds.remove(node.key); + selectionHasChanged = true; + } + + /** + * Selects all the open children to a node + * + * @param node + * The parent node + */ + private void selectAllChildren(TreeNode node, boolean includeRootNode) { + if (includeRootNode) { + node.setSelected(true); + selectedIds.add(node.key); + } + + for (TreeNode child : node.getChildren()) { + if (!child.isLeaf() && child.getState()) { + selectAllChildren(child, true); + } else { + child.setSelected(true); + selectedIds.add(child.key); + } + } + selectionHasChanged = true; + } + + /** + * Selects all children until a stop child is reached + * + * @param root + * The root not to start from + * @param stopNode + * The node to finish with + * @param includeRootNode + * Should the root node be selected + * @param includeStopNode + * Should the stop node be selected + * + * @return Returns false if the stop child was found, else true if all + * children was selected + */ + private boolean selectAllChildrenUntil(TreeNode root, TreeNode stopNode, + boolean includeRootNode, boolean includeStopNode) { + if (includeRootNode) { + root.setSelected(true); + selectedIds.add(root.key); + } + if (root.getState() && root != stopNode) { + for (TreeNode child : root.getChildren()) { + if (!child.isLeaf() && child.getState() && child != stopNode) { + if (!selectAllChildrenUntil(child, stopNode, true, + includeStopNode)) { + return false; + } + } else if (child == stopNode) { + if (includeStopNode) { + child.setSelected(true); + selectedIds.add(child.key); + } + return false; + } else { + child.setSelected(true); + selectedIds.add(child.key); + } + } + } + selectionHasChanged = true; + + return true; + } + + /** + * Select a range between two nodes which have no relation to each other + * + * @param startNode + * The start node to start the selection from + * @param endNode + * The end node to end the selection to + */ + private void doNoRelationSelection(TreeNode startNode, TreeNode endNode) { + + TreeNode commonParent = getCommonGrandParent(startNode, endNode); + TreeNode startBranch = null, endBranch = null; + + // Find the children of the common parent + List children; + if (commonParent != null) { + children = commonParent.getChildren(); + } else { + children = getRootNodes(); + } + + // Find the start and end branches + for (TreeNode node : children) { + if (nodeIsInBranch(startNode, node)) { + startBranch = node; + } + if (nodeIsInBranch(endNode, node)) { + endBranch = node; + } + } + + // Swap nodes if necessary + if (children.indexOf(startBranch) > children.indexOf(endBranch)) { + TreeNode temp = startBranch; + startBranch = endBranch; + endBranch = temp; + + temp = startNode; + startNode = endNode; + endNode = temp; + } + + // Select all children under the start node + selectAllChildren(startNode, true); + TreeNode startParent = startNode.getParentNode(); + TreeNode currentNode = startNode; + while (startParent != null && startParent != commonParent) { + List startChildren = startParent.getChildren(); + for (int i = startChildren.indexOf(currentNode) + 1; i < startChildren + .size(); i++) { + selectAllChildren(startChildren.get(i), true); + } + + currentNode = startParent; + startParent = startParent.getParentNode(); + } + + // Select nodes until the end node is reached + for (int i = children.indexOf(startBranch) + 1; i <= children + .indexOf(endBranch); i++) { + selectAllChildrenUntil(children.get(i), endNode, true, true); + } + + // Ensure end node was selected + endNode.setSelected(true); + selectedIds.add(endNode.key); + selectionHasChanged = true; + } + + /** + * Examines the children of the branch node and returns true if a node is in + * that branch + * + * @param node + * The node to search for + * @param branch + * The branch to search in + * @return True if found, false if not found + */ + private boolean nodeIsInBranch(TreeNode node, TreeNode branch) { + if (node == branch) { + return true; + } + for (TreeNode child : branch.getChildren()) { + if (child == node) { + return true; + } + if (!child.isLeaf() && child.getState()) { + if (nodeIsInBranch(node, child)) { + return true; + } + } + } + return false; + } + + /** + * Selects a range of items which are in direct relation with each other.
+ * NOTE: The start node MUST be before the end node! + * + * @param startNode + * + * @param endNode + */ + private void doRelationSelection(TreeNode startNode, TreeNode endNode) { + TreeNode currentNode = endNode; + while (currentNode != startNode) { + currentNode.setSelected(true); + selectedIds.add(currentNode.key); + + // Traverse children above the selection + List subChildren = currentNode.getParentNode() + .getChildren(); + if (subChildren.size() > 1) { + selectNodeRange(subChildren.iterator().next().key, + currentNode.key); + } else if (subChildren.size() == 1) { + TreeNode n = subChildren.get(0); + n.setSelected(true); + selectedIds.add(n.key); + } + + currentNode = currentNode.getParentNode(); + } + startNode.setSelected(true); + selectedIds.add(startNode.key); + selectionHasChanged = true; + } + + /** + * Selects a range of items which have the same parent. + * + * @param startNode + * The start node + * @param endNode + * The end node + */ + private void doSiblingSelection(TreeNode startNode, TreeNode endNode) { + TreeNode parent = startNode.getParentNode(); + + List children; + if (parent == null) { + // Topmost parent + children = getRootNodes(); + } else { + children = parent.getChildren(); + } + + // Swap start and end point if needed + if (children.indexOf(startNode) > children.indexOf(endNode)) { + TreeNode temp = startNode; + startNode = endNode; + endNode = temp; + } + + Iterator childIter = children.iterator(); + boolean startFound = false; + while (childIter.hasNext()) { + TreeNode node = childIter.next(); + if (node == startNode) { + startFound = true; + } + + if (startFound && node != endNode && node.getState()) { + selectAllChildren(node, true); + } else if (startFound && node != endNode) { + node.setSelected(true); + selectedIds.add(node.key); + } + + if (node == endNode) { + node.setSelected(true); + selectedIds.add(node.key); + break; + } + } + selectionHasChanged = true; + } + + /** + * Returns the first common parent of two nodes + * + * @param node1 + * The first node + * @param node2 + * The second node + * @return The common parent or null + */ + public TreeNode getCommonGrandParent(TreeNode node1, TreeNode node2) { + // If either one does not have a grand parent then return null + if (node1.getParentNode() == null || node2.getParentNode() == null) { + return null; + } + + // If the nodes are parents of each other then return null + if (node1.isGrandParentOf(node2) || node2.isGrandParentOf(node1)) { + return null; + } + + // Get parents of node1 + List parents1 = new ArrayList(); + TreeNode parent1 = node1.getParentNode(); + while (parent1 != null) { + parents1.add(parent1); + parent1 = parent1.getParentNode(); + } + + // Get parents of node2 + List parents2 = new ArrayList(); + TreeNode parent2 = node2.getParentNode(); + while (parent2 != null) { + parents2.add(parent2); + parent2 = parent2.getParentNode(); + } + + // Search the parents for the first common parent + for (int i = 0; i < parents1.size(); i++) { + parent1 = parents1.get(i); + for (int j = 0; j < parents2.size(); j++) { + parent2 = parents2.get(j); + if (parent1 == parent2) { + return parent1; + } + } + } + + return null; + } + + /** + * Sets the node currently in focus + * + * @param node + * The node to focus or null to remove the focus completely + * @param scrollIntoView + * Scroll the node into view + */ + public void setFocusedNode(TreeNode node, boolean scrollIntoView) { + // Unfocus previously focused node + if (focusedNode != null) { + focusedNode.setFocused(false); + } + + if (node != null) { + node.setFocused(true); + } + + focusedNode = node; + + if (node != null && scrollIntoView) { + /* + * Delay scrolling the focused node into view if we are still + * rendering. #5396 + */ + if (!rendering) { + node.scrollIntoView(); + } else { + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + focusedNode.scrollIntoView(); + } + }); + } + } + } + + /** + * Focuses a node and scrolls it into view + * + * @param node + * The node to focus + */ + public void setFocusedNode(TreeNode node) { + setFocusedNode(node, true); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event + * .dom.client.FocusEvent) + */ + public void onFocus(FocusEvent event) { + treeHasFocus = true; + // If no node has focus, focus the first item in the tree + if (focusedNode == null && lastSelection == null && selectable) { + setFocusedNode(getFirstRootNode(), false); + } else if (focusedNode != null && selectable) { + setFocusedNode(focusedNode, false); + } else if (lastSelection != null && selectable) { + setFocusedNode(lastSelection, false); + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event + * .dom.client.BlurEvent) + */ + public void onBlur(BlurEvent event) { + treeHasFocus = false; + if (focusedNode != null) { + focusedNode.setFocused(false); + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google + * .gwt.event.dom.client.KeyPressEvent) + */ + public void onKeyPress(KeyPressEvent event) { + NativeEvent nativeEvent = event.getNativeEvent(); + int keyCode = nativeEvent.getKeyCode(); + if (keyCode == 0 && nativeEvent.getCharCode() == ' ') { + // Provide a keyCode for space to be compatible with FireFox + // keypress event + keyCode = CHARCODE_SPACE; + } + if (handleKeyNavigation(keyCode, + event.isControlKeyDown() || event.isMetaKeyDown(), + event.isShiftKeyDown())) { + event.preventDefault(); + event.stopPropagation(); + } + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt + * .event.dom.client.KeyDownEvent) + */ + public void onKeyDown(KeyDownEvent event) { + if (handleKeyNavigation(event.getNativeEvent().getKeyCode(), + event.isControlKeyDown() || event.isMetaKeyDown(), + event.isShiftKeyDown())) { + event.preventDefault(); + event.stopPropagation(); + } + } + + /** + * Handles the keyboard navigation + * + * @param keycode + * The keycode of the pressed key + * @param ctrl + * Was ctrl pressed + * @param shift + * Was shift pressed + * @return Returns true if the key was handled, else false + */ + protected boolean handleKeyNavigation(int keycode, boolean ctrl, + boolean shift) { + // Navigate down + if (keycode == getNavigationDownKey()) { + TreeNode node = null; + // If node is open and has children then move in to the children + if (!focusedNode.isLeaf() && focusedNode.getState() + && focusedNode.getChildren().size() > 0) { + node = focusedNode.getChildren().get(0); + } + + // Else move down to the next sibling + else { + node = getNextSibling(focusedNode); + if (node == null) { + // Else jump to the parent and try to select the next + // sibling there + TreeNode current = focusedNode; + while (node == null && current.getParentNode() != null) { + node = getNextSibling(current.getParentNode()); + current = current.getParentNode(); + } + } + } + + if (node != null) { + setFocusedNode(node); + if (selectable) { + if (!ctrl && !shift) { + selectNode(node, true); + } else if (shift && isMultiselect) { + deselectAll(); + selectNodeRange(lastSelection.key, node.key); + } else if (shift) { + selectNode(node, true); + } + } + } + return true; + } + + // Navigate up + if (keycode == getNavigationUpKey()) { + TreeNode prev = getPreviousSibling(focusedNode); + TreeNode node = null; + if (prev != null) { + node = getLastVisibleChildInTree(prev); + } else if (focusedNode.getParentNode() != null) { + node = focusedNode.getParentNode(); + } + if (node != null) { + setFocusedNode(node); + if (selectable) { + if (!ctrl && !shift) { + selectNode(node, true); + } else if (shift && isMultiselect) { + deselectAll(); + selectNodeRange(lastSelection.key, node.key); + } else if (shift) { + selectNode(node, true); + } + } + } + return true; + } + + // Navigate left (close branch) + if (keycode == getNavigationLeftKey()) { + if (!focusedNode.isLeaf() && focusedNode.getState()) { + focusedNode.setState(false, true); + } else if (focusedNode.getParentNode() != null + && (focusedNode.isLeaf() || !focusedNode.getState())) { + + if (ctrl || !selectable) { + setFocusedNode(focusedNode.getParentNode()); + } else if (shift) { + doRelationSelection(focusedNode.getParentNode(), + focusedNode); + setFocusedNode(focusedNode.getParentNode()); + } else { + focusAndSelectNode(focusedNode.getParentNode()); + } + } + return true; + } + + // Navigate right (open branch) + if (keycode == getNavigationRightKey()) { + if (!focusedNode.isLeaf() && !focusedNode.getState()) { + focusedNode.setState(true, true); + } else if (!focusedNode.isLeaf()) { + if (ctrl || !selectable) { + setFocusedNode(focusedNode.getChildren().get(0)); + } else if (shift) { + setSelected(focusedNode, true); + setFocusedNode(focusedNode.getChildren().get(0)); + setSelected(focusedNode, true); + } else { + focusAndSelectNode(focusedNode.getChildren().get(0)); + } + } + return true; + } + + // Selection + if (keycode == getNavigationSelectKey()) { + if (!focusedNode.isSelected()) { + selectNode( + focusedNode, + (!isMultiselect || multiSelectMode == MULTISELECT_MODE_SIMPLE) + && selectable); + } else { + deselectNode(focusedNode); + } + return true; + } + + // Home selection + if (keycode == getNavigationStartKey()) { + TreeNode node = getFirstRootNode(); + if (ctrl || !selectable) { + setFocusedNode(node); + } else if (shift) { + deselectAll(); + selectNodeRange(focusedNode.key, node.key); + } else { + selectNode(node, true); + } + sendSelectionToServer(); + return true; + } + + // End selection + if (keycode == getNavigationEndKey()) { + TreeNode lastNode = getLastRootNode(); + TreeNode node = getLastVisibleChildInTree(lastNode); + if (ctrl || !selectable) { + setFocusedNode(node); + } else if (shift) { + deselectAll(); + selectNodeRange(focusedNode.key, node.key); + } else { + selectNode(node, true); + } + sendSelectionToServer(); + return true; + } + + return false; + } + + private void focusAndSelectNode(TreeNode node) { + /* + * Keyboard navigation doesn't work reliably if the tree is in + * multiselect mode as well as isNullSelectionAllowed = false. It first + * tries to deselect the old focused node, which fails since there must + * be at least one selection. After this the newly focused node is + * selected and we've ended up with two selected nodes even though we + * only navigated with the arrow keys. + * + * Because of this, we first select the next node and later de-select + * the old one. + */ + TreeNode oldFocusedNode = focusedNode; + setFocusedNode(node); + setSelected(focusedNode, true); + setSelected(oldFocusedNode, false); + } + + /** + * Traverses the tree to the bottom most child + * + * @param root + * The root of the tree + * @return The bottom most child + */ + private TreeNode getLastVisibleChildInTree(TreeNode root) { + if (root.isLeaf() || !root.getState() || root.getChildren().size() == 0) { + return root; + } + List children = root.getChildren(); + return getLastVisibleChildInTree(children.get(children.size() - 1)); + } + + /** + * Gets the next sibling in the tree + * + * @param node + * The node to get the sibling for + * @return The sibling node or null if the node is the last sibling + */ + private TreeNode getNextSibling(TreeNode node) { + TreeNode parent = node.getParentNode(); + List children; + if (parent == null) { + children = getRootNodes(); + } else { + children = parent.getChildren(); + } + + int idx = children.indexOf(node); + if (idx < children.size() - 1) { + return children.get(idx + 1); + } + + return null; + } + + /** + * Returns the previous sibling in the tree + * + * @param node + * The node to get the sibling for + * @return The sibling node or null if the node is the first sibling + */ + private TreeNode getPreviousSibling(TreeNode node) { + TreeNode parent = node.getParentNode(); + List children; + if (parent == null) { + children = getRootNodes(); + } else { + children = parent.getChildren(); + } + + int idx = children.indexOf(node); + if (idx > 0) { + return children.get(idx - 1); + } + + return null; + } + + /** + * Add this to the element mouse down event by using element.setPropertyJSO + * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again + * when the mouse is depressed in the mouse up event. + * + * @return Returns the JSO preventing text selection + */ + private native JavaScriptObject applyDisableTextSelectionIEHack() + /*-{ + return function(){ return false; }; + }-*/; + + /** + * Get the key that moves the selection head upwards. By default it is the + * up arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationUpKey() { + return KeyCodes.KEY_UP; + } + + /** + * Get the key that moves the selection head downwards. By default it is the + * down arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationDownKey() { + return KeyCodes.KEY_DOWN; + } + + /** + * Get the key that scrolls to the left in the table. By default it is the + * left arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationLeftKey() { + return KeyCodes.KEY_LEFT; + } + + /** + * Get the key that scroll to the right on the table. By default it is the + * right arrow key but by overriding this you can change the key to whatever + * you want. + * + * @return The keycode of the key + */ + protected int getNavigationRightKey() { + return KeyCodes.KEY_RIGHT; + } + + /** + * Get the key that selects an item in the table. By default it is the space + * bar key but by overriding this you can change the key to whatever you + * want. + * + * @return + */ + protected int getNavigationSelectKey() { + return CHARCODE_SPACE; + } + + /** + * Get the key the moves the selection one page up in the table. By default + * this is the Page Up key but by overriding this you can change the key to + * whatever you want. + * + * @return + */ + protected int getNavigationPageUpKey() { + return KeyCodes.KEY_PAGEUP; + } + + /** + * Get the key the moves the selection one page down in the table. By + * default this is the Page Down key but by overriding this you can change + * the key to whatever you want. + * + * @return + */ + protected int getNavigationPageDownKey() { + return KeyCodes.KEY_PAGEDOWN; + } + + /** + * Get the key the moves the selection to the beginning of the table. By + * default this is the Home key but by overriding this you can change the + * key to whatever you want. + * + * @return + */ + protected int getNavigationStartKey() { + return KeyCodes.KEY_HOME; + } + + /** + * Get the key the moves the selection to the end of the table. By default + * this is the End key but by overriding this you can change the key to + * whatever you want. + * + * @return + */ + protected int getNavigationEndKey() { + return KeyCodes.KEY_END; + } + + private final String SUBPART_NODE_PREFIX = "n"; + private final String EXPAND_IDENTIFIER = "expand"; + + /* + * In webkit, focus may have been requested for this component but not yet + * gained. Use this to trac if tree has gained the focus on webkit. See + * FocusImplSafari and #6373 + */ + private boolean treeHasFocus; + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.terminal.gwt.client.ui.SubPartAware#getSubPartElement(java + * .lang.String) + */ + public Element getSubPartElement(String subPart) { + if ("fe".equals(subPart)) { + if (BrowserInfo.get().isOpera() && focusedNode != null) { + return focusedNode.getElement(); + } + return getFocusElement(); + } + + if (subPart.startsWith(SUBPART_NODE_PREFIX + "[")) { + boolean expandCollapse = false; + + // Node + String[] nodes = subPart.split("/"); + TreeNode treeNode = null; + try { + for (String node : nodes) { + if (node.startsWith(SUBPART_NODE_PREFIX)) { + + // skip SUBPART_NODE_PREFIX"[" + node = node.substring(SUBPART_NODE_PREFIX.length() + 1); + // skip "]" + node = node.substring(0, node.length() - 1); + int position = Integer.parseInt(node); + if (treeNode == null) { + treeNode = getRootNodes().get(position); + } else { + treeNode = treeNode.getChildren().get(position); + } + } else if (node.startsWith(EXPAND_IDENTIFIER)) { + expandCollapse = true; + } + } + + if (expandCollapse) { + return treeNode.getElement(); + } else { + return treeNode.nodeCaptionSpan; + } + } catch (Exception e) { + // Invalid locator string or node could not be found + return null; + } + } + return null; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.terminal.gwt.client.ui.SubPartAware#getSubPartName(com.google + * .gwt.user.client.Element) + */ + public String getSubPartName(Element subElement) { + // Supported identifiers: + // + // n[index]/n[index]/n[index]{/expand} + // + // Ends with "/expand" if the target is expand/collapse indicator, + // otherwise ends with the node + + boolean isExpandCollapse = false; + + if (!getElement().isOrHasChild(subElement)) { + return null; + } + + if (subElement == getFocusElement()) { + return "fe"; + } + + TreeNode treeNode = Util.findWidget(subElement, TreeNode.class); + if (treeNode == null) { + // Did not click on a node, let somebody else take care of the + // locator string + return null; + } + + if (subElement == treeNode.getElement()) { + // Targets expand/collapse arrow + isExpandCollapse = true; + } + + ArrayList positions = new ArrayList(); + while (treeNode.getParentNode() != null) { + positions.add(0, + treeNode.getParentNode().getChildren().indexOf(treeNode)); + treeNode = treeNode.getParentNode(); + } + positions.add(0, getRootNodes().indexOf(treeNode)); + + String locator = ""; + for (Integer i : positions) { + locator += SUBPART_NODE_PREFIX + "[" + i + "]/"; + } + + locator = locator.substring(0, locator.length() - 1); + if (isExpandCollapse) { + locator += "/" + EXPAND_IDENTIFIER; + } + return locator; + } + + public Action[] getActions() { + if (bodyActionKeys == null) { + return new Action[] {}; + } + final Action[] actions = new Action[bodyActionKeys.length]; + for (int i = 0; i < actions.length; i++) { + final String actionKey = bodyActionKeys[i]; + final TreeAction a = new TreeAction(this, null, actionKey); + a.setCaption(getActionCaption(actionKey)); + a.setIconUrl(getActionIcon(actionKey)); + actions[i] = a; + } + return actions; + } + + public ApplicationConnection getClient() { + return client; + } + + public String getPaintableId() { + return paintableId; + } + + private void handleBodyContextMenu(ContextMenuEvent event) { + if (!readonly && !disabled) { + if (bodyActionKeys != null) { + int left = event.getNativeEvent().getClientX(); + int top = event.getNativeEvent().getClientY(); + top += Window.getScrollTop(); + left += Window.getScrollLeft(); + client.getContextMenu().showAt(this, left, top); + } + event.stopPropagation(); + event.preventDefault(); + } + } + + public void registerAction(String key, String caption, String iconUrl) { + actionMap.put(key + "_c", caption); + if (iconUrl != null) { + actionMap.put(key + "_i", iconUrl); + } else { + actionMap.remove(key + "_i"); + } + + } + + public void registerNode(TreeNode treeNode) { + keyToNode.put(treeNode.key, treeNode); + } + + public void clearNodeToKeyMap() { + keyToNode.clear(); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/treetable/TreeTableConnector.java b/src/com/vaadin/terminal/gwt/client/ui/treetable/TreeTableConnector.java new file mode 100644 index 0000000000..aff2fafdb9 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/treetable/TreeTableConnector.java @@ -0,0 +1,102 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.treetable; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.FocusableScrollPanel; +import com.vaadin.terminal.gwt.client.ui.table.TableConnector; +import com.vaadin.terminal.gwt.client.ui.table.VScrollTable.VScrollTableBody.VScrollTableRow; +import com.vaadin.terminal.gwt.client.ui.treetable.VTreeTable.PendingNavigationEvent; +import com.vaadin.ui.TreeTable; + +@Component(TreeTable.class) +public class TreeTableConnector extends TableConnector { + public static final String ATTRIBUTE_HIERARCHY_COLUMN_INDEX = "hci"; + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + FocusableScrollPanel widget = null; + int scrollPosition = 0; + if (getWidget().collapseRequest) { + widget = (FocusableScrollPanel) getWidget().getWidget(1); + scrollPosition = widget.getScrollPosition(); + } + getWidget().animationsEnabled = uidl.getBooleanAttribute("animate"); + getWidget().colIndexOfHierarchy = uidl + .hasAttribute(ATTRIBUTE_HIERARCHY_COLUMN_INDEX) ? uidl + .getIntAttribute(ATTRIBUTE_HIERARCHY_COLUMN_INDEX) : 0; + int oldTotalRows = getWidget().getTotalRows(); + super.updateFromUIDL(uidl, client); + if (getWidget().collapseRequest) { + if (getWidget().collapsedRowKey != null + && getWidget().scrollBody != null) { + VScrollTableRow row = getWidget().getRenderedRowByKey( + getWidget().collapsedRowKey); + if (row != null) { + getWidget().setRowFocus(row); + getWidget().focus(); + } + } + + int scrollPosition2 = widget.getScrollPosition(); + if (scrollPosition != scrollPosition2) { + widget.setScrollPosition(scrollPosition); + } + + // check which rows are needed from the server and initiate a + // deferred fetch + getWidget().onScroll(null); + } + // Recalculate table size if collapse request, or if page length is zero + // (not sent by server) and row count changes (#7908). + if (getWidget().collapseRequest + || (!uidl.hasAttribute("pagelength") && getWidget() + .getTotalRows() != oldTotalRows)) { + /* + * Ensure that possibly removed/added scrollbars are considered. + * Triggers row calculations, removes cached rows etc. Basically + * cleans up state. Be careful if touching this, you will break + * pageLength=0 if you remove this. + */ + getWidget().triggerLazyColumnAdjustment(true); + + getWidget().collapseRequest = false; + } + if (uidl.hasAttribute("focusedRow")) { + String key = uidl.getStringAttribute("focusedRow"); + getWidget().setRowFocus(getWidget().getRenderedRowByKey(key)); + getWidget().focusParentResponsePending = false; + } else if (uidl.hasAttribute("clearFocusPending")) { + // Special case to detect a response to a focusParent request that + // does not return any focusedRow because the selected node has no + // parent + getWidget().focusParentResponsePending = false; + } + + while (!getWidget().collapseRequest + && !getWidget().focusParentResponsePending + && !getWidget().pendingNavigationEvents.isEmpty()) { + // Keep replaying any queued events as long as we don't have any + // potential content changes pending + PendingNavigationEvent event = getWidget().pendingNavigationEvents + .removeFirst(); + getWidget() + .handleNavigation(event.keycode, event.ctrl, event.shift); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VTreeTable.class); + } + + @Override + public VTreeTable getWidget() { + return (VTreeTable) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/treetable/VTreeTable.java b/src/com/vaadin/terminal/gwt/client/ui/treetable/VTreeTable.java new file mode 100644 index 0000000000..2e15e7c445 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/treetable/VTreeTable.java @@ -0,0 +1,805 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.treetable; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import com.google.gwt.animation.client.Animation; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.ImageElement; +import com.google.gwt.dom.client.SpanElement; +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.dom.client.TableCellElement; +import com.google.gwt.event.dom.client.KeyCodes; +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.Widget; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.ComputedStyle; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.table.VScrollTable; +import com.vaadin.terminal.gwt.client.ui.treetable.VTreeTable.VTreeTableScrollBody.VTreeTableRow; + +public class VTreeTable extends VScrollTable { + + static class PendingNavigationEvent { + final int keycode; + final boolean ctrl; + final boolean shift; + + public PendingNavigationEvent(int keycode, boolean ctrl, boolean shift) { + this.keycode = keycode; + this.ctrl = ctrl; + this.shift = shift; + } + + @Override + public String toString() { + String string = "Keyboard event: " + keycode; + if (ctrl) { + string += " + ctrl"; + } + if (shift) { + string += " + shift"; + } + return string; + } + } + + boolean collapseRequest; + private boolean selectionPending; + int colIndexOfHierarchy; + String collapsedRowKey; + VTreeTableScrollBody scrollBody; + boolean animationsEnabled; + LinkedList pendingNavigationEvents = new LinkedList(); + boolean focusParentResponsePending; + + @Override + protected VScrollTableBody createScrollBody() { + scrollBody = new VTreeTableScrollBody(); + return scrollBody; + } + + /* + * Overridden to allow animation of expands and collapses of nodes. + */ + @Override + protected void addAndRemoveRows(UIDL partialRowAdditions) { + if (partialRowAdditions == null) { + return; + } + + if (animationsEnabled && browserSupportsAnimation()) { + if (partialRowAdditions.hasAttribute("hide")) { + scrollBody.unlinkRowsAnimatedAndUpdateCacheWhenFinished( + partialRowAdditions.getIntAttribute("firstprowix"), + partialRowAdditions.getIntAttribute("numprows")); + } else { + scrollBody.insertRowsAnimated(partialRowAdditions, + partialRowAdditions.getIntAttribute("firstprowix"), + partialRowAdditions.getIntAttribute("numprows")); + discardRowsOutsideCacheWindow(); + } + } else { + super.addAndRemoveRows(partialRowAdditions); + } + } + + private boolean browserSupportsAnimation() { + BrowserInfo bi = BrowserInfo.get(); + return !(bi.isSafari4()); + } + + class VTreeTableScrollBody extends VScrollTable.VScrollTableBody { + private int identWidth = -1; + + VTreeTableScrollBody() { + super(); + } + + @Override + protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) { + if (uidl.hasAttribute("gen_html")) { + // This is a generated row. + return new VTreeTableGeneratedRow(uidl, aligns2); + } + return new VTreeTableRow(uidl, aligns2); + } + + class VTreeTableRow extends + VScrollTable.VScrollTableBody.VScrollTableRow { + + private boolean isTreeCellAdded = false; + private SpanElement treeSpacer; + private boolean open; + private int depth; + private boolean canHaveChildren; + protected Widget widgetInHierarchyColumn; + + public VTreeTableRow(UIDL uidl, char[] aligns2) { + super(uidl, aligns2); + } + + @Override + public void addCell(UIDL rowUidl, String text, char align, + String style, boolean textIsHTML, boolean isSorted, + String description) { + super.addCell(rowUidl, text, align, style, textIsHTML, + isSorted, description); + + addTreeSpacer(rowUidl); + } + + protected boolean addTreeSpacer(UIDL rowUidl) { + if (cellShowsTreeHierarchy(getElement().getChildCount() - 1)) { + Element container = (Element) getElement().getLastChild() + .getFirstChild(); + + if (rowUidl.hasAttribute("icon")) { + // icons are in first content cell in TreeTable + ImageElement icon = Document.get().createImageElement(); + icon.setClassName("v-icon"); + icon.setAlt("icon"); + icon.setSrc(client.translateVaadinUri(rowUidl + .getStringAttribute("icon"))); + container.insertFirst(icon); + } + + String classname = "v-treetable-treespacer"; + if (rowUidl.getBooleanAttribute("ca")) { + canHaveChildren = true; + open = rowUidl.getBooleanAttribute("open"); + classname += open ? " v-treetable-node-open" + : " v-treetable-node-closed"; + } + + treeSpacer = Document.get().createSpanElement(); + + treeSpacer.setClassName(classname); + container.insertFirst(treeSpacer); + depth = rowUidl.hasAttribute("depth") ? rowUidl + .getIntAttribute("depth") : 0; + setIdent(); + isTreeCellAdded = true; + return true; + } + return false; + } + + private boolean cellShowsTreeHierarchy(int curColIndex) { + if (isTreeCellAdded) { + return false; + } + return curColIndex == colIndexOfHierarchy + + (showRowHeaders ? 1 : 0); + } + + @Override + public void onBrowserEvent(Event event) { + if (event.getEventTarget().cast() == treeSpacer + && treeSpacer.getClassName().contains("node")) { + if (event.getTypeInt() == Event.ONMOUSEUP) { + sendToggleCollapsedUpdate(getKey()); + } + return; + } + super.onBrowserEvent(event); + } + + @Override + public void addCell(UIDL rowUidl, Widget w, char align, + String style, boolean isSorted) { + super.addCell(rowUidl, w, align, style, isSorted); + if (addTreeSpacer(rowUidl)) { + widgetInHierarchyColumn = w; + } + + } + + private void setIdent() { + if (getIdentWidth() > 0 && depth != 0) { + treeSpacer.getStyle().setWidth( + (depth + 1) * getIdentWidth(), Unit.PX); + } + } + + @Override + protected void onAttach() { + super.onAttach(); + if (getIdentWidth() < 0) { + detectIdent(this); + } + } + + private int getHierarchyAndIconWidth() { + int consumedSpace = treeSpacer.getOffsetWidth(); + if (treeSpacer.getParentElement().getChildCount() > 2) { + // icon next to tree spacer + consumedSpace += ((com.google.gwt.dom.client.Element) treeSpacer + .getNextSibling()).getOffsetWidth(); + } + return consumedSpace; + } + + } + + protected class VTreeTableGeneratedRow extends VTreeTableRow { + private boolean spanColumns; + private boolean htmlContentAllowed; + + public VTreeTableGeneratedRow(UIDL uidl, char[] aligns) { + super(uidl, aligns); + addStyleName("v-table-generated-row"); + } + + public boolean isSpanColumns() { + return spanColumns; + } + + @Override + protected void initCellWidths() { + if (spanColumns) { + setSpannedColumnWidthAfterDOMFullyInited(); + } else { + super.initCellWidths(); + } + } + + private void setSpannedColumnWidthAfterDOMFullyInited() { + // Defer setting width on spanned columns to make sure that + // they are added to the DOM before trying to calculate + // widths. + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + + public void execute() { + if (showRowHeaders) { + setCellWidth(0, tHead.getHeaderCell(0).getWidth()); + calcAndSetSpanWidthOnCell(1); + } else { + calcAndSetSpanWidthOnCell(0); + } + } + }); + } + + @Override + protected boolean isRenderHtmlInCells() { + return htmlContentAllowed; + } + + @Override + protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col, + int visibleColumnIndex) { + htmlContentAllowed = uidl.getBooleanAttribute("gen_html"); + spanColumns = uidl.getBooleanAttribute("gen_span"); + + final Iterator cells = uidl.getChildIterator(); + if (spanColumns) { + int colCount = uidl.getChildCount(); + if (cells.hasNext()) { + final Object cell = cells.next(); + if (cell instanceof String) { + addSpannedCell(uidl, cell.toString(), aligns[0], + "", htmlContentAllowed, false, null, + colCount); + } else { + addSpannedCell(uidl, (Widget) cell, aligns[0], "", + false, colCount); + } + } + } else { + super.addCellsFromUIDL(uidl, aligns, col, + visibleColumnIndex); + } + } + + private void addSpannedCell(UIDL rowUidl, Widget w, char align, + String style, boolean sorted, int colCount) { + TableCellElement td = DOM.createTD().cast(); + td.setColSpan(colCount); + initCellWithWidget(w, align, style, sorted, td); + td.getStyle().setHeight(getRowHeight(), Unit.PX); + if (addTreeSpacer(rowUidl)) { + widgetInHierarchyColumn = w; + } + } + + private void addSpannedCell(UIDL rowUidl, String text, char align, + String style, boolean textIsHTML, boolean sorted, + String description, int colCount) { + // String only content is optimized by not using Label widget + final TableCellElement td = DOM.createTD().cast(); + td.setColSpan(colCount); + initCellWithText(text, align, style, textIsHTML, sorted, + description, td); + td.getStyle().setHeight(getRowHeight(), Unit.PX); + addTreeSpacer(rowUidl); + } + + @Override + protected void setCellWidth(int cellIx, int width) { + if (isSpanColumns()) { + if (showRowHeaders) { + if (cellIx == 0) { + super.setCellWidth(0, width); + } else { + // We need to recalculate the spanning TDs width for + // every cellIx in order to support column resizing. + calcAndSetSpanWidthOnCell(1); + } + } else { + // Same as above. + calcAndSetSpanWidthOnCell(0); + } + } else { + super.setCellWidth(cellIx, width); + } + } + + private void calcAndSetSpanWidthOnCell(final int cellIx) { + int spanWidth = 0; + for (int ix = (showRowHeaders ? 1 : 0); ix < tHead + .getVisibleCellCount(); ix++) { + spanWidth += tHead.getHeaderCell(ix).getOffsetWidth(); + } + Util.setWidthExcludingPaddingAndBorder((Element) getElement() + .getChild(cellIx), spanWidth, 13, false); + } + } + + private int getIdentWidth() { + return identWidth; + } + + private void detectIdent(VTreeTableRow vTreeTableRow) { + identWidth = vTreeTableRow.treeSpacer.getOffsetWidth(); + if (identWidth == 0) { + identWidth = -1; + return; + } + Iterator iterator = iterator(); + while (iterator.hasNext()) { + VTreeTableRow next = (VTreeTableRow) iterator.next(); + next.setIdent(); + } + } + + protected void unlinkRowsAnimatedAndUpdateCacheWhenFinished( + final int firstIndex, final int rows) { + List rowsToDelete = new ArrayList(); + for (int ix = firstIndex; ix < firstIndex + rows; ix++) { + VScrollTableRow row = getRowByRowIndex(ix); + if (row != null) { + rowsToDelete.add(row); + } + } + RowCollapseAnimation anim = new RowCollapseAnimation(rowsToDelete) { + @Override + protected void onComplete() { + super.onComplete(); + // Actually unlink the rows and update the cache after the + // animation is done. + unlinkAndReindexRows(firstIndex, rows); + discardRowsOutsideCacheWindow(); + ensureCacheFilled(); + } + }; + anim.run(150); + } + + protected List insertRowsAnimated(UIDL rowData, + int firstIndex, int rows) { + List insertedRows = insertAndReindexRows(rowData, + firstIndex, rows); + RowExpandAnimation anim = new RowExpandAnimation(insertedRows); + anim.run(150); + return insertedRows; + } + + /** + * Prepares the table for animation by copying the background colors of + * all TR elements to their respective TD elements if the TD element is + * transparent. This is needed, since if TDs have transparent + * backgrounds, the rows sliding behind them are visible. + */ + private class AnimationPreparator { + private final int lastItemIx; + + public AnimationPreparator(int lastItemIx) { + this.lastItemIx = lastItemIx; + } + + public void prepareTableForAnimation() { + int ix = lastItemIx; + VScrollTableRow row = null; + while ((row = getRowByRowIndex(ix)) != null) { + copyTRBackgroundsToTDs(row); + --ix; + } + } + + private void copyTRBackgroundsToTDs(VScrollTableRow row) { + Element tr = row.getElement(); + ComputedStyle cs = new ComputedStyle(tr); + String backgroundAttachment = cs + .getProperty("backgroundAttachment"); + String backgroundClip = cs.getProperty("backgroundClip"); + String backgroundColor = cs.getProperty("backgroundColor"); + String backgroundImage = cs.getProperty("backgroundImage"); + String backgroundOrigin = cs.getProperty("backgroundOrigin"); + for (int ix = 0; ix < tr.getChildCount(); ix++) { + Element td = tr.getChild(ix).cast(); + if (!elementHasBackground(td)) { + td.getStyle().setProperty("backgroundAttachment", + backgroundAttachment); + td.getStyle().setProperty("backgroundClip", + backgroundClip); + td.getStyle().setProperty("backgroundColor", + backgroundColor); + td.getStyle().setProperty("backgroundImage", + backgroundImage); + td.getStyle().setProperty("backgroundOrigin", + backgroundOrigin); + } + } + } + + private boolean elementHasBackground(Element element) { + ComputedStyle cs = new ComputedStyle(element); + String clr = cs.getProperty("backgroundColor"); + String img = cs.getProperty("backgroundImage"); + return !("rgba(0, 0, 0, 0)".equals(clr.trim()) + || "transparent".equals(clr.trim()) || img == null); + } + + public void restoreTableAfterAnimation() { + int ix = lastItemIx; + VScrollTableRow row = null; + while ((row = getRowByRowIndex(ix)) != null) { + restoreStyleForTDsInRow(row); + + --ix; + } + } + + private void restoreStyleForTDsInRow(VScrollTableRow row) { + Element tr = row.getElement(); + for (int ix = 0; ix < tr.getChildCount(); ix++) { + Element td = tr.getChild(ix).cast(); + td.getStyle().clearProperty("backgroundAttachment"); + td.getStyle().clearProperty("backgroundClip"); + td.getStyle().clearProperty("backgroundColor"); + td.getStyle().clearProperty("backgroundImage"); + td.getStyle().clearProperty("backgroundOrigin"); + } + } + } + + /** + * Animates row expansion using the GWT animation framework. + * + * The idea is as follows: + * + * 1. Insert all rows normally + * + * 2. Insert a newly created DIV containing a new TABLE element below + * the DIV containing the actual scroll table body. + * + * 3. Clone the rows that were inserted in step 1 and attach the clones + * to the new TABLE element created in step 2. + * + * 4. The new DIV from step 2 is absolutely positioned so that the last + * inserted row is just behind the row that was expanded. + * + * 5. Hide the contents of the originally inserted rows by setting the + * DIV.v-table-cell-wrapper to display:none;. + * + * 6. Set the height of the originally inserted rows to 0. + * + * 7. The animation loop slides the DIV from step 2 downwards, while at + * the same pace growing the height of each of the inserted rows from 0 + * to full height. The first inserted row grows from 0 to full and after + * this the second row grows from 0 to full, etc until all rows are full + * height. + * + * 8. Remove the DIV from step 2 + * + * 9. Restore display:block; to the DIV.v-table-cell-wrapper elements. + * + * 10. DONE + */ + private class RowExpandAnimation extends Animation { + + private final List rows; + private Element cloneDiv; + private Element cloneTable; + private AnimationPreparator preparator; + + public RowExpandAnimation(List rows) { + this.rows = rows; + buildAndInsertAnimatingDiv(); + preparator = new AnimationPreparator(rows.get(0).getIndex() - 1); + preparator.prepareTableForAnimation(); + for (VScrollTableRow row : rows) { + cloneAndAppendRow(row); + row.addStyleName("v-table-row-animating"); + setCellWrapperDivsToDisplayNone(row); + row.setHeight(getInitialHeight()); + } + } + + protected String getInitialHeight() { + return "0px"; + } + + private void cloneAndAppendRow(VScrollTableRow row) { + Element clonedTR = null; + clonedTR = row.getElement().cloneNode(true).cast(); + clonedTR.getStyle().setVisibility(Visibility.VISIBLE); + cloneTable.appendChild(clonedTR); + } + + protected double getBaseOffset() { + return rows.get(0).getAbsoluteTop() + - rows.get(0).getParent().getAbsoluteTop() + - rows.size() * getRowHeight(); + } + + private void buildAndInsertAnimatingDiv() { + cloneDiv = DOM.createDiv(); + cloneDiv.addClassName("v-treetable-animation-clone-wrapper"); + cloneTable = DOM.createTable(); + cloneTable.addClassName("v-treetable-animation-clone"); + cloneDiv.appendChild(cloneTable); + insertAnimatingDiv(); + } + + private void insertAnimatingDiv() { + Element tableBody = getElement().cast(); + Element tableBodyParent = tableBody.getParentElement().cast(); + tableBodyParent.insertAfter(cloneDiv, tableBody); + } + + @Override + protected void onUpdate(double progress) { + animateDiv(progress); + animateRowHeights(progress); + } + + private void animateDiv(double progress) { + double offset = calculateDivOffset(progress, getRowHeight()); + + cloneDiv.getStyle().setTop(getBaseOffset() + offset, Unit.PX); + } + + private void animateRowHeights(double progress) { + double rh = getRowHeight(); + double vlh = calculateHeightOfAllVisibleLines(progress, rh); + int ix = 0; + + while (ix < rows.size()) { + double height = vlh < rh ? vlh : rh; + rows.get(ix).setHeight(height + "px"); + vlh -= height; + ix++; + } + } + + protected double calculateHeightOfAllVisibleLines(double progress, + double rh) { + return rows.size() * rh * progress; + } + + protected double calculateDivOffset(double progress, double rh) { + return progress * rows.size() * rh; + } + + @Override + protected void onComplete() { + preparator.restoreTableAfterAnimation(); + for (VScrollTableRow row : rows) { + resetCellWrapperDivsDisplayProperty(row); + row.removeStyleName("v-table-row-animating"); + } + Element tableBodyParent = (Element) getElement() + .getParentElement(); + tableBodyParent.removeChild(cloneDiv); + } + + private void setCellWrapperDivsToDisplayNone(VScrollTableRow row) { + Element tr = row.getElement(); + for (int ix = 0; ix < tr.getChildCount(); ix++) { + getWrapperDiv(tr, ix).getStyle().setDisplay(Display.NONE); + } + } + + private Element getWrapperDiv(Element tr, int tdIx) { + Element td = tr.getChild(tdIx).cast(); + return td.getChild(0).cast(); + } + + private void resetCellWrapperDivsDisplayProperty(VScrollTableRow row) { + Element tr = row.getElement(); + for (int ix = 0; ix < tr.getChildCount(); ix++) { + getWrapperDiv(tr, ix).getStyle().clearProperty("display"); + } + } + + } + + /** + * This is the inverse of the RowExpandAnimation and is implemented by + * extending it and overriding the calculation of offsets and heights. + */ + private class RowCollapseAnimation extends RowExpandAnimation { + + private final List rows; + + public RowCollapseAnimation(List rows) { + super(rows); + this.rows = rows; + } + + @Override + protected String getInitialHeight() { + return getRowHeight() + "px"; + } + + @Override + protected double getBaseOffset() { + return getRowHeight(); + } + + @Override + protected double calculateHeightOfAllVisibleLines(double progress, + double rh) { + return rows.size() * rh * (1 - progress); + } + + @Override + protected double calculateDivOffset(double progress, double rh) { + return -super.calculateDivOffset(progress, rh); + } + } + } + + /** + * Icons rendered into first actual column in TreeTable, not to row header + * cell + */ + @Override + protected String buildCaptionHtmlSnippet(UIDL uidl) { + if (uidl.getTag().equals("column")) { + return super.buildCaptionHtmlSnippet(uidl); + } else { + String s = uidl.getStringAttribute("caption"); + return s; + } + } + + @Override + protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) { + if (collapseRequest || focusParentResponsePending) { + // Enqueue the event if there might be pending content changes from + // the server + if (pendingNavigationEvents.size() < 10) { + // Only keep 10 keyboard events in the queue + PendingNavigationEvent pendingNavigationEvent = new PendingNavigationEvent( + keycode, ctrl, shift); + pendingNavigationEvents.add(pendingNavigationEvent); + } + return true; + } + + VTreeTableRow focusedRow = (VTreeTableRow) getFocusedRow(); + if (focusedRow != null) { + if (focusedRow.canHaveChildren + && ((keycode == KeyCodes.KEY_RIGHT && !focusedRow.open) || (keycode == KeyCodes.KEY_LEFT && focusedRow.open))) { + if (!ctrl) { + client.updateVariable(paintableId, "selectCollapsed", true, + false); + } + sendSelectedRows(false); + sendToggleCollapsedUpdate(focusedRow.getKey()); + return true; + } else if (keycode == KeyCodes.KEY_RIGHT && focusedRow.open) { + // already expanded, move selection down if next is on a deeper + // level (is-a-child) + VTreeTableScrollBody body = (VTreeTableScrollBody) focusedRow + .getParent(); + Iterator iterator = body.iterator(); + VTreeTableRow next = null; + while (iterator.hasNext()) { + next = (VTreeTableRow) iterator.next(); + if (next == focusedRow) { + next = (VTreeTableRow) iterator.next(); + break; + } + } + if (next != null) { + if (next.depth > focusedRow.depth) { + selectionPending = true; + return super.handleNavigation(getNavigationDownKey(), + ctrl, shift); + } + } else { + // Note, a minor change here for a bit false behavior if + // cache rows is disabled + last visible row + no childs for + // the node + selectionPending = true; + return super.handleNavigation(getNavigationDownKey(), ctrl, + shift); + } + } else if (keycode == KeyCodes.KEY_LEFT) { + // already collapsed move selection up to parent node + // do on the server side as the parent is not necessary + // rendered on the client, could check if parent is visible if + // a performance issue arises + + client.updateVariable(paintableId, "focusParent", + focusedRow.getKey(), true); + + // Set flag that we should enqueue navigation events until we + // get a response to this request + focusParentResponsePending = true; + + return true; + } + } + return super.handleNavigation(keycode, ctrl, shift); + } + + private void sendToggleCollapsedUpdate(String rowKey) { + collapsedRowKey = rowKey; + collapseRequest = true; + client.updateVariable(paintableId, "toggleCollapsed", rowKey, true); + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (event.getTypeInt() == Event.ONKEYUP && selectionPending) { + sendSelectedRows(); + } + } + + @Override + protected void sendSelectedRows(boolean immediately) { + super.sendSelectedRows(immediately); + selectionPending = false; + } + + @Override + protected void reOrderColumn(String columnKey, int newIndex) { + super.reOrderColumn(columnKey, newIndex); + // current impl not intelligent enough to survive without visiting the + // server to redraw content + client.sendPendingVariableChanges(); + } + + @Override + public void setStyleName(String style) { + super.setStyleName(style + " v-treetable"); + } + + @Override + protected void updateTotalRows(UIDL uidl) { + // Make sure that initializedAndAttached & al are not reset when the + // totalrows are updated on expand/collapse requests. + int newTotalRows = uidl.getIntAttribute("totalrows"); + setTotalRows(newTotalRows); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/twincolselect/TwinColSelectConnector.java b/src/com/vaadin/terminal/gwt/client/ui/twincolselect/TwinColSelectConnector.java new file mode 100644 index 0000000000..de1095f664 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/twincolselect/TwinColSelectConnector.java @@ -0,0 +1,63 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.twincolselect; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.DirectionalManagedLayout; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.optiongroup.OptionGroupBaseConnector; +import com.vaadin.ui.TwinColSelect; + +@Component(TwinColSelect.class) +public class TwinColSelectConnector extends OptionGroupBaseConnector implements + DirectionalManagedLayout { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // Captions are updated before super call to ensure the widths are set + // correctly + if (isRealUpdate(uidl)) { + getWidget().updateCaptions(uidl); + getLayoutManager().setWidthNeedsUpdate(this); + } + + super.updateFromUIDL(uidl, client); + } + + @Override + protected void init() { + getLayoutManager().registerDependency(this, + getWidget().captionWrapper.getElement()); + } + + @Override + protected Widget createWidget() { + return GWT.create(VTwinColSelect.class); + } + + @Override + public VTwinColSelect getWidget() { + return (VTwinColSelect) super.getWidget(); + } + + public void layoutVertically() { + if (isUndefinedHeight()) { + getWidget().clearInternalHeights(); + } else { + getWidget().setInternalHeights(); + } + } + + public void layoutHorizontally() { + if (isUndefinedWidth()) { + getWidget().clearInternalWidths(); + } else { + getWidget().setInternalWidths(); + } + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/twincolselect/VTwinColSelect.java b/src/com/vaadin/terminal/gwt/client/ui/twincolselect/VTwinColSelect.java new file mode 100644 index 0000000000..8f1ea09b8f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/twincolselect/VTwinColSelect.java @@ -0,0 +1,600 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.twincolselect; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.DoubleClickEvent; +import com.google.gwt.event.dom.client.DoubleClickHandler; +import com.google.gwt.event.dom.client.HasDoubleClickHandlers; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.MouseDownEvent; +import com.google.gwt.event.dom.client.MouseDownHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.Panel; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ui.SubPartAware; +import com.vaadin.terminal.gwt.client.ui.button.VButton; +import com.vaadin.terminal.gwt.client.ui.optiongroup.VOptionGroupBase; + +public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, + MouseDownHandler, DoubleClickHandler, SubPartAware { + + private static final String CLASSNAME = "v-select-twincol"; + public static final String ATTRIBUTE_LEFT_CAPTION = "lc"; + public static final String ATTRIBUTE_RIGHT_CAPTION = "rc"; + + private static final int VISIBLE_COUNT = 10; + + private static final int DEFAULT_COLUMN_COUNT = 10; + + private final DoubleClickListBox options; + + private final DoubleClickListBox selections; + + FlowPanel captionWrapper; + + private HTML optionsCaption = null; + + private HTML selectionsCaption = null; + + private final VButton add; + + private final VButton remove; + + private final FlowPanel buttons; + + private final Panel panel; + + /** + * A ListBox which catches double clicks + * + */ + public class DoubleClickListBox extends ListBox implements + HasDoubleClickHandlers { + public DoubleClickListBox(boolean isMultipleSelect) { + super(isMultipleSelect); + } + + public DoubleClickListBox() { + super(); + } + + @Override + public HandlerRegistration addDoubleClickHandler( + DoubleClickHandler handler) { + return addDomHandler(handler, DoubleClickEvent.getType()); + } + } + + public VTwinColSelect() { + super(CLASSNAME); + + captionWrapper = new FlowPanel(); + + options = new DoubleClickListBox(); + options.addClickHandler(this); + options.addDoubleClickHandler(this); + options.setVisibleItemCount(VISIBLE_COUNT); + options.setStyleName(CLASSNAME + "-options"); + + selections = new DoubleClickListBox(); + selections.addClickHandler(this); + selections.addDoubleClickHandler(this); + selections.setVisibleItemCount(VISIBLE_COUNT); + selections.setStyleName(CLASSNAME + "-selections"); + + buttons = new FlowPanel(); + buttons.setStyleName(CLASSNAME + "-buttons"); + add = new VButton(); + add.setText(">>"); + add.addClickHandler(this); + remove = new VButton(); + remove.setText("<<"); + remove.addClickHandler(this); + + panel = ((Panel) optionsContainer); + + panel.add(captionWrapper); + captionWrapper.getElement().getStyle().setOverflow(Overflow.HIDDEN); + // Hide until there actually is a caption to prevent IE from rendering + // extra empty space + captionWrapper.setVisible(false); + + panel.add(options); + buttons.add(add); + final HTML br = new HTML(""); + br.setStyleName(CLASSNAME + "-deco"); + buttons.add(br); + buttons.add(remove); + panel.add(buttons); + panel.add(selections); + + options.addKeyDownHandler(this); + options.addMouseDownHandler(this); + + selections.addMouseDownHandler(this); + selections.addKeyDownHandler(this); + } + + public HTML getOptionsCaption() { + if (optionsCaption == null) { + optionsCaption = new HTML(); + optionsCaption.setStyleName(CLASSNAME + "-caption-left"); + optionsCaption.getElement().getStyle() + .setFloat(com.google.gwt.dom.client.Style.Float.LEFT); + captionWrapper.add(optionsCaption); + } + + return optionsCaption; + } + + public HTML getSelectionsCaption() { + if (selectionsCaption == null) { + selectionsCaption = new HTML(); + selectionsCaption.setStyleName(CLASSNAME + "-caption-right"); + selectionsCaption.getElement().getStyle() + .setFloat(com.google.gwt.dom.client.Style.Float.RIGHT); + captionWrapper.add(selectionsCaption); + } + + return selectionsCaption; + } + + protected void updateCaptions(UIDL uidl) { + String leftCaption = (uidl.hasAttribute(ATTRIBUTE_LEFT_CAPTION) ? uidl + .getStringAttribute(ATTRIBUTE_LEFT_CAPTION) : null); + String rightCaption = (uidl.hasAttribute(ATTRIBUTE_RIGHT_CAPTION) ? uidl + .getStringAttribute(ATTRIBUTE_RIGHT_CAPTION) : null); + + boolean hasCaptions = (leftCaption != null || rightCaption != null); + + if (leftCaption == null) { + removeOptionsCaption(); + } else { + getOptionsCaption().setText(leftCaption); + + } + + if (rightCaption == null) { + removeSelectionsCaption(); + } else { + getSelectionsCaption().setText(rightCaption); + } + + captionWrapper.setVisible(hasCaptions); + } + + private void removeOptionsCaption() { + if (optionsCaption == null) { + return; + } + + if (optionsCaption.getParent() != null) { + captionWrapper.remove(optionsCaption); + } + + optionsCaption = null; + } + + private void removeSelectionsCaption() { + if (selectionsCaption == null) { + return; + } + + if (selectionsCaption.getParent() != null) { + captionWrapper.remove(selectionsCaption); + } + + selectionsCaption = null; + } + + @Override + protected void buildOptions(UIDL uidl) { + final boolean enabled = !isDisabled() && !isReadonly(); + options.setMultipleSelect(isMultiselect()); + selections.setMultipleSelect(isMultiselect()); + options.setEnabled(enabled); + selections.setEnabled(enabled); + add.setEnabled(enabled); + remove.setEnabled(enabled); + options.clear(); + selections.clear(); + for (final Iterator i = uidl.getChildIterator(); i.hasNext();) { + final UIDL optionUidl = (UIDL) i.next(); + if (optionUidl.hasAttribute("selected")) { + selections.addItem(optionUidl.getStringAttribute("caption"), + optionUidl.getStringAttribute("key")); + } else { + options.addItem(optionUidl.getStringAttribute("caption"), + optionUidl.getStringAttribute("key")); + } + } + + if (getRows() > 0) { + options.setVisibleItemCount(getRows()); + selections.setVisibleItemCount(getRows()); + + } + + } + + @Override + protected String[] getSelectedItems() { + final ArrayList selectedItemKeys = new ArrayList(); + for (int i = 0; i < selections.getItemCount(); i++) { + selectedItemKeys.add(selections.getValue(i)); + } + return selectedItemKeys.toArray(new String[selectedItemKeys.size()]); + } + + private boolean[] getSelectionBitmap(ListBox listBox) { + final boolean[] selectedIndexes = new boolean[listBox.getItemCount()]; + for (int i = 0; i < listBox.getItemCount(); i++) { + if (listBox.isItemSelected(i)) { + selectedIndexes[i] = true; + } else { + selectedIndexes[i] = false; + } + } + return selectedIndexes; + } + + private void addItem() { + Set movedItems = moveSelectedItems(options, selections); + selectedKeys.addAll(movedItems); + + client.updateVariable(paintableId, "selected", + selectedKeys.toArray(new String[selectedKeys.size()]), + isImmediate()); + } + + private void removeItem() { + Set movedItems = moveSelectedItems(selections, options); + selectedKeys.removeAll(movedItems); + + client.updateVariable(paintableId, "selected", + selectedKeys.toArray(new String[selectedKeys.size()]), + isImmediate()); + } + + private Set moveSelectedItems(ListBox source, ListBox target) { + final boolean[] sel = getSelectionBitmap(source); + final Set movedItems = new HashSet(); + int lastSelected = 0; + for (int i = 0; i < sel.length; i++) { + if (sel[i]) { + final int optionIndex = i + - (sel.length - source.getItemCount()); + movedItems.add(source.getValue(optionIndex)); + + // Move selection to another column + final String text = source.getItemText(optionIndex); + final String value = source.getValue(optionIndex); + target.addItem(text, value); + target.setItemSelected(target.getItemCount() - 1, true); + source.removeItem(optionIndex); + + if (source.getItemCount() > 0) { + lastSelected = optionIndex > 0 ? optionIndex - 1 : 0; + } + } + } + + if (source.getItemCount() > 0) { + source.setSelectedIndex(lastSelected); + } + + // If no items are left move the focus to the selections + if (source.getItemCount() == 0) { + target.setFocus(true); + } else { + source.setFocus(true); + } + + return movedItems; + } + + @Override + public void onClick(ClickEvent event) { + super.onClick(event); + if (event.getSource() == add) { + addItem(); + + } else if (event.getSource() == remove) { + removeItem(); + + } else if (event.getSource() == options) { + // unselect all in other list, to avoid mistakes (i.e wrong button) + final int c = selections.getItemCount(); + for (int i = 0; i < c; i++) { + selections.setItemSelected(i, false); + } + } else if (event.getSource() == selections) { + // unselect all in other list, to avoid mistakes (i.e wrong button) + final int c = options.getItemCount(); + for (int i = 0; i < c; i++) { + options.setItemSelected(i, false); + } + } + } + + void clearInternalHeights() { + selections.setHeight(""); + options.setHeight(""); + } + + void setInternalHeights() { + int captionHeight = Util.getRequiredHeight(captionWrapper); + int totalHeight = getOffsetHeight(); + + String selectHeight = (totalHeight - captionHeight) + "px"; + + selections.setHeight(selectHeight); + options.setHeight(selectHeight); + + } + + void clearInternalWidths() { + int cols = -1; + if (getColumns() > 0) { + cols = getColumns(); + } else { + cols = DEFAULT_COLUMN_COUNT; + } + + if (cols >= 0) { + String colWidth = cols + "em"; + String containerWidth = (2 * cols + 4) + "em"; + // Caption wrapper width == optionsSelect + buttons + + // selectionsSelect + String captionWrapperWidth = (2 * cols + 4 - 0.5) + "em"; + + options.setWidth(colWidth); + if (optionsCaption != null) { + optionsCaption.setWidth(colWidth); + } + selections.setWidth(colWidth); + if (selectionsCaption != null) { + selectionsCaption.setWidth(colWidth); + } + buttons.setWidth("3.5em"); + optionsContainer.setWidth(containerWidth); + captionWrapper.setWidth(captionWrapperWidth); + } + } + + void setInternalWidths() { + DOM.setStyleAttribute(getElement(), "position", "relative"); + int bordersAndPaddings = Util.measureHorizontalPaddingAndBorder( + buttons.getElement(), 0); + + int buttonWidth = Util.getRequiredWidth(buttons); + int totalWidth = getOffsetWidth(); + + int spaceForSelect = (totalWidth - buttonWidth - bordersAndPaddings) / 2; + + options.setWidth(spaceForSelect + "px"); + if (optionsCaption != null) { + optionsCaption.setWidth(spaceForSelect + "px"); + } + + selections.setWidth(spaceForSelect + "px"); + if (selectionsCaption != null) { + selectionsCaption.setWidth(spaceForSelect + "px"); + } + captionWrapper.setWidth("100%"); + } + + @Override + protected void setTabIndex(int tabIndex) { + options.setTabIndex(tabIndex); + selections.setTabIndex(tabIndex); + add.setTabIndex(tabIndex); + remove.setTabIndex(tabIndex); + } + + public void focus() { + options.setFocus(true); + } + + /** + * Get the key that selects an item in the table. By default it is the Enter + * key but by overriding this you can change the key to whatever you want. + * + * @return + */ + protected int getNavigationSelectKey() { + return KeyCodes.KEY_ENTER; + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt + * .event.dom.client.KeyDownEvent) + */ + public void onKeyDown(KeyDownEvent event) { + int keycode = event.getNativeKeyCode(); + + // Catch tab and move between select:s + if (keycode == KeyCodes.KEY_TAB && event.getSource() == options) { + // Prevent default behavior + event.preventDefault(); + + // Remove current selections + for (int i = 0; i < options.getItemCount(); i++) { + options.setItemSelected(i, false); + } + + // Focus selections + selections.setFocus(true); + } + + if (keycode == KeyCodes.KEY_TAB && event.isShiftKeyDown() + && event.getSource() == selections) { + // Prevent default behavior + event.preventDefault(); + + // Remove current selections + for (int i = 0; i < selections.getItemCount(); i++) { + selections.setItemSelected(i, false); + } + + // Focus options + options.setFocus(true); + } + + if (keycode == getNavigationSelectKey()) { + // Prevent default behavior + event.preventDefault(); + + // Decide which select the selection was made in + if (event.getSource() == options) { + // Prevents the selection to become a single selection when + // using Enter key + // as the selection key (default) + options.setFocus(false); + + addItem(); + + } else if (event.getSource() == selections) { + // Prevents the selection to become a single selection when + // using Enter key + // as the selection key (default) + selections.setFocus(false); + + removeItem(); + } + } + + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.MouseDownHandler#onMouseDown(com.google + * .gwt.event.dom.client.MouseDownEvent) + */ + public void onMouseDown(MouseDownEvent event) { + // Ensure that items are deselected when selecting + // from a different source. See #3699 for details. + if (event.getSource() == options) { + for (int i = 0; i < selections.getItemCount(); i++) { + selections.setItemSelected(i, false); + } + } else if (event.getSource() == selections) { + for (int i = 0; i < options.getItemCount(); i++) { + options.setItemSelected(i, false); + } + } + + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.DoubleClickHandler#onDoubleClick(com. + * google.gwt.event.dom.client.DoubleClickEvent) + */ + public void onDoubleClick(DoubleClickEvent event) { + if (event.getSource() == options) { + addItem(); + options.setSelectedIndex(-1); + options.setFocus(false); + } else if (event.getSource() == selections) { + removeItem(); + selections.setSelectedIndex(-1); + selections.setFocus(false); + } + + } + + private static final String SUBPART_OPTION_SELECT = "leftSelect"; + private static final String SUBPART_OPTION_SELECT_ITEM = SUBPART_OPTION_SELECT + + "-item"; + private static final String SUBPART_SELECTION_SELECT = "rightSelect"; + private static final String SUBPART_SELECTION_SELECT_ITEM = SUBPART_SELECTION_SELECT + + "-item"; + private static final String SUBPART_LEFT_CAPTION = "leftCaption"; + private static final String SUBPART_RIGHT_CAPTION = "rightCaption"; + private static final String SUBPART_ADD_BUTTON = "add"; + private static final String SUBPART_REMOVE_BUTTON = "remove"; + + public Element getSubPartElement(String subPart) { + if (SUBPART_OPTION_SELECT.equals(subPart)) { + return options.getElement(); + } else if (subPart.startsWith(SUBPART_OPTION_SELECT_ITEM)) { + String idx = subPart.substring(SUBPART_OPTION_SELECT_ITEM.length()); + return (Element) options.getElement().getChild( + Integer.parseInt(idx)); + } else if (SUBPART_SELECTION_SELECT.equals(subPart)) { + return selections.getElement(); + } else if (subPart.startsWith(SUBPART_SELECTION_SELECT_ITEM)) { + String idx = subPart.substring(SUBPART_SELECTION_SELECT_ITEM + .length()); + return (Element) selections.getElement().getChild( + Integer.parseInt(idx)); + } else if (optionsCaption != null + && SUBPART_LEFT_CAPTION.equals(subPart)) { + return optionsCaption.getElement(); + } else if (selectionsCaption != null + && SUBPART_RIGHT_CAPTION.equals(subPart)) { + return selectionsCaption.getElement(); + } else if (SUBPART_ADD_BUTTON.equals(subPart)) { + return add.getElement(); + } else if (SUBPART_REMOVE_BUTTON.equals(subPart)) { + return remove.getElement(); + } + + return null; + } + + public String getSubPartName(Element subElement) { + if (optionsCaption != null + && optionsCaption.getElement().isOrHasChild(subElement)) { + return SUBPART_LEFT_CAPTION; + } else if (selectionsCaption != null + && selectionsCaption.getElement().isOrHasChild(subElement)) { + return SUBPART_RIGHT_CAPTION; + } else if (options.getElement().isOrHasChild(subElement)) { + if (options.getElement() == subElement) { + return SUBPART_OPTION_SELECT; + } else { + int idx = Util.getChildElementIndex(subElement); + return SUBPART_OPTION_SELECT_ITEM + idx; + } + } else if (selections.getElement().isOrHasChild(subElement)) { + if (selections.getElement() == subElement) { + return SUBPART_SELECTION_SELECT; + } else { + int idx = Util.getChildElementIndex(subElement); + return SUBPART_SELECTION_SELECT_ITEM + idx; + } + } else if (add.getElement().isOrHasChild(subElement)) { + return SUBPART_ADD_BUTTON; + } else if (remove.getElement().isOrHasChild(subElement)) { + return SUBPART_REMOVE_BUTTON; + } + + return null; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/upload/UploadConnector.java b/src/com/vaadin/terminal/gwt/client/ui/upload/UploadConnector.java new file mode 100644 index 0000000000..c8b9fae07a --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/upload/UploadConnector.java @@ -0,0 +1,67 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.upload; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; +import com.vaadin.terminal.gwt.client.ui.Component; +import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; +import com.vaadin.ui.Upload; + +@Component(value = Upload.class, loadStyle = LoadStyle.LAZY) +public class UploadConnector extends AbstractComponentConnector implements + Paintable { + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + if (uidl.hasAttribute("notStarted")) { + getWidget().t.schedule(400); + return; + } + if (uidl.hasAttribute("forceSubmit")) { + getWidget().submit(); + return; + } + getWidget().setImmediate(getState().isImmediate()); + getWidget().client = client; + getWidget().paintableId = uidl.getId(); + getWidget().nextUploadId = uidl.getIntAttribute("nextid"); + final String action = client.translateVaadinUri(uidl + .getStringVariable("action")); + getWidget().element.setAction(action); + if (uidl.hasAttribute("buttoncaption")) { + getWidget().submitButton.setText(uidl + .getStringAttribute("buttoncaption")); + getWidget().submitButton.setVisible(true); + } else { + getWidget().submitButton.setVisible(false); + } + getWidget().fu.setName(getWidget().paintableId + "_file"); + + if (!isEnabled() || isReadOnly()) { + getWidget().disableUpload(); + } else if (!uidl.getBooleanAttribute("state")) { + // Enable the button only if an upload is not in progress + getWidget().enableUpload(); + getWidget().ensureTargetFrame(); + } + } + + @Override + protected Widget createWidget() { + return GWT.create(VUpload.class); + } + + @Override + public VUpload getWidget() { + return (VUpload) super.getWidget(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategy.java b/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategy.java new file mode 100644 index 0000000000..0015ce933f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategy.java @@ -0,0 +1,26 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.upload; + + +public class UploadIFrameOnloadStrategy { + + native void hookEvents(com.google.gwt.dom.client.Element iframe, + VUpload upload) + /*-{ + iframe.onload = function() { + upload.@com.vaadin.terminal.gwt.client.ui.VUpload::onSubmitComplete()(); + }; + }-*/; + + /** + * @param iframe + * the iframe whose onLoad event is to be cleaned + */ + native void unHookEvents(com.google.gwt.dom.client.Element iframe) + /*-{ + iframe.onload = null; + }-*/; + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategyIE.java b/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategyIE.java new file mode 100644 index 0000000000..27ea401be2 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/upload/UploadIFrameOnloadStrategyIE.java @@ -0,0 +1,29 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui.upload; + +import com.google.gwt.dom.client.Element; + +/** + * IE does not have onload, detect onload via readystatechange + * + */ +public class UploadIFrameOnloadStrategyIE extends UploadIFrameOnloadStrategy { + @Override + native void hookEvents(Element iframe, VUpload upload) + /*-{ + iframe.onreadystatechange = function() { + if (iframe.readyState == 'complete') { + upload.@com.vaadin.terminal.gwt.client.ui.VUpload::onSubmitComplete()(); + } + }; + }-*/; + + @Override + native void unHookEvents(Element iframe) + /*-{ + iframe.onreadystatechange = null; + }-*/; + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/upload/VUpload.java b/src/com/vaadin/terminal/gwt/client/ui/upload/VUpload.java new file mode 100644 index 0000000000..4fe53fb89c --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/upload/VUpload.java @@ -0,0 +1,316 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui.upload; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.FormElement; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.FileUpload; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FormPanel; +import com.google.gwt.user.client.ui.Hidden; +import com.google.gwt.user.client.ui.Panel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.BrowserInfo; +import com.vaadin.terminal.gwt.client.VConsole; +import com.vaadin.terminal.gwt.client.VTooltip; +import com.vaadin.terminal.gwt.client.ui.button.VButton; + +/** + * + * Note, we are not using GWT FormPanel as we want to listen submitcomplete + * events even though the upload component is already detached. + * + */ +public class VUpload extends SimplePanel { + + private final class MyFileUpload extends FileUpload { + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (event.getTypeInt() == Event.ONCHANGE) { + if (immediate && fu.getFilename() != null + && !"".equals(fu.getFilename())) { + submit(); + } + } else if (BrowserInfo.get().isIE() + && event.getTypeInt() == Event.ONFOCUS) { + // IE and user has clicked on hidden textarea part of upload + // field. Manually open file selector, other browsers do it by + // default. + fireNativeClick(fu.getElement()); + // also remove focus to enable hack if user presses cancel + // button + fireNativeBlur(fu.getElement()); + } + } + } + + public static final String CLASSNAME = "v-upload"; + + /** + * FileUpload component that opens native OS dialog to select file. + */ + FileUpload fu = new MyFileUpload(); + + Panel panel = new FlowPanel(); + + UploadIFrameOnloadStrategy onloadstrategy = GWT + .create(UploadIFrameOnloadStrategy.class); + + ApplicationConnection client; + + protected String paintableId; + + /** + * Button that initiates uploading + */ + protected final VButton submitButton; + + /** + * When expecting big files, programmer may initiate some UI changes when + * uploading the file starts. Bit after submitting file we'll visit the + * server to check possible changes. + */ + protected Timer t; + + /** + * some browsers tries to send form twice if submit is called in button + * click handler, some don't submit at all without it, so we need to track + * if form is already being submitted + */ + private boolean submitted = false; + + private boolean enabled = true; + + private boolean immediate; + + private Hidden maxfilesize = new Hidden(); + + protected FormElement element; + + private com.google.gwt.dom.client.Element synthesizedFrame; + + protected int nextUploadId; + + public VUpload() { + super(com.google.gwt.dom.client.Document.get().createFormElement()); + + element = getElement().cast(); + setEncoding(getElement(), FormPanel.ENCODING_MULTIPART); + element.setMethod(FormPanel.METHOD_POST); + + setWidget(panel); + panel.add(maxfilesize); + panel.add(fu); + submitButton = new VButton(); + submitButton.addClickHandler(new ClickHandler() { + public void onClick(ClickEvent event) { + if (immediate) { + // fire click on upload (eg. focused button and hit space) + fireNativeClick(fu.getElement()); + } else { + submit(); + } + } + }); + panel.add(submitButton); + + setStyleName(CLASSNAME); + + sinkEvents(VTooltip.TOOLTIP_EVENTS); + } + + @Override + public void onBrowserEvent(Event event) { + if ((event.getTypeInt() & VTooltip.TOOLTIP_EVENTS) > 0) { + client.handleTooltipEvent(event, this); + } + super.onBrowserEvent(event); + } + + private static native void setEncoding(Element form, String encoding) + /*-{ + form.enctype = encoding; + }-*/; + + protected void setImmediate(boolean booleanAttribute) { + if (immediate != booleanAttribute) { + immediate = booleanAttribute; + if (immediate) { + fu.sinkEvents(Event.ONCHANGE); + fu.sinkEvents(Event.ONFOCUS); + } + } + setStyleName(getElement(), CLASSNAME + "-immediate", immediate); + } + + private static native void fireNativeClick(Element element) + /*-{ + element.click(); + }-*/; + + private static native void fireNativeBlur(Element element) + /*-{ + element.blur(); + }-*/; + + protected void disableUpload() { + submitButton.setEnabled(false); + if (!submitted) { + // Cannot disable the fileupload while submitting or the file won't + // be submitted at all + fu.getElement().setPropertyBoolean("disabled", true); + } + enabled = false; + } + + protected void enableUpload() { + submitButton.setEnabled(true); + fu.getElement().setPropertyBoolean("disabled", false); + enabled = true; + if (submitted) { + /* + * An old request is still in progress (most likely cancelled), + * ditching that target frame to make it possible to send a new + * file. A new target frame is created later." + */ + cleanTargetFrame(); + submitted = false; + } + } + + /** + * Re-creates file input field and populates panel. This is needed as we + * want to clear existing values from our current file input field. + */ + private void rebuildPanel() { + panel.remove(submitButton); + panel.remove(fu); + fu = new MyFileUpload(); + fu.setName(paintableId + "_file"); + fu.getElement().setPropertyBoolean("disabled", !enabled); + panel.add(fu); + panel.add(submitButton); + if (immediate) { + fu.sinkEvents(Event.ONCHANGE); + } + } + + /** + * Called by JSNI (hooked via {@link #onloadstrategy}) + */ + private void onSubmitComplete() { + /* Needs to be run dereferred to avoid various browser issues. */ + Scheduler.get().scheduleDeferred(new Command() { + public void execute() { + if (submitted) { + if (client != null) { + if (t != null) { + t.cancel(); + } + VConsole.log("VUpload:Submit complete"); + client.sendPendingVariableChanges(); + } + + rebuildPanel(); + + submitted = false; + enableUpload(); + if (!isAttached()) { + /* + * Upload is complete when upload is already abandoned. + */ + cleanTargetFrame(); + } + } + } + }); + } + + protected void submit() { + if (fu.getFilename().length() == 0 || submitted || !enabled) { + VConsole.log("Submit cancelled (disabled, no file or already submitted)"); + return; + } + // flush possibly pending variable changes, so they will be handled + // before upload + client.sendPendingVariableChanges(); + + element.submit(); + submitted = true; + VConsole.log("Submitted form"); + + disableUpload(); + + /* + * Visit server a moment after upload has started to see possible + * changes from UploadStarted event. Will be cleared on complete. + */ + t = new Timer() { + @Override + public void run() { + VConsole.log("Visiting server to see if upload started event changed UI."); + client.updateVariable(paintableId, "pollForStart", + nextUploadId, true); + } + }; + t.schedule(800); + } + + @Override + protected void onAttach() { + super.onAttach(); + if (client != null) { + ensureTargetFrame(); + } + } + + protected void ensureTargetFrame() { + if (synthesizedFrame == null) { + // Attach a hidden IFrame to the form. This is the target iframe to + // which the form will be submitted. We have to create the iframe + // using innerHTML, because setting an iframe's 'name' property + // dynamically doesn't work on most browsers. + DivElement dummy = Document.get().createDivElement(); + dummy.setInnerHTML("