diff --git a/org.fortiss.tooling.base.ui/META-INF/MANIFEST.MF b/org.fortiss.tooling.base.ui/META-INF/MANIFEST.MF index 9bb1ff85da9bb0ceadff74e98f30f861d556d799..e7108c82a4fdc073a777578f0329f3db93bb9a77 100644 --- a/org.fortiss.tooling.base.ui/META-INF/MANIFEST.MF +++ b/org.fortiss.tooling.base.ui/META-INF/MANIFEST.MF @@ -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, diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/.ratings b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/.ratings index 16c1534386d115e94e2a6106fc20f35f4acd5893..2d1b98a8f4ee06c2a92ac72d47ec75a6dbf1b2d2 100644 --- a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/.ratings +++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/.ratings @@ -7,4 +7,7 @@ DiagramEditorBase.java 09663ce095074d1a8eef086284eea0a7776e0431 GREEN DiagramKeyHandler.java cfd15ac8f9fc933739cef5e7039960e19826d1ce GREEN FormsEditorBase.java 4046d340913d951340084ae7240d79f8e75cb8d4 GREEN GEFEditorBase.java e668f596f45f07215994cbbd3929a9438331718f GREEN +SourceEditorBase.java 347d6b45d577abd7e23a7234904be118e82469f1 YELLOW +SourceEditorConfigurationBase.java db3898fe1cace33aab0dd83d9711205c235c9861 YELLOW +SourceEditorUndoRedo.java 5f5d1b05c8b1287a9e37d866eda3474b6dcbd014 YELLOW TreeViewerEditorBase.java 1c59689ff57c4f3cc180d85f13021fc03461ecb0 GREEN diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/SourceEditorBase.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/SourceEditorBase.java new file mode 100644 index 0000000000000000000000000000000000000000..347d6b45d577abd7e23a7234904be118e82469f1 --- /dev/null +++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/SourceEditorBase.java @@ -0,0 +1,373 @@ +/*-------------------------------------------------------------------------+ +| 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 SourceEditorUndoRedo(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(() -> setStringRepresentation( + 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 setStringRepresentation(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); + } + } +} diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/SourceEditorConfigurationBase.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/SourceEditorConfigurationBase.java new file mode 100644 index 0000000000000000000000000000000000000000..db3898fe1cace33aab0dd83d9711205c235c9861 --- /dev/null +++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/SourceEditorConfigurationBase.java @@ -0,0 +1,121 @@ +/*-------------------------------------------------------------------------+ +| 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 org.eclipse.jface.text.IDocument.DEFAULT_CONTENT_TYPE; +import static org.eclipse.wb.swt.SWTResourceManager.getColor; + +import java.util.List; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.jface.text.presentation.IPresentationReconciler; +import org.eclipse.jface.text.presentation.PresentationReconciler; +import org.eclipse.jface.text.rules.DefaultDamagerRepairer; +import org.eclipse.jface.text.rules.IRule; +import org.eclipse.jface.text.rules.IToken; +import org.eclipse.jface.text.rules.ITokenScanner; +import org.eclipse.jface.text.rules.IWordDetector; +import org.eclipse.jface.text.rules.RuleBasedScanner; +import org.eclipse.jface.text.rules.WordRule; +import org.eclipse.jface.text.source.IAnnotationHover; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.jface.text.source.SourceViewerConfiguration; +import org.eclipse.swt.graphics.Color; +import org.fortiss.tooling.base.ui.editor.SourceEditorBase; +import org.fortiss.tooling.base.ui.editor.annotations.AnnotationHover; + +/** + * Class responsible for proper configuration of the {@link SourceEditorBase}. + * + * @author doebber + */ +public abstract class SourceEditorConfigurationBase<T extends EObject> + extends SourceViewerConfiguration { + + /** Stores the actual editor object. */ + protected SourceEditorBase<T> editor; + + /** Constructor. */ + public SourceEditorConfigurationBase(SourceEditorBase<T> editor) { + this.editor = editor; + } + + /** Color constant used to display code dark red. */ + protected Color DARK_RED = getColor(128, 0, 0); + + /** Color constant used to display code dark blue. */ + protected Color DARK_BLUE = getColor(0, 0, 128); + + /** Detector used to create the scanners. */ + protected IWordDetector detector = new IWordDetector() { + /** {@inheritDoc} */ + @Override + public boolean isWordStart(char c) { + return Character.isLetterOrDigit(c); + } + + /** {@inheritDoc} */ + @Override + public boolean isWordPart(char c) { + return Character.isLetterOrDigit(c) || c == '_'; + } + }; + + /** {@inheritDoc} */ + @Override + public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) { + PresentationReconciler reconciler = new PresentationReconciler(); + + DefaultDamagerRepairer dr = new DefaultDamagerRepairer(getScanner()); + reconciler.setDamager(dr, DEFAULT_CONTENT_TYPE); + reconciler.setRepairer(dr, DEFAULT_CONTENT_TYPE); + + return reconciler; + } + + /** Returns the scanner. */ + private ITokenScanner getScanner() { + return getScannerForSyntaxHighlighting(); + } + + /** Returns the annotation hover. */ + @Override + public IAnnotationHover getAnnotationHover(ISourceViewer sourceViewer) { + return new AnnotationHover(editor.getAnnotationModel()); + } + + /** Returns the rule based scanner. */ + private RuleBasedScanner getScannerForSyntaxHighlighting() { + List<WordRule> rules = getCommonRules(); + rules.add(getRuleSpecificToEditor()); + RuleBasedScanner scanner = new RuleBasedScanner(); + scanner.setRules(rules.toArray(new IRule[0])); + return scanner; + } + + /** Returns a list of rules common for the syntax highlighting. */ + abstract protected List<WordRule> getCommonRules(); + + /** Returns a rule specific to the editor. */ + abstract protected WordRule getRuleSpecificToEditor(); + + /** Adds the words to the rule with the given token. */ + protected void addWordsToRule(List<String> words, WordRule rule, IToken token) { + for(String word : words) { + rule.addWord(word, token); + } + } +} diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/SourceEditorUndoRedo.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/SourceEditorUndoRedo.java new file mode 100644 index 0000000000000000000000000000000000000000..5f5d1b05c8b1287a9e37d866eda3474b6dcbd014 --- /dev/null +++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/SourceEditorUndoRedo.java @@ -0,0 +1,200 @@ +/*-------------------------------------------------------------------------+ +| 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 SourceEditorUndoRedo 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 SourceEditorUndoRedo(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()); + } +} diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/annotations/.ratings b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/annotations/.ratings new file mode 100644 index 0000000000000000000000000000000000000000..3c068c53b36a251eb4d369355471e4206b1b35e1 --- /dev/null +++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/annotations/.ratings @@ -0,0 +1,3 @@ +AnnotationHover.java 02988c3a040a394afb427af10c965d9f82b9a01d GREEN +AnnotationMarkerAccess.java 1977a4093f76d42638a560ed305f1d2cc3dedc4e GREEN +ErrorAnnotation.java 3e05aaeb93ca19bb99aed312427752c2c3f1959f GREEN diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/annotations/AnnotationHover.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/annotations/AnnotationHover.java new file mode 100644 index 0000000000000000000000000000000000000000..02988c3a040a394afb427af10c965d9f82b9a01d --- /dev/null +++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/annotations/AnnotationHover.java @@ -0,0 +1,78 @@ +/*-------------------------------------------------------------------------+ +| 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.annotations; + +import java.util.ArrayList; +import java.util.Iterator; + +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextHover; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.source.Annotation; +import org.eclipse.jface.text.source.AnnotationModel; +import org.eclipse.jface.text.source.IAnnotationHover; +import org.eclipse.jface.text.source.ISourceViewer; +import org.fortiss.tooling.base.ui.editor.annotations.ErrorAnnotation; + +/** + * Class acting as annotation hover manager. + * + * @author doebber + */ +public class AnnotationHover implements IAnnotationHover, ITextHover { + + /** Stores the {@link AnnotationModel}. */ + private final AnnotationModel model; + + /** Constructor. */ + public AnnotationHover(AnnotationModel model) { + this.model = model; + } + + /** {@inheritDoc} */ + @Override + public String getHoverInfo(ISourceViewer sourceViewer, int lineNumber) { + Iterator<?> ite = model.getAnnotationIterator(); + + ArrayList<String> all = new ArrayList<String>(); + + while(ite.hasNext()) { + Annotation a = (Annotation)ite.next(); + if(a instanceof ErrorAnnotation) { + all.add(((ErrorAnnotation)a).getText()); + } + } + + StringBuffer total = new StringBuffer(); + for(String str : all) { + total.append(" " + str);// + "\n"); + } + + return total.toString(); + } + + /** {@inheritDoc} */ + @Override + public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) { + return null; + } + + /** {@inheritDoc} */ + @Override + public IRegion getHoverRegion(ITextViewer textViewer, int offset) { + return null; + } +} diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/annotations/AnnotationMarkerAccess.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/annotations/AnnotationMarkerAccess.java new file mode 100644 index 0000000000000000000000000000000000000000..1977a4093f76d42638a560ed305f1d2cc3dedc4e --- /dev/null +++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/annotations/AnnotationMarkerAccess.java @@ -0,0 +1,104 @@ +/*-------------------------------------------------------------------------+ +| 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.annotations; + +import static org.eclipse.jface.text.source.ImageUtilities.drawImage; + +import org.eclipse.jface.text.source.Annotation; +import org.eclipse.jface.text.source.IAnnotationAccess; +import org.eclipse.jface.text.source.IAnnotationAccessExtension; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Canvas; + +/** + * Class providing annotation marker access. + * + * @author doebber + */ +public class AnnotationMarkerAccess implements IAnnotationAccess, IAnnotationAccessExtension { + /** {@inheritDoc} */ + @Override + public Object getType(Annotation annotation) { + return annotation.getType(); + } + + /** {@inheritDoc} */ + @Override + public boolean isMultiLine(Annotation annotation) { + return true; + } + + /** {@inheritDoc} */ + @Override + public boolean isTemporary(Annotation annotation) { + return !annotation.isPersistent(); + } + + /** {@inheritDoc} */ + @Override + public String getTypeLabel(Annotation annotation) { + if(annotation instanceof ErrorAnnotation) { + return "Errors"; + } + + return null; + } + + /** {@inheritDoc} */ + @Override + public int getLayer(Annotation annotation) { + if(annotation instanceof ErrorAnnotation) { + return ((ErrorAnnotation)annotation).getLayer(); + } + + return 0; + } + + /** {@inheritDoc} */ + @Override + public void paint(Annotation annotation, GC gc, Canvas canvas, Rectangle bounds) { + drawImage(((ErrorAnnotation)annotation).getImage(), gc, canvas, bounds, SWT.CENTER, + SWT.TOP); + } + + /** {@inheritDoc} */ + @Override + public boolean isPaintable(Annotation annotation) { + if(annotation instanceof ErrorAnnotation) { + return ((ErrorAnnotation)annotation).getImage() != null; + } + + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean isSubtype(Object annotationType, Object potentialSupertype) { + if(annotationType.equals(potentialSupertype)) { + return true; + } + + return false; + } + + /** {@inheritDoc} */ + @Override + public Object[] getSupertypes(Object annotationType) { + return new Object[0]; + } +} diff --git a/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/annotations/ErrorAnnotation.java b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/annotations/ErrorAnnotation.java new file mode 100644 index 0000000000000000000000000000000000000000..3e05aaeb93ca19bb99aed312427752c2c3f1959f --- /dev/null +++ b/org.fortiss.tooling.base.ui/src/org/fortiss/tooling/base/ui/editor/annotations/ErrorAnnotation.java @@ -0,0 +1,100 @@ +/*-------------------------------------------------------------------------+ +| 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.annotations; + +import static org.eclipse.wb.swt.ResourceManager.getPluginImage; +import static org.fortiss.tooling.base.ui.ToolingBaseUIActivator.PLUGIN_ID; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.source.Annotation; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.RGB; + +/** + * Objects of this class represent one error annotation. + * + * @author doebber + */ +public class ErrorAnnotation extends Annotation { + /** Stores the marker. */ + private IMarker marker; + /** Stores the error text. */ + private String text; + /** Stores the line number. */ + private int line; + /** Stores the error's text position. */ + private Position position; + /** Stores the error type. */ + public static String ERROR_TYPE = "error.type"; + /** Stores the error color. */ + public static final RGB ERROR_RGB = new RGB(255, 0, 0); + + /** Constructor. */ + public ErrorAnnotation(IMarker marker) { + this.marker = marker; + } + + /** Constructor. */ + public ErrorAnnotation(int line, String text) { + super(ERROR_TYPE, true, null); + this.marker = null; + this.line = line; + this.text = text; + } + + /** Returns the marker. */ + public IMarker getMarker() { + return marker; + } + + /** Returns the line. */ + public int getLine() { + return line; + } + + /** Returns the error text. */ + @Override + public String getText() { + return text; + } + + /** Returns the image. */ + public Image getImage() { + return getPluginImage(PLUGIN_ID, "/icons/error.gif"); + } + + /** Returns the layer. */ + public int getLayer() { + return 3; + } + + /** Returns the type. */ + @Override + public String getType() { + return ERROR_TYPE; + } + + /** Returns the position. */ + public Position getPosition() { + return position; + } + + /** Sets the position. */ + public void setPosition(Position position) { + this.position = position; + } +}