/* ******************************************************************************
* 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.richtext;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.IUndoManager;
import org.eclipse.jface.text.IUndoManagerExtension;
import org.eclipse.jface.text.Region;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.text.undo.DocumentUndoEvent;
public class RichTextViewerUndoManager implements IUndoManager,
IUndoManagerExtension {
/**
* Internal listener to mouse and key events.
*/
private class KeyAndMouseListener implements MouseListener, KeyListener {
/*
* @see MouseListener#mouseDoubleClick
*/
public void mouseDoubleClick(MouseEvent e) {
}
/*
* If the right mouse button is pressed, the current editing command is
* closed
*
* @see MouseListener#mouseDown
*/
public void mouseDown(MouseEvent e) {
if (e.button == 1)
commit();
}
/*
* @see MouseListener#mouseUp
*/
public void mouseUp(MouseEvent e) {
}
/*
* @see KeyListener#keyPressed
*/
public void keyReleased(KeyEvent e) {
}
/*
* On cursor keys, the current editing command is closed
*
* @see KeyListener#keyPressed
*/
public void keyPressed(KeyEvent e) {
switch (e.keyCode) {
case SWT.ARROW_UP:
case SWT.ARROW_DOWN:
case SWT.ARROW_LEFT:
case SWT.ARROW_RIGHT:
commit();
break;
}
}
}
/**
* Internal text input listener.
*/
private class TextInputListener implements ITextInputListener {
/*
* @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument,
* org.eclipse.jface.text.IDocument)
*/
public void inputDocumentAboutToBeChanged(IDocument oldInput,
IDocument newInput) {
disconnectDocumentUndoManager();
}
/*
* @see org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org.eclipse.jface.text.IDocument,
* org.eclipse.jface.text.IDocument)
*/
public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
connectDocumentUndoManager(newInput);
}
}
/**
* Internal document undo listener.
*/
private class RichDocumentUndoListener implements IRichDocumentUndoListener {
/*
* @see org.eclipse.jface.text.IDocumentUndoListener#documentUndoNotification(DocumentUndoEvent)
*/
public void documentUndoNotification(RichDocumentUndoEvent event) {
if (!isConnected())
return;
int eventType = event.getEventType();
if (((eventType & DocumentUndoEvent.ABOUT_TO_UNDO) != 0)
|| ((eventType & DocumentUndoEvent.ABOUT_TO_REDO) != 0)) {
if (event.isCompound()) {
ITextViewerExtension extension = null;
if (fTextViewer instanceof ITextViewerExtension)
extension = (ITextViewerExtension) fTextViewer;
if (extension != null)
extension.setRedraw(false);
}
// ATTN: commended for test
// fTextViewer.getTextWidget().getDisplay().syncExec( new Runnable() {
// public void run() {
// if ( fTextViewer instanceof TextViewer )
// ( (TextViewer) fTextViewer ).ignoreAutoEditStrategies( true );
// }
// } );
} else if (((eventType & DocumentUndoEvent.UNDONE) != 0)
|| ((eventType & DocumentUndoEvent.REDONE) != 0)) {
// fTextViewer.getTextWidget().getDisplay().syncExec( new Runnable() {
// public void run() {
// if ( fTextViewer instanceof TextViewer )
// ( (TextViewer) fTextViewer ).ignoreAutoEditStrategies( false );
// }
// } );
if (event.isCompound()) {
ITextViewerExtension extension = null;
if (fTextViewer instanceof ITextViewerExtension)
extension = (ITextViewerExtension) fTextViewer;
if (extension != null)
extension.setRedraw(true);
}
// Reveal the change if this manager's viewer has the focus.
if (fTextViewer != null) {
StyledText widget = fTextViewer.getTextWidget();
if (widget != null && !widget.isDisposed()
&& (widget.isFocusControl()))// ||
// fTextViewer.getTextWidget()
// == control))
selectAndReveal(event.getOffset(),
event.getText() == null ? 0 : event.getText()
.length());
}
}
}
}
/** The internal key and mouse event listener */
private KeyAndMouseListener fKeyAndMouseListener;
/** The internal text input listener */
private TextInputListener fTextInputListener;
/** The text viewer the undo manager is connected to */
private ITextViewer fTextViewer;
/** The undo level */
private int fUndoLevel;
/** The document undo manager that is active. */
private IRichDocumentUndoManager fDocumentUndoManager;
/** The document that is active. */
private IRichDocument fDocument;
/** The document undo listener */
private IRichDocumentUndoListener fDocumentUndoListener;
/**
* Creates a new undo manager who remembers the specified number of edit
* commands.
*
* @param undoLevel
* the length of this manager's history
*/
public RichTextViewerUndoManager(int undoLevel) {
fUndoLevel = undoLevel;
}
/**
* Returns whether this undo manager is connected to a text viewer.
*
* @return <code>true</code> if connected, <code>false</code> otherwise
*/
private boolean isConnected() {
return fTextViewer != null && fDocumentUndoManager != null;
}
public IRichDocumentUndoManager getDocumentUndoManager() {
return fDocumentUndoManager;
}
/*
* @see IUndoManager#beginCompoundChange
*/
public void beginCompoundChange() {
if (isConnected()) {
fDocumentUndoManager.beginCompoundChange();
//inCompound = true;
}
}
/*
* @see IUndoManager#endCompoundChange
*/
public void endCompoundChange() {
if (isConnected()) {
fDocumentUndoManager.endCompoundChange();
//inCompound = false;
}
}
/**
* Registers all necessary listeners with the text viewer.
*/
private void addListeners() {
StyledText text = fTextViewer.getTextWidget();
if (text != null) {
fKeyAndMouseListener = new KeyAndMouseListener();
text.addMouseListener(fKeyAndMouseListener);
text.addKeyListener(fKeyAndMouseListener);
fTextInputListener = new TextInputListener();
fTextViewer.addTextInputListener(fTextInputListener);
}
}
/**
* Unregister all previously installed listeners from the text viewer.
*/
private void removeListeners() {
StyledText text = fTextViewer.getTextWidget();
if (text != null) {
if (fKeyAndMouseListener != null) {
text.removeMouseListener(fKeyAndMouseListener);
text.removeKeyListener(fKeyAndMouseListener);
fKeyAndMouseListener = null;
}
if (fTextInputListener != null) {
fTextViewer.removeTextInputListener(fTextInputListener);
fTextInputListener = null;
}
}
}
// /**
// * Shows the given exception in an error dialog.
// *
// * @param title
// * the dialog title
// * @param ex
// * the exception
// */
// private void openErrorDialog(final String title, final Exception ex) {
// Shell shell = null;
// if (isConnected()) {
// StyledText st = fTextViewer.getTextWidget();
// if (st != null && !st.isDisposed())
// shell = st.getShell();
// }
// if (Display.getCurrent() != null)
// MessageDialog.openError(shell, title, ex.getLocalizedMessage());
// else {
// Display display;
// final Shell finalShell = shell;
// if (finalShell != null)
// display = finalShell.getDisplay();
// else
// display = Display.getDefault();
// display.syncExec(new Runnable() {
// public void run() {
// MessageDialog.openError(finalShell, title, ex
// .getLocalizedMessage());
// }
// });
// }
// }
/*
* @see org.eclipse.jface.text.IUndoManager#setMaximalUndoLevel(int)
*/
public void setMaximalUndoLevel(int undoLevel) {
fUndoLevel = Math.max(0, undoLevel);
if (isConnected()) {
fDocumentUndoManager.setMaximalUndoLevel(fUndoLevel);
}
}
/*
* @see org.eclipse.jface.text.IUndoManager#connect(org.eclipse.jface.text.ITextViewer)
*/
public void connect(ITextViewer textViewer) {
if (fTextViewer == null && textViewer != null) {
fTextViewer = textViewer;
addListeners();
}
IDocument doc = fTextViewer.getDocument();
connectDocumentUndoManager(doc);
}
/*
* @see org.eclipse.jface.text.IUndoManager#disconnect()
*/
public void disconnect() {
if (fTextViewer != null) {
removeListeners();
fTextViewer = null;
}
disconnectDocumentUndoManager();
}
/*
* @see org.eclipse.jface.text.IUndoManager#reset()
*/
public void reset() {
if (isConnected()) {
fDocumentUndoManager.reset();
}
}
/*
* @see org.eclipse.jface.text.IUndoManager#redoable()
*/
public boolean redoable() {
if (isConnected()) {
return fDocumentUndoManager.redoable();
}
return false;
}
/*
* @see org.eclipse.jface.text.IUndoManager#undoable()
*/
public boolean undoable() {
if (isConnected())
return fDocumentUndoManager.undoable();
return false;
}
/*
* @see org.eclipse.jface.text.IUndoManager#redo()
*/
public void redo() {
if (isConnected()) {
try {
fDocumentUndoManager.redo();
} catch (ExecutionException ex) {
// openErrorDialog(JFaceTextMessages.getString("DefaultUndoManager.error.redoFailed.title"),
// ex); //$NON-NLS-1$
}
}
}
/*
* @see org.eclipse.jface.text.IUndoManager#undo()
*/
public void undo() {
if (isConnected()) {
try {
fDocumentUndoManager.undo();
} catch (ExecutionException ex) {
// openErrorDialog(JFaceTextMessages.getString("DefaultUndoManager.error.undoFailed.title"),
// ex); //$NON-NLS-1$
}
}
}
/**
* Selects and reveals the specified range.
*
* @param offset
* the offset of the range
* @param length
* the length of the range
*/
private void selectAndReveal(int offset, int length) {
if (fTextViewer instanceof ITextViewerExtension5) {
ITextViewerExtension5 extension = (ITextViewerExtension5) fTextViewer;
extension.exposeModelRange(new Region(offset, length));
} else if (!fTextViewer.overlapsWithVisibleRegion(offset, length))
fTextViewer.resetVisibleRegion();
fTextViewer.setSelectedRange(offset, length);
fTextViewer.revealRange(offset, length);
}
/*
* @see org.eclipse.jface.text.IUndoManagerExtension#getUndoContext()
*/
public IUndoContext getUndoContext() {
if (isConnected()) {
return fDocumentUndoManager.getUndoContext();
}
return null;
}
private void connectDocumentUndoManager(IDocument document) {
disconnectDocumentUndoManager();
if (document != null && document instanceof IRichDocument) {
fDocument = (IRichDocument) document;
RichDocumentUndoManagerRegistry.connect(fDocument);
fDocumentUndoManager = RichDocumentUndoManagerRegistry
.getDocumentUndoManager(fDocument);
fDocumentUndoManager.connect(this);
setMaximalUndoLevel(fUndoLevel);
fDocumentUndoListener = new RichDocumentUndoListener();
fDocumentUndoManager.addDocumentUndoListener(fDocumentUndoListener);
} else {
fDocument = null;
}
}
private void disconnectDocumentUndoManager() {
if (fDocumentUndoManager != null) {
fDocumentUndoManager.disconnect(this);
RichDocumentUndoManagerRegistry.disconnect(fDocument);
fDocumentUndoManager
.removeDocumentUndoListener(fDocumentUndoListener);
fDocumentUndoListener = null;
fDocumentUndoManager = null;
}
}
public void commit() {
if (isConnected()) {
fDocumentUndoManager.commit();
}
}
}