blob: 3d0fe817b9c50bd70fe108b23505d7848cfce331 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.e4.ui.workbench.addons.dndaddon;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.e4.ui.internal.workbench.swt.AbstractPartRenderer;
import org.eclipse.e4.ui.model.application.ui.MUIElement;
import org.eclipse.e4.ui.model.application.ui.basic.MPartStack;
import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
import org.eclipse.e4.ui.services.IStylingEngine;
import org.eclipse.e4.ui.widgets.ImageBasedFrame;
import org.eclipse.e4.ui.workbench.IPresentationEngine;
import org.eclipse.e4.ui.workbench.UIEvents;
import org.eclipse.e4.ui.workbench.modeling.EModelService;
import org.eclipse.jface.util.Geometry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.events.DragDetectListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Region;
import org.eclipse.swt.graphics.Resource;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tracker;
import org.osgi.service.event.EventHandler;
class DnDManager {
private static final Rectangle offScreenRect = new Rectangle(10000, -10000, 1, 1);
public static final int HOSTED = 1;
public static final int GHOSTED = 2;
public static final int SIMPLE = 3;
private int feedbackStyle = SIMPLE;
Collection<DragAgent> dragAgents = new ArrayList<>();
Collection<DropAgent> dropAgents = new ArrayList<>();
DnDInfo info;
DragAgent dragAgent;
private MWindow dragWindow;
private Shell dragHost;
private Control dragCtrl;
private Tracker tracker;
boolean dragging;
private Shell overlayFrame;
private List<Rectangle> frames = new ArrayList<>();
DragDetectListener dragDetector = e -> {
if (dragging || e.widget.isDisposed()) {
return;
}
info.update(e);
dragAgent = getDragAgent(info);
if (dragAgent != null) {
try {
dragging = true;
isModified = (e.stateMask & SWT.MOD1) != 0;
startDrag();
} finally {
dragging = false;
}
}
};
public void addFrame(Rectangle newRect) {
frames.add(newRect);
updateOverlay();
}
private List<Image> images = new ArrayList<>();
private List<Rectangle> imageRects = new ArrayList<>();
protected boolean isModified;
public void addImage(Rectangle imageRect, Image image) {
imageRects.add(imageRect);
images.add(image);
updateOverlay();
}
public DnDManager(MWindow topLevelWindow) {
dragWindow = topLevelWindow;
info = new DnDInfo(topLevelWindow);
// Dragging stacks and parts
dragAgents.add(new PartDragAgent(this));
dropAgents.add(new StackDropAgent(this));
dropAgents.add(new SplitDropAgent2(this));
dropAgents.add(new DetachedDropAgent(this));
// dragging trim
dragAgents.add(new IBFDragAgent(this));
dropAgents.add(new TrimDropAgent(this));
// Register a 'dragDetect' against any stacks that get created
hookWidgets();
getDragShell().addDisposeListener(e -> dispose());
}
/**
* Do any widget specific logic hookup. We should break the model generic logic away from the
* specific platform by moving this code into an SWT-specific subclass
*/
private void hookWidgets() {
EventHandler stackWidgetHandler = event -> {
MUIElement element = (MUIElement) event.getProperty(UIEvents.EventTags.ELEMENT);
// Only add listeners for stacks in *this* window
MWindow elementWin = getModelService().getTopLevelWindowFor(element);
if (elementWin != dragWindow) {
return;
}
// Listen for drags starting in CTabFolders
if (element.getWidget() instanceof CTabFolder || element.getWidget() instanceof ImageBasedFrame) {
Control ctrl = (Control) element.getWidget();
// Ensure there's only one drag detect listener per ctrl
ctrl.removeDragDetectListener(dragDetector);
ctrl.addDragDetectListener(dragDetector);
}
};
IEventBroker eventBroker = dragWindow.getContext().get(IEventBroker.class);
eventBroker.subscribe(UIEvents.UIElement.TOPIC_WIDGET, stackWidgetHandler);
}
public MWindow getDragWindow() {
return dragWindow;
}
public EModelService getModelService() {
return dragWindow.getContext().get(EModelService.class);
}
public Shell getDragShell() {
return (Shell) (dragWindow.getWidget() instanceof Shell ? dragWindow.getWidget() : null);
}
protected void dispose() {
clearOverlay();
if (overlayFrame != null && !overlayFrame.isDisposed()) {
overlayFrame.dispose();
}
overlayFrame = null;
for (DragAgent agent : dragAgents) {
agent.dispose();
}
for (DropAgent agent : dropAgents) {
agent.dispose();
}
info.curElement = null;
info = null;
}
private void track() {
Display.getCurrent().syncExec(() -> {
info.update();
dragAgent.track(info);
// Hack: Spin the event loop
update();
});
}
protected void startDrag() {
// Create a new tracker for this drag instance
Shell activeShell = Display.getCurrent().getActiveShell();
if (activeShell == null) {
return;
}
tracker = new Tracker(activeShell, SWT.NULL);
tracker.setStippled(true);
setRectangle(offScreenRect);
tracker.addKeyListener(new KeyListener() {
@Override
public void keyReleased(KeyEvent e) {
if (e.keyCode == SWT.MOD1) {
isModified = false;
track();
}
}
@Override
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.MOD1) {
isModified = true;
track();
}
}
});
tracker.addListener(SWT.Move, event -> track());
// Some control needs to capture the mouse during the drag or
// other controls will interfere with the cursor
getDragShell().setCapture(true);
try {
dragAgent.dragStart(info);
// Run tracker until mouse up occurs or escape key pressed.
boolean performDrop = tracker.open();
// clean up
finishDrag(performDrop);
} finally {
getDragShell().setCursor(null);
getDragShell().setCapture(false);
}
}
public void update() {
Display.getCurrent().update();
}
/**
* @param performDrop
*/
private void finishDrag(boolean performDrop) {
// Tear down any feedback
if (tracker != null && !tracker.isDisposed()) {
tracker.dispose();
tracker = null;
}
if (overlayFrame != null) {
overlayFrame.dispose();
overlayFrame = null;
}
if (dragHost != null) {
dragHost.dispose();
dragHost = null;
}
// Perform either drop or cancel
try {
dragAgent.dragFinished(performDrop, info);
} finally {
dragAgent = null;
}
}
public void setCursor(Cursor newCursor) {
getDragShell().setCursor(newCursor);
}
public void setRectangle(Rectangle newRect) {
if (tracker == null) {
return;
}
Rectangle[] rectArray = { Geometry.copy(newRect) };
tracker.setRectangles(rectArray);
}
public void hostElement(MUIElement element, int xOffset, int yOffset) {
if (element == null) {
if (dragHost != null && !dragHost.isDisposed()) {
dragHost.dispose();
}
dragHost = null;
return;
}
Shell parentShell = getDragShell();
dragHost = new Shell(parentShell, SWT.NO_TRIM);
dragHost.setBackground(parentShell.getDisplay().getSystemColor(SWT.COLOR_WHITE));
dragHost.setLayout(new FillLayout());
dragHost.setAlpha(120);
Region shellRgn = new Region(dragHost.getDisplay());
dragHost.setRegion(shellRgn);
dragCtrl = (Control) element.getWidget();
if (dragCtrl != null) {
dragHost.setSize(dragCtrl.getSize());
} else {
dragHost.setSize(400, 400);
}
if (feedbackStyle == HOSTED) {
// Special code to wrap the element in a CTF if it's coming from one
MUIElement elementParent = element.getParent();
if (elementParent instanceof MPartStack
&& elementParent.getWidget() instanceof CTabFolder) {
CTabFolder curCTF = (CTabFolder) elementParent.getWidget();
dragHost.setSize(curCTF.getSize());
CTabItem elementItem = getItemForElement(curCTF, element);
assert (elementItem != null);
IPresentationEngine renderingEngine = dragWindow.getContext().get(
IPresentationEngine.class);
CTabFolder ctf = new CTabFolder(dragHost, SWT.BORDER);
CTabItem newItem = new CTabItem(ctf, SWT.NONE);
newItem.setText(elementItem.getText());
newItem.setImage(elementItem.getImage());
element.getParent().getChildren().remove(element);
dragCtrl = (Control) renderingEngine.createGui(element, ctf, getModelService()
.getContainingContext(element));
newItem.setControl(dragCtrl);
}
} else if (feedbackStyle == GHOSTED) {
dragCtrl.setParent(dragHost);
dragCtrl.setLocation(0, 0);
dragHost.layout();
}
update();
// Pass the shell to the info element for tracking
dragHost.open();
info.setDragHost(dragHost, xOffset, yOffset);
}
public void setDragHostVisibility(boolean visible) {
if (dragHost == null || dragHost.isDisposed()) {
return;
}
if (visible) {
if (dragHost.getChildren().length > 0
&& dragHost.getChildren()[0] instanceof CTabFolder) {
CTabFolder ctf = (CTabFolder) dragHost.getChildren()[0];
dragCtrl.setParent(ctf);
dragHost.setVisible(true);
} else {
dragCtrl.setParent(dragHost);
}
} else {
dragHost.setVisible(false);
}
}
private CTabItem getItemForElement(CTabFolder elementCTF, MUIElement element) {
for (CTabItem item : elementCTF.getItems()) {
if (item.getData(AbstractPartRenderer.OWNING_ME) == element) {
return item;
}
}
return null;
}
public void clearOverlay() {
frames.clear();
images.clear();
imageRects.clear();
if (overlayFrame != null) {
overlayFrame.setVisible(false);
}
}
private void updateOverlay() {
Rectangle bounds = getOverlayBounds();
if (bounds == null) {
clearOverlay();
return;
}
if (overlayFrame == null) {
overlayFrame = new Shell(getDragShell(), SWT.NO_TRIM | SWT.ON_TOP);
overlayFrame.setData(DragAndDropUtil.IGNORE_AS_DROP_TARGET, Boolean.TRUE);
overlayFrame.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_DARK_GREEN));
overlayFrame.setAlpha(150);
IStylingEngine stylingEngine = dragWindow.getContext().get(IStylingEngine.class);
stylingEngine.setClassname(overlayFrame, "DragFeedback"); //$NON-NLS-1$
stylingEngine.style(overlayFrame);
overlayFrame.addPaintListener(e -> {
for (int i = 0; i < images.size(); i++) {
Image image = images.get(i);
Rectangle iRect = imageRects.get(i);
e.gc.drawImage(image, iRect.x, iRect.y);
}
});
}
// Reset for this set of overlays
overlayFrame.setBounds(bounds);
Region curRegion = overlayFrame.getRegion();
if (curRegion != null && !curRegion.isDisposed()) {
curRegion.dispose();
}
Region rgn = new Region();
// Add frames
for (Rectangle frameRect : frames) {
int x = frameRect.x - bounds.x;
int y = frameRect.y - bounds.y;
if (frameRect.width > 6) {
Rectangle outerBounds = new Rectangle(x - 3, y - 3, frameRect.width + 6,
frameRect.height + 6);
rgn.add(outerBounds);
Rectangle innerBounds = new Rectangle(x, y, frameRect.width, frameRect.height);
rgn.subtract(innerBounds);
} else {
Rectangle itemBounds = new Rectangle(x, y, frameRect.width, frameRect.height);
rgn.add(itemBounds);
}
}
// Add images
for (int i = 0; i < images.size(); i++) {
Rectangle ir = imageRects.get(i);
Image im = images.get(i);
int x = ir.x - bounds.x;
int y = ir.y - bounds.y;
rgn.add(x, y, im.getBounds().width, im.getBounds().height);
}
overlayFrame.setRegion(rgn);
// Region object needs to be disposed at the right point in time
addResourceDisposeListener(overlayFrame, rgn);
overlayFrame.setVisible(true);
}
private void addResourceDisposeListener(Control control, final Resource resource) {
control.addDisposeListener(e -> {
if (!resource.isDisposed()) {
resource.dispose();
}
});
}
private Rectangle getOverlayBounds() {
Rectangle bounds = null;
for (Rectangle fr : frames) {
if (fr.width > 6) {
Rectangle outerBounds = new Rectangle(fr.x - 3, fr.y - 3, fr.width + 6,
fr.height + 6);
if (bounds == null) {
bounds = outerBounds;
}
bounds.add(outerBounds);
} else {
if (bounds == null) {
bounds = fr;
}
bounds.add(fr);
}
}
for (Rectangle ir : imageRects) {
if (bounds == null) {
bounds = ir;
}
bounds.add(ir);
}
return bounds;
}
public void frameRect(Rectangle bounds) {
clearOverlay();
if (bounds != null) {
addFrame(bounds);
}
}
public void addDragAgent(DragAgent newAgent) {
if (!dragAgents.contains(newAgent)) {
dragAgents.add(newAgent);
}
}
public void removeDragAgent(DragAgent agentToRemove) {
dragAgents.remove(agentToRemove);
}
public void addDropAgent(DropAgent newAgent) {
if (!dropAgents.contains(newAgent)) {
dropAgents.add(newAgent);
}
}
public void removeDropAgent(DropAgent agentToRemove) {
dropAgents.remove(agentToRemove);
}
private DragAgent getDragAgent(DnDInfo info) {
for (DragAgent agent : dragAgents) {
if (agent.canDrag(info)) {
return agent;
}
}
return null;
}
public DropAgent getDropAgent(MUIElement dragElement, DnDInfo info) {
for (DropAgent agent : dropAgents) {
if (agent.canDrop(dragElement, info)) {
return agent;
}
}
return null;
}
/**
* @return Return the feedback style
*/
public int getFeedbackStyle() {
return feedbackStyle;
}
/**
* @param newBounds
*/
public void setHostBounds(Rectangle newBounds) {
if (dragHost == null || dragHost.isDisposed()) {
return;
}
info.setDragHostBounds(newBounds);
update();
}
}