/*******************************************************************************
* Copyright (c) 2011, 2016 EclipseSource and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* EclipseSource - initial API and implementation
******************************************************************************/
package org.eclipse.nebula.widgets.richtext;
import static org.eclipse.rap.rwt.widgets.WidgetUtil.getId;
import java.io.IOException;
import java.io.InputStream;
import org.eclipse.nebula.widgets.richtext.toolbar.ToolbarConfiguration;
import org.eclipse.rap.json.JsonObject;
import org.eclipse.rap.json.JsonValue;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.ClientFileLoader;
import org.eclipse.rap.rwt.remote.AbstractOperationHandler;
import org.eclipse.rap.rwt.remote.Connection;
import org.eclipse.rap.rwt.remote.OperationHandler;
import org.eclipse.rap.rwt.remote.RemoteObject;
import org.eclipse.rap.rwt.service.ResourceManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Layout;
/**
* Rich Text Editor control that wraps CKEditor, a web-based WYSIWYG/Rich-Text editor.
*
* @see <a href="http://ckeditor.com/">http://ckeditor.com/</a>
*
* @since 3.1
*/
@SuppressWarnings( "deprecation" )
public class RichTextEditor extends Composite {
private static final String RESOURCES_PATH = "resources/";
private static final String REGISTER_PATH = "ckeditor/";
private static final String[] RESOURCE_FILES = {
"ckeditor.js",
"config.js",
"styles.js",
"contents.css",
"skins/moono/editor.css",
"skins/moono/editor_ie.css",
"skins/moono/editor_gecko.css",
"skins/moono/dialog.css",
"skins/moono/dialog_ie.css",
"skins/moono/icons.png",
"skins/moono/icons_hidpi.png",
"skins/moono/images/arrow.png",
"skins/moono/images/close.png",
"skins/moono/images/lock-open.png",
"skins/moono/images/lock.png",
"skins/moono/images/refresh.png",
"skins/moono/images/hidpi/close.png",
"skins/moono/images/hidpi/lock-open.png",
"skins/moono/images/hidpi/lock.png",
"skins/moono/images/hidpi/refresh.png",
"RichTextEditor.js",
"RichTextEditorHandler.js"
};
private static final String REMOTE_TYPE = "rwt.widgets.RichTextEditor";
private String text = "";
private boolean editable = true;
private final RemoteObject remoteObject;
private final OperationHandler operationHandler = new AbstractOperationHandler() {
@Override
public void handleSet( JsonObject properties ) {
JsonValue textValue = properties.get( "text" );
if( textValue != null ) {
text = textValue.asString();
}
}
};
private final RichTextEditorConfiguration config;
/**
* Constructs a new instance of this class given its parent.
*
* @param parent a composite control which will be the parent of the new instance (cannot be null)
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
* <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
* </ul>
*/
public RichTextEditor( Composite parent ) {
this( parent, SWT.NONE );
}
/**
* Constructs a new instance of this class given its parent.
*
* @param parent a composite control which will be the parent of the new instance (cannot be null)
* @param style the style of control to construct
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
* <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
* </ul>
*/
public RichTextEditor( Composite parent, int style ) {
this( parent, (RichTextEditorConfiguration) null, style );
}
/**
* Constructs a new instance of this class given its parent.
*
* @param parent a composite control which will be the parent of the new instance (cannot be null)
* @param toolbarConfig the
* {@link org.eclipse.nebula.widgets.richtext.toolbar.ToolbarConfiguration} to use or
* <code>null</code> for using the default
* {@link org.eclipse.nebula.widgets.richtext.toolbar.ToolbarConfiguration}
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
* <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
* </ul>
* @deprecated use constructors that take a {@link RichTextEditorConfiguration}
*/
@Deprecated
public RichTextEditor( Composite parent, ToolbarConfiguration toolbarConfig ) {
this( parent, toolbarConfig, SWT.NONE );
}
/**
* Constructs a new instance of this class given its parent, the given
* {@link ToolbarConfiguration} and a style value describing its behavior and appearance.
*
* @param parent the parent composite where this rich text editor should be added to
* @param toolbarConfig the
* {@link org.eclipse.nebula.widgets.richtext.toolbar.ToolbarConfiguration} to use or
* <code>null</code> for using the default
* {@link org.eclipse.nebula.widgets.richtext.toolbar.ToolbarConfiguration}
* @param style the style of widget to construct
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
* <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
* </ul>
* @deprecated use constructors that take a {@link RichTextEditorConfiguration}
*/
@Deprecated
public RichTextEditor( Composite parent, ToolbarConfiguration toolbarConfig, int style ) {
this( parent, toolbarConfig != null ? new RichTextEditorConfiguration( toolbarConfig ) : null, style );
}
/**
* Constructs a new instance of this class given its parent, the given
* {@link ToolbarConfiguration} and a style value describing its behavior and appearance.
*
* @param parent a composite control which will be the parent of the new instance (cannot be null)
* @param editorConfig the {@link RichTextEditorConfiguration} to use or <code>null</code> for
* using the default {@link RichTextEditorConfiguration}
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
* <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
* </ul>
*
* @since 3.2
*/
public RichTextEditor( Composite parent, RichTextEditorConfiguration editorConfig ) {
this( parent, editorConfig, SWT.NONE );
}
/**
* Constructs a new instance of this class given its parent, the given
* {@link ToolbarConfiguration} and a style value describing its behavior and appearance.
*
* @param parent a composite control which will be the parent of the new instance (cannot be null)
* @param editorConfig the {@link RichTextEditorConfiguration} to use or <code>null</code> for
* using the default {@link RichTextEditorConfiguration}
* @param style the style of control to construct
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
* <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
* </ul>
*
* @since 3.2
*/
public RichTextEditor( Composite parent, RichTextEditorConfiguration editorConfig, int style ) {
super( parent, style );
// init editor configuration
if( editorConfig == null ) {
config = new RichTextEditorConfiguration();
} else {
config = editorConfig;
}
registerResources();
loadJavaScript();
Connection connection = RWT.getUISession().getConnection();
remoteObject = connection.createRemoteObject( REMOTE_TYPE );
remoteObject.setHandler( operationHandler );
remoteObject.set( "parent", getId( this ) );
remoteObject.set( "config", config.toJson() );
}
private void registerResources() {
ResourceManager resourceManager = RWT.getResourceManager();
for( String fileName : RESOURCE_FILES ) {
registerFileIfNeeded( resourceManager, fileName );
}
String lang = ( String )config.getOption( RichTextEditorConfiguration.LANGUAGE );
String defaultLang = ( String )config.getOption( RichTextEditorConfiguration.DEFAULT_LANGUAGE );
registerFileIfNeeded( resourceManager, "lang/" + lang + ".js" );
registerFileIfNeeded( resourceManager, "lang/" + defaultLang + ".js" );
}
private static void registerFileIfNeeded( ResourceManager resourceManager, String fileName ) {
boolean isRegistered = resourceManager.isRegistered( REGISTER_PATH + fileName );
if( !isRegistered ) {
try {
register( resourceManager, fileName );
} catch( IOException ioe ) {
throw new IllegalArgumentException( "Failed to load resources", ioe );
}
}
}
private static void loadJavaScript() {
ClientFileLoader loader = RWT.getClient().getService( ClientFileLoader.class );
ResourceManager resourceManager = RWT.getResourceManager();
loader.requireJs( resourceManager.getLocation( REGISTER_PATH + "ckeditor.js" ) );
loader.requireJs( resourceManager.getLocation( REGISTER_PATH + "config.js" ) );
loader.requireJs( resourceManager.getLocation( REGISTER_PATH + "RichTextEditor.js" ) );
loader.requireJs( resourceManager.getLocation( REGISTER_PATH + "RichTextEditorHandler.js" ) );
}
private static void register( ResourceManager resourceManager, String fileName ) throws IOException {
ClassLoader classLoader = RichTextEditor.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream( RESOURCES_PATH + fileName );
try {
resourceManager.register( REGISTER_PATH + fileName, inputStream );
} finally {
inputStream.close();
}
}
/**
* This method returns the {@link RichTextEditorConfiguration} that is used to configure this
* {@link RichTextEditor}. It can be used to change some configurations at runtime.
*
* @return The {@link RichTextEditorConfiguration} used to configure this {@link RichTextEditor}.
* @since 3.2
*/
public RichTextEditorConfiguration getEditorConfiguration() {
return config;
}
@Override
public void setLayout( Layout layout ) {
throw new UnsupportedOperationException( "Cannot change internal layout of RichTextEditor" );
}
@Override
public void setFont( Font font ) {
super.setFont( font );
remoteObject.set( "font", getCssFont() );
}
@Override
public void dispose() {
if( !isDisposed() ) {
remoteObject.destroy();
}
super.dispose();
}
/**
* Set text to the editing area. Can contain HTML tags for styling.
*
* @param text The text to set to the editing area.
*/
public void setText( String text ) {
checkWidget();
if( text == null ) {
SWT.error( SWT.ERROR_NULL_ARGUMENT );
}
this.text = text;
remoteObject.set( "text", text );
}
/**
* Get the text from the editing area. Contains HTML tags for formatting.
*
* @return The text that is currently set in the editing area.
*/
public String getText() {
checkWidget();
return text;
}
/**
* Returns the editable state.
*
* @return whether or not the receiver is editable
*
*/
public boolean isEditable() {
checkWidget();
return editable;
}
/**
* Sets the editable state.
*
* @param editable the new editable state
*/
public void setEditable( boolean editable ) {
checkWidget();
if( this.editable != editable ) {
this.editable = editable;
remoteObject.set( "editable", editable );
}
}
@Override
public boolean isReparentable() {
checkWidget();
// CKEditor can't be reparented
return false;
}
private String getCssFont() {
StringBuilder result = new StringBuilder();
if( getFont() != null ) {
FontData data = getFont().getFontData()[ 0 ];
result.append( data.getHeight() );
result.append( "px " );
result.append( data.getName() );
}
return result.toString();
}
}