/******************************************************************************* * 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)); } } }