/*****************************************************************************
* Copyright (c) 2015, 2016 CEA LIST.
*
* 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:
* Dirk Fauth <dirk.fauth@googlemail.com> - Initial API and implementation
*
*****************************************************************************/
package org.eclipse.nebula.widgets.richtext;
import java.text.MessageFormat;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.nebula.widgets.richtext.toolbar.JavaCallbackListener;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
/**
* A cell editor that manages HTML entry fields. It uses the {@link RichTextEditor} as editing
* control.
*
* <p>
* It creates the {@link RichTextEditor} instance always using the style bit {@link SWT#EMBEDDED} to
* ensure the editor is opened with a minimum size. Otherwise the editing framework will set the
* bounds to the size of the current cell, which makes the editor unusable.
* </p>
* <p>
* Additionally it supports the style bit {@link SWT#RESIZE} which is set by default if no
* specialized style is set. This enables resizing support of the embedded inline
* {@link RichTextEditor}. By additionally specifying the {@link SWT#MIN} style bit, it is not
* possible for a user to resize the editor below the specified minimum size via
* </p>
* <p>
* As the {@link RichTextEditor} uses a {@link Browser} internally, it is also possible to specify
* the browser type via style bit.
* </p>
*/
public class RichTextCellEditor extends CellEditor {
/**
* The rich text editor control, initially <code>null</code>.
*/
protected RichTextEditor editor;
/**
* The {@link RichTextEditorConfiguration} that should be used for creating the inline rich text editor
* control. If <code>null</code> the default {@link RichTextEditorConfiguration} will be used.
*/
protected RichTextEditorConfiguration editorConfiguration;
private ModifyListener modifyListener;
/**
* Create a resizable {@link RichTextCellEditor} with the default {@link RichTextEditorConfiguration}.
*
* @param parent
* The parent composite.
*/
public RichTextCellEditor(Composite parent) {
this(parent, (RichTextEditorConfiguration) null, SWT.RESIZE);
}
/**
* Create a resizable {@link RichTextCellEditor} with the given
* {@link org.eclipse.nebula.widgets.richtext.toolbar.ToolbarConfiguration}.
*
* @param parent
* The parent composite.
* @param toolbarConfiguration
* The {@link org.eclipse.nebula.widgets.richtext.toolbar.ToolbarConfiguration} that
* should be used for creating the {@link RichTextEditor}.
* @deprecated Use a constructor with {@link RichTextEditorConfiguration} parameter
*/
@Deprecated
public RichTextCellEditor(Composite parent, org.eclipse.nebula.widgets.richtext.toolbar.ToolbarConfiguration toolbarConfiguration) {
this(parent, toolbarConfiguration, SWT.RESIZE);
}
/**
* Create a resizable {@link RichTextCellEditor} with the default {@link RichTextEditorConfiguration}
* and the given style bits.
*
* @param parent
* The parent composite.
* @param style
* The style bits to use.
*/
public RichTextCellEditor(Composite parent, int style) {
this(parent, (RichTextEditorConfiguration) null, style);
}
/**
* Create a resizable {@link RichTextCellEditor} with the given
* {@link org.eclipse.nebula.widgets.richtext.toolbar.ToolbarConfiguration} and the given style
* bits.
*
* @param parent
* The parent composite.
* @param toolbarConfiguration
* The {@link org.eclipse.nebula.widgets.richtext.toolbar.ToolbarConfiguration} that
* should be used for creating the {@link RichTextEditor}.
* @param style
* The style bits to use.
* @deprecated Use a constructor with {@link RichTextEditorConfiguration} parameter
*/
@Deprecated
public RichTextCellEditor(Composite parent, org.eclipse.nebula.widgets.richtext.toolbar.ToolbarConfiguration toolbarConfiguration, int style) {
this(parent, new RichTextEditorConfiguration(toolbarConfiguration), style);
}
/**
* Create a resizable {@link RichTextCellEditor} with the given {@link RichTextEditorConfiguration} and
* the given style bits.
*
* @param parent
* The parent composite.
* @param editorConfiguration
* The {@link RichTextEditorConfiguration} that should be used for creating the
* {@link RichTextEditor}.
* @param style
* The style bits to use.
*/
public RichTextCellEditor(Composite parent, RichTextEditorConfiguration editorConfiguration, int style) {
super(parent, style | SWT.EMBEDDED);
this.editorConfiguration = editorConfiguration;
// call super#create(Composite) now because we override it locally empty
// to be able to set member variables like the ToolbarConfiguration.
super.create(parent);
}
@Override
public void create(Composite parent) {
// We override this method to be empty in here because it is called by
// the super constructor.
// Therefore we are not able to set member variables, e.g. the
// RichTextEditorConfiguration before the RichTextEditor control is created.
}
@Override
protected Control createControl(Composite parent) {
this.editor = new RichTextEditor(parent, this.editorConfiguration, getStyle()) {
@Override
protected int getMinimumHeight() {
return getMinimumDimension().y;
}
@Override
protected int getMinimumWidth() {
return getMinimumDimension().x;
}
};
this.editor.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.ESC) {
fireCancelEditor();
}
// apply the value on key combination CTRL + RETURN
// because RETURN will add a new line to the editor
if (e.keyCode == SWT.CR && e.stateMask == SWT.MOD1) {
fireApplyEditorValue();
}
// as a result of processing the above call, clients may have
// disposed this cell editor
if ((getControl() == null) || getControl().isDisposed()) {
return;
}
}
});
this.editor.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
RichTextCellEditor.this.focusLost();
}
});
this.editor.addModifyListener(getModifyListener());
this.editor.addJavaCallbackListener(new JavaCallbackListener() {
@Override
public void javaExecutionStarted() {
editor.setHandleFocusChanges(false);
}
@Override
public void javaExecutionFinished() {
editor.setHandleFocusChanges(true);
}
});
return this.editor;
}
/**
* @return The minimum dimension used for the rich text editor control.
*/
protected Point getMinimumDimension() {
return new Point(370, 200);
}
@Override
protected Object doGetValue() {
return this.editor.getText();
}
@Override
protected void doSetFocus() {
if (this.editor != null && !this.editor.isDisposed()) {
this.editor.setFocus();
}
}
@Override
protected void doSetValue(Object value) {
Assert.isTrue(this.editor != null);
this.editor.removeModifyListener(getModifyListener());
this.editor.setText(value != null ? (String) value : "");
this.editor.addModifyListener(getModifyListener());
}
/**
* Processes a modify event that occurred in this rich text cell editor. This framework method
* performs validation and sets the error message accordingly, and then reports a change via
* <code>fireEditorValueChanged</code>. Subclasses should call this method at appropriate times.
* Subclasses may extend or reimplement.
*
* @param e
* the SWT modify event
*
* @see TextCellEditor
*/
protected void editOccured(ModifyEvent e) {
String value = this.editor.getText();
if (value == null) {
value = "";//$NON-NLS-1$
}
Object typedValue = value;
boolean oldValidState = isValueValid();
boolean newValidState = isCorrect(typedValue);
if (!newValidState) {
// try to insert the current value into the error message.
setErrorMessage(MessageFormat.format(getErrorMessage(),
new Object[] { value }));
}
valueChanged(oldValidState, newValidState);
}
/**
* Return the modify listener.
*
* @see TextCellEditor
*/
private ModifyListener getModifyListener() {
if (modifyListener == null) {
modifyListener = new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
editOccured(e);
}
};
}
return modifyListener;
}
@Override
protected void focusLost() {
if (this.editor != null && this.editor.isHandleFocusChanges()) {
super.focusLost();
}
}
/**
* Return the created {@link RichTextEditor} control.
*
* @return The {@link RichTextEditor} control, or <code>null</code> if this cell editor has no
* control.
*/
public RichTextEditor getRichTextEditor() {
return (RichTextEditor) getControl();
}
}