RichTextArea widget implementation.
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@839 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/RichText.gwt.xml b/user/src/com/google/gwt/user/RichText.gwt.xml
new file mode 100644
index 0000000..4de2308
--- /dev/null
+++ b/user/src/com/google/gwt/user/RichText.gwt.xml
@@ -0,0 +1,48 @@
+<!-- -->
+<!-- Copyright 2006 Google Inc. All Rights Reserved. -->
+<!-- Deferred binding rules for browser selection. -->
+<!-- -->
+<module>
+ <inherits name="com.google.gwt.core.Core" />
+ <inherits name="com.google.gwt.user.UserAgent" />
+ <inherits name="com.google.gwt.i18n.I18N" />
+
+ <!-- IE-specific implementation -->
+ <replace-with
+ class="com.google.gwt.user.client.ui.impl.RichTextAreaImplIE6">
+ <when-type-is
+ class="com.google.gwt.user.client.ui.impl.RichTextAreaImpl" />
+ <when-property-is name="user.agent" value="ie6" />
+ </replace-with>
+
+ <!-- Mozilla-specific implementation -->
+ <replace-with
+ class="com.google.gwt.user.client.ui.impl.RichTextAreaImplMozilla">
+ <when-type-is
+ class="com.google.gwt.user.client.ui.impl.RichTextAreaImpl" />
+ <any>
+ <when-property-is name="user.agent" value="gecko1_8" />
+ <when-property-is name="user.agent" value="gecko" />
+ </any>
+ </replace-with>
+
+ <!-- Safari-specific implementation -->
+ <replace-with
+ class="com.google.gwt.user.client.ui.impl.RichTextAreaImplSafari">
+ <when-type-is
+ class="com.google.gwt.user.client.ui.impl.RichTextAreaImpl" />
+ <any>
+ <when-property-is name="user.agent" value="safari" />
+ </any>
+ </replace-with>
+
+ <!-- Opera-specific implementation -->
+ <replace-with
+ class="com.google.gwt.user.client.ui.impl.RichTextAreaImplOpera">
+ <when-type-is
+ class="com.google.gwt.user.client.ui.impl.RichTextAreaImpl" />
+ <any>
+ <when-property-is name="user.agent" value="opera" />
+ </any>
+ </replace-with>
+</module>
diff --git a/user/src/com/google/gwt/user/User.gwt.xml b/user/src/com/google/gwt/user/User.gwt.xml
index 3a84ffd..aa07463 100644
--- a/user/src/com/google/gwt/user/User.gwt.xml
+++ b/user/src/com/google/gwt/user/User.gwt.xml
@@ -29,5 +29,6 @@
<inherits name="com.google.gwt.user.Focus"/>
<inherits name="com.google.gwt.user.ImageBundle"/>
<inherits name="com.google.gwt.user.ClippedImage"/>
+ <inherits name="com.google.gwt.user.RichText"/>
<inherits name="com.google.gwt.user.SplitPanel"/>
</module>
diff --git a/user/src/com/google/gwt/user/client/ui/RichTextArea.java b/user/src/com/google/gwt/user/client/ui/RichTextArea.java
new file mode 100644
index 0000000..fae4aad
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/RichTextArea.java
@@ -0,0 +1,443 @@
+/*
+ * 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.google.gwt.user.client.ui;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.impl.RichTextAreaImpl;
+
+/**
+ * A rich text editor that allows complex styling and formatting.
+ *
+ * Because some browsers do not support rich text editing, and others support
+ * only a limited subset of functionality, there are two formatter interfaces,
+ * accessed via {@link #getBasicFormatter()} and {@link #getExtendedFormatter()}. A
+ * browser that does not support rich text editing at all will return
+ * <code>null</code> for both of these, while one that supports only the basic
+ * functionality will return <code>null</code> for the latter.
+ *
+ * <p>
+ * <img class='gallery' src='RichTextArea.png'/>
+ * </p>
+ *
+ * <h3>CSS Style Rules</h3>
+ * <ul class="css">
+ * <li>.gwt-RichTextArea { }</li>
+ * </ul>
+ */
+public class RichTextArea extends FocusWidget implements HasHTML,
+ SourcesMouseEvents, SourcesChangeEvents {
+
+ /**
+ * This interface is used to access basic formatting options, when available.
+ * If the implementation supports basic formatting, then
+ * {@link RichTextArea#getBasicFormatter()} will return an instance of this
+ * class.
+ */
+ public interface BasicFormatter {
+
+ /**
+ * Gets the background color.
+ *
+ * @return the background color
+ */
+ String getBackColor();
+
+ /**
+ * Gets the foreground color.
+ *
+ * @return the foreground color
+ */
+ String getForeColor();
+
+ /**
+ * Is the current region bold?
+ *
+ * @return true if the current region is bold
+ */
+ boolean isBold();
+
+ /**
+ * Is the current region italic?
+ *
+ * @return true if the current region is italic
+ */
+ boolean isItalic();
+
+ /**
+ * Is the current region subscript?
+ *
+ * @return true if the current region is subscript
+ */
+ boolean isSubscript();
+
+ /**
+ * Is the current region superscript?
+ *
+ * @return true if the current region is superscript
+ */
+ boolean isSuperscript();
+
+ /**
+ * Is the current region underlined?
+ *
+ * @return true if the current region is underlined
+ */
+ boolean isUnderlined();
+
+ /**
+ * Sets the background color.
+ *
+ * @param color the new background color
+ */
+ void setBackColor(String color);
+
+ /**
+ * Sets the font name.
+ *
+ * @param name the new font name
+ */
+ void setFontName(String name);
+
+ /**
+ * Sets the font size.
+ *
+ * @param fontSize the new font size
+ */
+ void setFontSize(FontSize fontSize);
+
+ /**
+ * Sets the foreground color.
+ *
+ * @param color the new foreground color
+ */
+ void setForeColor(String color);
+
+ /**
+ * Sets the justification.
+ *
+ * @param justification the new justification
+ */
+ void setJustification(Justification justification);
+
+ /**
+ * Toggles bold.
+ */
+ void toggleBold();
+
+ /**
+ * Toggles italic.
+ */
+ void toggleItalic();
+
+ /**
+ * Toggles subscript.
+ */
+ void toggleSubscript();
+
+ /**
+ * Toggles superscript.
+ */
+ void toggleSuperscript();
+
+ /**
+ * Toggles underline.
+ */
+ void toggleUnderline();
+
+ /**
+ * Selects all the text.
+ */
+ void selectAll();
+ }
+
+ /**
+ * Font size enumeration. Represents the seven basic HTML font sizes, as
+ * defined in CSS.
+ */
+ public static class FontSize {
+
+ /**
+ * Represents an XX-Small font.
+ */
+ public static final FontSize XX_SMALL = new FontSize(1);
+
+ /**
+ * Represents an X-Small font.
+ */
+ public static final FontSize X_SMALL = new FontSize(2);
+
+ /**
+ * Represents a Small font.
+ */
+ public static final FontSize SMALL = new FontSize(3);
+
+ /**
+ * Represents a Medium font.
+ */
+ public static final FontSize MEDIUM = new FontSize(4);
+
+ /**
+ * Represents a Large font.
+ */
+ public static final FontSize LARGE = new FontSize(5);
+
+ /**
+ * Represents an X-Large font.
+ */
+ public static final FontSize X_LARGE = new FontSize(6);
+
+ /**
+ * Represents an XX-Large font.
+ */
+ public static final FontSize XX_LARGE = new FontSize(7);
+
+ private int number;
+
+ private FontSize(int number) {
+ this.number = number;
+ }
+
+ /**
+ * Gets the HTML font number associated with this font size.
+ *
+ * @return an integer from 1 to 7 inclusive
+ */
+ public int getNumber() {
+ return number;
+ }
+ }
+
+ /**
+ * This interface is used to access full formatting options, when available.
+ * If the implementation supports full formatting, then
+ * {@link RichTextArea#getExtendedFormatter()} will return an instance of this
+ * class.
+ */
+ public interface ExtendedFormatter extends BasicFormatter {
+
+ /**
+ * Creates a link to the supplied URL.
+ *
+ * @param url the URL to be linked to
+ */
+ void createLink(String url);
+
+ /**
+ * Inserts a horizontal rule.
+ */
+ void insertHorizontalRule();
+
+ /**
+ * Inserts an image element.
+ *
+ * @param url the url of the image to be inserted
+ */
+ void insertImage(String url);
+
+ /**
+ * Starts an numbered list. Indentation will create nested items.
+ */
+ void insertOrderedList();
+
+ /**
+ * Starts an bulleted list. Indentation will create nested items.
+ */
+ void insertUnorderedList();
+
+ /**
+ * Is the current region strikethrough?
+ *
+ * @return true if the current region is strikethrough
+ */
+ boolean isStrikethrough();
+
+ /**
+ * Left indent.
+ */
+ void leftIndent();
+
+ /**
+ * Removes all formatting on the selected text.
+ */
+ void removeFormat();
+
+ /**
+ * Removes any link from the selected text.
+ */
+ void removeLink();
+
+ /**
+ * Right indent.
+ */
+ void rightIndent();
+
+ /**
+ * Toggles strikethrough.
+ */
+ void toggleStrikethrough();
+ }
+
+ /**
+ * Justification enumeration. The three values are <code>left</code>,
+ * <code>right</code>, <code>center</code>.
+ */
+ public static class Justification {
+
+ /**
+ * Center justification.
+ */
+ public static final Justification CENTER = new Justification("Center");
+
+ /**
+ * Left justification.
+ */
+ public static final Justification LEFT = new Justification("Left");
+
+ /**
+ * Right justification.
+ */
+ public static final Justification RIGHT = new Justification("Right");
+
+ private String tag;
+
+ private Justification(String tag) {
+ this.tag = tag;
+ }
+
+ public String toString() {
+ return "Justify " + tag;
+ }
+ }
+
+ private RichTextAreaImpl impl = (RichTextAreaImpl) GWT.create(RichTextAreaImpl.class);
+ private ChangeListenerCollection changeListeners;
+ private MouseListenerCollection mouseListeners;
+
+ /**
+ * Creates a new, blank {@link RichTextArea} object with no stylesheet.
+ */
+ public RichTextArea() {
+ setElement(impl.getElement());
+ setStyleName("gwt-RichTextArea");
+ }
+
+ public void addChangeListener(ChangeListener listener) {
+ if (changeListeners == null) {
+ changeListeners = new ChangeListenerCollection();
+ }
+ changeListeners.add(listener);
+ }
+
+ public void addMouseListener(MouseListener listener) {
+ if (mouseListeners == null) {
+ mouseListeners = new MouseListenerCollection();
+ }
+ mouseListeners.add(listener);
+ }
+
+ /**
+ * Gets the basic rich text formatting interface.
+ *
+ * @return <code>null</code> if basic formatting is not supported
+ */
+ public BasicFormatter getBasicFormatter() {
+ if ((impl instanceof BasicFormatter) && (impl.isBasicEditingSupported())) {
+ return (BasicFormatter) impl;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the full rich text formatting interface.
+ *
+ * @return <code>null</code> if full formatting is not supported
+ */
+ public ExtendedFormatter getExtendedFormatter() {
+ if ((impl instanceof ExtendedFormatter)
+ && (impl.isExtendedEditingSupported())) {
+ return (ExtendedFormatter) impl;
+ }
+ return null;
+ }
+
+ public String getHTML() {
+ return impl.getHTML();
+ }
+
+ public String getText() {
+ return impl.getText();
+ }
+
+ public void onBrowserEvent(Event event) {
+ switch (DOM.eventGetType(event)) {
+ case Event.ONMOUSEDOWN:
+ case Event.ONMOUSEUP:
+ case Event.ONMOUSEMOVE:
+ case Event.ONMOUSEOVER:
+ case Event.ONMOUSEOUT:
+ if (mouseListeners != null) {
+ mouseListeners.fireMouseEvent(this, event);
+ }
+ break;
+
+ case Event.ONCHANGE:
+ if (changeListeners != null) {
+ changeListeners.fireChange(this);
+ }
+ break;
+
+ default:
+ super.onBrowserEvent(event);
+ }
+ }
+
+ public void removeChangeListener(ChangeListener listener) {
+ if (changeListeners != null) {
+ changeListeners.remove(listener);
+ }
+ }
+
+ public void removeMouseListener(MouseListener listener) {
+ if (mouseListeners != null) {
+ mouseListeners.remove(listener);
+ }
+ }
+
+ public void setFocus(boolean focused) {
+ impl.setFocus(focused);
+ }
+
+ public void setHTML(String html) {
+ impl.setHTML(html);
+ }
+
+ public void setText(String text) {
+ impl.setText(text);
+ }
+
+ protected void onAttach() {
+ super.onAttach();
+ impl.initElement();
+ impl.hookEvents(this);
+ }
+
+ protected void onDetach() {
+ super.onDetach();
+ impl.unhookEvents(this);
+ }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImpl.java b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImpl.java
new file mode 100644
index 0000000..ca0c0ff
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImpl.java
@@ -0,0 +1,87 @@
+/*
+ * 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.google.gwt.user.client.ui.impl;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.RichTextArea;
+
+/**
+ * Base class for RichText platform implementations. The default version
+ * simply creates a text area with no rich text support.
+ */
+public class RichTextAreaImpl {
+
+ protected Element elem;
+
+ public RichTextAreaImpl() {
+ elem = createElement();
+ }
+
+ public Element getElement() {
+ return elem;
+ }
+
+ public String getHTML() {
+ return DOM.getAttribute(elem, "value");
+ }
+
+ public String getText() {
+ return DOM.getAttribute(elem, "value");
+ }
+
+ public void hookEvents(RichTextArea owner) {
+ DOM.setEventListener(elem, owner);
+ }
+
+ public void initElement() {
+ DOM.sinkEvents(elem, Event.MOUSEEVENTS | Event.KEYEVENTS | Event.ONCHANGE
+ | Event.ONCLICK);
+ }
+
+ public boolean isBasicEditingSupported() {
+ return false;
+ }
+
+ public boolean isExtendedEditingSupported() {
+ return false;
+ }
+
+ public native void setFocus(boolean focused) /*-{
+ if (focused) {
+ this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.focus();
+ } else {
+ this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.blur();
+ }
+ }-*/;
+
+ public void setHTML(String html) {
+ DOM.setAttribute(elem, "value", html);
+ }
+
+ public void setText(String text) {
+ DOM.setAttribute(elem, "value", text);
+ }
+
+ public void unhookEvents(RichTextArea owner) {
+ DOM.setEventListener(elem, null);
+ }
+
+ protected Element createElement() {
+ return DOM.createTextArea();
+ }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplIE6.java b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplIE6.java
new file mode 100644
index 0000000..153c42d
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplIE6.java
@@ -0,0 +1,64 @@
+/*
+ * 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.google.gwt.user.client.ui.impl;
+
+/**
+ * IE6-specific implementation of rich-text editing.
+ */
+public class RichTextAreaImplIE6 extends RichTextAreaImplStandard {
+
+ public native void initElement() /*-{
+ var elem = this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem;
+ var _this = this;
+
+ elem.onload = function() {
+ var doc = elem.contentWindow.document;
+ doc.designMode = 'On';
+
+ doc.open();
+ doc.write('<html><body></body></html>');
+ doc.close();
+
+ // Initialize event handling.
+ _this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplIE6::initEvents()();
+ };
+ }-*/;
+
+ native void initEvents() /*-{
+ var elem = this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem;
+ var handler = function(evt) {
+ if (elem.__listener) {
+ elem.__listener.
+ @com.google.gwt.user.client.ui.RichTextArea::onBrowserEvent(Lcom/google/gwt/user/client/Event;)(evt);
+ }
+ };
+
+ var body = elem.contentWindow.document.body;
+ body.attachEvent('onkeydown', handler);
+ body.attachEvent('onkeyup', handler);
+ body.attachEvent('onkeypress', handler);
+ body.attachEvent('onmousedown', handler);
+ body.attachEvent('onmouseup', handler);
+ body.attachEvent('onmousemove', handler);
+ body.attachEvent('onmouseover', handler);
+ body.attachEvent('onmouseout', handler);
+ body.attachEvent('onclick', handler);
+ }-*/;
+
+ public native String getText() /*-{
+ return this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.body.innerText;
+ }-*/;
+}
diff --git a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplMozilla.java b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplMozilla.java
new file mode 100644
index 0000000..1984e62
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplMozilla.java
@@ -0,0 +1,28 @@
+/*
+ * 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.google.gwt.user.client.ui.impl;
+
+/**
+ * Mozilla-specific implementation of rich-text editing.
+ */
+public class RichTextAreaImplMozilla extends RichTextAreaImplStandard {
+
+ public void setBackColor(String color) {
+ // Gecko uses 'BackColor' for the *entire area's* background. 'HiliteColor'
+ // does what we actually want.
+ execCommand("HiliteColor", color);
+ }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplOpera.java b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplOpera.java
new file mode 100644
index 0000000..bff1cb9
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplOpera.java
@@ -0,0 +1,152 @@
+/*
+ * 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.google.gwt.user.client.ui.impl;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.RichTextArea;
+
+/**
+ * Opera implementation of rich-text editing.
+ */
+public class RichTextAreaImplOpera extends RichTextAreaImplStandard {
+
+ private static boolean supportsEditing;
+
+ public Element createElement() {
+ supportsEditing = detectEditingSupport();
+ if (supportsEditing) {
+ return super.createElement();
+ }
+ return DOM.createTextArea();
+ }
+
+ public String getHTML() {
+ if (supportsEditing) {
+ return super.getHTML();
+ }
+
+ // Unsupported fallback.
+ return ((RichTextAreaImpl) this).getHTML();
+ }
+
+ public String getText() {
+ if (supportsEditing) {
+ return super.getText();
+ }
+
+ // Unsupported fallback.
+ return ((RichTextAreaImpl) this).getText();
+ }
+
+ public void hookEvents(RichTextArea owner) {
+ if (supportsEditing) {
+ super.hookEvents(owner);
+ return;
+ }
+
+ // Unsupported fallback.
+ ((RichTextAreaImpl) this).hookEvents(owner);
+ }
+
+ public void initElement() {
+ if (supportsEditing) {
+ super.initElement();
+ return;
+ }
+
+ // Unsupported fallback.
+ ((RichTextAreaImpl) this).initElement();
+ }
+
+ public boolean isBasicEditingSupported() {
+ return supportsEditing;
+ }
+
+ public boolean isFullEditingSupported() {
+ return supportsEditing;
+ }
+
+ public void setBackColor(String color) {
+ // Opera uses 'BackColor' for the *entire area's* background. 'HiliteColor'
+ // does what we actually want.
+ execCommand("HiliteColor", color);
+ }
+
+ public native void setFocus(boolean focused) /*-{
+ // Opera needs the *iframe* focused, not its window.
+ if (focused) {
+ this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.focus();
+ } else {
+ this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.blur();
+ }
+ }-*/;
+
+ public void setHTML(String html) {
+ if (supportsEditing) {
+ super.setHTML(html);
+ }
+
+ // Unsupported fallback.
+ DOM.setAttribute(elem, "value", html);
+ }
+
+ public void setText(String text) {
+ if (supportsEditing) {
+ super.setText(text);
+ }
+
+ // Unsupported fallback.
+ DOM.setAttribute(elem, "value", text);
+ }
+
+ public void unhookEvents(RichTextArea owner) {
+ if (supportsEditing) {
+ super.unhookEvents(owner);
+ return;
+ }
+
+ // Unsupported fallback.
+ ((RichTextAreaImpl) this).unhookEvents(owner);
+ }
+
+ void execCommand(String cmd, String param) {
+ if (isRichEditingActive(elem)) {
+ // Opera doesn't need focus for execCommand() to work, but focusing
+ // the editor causes it to lose its selection, so we focus *after*
+ // execCommand().
+ execCommandAssumingFocus(cmd, param);
+ setFocus(true);
+
+ // TODO: Opera has now lost its selection. Figure out a way to restore it
+ // reliably.
+ }
+ }
+
+ boolean queryCommandState(String cmd) {
+ // Opera doesn't need focus (and setting it dumps selection).
+ return queryCommandStateAssumingFocus(cmd);
+ }
+
+ String queryCommandValue(String cmd) {
+ // Opera doesn't need focus (and setting it dumps selection).
+ return queryCommandValueAssumingFocus(cmd);
+ }
+
+ private native boolean detectEditingSupport() /*-{
+ return !!$doc.designMode;
+ }-*/;
+}
diff --git a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplSafari.java b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplSafari.java
new file mode 100644
index 0000000..5569548
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplSafari.java
@@ -0,0 +1,127 @@
+/*
+ * 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.google.gwt.user.client.ui.impl;
+
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.RichTextArea.FontSize;
+
+/**
+ * Safari rich text platform implementation.
+ */
+public class RichTextAreaImplSafari extends RichTextAreaImplStandard {
+
+ private static final String[] sizeNumberCSSValues = new String[] {
+ "medium", "xx-small", "x-small", "small", "medium", "large", "x-large",
+ "xx-large"};
+
+ public Element createElement() {
+ Element elem = super.createElement();
+
+ // Use this opportunity to check if this version of Safari has full rich
+ // text support or not.
+ capabilityTest(elem);
+ return elem;
+ }
+
+ public native boolean isBold() /*-{
+ return !!this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.__gwt_isBold;
+ }-*/;
+
+ public native boolean isExtendedEditingSupported() /*-{
+ // __gwt_fullSupport is set in testCapability().
+ return this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.__gwt_fullSupport;
+ }-*/;
+
+ public native boolean isItalic() /*-{
+ return !!this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.__gwt_isItalic;
+ }-*/;
+
+ public native boolean isUnderlined() /*-{
+ return !!this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.__gwt_isUnderlined;
+ }-*/;
+
+ public native void setFocus(boolean focused) /*-{
+ // Opera needs the *iframe* focused, not its window.
+ if (focused) {
+ this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.focus();
+ this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.__gwt_restoreSelection();
+ } else {
+ this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.blur();
+ }
+ }-*/;
+
+ public void setFontSize(FontSize fontSize) {
+ // Safari only accepts css-style 'small, medium, large, etc' values.
+ int number = fontSize.getNumber();
+ if ((number >= 0) && (number <= 7)) {
+ execCommand("FontSize", sizeNumberCSSValues[number]);
+ return;
+ }
+ }
+
+ native void initEvents() /*-{
+ var elem = this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem;
+ var wnd = elem.contentWindow;
+ var doc = wnd.document;
+
+ // Create an expando on the element to hold the selection state.
+ elem.__gwt_selection = { baseOffset:0, extentOffset:0, baseNode:null, extentNode:null };
+
+ // A function for restoring the selection state.
+ elem.__gwt_restoreSelection = function() {
+ var sel = elem.__gwt_selection;
+ wnd.getSelection().setBaseAndExtent(sel.baseNode, sel.baseOffset, sel.extentNode, sel.extentOffset);
+ };
+
+ // Generic event dispatcher. Also stores selection state.
+ var handler = function(evt) {
+ // Store the editor's selection state.
+ var s = wnd.getSelection();
+ elem.__gwt_selection = {
+ baseOffset:s.baseOffset,
+ extentOffset:s.extentOffset,
+
+ baseNode:s.baseNode,
+ extentNode:s.extentNode
+ };
+
+ // Hang on to bold/italic/underlined states.
+ elem.__gwt_isBold = doc.queryCommandState('Bold');
+ elem.__gwt_isItalic = doc.queryCommandState('Italic');
+ elem.__gwt_isUnderlined = doc.queryCommandState('Underline');
+
+ // Dispatch the event.
+ if (elem.__listener) {
+ elem.__listener.
+ @com.google.gwt.user.client.ui.RichTextArea::onBrowserEvent(Lcom/google/gwt/user/client/Event;)(evt);
+ }
+ };
+
+ wnd.addEventListener('keydown', handler, true);
+ wnd.addEventListener('keyup', handler, true);
+ wnd.addEventListener('keypress', handler, true);
+ wnd.addEventListener('mousedown', handler, true);
+ wnd.addEventListener('mouseup', handler, true);
+ wnd.addEventListener('mousemove', handler, true);
+ wnd.addEventListener('mouseover', handler, true);
+ wnd.addEventListener('mouseout', handler, true);
+ wnd.addEventListener('click', handler, true);
+ }-*/;
+
+ private native void capabilityTest(Element elem) /*-{
+ elem.__gwt_fullSupport = $doc.queryCommandSupported('insertimage');
+ }-*/;
+}
diff --git a/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplStandard.java b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplStandard.java
new file mode 100644
index 0000000..6db11ae
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/impl/RichTextAreaImplStandard.java
@@ -0,0 +1,271 @@
+/*
+ * 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.google.gwt.user.client.ui.impl;
+
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.RichTextArea;
+import com.google.gwt.user.client.ui.RichTextArea.FontSize;
+import com.google.gwt.user.client.ui.RichTextArea.Justification;
+
+/**
+ * Basic rich text platform implementation.
+ */
+public class RichTextAreaImplStandard extends RichTextAreaImpl implements
+ RichTextArea.BasicFormatter, RichTextArea.ExtendedFormatter {
+
+ public native Element createElement() /*-{
+ return $doc.createElement('iframe');
+ }-*/;
+
+ public void createLink(String url) {
+ execCommand("CreateLink", url);
+ }
+
+ public String getBackColor() {
+ return queryCommandValue("BackColor");
+ }
+
+ public String getForeColor() {
+ return queryCommandValue("ForeColor");
+ }
+
+ public native String getHTML() /*-{
+ return this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.body.innerHTML;
+ }-*/;
+
+ public native String getText() /*-{
+ return this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.body.textContent;
+ }-*/;
+
+ public native void hookEvents(RichTextArea owner) /*-{
+ this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.__listener = owner;
+ }-*/;
+
+ public native void initElement() /*-{
+ // Some browsers don't like setting designMode until slightly _after_
+ // the iframe becomes attached to the DOM. Any non-zero timeout will do
+ // just fine.
+ var _this = this;
+ setTimeout(function() {
+ // Turn on design mode.
+ _this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.designMode = 'On';
+
+ // Initialize event handling.
+ _this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplStandard::initEvents()();
+ }, 1);
+ }-*/;
+
+ public void insertHorizontalRule() {
+ execCommand("InsertHorizontalRule", null);
+ }
+
+ public void insertImage(String url) {
+ execCommand("InsertImage", url);
+ }
+
+ public void insertOrderedList() {
+ execCommand("InsertOrderedList", null);
+ }
+
+ public void insertUnorderedList() {
+ execCommand("InsertUnorderedList", null);
+ }
+
+ public boolean isBasicEditingSupported() {
+ return true;
+ }
+
+ public boolean isBold() {
+ return queryCommandState("Bold");
+ }
+
+ public boolean isExtendedEditingSupported() {
+ return true;
+ }
+
+ public boolean isItalic() {
+ return queryCommandState("Italic");
+ }
+
+ public boolean isStrikethrough() {
+ return queryCommandState("Strikethrough");
+ }
+
+ public boolean isSubscript() {
+ return queryCommandState("Subscript");
+ }
+
+ public boolean isSuperscript() {
+ return queryCommandState("Superscript");
+ }
+
+ public boolean isUnderlined() {
+ return queryCommandState("Underline");
+ }
+
+ public void leftIndent() {
+ execCommand("Outdent", null);
+ }
+
+ public void removeFormat() {
+ execCommand("RemoveFormat", null);
+ }
+
+ public void removeLink() {
+ execCommand("Unlink", "false");
+ }
+
+ public void rightIndent() {
+ execCommand("Indent", null);
+ }
+
+ public void selectAll() {
+ execCommand("SelectAll", null);
+ }
+
+ public void setBackColor(String color) {
+ execCommand("BackColor", color);
+ }
+
+ public native void setFocus(boolean focused) /*-{
+ if (focused) {
+ this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.focus();
+ } else {
+ this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.blur();
+ }
+ }-*/;
+
+ public void setFontName(String name) {
+ execCommand("FontName", name);
+ }
+
+ public void setFontSize(FontSize fontSize) {
+ execCommand("FontSize", Integer.toString(fontSize.getNumber()));
+ }
+
+ public void setForeColor(String color) {
+ execCommand("ForeColor", color);
+ }
+
+ public native void setHTML(String html) /*-{
+ this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.body.innerHTML = html;
+ }-*/;
+
+ public void setJustification(Justification justification) {
+ if (justification == Justification.CENTER) {
+ execCommand("JustifyCenter", null);
+ } else if (justification == Justification.LEFT) {
+ execCommand("JustifyLeft", null);
+ } else if (justification == Justification.RIGHT) {
+ execCommand("JustifyRight", null);
+ }
+ }
+
+ public native void setText(String text) /*-{
+ this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.body.textContent = text;
+ }-*/;
+
+ public void toggleBold() {
+ execCommand("Bold", "false");
+ }
+
+ public void toggleItalic() {
+ execCommand("Italic", "false");
+ }
+
+ public void toggleStrikethrough() {
+ execCommand("Strikethrough", "false");
+ }
+
+ public void toggleSubscript() {
+ execCommand("Subscript", "false");
+ }
+
+ public void toggleSuperscript() {
+ execCommand("Superscript", "false");
+ }
+
+ public void toggleUnderline() {
+ execCommand("Underline", "False");
+ }
+
+ public native void unhookEvents(RichTextArea owner) /*-{
+ this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.__listener = null;
+ }-*/;
+
+ void execCommand(String cmd, String param) {
+ if (isRichEditingActive(elem)) {
+ // When executing a command, focus the iframe first, since some commands
+ // don't take properly when it's not focused.
+ setFocus(true);
+ execCommandAssumingFocus(cmd, param);
+ }
+ }
+
+ native void execCommandAssumingFocus(String cmd, String param) /*-{
+ this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.execCommand(cmd, false, param);
+ }-*/;
+
+ native void initEvents() /*-{
+ var elem = this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem;
+ var handler = function(evt) {
+ if (elem.__listener) {
+ elem.__listener.@com.google.gwt.user.client.ui.RichTextArea::onBrowserEvent(Lcom/google/gwt/user/client/Event;)(evt);
+ }
+ };
+
+ var wnd = elem.contentWindow;
+ wnd.addEventListener('keydown', handler, true);
+ wnd.addEventListener('keyup', handler, true);
+ wnd.addEventListener('keypress', handler, true);
+ wnd.addEventListener('mousedown', handler, true);
+ wnd.addEventListener('mouseup', handler, true);
+ wnd.addEventListener('mousemove', handler, true);
+ wnd.addEventListener('mouseover', handler, true);
+ wnd.addEventListener('mouseout', handler, true);
+ wnd.addEventListener('click', handler, true);
+ }-*/;
+
+ native boolean isRichEditingActive(Element e) /*-{
+ return ((e.contentWindow.document.designMode).toUpperCase()) == 'ON';
+ }-*/;
+
+ boolean queryCommandState(String cmd) {
+ if (isRichEditingActive(elem)) {
+ // When executing a command, focus the iframe first, since some commands
+ // don't take properly when it's not focused.
+ setFocus(true);
+ return queryCommandStateAssumingFocus(cmd);
+ } else {
+ return false;
+ }
+ }
+
+ native boolean queryCommandStateAssumingFocus(String cmd) /*-{
+ return !!this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.queryCommandState(cmd);
+ }-*/;
+
+ String queryCommandValue(String cmd) {
+ // When executing a command, focus the iframe first, since some commands
+ // don't take properly when it's not focused.
+ setFocus(true);
+ return queryCommandValueAssumingFocus(cmd);
+ }
+
+ native String queryCommandValueAssumingFocus(String cmd) /*-{
+ return this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.queryCommandValue(cmd);
+ }-*/;
+}