diff options
author | Teemu Suo-Anttila <teemusa@vaadin.com> | 2014-08-19 09:40:20 +0300 |
---|---|---|
committer | Teemu Suo-Anttila <teemusa@vaadin.com> | 2014-08-19 09:42:27 +0300 |
commit | c1a873bc9e47b98c58b298aa6935ab0853e6963f (patch) | |
tree | 3e8b209c54961473161335159bc98f9316d28a09 /client | |
parent | 2caaea2df9d558f0ce67daa3b0641cb832538506 (diff) | |
parent | 4dcace7123b605115efcbb395a320d460eed9c0e (diff) | |
download | vaadin-framework-c1a873bc9e47b98c58b298aa6935ab0853e6963f.tar.gz vaadin-framework-c1a873bc9e47b98c58b298aa6935ab0853e6963f.zip |
Merge remote-tracking branch 'origin/master' into grid
Change-Id: Iac6947bc82bfbbb6856a924e7d538d195cfb405e
Diffstat (limited to 'client')
9 files changed, 723 insertions, 170 deletions
diff --git a/client/src/com/vaadin/DefaultWidgetSet.gwt.xml b/client/src/com/vaadin/DefaultWidgetSet.gwt.xml index 2719493853..8512d547e3 100755 --- a/client/src/com/vaadin/DefaultWidgetSet.gwt.xml +++ b/client/src/com/vaadin/DefaultWidgetSet.gwt.xml @@ -14,4 +14,5 @@ file. Speeds up compilation and does not make the Javascript significantly larger. --> <collapse-all-properties /> + </module> diff --git a/client/src/com/vaadin/Vaadin.gwt.xml b/client/src/com/vaadin/Vaadin.gwt.xml index 711729f64f..aad0563975 100644 --- a/client/src/com/vaadin/Vaadin.gwt.xml +++ b/client/src/com/vaadin/Vaadin.gwt.xml @@ -68,7 +68,7 @@ <property-provider name="modernie"><![CDATA[ { var ua = $wnd.navigator.userAgent; - if (ua.indexOf('IE') == -1 && ua.indexOf('Trident') != -1) { return 'yes'; } + if (ua.indexOf('MSIE') == -1 && ua.indexOf('Trident') != -1) { return 'yes'; } return 'none'; } ]]></property-provider> diff --git a/client/src/com/vaadin/client/Util.java b/client/src/com/vaadin/client/Util.java index 49c862006b..e37b044826 100644 --- a/client/src/com/vaadin/client/Util.java +++ b/client/src/com/vaadin/client/Util.java @@ -1420,6 +1420,37 @@ public class Util { } /** + * Sets the selection range of an input element. + * + * We need this JSNI function to set selection range so that we can use the + * optional direction attribute to set the anchor to the end and the focus + * to the start. This makes Firefox work the same way as other browsers + * (#13477) + * + * @param elem + * the html input element. + * @param pos + * the index of the first selected character. + * @param length + * the selection length. + * @param direction + * a string indicating the direction in which the selection was + * performed. This may be "forward" or "backward", or "none" if + * the direction is unknown or irrelevant. + * + * @since + */ + public native static void setSelectionRange(Element elem, int pos, + int length, String direction) + /*-{ + try { + elem.setSelectionRange(pos, pos + length, direction); + } catch (e) { + // Firefox throws exception if TextBox is not visible, even if attached + } + }-*/; + + /** * Wrap a css size value and its unit and translate back and forth to the * string representation.<br/> * Eg. 50%, 123px, ... @@ -1571,7 +1602,8 @@ public class Util { * @return true if the two sizes are equals, otherwise false. */ public static boolean equals(String cssSize1, String cssSize2) { - return CssSize.fromString(cssSize1).equals(CssSize.fromString(cssSize2)); + return CssSize.fromString(cssSize1).equals( + CssSize.fromString(cssSize2)); } } diff --git a/client/src/com/vaadin/client/ui/VDragAndDropWrapper.java b/client/src/com/vaadin/client/ui/VDragAndDropWrapper.java index 4010ffd542..7bf341a387 100644 --- a/client/src/com/vaadin/client/ui/VDragAndDropWrapper.java +++ b/client/src/com/vaadin/client/ui/VDragAndDropWrapper.java @@ -114,7 +114,8 @@ public class VDragAndDropWrapper extends VCustomComponent implements * @return true if the event was handled as a drag start event */ private boolean startDrag(NativeEvent event) { - if (dragStartMode == WRAPPER || dragStartMode == COMPONENT) { + if (dragStartMode == WRAPPER || dragStartMode == COMPONENT + || dragStartMode == COMPONENT_OTHER) { VTransferable transferable = new VTransferable(); transferable.setDragSource(getConnector()); @@ -130,6 +131,10 @@ public class VDragAndDropWrapper extends VCustomComponent implements if (dragStartMode == WRAPPER) { dragEvent.createDragImage(getElement(), true); + } else if (dragStartMode == COMPONENT_OTHER + && getDragImageWidget() != null) { + dragEvent.createDragImage(getDragImageWidget().getElement(), + true); } else { dragEvent.createDragImage(widget.getElement(), true); } @@ -142,6 +147,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements protected final static int COMPONENT = 1; protected final static int WRAPPER = 2; protected final static int HTML5 = 3; + protected final static int COMPONENT_OTHER = 4; /** For internal use only. May be removed or replaced in the future. */ public int dragStartMode; @@ -458,6 +464,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements * Flag used by html5 dd */ private boolean currentlyValid; + private Widget dragImageWidget; private static final String OVER_STYLE = "v-ddwrapper-over"; @@ -661,4 +668,22 @@ public class VDragAndDropWrapper extends VCustomComponent implements notifySizePotentiallyChanged(); } + /** + * Set the widget that will be used as the drag image when using + * DragStartMode {@link COMPONENT_OTHER} . + * + * @param widget + */ + public void setDragAndDropWidget(Widget widget) { + dragImageWidget = widget; + } + + /** + * @return the widget used as drag image. Returns <code>null</code> if no + * widget is set. + */ + public Widget getDragImageWidget() { + return dragImageWidget; + } + } diff --git a/client/src/com/vaadin/client/ui/VFilterSelect.java b/client/src/com/vaadin/client/ui/VFilterSelect.java index 6ba0785acc..230c9e6639 100644 --- a/client/src/com/vaadin/client/ui/VFilterSelect.java +++ b/client/src/com/vaadin/client/ui/VFilterSelect.java @@ -254,7 +254,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** * Shows the popup where the user can see the filtered options - * + * * @param currentSuggestions * The filtered suggestions * @param currentPage @@ -345,7 +345,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** * Should the next page button be visible to the user? - * + * * @param active */ private void setNextButtonActive(boolean active) { @@ -365,7 +365,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** * Should the previous page button be visible to the user - * + * * @param active */ private void setPrevButtonActive(boolean active) { @@ -554,7 +554,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * 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? */ @@ -679,7 +679,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** * Was the popup just closed? - * + * * @return true if popup was just closed */ public boolean isJustClosed() { @@ -708,7 +708,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** * Updates style names in suggestion popup to help theme building. - * + * * @param uidl * UIDL for the whole combo box * @param componentState @@ -799,7 +799,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** * Sets the suggestions rendered in the menu - * + * * @param suggestions * The suggestions to be rendered in the menu */ @@ -1057,11 +1057,33 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, @Override public void setSelectionRange(int pos, int length) { if (textInputEnabled) { - super.setSelectionRange(pos, length); + /* + * set selection range with a backwards direction: anchor at the + * back, focus at the front. This means that items that are too + * long to display will display from the start and not the end + * even on Firefox. + * + * We need the JSNI function to set selection range so that we + * can use the optional direction attribute to set the anchor to + * the end and the focus to the start. This makes Firefox work + * the same way as other browsers (#13477) + */ + Util.setSelectionRange(getElement(), pos, length, "backward"); + } else { - super.setSelectionRange(0, getValue().length()); + /* + * Setting the selectionrange for an uneditable textbox leads to + * unwanted behaviour when the width of the textbox is narrower + * than the width of the entry: the end of the entry is shown + * instead of the beginning. (see #13477) + * + * To avoid this, we set the caret to the beginning of the line. + */ + + super.setSelectionRange(0, 0); } } + } @Deprecated @@ -1456,9 +1478,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, private void setText(final String text) { /** - * To leave caret in the beginning of the line. - * SetSelectionRange wouldn't work on IE - * (see #13477) + * To leave caret in the beginning of the line. SetSelectionRange + * wouldn't work on IE (see #13477) */ Direction previousDirection = tb.getDirection(); tb.setDirection(Direction.RTL); @@ -1763,10 +1784,14 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, if (!allowNewItem) { /* * New items are not allowed: If there is only one - * suggestion, select that. Otherwise do nothing. + * suggestion, select that. If there is more than one + * suggestion Enter key should work as Escape key. Otherwise + * do nothing. */ if (currentSuggestions.size() == 1) { onSuggestionSelected(currentSuggestions.get(0)); + } else if (currentSuggestions.size() > 1) { + reset(); } } else { // Handle addition of new items. diff --git a/client/src/com/vaadin/client/ui/VTabsheet.java b/client/src/com/vaadin/client/ui/VTabsheet.java index 15d9c83c49..aff1848647 100644 --- a/client/src/com/vaadin/client/ui/VTabsheet.java +++ b/client/src/com/vaadin/client/ui/VTabsheet.java @@ -1,12 +1,12 @@ /* * Copyright 2000-2014 Vaadin Ltd. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -42,9 +42,12 @@ 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.HasMouseDownHandlers; 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.regexp.shared.MatchResult; import com.google.gwt.regexp.shared.RegExp; @@ -74,7 +77,9 @@ import com.vaadin.shared.ui.tabsheet.TabsheetServerRpc; import com.vaadin.shared.ui.tabsheet.TabsheetState; public class VTabsheet extends VTabsheetBase implements Focusable, - FocusHandler, BlurHandler, KeyDownHandler, SubPartAware { + SubPartAware, + // TODO: These listeners are due to be removed in 7.3 + FocusHandler, BlurHandler, KeyDownHandler { private static class VCloseEvent { private Tab tab; @@ -95,10 +100,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * Representation of a single "tab" shown in the TabBar - * + * */ public static class Tab extends SimplePanel implements HasFocusHandlers, - HasBlurHandlers, HasKeyDownHandlers { + HasBlurHandlers, HasMouseDownHandlers, HasKeyDownHandlers { private static final String TD_CLASSNAME = CLASSNAME + "-tabitemcell"; private static final String TD_FIRST_CLASSNAME = TD_CLASSNAME + "-first"; @@ -152,10 +157,6 @@ public class VTabsheet extends VTabsheetBase implements Focusable, Roles.getTabRole().setAriaLabelledbyProperty(getElement(), Id.of(tabCaption.getElement())); - - addFocusHandler(getTabsheet()); - addBlurHandler(getTabsheet()); - addKeyDownHandler(getTabsheet()); } public boolean isHiddenOnServer() { @@ -197,7 +198,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * Toggles the style names for the Tab - * + * * @param selected * true if the Tab is selected * @param first @@ -282,6 +283,11 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } @Override + public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) { + return addDomHandler(handler, MouseDownEvent.getType()); + } + + @Override public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) { return addDomHandler(handler, KeyDownEvent.getType()); } @@ -420,8 +426,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } - static class TabBar extends ComplexPanel implements ClickHandler, - VCloseHandler { + static class TabBar extends ComplexPanel implements VCloseHandler { private final Element tr = DOM.createTR(); @@ -443,6 +448,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, setStyleName(spacerTd, CLASSNAME + "-spacertd"); DOM.appendChild(tr, spacerTd); DOM.appendChild(spacerTd, DOM.createDiv()); + setElement(el); } @@ -460,10 +466,20 @@ public class VTabsheet extends VTabsheetBase implements Focusable, return DOM.asOld(tr); } + /** + * Gets the number of tabs from the tab bar. + * + * @return the number of tabs from the tab bar. + */ public int getTabCount() { return getWidgetCount(); } + /** + * Adds a tab to the tab bar. + * + * @return the added tab. + */ public Tab addTab() { Tab t = new Tab(this); int tabIndex = getTabCount(); @@ -476,31 +492,18 @@ public class VTabsheet extends VTabsheetBase implements Focusable, t.setStyleNames(false, true); } - t.addClickHandler(this); + getTabsheet().selectionHandler.registerTab(t); + t.setCloseHandler(this); return t; } - @Override - public void onClick(ClickEvent event) { - TabCaption caption = (TabCaption) event.getSource(); - Element targetElement = event.getNativeEvent().getEventTarget() - .cast(); - // the tab should not be focused if the close button was clicked - if (targetElement == caption.getCloseButton()) { - return; - } - - int index = getWidgetIndex(caption.getParent()); - - navigateTab(getTabsheet().focusedTabIndex, index); - getTabsheet().focusedTabIndex = index; - getTabsheet().focusedTab = getTab(index); - getTabsheet().focus(); - getTabsheet().loadTabSheet(index); - } - + /** + * Gets the tab sheet instance where the tab bar is attached to. + * + * @return the tab sheet instance where the tab bar is attached to. + */ public VTabsheet getTabsheet() { return tabsheet; } @@ -538,7 +541,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, getTab(tabsheet.activeTabIndex).recalculateCaptionWidth(); } - public void navigateTab(int fromIndex, int toIndex) { + public Tab navigateTab(int fromIndex, int toIndex) { Tab newNavigated = getTab(toIndex); if (newNavigated == null) { throw new IllegalArgumentException( @@ -553,6 +556,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable, oldNavigated.setStyleNames(oldNavigated.equals(selected), isFirstVisibleTab(fromIndex), false); } + + return newNavigated; } public void removeTab(int i) { @@ -580,7 +585,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * Returns the index of the first visible tab - * + * * @return */ private int getFirstVisibleTab() { @@ -589,7 +594,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * Find the next visible tab. Returns -1 if none is found. - * + * * @param i * @return */ @@ -608,7 +613,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * Find the previous visible tab. Returns -1 if none is found. - * + * * @param i * @return */ @@ -663,7 +668,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** For internal use only. May be removed or replaced in the future. */ // tabbar and 'scroller' container public final Element tabs; - Tab focusedTab; + /** * The tabindex property (position in the browser's focus cycle.) Named like * this to avoid confusion with activeTabIndex. @@ -697,9 +702,6 @@ public class VTabsheet extends VTabsheetBase implements Focusable, private String currentStyle; - /** For internal use only. May be removed or replaced in the future. */ - private int focusedTabIndex = 0; - /** * @return Whether the tab could be selected or not. */ @@ -720,11 +722,13 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * Load the content of a tab of the provided index. - * + * * @param index * of the tab to load + * + * @return true if the specified sheet gets loaded, otherwise false. */ - public void loadTabSheet(int tabIndex) { + public boolean loadTabSheet(int tabIndex) { if (activeTabIndex != tabIndex && canSelectTab(tabIndex)) { tb.selectTab(tabIndex); @@ -741,12 +745,16 @@ public class VTabsheet extends VTabsheetBase implements Focusable, waitingForResponse = true; tb.getTab(tabIndex).focus(); // move keyboard focus to active tab + + return true; } + + return false; } /** * Returns the currently displayed widget in the tab panel. - * + * * @since 7.2 * @return currently displayed content widget */ @@ -756,7 +764,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * Returns the client to server RPC proxy for the tabsheet. - * + * * @since 7.2 * @return RPC proxy */ @@ -766,10 +774,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * For internal use only. - * + * * Avoid using this method directly and use appropriate superclass methods * where applicable. - * + * * @deprecated since 7.2 - use more specific methods instead (getRpcProxy(), * getConnectorForWidget(Widget) etc.) * @return ApplicationConnection @@ -799,9 +807,6 @@ public class VTabsheet extends VTabsheetBase implements Focusable, public VTabsheet() { super(CLASSNAME); - addHandler(this, FocusEvent.getType()); - addHandler(this, BlurEvent.getType()); - // Tab scrolling getElement().getStyle().setOverflow(Overflow.HIDDEN); tabs = DOM.createDiv(); @@ -812,18 +817,21 @@ public class VTabsheet extends VTabsheetBase implements Focusable, Roles.getTablistRole().setAriaHiddenState(scroller, true); DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME); + scrollerPrev = DOM.createButton(); scrollerPrev.setTabIndex(-1); DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME + "Prev"); Roles.getTablistRole().setAriaHiddenState(scrollerPrev, true); - DOM.sinkEvents(scrollerPrev, Event.ONCLICK); + DOM.sinkEvents(scrollerPrev, Event.ONCLICK | Event.ONMOUSEDOWN); + scrollerNext = DOM.createButton(); scrollerNext.setTabIndex(-1); DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME + "Next"); Roles.getTablistRole().setAriaHiddenState(scrollerNext, true); - DOM.sinkEvents(scrollerNext, Event.ONCLICK); + DOM.sinkEvents(scrollerNext, Event.ONCLICK | Event.ONMOUSEDOWN); + DOM.appendChild(getElement(), tabs); // Tabs @@ -856,35 +864,68 @@ public class VTabsheet extends VTabsheetBase implements Focusable, @Override public void onBrowserEvent(Event event) { + com.google.gwt.dom.client.Element eventTarget = DOM + .eventGetTarget(event); + if (event.getTypeInt() == Event.ONCLICK) { + // Tab scrolling - if (isScrolledTabs() && DOM.eventGetTarget(event) == scrollerPrev) { - int newFirstIndex = tb.scrollLeft(scrollerIndex); - if (newFirstIndex != -1) { - scrollerIndex = newFirstIndex; - updateTabScroller(); - } - event.stopPropagation(); - return; - } else if (isClippedTabs() - && DOM.eventGetTarget(event) == scrollerNext) { - int newFirstIndex = tb.scrollRight(scrollerIndex); + if (eventTarget == scrollerPrev || eventTarget == scrollerNext) { + scrollAccordingToScrollTarget(eventTarget); - if (newFirstIndex != -1) { - scrollerIndex = newFirstIndex; - updateTabScroller(); - } event.stopPropagation(); + } + + } else if (event.getTypeInt() == Event.ONMOUSEDOWN) { + + if (eventTarget == scrollerPrev || eventTarget == scrollerNext) { + // In case the focus was previously on a Tab, we need to cancel + // the upcoming blur on the Tab which will follow this mouse + // down event. + focusBlurManager.cancelNextBlurSchedule(); + return; } } + super.onBrowserEvent(event); } + /* + * Scroll the tab bar according to the last scrollTarget (the scroll button + * pressed). + */ + private void scrollAccordingToScrollTarget( + com.google.gwt.dom.client.Element scrollTarget) { + if (scrollTarget == null) { + return; + } + + int newFirstIndex = -1; + + // Scroll left. + if (isScrolledTabs() && scrollTarget == scrollerPrev) { + newFirstIndex = tb.scrollLeft(scrollerIndex); + + // Scroll right. + } else if (isClippedTabs() && scrollTarget == scrollerNext) { + newFirstIndex = tb.scrollRight(scrollerIndex); + } + + if (newFirstIndex != -1) { + scrollerIndex = newFirstIndex; + updateTabScroller(); + } + + // For this to work well, make sure the method gets called only from + // user events. + selectionHandler.focusTabAtIndex(scrollerIndex); + } + /** * Checks if the tab with the selected index has been scrolled out of the * view (on the left side). - * + * * @param index * @return */ @@ -1016,7 +1057,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * Renders the widget content for a tab sheet. - * + * * @param newWidget */ public void renderContent(Widget newWidget) { @@ -1239,64 +1280,467 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } @Override - public void onBlur(BlurEvent event) { - getVTooltip().hideTooltip(); + public void focus() { + getActiveTab().focus(); + } - if (focusedTab != null && focusedTab == event.getSource()) { - focusedTab.removeAssistiveDescription(); - focusedTab = null; - if (connector.hasEventListener(EventId.BLUR)) { - connector.getRpcProxy(FocusAndBlurServerRpc.class).blur(); - } - } + public void blur() { + getActiveTab().blur(); + } + + /* + * Gets the active tab. + */ + private Tab getActiveTab() { + return tb.getTab(activeTabIndex); } @Override - public void onFocus(FocusEvent event) { - if (focusedTab == null && event.getSource() instanceof Tab) { - focusedTab = (Tab) event.getSource(); + public void setConnector(AbstractComponentConnector connector) { + super.setConnector(connector); + + focusBlurManager.connector = connector; + } + + /* + * The focus and blur manager instance. + */ + private FocusBlurManager focusBlurManager = new FocusBlurManager(); + + /* + * Generate the correct focus/blur events for the main TabSheet component + * (#14304). + * + * The TabSheet must fire one focus event when the user clicks on the tab + * bar (i.e. inner TabBar class) containing the Tabs or when the focus is + * provided to the TabSheet by any means. Also one blur event should be + * fired only when the user leaves the tab bar. After the user focus on the + * tab bar and before leaving it, no matter how many times he's pressing the + * Tabs or the scroll buttons, the TabSheet component should not fire any of + * those blur/focus events. + * + * The only focusable elements contained in the tab bar are the Tabs (see + * inner class Tab). The reason is the accessibility support. + * + * Having this in mind, the chosen solution path for our problem is to match + * a sequence of focus/blur events on the tabs, choose only the first focus + * and last blur events and pass only those further to the main component. + * Any consecutive blur/focus events on 2 Tabs must be ignored. + * + * Because in a blur event we don't know whether or not a focus will follow, + * we just defer a command initiated on the blur event to wait and see if + * any focus will appear. The command will be executed after the next focus, + * so if no focus was triggered in the mean while it'll submit the blur + * event to the main component, otherwise it'll do nothing, so the main + * component will not generate the blur.. + */ + private static class FocusBlurManager { + + // The real tab with focus on it. If the focus goes to another element + // in the page this will be null. + private Tab focusedTab; + + /* + * Gets the focused tab. + */ + private Tab getFocusedTab() { + return focusedTab; + } + + /* + * Sets the local field tracking the focused tab. + */ + private void setFocusedTab(Tab focusedTab) { + this.focusedTab = focusedTab; + } + + /* + * The ultimate focus/blur event dispatcher. + */ + private AbstractComponentConnector connector; + + /** + * Delegate method for the onFocus event occurring on Tab. + * + * @since + * @param newFocusTab + * the new focused tab. + * @see #onBlur(Tab) + */ + public void onFocus(Tab newFocusTab) { + if (connector.hasEventListener(EventId.FOCUS)) { - connector.getRpcProxy(FocusAndBlurServerRpc.class).focus(); + + // Send the focus event only first time when we focus on any + // tab. The focused tab will be reseted on the last blur. + if (focusedTab == null) { + connector.getRpcProxy(FocusAndBlurServerRpc.class).focus(); + } + } + + cancelLastBlurSchedule(); + + setFocusedTab(newFocusTab); + } + + /** + * Delegate method for the onBlur event occurring on Tab. + * + * @param blurSource + * the source of the blur. + * + * @see #onFocus(Tab) + */ + public void onBlur(Tab blurSource) { + if (focusedTab != null && focusedTab == blurSource) { + + if (connector.hasEventListener(EventId.BLUR)) { + scheduleBlur(focusedTab); + } + } + } + + /* + * The last blur command to be executed. + */ + private BlurCommand blurCommand; + + /* + * Execute the final blur command. + */ + private class BlurCommand implements Command { + + /* + * The blur source. + */ + private Tab blurSource; + + /** + * Create the blur command using the blur source. + * + * @param blurSource + * the source. + * @param focusedTabProvider + * provides the current focused tab. + */ + public BlurCommand(Tab blurSource) { + this.blurSource = blurSource; + } + + /** + * Stop the command from being executed. + * + * @since + */ + public void stopSchedule() { + blurSource = null; } - if (focusedTab.hasTooltip()) { - focusedTab.setAssistiveDescription(getVTooltip().getUniqueId()); - getVTooltip().showAssistive(focusedTab.getTooltipInfo()); + /** + * Schedule the command for a deferred execution. + * + * @since + */ + public void scheduleDeferred() { + Scheduler.get().scheduleDeferred(this); + } + + @Override + public void execute() { + + Tab focusedTab = getFocusedTab(); + + if (blurSource == null) { + return; + } + + // The focus didn't change since this blur triggered, so + // the new focused element is not a tab. + if (focusedTab == blurSource) { + + // We're certain there's no focus anymore. + focusedTab.removeAssistiveDescription(); + setFocusedTab(null); + + connector.getRpcProxy(FocusAndBlurServerRpc.class).blur(); + } + + // Call this to set it to null and be consistent. + cancelLastBlurSchedule(); } } + + /* + * Schedule a new blur event for a deferred execution. + */ + private void scheduleBlur(Tab blurSource) { + + if (nextBlurScheduleCancelled) { + + // This will set the stopNextBlurCommand back to false as well. + cancelLastBlurSchedule(); + + // Reset the status. + nextBlurScheduleCancelled = false; + return; + } + + cancelLastBlurSchedule(); + + blurCommand = new BlurCommand(blurSource); + blurCommand.scheduleDeferred(); + } + + /** + * Remove the last blur deferred command from execution. + */ + public void cancelLastBlurSchedule() { + if (blurCommand != null) { + blurCommand.stopSchedule(); + blurCommand = null; + } + + // We really want to make sure this flag gets reseted at any time + // when something interact with the blur manager and ther's no blur + // command scheduled (as we just canceled it). + nextBlurScheduleCancelled = false; + } + + /** + * Cancel the next scheduled execution. This method must be called only + * from an event occurring before the onBlur event. It's the case of IE + * which doesn't trigger the focus event, so we're using this approach + * to cancel the next blur event prior it's execution, calling the + * method from mouse down event. + */ + public void cancelNextBlurSchedule() { + + // Make sure there's still no other command to be executed. + cancelLastBlurSchedule(); + + nextBlurScheduleCancelled = true; + } + + /* + * Flag that the next deferred command won't get executed. This is + * useful in case of IE where the user focus event don't fire and we're + * using the mouse down event to track the focus. But the mouse down + * event triggers before the blur, so we need to cancel the deferred + * execution in advance. + */ + private boolean nextBlurScheduleCancelled = false; + } @Override - public void focus() { - tb.getTab(activeTabIndex).focus(); + public void onBlur(BlurEvent event) { + selectionHandler.onBlur(event); } - public void blur() { - tb.getTab(activeTabIndex).blur(); + @Override + public void onFocus(FocusEvent event) { + selectionHandler.onFocus(event); } @Override public void onKeyDown(KeyDownEvent event) { - if (event.getSource() instanceof Tab) { - int keycode = event.getNativeEvent().getKeyCode(); - - if (!event.isAnyModifierKeyDown()) { - if (keycode == getPreviousTabKey()) { - selectPreviousTab(); - event.stopPropagation(); - } else if (keycode == getNextTabKey()) { - selectNextTab(); - event.stopPropagation(); - } else if (keycode == getCloseTabKey()) { - Tab tab = tb.getTab(activeTabIndex); - if (tab.isClosable()) { - tab.onClose(); + selectionHandler.onKeyDown(event); + } + + /* + * The tabs selection handler instance. + */ + private final TabSelectionHandler selectionHandler = new TabSelectionHandler(); + + /* + * Handle the events for selecting the tabs. + */ + private class TabSelectionHandler implements FocusHandler, BlurHandler, + KeyDownHandler, ClickHandler, MouseDownHandler { + + /** For internal use only. May be removed or replaced in the future. */ + // The current visible focused index. + private int focusedTabIndex = 0; + + /** + * Register the tab to the selection handler. + * + * @param tab + * the tab to register. + */ + public void registerTab(Tab tab) { + + // TODO: change VTabsheet.this to this in 7.3 + tab.addBlurHandler(VTabsheet.this); + tab.addFocusHandler(VTabsheet.this); + tab.addKeyDownHandler(VTabsheet.this); + + tab.addClickHandler(this); + tab.addMouseDownHandler(this); + } + + @Override + public void onBlur(final BlurEvent event) { + + getVTooltip().hideTooltip(); + + Object blurSource = event.getSource(); + + if (blurSource instanceof Tab) { + focusBlurManager.onBlur((Tab) blurSource); + } + } + + @Override + public void onFocus(FocusEvent event) { + + if (event.getSource() instanceof Tab) { + Tab focusSource = (Tab) event.getSource(); + focusBlurManager.onFocus(focusSource); + + if (focusSource.hasTooltip()) { + focusSource.setAssistiveDescription(getVTooltip() + .getUniqueId()); + getVTooltip().showAssistive(focusSource.getTooltipInfo()); + } + + } + } + + @Override + public void onClick(ClickEvent event) { + + // IE doesn't trigger focus when click, so we need to make sure + // the previous blur deferred command will get killed. + focusBlurManager.cancelLastBlurSchedule(); + + TabCaption caption = (TabCaption) event.getSource(); + Element targetElement = event.getNativeEvent().getEventTarget() + .cast(); + // the tab should not be focused if the close button was clicked + if (targetElement == caption.getCloseButton()) { + return; + } + + int index = tb.getWidgetIndex(caption.getParent()); + + tb.navigateTab(focusedTabIndex, index); + + focusedTabIndex = index; + + if (!loadTabSheet(index)) { + + // This needs to be called at the end, as the activeTabIndex + // is set in the loadTabSheet. + focus(); + } + } + + @Override + public void onMouseDown(MouseDownEvent event) { + + if (event.getSource() instanceof Tab) { + + // IE doesn't trigger focus when click, so we need to make sure + // the + // next blur deferred command will get killed. + focusBlurManager.cancelNextBlurSchedule(); + } + } + + @Override + public void onKeyDown(KeyDownEvent event) { + if (event.getSource() instanceof Tab) { + int keycode = event.getNativeEvent().getKeyCode(); + + if (!event.isAnyModifierKeyDown()) { + if (keycode == getPreviousTabKey()) { + selectPreviousTab(); + event.stopPropagation(); + + } else if (keycode == getNextTabKey()) { + selectNextTab(); + event.stopPropagation(); + + } else if (keycode == getCloseTabKey()) { + Tab tab = tb.getTab(activeTabIndex); + if (tab.isClosable()) { + tab.onClose(); + } + + } else if (keycode == getSelectTabKey()) { + loadTabSheet(focusedTabIndex); + + // Prevent the page from scrolling when hitting space + // (select key) to select the current tab. + event.preventDefault(); } - } else if (keycode == getSelectTabKey()) { - loadTabSheet(focusedTabIndex); } } } + + /* + * Left arrow key selection. + */ + private void selectPreviousTab() { + int newTabIndex = focusedTabIndex; + // Find the previous visible and enabled tab if any. + do { + newTabIndex--; + } while (newTabIndex >= 0 && !canSelectTab(newTabIndex)); + + if (newTabIndex >= 0) { + keySelectTab(newTabIndex); + } + } + + /* + * Right arrow key selection. + */ + private void selectNextTab() { + int newTabIndex = focusedTabIndex; + // Find the next visible and enabled tab if any. + do { + newTabIndex++; + } while (newTabIndex < getTabCount() && !canSelectTab(newTabIndex)); + + if (newTabIndex < getTabCount()) { + keySelectTab(newTabIndex); + } + } + + /* + * Select the specified tab using left/right key. + */ + private void keySelectTab(int newTabIndex) { + Tab tab = tb.getTab(newTabIndex); + if (tab == null) { + return; + } + + // Focus the tab, otherwise the selected one will loose focus and + // TabSheet will get blurred. + focusTabAtIndex(newTabIndex); + + tb.navigateTab(focusedTabIndex, newTabIndex); + + focusedTabIndex = newTabIndex; + } + + /** + * Focus the specified tab. Make sure to call this only from user + * events, otherwise will break things. + * + * @param tabIndex + * the index of the tab to set. + */ + void focusTabAtIndex(int tabIndex) { + Tab tabToFocus = tb.getTab(tabIndex); + if (tabToFocus != null) { + tabToFocus.focus(); + } + } + } /** @@ -1307,8 +1751,17 @@ public class VTabsheet extends VTabsheetBase implements Focusable, return KeyCodes.KEY_LEFT; } + /** + * Gets the key to activate the selected tab when navigating using + * previous/next (left/right) keys. + * + * @return the key to activate the selected tab. + * + * @see #getNextTabKey() + * @see #getPreviousTabKey() + */ protected int getSelectTabKey() { - return 32; // Space key + return KeyCodes.KEY_SPACE; } /** @@ -1327,66 +1780,32 @@ public class VTabsheet extends VTabsheetBase implements Focusable, return KeyCodes.KEY_DELETE; } - private void selectPreviousTab() { - int newTabIndex = focusedTabIndex; - // Find the previous visible and enabled tab if any. - do { - newTabIndex--; - } while (newTabIndex >= 0 && !canSelectTab(newTabIndex)); - - if (newTabIndex >= 0) { - tb.navigateTab(focusedTabIndex, newTabIndex); - focusedTabIndex = newTabIndex; - - // If this TabSheet already has focus, set the new selected tab - // as focused. - if (focusedTab != null) { - focusedTab = tb.getTab(focusedTabIndex); - focusedTab.focus(); - } - } - } - - private void selectNextTab() { - int newTabIndex = focusedTabIndex; - // Find the next visible and enabled tab if any. - do { - newTabIndex++; - } while (newTabIndex < getTabCount() && !canSelectTab(newTabIndex)); - - if (newTabIndex < getTabCount()) { + private void scrollIntoView(Tab tab) { - tb.navigateTab(focusedTabIndex, newTabIndex); - focusedTabIndex = newTabIndex; + if (!tab.isHiddenOnServer()) { - // If this TabSheet already has focus, set the new selected tab - // as focused. - if (focusedTab != null) { - focusedTab = tb.getTab(focusedTabIndex); - focusedTab.focus(); - } - } - } + // Check for visibility first as clipped tabs to the right are + // always visible. + // On IE8 a tab with false visibility would have the bounds of the + // full TabBar. + if (!tab.isVisible()) { + while (!tab.isVisible()) { + scrollerIndex = tb.scrollLeft(scrollerIndex); + } + updateTabScroller(); - private void scrollIntoView(Tab tab) { - if (!tab.isHiddenOnServer()) { - if (isClipped(tab)) { + } else if (isClipped(tab)) { while (isClipped(tab) && scrollerIndex != -1) { scrollerIndex = tb.scrollRight(scrollerIndex); } updateTabScroller(); - } else if (!tab.isVisible()) { - while (!tab.isVisible()) { - scrollerIndex = tb.scrollLeft(scrollerIndex); - } - updateTabScroller(); } } } /** * Makes tab bar visible. - * + * * @since 7.2 */ public void showTabs() { @@ -1397,7 +1816,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * Makes tab bar invisible. - * + * * @since 7.2 */ public void hideTabs() { diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java index 495e230156..7223e4ac83 100644 --- a/client/src/com/vaadin/client/ui/VWindow.java +++ b/client/src/com/vaadin/client/ui/VWindow.java @@ -245,6 +245,20 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, * state. */ setTabStopEnabled(doTabStop); + + // Fix for #14413. Any pseudo elements inside these elements are not + // visible on initial render unless we shake the DOM. + if (BrowserInfo.get().isIE8()) { + closeBox.getStyle().setDisplay(Display.NONE); + maximizeRestoreBox.getStyle().setDisplay(Display.NONE); + Scheduler.get().scheduleFinally(new Command() { + @Override + public void execute() { + closeBox.getStyle().clearDisplay(); + maximizeRestoreBox.getStyle().clearDisplay(); + } + }); + } } @Override diff --git a/client/src/com/vaadin/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java b/client/src/com/vaadin/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java index afb521b141..f222721e24 100644 --- a/client/src/com/vaadin/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java +++ b/client/src/com/vaadin/client/ui/draganddropwrapper/DragAndDropWrapperConnector.java @@ -17,8 +17,12 @@ package com.vaadin.client.ui.draganddropwrapper; import java.util.HashMap; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorMap; import com.vaadin.client.Paintable; import com.vaadin.client.UIDL; import com.vaadin.client.VConsole; @@ -81,6 +85,25 @@ public class DragAndDropWrapperConnector extends CustomComponentConnector getWidget().dragStartMode = uidl .getIntAttribute(DragAndDropWrapperConstants.DRAG_START_MODE); + + String dragImageComponentConnectorId = uidl + .getStringAttribute(DragAndDropWrapperConstants.DRAG_START_COMPONENT_ATTRIBUTE); + + ComponentConnector connector = null; + if (dragImageComponentConnectorId != null) { + connector = (ComponentConnector) ConnectorMap.get(client) + .getConnector(dragImageComponentConnectorId); + + if (connector == null) { + getLogger().log( + Level.WARNING, + "DragAndDropWrapper drag image component" + + " connector now found. Make sure the" + + " component is attached."); + } else { + getWidget().setDragAndDropWidget(connector.getWidget()); + } + } getWidget().initDragStartMode(); getWidget().html5DataFlavors = uidl .getMapAttribute(DragAndDropWrapperConstants.HTML5_DATA_FLAVORS); @@ -95,4 +118,7 @@ public class DragAndDropWrapperConnector extends CustomComponentConnector return (VDragAndDropWrapper) super.getWidget(); } + private static Logger getLogger() { + return Logger.getLogger(DragAndDropWrapperConnector.class.getName()); + } } diff --git a/client/tests/src/com/vaadin/client/TestVBrowserDetailsUserAgentParser.java b/client/tests/src/com/vaadin/client/TestVBrowserDetailsUserAgentParser.java index 5b428574e2..e38054e3e4 100644 --- a/client/tests/src/com/vaadin/client/TestVBrowserDetailsUserAgentParser.java +++ b/client/tests/src/com/vaadin/client/TestVBrowserDetailsUserAgentParser.java @@ -28,6 +28,7 @@ public class TestVBrowserDetailsUserAgentParser extends TestCase { private static final String IE10_WINDOWS_8 = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)"; private static final String IE11_WINDOWS_7 = "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; rv:11.0) like Gecko"; + private static final String IE11_WINDOWS_PHONE_8_1_UPDATE = "Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920) Like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537"; // "Version/" was added in 10.00 private static final String OPERA964_WINDOWS = "Opera/9.64(Windows NT 5.1; U; en) Presto/2.1.1"; @@ -396,6 +397,16 @@ public class TestVBrowserDetailsUserAgentParser extends TestCase { assertWindows(bd); } + public void testIE11WindowsPhone81Update() { + VBrowserDetails bd = new VBrowserDetails(IE11_WINDOWS_PHONE_8_1_UPDATE); + assertTrident(bd); + assertEngineVersion(bd, 7); + assertIE(bd); + assertBrowserMajorVersion(bd, 11); + assertBrowserMinorVersion(bd, 0); + assertWindows(bd); + } + /* * Helper methods below */ |