diff options
73 files changed, 2103 insertions, 404 deletions
diff --git a/all/src/main/templates/release-notes.html b/all/src/main/templates/release-notes.html index 8e2d29d955..568006e622 100644 --- a/all/src/main/templates/release-notes.html +++ b/all/src/main/templates/release-notes.html @@ -128,6 +128,7 @@ <li>Old input prompts have been replaced with placeholders utilizing the related browser functionality</li> <li>The old liferay theme (Liferay 6.0 look) has been removed</li> <li>Components in the compatibility packages now use the prefix "vaadin7-" in declarative design files</li> + <li>RichTextArea no longer receives a special "v-richtextarea-readonly" class when readonly, only the standard "v-readonly" class</li> </ul> <h3 id="knownissues">Known Issues and Limitations</h3> <ul> diff --git a/client/src/main/java/com/vaadin/client/ui/VRichTextArea.java b/client/src/main/java/com/vaadin/client/ui/VRichTextArea.java new file mode 100644 index 0000000000..75bec94dca --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/VRichTextArea.java @@ -0,0 +1,415 @@ +/* + * Copyright 2000-2016 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 + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.client.ui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Consumer; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.BodyElement; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.IFrameElement; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.KeyPressEvent; +import com.google.gwt.event.dom.client.KeyPressHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Focusable; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.HasEnabled; +import com.google.gwt.user.client.ui.RichTextArea; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ConnectorMap; +import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; +import com.vaadin.client.ui.richtextarea.VRichTextToolbar; + +/** + * This class implements a basic client side rich text editor component. + * + * @author Vaadin Ltd. + * + */ +public class VRichTextArea extends Composite implements Field, KeyPressHandler, + KeyDownHandler, Focusable, HasEnabled { + + /** + * The input node CSS classname. + */ + public static final String CLASSNAME = "v-richtextarea"; + + /** For internal use only. May be removed or replaced in the future. */ + public String id; + + /** For internal use only. May be removed or replaced in the future. */ + public ApplicationConnection client; + + /** For internal use only. May be removed or replaced in the future. */ + public boolean immediate = false; + + /** For internal use only. May be removed or replaced in the future. */ + public RichTextArea rta; + + /** For internal use only. May be removed or replaced in the future. */ + public VRichTextToolbar formatter; + + /** For internal use only. May be removed or replaced in the future. */ + public HTML html = new HTML(); + + private final FlowPanel fp = new FlowPanel(); + + private boolean enabled = true; + + /** For internal use only. May be removed or replaced in the future. */ + public int maxLength = -1; + + private int toolbarNaturalWidth = 500; + + /** For internal use only. May be removed or replaced in the future. */ + public HandlerRegistration keyPressHandler; + + private ShortcutActionHandlerOwner hasShortcutActionHandler; + + private boolean readOnly = false; + + private final Map<BlurHandler, HandlerRegistration> blurHandlers = new HashMap<BlurHandler, HandlerRegistration>(); + + private List<Command> inputHandlers = new ArrayList<>(); + + public VRichTextArea() { + createRTAComponents(); + fp.add(formatter); + fp.add(rta); + + initWidget(fp); + setStyleName(CLASSNAME); + + TouchScrollDelegate.enableTouchScrolling(html, html.getElement()); + } + + private void createRTAComponents() { + rta = new RichTextArea(); + rta.setWidth("100%"); + rta.addKeyDownHandler(this); + rta.addInitializeHandler(e -> { + // Must wait until iframe is attached to be able to access body + BodyElement rtaBody = IFrameElement.as(rta.getElement()) + .getContentDocument().getBody(); + addInputListener(rtaBody, event -> { + inputHandlers.forEach(handler -> handler.execute()); + }); + }); + + formatter = new VRichTextToolbar(rta); + + // Add blur handlers + for (Entry<BlurHandler, HandlerRegistration> handler : blurHandlers + .entrySet()) { + + // Remove old registration + handler.getValue().removeHandler(); + + // Add blur handlers + addBlurHandler(handler.getKey()); + } + } + + private native void addInputListener(Element element, + Consumer<NativeEvent> listener) + /*-{ + element.addEventListener("input", $entry(function(event) { + listener.@java.util.function.Consumer::accept(Ljava/lang/Object;)(event); + })); + }-*/; + + public void setMaxLength(int maxLength) { + if (maxLength >= 0) { + if (this.maxLength == -1) { + keyPressHandler = rta.addKeyPressHandler(this); + } + this.maxLength = maxLength; + } else if (this.maxLength != -1) { + this.maxLength = -1; + keyPressHandler.removeHandler(); + } + + } + + @Override + public void setEnabled(boolean enabled) { + if (this.enabled != enabled) { + // rta.setEnabled(enabled); + swapEditableArea(); + this.enabled = enabled; + } + } + + @Override + public boolean isEnabled() { + return enabled; + } + + /** + * Swaps html to rta and visa versa. + */ + private void swapEditableArea() { + String value = getValue(); + if (html.isAttached()) { + fp.remove(html); + if (BrowserInfo.get().isWebkit()) { + fp.remove(formatter); + createRTAComponents(); // recreate new RTA to bypass #5379 + fp.add(formatter); + } + fp.add(rta); + } else { + fp.remove(rta); + fp.add(html); + } + setValue(value); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void selectAll() { + /* + * There is a timing issue if trying to select all immediately on first + * render. Simple deferred command is not enough. Using Timer with + * moderated timeout. If this appears to fail on many (most likely slow) + * environments, consider increasing the timeout. + * + * FF seems to require the most time to stabilize its RTA. On Vaadin + * tiergarden test machines, 200ms was not enough always (about 50% + * success rate) - 300 ms was 100% successful. This however was not + * enough on a sluggish old non-virtualized XP test machine. A bullet + * proof solution would be nice, GWT 2.1 might however solve these. At + * least setFocus has a workaround for this kind of issue. + */ + new Timer() { + @Override + public void run() { + rta.getFormatter().selectAll(); + } + }.schedule(320); + } + + public void setReadOnly(boolean b) { + if (isReadOnly() != b) { + swapEditableArea(); + readOnly = b; + } + // reset visibility in case enabled state changed and the formatter was + // recreated + formatter.setVisible(!readOnly); + } + + private boolean isReadOnly() { + return readOnly; + } + + @Override + public void setHeight(String height) { + super.setHeight(height); + if (height == null || height.equals("")) { + rta.setHeight(""); + } + } + + @Override + public void setWidth(String width) { + if (width.equals("")) { + /* + * IE cannot calculate the width of the 100% iframe correctly if + * there is no width specified for the parent. In this case we would + * use the toolbar but IE cannot calculate the width of that one + * correctly either in all cases. So we end up using a default width + * for a RichTextArea with no width definition in all browsers (for + * compatibility). + */ + + super.setWidth(toolbarNaturalWidth + "px"); + } else { + super.setWidth(width); + } + } + + @Override + public void onKeyPress(KeyPressEvent event) { + if (maxLength >= 0) { + Scheduler.get().scheduleDeferred(new Command() { + @Override + public void execute() { + if (rta.getHTML().length() > maxLength) { + rta.setHTML(rta.getHTML().substring(0, maxLength)); + } + } + }); + } + } + + @Override + public void onKeyDown(KeyDownEvent event) { + // delegate to closest shortcut action handler + // throw event from the iframe forward to the shortcuthandler + ShortcutActionHandler shortcutHandler = getShortcutHandlerOwner() + .getShortcutActionHandler(); + if (shortcutHandler != null) { + shortcutHandler.handleKeyboardEvent( + com.google.gwt.user.client.Event.as(event.getNativeEvent()), + ConnectorMap.get(client).getConnector(this)); + } + } + + private ShortcutActionHandlerOwner getShortcutHandlerOwner() { + if (hasShortcutActionHandler == null) { + Widget parent = getParent(); + while (parent != null) { + if (parent instanceof ShortcutActionHandlerOwner) { + break; + } + parent = parent.getParent(); + } + hasShortcutActionHandler = (ShortcutActionHandlerOwner) parent; + } + return hasShortcutActionHandler; + } + + @Override + public int getTabIndex() { + return rta.getTabIndex(); + } + + @Override + public void setAccessKey(char key) { + rta.setAccessKey(key); + } + + @Override + public void setFocus(boolean focused) { + /* + * Similar issue as with selectAll. Focusing must happen before possible + * selectall, so keep the timeout here lower. + */ + new Timer() { + + @Override + public void run() { + rta.setFocus(true); + } + }.schedule(300); + } + + @Override + public void setTabIndex(int index) { + rta.setTabIndex(index); + } + + /** + * Sets the value of the text area. + * + * @param value + * The text value, as HTML + */ + public void setValue(String value) { + if (rta.isAttached()) { + rta.setHTML(value); + } else { + html.setHTML(value); + } + } + + /** + * Gets the value of the text area. + * + * @return the value as HTML + */ + public String getValue() { + if (rta.isAttached()) { + return rta.getHTML(); + } else { + return html.getHTML(); + } + } + + /** + * Browsers differ in what they return as the content of a visually empty + * rich text area. This method is used to normalize these to an empty + * string. See #8004. + * + * @return cleaned html string + */ + public String getSanitizedValue() { + BrowserInfo browser = BrowserInfo.get(); + String result = getValue(); + if (browser.isFirefox()) { + if ("<br>".equals(result)) { + result = ""; + } + } else if (browser.isWebkit() || browser.isEdge()) { + if ("<br>".equals(result) || "<div><br></div>".equals(result)) { + result = ""; + } + } else if (browser.isIE()) { + if ("<P> </P>".equals(result)) { + result = ""; + } + } else if (browser.isOpera()) { + if ("<br>".equals(result) || "<p><br></p>".equals(result)) { + result = ""; + } + } + return result; + } + + /** + * Adds a blur handler to the component. + * + * @param blurHandler + * the blur handler to add + */ + public void addBlurHandler(BlurHandler blurHandler) { + blurHandlers.put(blurHandler, rta.addBlurHandler(blurHandler)); + } + + /** + * Removes a blur handler. + * + * @param blurHandler + * the handler to remove + */ + public void removeBlurHandler(BlurHandler blurHandler) { + HandlerRegistration registration = blurHandlers.remove(blurHandler); + if (registration != null) { + registration.removeHandler(); + } + } + + public HandlerRegistration addInputHandler(Command inputHandler) { + inputHandlers.add(inputHandler); + return () -> inputHandlers.remove(inputHandler); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java b/client/src/main/java/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java new file mode 100644 index 0000000000..87c5b938e6 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java @@ -0,0 +1,139 @@ +/* + * Copyright 2000-2016 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.richtextarea; + +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.vaadin.client.annotations.OnStateChange; +import com.vaadin.client.ui.AbstractFieldConnector; +import com.vaadin.client.ui.ConnectorFocusAndBlurHandler; +import com.vaadin.client.ui.SimpleManagedLayout; +import com.vaadin.client.ui.VRichTextArea; +import com.vaadin.client.ui.textfield.ValueChangeHandler; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.Connect.LoadStyle; +import com.vaadin.shared.ui.richtextarea.RichTextAreaClientRpc; +import com.vaadin.shared.ui.richtextarea.RichTextAreaServerRpc; +import com.vaadin.shared.ui.richtextarea.RichTextAreaState; +import com.vaadin.ui.RichTextArea; + +/** + * Connector for RichTextArea. + */ +@Connect(value = RichTextArea.class, loadStyle = LoadStyle.EAGER) +public class RichTextAreaConnector extends AbstractFieldConnector + implements SimpleManagedLayout, ValueChangeHandler.Owner { + + private class RichTextAreaClientRpcImpl implements RichTextAreaClientRpc { + @Override + public void selectAll() { + getWidget().selectAll(); + } + } + + private ValueChangeHandler valueChangeHandler; + + @Override + protected void init() { + getWidget().addBlurHandler(new BlurHandler() { + @Override + public void onBlur(BlurEvent event) { + flush(); + } + }); + getWidget().addInputHandler(() -> { + valueChangeHandler.scheduleValueChange(); + }); + + registerRpc(RichTextAreaClientRpc.class, + new RichTextAreaClientRpcImpl()); + ConnectorFocusAndBlurHandler.addHandlers(this); + + valueChangeHandler = new ValueChangeHandler(this); + + getLayoutManager().registerDependency(this, + getWidget().formatter.getElement()); + } + + @OnStateChange("valueChangeMode") + private void updateValueChangeMode() { + valueChangeHandler.setValueChangeMode(getState().valueChangeMode); + } + + @OnStateChange("valueChangeTimeout") + private void updateValueChangeTimeout() { + valueChangeHandler.setValueChangeTimeout(getState().valueChangeTimeout); + } + + @OnStateChange("readOnly") + private void updateReadOnly() { + getWidget().setReadOnly(getState().readOnly); + } + + @Override + public void onUnregister() { + super.onUnregister(); + getLayoutManager().unregisterDependency(this, + getWidget().formatter.getElement()); + } + + @Override + public VRichTextArea getWidget() { + return (VRichTextArea) super.getWidget(); + } + + @Override + public void layout() { + if (!isUndefinedHeight()) { + int rootElementInnerHeight = getLayoutManager() + .getInnerHeight(getWidget().getElement()); + int formatterHeight = getLayoutManager() + .getOuterHeight(getWidget().formatter.getElement()); + int editorHeight = rootElementInnerHeight - formatterHeight; + if (editorHeight < 0) { + editorHeight = 0; + } + getWidget().rta.setHeight(editorHeight + "px"); + } + } + + @Override + public RichTextAreaState getState() { + return (RichTextAreaState) super.getState(); + } + + private boolean hasStateChanged(String widgetValue) { + return !widgetValue.equals(getState().value); + } + + @Override + public void sendValueChange() { + String widgetValue = getWidget().getSanitizedValue(); + if (!hasStateChanged(widgetValue)) { + return; + } + + getRpcProxy(RichTextAreaServerRpc.class).setText(widgetValue); + getState().value = widgetValue; + + } + + @Override + public void flush() { + super.flush(); + sendValueChange(); + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java b/client/src/main/java/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java new file mode 100644 index 0000000000..10c1de75e6 --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java @@ -0,0 +1,476 @@ +/* + * Copyright 2000-2016 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +/* + * Copyright 2007 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.richtextarea; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.KeyUpEvent; +import com.google.gwt.event.dom.client.KeyUpHandler; +import com.google.gwt.i18n.client.Constants; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.PushButton; +import com.google.gwt.user.client.ui.RichTextArea; +import com.google.gwt.user.client.ui.ToggleButton; + +/** + * A modified version of sample toolbar for use with {@link RichTextArea}. It + * provides a simple UI for all rich text formatting, dynamically displayed only + * for the available functionality. + */ +public class VRichTextToolbar extends Composite { + + /** + * This {@link ClientBundle} is used for all the button icons. Using a + * bundle allows all of these images to be packed into a single image, which + * saves a lot of HTTP requests, drastically improving startup time. + */ + public interface Images extends ClientBundle { + + ImageResource bold(); + + ImageResource createLink(); + + ImageResource hr(); + + ImageResource indent(); + + ImageResource insertImage(); + + ImageResource italic(); + + ImageResource justifyCenter(); + + ImageResource justifyLeft(); + + ImageResource justifyRight(); + + ImageResource ol(); + + ImageResource outdent(); + + ImageResource removeFormat(); + + ImageResource removeLink(); + + ImageResource strikeThrough(); + + ImageResource subscript(); + + ImageResource superscript(); + + ImageResource ul(); + + ImageResource underline(); + } + + /** + * This {@link Constants} interface is used to make the toolbar's strings + * internationalizable. + */ + public interface Strings extends Constants { + + String black(); + + String blue(); + + String bold(); + + String color(); + + String createLink(); + + String font(); + + String green(); + + String hr(); + + String indent(); + + String insertImage(); + + String italic(); + + String justifyCenter(); + + String justifyLeft(); + + String justifyRight(); + + String large(); + + String medium(); + + String normal(); + + String ol(); + + String outdent(); + + String red(); + + String removeFormat(); + + String removeLink(); + + String size(); + + String small(); + + String strikeThrough(); + + String subscript(); + + String superscript(); + + String ul(); + + String underline(); + + String white(); + + String xlarge(); + + String xsmall(); + + String xxlarge(); + + String xxsmall(); + + String yellow(); + } + + /** + * We use an inner EventHandler class to avoid exposing event methods on the + * RichTextToolbar itself. + */ + private class EventHandler + implements ClickHandler, ChangeHandler, KeyUpHandler { + + @Override + @SuppressWarnings("deprecation") + public void onChange(ChangeEvent event) { + Object sender = event.getSource(); + if (sender == backColors) { + basic.setBackColor( + backColors.getValue(backColors.getSelectedIndex())); + backColors.setSelectedIndex(0); + } else if (sender == foreColors) { + basic.setForeColor( + foreColors.getValue(foreColors.getSelectedIndex())); + foreColors.setSelectedIndex(0); + } else if (sender == fonts) { + basic.setFontName(fonts.getValue(fonts.getSelectedIndex())); + fonts.setSelectedIndex(0); + } else if (sender == fontSizes) { + basic.setFontSize( + fontSizesConstants[fontSizes.getSelectedIndex() - 1]); + fontSizes.setSelectedIndex(0); + } + } + + @Override + @SuppressWarnings("deprecation") + public void onClick(ClickEvent event) { + Object sender = event.getSource(); + if (sender == bold) { + basic.toggleBold(); + } else if (sender == italic) { + basic.toggleItalic(); + } else if (sender == underline) { + basic.toggleUnderline(); + } else if (sender == subscript) { + basic.toggleSubscript(); + } else if (sender == superscript) { + basic.toggleSuperscript(); + } else if (sender == strikethrough) { + extended.toggleStrikethrough(); + } else if (sender == indent) { + extended.rightIndent(); + } else if (sender == outdent) { + extended.leftIndent(); + } else if (sender == justifyLeft) { + basic.setJustification(RichTextArea.Justification.LEFT); + } else if (sender == justifyCenter) { + basic.setJustification(RichTextArea.Justification.CENTER); + } else if (sender == justifyRight) { + basic.setJustification(RichTextArea.Justification.RIGHT); + } else if (sender == insertImage) { + final String url = Window.prompt("Enter an image URL:", + "http://"); + if (url != null) { + extended.insertImage(url); + } + } else if (sender == createLink) { + final String url = Window.prompt("Enter a link URL:", + "http://"); + if (url != null) { + extended.createLink(url); + } + } else if (sender == removeLink) { + extended.removeLink(); + } else if (sender == hr) { + extended.insertHorizontalRule(); + } else if (sender == ol) { + extended.insertOrderedList(); + } else if (sender == ul) { + extended.insertUnorderedList(); + } else if (sender == removeFormat) { + extended.removeFormat(); + } else if (sender == richText) { + // We use the RichTextArea's onKeyUp event to update the toolbar + // status. This will catch any cases where the user moves the + // cursur using the keyboard, or uses one of the browser's + // built-in keyboard shortcuts. + updateStatus(); + } + } + + @Override + public void onKeyUp(KeyUpEvent event) { + if (event.getSource() == richText) { + // We use the RichTextArea's onKeyUp event to update the toolbar + // status. This will catch any cases where the user moves the + // cursor using the keyboard, or uses one of the browser's + // built-in keyboard shortcuts. + updateStatus(); + } + } + } + + private static final RichTextArea.FontSize[] fontSizesConstants = new RichTextArea.FontSize[] { + RichTextArea.FontSize.XX_SMALL, RichTextArea.FontSize.X_SMALL, + RichTextArea.FontSize.SMALL, RichTextArea.FontSize.MEDIUM, + RichTextArea.FontSize.LARGE, RichTextArea.FontSize.X_LARGE, + RichTextArea.FontSize.XX_LARGE }; + + private final Images images = (Images) GWT.create(Images.class); + private final Strings strings = (Strings) GWT.create(Strings.class); + private final EventHandler handler = new EventHandler(); + + private final RichTextArea richText; + @SuppressWarnings("deprecation") + private final RichTextArea.BasicFormatter basic; + @SuppressWarnings("deprecation") + private final RichTextArea.ExtendedFormatter extended; + + private final FlowPanel outer = new FlowPanel(); + private final FlowPanel topPanel = new FlowPanel(); + private final FlowPanel bottomPanel = new FlowPanel(); + private ToggleButton bold; + private ToggleButton italic; + private ToggleButton underline; + private ToggleButton subscript; + private ToggleButton superscript; + private ToggleButton strikethrough; + private PushButton indent; + private PushButton outdent; + private PushButton justifyLeft; + private PushButton justifyCenter; + private PushButton justifyRight; + private PushButton hr; + private PushButton ol; + private PushButton ul; + private PushButton insertImage; + private PushButton createLink; + private PushButton removeLink; + private PushButton removeFormat; + + private ListBox backColors; + private ListBox foreColors; + private ListBox fonts; + private ListBox fontSizes; + + /** + * Creates a new toolbar that drives the given rich text area. + * + * @param richText + * the rich text area to be controlled + */ + @SuppressWarnings("deprecation") + public VRichTextToolbar(RichTextArea richText) { + this.richText = richText; + basic = richText.getBasicFormatter(); + extended = richText.getExtendedFormatter(); + + outer.add(topPanel); + outer.add(bottomPanel); + topPanel.setStyleName("gwt-RichTextToolbar-top"); + bottomPanel.setStyleName("gwt-RichTextToolbar-bottom"); + + initWidget(outer); + setStyleName("gwt-RichTextToolbar"); + + if (basic != null) { + topPanel.add( + bold = createToggleButton(images.bold(), strings.bold())); + topPanel.add(italic = createToggleButton(images.italic(), + strings.italic())); + topPanel.add(underline = createToggleButton(images.underline(), + strings.underline())); + topPanel.add(subscript = createToggleButton(images.subscript(), + strings.subscript())); + topPanel.add(superscript = createToggleButton(images.superscript(), + strings.superscript())); + topPanel.add(justifyLeft = createPushButton(images.justifyLeft(), + strings.justifyLeft())); + topPanel.add(justifyCenter = createPushButton( + images.justifyCenter(), strings.justifyCenter())); + topPanel.add(justifyRight = createPushButton(images.justifyRight(), + strings.justifyRight())); + } + + if (extended != null) { + topPanel.add(strikethrough = createToggleButton( + images.strikeThrough(), strings.strikeThrough())); + topPanel.add(indent = createPushButton(images.indent(), + strings.indent())); + topPanel.add(outdent = createPushButton(images.outdent(), + strings.outdent())); + topPanel.add(hr = createPushButton(images.hr(), strings.hr())); + topPanel.add(ol = createPushButton(images.ol(), strings.ol())); + topPanel.add(ul = createPushButton(images.ul(), strings.ul())); + topPanel.add(insertImage = createPushButton(images.insertImage(), + strings.insertImage())); + topPanel.add(createLink = createPushButton(images.createLink(), + strings.createLink())); + topPanel.add(removeLink = createPushButton(images.removeLink(), + strings.removeLink())); + topPanel.add(removeFormat = createPushButton(images.removeFormat(), + strings.removeFormat())); + } + + if (basic != null) { + bottomPanel.add(backColors = createColorList("Background")); + bottomPanel.add(foreColors = createColorList("Foreground")); + bottomPanel.add(fonts = createFontList()); + bottomPanel.add(fontSizes = createFontSizes()); + + // We only use these handlers for updating status, so don't hook + // them up unless at least basic editing is supported. + richText.addKeyUpHandler(handler); + richText.addClickHandler(handler); + } + } + + private ListBox createColorList(String caption) { + final ListBox lb = new ListBox(); + lb.addChangeHandler(handler); + lb.setVisibleItemCount(1); + + lb.addItem(caption); + lb.addItem(strings.white(), "white"); + lb.addItem(strings.black(), "black"); + lb.addItem(strings.red(), "red"); + lb.addItem(strings.green(), "green"); + lb.addItem(strings.yellow(), "yellow"); + lb.addItem(strings.blue(), "blue"); + lb.setTabIndex(-1); + return lb; + } + + private ListBox createFontList() { + final ListBox lb = new ListBox(); + lb.addChangeHandler(handler); + lb.setVisibleItemCount(1); + + lb.addItem(strings.font(), ""); + lb.addItem(strings.normal(), "inherit"); + lb.addItem("Times New Roman", "Times New Roman"); + lb.addItem("Arial", "Arial"); + lb.addItem("Courier New", "Courier New"); + lb.addItem("Georgia", "Georgia"); + lb.addItem("Trebuchet", "Trebuchet"); + lb.addItem("Verdana", "Verdana"); + lb.setTabIndex(-1); + return lb; + } + + private ListBox createFontSizes() { + final ListBox lb = new ListBox(); + lb.addChangeHandler(handler); + lb.setVisibleItemCount(1); + + lb.addItem(strings.size()); + lb.addItem(strings.xxsmall()); + lb.addItem(strings.xsmall()); + lb.addItem(strings.small()); + lb.addItem(strings.medium()); + lb.addItem(strings.large()); + lb.addItem(strings.xlarge()); + lb.addItem(strings.xxlarge()); + lb.setTabIndex(-1); + return lb; + } + + private PushButton createPushButton(ImageResource img, String tip) { + final PushButton pb = new PushButton(new Image(img)); + pb.addClickHandler(handler); + pb.setTitle(tip); + pb.setTabIndex(-1); + return pb; + } + + private ToggleButton createToggleButton(ImageResource img, String tip) { + final ToggleButton tb = new ToggleButton(new Image(img)); + tb.addClickHandler(handler); + tb.setTitle(tip); + tb.setTabIndex(-1); + return tb; + } + + /** + * Updates the status of all the stateful buttons. + */ + @SuppressWarnings("deprecation") + private void updateStatus() { + if (basic != null) { + bold.setDown(basic.isBold()); + italic.setDown(basic.isItalic()); + underline.setDown(basic.isUnderlined()); + subscript.setDown(basic.isSubscript()); + superscript.setDown(basic.isSuperscript()); + } + + if (extended != null) { + strikethrough.setDown(extended.isStrikethrough()); + } + } +} diff --git a/client/src/main/java/com/vaadin/client/ui/textarea/TextAreaConnector.java b/client/src/main/java/com/vaadin/client/ui/textarea/TextAreaConnector.java index 1f98b04310..79f529ae5e 100644 --- a/client/src/main/java/com/vaadin/client/ui/textarea/TextAreaConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/textarea/TextAreaConnector.java @@ -25,7 +25,6 @@ import com.vaadin.client.ui.textfield.AbstractTextFieldConnector; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.textarea.TextAreaServerRpc; import com.vaadin.shared.ui.textarea.TextAreaState; -import com.vaadin.shared.ui.textfield.ValueChangeMode; import com.vaadin.ui.TextArea; @Connect(TextArea.class) @@ -46,10 +45,9 @@ public class TextAreaConnector extends AbstractTextFieldConnector { super.init(); getWidget().addChangeHandler(event -> sendValueChange()); getWidget().addDomHandler(event -> { - if (getState().valueChangeMode != ValueChangeMode.BLUR) { - scheduleValueChange(); - } + getValueChangeHandler().scheduleValueChange(); }, InputEvent.getType()); + getWidget().addMouseUpHandler(new ResizeMouseUpHandler()); } diff --git a/client/src/main/java/com/vaadin/client/ui/textfield/AbstractTextFieldConnector.java b/client/src/main/java/com/vaadin/client/ui/textfield/AbstractTextFieldConnector.java index bc8c82f549..d774ba2262 100644 --- a/client/src/main/java/com/vaadin/client/ui/textfield/AbstractTextFieldConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/textfield/AbstractTextFieldConnector.java @@ -15,8 +15,6 @@ */ package com.vaadin.client.ui.textfield; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.annotations.OnStateChange; import com.vaadin.client.ui.AbstractComponentConnector; @@ -25,14 +23,13 @@ import com.vaadin.client.ui.ConnectorFocusAndBlurHandler; import com.vaadin.shared.ui.textfield.AbstractTextFieldClientRpc; import com.vaadin.shared.ui.textfield.AbstractTextFieldServerRpc; import com.vaadin.shared.ui.textfield.AbstractTextFieldState; -import com.vaadin.shared.ui.textfield.ValueChangeMode; import com.vaadin.ui.AbstractTextField; /** * Connector class for AbstractTextField. */ public abstract class AbstractTextFieldConnector - extends AbstractComponentConnector { + extends AbstractComponentConnector implements ValueChangeHandler.Owner { private class AbstractTextFieldClientRpcImpl implements AbstractTextFieldClientRpc { @@ -62,19 +59,18 @@ public abstract class AbstractTextFieldConnector } private int lastSentCursorPosition = -1; - - private Timer valueChangeTrigger = new Timer() { - @Override - public void run() { - Scheduler.get().scheduleDeferred(() -> sendValueChange()); - } - }; + private ValueChangeHandler valueChangeHandler; @Override protected void init() { registerRpc(AbstractTextFieldClientRpc.class, new AbstractTextFieldClientRpcImpl()); ConnectorFocusAndBlurHandler.addHandlers(this); + valueChangeHandler = new ValueChangeHandler(this); + } + + protected ValueChangeHandler getValueChangeHandler() { + return valueChangeHandler; } /** @@ -88,50 +84,19 @@ public abstract class AbstractTextFieldConnector return (AbstractTextFieldWidget) getWidget(); } - /** - * Called whenever a change in the value has been detected. Schedules a - * value change to be sent to the server, depending on the current value - * change mode. - * <p> - * Note that this method does not consider the {@link ValueChangeMode#BLUR} - * mode but assumes that {@link #sendValueChange()} is called directly for - * this mode. - */ - protected void scheduleValueChange() { - switch (getState().valueChangeMode) { - case LAZY: - lazyTextChange(); - break; - case TIMEOUT: - timeoutTextChange(); - break; - case EAGER: - eagerTextChange(); - break; - case BLUR: - // Nothing to schedule for this mode - break; - } - } - - private void lazyTextChange() { - valueChangeTrigger.schedule(getState().valueChangeTimeout); - } - - private void timeoutTextChange() { - if (valueChangeTrigger.isRunning()) { - return; - } - valueChangeTrigger.schedule(getState().valueChangeTimeout); + @Override + public AbstractTextFieldState getState() { + return (AbstractTextFieldState) super.getState(); } - private void eagerTextChange() { - valueChangeTrigger.run(); + @OnStateChange("valueChangeMode") + private void updateValueChangeMode() { + valueChangeHandler.setValueChangeMode(getState().valueChangeMode); } - @Override - public AbstractTextFieldState getState() { - return (AbstractTextFieldState) super.getState(); + @OnStateChange("valueChangeTimeout") + private void updateValueChangeTimeout() { + valueChangeHandler.setValueChangeTimeout(getState().valueChangeTimeout); } @OnStateChange("readOnly") @@ -151,10 +116,12 @@ public abstract class AbstractTextFieldConnector * Sends the updated value and cursor position to the server, if either one * has changed. */ - protected void sendValueChange() { + @Override + public void sendValueChange() { if (!hasStateChanged()) { return; } + lastSentCursorPosition = getAbstractTextField().getCursorPos(); getRpcProxy(AbstractTextFieldServerRpc.class).setText( getAbstractTextField().getValue(), lastSentCursorPosition); diff --git a/client/src/main/java/com/vaadin/client/ui/textfield/TextFieldConnector.java b/client/src/main/java/com/vaadin/client/ui/textfield/TextFieldConnector.java index 0a937de349..84e8e7401b 100644 --- a/client/src/main/java/com/vaadin/client/ui/textfield/TextFieldConnector.java +++ b/client/src/main/java/com/vaadin/client/ui/textfield/TextFieldConnector.java @@ -20,7 +20,6 @@ import com.vaadin.client.ui.VTextField; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.Connect.LoadStyle; import com.vaadin.shared.ui.textfield.TextFieldState; -import com.vaadin.shared.ui.textfield.ValueChangeMode; import com.vaadin.ui.TextField; /** @@ -34,9 +33,7 @@ public class TextFieldConnector extends AbstractTextFieldConnector { super.init(); getWidget().addChangeHandler(event -> sendValueChange()); getWidget().addDomHandler(event -> { - if (getState().valueChangeMode != ValueChangeMode.BLUR) { - scheduleValueChange(); - } + getValueChangeHandler().scheduleValueChange(); }, InputEvent.getType()); } diff --git a/client/src/main/java/com/vaadin/client/ui/textfield/ValueChangeHandler.java b/client/src/main/java/com/vaadin/client/ui/textfield/ValueChangeHandler.java new file mode 100644 index 0000000000..89cca2394f --- /dev/null +++ b/client/src/main/java/com/vaadin/client/ui/textfield/ValueChangeHandler.java @@ -0,0 +1,129 @@ +/* + * Copyright 2000-2016 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui.textfield; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.user.client.Timer; +import com.vaadin.client.ComponentConnector; +import com.vaadin.shared.ui.ValueChangeMode; + +/** + * Helper for dealing with scheduling value change events based on a given mode + * and possibly timeout. + */ +public class ValueChangeHandler { + + /** + * Must be implemented by any user of a ValueChangeHandler. + */ + public interface Owner extends ComponentConnector { + /** + * Sends the current value to the server, if it has changed. + */ + void sendValueChange(); + } + + private Owner owner; + + private Timer valueChangeTrigger = new Timer() { + @Override + public void run() { + Scheduler.get().scheduleDeferred(() -> owner.sendValueChange()); + } + }; + + private int valueChangeTimeout = -1; + + private ValueChangeMode valueChangeMode; + + /** + * Creates a value change handler for the given owner. + * + * @param owner + * the owner connector + */ + public ValueChangeHandler(Owner owner) { + this.owner = owner; + } + + /** + * Called whenever a change in the value has been detected. Schedules a + * value change to be sent to the server, depending on the current value + * change mode. + * <p> + * Note that this method does not consider the {@link ValueChangeMode#BLUR} + * mode but assumes that {@link #sendValueChange()} is called directly for + * this mode. + */ + public void scheduleValueChange() { + switch (valueChangeMode) { + case LAZY: + lazyTextChange(); + break; + case TIMEOUT: + timeoutTextChange(); + break; + case EAGER: + eagerTextChange(); + break; + case BLUR: + // Nothing to schedule for this mode + break; + default: + throw new IllegalStateException("Unknown mode: " + valueChangeMode); + } + } + + private void lazyTextChange() { + valueChangeTrigger.schedule(valueChangeTimeout); + } + + private void timeoutTextChange() { + if (valueChangeTrigger.isRunning()) { + return; + } + valueChangeTrigger.schedule(valueChangeTimeout); + } + + private void eagerTextChange() { + valueChangeTrigger.run(); + } + + /** + * Sets the value change mode to use. + * + * @see ValueChangeMode + * + * @param valueChangeMode + * the value change mode to use + */ + public void setValueChangeMode(ValueChangeMode valueChangeMode) { + this.valueChangeMode = valueChangeMode; + } + + /** + * Sets the value change timeout to use. + * + * @see ValueChangeMode + * + * @param valueChangeTimeout + * the value change timeout + */ + public void setValueChangeTimeout(int valueChangeTimeout) { + this.valueChangeTimeout = valueChangeTimeout; + } + +} diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/VRichTextToolbar$Strings.properties b/client/src/main/resources/com/vaadin/client/ui/richtextarea/VRichTextToolbar$Strings.properties new file mode 100644 index 0000000000..363b704584 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/VRichTextToolbar$Strings.properties @@ -0,0 +1,35 @@ +bold = Toggle Bold +createLink = Create Link +hr = Insert Horizontal Rule +indent = Indent Right +insertImage = Insert Image +italic = Toggle Italic +justifyCenter = Center +justifyLeft = Left Justify +justifyRight = Right Justify +ol = Insert Ordered List +outdent = Indent Left +removeFormat = Remove Formatting +removeLink = Remove Link +strikeThrough = Toggle Strikethrough +subscript = Toggle Subscript +superscript = Toggle Superscript +ul = Insert Unordered List +underline = Toggle Underline +color = Color +black = Black +white = White +red = Red +green = Green +yellow = Yellow +blue = Blue +font = Font +normal = Normal +size = Size +xxsmall = XX-Small +xsmall = X-Small +small = Small +medium = Medium +large = Large +xlarge = X-Large +xxlarge = XX-Large
\ No newline at end of file diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/backColors.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/backColors.gif Binary files differnew file mode 100644 index 0000000000..ddfc1cea2c --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/backColors.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/bold.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/bold.gif Binary files differnew file mode 100644 index 0000000000..7c22eaac68 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/bold.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/createLink.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/createLink.gif Binary files differnew file mode 100644 index 0000000000..1a1412fe0e --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/createLink.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/fontSizes.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/fontSizes.gif Binary files differnew file mode 100644 index 0000000000..c2f4c8cb21 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/fontSizes.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/fonts.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/fonts.gif Binary files differnew file mode 100644 index 0000000000..1629cabb78 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/fonts.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/foreColors.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/foreColors.gif Binary files differnew file mode 100644 index 0000000000..2bb89ef189 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/foreColors.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/gwtLogo.png b/client/src/main/resources/com/vaadin/client/ui/richtextarea/gwtLogo.png Binary files differnew file mode 100644 index 0000000000..80728186d8 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/gwtLogo.png diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/hr.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/hr.gif Binary files differnew file mode 100644 index 0000000000..d507082cf1 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/hr.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/indent.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/indent.gif Binary files differnew file mode 100644 index 0000000000..905421ed76 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/indent.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/insertImage.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/insertImage.gif Binary files differnew file mode 100644 index 0000000000..394ec432a5 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/insertImage.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/italic.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/italic.gif Binary files differnew file mode 100644 index 0000000000..ffe0e97284 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/italic.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyCenter.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyCenter.gif Binary files differnew file mode 100644 index 0000000000..f7d4c4693d --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyCenter.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyLeft.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyLeft.gif Binary files differnew file mode 100644 index 0000000000..bc37a3ed5a --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyLeft.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyRight.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyRight.gif Binary files differnew file mode 100644 index 0000000000..892d569384 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyRight.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/ol.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/ol.gif Binary files differnew file mode 100644 index 0000000000..54f8e4f551 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/ol.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/outdent.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/outdent.gif Binary files differnew file mode 100644 index 0000000000..78fd1b5722 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/outdent.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeFormat.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeFormat.gif Binary files differnew file mode 100644 index 0000000000..cf92c9774f --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeFormat.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeLink.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeLink.gif Binary files differnew file mode 100644 index 0000000000..40721a7bca --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeLink.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/strikeThrough.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/strikeThrough.gif Binary files differnew file mode 100644 index 0000000000..a7a233c023 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/strikeThrough.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/subscript.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/subscript.gif Binary files differnew file mode 100644 index 0000000000..58b6fbb816 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/subscript.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/superscript.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/superscript.gif Binary files differnew file mode 100644 index 0000000000..a6270f6e21 --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/superscript.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/ul.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/ul.gif Binary files differnew file mode 100644 index 0000000000..83f1562bcb --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/ul.gif diff --git a/client/src/main/resources/com/vaadin/client/ui/richtextarea/underline.gif b/client/src/main/resources/com/vaadin/client/ui/richtextarea/underline.gif Binary files differnew file mode 100644 index 0000000000..06f0200fdd --- /dev/null +++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/underline.gif diff --git a/server/src/main/java/com/vaadin/ui/AbstractField.java b/server/src/main/java/com/vaadin/ui/AbstractField.java index f64cec9e66..4946d3c95b 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractField.java +++ b/server/src/main/java/com/vaadin/ui/AbstractField.java @@ -142,21 +142,26 @@ public abstract class AbstractField<T> extends AbstractComponent * * @param value * the new value to set - * @return {@code true} if this event originates from the client, - * {@code false} otherwise. + * @param userOriginated + * {@code true} if this event originates from the client, + * {@code false} otherwise. + * @return <code>true</code> if the value was updated, <code>false</code> + * otherwise */ - protected void setValue(T value, boolean userOriginated) { + protected boolean setValue(T value, boolean userOriginated) { if (userOriginated && isReadOnly()) { - return; + return false; } if (Objects.equals(value, getValue())) { - return; + return false; } doSetValue(value); if (!userOriginated) { markAsDirty(); } fireEvent(createValueChange(userOriginated)); + + return true; } /** diff --git a/server/src/main/java/com/vaadin/ui/AbstractTextField.java b/server/src/main/java/com/vaadin/ui/AbstractTextField.java index e1b0273c6c..b5119ec92c 100644 --- a/server/src/main/java/com/vaadin/ui/AbstractTextField.java +++ b/server/src/main/java/com/vaadin/ui/AbstractTextField.java @@ -27,10 +27,10 @@ import com.vaadin.event.FieldEvents.FocusEvent; import com.vaadin.event.FieldEvents.FocusListener; import com.vaadin.shared.Registration; import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc; +import com.vaadin.shared.ui.ValueChangeMode; import com.vaadin.shared.ui.textfield.AbstractTextFieldClientRpc; import com.vaadin.shared.ui.textfield.AbstractTextFieldServerRpc; import com.vaadin.shared.ui.textfield.AbstractTextFieldState; -import com.vaadin.shared.ui.textfield.ValueChangeMode; import com.vaadin.ui.declarative.DesignAttributeHandler; import com.vaadin.ui.declarative.DesignContext; @@ -40,7 +40,8 @@ import com.vaadin.ui.declarative.DesignContext; * @author Vaadin Ltd. * @since 8.0 */ -public abstract class AbstractTextField extends AbstractField<String> { +public abstract class AbstractTextField extends AbstractField<String> + implements HasValueChangeMode { private final class AbstractTextFieldServerRpcImpl implements AbstractTextFieldServerRpc { @@ -173,6 +174,7 @@ public abstract class AbstractTextField extends AbstractField<String> { /** * Returns the last known cursor position of the field. * + * @return the last known cursor position */ public int getCursorPosition() { return lastKnownCursorPosition; @@ -212,41 +214,17 @@ public abstract class AbstractTextField extends AbstractField<String> { listener); } - /** - * Sets the mode how the TextField triggers {@link ValueChange}s. - * - * @param mode - * the new mode - * - * @see ValueChangeMode - */ + @Override public void setValueChangeMode(ValueChangeMode mode) { getState().valueChangeMode = mode; } - /** - * Returns the currently set {@link ValueChangeMode}. - * - * @return the mode used to trigger {@link ValueChange}s. - * - * @see ValueChangeMode - */ + @Override public ValueChangeMode getValueChangeMode() { return getState(false).valueChangeMode; } - /** - * Sets how often {@link ValueChange}s are triggered when the - * {@link ValueChangeMode} is set to either {@link ValueChangeMode#LAZY} or - * {@link ValueChangeMode#TIMEOUT}. - * - * @param timeout - * timeout in milliseconds, must be greater or equal to 0 - * @throws IllegalArgumentException - * if given timeout is smaller than 0 - * - * @see ValueChangeMode - */ + @Override public void setValueChangeTimeout(int timeout) { if (timeout < 0) { throw new IllegalArgumentException( @@ -255,15 +233,7 @@ public abstract class AbstractTextField extends AbstractField<String> { getState().valueChangeTimeout = timeout; } - /** - * Returns the currently set timeout, in milliseconds, for how often - * {@link ValueChange}s are triggered if the current {@link ValueChangeMode} - * is set to either {@link ValueChangeMode#LAZY} or - * {@link ValueChangeMode#TIMEOUT}. - * - * @return the timeout in milliseconds of how often {@link ValueChange}s are - * triggered. - */ + @Override public int getValueChangeTimeout() { return getState(false).valueChangeTimeout; } @@ -303,7 +273,8 @@ public abstract class AbstractTextField extends AbstractField<String> { /** * Checks if the field is empty. * - * @return true if the field value is an empty string, false otherwise + * @return <code>true</code> if the field value is an empty string, + * <code>false</code> otherwise */ public boolean isEmpty() { return "".equals(getValue()); diff --git a/server/src/main/java/com/vaadin/ui/HasValueChangeMode.java b/server/src/main/java/com/vaadin/ui/HasValueChangeMode.java new file mode 100644 index 0000000000..0d48a29a72 --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/HasValueChangeMode.java @@ -0,0 +1,69 @@ +/* + * Copyright 2000-2016 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.ui; + +import com.vaadin.data.HasValue.ValueChange; +import com.vaadin.shared.ui.ValueChangeMode; + +/** + * Implemented by components which support value change modes. + */ +public interface HasValueChangeMode extends Component { + /** + * Sets the mode how the TextField triggers {@link ValueChange}s. + * + * @param valueChangeMode + * the new mode + * + * @see ValueChangeMode + */ + public void setValueChangeMode(ValueChangeMode valueChangeMode); + + /** + * Returns the currently set {@link ValueChangeMode}. + * + * @return the mode used to trigger {@link ValueChange}s. + * + * @see ValueChangeMode + */ + public ValueChangeMode getValueChangeMode(); + + /** + * Sets how often {@link ValueChange}s are triggered when the + * {@link ValueChangeMode} is set to either {@link ValueChangeMode#LAZY} or + * {@link ValueChangeMode#TIMEOUT}. + * + * @param valueChangeTimeout + * timeout in milliseconds, must be greater or equal to 0 + * @throws IllegalArgumentException + * if given timeout is smaller than 0 + * + * @see ValueChangeMode + */ + public void setValueChangeTimeout(int valueChangeTimeout); + + /** + * Returns the currently set timeout, in milliseconds, for how often + * {@link ValueChange}s are triggered if the current {@link ValueChangeMode} + * is set to either {@link ValueChangeMode#LAZY} or + * {@link ValueChangeMode#TIMEOUT}. + * + * @return the timeout in milliseconds of how often {@link ValueChange}s are + * triggered. + */ + public int getValueChangeTimeout(); + +} diff --git a/server/src/main/java/com/vaadin/ui/PasswordField.java b/server/src/main/java/com/vaadin/ui/PasswordField.java index a3fb4d265a..231236c01f 100644 --- a/server/src/main/java/com/vaadin/ui/PasswordField.java +++ b/server/src/main/java/com/vaadin/ui/PasswordField.java @@ -59,12 +59,6 @@ public class PasswordField extends TextField { setCaption(caption); } - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.AbstractField#readDesign(org.jsoup.nodes.Element , - * com.vaadin.ui.declarative.DesignContext) - */ @Override public void readDesign(Element design, DesignContext designContext) { super.readDesign(design, designContext); @@ -75,12 +69,6 @@ public class PasswordField extends TextField { } } - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.AbstractTextField#writeDesign(org.jsoup.nodes.Element - * , com.vaadin.ui.declarative.DesignContext) - */ @Override public void writeDesign(Element design, DesignContext designContext) { super.writeDesign(design, designContext); diff --git a/server/src/main/java/com/vaadin/ui/RichTextArea.java b/server/src/main/java/com/vaadin/ui/RichTextArea.java new file mode 100644 index 0000000000..49346928be --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/RichTextArea.java @@ -0,0 +1,170 @@ +/* + * Copyright 2000-2016 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 + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.ui; + +import org.jsoup.nodes.Element; + +import com.vaadin.shared.ui.ValueChangeMode; +import com.vaadin.shared.ui.richtextarea.RichTextAreaClientRpc; +import com.vaadin.shared.ui.richtextarea.RichTextAreaServerRpc; +import com.vaadin.shared.ui.richtextarea.RichTextAreaState; +import com.vaadin.ui.declarative.DesignContext; + +/** + * A simple RichTextArea to edit HTML format text. + */ +public class RichTextArea extends AbstractField<String> + implements HasValueChangeMode { + + private class RichTextAreaServerRpcImpl implements RichTextAreaServerRpc { + @Override + public void setText(String text) { + getUI().getConnectorTracker().getDiffState(RichTextArea.this) + .put("value", text); + if (!setValue(text, true)) { + // The value was not updated, this could happen if the field has + // been set to readonly on the server and the client does not + // know about it yet. Must re-send the correct state back. + markAsDirty(); + } + } + } + + /** + * Constructs an empty <code>RichTextArea</code> with no caption. + */ + public RichTextArea() { + super(); + registerRpc(new RichTextAreaServerRpcImpl()); + setValue(""); + } + + /** + * Constructs an empty <code>RichTextArea</code> with the given caption. + * + * @param caption + * the caption for the editor. + */ + public RichTextArea(String caption) { + this(); + setCaption(caption); + } + + /** + * Constructs a new <code>RichTextArea</code> with the given caption and + * initial text contents. + * + * @param caption + * the caption for the editor. + * @param value + * the initial text content of the editor. + */ + public RichTextArea(String caption, String value) { + this(caption); + setValue(value); + } + + @Override + public void readDesign(Element design, DesignContext designContext) { + super.readDesign(design, designContext); + setValue(design.html()); + } + + @Override + public void writeDesign(Element design, DesignContext designContext) { + super.writeDesign(design, designContext); + design.html(getValue()); + } + + @Override + protected RichTextAreaState getState() { + return (RichTextAreaState) super.getState(); + } + + @Override + protected RichTextAreaState getState(boolean markAsDirty) { + return (RichTextAreaState) super.getState(markAsDirty); + } + + @Override + public void setValue(String value) { + if (value == null) { + setValue("", false); + } else { + setValue(value, false); + } + } + + @Override + public String getValue() { + return getState(false).value; + } + + @Override + protected void doSetValue(String value) { + getState().value = value; + } + + /** + * Selects all text in the rich text area. As a side effect, focuses the + * rich text area. + * + * @since 6.5 + */ + public void selectAll() { + getRpcProxy(RichTextAreaClientRpc.class).selectAll(); + focus(); + } + + @Override + public void setValueChangeMode(ValueChangeMode mode) { + getState().valueChangeMode = mode; + } + + @Override + public ValueChangeMode getValueChangeMode() { + return getState(false).valueChangeMode; + } + + @Override + public void setValueChangeTimeout(int timeout) { + getState().valueChangeTimeout = timeout; + + } + + @Override + public int getValueChangeTimeout() { + return getState(false).valueChangeTimeout; + } + + /** + * Checks if the field is empty. + * + * @return <code>true</code> if the field value is an empty string, + * <code>false</code> otherwise + */ + public boolean isEmpty() { + return getValue().length() == 0; + } + + /** + * Clears the value of this field. + */ + public void clear() { + setValue(""); + } +} diff --git a/server/src/test/java/com/vaadin/tests/server/component/abstracttextfield/AbstractTextFieldDeclarativeTest.java b/server/src/test/java/com/vaadin/tests/server/component/abstracttextfield/AbstractTextFieldDeclarativeTest.java index 63367363d5..d7fd06dbe8 100644 --- a/server/src/test/java/com/vaadin/tests/server/component/abstracttextfield/AbstractTextFieldDeclarativeTest.java +++ b/server/src/test/java/com/vaadin/tests/server/component/abstracttextfield/AbstractTextFieldDeclarativeTest.java @@ -17,7 +17,7 @@ package com.vaadin.tests.server.component.abstracttextfield; import org.junit.Test; -import com.vaadin.shared.ui.textfield.ValueChangeMode; +import com.vaadin.shared.ui.ValueChangeMode; import com.vaadin.tests.design.DeclarativeTestBase; import com.vaadin.ui.AbstractTextField; import com.vaadin.ui.TextField; diff --git a/server/src/test/java/com/vaadin/ui/ComponentTest.java b/server/src/test/java/com/vaadin/ui/ComponentTest.java new file mode 100644 index 0000000000..8cd9afa776 --- /dev/null +++ b/server/src/test/java/com/vaadin/ui/ComponentTest.java @@ -0,0 +1,98 @@ +/* + * Copyright 2000-2016 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.ui; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import com.vaadin.server.ClientConnector; +import com.vaadin.server.ServerRpcMethodInvocation; +import com.vaadin.shared.communication.ServerRpc; + +/** + * Base class for component unit tests, providing helper methods for e.g. + * invoking RPC and updating diff state. + */ +public class ComponentTest { + + /** + * Perform operations on the component similar to what would be done when + * the component state is communicated to the client, e.g. update diff state + * and mark as clean. + * + * @param component + * the component to update + */ + public static void syncToClient(AbstractComponent component) { + updateDiffState(component); + component.getUI().getConnectorTracker().markClean(component); + } + + /** + * Checks if the connector has been marked dirty. + * + * @param connector + * the connector to check + * @return <code>true</code> if the connector has been marked dirty, + * <code>false</code> otherwise + */ + public static boolean isDirty(ClientConnector connector) { + return connector.getUI().getConnectorTracker().isDirty(connector); + } + + /** + * Updates the stored diff state from the current component state. + * + * @param rta + * the component to update + */ + public static void updateDiffState(AbstractComponent component) { + component.getUI().getSession().getCommunicationManager() + .encodeState(component, component.getState()); + + } + + /** + * Gets a proxy object which invokes ServerRpc methods. + * + * @param component + * the component which listens to the RPC + * @param serverRpcClass + * the server RPC class + * @return a proxy which can be used to invoke RPC methods + */ + @SuppressWarnings("unchecked") + public static <T extends ServerRpc> T getRpcProxy(Component component, + Class<T> serverRpcClass) { + return (T) Proxy.newProxyInstance(component.getClass().getClassLoader(), + new Class[] { serverRpcClass }, new InvocationHandler() { + + @Override + public Object invoke(Object proxy, Method method, + Object[] args) throws Throwable { + ServerRpcMethodInvocation invocation = new ServerRpcMethodInvocation( + component.getConnectorId(), serverRpcClass, + method.getName(), args.length); + invocation.setParameters(args); + component.getRpcManager(serverRpcClass.getName()) + .applyInvocation(invocation); + return null; + } + }); + } + +} diff --git a/server/src/test/java/com/vaadin/ui/RichTextAreaTest.java b/server/src/test/java/com/vaadin/ui/RichTextAreaTest.java new file mode 100644 index 0000000000..554e002848 --- /dev/null +++ b/server/src/test/java/com/vaadin/ui/RichTextAreaTest.java @@ -0,0 +1,107 @@ +/* + * Copyright 2000-2016 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.ui; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.server.ClientConnector; +import com.vaadin.server.ServerRpcManager.RpcInvocationException; +import com.vaadin.shared.ui.richtextarea.RichTextAreaServerRpc; +import com.vaadin.tests.util.MockUI; + +public class RichTextAreaTest extends ComponentTest { + + @Test + public void initiallyEmpty() { + RichTextArea tf = new RichTextArea(); + Assert.assertTrue(tf.isEmpty()); + } + + @Test + public void setValueServerWhenReadOnly() { + RichTextArea tf = new RichTextArea(); + tf.setReadOnly(true); + tf.setValue("foo"); + Assert.assertEquals("foo", tf.getValue()); + } + + @Test + public void diffStateAfterClientSetValueWhenReadOnly() { + UI ui = new MockUI(); + + // If the client has a non-readonly text field which is set to read-only + // on the server, then any update from the client must cause both the + // readonly state and the old value to be sent + RichTextArea rta = new RichTextArea(); + ui.setContent(rta); + rta.setValue("bar"); + rta.setReadOnly(true); + syncToClient(rta); + + // Client thinks the field says "foo" but it won't be updated because + // the field is readonly + getRpcProxy(rta, RichTextAreaServerRpc.class).setText("foo"); + + // The real value will be sent back as long as the field is marked as + // dirty and diffstate contains what the client has + Assert.assertEquals("foo", getDiffStateString(rta, "value")); + Assert.assertTrue("Component should be marked dirty", isDirty(rta)); + } + + @Test + public void setValueClientNotSentBack() throws RpcInvocationException { + UI ui = new MockUI(); + RichTextArea rta = new RichTextArea(); + ui.setContent(rta); + rta.setValue("bar"); + + updateDiffState(rta); + getRpcProxy(rta, RichTextAreaServerRpc.class).setText("foo"); + Assert.assertEquals("foo", getDiffStateString(rta, "value")); + } + + private String getDiffStateString(ClientConnector connector, String key) { + return connector.getUI().getConnectorTracker().getDiffState(connector) + .get(key).asString(); + } + + @Test + public void setValueClientRefusedWhenReadOnly() { + RichTextArea tf = new RichTextArea(); + tf.setValue("bar"); + tf.setReadOnly(true); + tf.setValue("foo", true); + Assert.assertEquals("bar", tf.getValue()); + } + + @Test + public void setValueNullBecomesEmptyString() { + RichTextArea tf = new RichTextArea(); + tf.setValue(null); + Assert.assertEquals("", tf.getValue()); + } + + @Test + public void emptyAfterClear() { + RichTextArea tf = new RichTextArea(); + tf.setValue("foobar"); + Assert.assertFalse(tf.isEmpty()); + tf.clear(); + Assert.assertTrue(tf.isEmpty()); + } + +} diff --git a/shared/src/main/java/com/vaadin/shared/ui/textfield/ValueChangeMode.java b/shared/src/main/java/com/vaadin/shared/ui/ValueChangeMode.java index 39b1bb668a..00c7f2c548 100644 --- a/shared/src/main/java/com/vaadin/shared/ui/textfield/ValueChangeMode.java +++ b/shared/src/main/java/com/vaadin/shared/ui/ValueChangeMode.java @@ -14,7 +14,7 @@ * the License. */ -package com.vaadin.shared.ui.textfield; +package com.vaadin.shared.ui; /** * Different modes for when and how often field value changes are transmitted diff --git a/shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaClientRpc.java b/shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaClientRpc.java new file mode 100644 index 0000000000..34c7e750db --- /dev/null +++ b/shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaClientRpc.java @@ -0,0 +1,29 @@ +/* + * Copyright 2000-2016 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.shared.ui.richtextarea; + +import com.vaadin.shared.communication.ClientRpc; + +/** + * Server to client RPC interface for RichTextArea. + */ +public interface RichTextAreaClientRpc extends ClientRpc { + + /** + * Selects everything in the field. + */ + void selectAll(); +} diff --git a/shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaServerRpc.java b/shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaServerRpc.java new file mode 100644 index 0000000000..f8d6d9121d --- /dev/null +++ b/shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaServerRpc.java @@ -0,0 +1,33 @@ +/* + * Copyright 2000-2016 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.shared.ui.richtextarea; + +import com.vaadin.shared.communication.ServerRpc; + +/** + * Client to server RPC interface for RichTextArea. + * + */ +public interface RichTextAreaServerRpc extends ServerRpc { + + /** + * Sends the updated text to the server. + * + * @param text + * the text in the field + */ + void setText(String text); +} diff --git a/shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaState.java b/shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaState.java new file mode 100644 index 0000000000..75ce561ec3 --- /dev/null +++ b/shared/src/main/java/com/vaadin/shared/ui/richtextarea/RichTextAreaState.java @@ -0,0 +1,52 @@ +/* + * Copyright 2000-2016 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.shared.ui.richtextarea; + +import com.vaadin.shared.AbstractFieldState; +import com.vaadin.shared.annotations.DelegateToWidget; +import com.vaadin.shared.annotations.NoLayout; +import com.vaadin.shared.ui.ValueChangeMode; + +/** + * State for RichTextArea. + * + */ +public class RichTextAreaState extends AbstractFieldState { + { + primaryStyleName = "v-richtextarea"; + } + + /** + * Maximum character count in text field. + */ + @DelegateToWidget + @NoLayout + public int maxLength = -1; + + /** + * The text in the field. + */ + @DelegateToWidget + @NoLayout + public String value = ""; + + @NoLayout + public ValueChangeMode valueChangeMode = ValueChangeMode.LAZY; + + @NoLayout + public int valueChangeTimeout = 400; + +} diff --git a/shared/src/main/java/com/vaadin/shared/ui/textfield/AbstractTextFieldState.java b/shared/src/main/java/com/vaadin/shared/ui/textfield/AbstractTextFieldState.java index 2eac070b5e..2e7b60d463 100644 --- a/shared/src/main/java/com/vaadin/shared/ui/textfield/AbstractTextFieldState.java +++ b/shared/src/main/java/com/vaadin/shared/ui/textfield/AbstractTextFieldState.java @@ -18,6 +18,7 @@ package com.vaadin.shared.ui.textfield; import com.vaadin.shared.AbstractFieldState; import com.vaadin.shared.annotations.DelegateToWidget; import com.vaadin.shared.annotations.NoLayout; +import com.vaadin.shared.ui.ValueChangeMode; /** * State class for AbstractTextField. diff --git a/themes/src/main/themes/VAADIN/themes/base/textfield/textfield.scss b/themes/src/main/themes/VAADIN/themes/base/textfield/textfield.scss index 8eb4d18740..5c05228ad8 100644 --- a/themes/src/main/themes/VAADIN/themes/base/textfield/textfield.scss +++ b/themes/src/main/themes/VAADIN/themes/base/textfield/textfield.scss @@ -113,7 +113,7 @@ textarea.v-textarea-readonly:focus { margin-right: 2px; } -.v-richtextarea-readonly { +.v-richtextarea.v-readonly { border: none; } diff --git a/uitest/src/main/java/com/vaadin/tests/TestCaptionWrapper.java b/uitest/src/main/java/com/vaadin/tests/TestCaptionWrapper.java index 43960ef70c..4519b28a6d 100644 --- a/uitest/src/main/java/com/vaadin/tests/TestCaptionWrapper.java +++ b/uitest/src/main/java/com/vaadin/tests/TestCaptionWrapper.java @@ -41,7 +41,7 @@ import com.vaadin.ui.Window; import com.vaadin.v7.ui.NativeSelect; import com.vaadin.v7.ui.OptionGroup; import com.vaadin.v7.ui.ProgressIndicator; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; import com.vaadin.v7.ui.Select; import com.vaadin.v7.ui.Table; import com.vaadin.v7.ui.TextField; diff --git a/uitest/src/main/java/com/vaadin/tests/TestForRichTextEditor.java b/uitest/src/main/java/com/vaadin/tests/TestForRichTextEditor.java deleted file mode 100644 index 5ad1032879..0000000000 --- a/uitest/src/main/java/com/vaadin/tests/TestForRichTextEditor.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2000-2016 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 - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.tests; - -import com.vaadin.shared.ui.label.ContentMode; -import com.vaadin.ui.Button; -import com.vaadin.ui.CheckBox; -import com.vaadin.ui.CustomComponent; -import com.vaadin.ui.Label; -import com.vaadin.ui.VerticalLayout; -import com.vaadin.v7.data.Property.ValueChangeEvent; -import com.vaadin.v7.data.Property.ValueChangeListener; -import com.vaadin.v7.ui.RichTextArea; - -/** - * - * @author Vaadin Ltd. - */ -public class TestForRichTextEditor extends CustomComponent - implements ValueChangeListener { - - private final VerticalLayout main = new VerticalLayout(); - - private Label l; - - private RichTextArea rte; - - public TestForRichTextEditor() { - - setCompositionRoot(main); - createNewView(); - } - - public void createNewView() { - main.removeAllComponents(); - main.addComponent(new Label( - "RTE uses google richtextArea and their examples toolbar.")); - - rte = new RichTextArea(); - rte.addListener(this); - - main.addComponent(rte); - - main.addComponent(new Button("commit content to label below")); - - l = new Label("", ContentMode.HTML); - main.addComponent(l); - - CheckBox b = new CheckBox("enabled"); - b.setImmediate(true); - b.addValueChangeListener(event -> rte.setEnabled(!rte.isEnabled())); - main.addComponent(b); - - } - - @Override - public void valueChange(ValueChangeEvent event) { - l.setValue(rte.getValue()); - } - -} diff --git a/uitest/src/main/java/com/vaadin/tests/components/AbstractComponentContainerTest.java b/uitest/src/main/java/com/vaadin/tests/components/AbstractComponentContainerTest.java index ef0a273172..f385dfe84c 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/AbstractComponentContainerTest.java +++ b/uitest/src/main/java/com/vaadin/tests/components/AbstractComponentContainerTest.java @@ -17,7 +17,7 @@ import com.vaadin.ui.NativeButton; import com.vaadin.ui.PopupDateField; import com.vaadin.ui.TabSheet; import com.vaadin.ui.VerticalSplitPanel; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; import com.vaadin.v7.ui.Table; import com.vaadin.v7.ui.TextArea; import com.vaadin.v7.ui.TextField; diff --git a/uitest/src/main/java/com/vaadin/tests/components/notification/NotificationsWaiAria.java b/uitest/src/main/java/com/vaadin/tests/components/notification/NotificationsWaiAria.java index cc0fcd36b9..7eefa585b2 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/notification/NotificationsWaiAria.java +++ b/uitest/src/main/java/com/vaadin/tests/components/notification/NotificationsWaiAria.java @@ -2,7 +2,7 @@ package com.vaadin.tests.components.notification; import com.vaadin.server.Page; import com.vaadin.server.VaadinRequest; -import com.vaadin.shared.ui.textfield.ValueChangeMode; +import com.vaadin.shared.ui.ValueChangeMode; import com.vaadin.shared.ui.ui.NotificationRole; import com.vaadin.tests.components.AbstractTestUI; import com.vaadin.ui.Button; diff --git a/uitest/src/main/java/com/vaadin/tests/components/popupview/PopupViewWithRTE.java b/uitest/src/main/java/com/vaadin/tests/components/popupview/PopupViewWithRTE.java index 200bc94be6..8ee6f3844e 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/popupview/PopupViewWithRTE.java +++ b/uitest/src/main/java/com/vaadin/tests/components/popupview/PopupViewWithRTE.java @@ -5,7 +5,7 @@ import com.vaadin.ui.Component; import com.vaadin.ui.PopupView; import com.vaadin.ui.PopupView.Content; import com.vaadin.ui.VerticalLayout; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; public class PopupViewWithRTE extends TestBase { diff --git a/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaEmptyString.java b/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaEmptyString.java index 17a8c2ea51..01dc10220f 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaEmptyString.java +++ b/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaEmptyString.java @@ -6,7 +6,7 @@ import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.Label; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; public class RichTextAreaEmptyString extends TestBase { diff --git a/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaRelativeHeightResize.java b/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaRelativeHeightResize.java index af5b3a1253..f8f2a40020 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaRelativeHeightResize.java +++ b/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaRelativeHeightResize.java @@ -20,7 +20,7 @@ import com.vaadin.tests.components.AbstractTestUI; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.VerticalLayout; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; public class RichTextAreaRelativeHeightResize extends AbstractTestUI { diff --git a/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaScrolling.java b/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaScrolling.java index f3a16636b7..2c76c0fbdb 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaScrolling.java +++ b/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaScrolling.java @@ -3,7 +3,7 @@ package com.vaadin.tests.components.richtextarea; import com.vaadin.tests.components.TestBase; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.VerticalLayout; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; public class RichTextAreaScrolling extends TestBase { diff --git a/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaSize.java b/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaSize.java index 3884f1d3d1..139b36ece8 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaSize.java +++ b/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaSize.java @@ -3,7 +3,7 @@ package com.vaadin.tests.components.richtextarea; import com.vaadin.tests.components.TestBase; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.VerticalLayout; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; public class RichTextAreaSize extends TestBase { diff --git a/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaTest.java b/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaTest.java index 376e84a1cb..689663c84a 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaTest.java +++ b/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaTest.java @@ -1,56 +1,21 @@ package com.vaadin.tests.components.richtextarea; -import java.util.LinkedHashMap; +import com.vaadin.tests.components.abstractfield.AbstractFieldTest; +import com.vaadin.ui.RichTextArea; -import com.vaadin.tests.components.abstractfield.LegacyAbstractFieldTest; -import com.vaadin.v7.ui.RichTextArea; - -public class RichTextAreaTest extends LegacyAbstractFieldTest<RichTextArea> { +public class RichTextAreaTest extends AbstractFieldTest<RichTextArea, String> { @Override protected Class<RichTextArea> getTestClass() { return RichTextArea.class; } - private Command<RichTextArea, Boolean> nullSelectionAllowedCommand = new Command<RichTextArea, Boolean>() { - - @Override - public void execute(RichTextArea c, Boolean value, Object data) { - c.setNullSettingAllowed(value); - - } - }; - private Command<RichTextArea, String> nullRepresentationCommand = new Command<RichTextArea, String>() { - - @Override - public void execute(RichTextArea c, String value, Object data) { - c.setNullRepresentation(value); - } - }; - @Override protected void createActions() { super.createActions(); - - createSetTextValueAction(CATEGORY_ACTIONS); - - createNullSettingAllowedAction(CATEGORY_FEATURES); - createNullRepresentationAction(CATEGORY_FEATURES); - } - - private void createNullSettingAllowedAction(String category) { - createBooleanAction("Null selection allowed", category, true, - nullSelectionAllowedCommand); - } - - private void createNullRepresentationAction(String category) { - LinkedHashMap<String, String> options = new LinkedHashMap<>(); - options.put("-", null); - options.put("null", "null"); - options.put("This is empty", "This is empty"); - options.put("- Nothing -", "- Nothing -"); - createSelectAction("Null representation", category, options, "null", - nullRepresentationCommand); + createClickAction("Select all", CATEGORY_FEATURES, (rta, a, b) -> { + rta.selectAll(); + }, null); } } diff --git a/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaUpdateWhileTyping.java b/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaUpdateWhileTyping.java index f23e145ec3..846579e6c3 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaUpdateWhileTyping.java +++ b/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaUpdateWhileTyping.java @@ -4,7 +4,7 @@ import com.vaadin.server.VaadinRequest; import com.vaadin.tests.components.AbstractTestUI; import com.vaadin.v7.shared.ui.progressindicator.ProgressIndicatorServerRpc; import com.vaadin.v7.ui.ProgressIndicator; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; public class RichTextAreaUpdateWhileTyping extends AbstractTestUI { diff --git a/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaWithKeyboardShortcuts.java b/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaWithKeyboardShortcuts.java index 481ea55267..2ed52a2d95 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaWithKeyboardShortcuts.java +++ b/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreaWithKeyboardShortcuts.java @@ -11,7 +11,7 @@ import com.vaadin.ui.Panel; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.Window; import com.vaadin.v7.ui.AbstractField; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; @SuppressWarnings("serial") public class RichTextAreaWithKeyboardShortcuts extends TestBase { diff --git a/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreas.java b/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreas.java index 37b9283eb4..8585d19bc2 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreas.java +++ b/uitest/src/main/java/com/vaadin/tests/components/richtextarea/RichTextAreas.java @@ -1,7 +1,7 @@ package com.vaadin.tests.components.richtextarea; import com.vaadin.tests.components.ComponentTestCase; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; public class RichTextAreas extends ComponentTestCase<RichTextArea> { diff --git a/uitest/src/main/java/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckbox.java b/uitest/src/main/java/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckbox.java index 98f90553a1..12040900bb 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckbox.java +++ b/uitest/src/main/java/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckbox.java @@ -2,7 +2,7 @@ package com.vaadin.tests.components.splitpanel; import com.vaadin.annotations.Theme; import com.vaadin.server.VaadinRequest; -import com.vaadin.shared.ui.textfield.ValueChangeMode; +import com.vaadin.shared.ui.ValueChangeMode; import com.vaadin.ui.CheckBox; import com.vaadin.ui.GridLayout; import com.vaadin.ui.Label; diff --git a/uitest/src/main/java/com/vaadin/tests/components/splitpanel/SplitPanelWithRichTextArea.java b/uitest/src/main/java/com/vaadin/tests/components/splitpanel/SplitPanelWithRichTextArea.java index 6bd2eb838e..41a14bb178 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/splitpanel/SplitPanelWithRichTextArea.java +++ b/uitest/src/main/java/com/vaadin/tests/components/splitpanel/SplitPanelWithRichTextArea.java @@ -3,7 +3,7 @@ package com.vaadin.tests.components.splitpanel; import com.vaadin.tests.components.TestBase; import com.vaadin.ui.Label; import com.vaadin.ui.VerticalSplitPanel; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; public class SplitPanelWithRichTextArea extends TestBase { diff --git a/uitest/src/main/java/com/vaadin/tests/components/textfield/MultipleTextChangeEvents.java b/uitest/src/main/java/com/vaadin/tests/components/textfield/MultipleTextChangeEvents.java index 1f6eb569cc..0654c77851 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/textfield/MultipleTextChangeEvents.java +++ b/uitest/src/main/java/com/vaadin/tests/components/textfield/MultipleTextChangeEvents.java @@ -2,8 +2,8 @@ package com.vaadin.tests.components.textfield; import com.vaadin.event.Action; import com.vaadin.event.Action.Handler; +import com.vaadin.shared.ui.ValueChangeMode; import com.vaadin.event.ShortcutAction; -import com.vaadin.shared.ui.textfield.ValueChangeMode; import com.vaadin.tests.components.TestBase; import com.vaadin.tests.util.Log; import com.vaadin.ui.TextField; diff --git a/uitest/src/main/java/com/vaadin/tests/components/textfield/TextFieldsValueChangeMode.java b/uitest/src/main/java/com/vaadin/tests/components/textfield/TextFieldsValueChangeMode.java new file mode 100644 index 0000000000..c989d49c8f --- /dev/null +++ b/uitest/src/main/java/com/vaadin/tests/components/textfield/TextFieldsValueChangeMode.java @@ -0,0 +1,152 @@ +package com.vaadin.tests.components.textfield; + +import com.vaadin.data.HasValue.ValueChange; +import com.vaadin.server.VaadinRequest; +import com.vaadin.shared.ui.ValueChangeMode; +import com.vaadin.tests.components.AbstractTestUIWithLog; +import com.vaadin.tests.util.TestUtils; +import com.vaadin.ui.AbstractField; +import com.vaadin.ui.AbstractTextField; +import com.vaadin.ui.Component; +import com.vaadin.ui.HasValueChangeMode; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.RichTextArea; +import com.vaadin.ui.TextArea; +import com.vaadin.ui.TextField; +import com.vaadin.ui.VerticalLayout; + +public class TextFieldsValueChangeMode extends AbstractTestUIWithLog { + + @Override + protected void setup(VaadinRequest request) { + log.setNumberLogRows(false); + HorizontalLayout hl = new HorizontalLayout(); + hl.addComponent(createFields(TextField.class)); + hl.addComponent(createFields(TextArea.class)); + hl.addComponent(createFields(RichTextArea.class)); + addComponent(hl); + } + + private Component createFields(Class<?> fieldClass) { + VerticalLayout vl = new VerticalLayout(); + String id = fieldClass.getSimpleName().toLowerCase(); + try { + AbstractField<String> f = (AbstractField<String>) fieldClass + .newInstance(); + f.setId(id + "-default"); + f.setCaption(f.getId()); + f.addValueChangeListener(this::logValueChange); + + vl.addComponent(f); + } catch (InstantiationException | IllegalAccessException e) { + e.printStackTrace(); + } + + try { + AbstractField<String> eager = (AbstractField<String>) fieldClass + .newInstance(); + eager.setId(id + "-eager"); + eager.setCaption(eager.getId()); + + eager.addValueChangeListener(this::logValueChange); + ((HasValueChangeMode) eager) + .setValueChangeMode(ValueChangeMode.EAGER); + vl.addComponent(eager); + } catch (InstantiationException | IllegalAccessException e) { + e.printStackTrace(); + } + try { + AbstractField<String> timeout = (AbstractField<String>) fieldClass + .newInstance(); + timeout.setId(id + "-timeout"); + timeout.setCaption(timeout.getId()); + timeout.addValueChangeListener(this::logValueChange); + ((HasValueChangeMode) timeout) + .setValueChangeMode(ValueChangeMode.TIMEOUT); + ((HasValueChangeMode) timeout).setValueChangeTimeout(1000); + vl.addComponent(timeout); + } catch (InstantiationException | IllegalAccessException e) { + e.printStackTrace(); + } + + return vl; + } + + private void logValueChange(ValueChange<String> listener) { + AbstractField<String> field = (AbstractField<String>) listener + .getConnector(); + String msg = "Value change event for " + field.getCaption() + + ", new value: '" + listener.getValue() + "'"; + if (field instanceof AbstractTextField) { + msg += " Cursor at index:" + + ((AbstractTextField) field).getCursorPosition(); + } + log(msg); + + } + + @Override + protected String getTestDescription() { + return "Simple TextChangeEvent test cases."; + } + + /** + * "Autosuggest" + * + * Known issue is timing if suggestion comes while typing more content. IMO + * we will not support this kind of features in default TextField, but + * hopefully make it easily extendable to perfect suggest feature. MT + * 2010-10 + * + */ + private class VaadinDeveloperNameField extends TextField { + private String[] names = new String[] { "Matti Tahvonen", + "Marc Englund", "Joonas Lehtinen", "Jouni Koivuviita", + "Marko Grönroos", "Artur Signell" }; + + public VaadinDeveloperNameField() { + setCaption("Start typing 'old' Vaadin developers."); + addValueChangeListener(listener -> { + boolean atTheEndOfText = listener.getValue() + .length() == getCursorPosition(); + String match = findMatch(listener.getValue()); + if (match != null) { + setStyleName("match"); + String curText = listener.getValue(); + int matchlenght = curText.length(); + // autocomplete if caret is at the end of the text + if (atTheEndOfText) { + suggest(match, matchlenght); + } + } else { + setStyleName("nomatch"); + } + }); + setStyleName("nomatch"); + } + + @Override + public void attach() { + super.attach(); + TestUtils.injectCSS(getUI(), ".match { background:green ;} " + + ".nomatch {background:red;}"); + } + + private void suggest(String match, int matchlenght) { + setValue(match); + setSelection(matchlenght, match.length() - matchlenght); + } + + private String findMatch(String currentTextContent) { + if (currentTextContent.length() > 0) { + for (int i = 0; i < names.length; i++) { + if (names[i].startsWith(currentTextContent)) { + return names[i]; + } + } + } + return null; + } + } + +} diff --git a/uitest/src/main/java/com/vaadin/tests/components/tree/TreeScrolling.java b/uitest/src/main/java/com/vaadin/tests/components/tree/TreeScrolling.java index e72498e6d7..7d9f48c75b 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/tree/TreeScrolling.java +++ b/uitest/src/main/java/com/vaadin/tests/components/tree/TreeScrolling.java @@ -3,7 +3,7 @@ package com.vaadin.tests.components.tree; import com.vaadin.tests.components.AbstractTestCase; import com.vaadin.ui.LegacyWindow; import com.vaadin.ui.VerticalLayout; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; import com.vaadin.v7.ui.TextField; import com.vaadin.v7.ui.Tree; diff --git a/uitest/src/main/java/com/vaadin/tests/components/uitest/components/TextFieldsCssTest.java b/uitest/src/main/java/com/vaadin/tests/components/uitest/components/TextFieldsCssTest.java index a304ae33f0..284914f264 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/uitest/components/TextFieldsCssTest.java +++ b/uitest/src/main/java/com/vaadin/tests/components/uitest/components/TextFieldsCssTest.java @@ -7,7 +7,7 @@ import com.vaadin.ui.PasswordField; import com.vaadin.ui.TextField; import com.vaadin.ui.themes.ChameleonTheme; import com.vaadin.ui.themes.Reindeer; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; import com.vaadin.v7.ui.TextArea; public class TextFieldsCssTest extends GridLayout { diff --git a/uitest/src/main/java/com/vaadin/tests/components/window/WindowCloseShortcuts.java b/uitest/src/main/java/com/vaadin/tests/components/window/WindowCloseShortcuts.java index dca7074f2e..f5ab65a65b 100644 --- a/uitest/src/main/java/com/vaadin/tests/components/window/WindowCloseShortcuts.java +++ b/uitest/src/main/java/com/vaadin/tests/components/window/WindowCloseShortcuts.java @@ -39,7 +39,7 @@ import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.Window; import com.vaadin.ui.declarative.Design; import com.vaadin.ui.declarative.DesignContext; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; @Theme("valo") @SuppressWarnings("serial") diff --git a/uitest/src/main/java/com/vaadin/tests/fields/TabIndexes.java b/uitest/src/main/java/com/vaadin/tests/fields/TabIndexes.java index 0ec27edf5a..12539b84cd 100644 --- a/uitest/src/main/java/com/vaadin/tests/fields/TabIndexes.java +++ b/uitest/src/main/java/com/vaadin/tests/fields/TabIndexes.java @@ -14,13 +14,13 @@ import com.vaadin.ui.GridLayout; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.InlineDateField; import com.vaadin.ui.PopupDateField; +import com.vaadin.ui.RichTextArea; import com.vaadin.ui.Slider; import com.vaadin.v7.ui.ComboBox; import com.vaadin.v7.ui.ListSelect; import com.vaadin.v7.ui.NativeSelect; import com.vaadin.v7.ui.OptionGroup; import com.vaadin.v7.ui.PasswordField; -import com.vaadin.v7.ui.RichTextArea; import com.vaadin.v7.ui.Table; import com.vaadin.v7.ui.TextArea; import com.vaadin.v7.ui.TextField; @@ -28,7 +28,6 @@ import com.vaadin.v7.ui.Tree; import com.vaadin.v7.ui.TreeTable; import com.vaadin.v7.ui.TwinColSelect; -@SuppressWarnings("rawtypes") public class TabIndexes extends AbstractTestUIWithLog { private List<Focusable> fields; diff --git a/uitest/src/main/java/com/vaadin/tests/navigator/NavigatorTest.java b/uitest/src/main/java/com/vaadin/tests/navigator/NavigatorTest.java index 276e7e90d5..392a3f3854 100644 --- a/uitest/src/main/java/com/vaadin/tests/navigator/NavigatorTest.java +++ b/uitest/src/main/java/com/vaadin/tests/navigator/NavigatorTest.java @@ -16,7 +16,7 @@ import com.vaadin.ui.Layout; import com.vaadin.ui.TextField; import com.vaadin.ui.UI; import com.vaadin.ui.VerticalLayout; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; import com.vaadin.v7.ui.Table; public class NavigatorTest extends UI { diff --git a/uitest/src/main/java/com/vaadin/tests/themes/valo/Forms.java b/uitest/src/main/java/com/vaadin/tests/themes/valo/Forms.java index e35318209f..c7a677de7f 100644 --- a/uitest/src/main/java/com/vaadin/tests/themes/valo/Forms.java +++ b/uitest/src/main/java/com/vaadin/tests/themes/valo/Forms.java @@ -34,7 +34,7 @@ import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.themes.ValoTheme; import com.vaadin.v7.ui.ComboBox; import com.vaadin.v7.ui.OptionGroup; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; import com.vaadin.v7.ui.TextArea; import com.vaadin.v7.ui.TextField; diff --git a/uitest/src/main/java/com/vaadin/tests/themes/valo/TextFields.java b/uitest/src/main/java/com/vaadin/tests/themes/valo/TextFields.java index 4daf6f90e0..a626345128 100644 --- a/uitest/src/main/java/com/vaadin/tests/themes/valo/TextFields.java +++ b/uitest/src/main/java/com/vaadin/tests/themes/valo/TextFields.java @@ -28,7 +28,7 @@ import com.vaadin.ui.TextArea; import com.vaadin.ui.TextField; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.themes.ValoTheme; -import com.vaadin.v7.ui.RichTextArea; +import com.vaadin.ui.RichTextArea; public class TextFields extends VerticalLayout implements View { private TestIcon testIcon = new TestIcon(140); diff --git a/uitest/src/main/java/com/vaadin/v7/tests/components/textarea/TextAreaCursorPosition.java b/uitest/src/main/java/com/vaadin/v7/tests/components/textarea/TextAreaCursorPosition.java index eb08a9494b..f5f0b6890b 100644 --- a/uitest/src/main/java/com/vaadin/v7/tests/components/textarea/TextAreaCursorPosition.java +++ b/uitest/src/main/java/com/vaadin/v7/tests/components/textarea/TextAreaCursorPosition.java @@ -1,6 +1,6 @@ package com.vaadin.v7.tests.components.textarea; -import com.vaadin.shared.ui.textfield.ValueChangeMode; +import com.vaadin.shared.ui.ValueChangeMode; import com.vaadin.tests.components.TestBase; import com.vaadin.ui.AbstractField; import com.vaadin.ui.AbstractTextField; diff --git a/uitest/src/main/java/com/vaadin/v7/tests/components/textfield/TextChangeEvents.java b/uitest/src/main/java/com/vaadin/v7/tests/components/textfield/TextChangeEvents.java deleted file mode 100644 index ec66f3bc20..0000000000 --- a/uitest/src/main/java/com/vaadin/v7/tests/components/textfield/TextChangeEvents.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.vaadin.v7.tests.components.textfield; - -import com.vaadin.shared.ui.textfield.ValueChangeMode; -import com.vaadin.tests.components.TestBase; -import com.vaadin.tests.util.Log; -import com.vaadin.tests.util.TestUtils; -import com.vaadin.ui.TextArea; -import com.vaadin.ui.TextField; -import com.vaadin.v7.event.FieldEvents.TextChangeEvent; -import com.vaadin.v7.event.FieldEvents.TextChangeListener; - -public class TextChangeEvents extends TestBase { - Log l = new Log(10); - - @Override - protected void setup() { - - TextField tf = new TextField("Default"); - - TextChangeListener inputEventListener = new TextChangeListener() { - - @Override - public void textChange(TextChangeEvent event) { - l.log("Text change event for " - + event.getComponent().getCaption() - + ", text content currently:'" + event.getText() - + "' Cursor at index:" + event.getCursorPosition()); - } - }; - - tf.addValueChangeListener(listener -> { - l.log("Text change event for " + tf.getCaption() - + ", text content currently:'" + listener.getValue() - + "' Cursor at index:" + tf.getCursorPosition()); - }); - - getLayout().addComponent(tf); - - TextField eager = new TextField("Eager"); - eager.addValueChangeListener(listener -> { - l.log("Text change event for " + eager.getCaption() - + ", text content currently:'" + listener.getValue() - + "' Cursor at index:" + eager.getCursorPosition()); - }); - eager.setValueChangeMode(ValueChangeMode.EAGER); - getLayout().addComponent(eager); - - TextField to = new TextField("Timeout 3s"); - to.addValueChangeListener(listener -> { - l.log("Text change event for " + to.getCaption() - + ", text content currently:'" + listener.getValue() - + "' Cursor at index:" + to.getCursorPosition()); - }); - to.setValueChangeMode(ValueChangeMode.TIMEOUT); - to.setValueChangeTimeout(3000); - getLayout().addComponent(to); - - TextArea ta = new TextArea("Default text area"); - ta.addValueChangeListener(listener -> { - l.log("Text change event for " + ta.getCaption() - + ", text content currently:'" + listener.getValue() - + "' Cursor at index:" + ta.getCursorPosition()); - }); - getLayout().addComponent(ta); - - VaadinDeveloperNameField vd = new VaadinDeveloperNameField(); - vd.addValueChangeListener(listener -> { - l.log("Text change event for " + vd.getCaption() - + ", text content currently:'" + listener.getValue() - + "' Cursor at index:" + vd.getCursorPosition()); - }); - getLayout().addComponent(vd); - - getLayout().addComponent(l); - } - - @Override - protected String getDescription() { - return "Simple TextChangeEvent test cases."; - } - - @Override - protected Integer getTicketNumber() { - return null; - } - - /** - * "Autosuggest" - * - * Known issue is timing if suggestion comes while typing more content. IMO - * we will not support this kind of features in default TextField, but - * hopefully make it easily extendable to perfect suggest feature. MT - * 2010-10 - * - */ - private class VaadinDeveloperNameField extends TextField { - private String[] names = new String[] { "Matti Tahvonen", - "Marc Englund", "Joonas Lehtinen", "Jouni Koivuviita", - "Marko Grönroos", "Artur Signell" }; - - public VaadinDeveloperNameField() { - setCaption("Start typing 'old' Vaadin developers."); - addValueChangeListener(listener -> { - boolean atTheEndOfText = listener.getValue() - .length() == getCursorPosition(); - String match = findMatch(listener.getValue()); - if (match != null) { - setStyleName("match"); - String curText = listener.getValue(); - int matchlenght = curText.length(); - // autocomplete if caret is at the end of the text - if (atTheEndOfText) { - suggest(match, matchlenght); - } - } else { - setStyleName("nomatch"); - } - }); - setStyleName("nomatch"); - } - - @Override - public void attach() { - super.attach(); - TestUtils.injectCSS(getUI(), ".match { background:green ;} " - + ".nomatch {background:red;}"); - } - - private void suggest(String match, int matchlenght) { - setValue(match); - setSelection(matchlenght, match.length() - matchlenght); - } - - private String findMatch(String currentTextContent) { - if (currentTextContent.length() > 0) { - for (int i = 0; i < names.length; i++) { - if (names[i].startsWith(currentTextContent)) { - return names[i]; - } - } - } - return null; - } - } - -} diff --git a/uitest/src/test/java/com/vaadin/tests/components/textfield/TextFieldsValueChangeModeTest.java b/uitest/src/test/java/com/vaadin/tests/components/textfield/TextFieldsValueChangeModeTest.java new file mode 100644 index 0000000000..a9417c7d29 --- /dev/null +++ b/uitest/src/test/java/com/vaadin/tests/components/textfield/TextFieldsValueChangeModeTest.java @@ -0,0 +1,124 @@ +/* + * Copyright 2000-2016 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 + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.textfield; + +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class TextFieldsValueChangeModeTest extends MultiBrowserTest { + + @Test + public void textFieldEager() { + testEager("textfield-eager"); + } + + @Test + public void textAreaEager() { + testEager("textarea-eager"); + } + + @Test + @Ignore("No support for typing in a RichTextArea in TestBench") + public void richTextAreaEager() { + testEager("richtextarea-eager"); + } + + @Test + public void textFieldDefault() { + testDefault("textfield-default"); + } + + @Test + public void textAreaDefault() { + testDefault("textarea-default"); + } + + @Test + @Ignore("No support for typing in a RichTextArea in TestBench") + public void richTextAreaDefault() { + testEager("richtextarea-default"); + } + + @Test + public void textFieldTimeout() { + testTimeout("textfield-timeout"); + } + + @Test + public void textAreaTimeout() { + testTimeout("textarea-timeout"); + } + + @Test + @Ignore("No support for typing in a RichTextArea in TestBench") + public void richTextAreaTimeout() { + testEager("richtextarea-timeout"); + } + + private void testEager(String id) { + openTestURL(); + WebElement eagerTextField = findElement(By.id(id)); + eagerTextField.sendKeys("f"); + eagerTextField.sendKeys("o"); + eagerTextField.sendKeys("o"); + assertLog(id, "f", "fo", "foo"); + } + + private void testDefault(String id) { + openTestURL(); + WebElement eagerTextField = findElement(By.id(id)); + eagerTextField.sendKeys("f"); + eagerTextField.sendKeys("o"); + eagerTextField.sendKeys("o"); + sleep(400); // Default timeout is 400ms + assertLog(id, "foo"); + + } + + private void testTimeout(String id) { + openTestURL(); + WebElement eagerTextField = findElement(By.id(id)); + eagerTextField.sendKeys("f"); + eagerTextField.sendKeys("o"); + eagerTextField.sendKeys("o"); + sleep(1000); // Timer set to 1000ms + eagerTextField.sendKeys("b"); + eagerTextField.sendKeys("a"); + eagerTextField.sendKeys("a"); + sleep(1000); // Timer set to 1000ms + assertLog(id, "foo", "foobaa"); + } + + private void assertLog(String id, String... messages) { + for (int i = 0; i < messages.length; i++) { + String expected = "Value change event for " + id + ", new value: '" + + messages[i] + "'"; + + String log = getLogRow(messages.length - 1 - i); + int tail = log.indexOf(" Cursor at"); + if (tail != -1) { + log = log.substring(0, tail); + } + Assert.assertEquals(expected, log); + } + + } +} |