From dd786ffd3554828e91d4398bc9a3ce6e498f74b2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Johannes=20Dahlstr=C3=B6m?= Date: Fri, 30 Dec 2011 12:51:56 +0000 Subject: [PATCH] Merged changes from 6.7 svn changeset:22500/svn branch:6.8 --- build/bin/tagrelease.py | 2 +- .../data/validator/AbstractValidator.java | 2 +- .../terminal/gwt/client/ui/VScrollTable.java | 13 +- .../terminal/gwt/client/ui/VTextField.java | 25 ++- src/com/vaadin/ui/Window.java | 24 +++ .../component/window/AttachDetachWindow.java | 179 ++++++++++++++++++ ...angeEventsWithNonImmediateValueChange.java | 12 +- .../AttachShouldBeCalledForSubWindows.java | 129 +++++++++++++ 8 files changed, 368 insertions(+), 18 deletions(-) create mode 100644 tests/server-side/com/vaadin/tests/server/component/window/AttachDetachWindow.java create mode 100644 tests/testbench/com/vaadin/tests/components/window/AttachShouldBeCalledForSubWindows.java diff --git a/build/bin/tagrelease.py b/build/bin/tagrelease.py index 8745553596..0101ecdbd6 100644 --- a/build/bin/tagrelease.py +++ b/build/bin/tagrelease.py @@ -95,7 +95,7 @@ def tagCommand(version, changeset, bookRepo): tagUrl = repoRoot+"/releases/"+version # Book tag parameters - if not re.search(r'branches/[0-9\.]+$', bookRepo): + if (not re.search(r'branches/[0-9\.]+$', bookRepo)) and (not re.search(r'doc/trunk$', bookRepo)): print "Bad documentation branch '%s' for release." % (bookRepo) sys.exit(1) bookTagUrl = repoRoot+"/doc/tags/"+version diff --git a/src/com/vaadin/data/validator/AbstractValidator.java b/src/com/vaadin/data/validator/AbstractValidator.java index e0c63b1565..5c8dd9b31a 100644 --- a/src/com/vaadin/data/validator/AbstractValidator.java +++ b/src/com/vaadin/data/validator/AbstractValidator.java @@ -49,7 +49,7 @@ public abstract class AbstractValidator implements Validator { public void validate(Object value) throws InvalidValueException { if (!isValid(value)) { - String message = errorMessage.replace("{0}", String.valueOf(value)); + String message = getErrorMessage().replace("{0}", String.valueOf(value)); throw new InvalidValueException(message); } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java index 3120aa3cdb..f1c1927b26 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VScrollTable.java @@ -3936,7 +3936,6 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, Element table = DOM.createTable(); private int firstRendered; - private int lastRendered; private char[] aligns; @@ -4045,6 +4044,11 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, 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); @@ -4074,7 +4078,7 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, rowRequestHandler.setReqFirstRow(lastRendered + 1); rowRequestHandler.setReqRows(reactLastRow - lastRendered); rowRequestHandler.deferRowFetch(1); - } else if (scrollBody.getFirstRendered() > reactFirstRow) { + } else if (firstRendered > reactFirstRow) { /* * Branch for fetching cache above visible area. * @@ -6116,17 +6120,18 @@ public class VScrollTable extends FlowPanel implements Table, ScrollHandler, 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; // scrolled withing "non-react area" + return; } if (firstRowInViewPort - pageLength * cache_rate > lastRendered || firstRowInViewPort + pageLength + pageLength * cache_rate < firstRendered) { - // need a totally new set + // need a totally new set of rows rowRequestHandler .setReqFirstRow((firstRowInViewPort - (int) (pageLength * cache_rate))); int last = firstRowInViewPort + (int) (cache_rate * pageLength) diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTextField.java b/src/com/vaadin/terminal/gwt/client/ui/VTextField.java index f059e0cea5..44ee7c11df 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTextField.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VTextField.java @@ -58,6 +58,13 @@ public class VTextField extends TextBoxBase implements Paintable, Field, private 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; + private boolean immediate = false; private int extraHorizontalPixels = -1; private int extraVerticalPixels = -1; @@ -145,18 +152,22 @@ public class VTextField extends TextBoxBase implements Paintable, Field, text = ""; } if (!text.equals(getLastCommunicatedString())) { - if (text.equals(valueBeforeEdit)) { + if (valueBeforeEditIsSynced && text.equals(valueBeforeEdit)) { /* - * Value change for the current text has been enqueued, 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. + * 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(id, 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; } @@ -334,6 +345,7 @@ public class VTextField extends TextBoxBase implements Paintable, Field, } lastTextChangeString = valueBeforeEdit = text; + valueBeforeEditIsSynced = true; } protected void onCut() { @@ -423,6 +435,7 @@ public class VTextField extends TextBoxBase implements Paintable, Field, sendValueChange = immediate; client.updateVariable(id, "text", getText(), false); valueBeforeEdit = newText; + valueBeforeEditIsSynced = true; } /* diff --git a/src/com/vaadin/ui/Window.java b/src/com/vaadin/ui/Window.java index 1e0172d60f..5f6c29f182 100644 --- a/src/com/vaadin/ui/Window.java +++ b/src/com/vaadin/ui/Window.java @@ -2356,4 +2356,28 @@ public class Window extends Panel implements URIHandler, ParameterHandler, } } + /** + * Notifies the child components and subwindows that the window is attached + * to the application. + */ + @Override + public void attach() { + super.attach(); + for (Window w : subwindows) { + w.attach(); + } + } + + /** + * Notifies the child components and subwindows that the window is detached + * from the application. + */ + @Override + public void detach() { + super.detach(); + for (Window w : subwindows) { + w.detach(); + } + } + } diff --git a/tests/server-side/com/vaadin/tests/server/component/window/AttachDetachWindow.java b/tests/server-side/com/vaadin/tests/server/component/window/AttachDetachWindow.java new file mode 100644 index 0000000000..8daee1ba65 --- /dev/null +++ b/tests/server-side/com/vaadin/tests/server/component/window/AttachDetachWindow.java @@ -0,0 +1,179 @@ +package com.vaadin.tests.server.component.window; + +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import com.vaadin.Application; +import com.vaadin.ui.Component; +import com.vaadin.ui.Label; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.Window; + +import org.junit.Test; + +public class AttachDetachWindow { + + private Application testApp = new Application() { + @Override + public void init() { + } + }; + + private class TestWindow extends Window { + boolean windowAttachCalled = false; + boolean contentAttachCalled = false; + boolean childAttachCalled = false; + boolean windowDetachCalled = false; + boolean contentDetachCalled = false; + boolean childDetachCalled = false; + + TestWindow() { + setContent(new VerticalLayout() { + @Override + public void attach() { + super.attach(); + contentAttachCalled = true; + } + + @Override + public void detach() { + super.detach(); + contentDetachCalled = true; + } + }); + addComponent(new Label() { + @Override + public void attach() { + super.attach(); + childAttachCalled = true; + } + + @Override + public void detach() { + super.detach(); + childDetachCalled = true; + } + }); + } + + Component getChild() { + return getComponentIterator().next(); + } + + @Override + public void attach() { + super.attach(); + windowAttachCalled = true; + } + + @Override + public void detach() { + super.detach(); + windowDetachCalled = true; + } + } + + TestWindow main = new TestWindow(); + TestWindow sub = new TestWindow(); + + @Test + public void addSubWindowBeforeAttachingMainWindow() { + assertUnattached(main); + assertUnattached(sub); + + main.addWindow(sub); + assertUnattached(main); + assertUnattached(sub); + + // attaching main should recurse to sub + testApp.setMainWindow(main); + assertAttached(main); + assertAttached(sub); + } + + @Test + public void addSubWindowAfterAttachingMainWindow() { + assertUnattached(main); + assertUnattached(sub); + + testApp.setMainWindow(main); + assertAttached(main); + assertUnattached(sub); + + // main is already attached, so attach should be called for sub + main.addWindow(sub); + assertAttached(main); + assertAttached(sub); + } + + @Test + public void removeSubWindowBeforeDetachingMainWindow() { + testApp.addWindow(main); + main.addWindow(sub); + + // sub should be detached when removing from attached main + main.removeWindow(sub); + assertAttached(main); + assertDetached(sub); + + // main detach should recurse to sub + testApp.removeWindow(main); + assertDetached(main); + assertDetached(sub); + } + + @Test + public void removeSubWindowAfterDetachingMainWindow() { + testApp.addWindow(main); + main.addWindow(sub); + + // main detach should recurse to sub + testApp.removeWindow(main); + assertDetached(main); + assertDetached(sub); + + main.removeWindow(sub); + assertDetached(main); + assertDetached(sub); + } + + /** + * Asserts that win and its children are attached to testApp and their + * attach() methods have been called. + */ + private void assertAttached(TestWindow win) { + assertTrue("window attach not called", win.windowAttachCalled); + assertTrue("window content attach not called", win.contentAttachCalled); + assertTrue("window child attach not called", win.childAttachCalled); + + assertSame("window not attached", win.getApplication(), testApp); + assertSame("window content not attached", win.getContent() + .getApplication(), testApp); + assertSame("window children not attached", win.getChild() + .getApplication(), testApp); + } + + /** + * Asserts that win and its children are not attached. + */ + private void assertUnattached(TestWindow win) { + assertSame("window not detached", win.getApplication(), null); + assertSame("window content not detached", win.getContent() + .getApplication(), null); + assertSame("window children not detached", win.getChild() + .getApplication(), null); + } + + /** + * Asserts that win and its children are unattached and their detach() + * methods have been been called. + * + * @param win + */ + private void assertDetached(TestWindow win) { + assertUnattached(win); + assertTrue("window detach not called", win.windowDetachCalled); + assertTrue("window content detach not called", win.contentDetachCalled); + assertTrue("window child detach not called", win.childDetachCalled); + } +} diff --git a/tests/testbench/com/vaadin/tests/components/textfield/TextChangeEventsWithNonImmediateValueChange.java b/tests/testbench/com/vaadin/tests/components/textfield/TextChangeEventsWithNonImmediateValueChange.java index a893739bff..df5b275a8e 100644 --- a/tests/testbench/com/vaadin/tests/components/textfield/TextChangeEventsWithNonImmediateValueChange.java +++ b/tests/testbench/com/vaadin/tests/components/textfield/TextChangeEventsWithNonImmediateValueChange.java @@ -19,10 +19,9 @@ public class TextChangeEventsWithNonImmediateValueChange extends TestBase { TextChangeListener inputEventListener = new TextChangeListener() { public void textChange(TextChangeEvent event) { - l.log("Text change event for " - + event.getComponent().getCaption() - + ", text content currently:'" + event.getText() - + "' Cursor at index:" + event.getCursorPosition()); + l.log("Text change event, text content currently:'" + + event.getText() + "' Cursor at index:" + + event.getCursorPosition()); } }; @@ -33,7 +32,7 @@ public class TextChangeEventsWithNonImmediateValueChange extends TestBase { tf.addListener(new ValueChangeListener() { public void valueChange(ValueChangeEvent event) { - l.log("Value change:" + event.getProperty().toString()); + l.log("Value change: '" + event.getProperty().toString() + "'"); } }); @@ -44,7 +43,8 @@ public class TextChangeEventsWithNonImmediateValueChange extends TestBase { @Override protected String getDescription() { - return "Type a, pause for a second, type ENTER, type a. Text field should not forget the last textchange event right after valuechange (enter)."; + return "Type a, pause for a second, type ENTER, type a. Text field should not forget the last textchange event right after valuechange (enter)." + + "
Then press backspace. The text field should send a text change event even though the text in the field is the same as the field's value"; } @Override diff --git a/tests/testbench/com/vaadin/tests/components/window/AttachShouldBeCalledForSubWindows.java b/tests/testbench/com/vaadin/tests/components/window/AttachShouldBeCalledForSubWindows.java new file mode 100644 index 0000000000..a6040c06d3 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/window/AttachShouldBeCalledForSubWindows.java @@ -0,0 +1,129 @@ +package com.vaadin.tests.components.window; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.vaadin.event.ShortcutAction.KeyCode; +import com.vaadin.terminal.gwt.server.HttpServletRequestListener; +import com.vaadin.tests.components.AbstractTestCase; +import com.vaadin.tests.util.Log; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.Component; +import com.vaadin.ui.Label; +import com.vaadin.ui.Window; + +public class AttachShouldBeCalledForSubWindows extends AbstractTestCase + implements HttpServletRequestListener { + private static final long serialVersionUID = 1L; + + private Log log = new Log(20); + + boolean addSubWindowBeforeMainWindow = true; + + @Override + public void init() { + + Window mainWindow = new Window() { + @Override + public void attach() { + log(this); + super.attach(); + } + + @Override + public void addWindow(Window w) { + log.log("Adding sub window"); + super.addWindow(w); + log.log("Sub window added"); + + } + }; + mainWindow.setCaption("Main window"); + mainWindow.addComponent(log); + mainWindow.getContent().setSizeFull(); + Label label = new Label("This is the main app") { + @Override + public void attach() { + log(this); + super.attach(); + } + }; + + mainWindow.addComponent(label); + Window loginWindow = createSubWindow(); + if (addSubWindowBeforeMainWindow) { + mainWindow.addWindow(loginWindow); + } + + log.log("Setting main window"); + setMainWindow(mainWindow); // At this point + log.log("Main window set"); + + if (!addSubWindowBeforeMainWindow) { + mainWindow.addWindow(loginWindow); + } + } + + private Window createSubWindow() { + Window w = new Window("Sub window") { + @Override + public void attach() { + log(this); + super.attach(); + } + }; + Button okButton = new Button("OK") { + @Override + public void attach() { + super.attach(); + log(this); + } + }; + okButton.addListener(new ClickListener() { + + public void buttonClick(ClickEvent event) { + log.log("Button clicked"); + + } + }); + okButton.setClickShortcut(KeyCode.ENTER); + w.addComponent(okButton); + w.center(); + return w; + } + + public void log(Component c) { + Class cls = c.getClass(); + if (cls.isAnonymousClass()) { + cls = cls.getSuperclass(); + } + log.log(cls.getName() + " '" + c.getCaption() + + "' attached to application"); + } + + @Override + protected String getDescription() { + return "By default attaches a sub window with a button to the main window and then set the main window to the application. Use ?attachMainFirst to reverse the order. In both cases attach events should be sent for the components in the sub window"; + } + + @Override + protected Integer getTicketNumber() { + return 8170; + } + + public void onRequestStart(HttpServletRequest request, + HttpServletResponse response) { + if (request.getParameter("attachMainFirst") != null) { + addSubWindowBeforeMainWindow = false; + } + + } + + public void onRequestEnd(HttpServletRequest request, + HttpServletResponse response) { + // TODO Auto-generated method stub + + } +} -- 2.39.5