]> source.dussan.org Git - vaadin-framework.git/commitdiff
Merge remote branch 'origin/6.8'
authorLeif Åstrand <leif@vaadin.com>
Tue, 10 Apr 2012 09:56:39 +0000 (12:56 +0300)
committerLeif Åstrand <leif@vaadin.com>
Tue, 10 Apr 2012 09:56:39 +0000 (12:56 +0300)
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

1  2 
src/com/vaadin/terminal/gwt/client/ui/TabsheetConnector.java
src/com/vaadin/terminal/gwt/client/ui/TreeConnector.java
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
src/com/vaadin/ui/TabSheet.java
tests/integration_tests.xml
tests/server-side/com/vaadin/tests/server/component/abstractfield/RemoveListenersOnDetach.java

index 297bb0640032ad4a547fd9957a90dd80f165209b,0000000000000000000000000000000000000000..afb9b69e6e0bf23b4f7e1b42514d99971228894a
mode 100644,000000..100644
--- /dev/null
@@@ -1,106 -1,0 +1,102 @@@
-             // 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");
 +/*
 +@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();
 +
 +    }
 +
 +}
index e1aef7f9e2f05aa26307c8d3155eb36f27c30241,0000000000000000000000000000000000000000..78a0a8453bc4d6567e6261faab6c185a5969f013
mode 100644,000000..100644
--- /dev/null
@@@ -1,251 -1,0 +1,258 @@@
-             getWidget().multiSelectMode = uidl
-                     .getIntAttribute("multiselectmode");
 +/*
 +@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<UIDL> 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();
 +    }
 +
 +}
index 027f7975d3d513515eef1afcc2ba655106c35e5e,7b9b900289d9dc53269e207876305fe3f1bdd79f..2cdc04195536adb325d4b904f56bb9f061aeeeb6
@@@ -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() {
index 361e65fa341820f213011565e63f38a294d82972,f3939de6e13927d76f95bc93786fdb6e6cc885b3..3b9d035a9a6393a8ec68935b67251e08a5fc10cd
@@@ -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;
  
  /**
   * <p>
@@@ -671,20 -596,10 +674,10 @@@ public abstract class AbstractField<T> 
      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;
          // 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) {
          }
      }
  
 -        if (actionManager != null) {
 -            actionManager.setViewer(getWindow());
 -        }
+     /**
+      * 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((Window) null);
 -        }
+         // No-op if listeners already registered
+         addPropertyListeners();
+     }
+     @Override
+     public void detach() {
+         super.detach();
+         // 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.
       * 
          }
      }
  
 -}
 +    /**
 +     * 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<T, Object> 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<T, ?> converter) {
 +        this.converter = (Converter<T, Object>) 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;
+         }
+     }
 +}
index 82a2930dd854cb3ff0e8567233b69e512c183c5e,cb89c5e0c82fca76d750efbb0ab02e80067e5b26..c4c524210fbfbb3c3825062b96fbaf1d4f3fb869
@@@ -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
+      * <code>index &lt; 0 || index &gt; {@link #getComponentCount()}</code>.
+      * 
+      * @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
Simple merge
index 0000000000000000000000000000000000000000,32b80e0bcd95c2a48a747e86ec15ed7ebe9fdf9e..9aeccdb56fa3baf40b92952d56ad4d6cacc3856e
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,72 +1,73 @@@
 -import org.junit.Test;
 -
+ 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;
+ 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);
+     }
+ }