blob: 9892b7b63452a3c70d29a7e46c624917dfbd5a36 [file] [log] [blame]
/*********************************************************************
* Copyright (c) 2005, 2019 SAP SE
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* SAP SE - initial API, implementation and documentation
* Felix Velasco (mwenz) - Bug 323351 - Enable to suppress/reactivate the speed buttons
* Bug 336488 - DiagramEditor API
* mgorning - Bug 369370 - visibility of context button pad for graphical entities
* mwenz - Bug 392309 - Scrollbars appear when a tooltip is being displayed on a decorator
* pjpaulin - Bug 352120 - Now uses IDiagramContainerUI interface
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipse.graphiti.ui.internal.contextbuttons;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.Tool;
import org.eclipse.gef.editparts.ScalableFreeformRootEditPart;
import org.eclipse.gef.editparts.ZoomListener;
import org.eclipse.gef.editparts.ZoomManager;
import org.eclipse.gef.tools.AbstractConnectionCreationTool;
import org.eclipse.gef.tools.CreationTool;
import org.eclipse.graphiti.internal.contextbuttons.IContextButtonPadDeclaration;
import org.eclipse.graphiti.internal.contextbuttons.SpecialContextButtonPadDeclaration;
import org.eclipse.graphiti.internal.contextbuttons.StandardContextButtonPadDeclaration;
import org.eclipse.graphiti.internal.features.context.impl.base.PictogramElementContext;
import org.eclipse.graphiti.internal.pref.GFPreferences;
import org.eclipse.graphiti.internal.services.GraphitiInternal;
import org.eclipse.graphiti.mm.pictograms.Diagram;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.tb.IContextButtonPadData;
import org.eclipse.graphiti.tb.IToolBehaviorProvider;
import org.eclipse.graphiti.ui.editor.DiagramBehavior;
import org.eclipse.graphiti.ui.internal.IResourceRegistry;
import org.eclipse.graphiti.ui.internal.parts.IPictogramElementEditPart;
import org.eclipse.swt.SWT;
/**
* The context button manager shows and hides the context button pad. Mostly
* showing/hiding the context button pad is triggered by mouse events.
*
* @noinstantiate This class is not intended to be instantiated by clients.
* @noextend This class is not intended to be subclassed by clients.
*/
public class ContextButtonManagerForPad implements IContextButtonManager {
/**
* The context button pad is not shown, when the zoom level is below this
* minimum value.
*/
protected static final double MINIMUM_ZOOM_LEVEL = 0.75d;
/**
* The container on which this context button manager works, see
* {@link #getContainer()}. It is set in the constructor.
*/
private DiagramBehavior diagramBehavior;
/**
* A backward-map from the edit-part figures to their edit-parts as
* described in {@link #getFigure2EditPart()}.
*/
private Map<IFigure, EditPart> figure2EditPart = new HashMap<IFigure, EditPart>();
/**
* The currently active figure as described in {@link #getActiveFigure()}.
*/
private IFigure activeFigure;
/**
* The currently active figure as described in
* {@link #getActiveContextButtonPad()}.
*/
private ContextButtonPad activeContextButtonPad;
/**
* The current state of context button pad enablement
*/
private boolean contextButtonShowing;
// ============================= listener =================================
/**
* The zoom-listener is registered on the container and calls
* {@link #handleZoomChanged()} on zoom level changes.
*/
private ZoomListener zoomListener = new ZoomListener() {
public void zoomChanged(double newZoom) {
handleZoomChanged();
}
};
/**
* The mouse motion listener is registered on the relevant figures. It calls
* {@link #showContextButtonsInstantly()} when the mouse enters the figure.
*/
private MouseMotionListener mouseMotionListener = new MouseMotionListener.Stub() {
@Override
public void mouseEntered(MouseEvent me) {
reactOnMouse(me);
}
@Override
public void mouseMoved(MouseEvent me) {
reactOnMouse(me);
}
private void reactOnMouse(MouseEvent me) {
DiagramBehavior diagramBehavior = getDiagramBehavior();
if (diagramBehavior.isDirectEditingActive()) {
return;
}
Tool activeTool = diagramBehavior.getEditDomain().getActiveTool();
if (activeTool instanceof CreationTool || activeTool instanceof AbstractConnectionCreationTool) {
return;
}
if ((me.getState() & SWT.MOD1) != 0) {
// If CTRL is pressed while buttons are shown hide them
hideContextButtonsInstantly();
return;
}
if (!isContextButtonShowing()) {
return;
}
Object source = me.getSource();
showContextButtonsInstantly((IFigure) source, me.getLocation());
}
};
private IResourceRegistry resourceRegistry;
// ============================ constructor ===============================
/**
* Creates a new ContextButtonManagerForPad.
*
* @param container
* The container on which this context button manager works, see
* {@link #getContainer()}.
* @param iResourceRegistry
*/
public ContextButtonManagerForPad(DiagramBehavior diagramBehavior, IResourceRegistry resourceRegistry) {
this.diagramBehavior = diagramBehavior;
this.resourceRegistry = resourceRegistry;
ZoomManager zoomMgr = (ZoomManager) getDiagramBehavior().getDiagramContainer().getGraphicalViewer()
.getProperty(ZoomManager.class.toString());
if (zoomMgr != null) {
zoomMgr.addZoomListener(zoomListener);
}
contextButtonShowing = true;
}
// ====================== getter/setter for fields ========================
/**
* Returns the container this context button manager works on. It is set in the
* constructor and can not be changed.
*
* @return The container this context button manager works on.
*/
public DiagramBehavior getDiagramBehavior() {
return diagramBehavior;
}
/**
* Returns a backward-map from the edit-part figures to their edit-parts. So
* it delivers the opposite of GraphicalEditPart.getFigure(). This map is
* maintained in {@link #register(GraphicalEditPart)} and
* {@link #deRegister(GraphicalEditPart)}.
*
* @return A backward-map from the edit-part figures to their edit-parts.
*/
private Map<IFigure, EditPart> getFigure2EditPart() {
return figure2EditPart;
}
/**
* Sets the active figure and context button pad. A figure is called active,
* when a context button pad is currently active (shown) for this figure.
* There can only be one active figure and context button pad at a time.
* Figure and context button pad are either both null or both not null.
*
* @param activeFigure
* The figure to set active.
* @param activeContextButtonPad
* The context button pad to set active.
*/
private void setActive(IFigure activeFigure, ContextButtonPad activeContextButtonPad) {
this.activeFigure = activeFigure;
this.activeContextButtonPad = activeContextButtonPad;
}
/**
* Returns the active figure as described in
* {@link #setActive(IFigure, ContextButtonPad)}.
*
* @return The active figure as described in
* {@link #setActive(IFigure, ContextButtonPad)}.
*/
private IFigure getActiveFigure() {
return activeFigure;
}
/**
* Returns the active context button pad as described in
* {@link #setActive(IFigure, ContextButtonPad)}.
*
* @return The active context button pad as described in
* {@link #setActive(IFigure, ContextButtonPad)}.
*/
private ContextButtonPad getActiveContextButtonPad() {
return activeContextButtonPad;
}
// =================== interface IContextButtonManager ====================
/**
* Registers a given edit-part. This means, that a context button pad will
* be shown for this edit-part when the mouse enters its figure. Typically
* this method is called, when an edit-part is activated.
*/
public void register(GraphicalEditPart graphicalEditPart) {
getFigure2EditPart().put(graphicalEditPart.getFigure(), graphicalEditPart);
graphicalEditPart.getFigure().addMouseMotionListener(mouseMotionListener);
}
/**
* Deregisters a given edit-part, which is opposite to
* {@link #register(GraphicalEditPart)}. If a context-button pad is
* currently shown for this edit-part / figure, it is hidden first.
* Typically this method is called, when an edit-part is deactivated.
*/
public void deRegister(GraphicalEditPart graphicalEditPart) {
if (graphicalEditPart.getFigure().equals(getActiveFigure())) {
hideContextButtonsInstantly();
}
getFigure2EditPart().remove(graphicalEditPart.getFigure());
graphicalEditPart.getFigure().removeMouseMotionListener(mouseMotionListener);
}
/**
* Hides the context button pad (if there is currently a context button pad
* active).
*/
public void hideContextButtonsInstantly() {
if (getActiveContextButtonPad() != null) {
synchronized (this) {
ScalableFreeformRootEditPart rootEditPart = (ScalableFreeformRootEditPart) getDiagramBehavior()
.getDiagramContainer().getGraphicalViewer().getRootEditPart();
IFigure feedbackLayer = rootEditPart.getLayer(LayerConstants.HANDLE_LAYER);
feedbackLayer.remove(getActiveContextButtonPad());
setActive(null, null);
}
}
}
/**
* Returns true, if for the given figure a replacement of the context button
* pad is required. For example it returns false, if there is already a
* context button pad shown for this figure or if the mouse is still on the
* context button pad, and it returns true, if there is currently no context
* button pad.
*
* @param figure
* The figure which to check.
* @param mouseLocation
* current mouse location in the diagram
* @return true, if for the given figure a replacement of the context button
* pad is required.
*/
private boolean replaceContextButtonPad(IFigure figure, Point mouseLocation) {
// requires new context buttons, if there is no active figure
if (getActiveFigure() == null) {
return true;
}
// requires no changed context buttons, if the given figure equals
// the active figure
if (figure.equals(getActiveFigure()))
return false;
// requires changed context buttons, if the given figure is a child of
// the active figure (otherwise children would not have context buttons
// when the mouse moves from parent to child -- see next check)
IFigure parent = figure.getParent();
while (parent != null) {
if (parent.equals(getActiveFigure()))
return true;
parent = parent.getParent();
}
// requires changed context buttons, if the mouse location is inside the
// active figure (that means the
// figure overlaps the active figure)
if (getActiveFigure().containsPoint(mouseLocation)) {
return true;
}
// requires no (new) context buttons, if the the mouse is still in the
// sensitive area of the active context button pad
if (getActiveContextButtonPad() != null) {
if (getActiveContextButtonPad().isMouseInOverlappingArea()) {
return false;
}
}
return true;
}
/**
* Shows the context button pad for the given figure, but only if
* {@link #replaceContextButtonPad(IFigure)} return true and the zoom-level
* is at least {@link #MINIMUM_ZOOM_LEVEL}.
*
* @param figure
* The figure for which to show the context button pad.
* @param mouse
* The current location of the mouse.
*/
private void showContextButtonsInstantly(IFigure figure, Point mouse) {
if (!replaceContextButtonPad(figure, mouse))
return;
synchronized (this) {
hideContextButtonsInstantly();
// determine zoom level
ScalableFreeformRootEditPart rootEditPart = (ScalableFreeformRootEditPart) getDiagramBehavior()
.getDiagramContainer().getGraphicalViewer().getRootEditPart();
double zoom = rootEditPart.getZoomManager().getZoom();
if (zoom < MINIMUM_ZOOM_LEVEL) {
return;
}
// determine pictogram element
IPictogramElementEditPart editPart = (IPictogramElementEditPart) getFigure2EditPart().get(figure);
PictogramElement pe = editPart.getPictogramElement();
if (pe instanceof Diagram) {
// No context buttons of diagram itself, should also exit here
// to avoid showing scrollbars when displaying tooltips on root
// decorators (see Bugzilla 392309)
return;
}
if (!GraphitiInternal.getEmfService().isObjectAlive(pe)) {
return;
}
PictogramElementContext context = new PictogramElementContext(pe);
// retrieve context button pad data
IToolBehaviorProvider toolBehaviorProvider = getDiagramBehavior().getDiagramTypeProvider()
.getCurrentToolBehaviorProvider();
IContextButtonPadData contextButtonPadData = toolBehaviorProvider.getContextButtonPad(context);
if (contextButtonPadData == null) {
return; // no context buttons to show
}
if (contextButtonPadData.getDomainSpecificContextButtons().size() == 0
&& contextButtonPadData.getGenericContextButtons().size() == 0
&& contextButtonPadData.getCollapseContextButton() == null) {
return; // no context buttons to show
}
if (!contextButtonPadData.getPadLocation().contains(mouse.x, mouse.y)) {
return; // mouse outside area of context button pad
}
// determine context button pad declaration
int declarationType = GFPreferences.getInstance().getContextButtonPadDeclaration();
IContextButtonPadDeclaration declaration;
if (declarationType == 1) {
declaration = new SpecialContextButtonPadDeclaration(contextButtonPadData);
} else {
declaration = new StandardContextButtonPadDeclaration(contextButtonPadData);
}
// create context button pad and add to handle layer
EditPart activeEditPart = getFigure2EditPart().get(figure);
ContextButtonPad contextButtonPad = new ContextButtonPad(this, declaration, zoom, getDiagramBehavior(),
activeEditPart, resourceRegistry);
setActive(figure, contextButtonPad);
IFigure feedbackLayer = rootEditPart.getLayer(LayerConstants.HANDLE_LAYER);
feedbackLayer.add(contextButtonPad);
}
}
/**
* Sets the general availability of the context button pad.
*
*/
public void setContextButtonShowing(boolean enable) {
contextButtonShowing = enable;
}
/**
* Checks general availability of the context button pad.
*
* @return whether the context button pad is enabled
*/
private boolean isContextButtonShowing() {
return contextButtonShowing;
}
/**
* Is called when the zoom-level changes and hides the context buttons.
*/
private void handleZoomChanged() {
hideContextButtonsInstantly();
// It would be possible to show a new context button pad, depending
// on the new mouse location. But to avoid problems we skip this.
// The scenario, that the zoom changes when context buttons are
// visible is not so typical anyway.
}
}