/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.gwt.user.client.ui.rta.internal;
import org.xwiki.gwt.dom.client.Document;
import org.xwiki.gwt.dom.client.Element;
import org.xwiki.gwt.user.client.ui.rta.RichTextArea;
import com.google.gwt.dom.client.BodyElement;
import com.google.gwt.dom.client.IFrameElement;
/**
* Mozilla-specific implementation of rich-text editing.
*
* @version $Id: b1bf9cdca81347cbdc954cf2c8f079f64e298538 $
*/
public class RichTextAreaImplMozilla extends com.google.gwt.user.client.ui.impl.RichTextAreaImplMozilla
{
/**
* {@inheritDoc}
* <p>
* NOTE: Remove this method as soon as Issue 3156 is fixed. <br>
* We also need this method to be able to hook simplification of the DOM tree storing meta data in elements.
* </p>
* <p>
* See http://code.google.com/p/google-web-toolkit/issues/detail?id=3156
* </p>
*
* @see com.google.gwt.user.client.ui.impl.RichTextAreaImplMozilla#setHTMLImpl(String)
*/
@Override
protected void setHTMLImpl(String html)
{
if (elem.getPropertyBoolean(RichTextArea.DIRTY)) {
elem.setPropertyBoolean(RichTextArea.DIRTY, false);
((Element) IFrameElement.as(elem).getContentDocument().getBody().cast()).xSetInnerHTML(html);
}
}
@Override
public native void initElement()
/*-{
var iframe = this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem;
if (!iframe[@org.xwiki.gwt.user.client.ui.rta.RichTextArea::LOADED]
|| iframe[@org.xwiki.gwt.user.client.ui.rta.RichTextArea::INITIALIZING]) {
// We need to signal that the element is initializing even when the rich text area is not fully loaded for
// the case when the rich text area widget is quickly attached and detached.
this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplStandard::onElementInitializing()();
}
if (!iframe[@org.xwiki.gwt.user.client.ui.rta.RichTextArea::INITIALIZING]) return;
this.@com.google.gwt.user.client.ui.impl.RichTextAreaImplStandard::onElementInitialized()();
var outer = this;
iframe.contentWindow.addEventListener('unload', function(event) {
event.target.defaultView && event.target.defaultView.removeEventListener('unload', arguments.callee, false);
iframe[@org.xwiki.gwt.user.client.ui.rta.RichTextArea::LOADED] = false;
// Uninitialize the iframe element only if the event listeners are still attached.
if (iframe.__gwt_handler) {
outer.@com.google.gwt.user.client.ui.impl.RichTextAreaImplStandard::uninitElement()();
}
}, false);
}-*/;
@Override
protected void setEnabledImpl(boolean enabled)
{
if (enabled != isEnabledImpl()) {
Document document = (Document) IFrameElement.as(elem).getContentDocument();
document.setDesignMode(enabled);
// When the rich text area is empty the design mode is not fully initialized until the user types a
// printable key. This causes problems if we add content to the rich text area using the DOM API before the
// user has typed any printable key (e.g. insert a symbol, a link, an image etc.). We found that inserting
// some text using the insertHTML command and then deleting it fixes this problem. Unfortunately by doing
// this we also add an entry to the browser's editing history. We're safe as long as we use a custom history
// mechanism. See https://bugzilla.mozilla.org/show_bug.cgi?id=346523
if (enabled && isEmpty(document)) {
try {
document.execCommand("insertHTML", "x");
document.execCommand("selectAll", null);
document.execCommand("delete", null);
} catch (Exception e) {
// Ignore: execCommand throws an exception if the in-line frame is hidden through CSS. This can
// happen when the rich text area is loaded in background.
}
}
}
}
/**
* A document is empty is its body is empty. The body element is empty if it contains just a {@code br} element.
*
* @param document a DOM document
* @return {@code true} if the given document is empty, {@code false} otherwise
*/
private boolean isEmpty(Document document)
{
BodyElement body = document.getBody();
return body.getChildCount() == 1 && "br".equalsIgnoreCase(body.getFirstChild().getNodeName());
}
@Override
protected boolean isEnabledImpl()
{
return ((Document) IFrameElement.as(elem).getContentDocument()).isDesignMode();
}
@Override
protected void hookEvents()
{
// JSNI doesn't support super.*
// See http://code.google.com/p/google-web-toolkit/issues/detail?id=3507
super.hookEvents();
// Double click event is not caught by default.
// See http://code.google.com/p/google-web-toolkit/issues/detail?id=3944
hookCustomEvents();
}
/**
* Hooks custom events.
*/
protected native void hookCustomEvents()
/*-{
var elem = this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem;
elem.contentWindow.addEventListener('dblclick', elem.__gwt_handler, true);
elem.contentWindow.addEventListener('paste', elem.__gwt_handler, true);
elem.contentWindow.addEventListener('copy', elem.__gwt_handler, true);
}-*/;
@Override
protected void unhookEvents()
{
// Double click event is not caught by default.
// See http://code.google.com/p/google-web-toolkit/issues/detail?id=3944
unhookCustomEvents();
// JSNI doesn't support super.*
// See http://code.google.com/p/google-web-toolkit/issues/detail?id=3507
super.unhookEvents();
}
/**
* Unhooks custom events.
*/
protected native void unhookCustomEvents()
/*-{
var elem = this.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem;
elem.contentWindow.removeEventListener('dblclick', elem.__gwt_handler, true);
elem.contentWindow.removeEventListener('paste', elem.__gwt_handler, true);
elem.contentWindow.removeEventListener('copy', elem.__gwt_handler, true);
}-*/;
@Override
public void setFocus(boolean focused)
{
if (focused) {
// We need to focus the body element (especially if we set contentEditable=true) to initialize the caret
// (otherwise the caret is hidden before the user clicks on the rich text area).
IFrameElement.as(getElement()).getContentDocument().getBody().focus();
}
super.setFocus(focused);
}
}