From: Leif Åstrand Date: Tue, 10 Apr 2012 09:56:39 +0000 (+0300) Subject: Merge remote branch 'origin/6.8' X-Git-Tag: 7.0.0.alpha2~104 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=a8941d2e09c6b0f5e904b60378f9b469d2d9fd9e;p=vaadin-framework.git Merge remote branch 'origin/6.8' Conflicts: src/com/vaadin/terminal/gwt/client/ui/VTabsheet.java src/com/vaadin/terminal/gwt/client/ui/VTree.java src/com/vaadin/terminal/gwt/client/ui/VWindow.java src/com/vaadin/ui/AbstractField.java --- a8941d2e09c6b0f5e904b60378f9b469d2d9fd9e diff --cc src/com/vaadin/terminal/gwt/client/ui/TabsheetConnector.java index 297bb06400,0000000000..afb9b69e6e mode 100644,000000..100644 --- a/src/com/vaadin/terminal/gwt/client/ui/TabsheetConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/TabsheetConnector.java @@@ -1,106 -1,0 +1,102 @@@ +/* +@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()) { - // FIXME: This makes tab sheet tabs go to 1px width on every update - // and then back to original width - // update width later, in updateTabScroller(); - DOM.setStyleAttribute(getWidget().tabs, "width", "1px"); + 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 --cc src/com/vaadin/terminal/gwt/client/ui/TreeConnector.java index e1aef7f9e2,0000000000..78a0a8453b mode 100644,000000..100644 --- a/src/com/vaadin/terminal/gwt/client/ui/TreeConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/TreeConnector.java @@@ -1,251 -1,0 +1,258 @@@ +/* +@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) { - getWidget().multiSelectMode = uidl - .getIntAttribute("multiselectmode"); ++ 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 --cc src/com/vaadin/terminal/gwt/client/ui/VTabsheet.java index 027f7975d3,7b9b900289..2cdc041955 --- a/src/com/vaadin/terminal/gwt/client/ui/VTabsheet.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VTabsheet.java @@@ -577,19 -615,23 +583,19 @@@ public class VTabsheet extends VTabshee } addStyleDependentName("loading"); - // run updating variables in deferred command to bypass some FF - // optimization issues - Scheduler.get().scheduleDeferred(new Command() { - public void execute() { - previousVisibleWidget = tp.getWidget(tp.getVisibleWidget()); - DOM.setStyleAttribute( - DOM.getParent(previousVisibleWidget.getElement()), - "visibility", "hidden"); - client.updateVariable(id, "selected", tabKeys.get(tabIndex) - .toString(), true); - } - }); + // 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; - - return true; } - return false; + // 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() { diff --cc src/com/vaadin/ui/AbstractField.java index 361e65fa34,f3939de6e1..3b9d035a9a --- a/src/com/vaadin/ui/AbstractField.java +++ b/src/com/vaadin/ui/AbstractField.java @@@ -21,17 -17,14 +21,15 @@@ import com.vaadin.data.Property import com.vaadin.data.Validatable; import com.vaadin.data.Validator; import com.vaadin.data.Validator.InvalidValueException; +import com.vaadin.data.util.converter.Converter; +import com.vaadin.data.util.converter.ConverterFactory; import com.vaadin.event.Action; -import com.vaadin.event.ActionManager; import com.vaadin.event.ShortcutAction; import com.vaadin.event.ShortcutListener; +import com.vaadin.terminal.AbstractErrorMessage; import com.vaadin.terminal.CompositeErrorMessage; import com.vaadin.terminal.ErrorMessage; --import com.vaadin.terminal.PaintException; --import com.vaadin.terminal.PaintTarget; +import com.vaadin.terminal.gwt.client.AbstractFieldState; /** *

@@@ -671,20 -596,10 +674,10 @@@ public abstract class AbstractField public void setPropertyDataSource(Property newDataSource) { // Saves the old value - final Object oldValue = value; + final Object oldValue = getInternalValue(); - // Stops listening the old data source changes - if (dataSource != null - && Property.ValueChangeNotifier.class - .isAssignableFrom(dataSource.getClass())) { - ((Property.ValueChangeNotifier) dataSource).removeListener(this); - } - if (dataSource != null - && Property.ReadOnlyStatusChangeNotifier.class - .isAssignableFrom(dataSource.getClass())) { - ((Property.ReadOnlyStatusChangeNotifier) dataSource) - .removeListener(this); - } + // Stop listening to the old data source + removePropertyListeners(); // Sets the new data source dataSource = newDataSource; @@@ -706,27 -607,18 +699,21 @@@ // Gets the value from source try { if (dataSource != null) { - setInternalValue(String.class == getType() ? dataSource - .toString() : dataSource.getValue()); + T fieldValue = convertFromDataSource(getDataSourceValue()); + setInternalValue(fieldValue); + } + setModified(false); + if (getCurrentBufferedSourceException() != null) { + setCurrentBufferedSourceException(null); } - modified = false; } catch (final Throwable e) { - currentBufferedSourceException = new Buffered.SourceException(this, - e); - modified = true; + setCurrentBufferedSourceException(new Buffered.SourceException( + this, e)); + setModified(true); } - // Listens the new data source if possible - if (dataSource instanceof Property.ValueChangeNotifier) { - ((Property.ValueChangeNotifier) dataSource).addListener(this); - } - if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) { - ((Property.ReadOnlyStatusChangeNotifier) dataSource) - .addListener(this); - } + // Listen to new data source if possible + addPropertyListeners(); // Copy the validators from the data source if (dataSource instanceof Validatable) { @@@ -1355,6 -1109,32 +1352,26 @@@ } } + /** + * Notifies the component that it is connected to an application. + * + * @see com.vaadin.ui.Component#attach() + */ + @Override + public void attach() { + super.attach(); - if (actionManager != null) { - actionManager.setViewer(getWindow()); - } + // No-op if listeners already registered + addPropertyListeners(); + } + + @Override + public void detach() { + super.detach(); - if (actionManager != null) { - actionManager.setViewer((Window) null); - } + // Stop listening to data source events on detach to avoid a potential + // memory leak. See #6155. + removePropertyListeners(); + } + /** * Is this field required. Required fields must filled by the user. * @@@ -1556,40 -1334,30 +1573,66 @@@ } } + /** + * Gets the converter used to convert the property data source value to the + * field value. + * + * @return The converter or null if none is set. + */ + public Converter getConverter() { + return converter; + } + + /** + * Sets the converter used to convert the field value to property data + * source type. The converter must have a presentation type that matches the + * field type. + * + * @param converter + * The new converter to use. + */ + public void setConverter(Converter converter) { + this.converter = (Converter) converter; + requestRepaint(); + } + + @Override + public AbstractFieldState getState() { + return (AbstractFieldState) super.getState(); + } + + @Override + public void updateState() { + super.updateState(); + + // Hide the error indicator if needed + getState().setHideErrors(shouldHideErrors()); + } + + private void addPropertyListeners() { + if (!isListening) { + if (dataSource instanceof Property.ValueChangeNotifier) { + ((Property.ValueChangeNotifier) dataSource).addListener(this); + } + if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) { + ((Property.ReadOnlyStatusChangeNotifier) dataSource) + .addListener(this); + } + isListening = true; + } + } + + private void removePropertyListeners() { + if (isListening) { + if (dataSource instanceof Property.ValueChangeNotifier) { + ((Property.ValueChangeNotifier) dataSource) + .removeListener(this); + } + if (dataSource instanceof Property.ReadOnlyStatusChangeNotifier) { + ((Property.ReadOnlyStatusChangeNotifier) dataSource) + .removeListener(this); + } + isListening = false; + } + } -} +} diff --cc src/com/vaadin/ui/TabSheet.java index 82a2930dd8,cb89c5e0c8..c4c524210f --- a/src/com/vaadin/ui/TabSheet.java +++ b/src/com/vaadin/ui/TabSheet.java @@@ -574,27 -594,27 +574,48 @@@ public class TabSheet extends AbstractC } } + /** + * Sets the selected tab in the TabSheet. Ensures that the selected tab is + * repainted if needed. + * + * @param c + * The new selection or null for no selection + */ + private void setSelected(Component c) { + selected = c; + // Repaint of the selected component is needed as only the selected + // component is communicated to the client. Otherwise this will be a + // "cached" update even though the client knows nothing about the + // connector + if (selected instanceof ComponentContainer) { + ((ComponentContainer) selected).requestRepaintAll(); + } else if (selected != null) { + selected.requestRepaint(); + } + + } + + /** + * Sets the selected tab. The tab is identified by the corresponding + * {@link Tab Tab} instance. Does nothing if the tabsheet doesn't contain + * the given tab. + * + * @param tab + */ + public void setSelectedTab(Tab tab) { + setSelectedTab(tab.getComponent()); + } + + /** + * Sets the selected tab, identified by its position. Does nothing if + * index < 0 || index > {@link #getComponentCount()}. + * + * @param index + */ + public void setSelectedTab(int index) { + setSelectedTab(getTab(index)); + } + /** * Checks if the current selection is valid, and updates the selection if * the previously selected component is not visible and enabled. The first diff --cc tests/server-side/com/vaadin/tests/server/component/abstractfield/RemoveListenersOnDetach.java index 0000000000,32b80e0bcd..9aeccdb56f mode 000000,100644..100644 --- a/tests/server-side/com/vaadin/tests/server/component/abstractfield/RemoveListenersOnDetach.java +++ b/tests/server-side/com/vaadin/tests/server/component/abstractfield/RemoveListenersOnDetach.java @@@ -1,0 -1,72 +1,73 @@@ + package com.vaadin.tests.server.component.abstractfield; + + import static org.junit.Assert.assertEquals; + ++import org.junit.Test; ++ + import com.vaadin.data.Property; + import com.vaadin.data.util.AbstractProperty; ++import com.vaadin.data.util.converter.Converter.ConversionException; + import com.vaadin.ui.AbstractField; + -import org.junit.Test; - + public class RemoveListenersOnDetach { + + int numValueChanges = 0; + int numReadOnlyChanges = 0; + + AbstractField field = new AbstractField() { + @Override + public Class getType() { + return null; + } + + @Override + public void valueChange(Property.ValueChangeEvent event) { + super.valueChange(event); + numValueChanges++; + } + + @Override + public void readOnlyStatusChange( + Property.ReadOnlyStatusChangeEvent event) { + super.readOnlyStatusChange(event); + numReadOnlyChanges++; + } + }; + + Property property = new AbstractProperty() { + public Object getValue() { + return null; + } + + public void setValue(Object newValue) throws ReadOnlyException, + ConversionException { + fireValueChange(); + } + + public Class getType() { + return null; + } + }; + + @Test + public void testAttachDetach() { + field.setPropertyDataSource(property); + + property.setValue(null); + property.setReadOnly(true); + assertEquals(1, numValueChanges); + assertEquals(1, numReadOnlyChanges); + + field.attach(); + property.setValue(null); + property.setReadOnly(false); + assertEquals(2, numValueChanges); + assertEquals(2, numReadOnlyChanges); + + field.detach(); + property.setValue(null); + property.setReadOnly(true); + assertEquals(2, numValueChanges); + assertEquals(2, numReadOnlyChanges); + } + }