Commit 6cadad1e authored by Sudeep Kanav's avatar Sudeep Kanav

Moved the abstract source editor to tooling base.

Issue-Ref: 3536
Signed-off-by: Sudeep Kanav's avatarSudeep Kanav <kanav@fortiss.org>
parent 54cab21b
......@@ -23,6 +23,7 @@ Export-Package: org.fortiss.tooling.base.ui,
org.fortiss.tooling.base.ui.dnd.gef,
org.fortiss.tooling.base.ui.dnd.jface,
org.fortiss.tooling.base.ui.editor,
org.fortiss.tooling.base.ui.editor.annotations,
org.fortiss.tooling.base.ui.editpart,
org.fortiss.tooling.base.ui.editpart.allocation,
org.fortiss.tooling.base.ui.editpart.command,
......
......@@ -3,8 +3,10 @@ AllocationDiagramEditorBase.java fa544546f73eea1150d90b7257b0e6bce82c288c GREEN
CommonDiagramEditorBase.java 69994faea09eec342b8dfaa484ffc03b60978496 GREEN
ConstraintBasedProcessEditor.java 155a5a7ac41c668ae7ef978e8be13b83f5abd67d GREEN
ConstraintBasedProcessEditorHelper.java 11d48e8a2aecd48eea259a42ef1192c4b292a334 GREEN
CustomUndoRedoForSourceEditor.java 30c68687962e1b8ff299cfdb938c4dad444edee0 YELLOW
DiagramEditorBase.java 09663ce095074d1a8eef086284eea0a7776e0431 GREEN
DiagramKeyHandler.java cfd15ac8f9fc933739cef5e7039960e19826d1ce GREEN
FormsEditorBase.java 4046d340913d951340084ae7240d79f8e75cb8d4 GREEN
GEFEditorBase.java e668f596f45f07215994cbbd3929a9438331718f GREEN
SourceEditorBase.java 66c3c1c0aea638912efeef6de7cc6502481b95ae YELLOW
TreeViewerEditorBase.java 1c59689ff57c4f3cc180d85f13021fc03461ecb0 GREEN
/*-------------------------------------------------------------------------+
| Copyright 2014 fortiss GmbH |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
+--------------------------------------------------------------------------*/
package org.fortiss.tooling.base.ui.editor;
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;
/**
* Hack to fix the seemingly broken undo-redo functionality of SourceViewer.
*
* @author aravantinos
*/
public class CustomUndoRedoForSourceEditor implements KeyListener, ExtendedModifyListener {
/**
* Encapsulation of the Undo and Redo stack(s).
*/
private static class UndoRedoStack<T> {
/** Command stack in the "undo direction". */
private Stack<T> undo;
/** Command stack in the "redo direction". */
private Stack<T> redo;
/** Constructor. */
public UndoRedoStack() {
undo = new Stack<T>();
redo = new Stack<T>();
}
/** Adds a command to the undo stack. */
public void pushUndo(T delta) {
undo.add(delta);
}
/** Adds a command to the redo stack. */
public void pushRedo(T delta) {
redo.add(delta);
}
/** Pops a command from the undo stack. */
public T popUndo() {
T res = undo.pop();
return res;
}
/** Pops a command from the redo stack. */
public T popRedo() {
T res = redo.pop();
return res;
}
/** Empties the redo stack. */
public void clearRedo() {
redo.clear();
}
/** Returns true if the undo stack is not empty. */
public boolean hasUndo() {
return !undo.isEmpty();
}
/** Returns true if the redo stack is not empty. */
public boolean hasRedo() {
return !redo.isEmpty();
}
}
/** The editor GUI itself. */
private StyledText editor;
/** The local undo/redo command stack. */
private UndoRedoStack<ExtendedModifyEvent> stack;
/** Flag to know whether we are in the course of an undo event. */
private boolean isUndo;
/** Flag to know whether we are in the course of an redo event. */
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 CustomUndoRedoForSourceEditor(StyledText editor) {
editor.addExtendedModifyListener(this);
editor.addKeyListener(this);
this.editor = editor;
stack = new UndoRedoStack<ExtendedModifyEvent>();
}
/** {@inheritDoc} */
@Override
public void keyPressed(KeyEvent e) {
// Listen to CTRL+Z for Undo, to CTRL+Y or CTRL+SHIFT+Z for Redo
if((e.stateMask & SWT.MOD1) > 0 && !((e.stateMask & SWT.ALT) > 0)) {
boolean isShift = (e.stateMask & SWT.SHIFT) > 0;
if(!isShift && e.keyCode == 'z') {
undo();
}
}
}
/** {@inheritDoc} */
@Override
public void keyReleased(KeyEvent e) {
// Listen to CTRL+Z for Undo, to CTRL+Y or CTRL+SHIFT+Z for Redo
if((e.stateMask & SWT.MOD1) > 0 && !((e.stateMask & SWT.ALT) > 0)) {
boolean isShift = (e.stateMask & SWT.SHIFT) > 0;
if(!isShift && e.keyCode == 'z') {
undo();
} else if(!isShift && e.keyCode == 'y' || isShift && e.keyCode == 'z') {
redo();
}
}
}
/**
* 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)
*/
@Override
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();
}
}
}
/**
* 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());
}
}
/*-------------------------------------------------------------------------+
| Copyright 2012 fortiss GmbH |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
+--------------------------------------------------------------------------*/
package org.fortiss.tooling.base.ui.editor;
import static java.util.regex.Pattern.compile;
import static org.eclipse.wb.swt.SWTResourceManager.getColor;
import static org.fortiss.tooling.base.ui.editor.annotations.ErrorAnnotation.ERROR_RGB;
import static org.fortiss.tooling.base.ui.editor.annotations.ErrorAnnotation.ERROR_TYPE;
import static org.fortiss.tooling.base.ui.utils.FontUtils.CODEFONT_11PT;
import static org.fortiss.tooling.base.ui.utils.FontUtils.createFont;
import java.util.regex.Pattern;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EContentAdapter;
import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.ITextListener;
import org.eclipse.jface.text.TextEvent;
import org.eclipse.jface.text.source.AnnotationBarHoverManager;
import org.eclipse.jface.text.source.AnnotationModel;
import org.eclipse.jface.text.source.AnnotationPainter;
import org.eclipse.jface.text.source.AnnotationRulerColumn;
import org.eclipse.jface.text.source.CompositeRuler;
import org.eclipse.jface.text.source.IAnnotationAccess;
import org.eclipse.jface.text.source.ISharedTextColors;
import org.eclipse.jface.text.source.OverviewRuler;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ST;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.actions.TextStyledTextActionHandler;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.wb.swt.SWTResourceManager;
import org.fortiss.tooling.base.ui.editor.annotations.AnnotationHover;
import org.fortiss.tooling.base.ui.editor.annotations.AnnotationMarkerAccess;
import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
import org.fortiss.tooling.kernel.service.ILibraryService;
import org.fortiss.tooling.kernel.service.IPersistencyService;
import org.fortiss.tooling.kernel.ui.extension.base.EditorBase;
import org.fortiss.tooling.kernel.ui.service.IActionService;
/**
* Editor for code specifications.
*
* @author kanav
* @param <T>
*/
public abstract class SourceEditorBase<T extends EObject> extends EditorBase<T> {
/** Stores the forms toolkit. */
private final FormToolkit formToolkit = new FormToolkit(Display.getDefault());
/** Stores the scrolled form. */
private ScrolledForm scrldfrm;
/** Stores the source viewer. */
protected SourceViewer sourceViewer;
/** Stores the annotation model. */
protected AnnotationModel annotationModel = new AnnotationModel();
/** Tracks whether changes to the source programmatically or by the user. */
private EditSource editSource = new EditSource();
/** Returns annotationModel. */
public AnnotationModel getAnnotationModel() {
return annotationModel;
}
/** Stores the pattern applied to the error messages. */
protected final Pattern pattern = compile("line (\\d*):(\\d*) (.*)");
/** {@inheritDoc} */
@Override
public void createPartControl(Composite parent) {
installModelChangeListener();
parent.setLayout(new FillLayout());
scrldfrm = formToolkit.createScrolledForm(parent);
formToolkit.paintBordersFor(scrldfrm);
scrldfrm.setText(getTitleText());
scrldfrm.getBody().setLayout(new FillLayout(SWT.HORIZONTAL));
formToolkit.decorateFormHeading(scrldfrm.getForm());
createCodeEditor(scrldfrm.getBody());
updateModel();
if(ILibraryService.getInstance().isLibraryElementShadow(editedObject)) {
sourceViewer.getControl().setEnabled(false);
}
}
/** Returns the title of the source viewer. */
protected abstract String getTitleText();
/**
* Installs an adapter that updates the editor whenever the underlying
* model is modified. Currently, only renaming is supported.
*/
protected void installModelChangeListener() {
getEditedObject().eAdapters().add(new EContentAdapter() {
/** {@inheritDoc} */
@Override
public void notifyChanged(Notification notification) {
if((notification.getEventType() == Notification.SET)) {
// Only trigger for model changes of the string representation. Otherwise, we
// would also react on the indirect change of the container.
if(!isTargetFeature(notification) || !isUpdateableAndRequired()) {
return;
}
// Do not act on user edits.
if(editSource.editor) {
return;
}
// Update the displayed sources and track the state.
editSource.model = true;
String formattedCode = getStringRepresentation();
sourceViewer.getTextWidget().setText(formattedCode);
sourceViewer.unconfigure();
sourceViewer.configure(getSourceViewerConfig());
editSource.model = false;
showErrors();
}
}
/**
* Check whether the modification updated the string representation of the code spec.
*/
private boolean isTargetFeature(Notification notification) {
if(notification.getFeature() instanceof EAttribute) {
EAttribute feature = (EAttribute)notification.getFeature();
if(feature == getTargetFeature()) {
return true;
}
}
return false;
}
/** Check parsability and the presence of a change. */
private boolean isUpdateableAndRequired() {
// Only update parsable models.
if(toBeParsed()) {
return false;
}
// No change --> Exit.
if((sourceViewer.getTextWidget() == null) || (sourceViewer.getTextWidget().getText()
.equals(getStringRepresentation()))) {
return false;
}
return true;
}
});
}
/** Returns the feature in ecore model for this source editor. */
protected abstract EAttribute getTargetFeature();
/** Creates the actual editor. */
private void createCodeEditor(Composite parent) {
IAnnotationAccess fAnnotationAccess = new AnnotationMarkerAccess();
// rulers
CompositeRuler fCompositeRuler = new CompositeRuler();
OverviewRuler fOverviewRuler =
new OverviewRuler(fAnnotationAccess, 12, new ISharedTextColors() {
/** {@inheritDoc} */
@Override
public Color getColor(RGB rgb) {
// Cannot use static import to disambiguate from local method name
return SWTResourceManager.getColor(rgb);
}
/** {@inheritDoc} */
@Override
public void dispose() {
// Nothing to do
}
});
AnnotationRulerColumn annotationRuler =
new AnnotationRulerColumn(annotationModel, 16, fAnnotationAccess);
fCompositeRuler.setModel(annotationModel);
fOverviewRuler.setModel(annotationModel);
// annotation ruler is decorating our composite ruler
fCompositeRuler.addDecorator(0, annotationRuler);
// add what types are show on the different rulers
annotationRuler.addAnnotationType(ERROR_TYPE);
fOverviewRuler.addAnnotationType(ERROR_TYPE);
fOverviewRuler.addHeaderAnnotationType(ERROR_TYPE);
// set what layer this type is on
fOverviewRuler.setAnnotationTypeLayer(ERROR_TYPE, 3);
// set what color is used on the overview ruler for the type
fOverviewRuler.setAnnotationTypeColor(ERROR_TYPE, getColor(ERROR_RGB));
// create the actual source viewer
sourceViewer = new SourceViewer(parent, fCompositeRuler, fOverviewRuler, true,
SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL);
sourceViewer.setDocument(new Document(), annotationModel);
// Context menu
Menu menu = new Menu(parent.getShell(), SWT.POP_UP);
final StyledText editor = sourceViewer.getTextWidget();
createMenuItem(menu, "Cut", () -> editor.invokeAction(ST.CUT));
createMenuItem(menu, "Copy", () -> editor.invokeAction(ST.COPY));
createMenuItem(menu, "Paste", () -> editor.invokeAction(ST.PASTE));
editor.setMenu(menu);
// hover manager that shows text when hovering
AnnotationBarHoverManager fAnnotationHoverManager =
new AnnotationBarHoverManager(fCompositeRuler, sourceViewer,
new AnnotationHover(annotationModel), new AnnotationConfiguration());
fAnnotationHoverManager.install(annotationRuler.getControl());
// to paint the annotations
AnnotationPainter ap = new AnnotationPainter(sourceViewer, fAnnotationAccess);
ap.addAnnotationType(null, ERROR_TYPE);
ap.setAnnotationTypeColor(ERROR_TYPE, getColor(ERROR_RGB));
sourceViewer.addPainter(ap);
// Setup the editor concerning syntax highlighting and content assist
sourceViewer.configure(getSourceViewerConfig());
Font font = createFont(CODEFONT_11PT);
sourceViewer.getTextWidget().setFont(font);
new CustomUndoRedoForSourceEditor(sourceViewer.getTextWidget());
sourceViewer.addTextListener(new ITextListener() {
@Override
public void textChanged(TextEvent event) {
if(editSource.model) {
return;
}
if(event.getDocumentEvent() != null) {
if(!isInitialLoading(event)) {
// Persist the current state of the editor on every input. Guarded to
// support the model change listener (responsible for renaming).
editSource.editor = true;
ITopLevelElement modelContext = IPersistencyService.getInstance()
.getTopLevelElementFor(getEditedObject());
modelContext.runAsCommand(() -> updateTheStringInTheModel(
sourceViewer.getTextWidget().getText()));
editSource.editor = false;
}
showErrors();
}
}
private boolean isInitialLoading(TextEvent event) {
if(event.getDocumentEvent().getModificationStamp() == 1) {
return true;
}
return false;
}
});
sourceViewer.getControl().addDisposeListener(new DisposeListener() {
/** {@inheritDoc} */
@Override
public void widgetDisposed(DisposeEvent e) {
font.dispose();
}
});
}
/**
* Creates the SourceViewerConfiguration containing the setup for both syntax highlighting and
* content assist.
*/
abstract protected SourceViewerConfiguration getSourceViewerConfig();
/** Returns the string representation of the object. */
abstract protected String getStringRepresentation();
/** Update the string representation in the model element. */
abstract protected void updateTheStringInTheModel(String s);
/** Displays the actual error in the editor. */
public abstract void showErrors();
/** {@inheritDoc} */
@Override
public void registerGlobalActions(IActionBars bars) {
IActionService.getInstance().registerGlobalUndoRedoActions(bars);
super.registerGlobalActions(bars);
}
/** {@inheritDoc} */
@Override
protected void addTextStyledText(TextStyledTextActionHandler textStyledTextActionHandler) {
textStyledTextActionHandler.addText(sourceViewer.getTextWidget());
}
/** {@inheritDoc} */
@Override
public void dispose() {
super.dispose();
}
/** Performs the text to model binding. */
abstract protected void updateModel();
/** Checks if the model element is to be parsed or not. */
abstract protected boolean toBeParsed();
/**
* Adds a menu item to <code>menu</code> with description <code>description</code>, triggering
* <code>r</code> when selected.
*/
protected static void createMenuItem(Menu menu, String description, Runnable r) {
MenuItem pasteItem = new MenuItem(menu, SWT.PUSH);
pasteItem.setText(description);
pasteItem.addSelectionListener(new SelectionAdapter() {
/** {@inheritDoc} */
@Override
public void widgetSelected(SelectionEvent e) {
r.run();
}
});
}
/** Encapsulates the source of a textual change in the spec. */
private class EditSource {
/** Change performed by a user. */
public boolean editor = false;
/** Change coming from the model. */
public boolean model = false;
}
/** Class acting as annotation configuration. */
public static class AnnotationConfiguration implements IInformationControlCreator {
/** {@inheritDoc} */
@Override
public IInformationControl createInformationControl(Shell shell) {
return new DefaultInformationControl(shell);
}
}
}