summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
authorArtur Signell <artur@vaadin.com>2016-09-01 14:56:41 +0300
committerVaadin Code Review <review@vaadin.com>2016-09-12 08:11:33 +0000
commit78a5468279ddc442ac64d045f5fe4aa79ed9ef6e (patch)
tree4aabf5ea7495e1b0a3e39dc40ab1813bbe67dd69 /client
parentea89e24646cead0eef80dd42a7426fae4e0a6092 (diff)
downloadvaadin-framework-78a5468279ddc442ac64d045f5fe4aa79ed9ef6e.tar.gz
vaadin-framework-78a5468279ddc442ac64d045f5fe4aa79ed9ef6e.zip
Implement new RichTextArea
Change-Id: I6f430c77caaad6d610133f340eba960f2268897e
Diffstat (limited to 'client')
-rw-r--r--client/src/main/java/com/vaadin/client/ui/VRichTextArea.java415
-rw-r--r--client/src/main/java/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java139
-rw-r--r--client/src/main/java/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java476
-rw-r--r--client/src/main/java/com/vaadin/client/ui/textarea/TextAreaConnector.java6
-rw-r--r--client/src/main/java/com/vaadin/client/ui/textfield/AbstractTextFieldConnector.java71
-rw-r--r--client/src/main/java/com/vaadin/client/ui/textfield/TextFieldConnector.java5
-rw-r--r--client/src/main/java/com/vaadin/client/ui/textfield/ValueChangeHandler.java129
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/VRichTextToolbar$Strings.properties35
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/backColors.gifbin0 -> 104 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/bold.gifbin0 -> 900 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/createLink.gifbin0 -> 954 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/fontSizes.gifbin0 -> 96 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/fonts.gifbin0 -> 147 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/foreColors.gifbin0 -> 173 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/gwtLogo.pngbin0 -> 11454 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/hr.gifbin0 -> 853 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/indent.gifbin0 -> 76 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/insertImage.gifbin0 -> 946 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/italic.gifbin0 -> 190 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyCenter.gifbin0 -> 70 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyLeft.gifbin0 -> 71 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyRight.gifbin0 -> 70 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/ol.gifbin0 -> 204 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/outdent.gifbin0 -> 76 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/removeFormat.gifbin0 -> 962 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/removeLink.gifbin0 -> 585 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/strikeThrough.gifbin0 -> 915 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/subscript.gifbin0 -> 933 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/superscript.gifbin0 -> 232 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/ul.gifbin0 -> 133 bytes
-rw-r--r--client/src/main/resources/com/vaadin/client/ui/richtextarea/underline.gifbin0 -> 914 bytes
31 files changed, 1216 insertions, 60 deletions
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>&nbsp;</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
new file mode 100644
index 0000000000..ddfc1cea2c
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/backColors.gif
Binary files differ
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
new file mode 100644
index 0000000000..7c22eaac68
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/bold.gif
Binary files differ
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
new file mode 100644
index 0000000000..1a1412fe0e
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/createLink.gif
Binary files differ
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
new file mode 100644
index 0000000000..c2f4c8cb21
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/fontSizes.gif
Binary files differ
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
new file mode 100644
index 0000000000..1629cabb78
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/fonts.gif
Binary files differ
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
new file mode 100644
index 0000000000..2bb89ef189
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/foreColors.gif
Binary files differ
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
new file mode 100644
index 0000000000..80728186d8
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/gwtLogo.png
Binary files differ
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
new file mode 100644
index 0000000000..d507082cf1
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/hr.gif
Binary files differ
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
new file mode 100644
index 0000000000..905421ed76
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/indent.gif
Binary files differ
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
new file mode 100644
index 0000000000..394ec432a5
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/insertImage.gif
Binary files differ
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
new file mode 100644
index 0000000000..ffe0e97284
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/italic.gif
Binary files differ
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
new file mode 100644
index 0000000000..f7d4c4693d
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyCenter.gif
Binary files differ
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
new file mode 100644
index 0000000000..bc37a3ed5a
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyLeft.gif
Binary files differ
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
new file mode 100644
index 0000000000..892d569384
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/justifyRight.gif
Binary files differ
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
new file mode 100644
index 0000000000..54f8e4f551
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/ol.gif
Binary files differ
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
new file mode 100644
index 0000000000..78fd1b5722
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/outdent.gif
Binary files differ
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
new file mode 100644
index 0000000000..cf92c9774f
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeFormat.gif
Binary files differ
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
new file mode 100644
index 0000000000..40721a7bca
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/removeLink.gif
Binary files differ
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
new file mode 100644
index 0000000000..a7a233c023
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/strikeThrough.gif
Binary files differ
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
new file mode 100644
index 0000000000..58b6fbb816
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/subscript.gif
Binary files differ
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
new file mode 100644
index 0000000000..a6270f6e21
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/superscript.gif
Binary files differ
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
new file mode 100644
index 0000000000..83f1562bcb
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/ul.gif
Binary files differ
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
new file mode 100644
index 0000000000..06f0200fdd
--- /dev/null
+++ b/client/src/main/resources/com/vaadin/client/ui/richtextarea/underline.gif
Binary files differ