/* ****************************************************************************** * Copyright (c) 2006-2012 XMind Ltd. and others. * * This file is a part of XMind 3. XMind releases 3 and * above are dual-licensed under the Eclipse Public License (EPL), * which is available at http://www.eclipse.org/legal/epl-v10.html * and the GNU Lesser General Public License (LGPL), * which is available at http://www.gnu.org/licenses/lgpl.html * See http://www.xmind.net/license.html for details. * * Contributors: * XMind Ltd. - initial API and implementation *******************************************************************************/ package org.xmind.ui.texteditor; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextOperationTarget; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.swt.SWT; 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.Shell; import org.xmind.gef.GEF; import org.xmind.gef.IGraphicalViewer; import org.xmind.gef.Request; import org.xmind.gef.command.Command; import org.xmind.gef.command.CommandStackBase; import org.xmind.gef.command.ICommandStack; import org.xmind.gef.command.ICommandStack2; import org.xmind.gef.command.ICommandStackDelegate; import org.xmind.gef.event.KeyEvent; import org.xmind.gef.part.IGraphicalEditPart; import org.xmind.gef.part.IPart; import org.xmind.gef.tool.EditTool; import org.xmind.gef.tool.PartTextSelection; import org.xmind.ui.viewers.SWTUtils; public abstract class FloatingTextEditTool extends EditTool { private static final boolean DEBUG = false; private class TextCommandStackDelegate extends CommandStackBase implements ICommandStackDelegate { public boolean canExecute(Command command) { return false; } public boolean canRedo() { return FloatingTextEditTool.this.canRedo(); } public boolean canUndo() { //return FloatingTextEditTool.this.canUndo(); return true; } public void clear() { } public void execute(Command command) { } public String getRedoLabel() { return FloatingTextEditTool.this.getRedoLabel(); } public String getUndoLabel() { return FloatingTextEditTool.this.getUndoLabel(); } public void redo() { FloatingTextEditTool.this.redo(); } public void undo() { FloatingTextEditTool.this.undo(); } public void fireUpdate() { fireEvent(GEF.CS_UPDATED); } } private class EditorListener extends IFloatingTextEditorListener.Stub implements Listener { public void editingCanceled(TextEvent e) { if (closingFromTool) return; closingFromEditor = true; cancelEditing(); closingFromEditor = false; } public void editingFinished(TextEvent e) { if (closingFromTool) return; closingFromEditor = true; finishEditing(); closingFromEditor = false; } public void textChanged(TextEvent e) { Display.getCurrent().asyncExec(new Runnable() { public void run() { if (!getStatus().isStatus(GEF.ST_ACTIVE)) return; updateCommandActions(); } }); } public void handleEvent(Event event) { if (event.type == SWT.Dispose) { uninstallCommandStackDelegate(); } else if (event.type == SWT.FocusOut) { final Shell oldShell = getTargetViewer().getControl() .getShell(); final Display display = event.display; display.asyncExec(new Runnable() { public void run() { if (!getStatus().isStatus(GEF.ST_ACTIVE) || oldShell.isDisposed()) return; if (!display.isDisposed()) { display.asyncExec(new Runnable() { public void run() { if (display.isDisposed() || oldShell.isDisposed() || !getStatus() .isStatus(GEF.ST_ACTIVE)) return; Shell newShell = display.getActiveShell(); if (newShell != null && !newShell.isDisposed() && !isDescendantShell(newShell, oldShell)) { finishEditing(); } } }); } } }); } } private boolean isDescendantShell(Shell newShell, Shell oldShell) { Composite parent = newShell.getParent(); if (parent == null || !(parent instanceof Shell)) return false; if (parent == oldShell) return true; return isDescendantShell((Shell) parent, oldShell); } } private class EditorSelectionChangedListener implements ISelectionChangedListener { public void selectionChanged(SelectionChangedEvent event) { notifySelectionChange(); } } private FloatingTextEditor editor = null; private boolean closingFromEditor = false; private boolean closingFromTool = false; private EditorListener editorListener = null; private ISelectionChangedListener editorSelectionChangedListener; private boolean notifyingSelectionChange = false; private TextCommandStackDelegate delegate = null; private ICommandStackDelegate oldDelegate = null; private boolean focusOnRequest = true; public FloatingTextEditTool() { this(false); } public FloatingTextEditTool(boolean listensToSelectionChange) { if (listensToSelectionChange) { editorSelectionChangedListener = new EditorSelectionChangedListener(); } else { editorSelectionChangedListener = null; } } public FloatingTextEditor getEditor() { return editor; } public ITextSelection getTextSelection() { ISelection editorSelection = editor == null ? null : editor.getSelection(); if (editorSelection instanceof ITextSelection) { ITextSelection s = (ITextSelection) editorSelection; PartTextSelection realSelection = new PartTextSelection(getSource(), (IDocument) editor.getInput(), s.getOffset(), s.getLength()); return realSelection; } return null; } public void setTextSelection(ITextSelection selection) { if (notifyingSelectionChange) return; if (selection != null) { editor.setSelection(selection, true); } else { cancelEditing(); } } protected void notifySelectionChange() { if (editorSelectionChangedListener == null) return; notifyingSelectionChange = true; getTargetViewer().setSelection(getTextSelection(), false); notifyingSelectionChange = false; } @Override protected void handleEditRequest(Request request) { focusOnRequest = !request.hasParameter(GEF.PARAM_FOCUS) || request.isParameter(GEF.PARAM_FOCUS); super.handleEditRequest(request); if (getDomain().getActiveTool() == this) { Object text = request.getParameter(GEF.PARAM_TEXT); if (text instanceof String) { if (editor != null && !editor.isClosed()) { editor.replaceText((String) text); } } Object selection = request.getParameter(GEF.PARAM_TEXT_SELECTION); if (selection instanceof ITextSelection) { setTextSelection((ITextSelection) selection); } if (focusOnRequest && editor != null && !editor.isClosed()) { editor.setFocus(); } } } protected boolean startEditing(IGraphicalEditPart source) { IDocument document = getTextContents(source); if (document == null) return false; if (editor == null) { editor = createEditor(); } if (editor == null) return false; hookEditor(editor); boolean started = openEditor(editor, document); if (started) { notifySelectionChange(); } return started; } protected void installCommandStackDelegate() { ICommandStack cs = getDomain().getCommandStack(); if (cs != null && cs instanceof ICommandStack2) { delegate = new TextCommandStackDelegate(); ICommandStack2 cs2 = (ICommandStack2) cs; oldDelegate = cs2.getDelegate(); cs2.setDelegate(delegate); } } protected void uninstallCommandStackDelegate() { if (delegate != null) { ICommandStack cs = getDomain().getCommandStack(); if (cs != null && cs instanceof ICommandStack2) { ICommandStack2 cs2 = (ICommandStack2) cs; cs2.setDelegate(oldDelegate); } oldDelegate = null; delegate = null; } } protected void updateCommandActions() { if (delegate != null) { delegate.fireUpdate(); } } protected boolean openEditor(FloatingTextEditor editor, IDocument document) { boolean wasOpen = !editor.isClosed(); editor.setInput(document); boolean isOpen = editor.open(false); if (isOpen) { if (editor.canDoOperation(ITextOperationTarget.SELECT_ALL)) { editor.doOperation(ITextOperationTarget.SELECT_ALL); } if (!wasOpen) { hookEditorControl(editor, editor.getTextViewer()); } } return isOpen; } protected void hookEditorControl(FloatingTextEditor editor, ITextViewer textViewer) { installCommandStackDelegate(); textViewer.getTextWidget().addListener(SWT.FocusOut, getEditorListener()); textViewer.getTextWidget().addListener(SWT.Dispose, getEditorListener()); } protected void cancelEditing() { if (editor != null) { if (closingFromEditor) { unhookEditor(editor); } else if (!editor.isClosed()) { closingFromTool = true; closeEditor(editor, false); closingFromTool = false; } editor = null; } super.cancelEditing(); notifySelectionChange(); } protected void finishEditing() { if (DEBUG) System.out.println("Finish Editing"); //$NON-NLS-1$ if (editor != null) { if (closingFromEditor) { unhookEditor(editor); } else if (!editor.isClosed()) { closingFromTool = true; if (DEBUG) System.out.println("Close editor"); //$NON-NLS-1$ closeEditor(editor, true); closingFromTool = false; } Object input = editor.getInput(); if (input instanceof IDocument) { IDocument document = (IDocument) input; if (DEBUG) System.out.println("Perform text modification"); //$NON-NLS-1$ handleTextModified(getSource(), document); } editor = null; } super.finishEditing(); notifySelectionChange(); } protected void closeEditor(FloatingTextEditor editor, boolean finish) { unhookEditor(editor); editor.close(finish); } protected abstract IDocument getTextContents(IPart source); protected abstract void handleTextModified(IPart source, IDocument document); protected FloatingTextEditor createEditor() { int style = SWT.BORDER | SWT.V_SCROLL | (isMultilineAllowed() ? SWT.MULTI : SWT.SINGLE); if (isWrapAllowed()) { style |= SWT.WRAP; } else { style |= SWT.H_SCROLL; } FloatingTextEditor editor = new FloatingTextEditor( getTargetViewer().getCanvas(), style); return editor; } protected void hookEditor(FloatingTextEditor editor) { editor.addFloatingTextEditorListener(getEditorListener()); if (editorSelectionChangedListener != null) editor.addSelectionChangedListener(editorSelectionChangedListener); } protected void unhookEditor(FloatingTextEditor editor) { if (editorSelectionChangedListener != null) editor.removeSelectionChangedListener( editorSelectionChangedListener); editor.removeFloatingTextEditorListener(getEditorListener()); Display.getCurrent().asyncExec(new Runnable() { @Override public void run() { restoreFocusControl(); } }); } private EditorListener getEditorListener() { if (editorListener == null) editorListener = new EditorListener(); return editorListener; } protected boolean isMultilineAllowed() { return false; } protected boolean isWrapAllowed() { return false; } protected void selectAll() { if (editor != null && editor.canDoOperation(FloatingTextEditor.SELECT_ALL)) { editor.doOperation(FloatingTextEditor.SELECT_ALL); } } protected void copy() { if (editor != null && editor.canDoOperation(FloatingTextEditor.COPY)) { editor.doOperation(FloatingTextEditor.COPY); } } protected void cut() { if (editor != null && editor.canDoOperation(FloatingTextEditor.CUT)) { editor.doOperation(FloatingTextEditor.CUT); } } protected void delete() { if (editor != null && editor.canDoOperation(FloatingTextEditor.DELETE)) { editor.doOperation(FloatingTextEditor.DELETE); } } protected void paste() { if (editor != null && editor.canDoOperation(FloatingTextEditor.PASTE)) { editor.doOperation(FloatingTextEditor.PASTE); } } protected void undo() { if (editor != null && editor.canDoOperation(FloatingTextEditor.UNDO)) editor.doOperation(FloatingTextEditor.UNDO); } protected void redo() { if (editor != null && editor.canDoOperation(FloatingTextEditor.REDO)) editor.doOperation(FloatingTextEditor.REDO); } public boolean canUndo() { return editor != null && editor.canDoOperation(FloatingTextEditor.UNDO); } public boolean canRedo() { return editor != null && editor.canDoOperation(FloatingTextEditor.REDO); } protected abstract String getUndoLabel(); protected abstract String getRedoLabel(); protected boolean shouldFinish(KeyEvent ke) { return SWTUtils.matchKey(ke.getState(), ke.keyCode, 0, SWT.CR); } protected boolean shouldCancel(KeyEvent ke) { return SWTUtils.matchKey(ke.getState(), ke.keyCode, 0, SWT.ESC); } protected void restoreFocusControl() { IGraphicalViewer viewer = getTargetViewer(); if (viewer == null) return; Control control = viewer.getControl(); if (control == null || control.isDisposed()) return; control.setFocus(); } }