/**
* Copyright (c) 2011 itemis AG and others.
* 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:
* itemis AG - initial API and implementation
*
*/
package org.yakindu.base.xtext.utils.jface.viewers;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.text.ITextListener;
import org.eclipse.jface.text.TextEvent;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.xtext.ui.editor.XtextEditor;
import org.yakindu.base.utils.jface.viewers.StyledTextCellEditor;
import org.yakindu.base.xtext.utils.jface.fieldassist.CompletionProposalAdapter;
import org.yakindu.base.xtext.utils.jface.viewers.context.IXtextFakeContextResourcesProvider;
import com.google.inject.Injector;
/**
* This class integrates Xtext features into a {@link CellEditor} and can be
* used e.g. in jFace {@link StructuredViewer}s or in GMF EditParts via
* DirectEditManager.
*
* The current implementation supports, code completion, syntax highlighting and
* validation
*
* @see XtextStyledTextProvider
*
* @author andreas.muelder@itemis.de
* @author alexander.nyssen@itemis.de
* @author patrick.koenemann@itemis.de
*/
public class XtextStyledTextCellEditor extends StyledTextCellEditor {
private Injector injector;
private StyledTextXtextAdapter xtextAdapter;
private IXtextFakeContextResourcesProvider contextFakeResourceProvider;
private final static String CONTEXTMENUID = "org.yakindu.base.xtext.utils.jface.viewers.StyledTextXtextAdapterContextMenu";
public XtextStyledTextCellEditor(int style, Injector injector,
IXtextFakeContextResourcesProvider contextFakeResourceProvider) {
this(style, injector);
this.contextFakeResourceProvider = contextFakeResourceProvider;
}
public XtextStyledTextCellEditor(int style, Injector injector) {
setStyle(style);
this.injector = injector;
}
/**
* Creates an {@link SourceViewer} and returns the {@link StyledText} widget
* of the viewer as the cell editors control. Some code is copied from
* {@link XtextEditor}.
*/
@Override
protected Control createControl(Composite parent) {
StyledText styledText = (StyledText) super.createControl(parent);
styledText.addFocusListener(new FocusAdapter() {
public void focusLost(FocusEvent e) {
XtextStyledTextCellEditor.this.focusLost();
}
});
// adapt to xtext
xtextAdapter = new StyledTextXtextAdapter(injector,
contextFakeResourceProvider == null ? IXtextFakeContextResourcesProvider.NULL_CONTEXT_PROVIDER
: contextFakeResourceProvider);
xtextAdapter.adapt(styledText);
// configure content assist
final IContentAssistant contentAssistant = xtextAdapter.getContentAssistant();
completionProposalAdapter = new CompletionProposalAdapter(styledText, contentAssistant, KeyStroke.getInstance(
SWT.CTRL, SWT.SPACE), null);
// This listener notifies the modification, when text is selected via
// proposal. A ModifyEvent is not thrown by the StyledText in this case.
xtextAdapter.getXtextSourceviewer().addTextListener(new ITextListener() {
public void textChanged(TextEvent event) {
editOccured(null);
}
});
if ((styledText.getStyle() & SWT.SINGLE) != 0) {
// The regular key down event is too late (after popup is closed
// again).
// when using the StyledText.VerifyKey event (3005), we get the
// event early enough!
styledText.addListener(3005, new Listener() {
public void handleEvent(Event event) {
if (event.character == SWT.CR && !completionProposalAdapter.isProposalPopupOpen()) {
focusLost();
}
}
});
}
styledText.addListener(3005, new Listener() {
public void handleEvent(Event event) {
if (event.character == '\u001b' // ESC
&& !completionProposalAdapter.isProposalPopupOpen()) {
XtextStyledTextCellEditor.this.fireCancelEditor();
}
}
});
initContextMenu(styledText);
return styledText;
}
protected void initContextMenu(Control control) {
MenuManager menuManager = createMenuManager();
Menu contextMenu = menuManager.createContextMenu(control);
control.setMenu(contextMenu);
IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
IWorkbenchPartSite site = window.getActivePage().getActiveEditor().getSite();
site.registerContextMenu(CONTEXTMENUID, menuManager, site.getSelectionProvider());
}
protected MenuManager createMenuManager() {
return new FilteringMenuManager();
}
protected void keyReleaseOccured(KeyEvent keyEvent) {
if (keyEvent.character == '\u001b') { // ESC
return;
}
super.keyReleaseOccured(keyEvent);
}
@Override
protected void doSetValue(Object value) {
super.doSetValue(value);
// Reset the undo manager to prevend deletion of complete text if the
// user hits ctrl+z after cell editor opens
xtextAdapter.sourceviewer.getUndoManager().reset();
}
@Override
public boolean isUndoEnabled() {
return true;
}
@Override
public void performUndo() {
xtextAdapter.sourceviewer.getUndoManager().undo();
}
@Override
public boolean isRedoEnabled() {
return true;
}
@Override
public void performRedo() {
xtextAdapter.sourceviewer.getUndoManager().redo();
}
@Override
public boolean isFindEnabled() {
return true;
}
// in gtk, we need this flag to let one focus lost event pass. See
// focusLost() for details.
private boolean ignoreNextFocusLost = false;
private CompletionProposalAdapter completionProposalAdapter;
/*
* In gtk, the focus lost event is fired _after_ the CR event, so we need to
* remember the state when the proposal popup window is open.
*/
@Override
protected void focusLost() {
if (SWT.getPlatform().equals("gtk")) {
if (ignoreNextFocusLost) {
ignoreNextFocusLost = false;
return;
}
if (completionProposalAdapter.isProposalPopupOpen()) {
ignoreNextFocusLost = true;
return;
}
}
if (!completionProposalAdapter.isProposalPopupOpen())
super.focusLost();
}
@Override
public void dispose() {
xtextAdapter.dispose();
super.dispose();
}
/*
* This is damn important! If we don't return false here, the
* ColumnEditorViewer calls applyEditorValue on FocusLostEvents!
*/
@Override
protected boolean dependsOnExternalFocusListener() {
return false;
}
public void setVisibleRegion(int start, int length) {
xtextAdapter.setVisibleRegion(start, length);
}
public StyledTextXtextAdapter getXtextAdapter() {
return xtextAdapter;
}
}