| /* |
| * Copyright 2008 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.core.client.JavaScriptException; |
| import com.google.gwt.dom.client.Element; |
| import com.google.gwt.safehtml.shared.annotations.IsSafeHtml; |
| import com.google.gwt.safehtml.shared.annotations.SuppressIsSafeHtmlCastCheck; |
| import com.google.gwt.user.client.DOM; |
| 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. |
| * |
| * <p>Deprecated, may be flattened into RichTextAreaImpl as it has no other |
| * direct subclasses.</p> |
| */ |
| @Deprecated |
| public abstract class RichTextAreaImplStandard extends RichTextAreaImpl implements |
| RichTextArea.Formatter { |
| |
| /** |
| * The message displayed when the formatter is used before the RichTextArea |
| * is initialized. |
| */ |
| private static final String INACTIVE_MESSAGE = "RichTextArea formatters " + |
| "cannot be used until the RichTextArea is attached and focused."; |
| |
| /** |
| * Holds a cached copy of any user setHTML/setText/setEnabled actions until |
| * the real text area is fully initialized. Becomes <code>null</code> after |
| * init. |
| */ |
| private Element beforeInitPlaceholder = DOM.createDiv(); |
| |
| /** |
| * Set to true when the {@link RichTextArea} is attached to the page and |
| * {@link #initElement()} is called. If the {@link RichTextArea} is detached |
| * before {@link #onElementInitialized()} is called, this will be set to |
| * false. See issue 1897 for details. |
| */ |
| protected boolean initializing; |
| |
| /** |
| * Indicates that the text area should be focused as soon as it is loaded. |
| */ |
| private boolean isPendingFocus; |
| |
| /** |
| * True when the element has been attached. |
| */ |
| private boolean isReady; |
| |
| @Override |
| 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"); |
| } |
| |
| @Override |
| public final String getHTML() { |
| return beforeInitPlaceholder == null ? getHTMLImpl() : beforeInitPlaceholder.getInnerHTML(); |
| } |
| |
| @Override |
| public final String getText() { |
| return beforeInitPlaceholder == null ? getTextImpl() : beforeInitPlaceholder.getInnerText(); |
| } |
| |
| @Override |
| public native void initElement() /*-{ |
| // Most 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; |
| _this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplStandard::onElementInitializing()(); |
| setTimeout($entry(function() { |
| // We need to check to see if the content window still is there. It might not be if the RTA |
| // first was attached to the DOM and then quickly was removed before the timeout fired. |
| if (_this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow != null) { |
| // Turn on design mode. |
| _this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.designMode = 'On'; |
| |
| // Send notification that the iframe has reached design mode. |
| _this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplStandard::onElementInitialized()(); |
| } |
| }), 1); |
| }-*/; |
| |
| public void insertHorizontalRule() { |
| execCommand("InsertHorizontalRule", null); |
| } |
| |
| public void insertHTML(@IsSafeHtml String html) { |
| execCommand("InsertHTML", html); |
| } |
| |
| public void insertImage(String url) { |
| execCommand("InsertImage", url); |
| } |
| |
| public void insertOrderedList() { |
| execCommand("InsertOrderedList", null); |
| } |
| |
| public void insertUnorderedList() { |
| execCommand("InsertUnorderedList", null); |
| } |
| |
| public boolean isBold() { |
| return queryCommandState("Bold"); |
| } |
| |
| @Override |
| public boolean isEnabled() { |
| return beforeInitPlaceholder == null ? isEnabledImpl() |
| : !beforeInitPlaceholder.getPropertyBoolean("disabled"); |
| } |
| |
| 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 redo() { |
| execCommand("Redo", "false"); |
| } |
| |
| 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); |
| } |
| |
| @Override |
| public void setEnabled(boolean enabled) { |
| if (beforeInitPlaceholder == null) { |
| setEnabledImpl(enabled); |
| } else { |
| beforeInitPlaceholder.setPropertyBoolean("disabled", !enabled); |
| } |
| } |
| |
| @Override |
| public void setFocus(boolean focused) { |
| if (initializing) { |
| // Issue 3503: if we focus before the iframe is in design mode, the text |
| // caret will not appear. |
| isPendingFocus = focused; |
| } else { |
| setFocusImpl(focused); |
| } |
| } |
| |
| 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); |
| } |
| |
| @Override |
| public final void setHTML(@IsSafeHtml String html) { |
| if (beforeInitPlaceholder == null) { |
| setHTMLImpl(html); |
| } else { |
| beforeInitPlaceholder.setInnerHTML(html); |
| } |
| } |
| |
| public void setJustification(Justification justification) { |
| if (justification == Justification.CENTER) { |
| execCommand("JustifyCenter", null); |
| } else if (justification == Justification.FULL) { |
| execCommand("JustifyFull", null); |
| } else if (justification == Justification.LEFT) { |
| execCommand("JustifyLeft", null); |
| } else if (justification == Justification.RIGHT) { |
| execCommand("JustifyRight", null); |
| } |
| } |
| |
| @Override |
| public final void setText(String text) { |
| if (beforeInitPlaceholder == null) { |
| setTextImpl(text); |
| } else { |
| beforeInitPlaceholder.setInnerText(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 void undo() { |
| execCommand("Undo", "false"); |
| } |
| |
| @Override |
| @SuppressIsSafeHtmlCastCheck |
| public void uninitElement() { |
| isReady = false; |
| |
| // Issue 1897: initElement uses a timeout, so its possible to call this |
| // method after calling initElement, but before the event system is in |
| // place. |
| if (initializing) { |
| initializing = false; |
| return; |
| } |
| |
| // Unhook all custom event handlers when the element is detached. |
| unhookEvents(); |
| |
| // Recreate the placeholder element and store the iframe's contents and the |
| // enabled status in it. This is necessary because some browsers will wipe |
| // the iframe's contents when it is removed from the DOM. |
| @IsSafeHtml String html = getHTML(); // TODO: mXSS |
| boolean enabled = isEnabled(); |
| beforeInitPlaceholder = DOM.createDiv(); |
| beforeInitPlaceholder.setInnerHTML(html); |
| setEnabled(enabled); |
| } |
| |
| protected native String getHTMLImpl() /*-{ |
| return this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.body.innerHTML; |
| }-*/; |
| |
| protected native String getTextImpl() /*-{ |
| return this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.body.textContent; |
| }-*/; |
| |
| @Override |
| protected native void hookEvents() /*-{ |
| var elem = this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem; |
| var wnd = elem.contentWindow; |
| |
| elem.__gwt_handler = $entry(function(evt) { |
| @com.google.gwt.user.client.DOM::dispatchEvent(Lcom/google/gwt/user/client/Event;Lcom/google/gwt/dom/client/Element;)(evt, elem); |
| }); |
| |
| elem.__gwt_focusHandler = function(evt) { |
| if (elem.__gwt_isFocused) { |
| return; |
| } |
| |
| elem.__gwt_isFocused = true; |
| elem.__gwt_handler(evt); |
| }; |
| |
| elem.__gwt_blurHandler = function(evt) { |
| if (!elem.__gwt_isFocused) { |
| return; |
| } |
| |
| elem.__gwt_isFocused = false; |
| elem.__gwt_handler(evt); |
| }; |
| |
| wnd.addEventListener('keydown', elem.__gwt_handler, true); |
| wnd.addEventListener('keyup', elem.__gwt_handler, true); |
| wnd.addEventListener('keypress', elem.__gwt_handler, true); |
| wnd.addEventListener('mousedown', elem.__gwt_handler, true); |
| wnd.addEventListener('mouseup', elem.__gwt_handler, true); |
| wnd.addEventListener('mousemove', elem.__gwt_handler, true); |
| wnd.addEventListener('mouseover', elem.__gwt_handler, true); |
| wnd.addEventListener('mouseout', elem.__gwt_handler, true); |
| wnd.addEventListener('click', elem.__gwt_handler, true); |
| |
| wnd.addEventListener('focus', elem.__gwt_focusHandler, true); |
| wnd.addEventListener('blur', elem.__gwt_blurHandler, true); |
| }-*/; |
| |
| protected native boolean isEnabledImpl() /*-{ |
| var elem = this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem; |
| return elem.contentWindow.document.designMode.toUpperCase() == 'ON'; |
| }-*/; |
| |
| @Override @SuppressIsSafeHtmlCastCheck |
| protected void onElementInitialized() { |
| // Issue 1897: This method is called after a timeout, during which time the |
| // element might by detached. |
| if (!initializing) { |
| return; |
| } |
| initializing = false; |
| isReady = true; |
| |
| // When the iframe is ready, ensure cached content is set. |
| if (beforeInitPlaceholder != null) { |
| setHTMLImpl(beforeInitPlaceholder.getInnerHTML()); |
| setEnabledImpl(isEnabled()); |
| beforeInitPlaceholder = null; |
| } |
| |
| super.onElementInitialized(); |
| |
| // Focus on the element now that it is initialized |
| if (isPendingFocus) { |
| isPendingFocus = false; |
| setFocus(true); |
| } |
| } |
| |
| protected void onElementInitializing() { |
| initializing = true; |
| isPendingFocus = false; |
| } |
| |
| protected native void setEnabledImpl(boolean enabled) /*-{ |
| var elem = this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem; |
| elem.contentWindow.document.designMode = enabled ? 'On' : 'Off'; |
| }-*/; |
| |
| protected native void setFocusImpl(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(); |
| } |
| }-*/; |
| |
| protected native void setHTMLImpl(@IsSafeHtml String html) /*-{ |
| this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.body.innerHTML = html; |
| }-*/; |
| |
| protected native void setTextImpl(String text) /*-{ |
| this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.body.textContent = text; |
| }-*/; |
| |
| protected native void unhookEvents() /*-{ |
| var elem = this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem; |
| var wnd = elem.contentWindow; |
| |
| wnd.removeEventListener('keydown', elem.__gwt_handler, true); |
| wnd.removeEventListener('keyup', elem.__gwt_handler, true); |
| wnd.removeEventListener('keypress', elem.__gwt_handler, true); |
| wnd.removeEventListener('mousedown', elem.__gwt_handler, true); |
| wnd.removeEventListener('mouseup', elem.__gwt_handler, true); |
| wnd.removeEventListener('mousemove', elem.__gwt_handler, true); |
| wnd.removeEventListener('mouseover', elem.__gwt_handler, true); |
| wnd.removeEventListener('mouseout', elem.__gwt_handler, true); |
| wnd.removeEventListener('click', elem.__gwt_handler, true); |
| |
| wnd.removeEventListener('focus', elem.__gwt_focusHandler, true); |
| wnd.removeEventListener('blur', elem.__gwt_blurHandler, true); |
| |
| elem.__gwt_handler = null; |
| elem.__gwt_focusHandler = null; |
| elem.__gwt_blurHandler = null; |
| }-*/; |
| |
| void execCommand(String cmd, String param) { |
| assert isReady : INACTIVE_MESSAGE; |
| if (isReady) { |
| // When executing a command, focus the iframe first, since some commands |
| // don't take properly when it's not focused. |
| setFocus(true); |
| try { |
| execCommandAssumingFocus(cmd, param); |
| } catch (JavaScriptException e) { |
| // In mozilla, editing throws a JS exception if the iframe is |
| // *hidden, but attached*. |
| } |
| } |
| } |
| |
| native void execCommandAssumingFocus(String cmd, String param) /*-{ |
| this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.execCommand(cmd, false, param); |
| }-*/; |
| |
| boolean queryCommandState(String cmd) { |
| if (isReady) { |
| // When executing a command, focus the iframe first, since some commands |
| // don't take properly when it's not focused. |
| setFocus(true); |
| try { |
| return queryCommandStateAssumingFocus(cmd); |
| } catch (JavaScriptException e) { |
| return false; |
| } |
| } |
| 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) { |
| if (isReady) { |
| // When executing a command, focus the iframe first, since some commands |
| // don't take properly when it's not focused. |
| setFocus(true); |
| try { |
| return queryCommandValueAssumingFocus(cmd); |
| } catch (JavaScriptException e) { |
| return ""; |
| } |
| } |
| return ""; |
| } |
| |
| native String queryCommandValueAssumingFocus(String cmd) /*-{ |
| return this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem.contentWindow.document.queryCommandValue(cmd); |
| }-*/; |
| } |