| /******************************************************************************* |
| * Copyright (c) 2009 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 API and implementation |
| *******************************************************************************/ |
| //------------------------------------------------------------------------------ |
| // 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.net.URL; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.commands.ParameterizedCommand; |
| import org.eclipse.core.commands.operations.IUndoContext; |
| import org.eclipse.epf.common.serviceability.Logger; |
| 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.jface.action.Action; |
| import org.eclipse.jface.action.IAction; |
| import org.eclipse.jface.bindings.Binding; |
| import org.eclipse.jface.bindings.keys.KeySequence; |
| import org.eclipse.jface.bindings.keys.KeyStroke; |
| import org.eclipse.jface.bindings.keys.SWTKeySupport; |
| import org.eclipse.jface.text.Document; |
| import org.eclipse.jface.text.DocumentEvent; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IDocumentListener; |
| import org.eclipse.jface.text.ITextViewerExtension; |
| import org.eclipse.jface.text.ITextViewerExtension6; |
| import org.eclipse.jface.text.IUndoManager; |
| import org.eclipse.jface.text.IUndoManagerExtension; |
| import org.eclipse.jface.text.TextViewer; |
| import org.eclipse.jface.text.TextViewerUndoManager; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.CTabFolder; |
| import org.eclipse.swt.custom.CTabItem; |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.custom.VerifyKeyListener; |
| import org.eclipse.swt.custom.ViewForm; |
| import org.eclipse.swt.dnd.DND; |
| import org.eclipse.swt.dnd.DropTarget; |
| import org.eclipse.swt.dnd.DropTargetEvent; |
| import org.eclipse.swt.dnd.DropTargetListener; |
| import org.eclipse.swt.dnd.HTMLTransfer; |
| import org.eclipse.swt.dnd.TextTransfer; |
| import org.eclipse.swt.dnd.Transfer; |
| 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.events.VerifyEvent; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.layout.FillLayout; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| 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.IActionBars; |
| import org.eclipse.ui.IEditorSite; |
| import org.eclipse.ui.IKeyBindingService; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.actions.ActionFactory; |
| import org.eclipse.ui.keys.IBindingService; |
| import org.eclipse.ui.operations.OperationHistoryActionHandler; |
| import org.eclipse.ui.operations.RedoActionHandler; |
| import org.eclipse.ui.operations.UndoActionHandler; |
| import org.eclipse.ui.texteditor.IAbstractTextEditorHelpContextIds; |
| import org.eclipse.ui.texteditor.IReadOnlyDependent; |
| import org.eclipse.ui.texteditor.ITextEditorActionConstants; |
| import org.eclipse.ui.texteditor.IUpdate; |
| import org.eclipse.ui.texteditor.IWorkbenchActionDefinitionIds; |
| |
| /** |
| * The default rich text editor implementation. |
| * <p> |
| * The default rich text editor uses XHTML as the underlying markup language for |
| * the rich text content. It is implemented using a <code>ViewForm</code> |
| * control with a tool bar at the top, a tab folder that contains a |
| * <code>RichText</code> control for entering the rich text content, and a tab |
| * foler that contains a <code>StyleText</code> control for viewing and |
| * modifying the XHTML representation of the rich text content. |
| * |
| * @author Kelvin Low |
| * @author Jeff Hardy |
| * @since 1.0 |
| */ |
| public class RichTextEditor implements IRichTextEditor { |
| |
| // The HTML tab name. |
| protected static final String HTML_TAB_NAME = RichTextResources.htmlTab_text; |
| |
| // If true, log debugging info. |
| protected boolean debug; |
| |
| // The plug-in logger. |
| protected Logger logger; |
| |
| // The base path used for resolving links (<href>, <img>, etc.). |
| protected String basePath; |
| |
| // The editor form. |
| protected ViewForm form; |
| |
| // The editor tool bar. |
| protected IRichTextToolBar toolBar; |
| |
| // The editor content. |
| protected Composite content; |
| |
| // The editor tab folder. |
| protected CTabFolder tabFolder; |
| |
| // The rich text tab |
| protected CTabItem richTextTab; |
| |
| // The HTML source tab |
| protected CTabItem htmlTab; |
| |
| // The embedded rich text control. |
| protected IRichText richText; |
| |
| // The underlying HTML text editor. |
| protected TextViewer sourceViewer; |
| |
| protected IDocument currentDoc; |
| |
| // Drop support |
| protected DropTarget sourceEditDropTarget; |
| |
| // HTML editor's context menu |
| protected Menu contextMenu; |
| |
| // Has the HTML source been modified? |
| protected boolean sourceModified = false; |
| |
| // The editor's editable flag. |
| protected boolean editable = true; |
| |
| private OperationHistoryActionHandler undoAction; |
| |
| private OperationHistoryActionHandler redoAction; |
| |
| private IEditorSite editorSite; |
| |
| /** The actions registered with the editor. */ |
| private Map<String, IAction> fActions= new HashMap<String, IAction>(10); |
| |
| /** The verify key listener for activation code triggering. */ |
| private ActivationCodeTrigger fActivationCodeTrigger= new ActivationCodeTrigger(); |
| |
| /** The editor's action activation codes. */ |
| private List fActivationCodes= new ArrayList(2); |
| |
| final IUndoManager undoManager= new TextViewerUndoManager(10); |
| |
| /** |
| * The key binding scopes of this editor. |
| * @since 2.1 |
| */ |
| private String[] fKeyBindingScopes; |
| |
| |
| protected IDocumentListener sourceEditDocumentListener= new IDocumentListener() { |
| public void documentAboutToBeChanged(DocumentEvent event) { |
| } |
| public void documentChanged(DocumentEvent event) { |
| sourceModified = true; |
| if (richText != null && richText instanceof RichText) { |
| richText.notifyModifyListeners(); |
| } |
| } |
| }; |
| |
| |
| // The deactivate listener for the sourceEdit control. |
| protected Listener sourceEditDeactivateListener = new Listener() { |
| public void handleEvent(Event event) { |
| if (sourceModified) { |
| updateRichText(sourceViewer.getTextWidget().getText()); |
| setModified(true); |
| sourceModified = false; |
| } |
| } |
| }; |
| |
| // The key listener for the sourceEdit control. |
| protected KeyListener sourceEditKeyListener = new KeyListener() { |
| public void keyPressed(KeyEvent e) { |
| Object adapter = PlatformUI.getWorkbench().getAdapter( |
| IBindingService.class); |
| if (adapter != null && adapter instanceof IBindingService) { |
| int accel = SWTKeySupport |
| .convertEventToUnmodifiedAccelerator(e); |
| KeyStroke stroke = SWTKeySupport |
| .convertAcceleratorToKeyStroke(accel); |
| KeySequence seq = KeySequence.getInstance(stroke); |
| Binding bind = ((IBindingService) adapter).getPerfectMatch(seq); |
| if (bind != null) { |
| ParameterizedCommand command = bind |
| .getParameterizedCommand(); |
| if (command != null) { |
| String cmdId = command.getId(); |
| if (cmdId != null |
| && cmdId |
| .equals("org.eclipse.ui.edit.findReplace")) { //$NON-NLS-1$ |
| richText.getFindReplaceAction().execute(RichTextEditor.this); |
| } |
| } |
| } |
| } |
| } |
| |
| public void keyReleased(KeyEvent e) { |
| } |
| }; |
| |
| /** |
| * Creates a new instance. |
| * |
| * @param parent |
| * the parent composite |
| * @param style |
| * the editor style |
| */ |
| public RichTextEditor(Composite parent, int style, IEditorSite editorSite) { |
| this(parent, style, editorSite, null); |
| } |
| |
| /** |
| * Creates a new instance. |
| * |
| * @param parent |
| * the parent composite |
| * @param style |
| * the editor style |
| * @param basePath |
| * the base path used for resolving links |
| */ |
| public RichTextEditor(Composite parent, int style, IEditorSite editorSite, String basePath) { |
| this.basePath = basePath; |
| this.editorSite = editorSite; |
| debug = RichTextPlugin.getDefault().isDebugging(); |
| logger = RichTextPlugin.getDefault().getLogger(); |
| init(parent, style); |
| } |
| |
| /** |
| * Initializes this editor. |
| * |
| * @param parent |
| * the parent composite |
| * @param style |
| * the editor style |
| */ |
| protected void init(Composite parent, int style) { |
| try { |
| form = new ViewForm(parent, style); |
| form.marginHeight = 0; |
| form.marginWidth = 0; |
| |
| toolBar = new RichTextToolBar(form, SWT.FLAT, this); |
| |
| content = new Composite(form, SWT.FLAT); |
| GridLayout layout = new GridLayout(); |
| layout.marginHeight = 0; |
| layout.marginWidth = 0; |
| content.setLayout(layout); |
| |
| tabFolder = createEditorTabFolder(content, style); |
| |
| form.setTopCenter(((RichTextToolBar)toolBar).getToolbarMgr().getControl()); |
| form.setTopLeft(((RichTextToolBar)toolBar).getToolbarMgrCombo().getControl()); |
| form.setContent(content); |
| } catch (Exception e) { |
| logger.logError(e); |
| } |
| } |
| |
| /** |
| * Returns the form control. |
| * |
| * @return the form control |
| */ |
| public Control getControl() { |
| return form; |
| } |
| |
| /** |
| * Returns the rich text control embedded within this editor. |
| */ |
| public IRichText getRichTextControl() { |
| return richText; |
| } |
| |
| /** |
| * Sets the layout data. |
| * |
| * @param layoutData |
| * the layout data to set |
| */ |
| public void setLayoutData(Object layoutData) { |
| if (form != null) { |
| form.setLayoutData(layoutData); |
| } |
| } |
| |
| /** |
| * Returns the layout data. |
| * |
| * @return the editor's layout data |
| */ |
| public Object getLayoutData() { |
| if (form != null) { |
| return form.getLayoutData(); |
| } |
| return null; |
| } |
| |
| /** |
| * Sets focus to this editor. |
| */ |
| public void setFocus() { |
| if (richText != null) { |
| richText.setFocus(); |
| } |
| setSelection(0); |
| if (toolBar != null && tabFolder != null) { |
| toolBar.updateToolBar(editable); |
| } |
| |
| } |
| |
| /** |
| * Tells the control it does not have focus. |
| */ |
| public void setBlur() { |
| if (richText != null) { |
| richText.setBlur(); |
| } |
| } |
| |
| /** |
| * Checks whether this editor has focus. |
| * |
| * @return <code>true</code> if this editor has the user-interface focus |
| */ |
| public boolean hasFocus() { |
| if (richText != null) { |
| return richText.hasFocus(); |
| } |
| return false; |
| } |
| |
| /** |
| * Selects the Rich Text or HTML tab. |
| * |
| * @param index |
| * <code>0</code> for the Rich Text tab, <code>1</code> for |
| * the HTML tab. |
| */ |
| public void setSelection(int index) { |
| if (tabFolder != null) { |
| tabFolder.setSelection(index); |
| } |
| } |
| |
| /** |
| * Returns the base path used for resolving text and image links. |
| * |
| * @return the base path used for resolving links specified with <href>, |
| * <img>, etc. |
| */ |
| 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() { |
| if (richText != null) { |
| return richText.getCopyURL(); |
| } |
| return null; |
| } |
| |
| /** |
| * Sets the base URL of the rich text control whose content was last copied |
| * to the clipboard. |
| */ |
| public void setCopyURL() { |
| if (richText != null) { |
| richText.setCopyURL(); |
| } |
| } |
| |
| /** |
| * Returns the editable state. |
| * |
| * @return <code>true</code> if the content can be edited |
| */ |
| public boolean getEditable() { |
| return editable; |
| } |
| |
| /** |
| * Sets the editable state. |
| * |
| * @param editable |
| * the editable state |
| */ |
| public void setEditable(boolean editable) { |
| this.editable = editable; |
| if (toolBar != null && tabFolder != null) { |
| toolBar.updateToolBar(editable); |
| } |
| if (richText != null) { |
| richText.setEditable(editable); |
| } |
| if (sourceViewer != null) { |
| sourceViewer.setEditable(editable); |
| } |
| } |
| |
| /** |
| * Checks whether the content has been modified. |
| * |
| * @return <code>true</code> if the content has been modified |
| */ |
| public boolean getModified() { |
| if (richText != null) { |
| return richText.getModified(); |
| } |
| return false; |
| } |
| |
| /** |
| * Sets the modified state. |
| * |
| * @param modified |
| * the modified state |
| */ |
| public void setModified(boolean modified) { |
| if (richText != null) { |
| richText.setModified(modified); |
| } |
| } |
| |
| /** |
| * Returns the rich text content. |
| * |
| * @return the rich text content formatted in XHTML |
| */ |
| public String getText() { |
| if (sourceModified) { |
| setText(getSourceEdit().getText()); |
| setModified(true); |
| sourceModified = false; |
| } |
| if (richText != null) { |
| return richText.getText(); |
| } |
| return ""; //$NON-NLS-1$ |
| } |
| |
| /** |
| * Sets the rich text content. |
| * |
| * @param text |
| * the rich text content in XHTML format |
| */ |
| public void setText(String text) { |
| if (richText != null) { |
| richText.setText(text); |
| } |
| sourceModified = false; |
| if (tabFolder != null) { |
| if (toolBar != null) { |
| toolBar.updateToolBar(editable); |
| } |
| if (getSourceEdit() != null) { |
| removeModifyListeners(); |
| currentDoc.set(text); |
| addModifyListeners(); |
| } |
| } |
| } |
| |
| protected void addModifyListeners() { |
| if (currentDoc != null) { |
| currentDoc.addDocumentListener(sourceEditDocumentListener); |
| } |
| } |
| |
| protected void removeModifyListeners() { |
| if (currentDoc != null) { |
| currentDoc.removeDocumentListener(sourceEditDocumentListener); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.epf.richtext.IRichText#checkModify() |
| */ |
| public void checkModify() { |
| richText.checkModify(); |
| if (sourceModified) { |
| notifyModifyListeners(); |
| } |
| if (debug) { |
| printDebugMessage("checkModify", "modified=" + sourceModified); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| /** |
| * Restores the rich text content back to the initial value. |
| */ |
| public void restoreText() { |
| if (richText != null) { |
| richText.restoreText(); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.epf.richtext.IRichText#getSelected() |
| */ |
| public RichTextSelection getSelected() { |
| if (tabFolder.getSelection() == htmlTab) { |
| String HTMLsource = getSourceEdit().getText(); |
| Point sel = sourceViewer.getSelectedRange(); |
| int selStartIndex = sel.x; |
| int selEndIndex = sel.x + sel.y - 1; |
| richText.getSelected().clear(); |
| richText.getSelected().setText(HTMLsource.substring(selStartIndex, selEndIndex + 1)); |
| } |
| return richText.getSelected(); |
| } |
| |
| /** |
| * 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 (richText != null) { |
| return richText.getData(key); |
| } |
| return null; |
| } |
| |
| /** |
| * Sets an application specific property name and value. |
| * |
| * @param key |
| * the name of the property |
| * @param value |
| * the new value for the property |
| */ |
| public void setData(String key, Object value) { |
| if (richText != null) { |
| richText.setData(key, value); |
| } |
| } |
| |
| /** |
| * 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) { |
| if (richText != null) { |
| return richText.executeCommand(command); |
| } |
| return 0; |
| } |
| |
| /** |
| * 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 (richText != null) { |
| return richText.executeCommand(command, param); |
| } |
| return 0; |
| } |
| |
| /** |
| * 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 (richText != null) { |
| return richText.executeCommand(command, params); |
| } |
| return 0; |
| } |
| |
| /** |
| * Disposes the operating system resources allocated by this editor. |
| */ |
| public void dispose() { |
| if (contextMenu != null && !contextMenu.isDisposed()) { |
| contextMenu.dispose(); |
| contextMenu = null; |
| } |
| if (sourceEditDropTarget != null) { |
| sourceEditDropTarget.dispose(); |
| sourceEditDropTarget = null; |
| } |
| if (fActivationCodeTrigger != null) { |
| fActivationCodeTrigger.uninstall(); |
| fActivationCodeTrigger= null; |
| } |
| removeModifyListeners(); |
| if (getSourceEdit() != null) { |
| getSourceEdit().removeListener(SWT.Deactivate, |
| sourceEditDeactivateListener); |
| getSourceEdit().removeKeyListener(sourceEditKeyListener); |
| sourceEditDeactivateListener = null; |
| sourceEditKeyListener = null; |
| } |
| |
| if (sourceViewer != null) { |
| sourceViewer= null; |
| } |
| |
| if (fActions != null) { |
| fActions.clear(); |
| fActions= null; |
| } |
| if (fActivationCodes != null) { |
| fActivationCodes.clear(); |
| fActivationCodes= null; |
| } |
| |
| if (richText != null) { |
| richText.dispose(); |
| richText = null; |
| } |
| } |
| |
| /** |
| * Checks whether this control has been disposed. |
| * |
| * @return <code>true</code> if this control is disposed successfully |
| */ |
| public boolean isDisposed() { |
| if (richText != null) { |
| return richText.isDisposed(); |
| } |
| return true; |
| } |
| |
| /** |
| * Returns the modify listeners attached to this editor. |
| * |
| * @return an iterator for retrieving the modify listeners |
| */ |
| public Iterator<ModifyListener> getModifyListeners() { |
| if (richText != null) { |
| richText.getModifyListeners(); |
| } |
| return null; |
| } |
| |
| /** |
| * Adds a listener to the collection of listeners who will be notified when |
| * keys are pressed and released within this editor. |
| * |
| * @param listener |
| * the listener which should be notified |
| */ |
| public void addKeyListener(KeyListener listener) { |
| if (richText != null) { |
| richText.addKeyListener(listener); |
| } |
| } |
| |
| /** |
| * Removes a listener from the collection of listeners who will be notified |
| * when keys are pressed and released within this editor. |
| * |
| * @param listener |
| * the listener which should no longer be notified |
| */ |
| public void removeKeyListener(KeyListener listener) { |
| if (richText != null) { |
| richText.removeKeyListener(listener); |
| } |
| } |
| |
| /** |
| * Adds a listener to the collection of listeners who will be notified when |
| * the content of this editor is modified. |
| * |
| * @param listener |
| * the listener which should be notified |
| */ |
| public void addModifyListener(ModifyListener listener) { |
| if (richText != null) { |
| richText.addModifyListener(listener); |
| } |
| } |
| |
| /** |
| * Removes a listener from the collection of listeners who will be notified |
| * when the content of this editor is modified. |
| * |
| * @param listener |
| * the listener which should no longer be notified |
| */ |
| public void removeModifyListener(ModifyListener listener) { |
| if (richText != null) { |
| richText.removeModifyListener(listener); |
| } |
| } |
| |
| /** |
| * Adds the listener to the collection of listeners who will be notifed when |
| * this editor is disposed. |
| * |
| * @param listener |
| * the listener which should be notified |
| */ |
| public void addDisposeListener(DisposeListener listener) { |
| if (richText != null) { |
| richText.addDisposeListener(listener); |
| } |
| } |
| |
| /** |
| * Removes a listener from the collection of listeners who will be notified |
| * when this editor is disposed. |
| * |
| * @param listener |
| * the listener which should no longer be notified |
| */ |
| public void removeDisposeListener(DisposeListener listener) { |
| if (richText != null) { |
| richText.removeDisposeListener(listener); |
| } |
| } |
| |
| /** |
| * Adds a listener to the collection of listeners who will be notified when |
| * help events are generated for this editor. |
| * |
| * @param listener |
| * the listener which should be notified |
| */ |
| public void addHelpListener(HelpListener listener) { |
| if (richText != null) { |
| richText.addHelpListener(listener); |
| } |
| } |
| |
| /** |
| * Removes a listener from the collection of listeners who will be notified |
| * when help events are generated for this editor. |
| * |
| * @param listener |
| * the listener which should no longer be notified |
| */ |
| public void removeHelpListener(HelpListener listener) { |
| if (richText != null) { |
| richText.removeHelpListener(listener); |
| } |
| } |
| |
| /** |
| * Adds the listener to the collection of listeners who will be notifed when |
| * an event of the given type occurs within this editor. |
| * |
| * @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 (richText != null) { |
| richText.addListener(eventType, listener); |
| } |
| } |
| |
| /** |
| * Removes the listener from the collection of listeners who will be notifed |
| * when an event of the given type occurs within this editor. |
| * |
| * @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 (richText != null) { |
| richText.removeListener(eventType, listener); |
| } |
| } |
| |
| /** |
| * Returns the event listeners attached to this editor. |
| * |
| * @return an iterator for retrieving the event listeners attached to this |
| * editor |
| */ |
| public Iterator<RichTextListener> getListeners() { |
| if (richText != null) { |
| return richText.getListeners(); |
| } |
| return null; |
| } |
| |
| /** |
| * Notifies the modify listeners that the rich text editor content has |
| * changed. |
| */ |
| public void notifyModifyListeners() { |
| if (richText != null) { |
| Event event = new Event(); |
| event.display = Display.getCurrent(); |
| event.widget = richText.getControl(); |
| |
| for (Iterator<ModifyListener> i = getModifyListeners(); i != null && i.hasNext();) { |
| ModifyListener listener = i.next(); |
| listener.modifyText(new ModifyEvent(event)); |
| } |
| } |
| } |
| |
| /** |
| * Fills the tool bar with action items. |
| * |
| * @param toolBar |
| * a tool bar contain rich text actions |
| */ |
| public void fillToolBar(IRichTextToolBar toolBar) { |
| } |
| |
| /** |
| * Creates the underlying rich text control. |
| * |
| * @param parent |
| * the parent composite |
| * @param style |
| * the style for the control |
| * @param basePath |
| * the path used for resolving links |
| */ |
| protected IRichText createRichTextControl(Composite parent, int style, |
| String basePath) { |
| return new RichText(parent, style, basePath); |
| } |
| |
| /** |
| * Creates the editor tab folder. |
| * |
| * @param parent |
| * the parent control |
| * @param style |
| * the style for the control |
| * @return a new editor toolbar |
| */ |
| protected CTabFolder createEditorTabFolder(Composite parent, int style) { |
| CTabFolder folder = new CTabFolder(parent, SWT.FLAT | SWT.BOTTOM); |
| folder.setLayout(new GridLayout(1, true)); |
| folder.setLayoutData(new GridData(GridData.FILL_BOTH)); |
| |
| Composite richTextComposite = new Composite(folder, SWT.FLAT); |
| GridLayout richTextCompositeLayout = new GridLayout(1, false); |
| richTextCompositeLayout.marginHeight = 0; |
| richTextCompositeLayout.marginWidth = 0; |
| richTextComposite.setLayout(richTextCompositeLayout); |
| richTextComposite.setLayoutData(new GridData(GridData.FILL_BOTH)); |
| |
| richText = createRichTextControl(richTextComposite, style, basePath); |
| richText.setData(PROPERTY_NAME, this); |
| richText.getFindReplaceAction().setRichText(this); |
| |
| richTextTab = new CTabItem(folder, SWT.FLAT); |
| richTextTab.setText(RichTextResources.richTextTab_text); |
| richTextTab.setToolTipText(RichTextResources.richTextTab_toolTipText); |
| richTextTab.setControl(richTextComposite); |
| |
| Composite htmlComposite = new Composite(folder, SWT.FLAT); |
| htmlComposite.setLayout(new FillLayout()); |
| |
| sourceViewer = new TextViewer(htmlComposite, SWT.FLAT | SWT.MULTI |
| | SWT.WRAP | SWT.V_SCROLL); |
| sourceViewer.setUndoManager(undoManager); |
| setDocument(null); |
| addModifyListeners(); |
| getSourceEdit().addListener(SWT.Deactivate, sourceEditDeactivateListener); |
| getSourceEdit().addKeyListener(sourceEditKeyListener); |
| contextMenu = new Menu(parent.getShell(), SWT.POP_UP); |
| getSourceEdit().setMenu(contextMenu); |
| // FIXME! This opens up a can of worms, especially with DBCS characters. |
| // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=179432. |
| //addDropSupportToStyledText(); |
| fillContextMenu(contextMenu); |
| |
| |
| htmlTab = new CTabItem(folder, SWT.NONE); |
| htmlTab.setText(HTML_TAB_NAME); |
| htmlTab.setToolTipText(RichTextResources.htmlTab_toolTipText); |
| htmlTab.setControl(htmlComposite); |
| |
| folder.addSelectionListener(new SelectionAdapter() { |
| public void widgetSelected(SelectionEvent event) { |
| CTabItem item = (CTabItem) event.item; |
| if (item.getText().equals(HTML_TAB_NAME)) { |
| removeModifyListeners(); |
| currentDoc.set(getText()); |
| sourceModified = false; |
| addModifyListeners(); |
| if (toolBar != null) { |
| toolBar.updateToolBar(editable); |
| } |
| } else { |
| updateRichText(getSourceEdit().getText()); |
| setModified(true); |
| if (toolBar != null) { |
| toolBar.updateToolBar(editable); |
| } |
| } |
| } |
| }); |
| fillToolBar(toolBar); |
| |
| initializeActivationCodeTrigger(); |
| createActions(); |
| |
| folder.setSelection(0); |
| |
| return folder; |
| } |
| |
| |
| private void setDocument(IDocument doc) { |
| if (doc == null) { |
| doc = new Document(); |
| } |
| // clean up old doc |
| undoManager.disconnect(); |
| IDocument oldDoc = sourceViewer.getDocument(); |
| if (oldDoc != null) { |
| oldDoc.removeDocumentListener(sourceEditDocumentListener); |
| } |
| |
| // hook up new doc |
| currentDoc = doc; |
| sourceViewer.setDocument(currentDoc); |
| currentDoc.addDocumentListener(sourceEditDocumentListener); |
| undoManager.connect(sourceViewer); |
| if (undoAction != null) { |
| undoAction.setContext(getUndoContext()); |
| } |
| if (redoAction != null) { |
| redoAction.setContext(getUndoContext()); |
| } |
| } |
| |
| /** |
| * Returns the HTML source edit control. |
| * |
| * @return a <code>StyleText</code> object. |
| */ |
| public StyledText getSourceEdit() { |
| if (sourceViewer != null) { |
| return sourceViewer.getTextWidget(); |
| } |
| return null; |
| } |
| |
| /** |
| * Inserts text at the selection (overwriting the selection). |
| */ |
| public void addHTML(String text) { |
| if (text == null || text.length() == 0) |
| return; |
| if (tabFolder.getSelection() == richTextTab) { |
| //To avoid encoding of javascript |
| text = text.replaceAll("&", "&"); //$NON-NLS-1$//$NON-NLS-2$ |
| executeCommand(RichTextCommand.ADD_HTML, text); |
| } else if (tabFolder.getSelection() == htmlTab) { |
| String oldHTML = getSourceEdit().getText(); |
| Point sel = sourceViewer.getSelectedRange(); |
| int selStartIndex = sel.x; |
| int selEndIndex = sel.x + sel.y - 1; |
| String newHTML = oldHTML.substring(0, selStartIndex) + text |
| + oldHTML.substring(selEndIndex + 1); |
| removeModifyListeners(); |
| currentDoc.set(newHTML); |
| addModifyListeners(); |
| updateRichText(newHTML); |
| } |
| } |
| |
| |
| /** |
| * Inserts an image at the selection (overwriting the selection). |
| */ |
| public void addImage(String imageURL, String height, String width, String altTag) { |
| if (tabFolder.getSelection() == richTextTab) { |
| executeCommand( |
| RichTextCommand.ADD_IMAGE, |
| new String[] { |
| imageURL, |
| height, width, altTag }); |
| } else if (tabFolder.getSelection() == htmlTab) { |
| StringBuffer imageLink = new StringBuffer(); |
| // order of these attributes is the same as JTidy'ed HTML |
| imageLink.append("<img"); //$NON-NLS-1$ |
| if (height.length() > 0) { |
| imageLink.append(" height=\"" + height + "\""); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| if (altTag.length() > 0) { |
| imageLink.append(" alt=\"" + altTag + "\""); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| imageLink.append(" src=\"" + imageURL + "\""); //$NON-NLS-1$ //$NON-NLS-2$ |
| if (width.length() > 0) { |
| imageLink.append(" width=\"" + width + "\""); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| imageLink.append(" />"); //$NON-NLS-1$ |
| String oldHTML = getSourceEdit().getText(); |
| Point sel = sourceViewer.getSelectedRange(); |
| int selStartIndex = sel.x; |
| int selEndIndex = sel.x + sel.y - 1; |
| String newHTML = oldHTML.substring(0, selStartIndex) + imageLink.toString() |
| + oldHTML.substring(selEndIndex + 1); |
| removeModifyListeners(); |
| currentDoc.set(newHTML); |
| addModifyListeners(); |
| updateRichText(newHTML); |
| } |
| } |
| |
| /** |
| * Checks whether the HTML tab is selected. |
| * |
| * @return <code>true</code> if the HTML tab is selected. |
| */ |
| public boolean isHTMLTabSelected() { |
| return (tabFolder.getSelection() == htmlTab); |
| } |
| |
| /** |
| * 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(RichTextEditor.this); |
| action.execute(RichTextEditor.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(RichTextEditor.this); |
| action.execute(RichTextEditor.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(RichTextEditor.this); |
| action.execute(RichTextEditor.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(RichTextEditor.this); |
| action.execute(RichTextEditor.this); |
| } |
| }); |
| |
| contextMenu.addMenuListener(new MenuListener() { |
| public void menuHidden(MenuEvent e) { |
| } |
| |
| public void menuShown(MenuEvent e) { |
| String selectedText = getSelected().getText(); |
| boolean selection = selectedText.length() > 0; |
| cutItem.setEnabled(editable && selection); |
| copyItem.setEnabled(selection); |
| pasteItem.setEnabled(editable); |
| pastePlainTextItem.setEnabled(editable); |
| } |
| }); |
| } |
| |
| /** |
| * Updates the content of the rich text control without updating the HTML |
| * source editor. |
| * <p> |
| * This method should be called by the HTML source editor to sync up its |
| * content with the rich text control. |
| * |
| * @param text |
| * the rich text content in XHTML format |
| */ |
| private void updateRichText(String text) { |
| if (richText != null) { |
| richText.setText(text); |
| richText.checkModify(); |
| } |
| sourceModified = false; |
| if (tabFolder != null) { |
| if (toolBar != null) { |
| toolBar.updateToolBar(editable); |
| } |
| } |
| } |
| |
| private void addDropSupportToStyledText() { |
| // this function based heavily on the example at: |
| // http://www.eclipse.org/articles/Article-SWT-DND/DND-in-SWT.html |
| |
| // Allow data to be copied to the drop target |
| int operations = DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_DEFAULT; |
| sourceEditDropTarget = new DropTarget(getSourceEdit(), operations); |
| |
| // Receive data in Text or HTML format |
| final TextTransfer textTransfer = TextTransfer.getInstance(); |
| final HTMLTransfer htmlTransfer = HTMLTransfer.getInstance(); |
| Transfer[] types = new Transfer[] {htmlTransfer, textTransfer}; |
| sourceEditDropTarget.setTransfer(types); |
| |
| sourceEditDropTarget.addDropListener(new DropTargetListener() { |
| public void dragEnter(DropTargetEvent event) { |
| if (event.detail == DND.DROP_DEFAULT) { |
| if ((event.operations & DND.DROP_COPY) != 0) { |
| event.detail = DND.DROP_COPY; |
| } else { |
| event.detail = DND.DROP_NONE; |
| } |
| } |
| if (!getEditable()) { |
| event.detail = DND.DROP_NONE; |
| } |
| // will accept text but prefer to have HTML dropped |
| for (int i = 0; i < event.dataTypes.length; i++) { |
| if (htmlTransfer.isSupportedType(event.dataTypes[i])){ |
| event.currentDataType = event.dataTypes[i]; |
| break; |
| } |
| } |
| } |
| public void dragOver(DropTargetEvent event) { |
| event.feedback = DND.FEEDBACK_SELECT | DND.FEEDBACK_INSERT_AFTER | DND.FEEDBACK_SCROLL; |
| } |
| public void dragOperationChanged(DropTargetEvent event) { |
| if (event.detail == DND.DROP_DEFAULT) { |
| if ((event.operations & DND.DROP_COPY) != 0) { |
| event.detail = DND.DROP_COPY; |
| } else { |
| event.detail = DND.DROP_NONE; |
| } |
| } |
| } |
| public void dragLeave(DropTargetEvent event) { |
| } |
| public void dropAccept(DropTargetEvent event) { |
| } |
| public void drop(DropTargetEvent event) { |
| if (textTransfer.isSupportedType(event.currentDataType) || |
| htmlTransfer.isSupportedType(event.currentDataType)) { |
| String text = (String)event.data; |
| addHTML(text); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Displays the given debug message to the console. |
| */ |
| private void printDebugMessage(String method, String msg, String text) { |
| StringBuffer strBuf = new StringBuffer(); |
| strBuf.append("RichTextEditor[").append(richText.getControl().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); |
| } |
| |
| public FindReplaceAction getFindReplaceAction() { |
| return richText.getFindReplaceAction(); |
| } |
| |
| public void setFindReplaceAction(FindReplaceAction findReplaceAction) { |
| if (richText != null) { |
| richText.setFindReplaceAction(findReplaceAction); |
| richText.getFindReplaceAction().setRichText(this); |
| |
| } |
| } |
| |
| public void setInitialText(String text) { |
| if (richText != null) { |
| richText.setInitialText(text); |
| } |
| if (getSourceEdit() != null) { |
| removeModifyListeners(); |
| setDocument(new Document(text)); |
| addModifyListeners(); |
| } |
| |
| } |
| |
| |
| /** |
| * from org.eclipse.ui.texteditor.AbstractTextEditor#getUndoContext() |
| * Returns this editor's viewer's undo manager undo context. |
| * |
| * @return the undo context or <code>null</code> if not available |
| * @since 3.1 |
| */ |
| private IUndoContext getUndoContext() { |
| if (sourceViewer instanceof ITextViewerExtension6) { |
| IUndoManager undoManager= ((ITextViewerExtension6)sourceViewer).getUndoManager(); |
| if (undoManager instanceof IUndoManagerExtension) |
| return ((IUndoManagerExtension)undoManager).getUndoContext(); |
| } |
| return null; |
| } |
| |
| protected void createActions() { |
| createUndoRedoActions(); |
| // select all |
| Action selectAllAction = new Action() { |
| @Override |
| public void run() { |
| getSourceEdit().selectAll(); |
| } |
| }; |
| selectAllAction.setActionDefinitionId(IWorkbenchActionDefinitionIds.SELECT_ALL); |
| registerAction(ActionFactory.SELECT_ALL.getId(), selectAllAction); |
| } |
| |
| /* |
| * from org.eclipse.ui.texteditor.AbstractTextEditor#createUndoRedoActions() |
| */ |
| protected void createUndoRedoActions() { |
| IUndoContext undoContext= getUndoContext(); |
| if (undoContext != null) { |
| // Use actions provided by global undo/redo |
| |
| // Create the undo action |
| undoAction= new UndoActionHandler(getEditorSite(), undoContext); |
| PlatformUI.getWorkbench().getHelpSystem().setHelp(undoAction, IAbstractTextEditorHelpContextIds.UNDO_ACTION); |
| undoAction.setActionDefinitionId(IWorkbenchActionDefinitionIds.UNDO); |
| registerAction(ITextEditorActionConstants.UNDO, undoAction); |
| |
| // Create the redo action. |
| redoAction= new RedoActionHandler(getEditorSite(), undoContext); |
| PlatformUI.getWorkbench().getHelpSystem().setHelp(redoAction, IAbstractTextEditorHelpContextIds.REDO_ACTION); |
| redoAction.setActionDefinitionId(IWorkbenchActionDefinitionIds.REDO); |
| registerAction(ITextEditorActionConstants.REDO, redoAction); |
| } |
| } |
| |
| private IEditorSite getEditorSite() { |
| return editorSite; |
| } |
| |
| |
| /** |
| * Registers the given undo/redo action under the given ID and |
| * ensures that previously installed actions get disposed. It |
| * also takes care of re-registering the new action with the |
| * global action handler. |
| * |
| * @param actionId the action id under which to register the action |
| * @param action the action to register |
| * @since 3.1 |
| */ |
| private void registerAction(String actionId, IAction action) { |
| IAction oldAction= getAction(actionId); |
| if (oldAction instanceof OperationHistoryActionHandler) |
| ((OperationHistoryActionHandler)oldAction).dispose(); |
| |
| setAction(actionId, action); |
| |
| IActionBars actionBars= getEditorSite().getActionBars(); |
| if (actionBars != null) |
| actionBars.setGlobalActionHandler(actionId, action); |
| } |
| |
| |
| /* |
| * @see ITextEditor#getAction(String) |
| */ |
| public IAction getAction(String actionID) { |
| assert actionID != null; |
| IAction action= (IAction) fActions.get(actionID); |
| |
| // if (action == null) { |
| // action= findContributedAction(actionID); |
| // if (action != null) |
| // setAction(actionID, action); |
| // } |
| |
| return action; |
| } |
| |
| /* |
| * @see ITextEditor#setAction(String, IAction) |
| */ |
| public void setAction(String actionID, IAction action) { |
| assert actionID != null; |
| if (action == null) { |
| action= (IAction) fActions.remove(actionID); |
| if (action != null) |
| fActivationCodeTrigger.unregisterActionFromKeyActivation(action); |
| } else { |
| fActions.put(actionID, action); |
| fActivationCodeTrigger.registerActionForKeyActivation(action); |
| } |
| } |
| |
| /** |
| * Initializes the activation code trigger. |
| * |
| * @since 2.1 |
| */ |
| private void initializeActivationCodeTrigger() { |
| fActivationCodeTrigger.install(); |
| fActivationCodeTrigger.setScopes(fKeyBindingScopes); |
| } |
| |
| /** |
| * Internal key verify listener for triggering action activation codes. |
| */ |
| class ActivationCodeTrigger implements VerifyKeyListener { |
| |
| /** Indicates whether this trigger has been installed. */ |
| private boolean fIsInstalled= false; |
| /** |
| * The key binding service to use. |
| * @since 2.0 |
| */ |
| private IKeyBindingService fKeyBindingService; |
| |
| /* |
| * @see VerifyKeyListener#verifyKey(org.eclipse.swt.events.VerifyEvent) |
| */ |
| public void verifyKey(VerifyEvent event) { |
| |
| ActionActivationCode code= null; |
| int size= fActivationCodes.size(); |
| for (int i= 0; i < size; i++) { |
| code= (ActionActivationCode) fActivationCodes.get(i); |
| if (code.matches(event)) { |
| IAction action= getAction(code.fActionId); |
| if (action != null) { |
| |
| if (action instanceof IUpdate) |
| ((IUpdate) action).update(); |
| |
| if (!action.isEnabled() && action instanceof IReadOnlyDependent) { |
| IReadOnlyDependent dependent= (IReadOnlyDependent) action; |
| boolean writable= dependent.isEnabled(true); |
| if (writable) { |
| event.doit= false; |
| return; |
| } |
| } else if (action.isEnabled()) { |
| event.doit= false; |
| action.run(); |
| return; |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Installs this trigger on the editor's text widget. |
| * @since 2.0 |
| */ |
| public void install() { |
| if (!fIsInstalled) { |
| |
| if (sourceViewer instanceof ITextViewerExtension) { |
| ITextViewerExtension e= (ITextViewerExtension) sourceViewer; |
| e.prependVerifyKeyListener(this); |
| } else { |
| StyledText text= sourceViewer.getTextWidget(); |
| text.addVerifyKeyListener(this); |
| } |
| |
| fKeyBindingService= getEditorSite().getKeyBindingService(); |
| fIsInstalled= true; |
| } |
| } |
| |
| /** |
| * Uninstalls this trigger from the editor's text widget. |
| * @since 2.0 |
| */ |
| public void uninstall() { |
| if (fIsInstalled) { |
| |
| if (sourceViewer instanceof ITextViewerExtension) { |
| ITextViewerExtension e= (ITextViewerExtension) sourceViewer; |
| e.removeVerifyKeyListener(this); |
| } else if (sourceViewer != null) { |
| StyledText text= sourceViewer.getTextWidget(); |
| if (text != null && !text.isDisposed()) |
| text.removeVerifyKeyListener(fActivationCodeTrigger); |
| } |
| |
| fIsInstalled= false; |
| fKeyBindingService= null; |
| } |
| } |
| |
| /** |
| * Registers the given action for key activation. |
| * @param action the action to be registered |
| * @since 2.0 |
| */ |
| public void registerActionForKeyActivation(IAction action) { |
| if (action.getActionDefinitionId() != null) |
| fKeyBindingService.registerAction(action); |
| } |
| |
| /** |
| * The given action is no longer available for key activation |
| * @param action the action to be unregistered |
| * @since 2.0 |
| */ |
| public void unregisterActionFromKeyActivation(IAction action) { |
| if (action.getActionDefinitionId() != null) |
| fKeyBindingService.unregisterAction(action); |
| } |
| |
| /** |
| * Sets the key binding scopes for this editor. |
| * @param keyBindingScopes the key binding scopes |
| * @since 2.1 |
| */ |
| public void setScopes(String[] keyBindingScopes) { |
| if (keyBindingScopes != null && keyBindingScopes.length > 0) |
| fKeyBindingService.setScopes(keyBindingScopes); |
| } |
| } |
| |
| /** |
| * Representation of action activation codes. |
| */ |
| static class ActionActivationCode { |
| |
| /** The action id. */ |
| public String fActionId; |
| /** The character. */ |
| public char fCharacter; |
| /** The key code. */ |
| public int fKeyCode= -1; |
| /** The state mask. */ |
| public int fStateMask= SWT.DEFAULT; |
| |
| /** |
| * Creates a new action activation code for the given action id. |
| * @param actionId the action id |
| */ |
| public ActionActivationCode(String actionId) { |
| fActionId= actionId; |
| } |
| |
| /** |
| * Returns <code>true</code> if this activation code matches the given verify event. |
| * @param event the event to test for matching |
| * @return whether this activation code matches <code>event</code> |
| */ |
| public boolean matches(VerifyEvent event) { |
| return (event.character == fCharacter && |
| (fKeyCode == -1 || event.keyCode == fKeyCode) && |
| (fStateMask == SWT.DEFAULT || event.stateMask == fStateMask)); |
| } |
| } |
| |
| } |