blob: 7c58a26c6fdbd9cec4e986f407892c55619ec131 [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2012, 2016 CEA LIST.
*
* 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:
* Arnault Lapitre (CEA LIST) arnault.lapitre@cea.fr
* - Initial API and implementation
*****************************************************************************/
package org.eclipse.efm.papyrus.view.property.concretesyntax.sheet.tooling;
import java.util.Stack;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ExtendedModifyEvent;
import org.eclipse.swt.custom.ExtendedModifyListener;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
/**
* Adds the Undo-Redo functionality (working Ctrl+Z and Ctrl+Y) to an instance
* of {@link StyledText}.
*
* @author Petr Bodnar
* @see {@linkplain http
* ://www.java2s.com/Code/Java/SWT-JFace-Eclipse/SWTUndoRedo.htm} -
* inspiration for this code, though not really functioning - it mainly
* shows which listeners to use...
* @see {@linkplain http
* ://stackoverflow.com/questions/7179464/swt-how-to-recreate
* -a-default-context-menu-for-text-fields} -
* "SWT's StyledText doesn't support Undo-Redo out-of-the-box"
*/
public class UndoStyledTextManager implements KeyListener, ExtendedModifyListener {
/**
* Encapsulation of the Undo and Redo stack(s).
*/
private static class UndoRedoStack<T> {
private Stack<T> undo;
private Stack<T> redo;
public UndoRedoStack() {
undo = new Stack<T>();
redo = new Stack<T>();
}
public void pushUndo(T delta) {
undo.add(delta);
}
public void pushRedo(T delta) {
redo.add(delta);
}
public T popUndo() {
T res = undo.pop();
return res;
}
public T popRedo() {
T res = redo.pop();
return res;
}
public void clearRedo() {
redo.clear();
}
public boolean hasUndo() {
return !undo.isEmpty();
}
public boolean hasRedo() {
return !redo.isEmpty();
}
}
private StyledText editor;
private UndoRedoStack<ExtendedModifyEvent> stack;
private boolean isUndo;
private boolean isRedo;
/**
* Creates a new instance of this class. Automatically starts listening to
* corresponding key and modify events coming from the given
* <var>editor</var>.
*
* @param editor
* the text field to which the Undo-Redo functionality should be
* added
*/
public UndoStyledTextManager(StyledText editor) {
editor.addExtendedModifyListener(this);
editor.addKeyListener(this);
this.editor = editor;
stack = new UndoRedoStack<ExtendedModifyEvent>();
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.swt.events.KeyListener#keyPressed(org.eclipse.swt.events.
* KeyEvent)
*/
public void keyPressed(KeyEvent e) {
// Listen to CTRL+Z for Undo, to CTRL+Y or CTRL+SHIFT+Z for Redo
boolean isCtrl = (e.stateMask & SWT.CTRL) > 0;
boolean isAlt = (e.stateMask & SWT.ALT) > 0;
if (isCtrl && !isAlt) {
boolean isShift = (e.stateMask & SWT.SHIFT) > 0;
if (!isShift && e.keyCode == 'z') {
undo();
} else if (!isShift && e.keyCode == 'y' || isShift
&& e.keyCode == 'z') {
redo();
}
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.swt.events.KeyListener#keyReleased(org.eclipse.swt.events
* .KeyEvent)
*/
public void keyReleased(KeyEvent e) {
// ignore
}
/**
* Creates a corresponding Undo or Redo step from the given event and pushes
* it to the stack. The Redo stack is, logically, emptied if the event comes
* from a normal user action.
*
* @param event
* @see org.eclipse.swt.custom.ExtendedModifyListener#modifyText(org.eclipse.
* swt.custom.ExtendedModifyEvent)
*/
public void modifyText(ExtendedModifyEvent event) {
if (isUndo) {
stack.pushRedo(event);
} else { // is Redo or a normal user action
stack.pushUndo(event);
if (!isRedo) {
stack.clearRedo();
// TODO Switch to treat consecutive characters as one event?
}
}
}
/**
* Performs the Undo action. A new corresponding Redo step is automatically
* pushed to the stack.
*/
private void undo() {
if (stack.hasUndo()) {
isUndo = true;
revertEvent(stack.popUndo());
isUndo = false;
}
}
/**
* Performs the Redo action. A new corresponding Undo step is automatically
* pushed to the stack.
*/
private void redo() {
if (stack.hasRedo()) {
isRedo = true;
revertEvent(stack.popRedo());
isRedo = false;
}
}
/**
* Reverts the given modify event, in the way as the Eclipse text editor
* does it.
*
* @param event
*/
private void revertEvent(ExtendedModifyEvent event) {
editor.replaceTextRange(event.start, event.length, event.replacedText);
// (causes the modifyText() listener method to be called)
editor.setSelectionRange(event.start, event.replacedText.length());
}
}