Просмотр исходного кода

added JDT Style code completion hint decoration

Andreas Mülder 13 лет назад
Родитель
Сommit
615c80123c

+ 465 - 0
de.itemis.xtext.utils/plugins/de.itemis.xtext.utils.gmf/src/de/itemis/xtext/utils/gmf/directedit/DirectEditManagerEx.java

@@ -0,0 +1,465 @@
+package de.itemis.xtext.utils.gmf.directedit;
+
+import java.lang.reflect.Constructor;
+
+import org.eclipse.draw2d.AbstractBorder;
+import org.eclipse.draw2d.AncestorListener;
+import org.eclipse.draw2d.Border;
+import org.eclipse.draw2d.ColorConstants;
+import org.eclipse.draw2d.Figure;
+import org.eclipse.draw2d.Graphics;
+import org.eclipse.draw2d.IFigure;
+import org.eclipse.draw2d.geometry.Insets;
+import org.eclipse.draw2d.geometry.Rectangle;
+import org.eclipse.gef.EditPart;
+import org.eclipse.gef.EditPartListener;
+import org.eclipse.gef.GraphicalEditPart;
+import org.eclipse.gef.LayerConstants;
+import org.eclipse.gef.commands.Command;
+import org.eclipse.gef.commands.CommandStack;
+import org.eclipse.gef.editparts.LayerManager;
+import org.eclipse.gef.requests.DirectEditRequest;
+import org.eclipse.gef.tools.CellEditorLocator;
+import org.eclipse.gef.tools.DirectEditManager;
+import org.eclipse.jface.viewers.CellEditor;
+import org.eclipse.jface.viewers.ICellEditorListener;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ControlListener;
+import org.eclipse.swt.events.VerifyListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * Full copy of DirectEditManager, changed visibility of BORDER_FRAME and
+ * getCellEditorFrame to protected
+ * 
+ * TODO: Delete me when https://bugs.eclipse.org/bugs/show_bug.cgi?id=388697 is fixed
+ */
+@SuppressWarnings("all")
+public abstract class DirectEditManagerEx extends DirectEditManager {
+
+	private static final Color BLUE = ColorConstants.menuBackgroundSelected;
+	protected static final Border BORDER_FRAME = new DirectEditBorder();
+
+	private AncestorListener ancestorListener;
+	private EditPartListener editPartListener;
+	private ControlListener controlListener;
+	private IFigure cellEditorFrame;
+	private ICellEditorListener cellEditorListener;
+	private boolean showingFeedback;
+	private boolean dirty;
+	private DirectEditRequest request;
+	private CellEditorLocator locator;
+	private GraphicalEditPart source;
+	private CellEditor ce;
+	private Class editorType;
+	private boolean committing = false;
+	private Object feature;
+
+	/**
+	 * Constructs a new DirectEditManager for the given source edit part. The
+	 * cell editor will be created by instantiating the type <i>editorType</i>.
+	 * The cell editor will be placed using the given CellEditorLocator.
+	 * 
+	 * @param source
+	 *            the source edit part
+	 * @param editorType
+	 *            the cell editor type
+	 * @param locator
+	 *            the locator
+	 */
+	public DirectEditManagerEx(GraphicalEditPart source, Class editorType,
+			CellEditorLocator locator) {
+		super(source, editorType, locator);
+		this.source = source;
+		this.locator = locator;
+		this.editorType = editorType;
+	}
+
+	/**
+	 * Constructs a new DirectEditManager for the given source edit part. The
+	 * cell editor will be created by instantiating the type <i>editorType</i>.
+	 * The cell editor will be placed using the given CellEditorLocator.
+	 * 
+	 * @param source
+	 *            the source edit part
+	 * @param editorType
+	 *            the cell editor type
+	 * @param locator
+	 *            the locator
+	 * @param feature
+	 *            If the EditPart supports direct editing of multiple features,
+	 *            this parameter can be used to discriminate among them.
+	 * @since 3.2
+	 */
+	public DirectEditManagerEx(GraphicalEditPart source, Class editorType,
+			CellEditorLocator locator, Object feature) {
+		this(source, editorType, locator);
+		this.feature = feature;
+	}
+
+	/**
+	 * Cleanup is done here. Any feedback is erased and listeners unhooked. If
+	 * the cell editor is not <code>null</code>, it will be
+	 * {@link CellEditor#deactivate() deativated}, {@link CellEditor#dispose()
+	 * disposed}, and set to <code>null</code>.
+	 */
+	protected void bringDown() {
+		eraseFeedback();
+		unhookListeners();
+		if (getCellEditor() != null) {
+			getCellEditor().deactivate();
+			getCellEditor().dispose();
+			setCellEditor(null);
+		}
+		request = null;
+		dirty = false;
+	}
+
+	/**
+	 * Commits the current value of the cell editor by getting a {@link Command}
+	 * from the source edit part and executing it via the {@link CommandStack}.
+	 * Finally, {@link #bringDown()} is called to perform and necessary cleanup.
+	 */
+	protected void commit() {
+		if (committing)
+			return;
+		committing = true;
+		try {
+			eraseFeedback();
+			if (isDirty()) {
+				CommandStack stack = getEditPart().getViewer().getEditDomain()
+						.getCommandStack();
+				stack.execute(getEditPart().getCommand(getDirectEditRequest()));
+			}
+		} finally {
+			bringDown();
+			committing = false;
+		}
+	}
+
+	/**
+	 * Creates the cell editor on the given composite. The cell editor is
+	 * created by instantiating the cell editor type passed into this
+	 * DirectEditManager's constuctor.
+	 * 
+	 * @param composite
+	 *            the composite to create the cell editor on
+	 * @return the newly created cell editor
+	 */
+	protected CellEditor createCellEditorOn(Composite composite) {
+		try {
+			Constructor constructor = editorType
+					.getConstructor(new Class[] { Composite.class });
+			return (CellEditor) constructor
+					.newInstance(new Object[] { composite });
+		} catch (Exception e) {
+			return null;
+		}
+	}
+
+	/**
+	 * Creates and returns the DirectEditRequest.
+	 * 
+	 * @return the direct edit request
+	 */
+	protected DirectEditRequest createDirectEditRequest() {
+		DirectEditRequest req = new DirectEditRequest();
+		req.setCellEditor(getCellEditor());
+		req.setDirectEditFeature(getDirectEditFeature());
+		return req;
+	}
+
+	/**
+	 * Asks the source edit part to erase source feedback.
+	 */
+	protected void eraseFeedback() {
+		if (showingFeedback) {
+			LayerManager.Helper.find(getEditPart())
+					.getLayer(LayerConstants.FEEDBACK_LAYER)
+					.remove(getCellEditorFrame());
+			cellEditorFrame = null;
+			getEditPart().eraseSourceFeedback(getDirectEditRequest());
+			showingFeedback = false;
+		}
+	}
+
+	/**
+	 * Returns the cell editor.
+	 * 
+	 * @return the cell editor
+	 */
+	protected CellEditor getCellEditor() {
+		return ce;
+	}
+
+	protected IFigure getCellEditorFrame() {
+		if (cellEditorFrame != null)
+			return cellEditorFrame;
+		cellEditorFrame = new Figure();
+		cellEditorFrame.setBorder(BORDER_FRAME);
+		return cellEditorFrame;
+	}
+
+	private Control getControl() {
+		return ce.getControl();
+	}
+
+	/**
+	 * @return <code>Object</code> that can be used if the EditPart supports
+	 *         direct editing of multiple features, this parameter can be used
+	 *         to discriminate among them.
+	 * @since 3.2
+	 */
+	protected Object getDirectEditFeature() {
+		return feature;
+	}
+
+	/**
+	 * Returns the direct edit request, creating it if needed.
+	 * 
+	 * @return the direct edit request
+	 */
+	protected DirectEditRequest getDirectEditRequest() {
+		if (request == null)
+			request = createDirectEditRequest();
+		return request;
+	}
+
+	/**
+	 * Returns the source edit part.
+	 * 
+	 * @return the source edit part
+	 */
+	protected GraphicalEditPart getEditPart() {
+		return source;
+	}
+
+	protected CellEditorLocator getLocator() {
+		return locator;
+	}
+
+	protected void handleValueChanged() {
+		setDirty(true);
+		showFeedback();
+		placeCellEditor();
+	}
+
+	protected void hookListeners() {
+		ancestorListener = new AncestorListener.Stub() {
+			public void ancestorMoved(IFigure ancestor) {
+				placeCellEditor();
+			}
+		};
+		getEditPart().getFigure().addAncestorListener(ancestorListener);
+
+		Control control = getControl();
+
+		controlListener = new ControlAdapter() {
+			public void controlMoved(ControlEvent e) {
+				// This must be handled async because during scrolling, the
+				// CellEditor moves
+				// first, but then afterwards the viewport Scrolls, which would
+				// cause the
+				// shadow to move twice
+				Display.getCurrent().asyncExec(new Runnable() {
+					public void run() {
+						placeBorder();
+					}
+				});
+			}
+
+			public void controlResized(ControlEvent e) {
+				placeBorder();
+			}
+		};
+		control.addControlListener(controlListener);
+
+		cellEditorListener = new ICellEditorListener() {
+			public void applyEditorValue() {
+				commit();
+			}
+
+			public void cancelEditor() {
+				bringDown();
+			}
+
+			public void editorValueChanged(boolean old, boolean newState) {
+				handleValueChanged();
+			}
+		};
+		getCellEditor().addListener(cellEditorListener);
+
+		editPartListener = new EditPartListener.Stub() {
+			public void partDeactivated(EditPart editpart) {
+				bringDown();
+			}
+		};
+		getEditPart().addEditPartListener(editPartListener);
+	}
+
+	/**
+	 * Initializes the cell editor. Subclasses should implement this to set the
+	 * initial text and add things such as {@link VerifyListener
+	 * VerifyListeners}, if needed.
+	 */
+	protected abstract void initCellEditor();
+
+	/**
+	 * Returns <code>true</code> if the cell editor's value has been changed.
+	 * 
+	 * @return <code>true</code> if the cell editor is dirty
+	 */
+	protected boolean isDirty() {
+		return dirty;
+	}
+
+	private void placeBorder() {
+		if (showingFeedback) {
+			IFigure shadow = getCellEditorFrame();
+			Rectangle rect = new Rectangle(getCellEditor().getControl()
+					.getBounds());
+			rect.expand(shadow.getInsets());
+			shadow.translateToRelative(rect);
+			shadow.setBounds(rect);
+		}
+	}
+
+	private void placeCellEditor() {
+		getLocator().relocate(getCellEditor());
+	}
+
+	/**
+	 * Sets the cell editor to the given editor.
+	 * 
+	 * @param editor
+	 *            the cell editor
+	 */
+	protected void setCellEditor(CellEditor editor) {
+		ce = editor;
+		if (ce == null)
+			return;
+		hookListeners();
+	}
+
+	/**
+	 * Sets the dirty property.
+	 * 
+	 * @param value
+	 *            the dirty property
+	 */
+	protected void setDirty(boolean value) {
+		dirty = value;
+	}
+
+	/**
+	 * Sets the source edit part.
+	 * 
+	 * @param source
+	 *            the source edit part
+	 */
+	protected void setEditPart(GraphicalEditPart source) {
+		this.source = source;
+		// source.addEditPartListener();
+	}
+
+	/**
+	 * Sets the CellEditorLocator used to place the cell editor in the correct
+	 * location.
+	 * 
+	 * @param locator
+	 *            the locator
+	 */
+	public void setLocator(CellEditorLocator locator) {
+		this.locator = locator;
+	}
+
+	/**
+	 * Shows the cell editor when direct edit is started. Calls
+	 * {@link #initCellEditor()}, {@link CellEditor#activate()}, and
+	 * {@link #showFeedback()}.
+	 */
+	public void show() {
+		if (getCellEditor() != null)
+			return;
+		Composite composite = (Composite) source.getViewer().getControl();
+		setCellEditor(createCellEditorOn(composite));
+		if (getCellEditor() == null)
+			return;
+		initCellEditor();
+		getCellEditor().activate();
+		placeCellEditor();
+		getControl().setVisible(true);
+		getCellEditor().setFocus();
+		showFeedback();
+	}
+
+	private void showCellEditorFrame() {
+		LayerManager.Helper.find(getEditPart())
+				.getLayer(LayerConstants.FEEDBACK_LAYER)
+				.add(getCellEditorFrame());
+		placeBorder();
+	}
+
+	/**
+	 * Asks the source edit part to show source feedback.
+	 */
+	public void showFeedback() {
+		if (!showingFeedback)
+			showCellEditorFrame();
+		showingFeedback = true;
+		showCellEditorFrame();
+		getEditPart().showSourceFeedback(getDirectEditRequest());
+	}
+
+	/**
+	 * Unhooks listeners. Called from {@link #bringDown()}.
+	 */
+	protected void unhookListeners() {
+		getEditPart().getFigure().removeAncestorListener(ancestorListener);
+		getEditPart().removeEditPartListener(editPartListener);
+		ancestorListener = null;
+		editPartListener = null;
+
+		if (getCellEditor() == null)
+			return;
+		getCellEditor().removeListener(cellEditorListener);
+		cellEditorListener = null;
+
+		Control control = getCellEditor().getControl();
+		if (control == null || control.isDisposed())
+			return;
+		control.removeControlListener(controlListener);
+		controlListener = null;
+	}
+
+	private static class DirectEditBorder extends AbstractBorder {
+		private static final Insets insets = new Insets(1, 2, 2, 2);
+
+		public Insets getInsets(IFigure figure) {
+			return insets;
+		}
+
+		public void paint(IFigure figure, Graphics graphics, Insets insets) {
+			Rectangle rect = getPaintRectangle(figure, insets);
+			graphics.setForegroundColor(ColorConstants.white);
+			graphics.drawLine(rect.x, rect.y, rect.x, rect.bottom());
+			rect.x++;
+			rect.width--;
+			rect.resize(-1, -1);
+			graphics.setForegroundColor(ColorConstants.black);
+			graphics.drawLine(rect.x + 2, rect.bottom(), rect.right(),
+					rect.bottom());
+			graphics.drawLine(rect.right(), rect.bottom(), rect.right(),
+					rect.y + 2);
+
+			rect.resize(-1, -1);
+			graphics.setForegroundColor(BLUE);
+			graphics.drawRectangle(rect);
+		}
+	}
+
+}

+ 25 - 3
de.itemis.xtext.utils/plugins/de.itemis.xtext.utils.gmf/src/de/itemis/xtext/utils/gmf/directedit/XtextDirectEditManager.java

@@ -18,15 +18,17 @@ import java.util.List;
 
 import org.eclipse.core.runtime.Assert;
 import org.eclipse.draw2d.AncestorListener;
+import org.eclipse.draw2d.CompoundBorder;
 import org.eclipse.draw2d.IFigure;
+import org.eclipse.draw2d.MarginBorder;
 import org.eclipse.draw2d.PositionConstants;
 import org.eclipse.draw2d.TextUtilities;
 import org.eclipse.draw2d.geometry.Dimension;
+import org.eclipse.draw2d.geometry.Insets;
 import org.eclipse.draw2d.geometry.Rectangle;
 import org.eclipse.gef.GraphicalEditPart;
 import org.eclipse.gef.editparts.ZoomManager;
 import org.eclipse.gef.tools.CellEditorLocator;
-import org.eclipse.gef.tools.DirectEditManager;
 import org.eclipse.gmf.runtime.common.core.util.Log;
 import org.eclipse.gmf.runtime.common.core.util.Trace;
 import org.eclipse.gmf.runtime.diagram.ui.internal.DiagramUIDebugOptions;
@@ -50,6 +52,7 @@ import org.eclipse.swt.graphics.Color;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.FontData;
 import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Display;
@@ -72,7 +75,7 @@ import de.itemis.xtext.utils.jface.viewers.context.IXtextFakeContextResourcesPro
  * 
  */
 @SuppressWarnings("restriction")
-public class XtextDirectEditManager extends DirectEditManager {
+public class XtextDirectEditManager extends DirectEditManagerEx {
 
 	/**
 	 * content assist background color
@@ -283,10 +286,17 @@ public class XtextDirectEditManager extends DirectEditManager {
 	 */
 	protected CellEditor createCellEditorOn(Composite composite) {
 
+		
+		Composite parent = new Composite(composite, SWT.None);
+		FillLayout fillLayout = new FillLayout();
+		fillLayout.marginWidth = 10;
+		parent.setLayout(fillLayout);
+		
+		
 		// if the client has overridden this class and provided their own editor
 		// type, then we should use that
 		if (editorType != null) {
-			return super.createCellEditorOn(composite);
+			return super.createCellEditorOn(parent);
 		}
 		XtextStyledTextCellEditorEx editor;
 		if (fakeProvider != null) {
@@ -664,6 +674,8 @@ public class XtextDirectEditManager extends DirectEditManager {
 			listenersAttached = true;
 		}
 	}
+	
+	
 
 	public void showFeedback() {
 		try {
@@ -674,6 +686,16 @@ public class XtextDirectEditManager extends DirectEditManager {
 		}
 	}
 
+	/**
+	 * Overridden to enlarge the cell editor frame for a control decorator
+	 */
+	protected IFigure getCellEditorFrame() {
+		IFigure cellEditorFrame = super.getCellEditorFrame();
+		cellEditorFrame.setBorder(new CompoundBorder(new MarginBorder(
+				new Insets(0, 10, 0, 0)), BORDER_FRAME));
+		return cellEditorFrame;
+	}
+
 	@Override
 	protected IXtextAwareEditPart getEditPart() {
 		return (IXtextAwareEditPart) super.getEditPart();

+ 0 - 1
de.itemis.xtext.utils/plugins/de.itemis.xtext.utils.jface/build.properties

@@ -3,5 +3,4 @@ output.. = bin/
 bin.includes = META-INF/,\
                .,\
                plugin.xml
-src.includes = .project
  

+ 33 - 0
de.itemis.xtext.utils/plugins/de.itemis.xtext.utils.jface/src/de/itemis/xtext/utils/jface/viewers/StyledTextXtextAdapter.java

@@ -4,6 +4,8 @@ import java.util.List;
 
 import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.jface.bindings.keys.KeyStroke;
+import org.eclipse.jface.fieldassist.ControlDecoration;
+import org.eclipse.jface.resource.ImageDescriptor;
 import org.eclipse.jface.text.IDocumentPartitioner;
 import org.eclipse.jface.text.contentassist.IContentAssistant;
 import org.eclipse.jface.text.source.AnnotationModel;
@@ -11,6 +13,9 @@ import org.eclipse.jface.text.source.ICharacterPairMatcher;
 import org.eclipse.jface.text.source.ISharedTextColors;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.widgets.Event;
 import org.eclipse.swt.widgets.Listener;
 import org.eclipse.ui.PlatformUI;
@@ -83,6 +88,8 @@ public class StyledTextXtextAdapter {
 
 	private StyledText styledText;
 
+	private ControlDecoration decoration;
+
 	public StyledTextXtextAdapter(Injector injector,
 			IXtextFakeContextResourcesProvider contextFakeResourceProvider) {
 		this.contextFakeResourceProvider = contextFakeResourceProvider;
@@ -149,6 +156,32 @@ public class StyledTextXtextAdapter {
 				.getService(IFocusService.class);
 		service.addFocusTracker(styledText, StyledText.class.getCanonicalName());
 
+		// add JDT Style code completion hint decoration
+		createContentAssistDecoration(styledText);
+	}
+
+	private void createContentAssistDecoration(StyledText styledText) {
+		decoration = new ControlDecoration(styledText, SWT.TOP | SWT.LEFT);
+		decoration.setShowHover(true);
+		decoration.setShowOnlyOnFocus(true);
+
+		final Image image = ImageDescriptor.createFromFile(
+				XtextStyledTextCellEditor.class,
+				"images/content_assist_cue.gif").createImage();
+		decoration.setImage(image);
+		decoration
+				.setDescriptionText("Content Assist Available (CTRL + Space)");
+		decoration.setMarginWidth(2);
+		styledText.addDisposeListener(new DisposeListener() {
+			public void widgetDisposed(DisposeEvent e) {
+				if (decoration != null) {
+					decoration.dispose();
+				}
+				if (image != null) {
+					image.dispose();
+				}
+			}
+		});
 	}
 
 	protected ValidationJob createValidationJob() {

BIN
de.itemis.xtext.utils/plugins/de.itemis.xtext.utils.jface/src/de/itemis/xtext/utils/jface/viewers/images/content_assist_cue.gif