blob: 3bc593dcffbc71db33b89a45237540daf9c3a0ab [file] [log] [blame]
//------------------------------------------------------------------------------
// Copyright (c) 2005, 2007 IBM Corporation 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:
// IBM Corporation - initial implementation
//------------------------------------------------------------------------------
package org.eclipse.epf.richtext;
import java.io.File;
import java.io.StringWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Platform;
import org.eclipse.epf.common.html.HTMLFormatter;
import org.eclipse.epf.common.serviceability.Logger;
import org.eclipse.epf.common.utils.FileUtil;
import org.eclipse.epf.common.utils.XMLUtil;
import org.eclipse.epf.common.xml.XSLTProcessor;
import org.eclipse.epf.richtext.actions.CopyAction;
import org.eclipse.epf.richtext.actions.CutAction;
import org.eclipse.epf.richtext.actions.FindReplaceAction;
import org.eclipse.epf.richtext.actions.PasteAction;
import org.eclipse.epf.richtext.actions.PastePlainTextAction;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.LocationAdapter;
import org.eclipse.swt.browser.LocationEvent;
import org.eclipse.swt.browser.StatusTextEvent;
import org.eclipse.swt.browser.StatusTextListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.HelpListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.MenuListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.ui.PlatformUI;
/**
* The default rich text control implementation.
* <p>
* The default rich text editor uses XHTML as the underlying markup language for
* the rich text content. It is implemented using a SWT <code>Browser</code>
* control and DHTML (HTML, CSS and JavaScript).
*
* @author Kelvin Low
* @author Jeff Hardy
* @since 1.0
*/
public class RichText implements IRichText {
// Encoded single quote. All single quotes need to be specially encoded to
// avoid JavaScript error when calling Browse.executeCommand().
private static final String ENCODED_SINGLE_QUOTE = "%sq%"; //$NON-NLS-1$
private static final String ENCODED_NEWLINE = "%EOL%"; //$NON-NLS-1$
private static final String STATUS_PREFIX = "$$$"; //$NON-NLS-1$
private static final int STATUS_PREFIX_LENGTH = STATUS_PREFIX.length();
private static final int STATUS_NOP = 0;
private static final int STATUS_INITIALIZED = 1;
private static final int STATUS_MODIFIED = 2;
private static final int STATUS_GET_TEXT = 3;
private static final int STATUS_KEY_DOWN = 4;
private static final int STATUS_KEY_UP = 5;
private static final int STATUS_SELECT_TEXT = 6;
private static final int STATUS_SELECT_CONTROL = 7;
private static final int STATUS_SELECT_NONE = 8;
private static final int STATUS_EXEC_CMD = 9;
// The default base path used for resolving links (<href>, <img>, etc.)
private static final String DEFAULT_BASE_PATH = System
.getProperty("user.home") //$NON-NLS-1$
+ System.getProperty("file.separator") + "rte"; //$NON-NLS-1$ //$NON-NLS-2$
// If true, log debugging info.
protected boolean debug;
// The plug-in logger.
protected Logger logger;
// The underlying SWT Browser used for loading the JavaScript/DHTML editor.
protected Browser editor;
// The underlying OleControlSite for the SWT Browser (Win32 only).
protected Control editorControl;
// The base URL of the rich text control whose content was last
// copied to the clipboard.
protected static URL copyURL;
// The context menu associated with this control.
private Menu contextMenu;
// The folder that contains the supporting CSS and JavaScript files
private String rteFolder;
// The URL that points to the supporting CSS and JavaScript files.
private String rteURL;
// The base path used for resolving links (<href>, <img>, etc.)
private String basePath;
// The DHTML initialization flag.
private boolean initialized;
// The initial focus.
private boolean initializedWithFocus;
// The control's initial text. This is used to cache the HTML source passed
// in via setText()while the DHTML initialization is happening.
private String initialText;
// The control's current text.
private String currentText = ""; //$NON-NLS-1$
// The control's editable flag.
private boolean editable = true;
// The control's modification flag.
private boolean modified;
// The control's text and object selection flag.
private boolean hasSelection;
// The control's text selection
private RichTextSelection richTextSelection = new RichTextSelection();
// JavaScript command execution status code.
private int status = 0;
// The HTML source formatter.
private HTMLFormatter htmlFormatter;
// The SWT event listeners.
private Map listeners;
// The modify listeners.
private List modifyListeners;
// The control's edit flag.
private boolean notifyingModifyListeners = false;
// The controls's focus flag.
private boolean hasFocus = false;
// The control's find/replace text action
private FindReplaceAction findReplaceAction;
/**
* Creates a new instance.
*
* @param parent
* the parent composite
* @param style
* the style for this control
* @param basePath
* the path used for resolving links
*/
public RichText(Composite parent, int style, String basePath) {
debug = RichTextPlugin.getDefault().isDebugging();
logger = RichTextPlugin.getDefault().getLogger();
findReplaceAction = new FindReplaceAction();
rteFolder = RichTextPlugin.getDefault().getInstallPath() + "rte/"; //$NON-NLS-1$
rteURL = XMLUtil.escape("file://" + rteFolder); //$NON-NLS-1$
setBasePath(basePath);
try {
editor = new Browser(parent, SWT.NONE);
if (debug) {
printDebugMessage("RichText", "basePath=" + basePath); //$NON-NLS-1$ //$NON-NLS-2$
}
editor.setLayoutData(new GridData(GridData.FILL_BOTH));
editor.setData(PROPERTY_NAME, this);
init(parent, style);
} catch (Exception e) {
editor = null;
String msg = "Failed to create RichText with basePath=" + basePath; //$NON-NLS-1$
logger.logError(msg, e);
if (debug) {
System.out.println(msg);
e.printStackTrace();
}
}
}
/**
* Creates a new instance.
*
* @param parent
* the parent composite
* @param style
* the style for this control
*/
public RichText(Composite parent, int style) {
this(parent, style, null);
}
/**
* Sets the base path for resolving links.
*/
protected void setBasePath(String path) {
if (path != null && path.length() > 0) {
if (path.startsWith(FileUtil.UNC_PATH_PREFIX)) {
basePath = FileUtil.UNC_PATH_PREFIX
+ FileUtil.appendSeparator(path.substring(
FileUtil.UNC_PATH_PREFIX_LENGTH).replace('\\',
'/'), "/"); //$NON-NLS-1$
} else {
basePath = FileUtil.appendSeparator(path).replace('\\', '/');
}
} else {
basePath = FileUtil.appendSeparator(DEFAULT_BASE_PATH).replace(
'\\', '/');
}
}
/**
* Initializes this control.
*
* @param parent
* the parent composite
* @param style
* the style for this control
* @throws Exception
* when an error has occurred while initialzing this control
*/
protected void init(Composite parent, int style) throws Exception {
try {
addStatusTextListener();
if (debug) {
printDebugMessage("init", "added status text listener"); //$NON-NLS-1$ //$NON-NLS-2$
}
String editorHTML = generateEditorHTML();
if (debug) {
printDebugMessage("init", "generated editor HTML"); //$NON-NLS-1$ //$NON-NLS-2$
}
editor.setText(editorHTML);
if (debug) {
printDebugMessage("init", "loaded editor HTML"); //$NON-NLS-1$ //$NON-NLS-2$
}
contextMenu = new Menu(parent.getShell(), SWT.POP_UP);
editor.setMenu(contextMenu);
fillContextMenu(contextMenu);
if (debug) {
printDebugMessage("init", "added context menu"); //$NON-NLS-1$ //$NON-NLS-2$
}
addListeners();
if (debug) {
printDebugMessage("init", "added listeners"); //$NON-NLS-1$ //$NON-NLS-2$
}
htmlFormatter = new HTMLFormatter();
if (debug) {
printDebugMessage("init", "instantiated HTMLFormatter"); //$NON-NLS-1$ //$NON-NLS-2$
}
} catch (Exception e) {
editor = null;
dispose();
throw e;
}
}
/**
* Returns this rich text control.
*
* @return this rich text control
*/
public Control getControl() {
return editor;
}
/**
* Sets the layout data.
*
* @param layoutData
* the layout data to set
*/
public void setLayoutData(Object layoutData) {
if (editor != null) {
editor.setLayoutData(layoutData);
}
}
/**
* Returns the layout data.
*
* @return this control's layout data
*/
public Object getLayoutData() {
if (editor != null) {
return editor.getLayoutData();
}
return null;
}
/**
* Sets focus to this control.
*/
public void setFocus() {
if (debug) {
printDebugMessage("setFocus, editable=" + editable); //$NON-NLS-1$
}
if (editor != null) {
if (initialized) {
if (!Platform.getOS().equals("win32")) { //$NON-NLS-1$
// Workaround for Mozilla and Firefox rich text editor focus
// issue.
editor.setFocus();
}
executeCommand(RichTextCommand.SET_FOCUS);
hasFocus = true;
} else {
initializedWithFocus = true;
}
}
}
/**
* Tells the control it does not have focus.
*/
public void setBlur() {
if (debug) {
printDebugMessage("setBlur, editable=" + editable); //$NON-NLS-1$
}
if (editor != null) {
if (initialized) {
hasFocus = false;
} else {
initializedWithFocus = false;
}
}
}
/**
* Checks whether this control has focus.
*
* @return <code>true</code> if this control has the user-interface focus
*/
public boolean hasFocus() {
if (editor != null) {
return hasFocus;
}
return false;
}
/**
* Returns the base path used for resolving text and image links.
*
* @return the base path used for resolving links in this control
*/
public String getBasePath() {
return basePath;
}
/**
* Returns the base URL of the rich text control whose content was last
* copied to the clipboard.
*
* @return the base URL of a rich text control
*/
public URL getCopyURL() {
return copyURL;
}
/**
* Sets the base URL of the rich text control whose content was last copied
* to the clipboard.
*/
public void setCopyURL() {
try {
copyURL = new File(basePath).toURL();
} catch (Exception e) {
copyURL = null;
}
}
/**
* Returns the editable state.
*
* @return <code>true</code> if the content is ediatble
*/
public boolean getEditable() {
return editable;
}
/**
* Sets the editable state.
*
* @param editable
* the editable state
*/
public void setEditable(boolean editable) {
this.editable = editable;
if (initialized) {
executeCommand(RichTextCommand.SET_EDITABLE, "" + editable); //$NON-NLS-1$
}
}
/**
* Checks whether the content has been modified.
*
* @return <code>true</code> if the content has been modified
*/
public boolean getModified() {
return modified;
}
/**
* Sets the modified state.
*
* @param modified
* the modified state
*/
public void setModified(boolean modified) {
this.modified = modified;
}
/**
* Returns the rich text content.
*
* @return the rich text content formatted in a markup language
*/
public String getText() {
if (editor != null && initialized) {
try {
executeCommand(RichTextCommand.GET_TEXT);
if (currentText != null && currentText.length() > 0) {
currentText = currentText.replaceAll(
"<P>&nbsp;</P>", "<br/>"); //$NON-NLS-1$ //$NON-NLS-2$
try {
// Call JTidy to format the source to XHTML.
currentText = htmlFormatter.formatHTML(currentText);
} catch (Exception e) {
logger.logError(e);
}
currentText = tidyText(currentText);
try {
// Call JTidy to reformat the source again just in case
// tidyText() destroys the formatting.
currentText = htmlFormatter.formatHTML(currentText);
} catch (Exception e) {
}
} else {
currentText = ""; //$NON-NLS-1$
}
if (debug) {
printDebugMessage("getText", "text=", currentText); //$NON-NLS-1$ //$NON-NLS-2$
}
return currentText;
} catch (Exception e) {
logger.logError(e);
}
}
return ""; //$NON-NLS-1$
}
/**
* Sets the rich text content.
*
* @param text
* the rich text content formatted in a markup language
*/
public void setText(String text) {
if (editor != null) {
if (debug) {
printDebugMessage("setText", "text=", text); //$NON-NLS-1$ //$NON-NLS-2$
}
String newText = text;
if (newText != null && newText.length() > 0) {
try {
// Call JTidy to format the source to XHTML.
newText = htmlFormatter.formatHTML(newText);
} catch (Exception e) {
logger.logError(e);
}
newText = tidyText(newText);
try {
// Call JTidy to reformat the source again just in case
// tidyText() destroys the formatting.
newText = htmlFormatter.formatHTML(newText);
} catch (Exception e) {
}
} else {
newText = ""; //$NON-NLS-1$
}
if (initialized) {
modified = !newText.equals(currentText);
}
initialText = newText;
if (debug) {
printDebugMessage(
"setText", "modified=" + modified + ", newText=", newText); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
if (initialized) {
try {
executeCommand(RichTextCommand.SET_TEXT, newText);
executeCommand(RichTextCommand.SET_EDITABLE, "" + editable); //$NON-NLS-1$
} catch (Exception e) {
logger.logError(e);
}
}
currentText = newText;
}
}
/**
* Restores the rich text content back to the initial value.
*/
public void restoreText() {
setText(initialText);
}
/**
* Returns the currently selected text.
*
* @return the selected text or <code>""</code> if there is no
* hasSelection
*/
public String getSelectedText() {
// executeCommand(RichTextCommand.GET_SELECTED_TEXT);
return richTextSelection.getText();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.epf.richtext.IRichText#getSelected()
*/
public RichTextSelection getSelected() {
return richTextSelection;
}
/**
* Returns an application specific property value.
*
* @param key
* the name of the property
* @return the value of the property or <code>null</code> if it has not
* been set
*/
public Object getData(String key) {
if (editor != null) {
editor.getData(key);
}
return null;
}
/**
* Sets an application specific property name and value.
*
* @param key
* the name of the property
* @param value
* the property value
*/
public void setData(String key, Object value) {
if (editor != null) {
editor.setData(key, value);
}
}
/**
* Executes the given JavaScript.
*
* @param script
* the JavaScript to execute
* @return a status code returned by the executed script
*/
protected int execute(String script) {
status = 0;
if (editor != null && script != null && script.length() > 0) {
try {
editor.execute(script);
if (debug) {
printDebugMessage("execute", script); //$NON-NLS-1$
}
} catch (Exception e) {
String msg = "Failed to execute " + script; //$NON-NLS-1$
logger.logError(msg, e);
if (debug) {
printDebugMessage("execute", msg); //$NON-NLS-1$
e.printStackTrace();
}
}
}
return status;
}
/**
* Executes the given rich text command. The supported command strings are
* defined in <code>RichTextCommand<code>.
*
* @param command a rich text command string.
* @return a status code returned by the executed command
*/
public int executeCommand(String command) {
status = 0;
if (command != null && command.equals(RichTextCommand.CLEAR_CONTENT)) {
String oldInitialText = initialText;
setText(""); //$NON-NLS-1$
initialText = oldInitialText;
status = 1;
modified = true;
notifyModifyListeners();
} else {
status = execute(command + "();"); //$NON-NLS-1$
}
return status;
}
/**
* Executes the given rich text command with a single parameter. The
* supported command strings are defined in <code>RichTextCommand<code>.
*
* @param command a rich text command string
* @param param a parameter for the command or <code>null</code>
* @return a status code returned by the executed command
*/
public int executeCommand(String command, String param) {
if (param == null) {
return executeCommand(command);
}
return execute(command + "('" + formatText(param) + "');"); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Executes the given rich text command with an array of parameters. The
* supported command strings are defined in <code>RichTextCommand<code>.
*
* @param command a rich text command string
* @param params an array of parameters for the command or <code>null</code>
* @return a status code returned by the executed command
*/
public int executeCommand(String command, String[] params) {
if (params == null || params.length == 0) {
return executeCommand(command);
}
StringBuffer sb = new StringBuffer();
int paramsLength = params.length;
for (int i = 0; i < paramsLength; i++) {
sb.append('\'').append(formatText(params[i])).append('\'');
if (i < paramsLength - 1) {
sb.append(',');
}
}
String param = sb.toString();
return execute(command + "(" + param + ");"); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* Disposes the operating system resources allocated by the control.
*/
public void dispose() {
if (contextMenu != null && !contextMenu.isDisposed()) {
contextMenu.dispose();
contextMenu = null;
}
if (listeners != null) {
listeners.clear();
listeners = null;
}
if (modifyListeners != null) {
modifyListeners.clear();
modifyListeners = null;
}
if (htmlFormatter != null) {
htmlFormatter = null;
}
if (this.findReplaceAction != null) {
this.findReplaceAction.dispose();
this.findReplaceAction = null;
}
}
/**
* Checks whether this control has been disposed.
*
* @return <code>true</code> if this control is disposed successfully
*/
public boolean isDisposed() {
return editor.isDisposed();
}
/**
* Returns the modify listeners attached to this control.
*
* @return an iterator for retrieving the modify listeners
*/
public Iterator getModifyListeners() {
return modifyListeners.iterator();
}
/**
* Adds a listener to the collection of listeners who will be notified when
* keys are pressed and released within this control.
*
* @param listener
* the listener which should be notified
*/
public void addKeyListener(KeyListener listener) {
if (editor != null) {
editor.addKeyListener(listener);
}
}
/**
* Removes a listener from the collection of listeners who will be notified
* when keys are pressed and released within this control.
*
* @param listener
* the listener which should no longer be notified
*/
public void removeKeyListener(KeyListener listener) {
if (editor != null) {
editor.removeKeyListener(listener);
}
}
/**
* Adds a listener to the collection of listeners who will be notified when
* the content of this control is modified.
*
* @param listener
* the listener which should be notified
*/
public void addModifyListener(ModifyListener listener) {
if (editor != null && listener != null
&& !modifyListeners.contains(listener)) {
modifyListeners.add(listener);
}
}
/**
* Removes a listener from the collection of listeners who will be notified
* when the content of this control is modified.
*
* @param listener
* the listener which should no longer be notified
*/
public void removeModifyListener(ModifyListener listener) {
if (editor != null && listener != null
&& modifyListeners.contains(listener)) {
modifyListeners.remove(listener);
}
}
/**
* Adds the listener to the collection of listeners who will be notifed when
* this control is disposed.
*
* @param listener
* the listener which should be notified
*/
public void addDisposeListener(DisposeListener listener) {
if (editor != null) {
editor.addDisposeListener(listener);
}
}
/**
* Removes a listener from the collection of listeners who will be notified
* when this control is disposed.
*
* @param listener
* the listener which should no longer be notified
*/
public void removeDisposeListener(DisposeListener listener) {
if (editor != null) {
editor.removeDisposeListener(listener);
}
}
/**
* Adds a listener to the collection of listeners who will be notified when
* help events are generated for this control.
*
* @param listener
* the listener which should be notified
*/
public void addHelpListener(HelpListener listener) {
if (editor != null) {
editor.addHelpListener(listener);
}
}
/**
* Removes a listener from the collection of listeners who will be notified
* when help events are generated for this control.
*
* @param listener
* the listener which should no longer be notified
*/
public void removeHelpListener(HelpListener listener) {
if (editor != null) {
editor.removeHelpListener(listener);
}
}
/**
* Adds the listener to the collection of listeners who will be notifed when
* an event of the given type occurs within this control.
*
* @param eventType
* the type of event to listen for
* @param listener
* the listener which should be notified when the event occurs
*/
public void addListener(int eventType, Listener listener) {
if (editor != null && !listeners.containsKey(listener)) {
if (eventType != SWT.SELECTED) {
if (editorControl == null
|| (eventType != SWT.Activate
&& eventType != SWT.Deactivate
&& eventType != SWT.FocusIn && eventType != SWT.FocusOut)) {
editor.addListener(eventType, listener);
}
}
listeners.put(listener, new RichTextListener(eventType, listener));
}
}
/**
* Removes the listener from the collection of listeners who will be notifed
* when an event of the given type occurs within this control.
*
* @param eventType
* the type of event to listen for
* @param listener
* the listener which should no longer be notified when the event
* occurs
*/
public void removeListener(int eventType, Listener listener) {
if (editor != null && listeners.containsKey(listener)) {
if (editorControl == null
|| (eventType != SWT.Activate
&& eventType != SWT.Deactivate
&& eventType != SWT.FocusIn && eventType != SWT.FocusOut)) {
editor.removeListener(eventType, listener);
}
listeners.remove(listener);
}
}
/**
* Returns the event listeners attached to this control.
*
* @return an iterator for retrieving the event listeners attached to this
* control
*/
public Iterator getListeners() {
return listeners.values().iterator();
}
/**
* Adds the listener to monitor events and status sent by the underlying
* DHTML editor.
*/
protected void addStatusTextListener() {
editor.addStatusTextListener(new StatusTextListener() {
public void changed(StatusTextEvent event) {
String eventText = event.text;
int eventTextLength = eventText.length();
if (eventText.startsWith(STATUS_PREFIX)
&& eventTextLength > STATUS_PREFIX_LENGTH) {
try {
int endStatusIndex = STATUS_PREFIX_LENGTH + 1;
if (eventText.length() > STATUS_PREFIX_LENGTH + 1
&& Character.isDigit(eventText
.charAt(endStatusIndex))) {
endStatusIndex++;
}
int statusType = Integer.parseInt(eventText.substring(
STATUS_PREFIX_LENGTH, endStatusIndex));
switch (statusType) {
case STATUS_NOP:
break;
case STATUS_INITIALIZED:
if (!initialized) {
initialized = true;
if (debug) {
printDebugMessage(
"statusTextListener", "STATUS_INITIALIZED"); //$NON-NLS-1$ //$NON-NLS-2$
}
if (!Platform.getOS().equals("win32")) { //$NON-NLS-1$
// Workaround Mozilla'a IFRAME height issue.
executeCommand(RichTextCommand.SET_HEIGHT,
"" + editor.getBounds().height); //$NON-NLS-1$
}
executeCommand(RichTextCommand.SET_TEXT,
currentText);
if (initializedWithFocus) {
setFocus();
}
if (!editable) {
executeCommand(
RichTextCommand.SET_EDITABLE,
"" + editable); //$NON-NLS-1$
}
}
break;
case STATUS_MODIFIED:
if (debug) {
printDebugMessage(
"statusTextListener", "STATUS_MODIFIED"); //$NON-NLS-1$ //$NON-NLS-2$
}
checkModify();
break;
case STATUS_GET_TEXT:
if (eventTextLength >= STATUS_PREFIX_LENGTH + 2) {
currentText = eventText
.substring(STATUS_PREFIX_LENGTH + 2);
} else {
currentText = ""; //$NON-NLS-1$
}
if (debug) {
printDebugMessage(
"statusTextListener", //$NON-NLS-1$
"STATUS_GET_TEXT, currentText=", currentText); //$NON-NLS-1$
}
break;
case STATUS_KEY_DOWN:
if (eventTextLength >= STATUS_PREFIX_LENGTH + 2) {
String cmd = eventText
.substring(STATUS_PREFIX_LENGTH + 2);
if (debug) {
printDebugMessage("statusTextListener", //$NON-NLS-1$
"STATUS_KEY_DOWN, cmd=" + cmd); //$NON-NLS-1$
}
if (cmd.equals(RichTextCommand.COPY)) {
setCopyURL();
} else if (cmd.equals(RichTextCommand.CUT)) {
setCopyURL();
CutAction action = new CutAction();
action.execute(RichText.this);
} else if (cmd
.equals(RichTextCommand.FIND_TEXT)) {
getFindReplaceAction().execute(RichText.this);
} else if (cmd.equals(RichTextCommand.PASTE)) {
PasteAction action = new PasteAction();
action.execute(RichText.this);
} else if (cmd.equals(RichTextCommand.SAVE)) {
PlatformUI.getWorkbench()
.getActiveWorkbenchWindow()
.getActivePage().getActiveEditor()
.doSave(null);
} else if (cmd.equals(RichTextCommand.SAVE_ALL)) {
PlatformUI.getWorkbench()
.getActiveWorkbenchWindow()
.getActivePage().saveAllEditors(
false);
}
}
break;
case STATUS_KEY_UP:
if (debug) {
printDebugMessage("statusTextListener", //$NON-NLS-1$
"STATUS_KEY_UP, modified=" + modified); //$NON-NLS-1$
}
checkModify();
break;
case STATUS_SELECT_TEXT:
if (eventTextLength >= STATUS_PREFIX_LENGTH + 2) {
String[] strings = eventText.substring(
STATUS_PREFIX_LENGTH + 2).split(
"\\$", 5); //$NON-NLS-1$
try {
// richTextSelection.setOffsetStart(Integer.parseInt(strings[0]));
richTextSelection.setFontName(strings[0]);
richTextSelection.setFontSize(strings[1]);
richTextSelection.setBlockStyle(strings[2]);
richTextSelection.setFlags(Integer
.parseInt(strings[3]));
richTextSelection.setText(strings[4]);
} catch (NumberFormatException e) {
logger.logError(e);
}
if (debug) {
printDebugMessage(
"selectionStatusListener", //$NON-NLS-1$
"current selection is=" + richTextSelection); //$NON-NLS-1$
}
hasSelection = true;
if (hasFocus())
notifyListeners(SWT.SELECTED, new Event());
} else {
richTextSelection.setText(""); //$NON-NLS-1$
hasSelection = false;
}
if (debug) {
printDebugMessage(
"statusTextListener", //$NON-NLS-1$
"STATUS_SELECT_TEXT, selectedText=", richTextSelection.getText()); //$NON-NLS-1$
}
break;
case STATUS_SELECT_CONTROL:
if (debug) {
printDebugMessage("statusTextListener", //$NON-NLS-1$
"STATUS_SELECT_CONTROL, control selected"); //$NON-NLS-1$
}
hasSelection = true;
break;
case STATUS_SELECT_NONE:
if (debug) {
printDebugMessage("statusTextListener", //$NON-NLS-1$
"STATUS_SELECT_NONE, no selection"); //$NON-NLS-1$
}
hasSelection = false;
break;
case STATUS_EXEC_CMD:
if (eventTextLength >= STATUS_PREFIX_LENGTH + 3) {
try {
status = Integer.parseInt(eventText
.substring(
STATUS_PREFIX_LENGTH + 2,
STATUS_PREFIX_LENGTH + 3));
} catch (Exception e) {
status = -1;
}
}
if (debug && status != 1) {
printDebugMessage("statusTextListener", //$NON-NLS-1$
"STATUS_EXEC_CMD, status=" + status); //$NON-NLS-1$
}
break;
}
} catch (Exception e) {
}
}
}
});
}
/**
* Generates the HTML source for the editor.
*
* @return the HTML source for the editor
*/
protected String generateEditorHTML() throws Exception {
String escapedBasePath = basePath;
if (escapedBasePath.startsWith(FileUtil.UNC_PATH_PREFIX))
escapedBasePath = escapedBasePath.replaceFirst(
"^\\\\\\\\", "\\\\\\\\\\\\\\\\"); //$NON-NLS-1$ //$NON-NLS-2$
escapedBasePath = XMLUtil
.escape("file://" + escapedBasePath.replaceAll("'", "\\\\'")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
String escapedRteUTL = rteURL.replaceAll("&apos;", "%27"); //$NON-NLS-1$ //$NON-NLS-2$
StringBuffer rteXML = new StringBuffer();
rteXML.append("<rte id=\"").append("rte") //$NON-NLS-1$ //$NON-NLS-2$
.append("\" css=\"").append(escapedRteUTL + "rte.css") //$NON-NLS-1$ //$NON-NLS-2$
.append("\" js=\"").append(escapedRteUTL + "rte.js") //$NON-NLS-1$ //$NON-NLS-2$
.append("\" baseURL=\"").append(escapedBasePath) //$NON-NLS-1$
.append("\"/>"); //$NON-NLS-1$
StringWriter result = new StringWriter();
XSLTProcessor.transform(
rteFolder + "rte.xsl", rteXML.toString(), result); //$NON-NLS-1$
return result.toString();
}
/**
* Fills the context menu with menu items.
*
* @param contextMenu
* a context menu containing rich text actions
*/
protected void fillContextMenu(Menu contextMenu) {
final MenuItem cutItem = new MenuItem(contextMenu, SWT.PUSH);
cutItem.setText(RichTextResources.cutAction_text);
cutItem.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
CutAction action = new CutAction();
action.execute(RichText.this);
}
});
final MenuItem copyItem = new MenuItem(contextMenu, SWT.PUSH);
copyItem.setText(RichTextResources.copyAction_text);
copyItem.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
CopyAction action = new CopyAction();
action.execute(RichText.this);
}
});
final MenuItem pasteItem = new MenuItem(contextMenu, SWT.PUSH);
pasteItem.setText(RichTextResources.pasteAction_text);
pasteItem.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
PasteAction action = new PasteAction();
action.execute(RichText.this);
}
});
final MenuItem pastePlainTextItem = new MenuItem(contextMenu, SWT.PUSH);
pastePlainTextItem.setText(RichTextResources.pastePlainTextAction_text);
pastePlainTextItem.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
PastePlainTextAction action = new PastePlainTextAction();
action.execute(RichText.this);
}
});
contextMenu.addMenuListener(new MenuListener() {
public void menuHidden(MenuEvent e) {
}
public void menuShown(MenuEvent e) {
getSelectedText();
cutItem.setEnabled(editable && hasSelection);
copyItem.setEnabled(hasSelection);
pasteItem.setEnabled(editable);
pastePlainTextItem.setEnabled(editable);
}
});
}
/**
* Adds listeners to manage the activation and focus events.
*/
protected void addListeners() {
editorControl = getControlSite(editor);
if (editorControl != null) {
if (debug) {
printDebugMessage(
"init", "editorControl=" + editorControl.getClass().getName()); //$NON-NLS-1$ //$NON-NLS-2$
}
editorControl.addListener(SWT.Activate, new Listener() {
public void handleEvent(Event event) {
if (debug) {
printDebugMessage("activateListener"); //$NON-NLS-1$
}
setFocus();
notifyListeners(SWT.Activate, event);
}
});
editorControl.addListener(SWT.Deactivate, new Listener() {
public void handleEvent(Event event) {
if (debug) {
printDebugMessage("deactivateListener"); //$NON-NLS-1$
}
setBlur();
notifyListeners(SWT.Deactivate, event);
}
});
editorControl.addListener(SWT.FocusIn, new Listener() {
public void handleEvent(Event event) {
if (debug) {
printDebugMessage("focusInListener"); //$NON-NLS-1$
}
executeCommand("updateSelection"); //$NON-NLS-1$
notifyListeners(SWT.FocusIn, event);
}
});
editorControl.addListener(SWT.KeyUp, new Listener() {
public void handleEvent(Event event) {
int keyCode = event.keyCode;
int stateMask = event.stateMask;
if (debug) {
printDebugMessage(
"keyUpListener", "keyCode=" + keyCode //$NON-NLS-1$ //$NON-NLS-2$
+ ", stateMask=" + stateMask + ", editable=" + editable); //$NON-NLS-1$ //$NON-NLS-2$
}
if ((stateMask & SWT.CTRL) > 0
|| (stateMask & SWT.ALT) > 0
|| ((stateMask & SWT.SHIFT) > 0 && keyCode == stateMask)) {
return;
}
if (editable) {
switch (event.keyCode) {
case SWT.ARROW_DOWN:
case SWT.ARROW_LEFT:
case SWT.ARROW_RIGHT:
case SWT.ARROW_UP:
case SWT.END:
case SWT.HOME:
case SWT.PAGE_DOWN:
case SWT.PAGE_UP:
case SWT.TAB:
return;
default:
checkModify();
break;
}
}
}
});
editor.addLocationListener(new LocationAdapter() {
public void changing(LocationEvent event) {
// Deactivate the links in the content page in readonly
// mode.
event.doit = editable;
}
});
} else {
editor.addListener(SWT.Activate, new Listener() {
public void handleEvent(Event event) {
if (debug) {
printDebugMessage("activateListener"); //$NON-NLS-1$
}
setFocus();
}
});
editor.addKeyListener(new KeyListener() {
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.TAB) {
if ((e.stateMask & SWT.SHIFT) != 0) {
editor.traverse(SWT.TRAVERSE_TAB_PREVIOUS);
} else {
editor.traverse(SWT.TRAVERSE_TAB_NEXT);
}
return;
}
if (!editable) {
e.doit = false;
}
}
public void keyReleased(KeyEvent e) {
if ((e.stateMask & SWT.CTRL) > 0
|| (e.stateMask & SWT.ALT) > 0)
return;
if (editable) {
switch (e.keyCode) {
case SWT.ARROW_DOWN:
case SWT.ARROW_LEFT:
case SWT.ARROW_RIGHT:
case SWT.ARROW_UP:
case SWT.END:
case SWT.HOME:
case SWT.PAGE_DOWN:
case SWT.PAGE_UP:
case SWT.SHIFT:
case SWT.TAB:
break;
default:
checkModify();
break;
}
}
}
});
}
editor.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
if (debug) {
printDebugMessage("disposeListener"); //$NON-NLS-1$
}
dispose();
}
});
listeners = new Hashtable();
modifyListeners = new ArrayList();
}
/**
* Notifies the rich text event listeners.
*
* @param eventType
* the event type
* @param event
* the SWT event
*/
protected void notifyListeners(int eventType, Event event) {
if (notifyingModifyListeners) {
return;
}
event.display = Display.getCurrent();
event.widget = editor;
for (Iterator i = listeners.values().iterator(); i.hasNext();) {
RichTextListener listener = (RichTextListener) i.next();
if (listener.getEventType() == eventType) {
if (debug) {
printDebugMessage(
"notifyListeners", "notifying listener, " + listener + ", eventType=" + eventType); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
listener.getListener().handleEvent(event);
if (debug) {
printDebugMessage(
"notifyListeners", "notified listener, " + listener + ", eventType=" + eventType); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
}
}
/**
* Notifies the modify listeners that the rich text editor content has
* changed.
*/
public void notifyModifyListeners() {
notifyingModifyListeners = true;
Event event = new Event();
event.display = Display.getCurrent();
event.widget = editor;
for (Iterator i = modifyListeners.iterator(); i.hasNext();) {
ModifyListener listener = (ModifyListener) i.next();
if (debug) {
printDebugMessage(
"notifyModifyListeners", "notifying listener, " + listener); //$NON-NLS-1$ //$NON-NLS-2$
}
listener.modifyText(new ModifyEvent(event));
if (debug) {
printDebugMessage(
"notifyModifyListeners", "notified listener, " + listener); //$NON-NLS-1$ //$NON-NLS-2$
}
}
notifyingModifyListeners = false;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.epf.richtext.IRichText#checkModify()
*/
public void checkModify() {
if (modified) {
notifyModifyListeners();
} else {
if (!getText().equals(initialText)) {
modified = true;
notifyModifyListeners();
}
}
if (debug) {
printDebugMessage("checkModify", "modified=" + modified); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
* This provides an opportunity for a sub class to tidy up the rich text.
*
* @param text
* rich text encoded in HTML format
*/
protected String tidyText(String text) {
return text;
}
/**
* Formats the text for consumption by the JavaScript/DHTML editor.
*
* @param text
* rich text encoded in HTML format
*/
protected String formatText(String text) {
if (text == null || text.length() == 0) {
return text;
}
StringBuffer result = new StringBuffer();
int textSize = text.length();
for (int i = 0; i < textSize; i++) {
char ch = text.charAt(i);
switch (ch) {
case '\r':
break;
case '\t':
result.append(' ');
break;
case '\n':
result.append(ENCODED_NEWLINE);
break;
case '\'':
result.append(ENCODED_SINGLE_QUOTE);
break;
case '\\':
result.append("\\\\"); //$NON-NLS-1$
break;
default:
result.append(ch);
}
}
return result.toString();
}
/**
* Returns the child <code>OleControlSite</code> contained within the
* given <code>Composite</code>.
*
* @param composite
* a <code>Composite</code> object, presumably a
* <code>Browser</code>
* @return an <code>OleControlSite</code> object
*/
private Control getControlSite(Composite composite) {
if (Platform.getOS().equals("win32")) { //$NON-NLS-1$
Control[] controls = composite.getChildren();
for (int i = 0; i < controls.length; i++) {
String controlClass = controls[i].getClass().getName();
if (controlClass.equals("org.eclipse.swt.browser.WebSite")) { //$NON-NLS-1$
return controls[i];
} else if (controls[i] instanceof Composite) {
return getControlSite((Composite) controls[i]);
}
}
}
return null;
}
/**
* Displays the given debug message to the console.
*/
private void printDebugMessage(String method, String msg, String text) {
StringBuffer strBuf = new StringBuffer();
strBuf.append("RichText[").append(editor.handle).append(']') //$NON-NLS-1$
.append('.').append(method);
if (msg != null && msg.length() > 0) {
strBuf.append(": ").append(msg); //$NON-NLS-1$
}
if (text != null && text.length() > 0) {
strBuf.append('\n').append(text);
}
System.out.println(strBuf);
}
/**
* Displays the given debug message to the console.
*/
private void printDebugMessage(String method, String msg) {
printDebugMessage(method, msg, null);
}
/**
* Displays the given debug message to the console.
*/
private void printDebugMessage(String method) {
printDebugMessage(method, null);
}
public FindReplaceAction getFindReplaceAction() {
return findReplaceAction;
}
public void setFindReplaceAction(FindReplaceAction findReplaceAction) {
if (findReplaceAction != null) {
if (this.findReplaceAction != null && this.findReplaceAction != findReplaceAction) {
this.findReplaceAction.dispose();
}
this.findReplaceAction = findReplaceAction;
this.findReplaceAction.setRichText(this);
}
}
}