/******************************************************************************* * Copyright (c) 2010, 2015 IBM Corporation 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: * 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.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.DragDetectEvent; import org.eclipse.swt.events.DragDetectListener; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; 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.Event; import org.eclipse.swt.widgets.Listener; 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<DragAgent>(); Collection<DropAgent> dropAgents = new ArrayList<DropAgent>(); DnDInfo info; DragAgent dragAgent; private MWindow dragWindow; private Shell dragHost; private Control dragCtrl; private Tracker tracker; boolean dragging = false; private Shell overlayFrame; private List<Rectangle> frames = new ArrayList<Rectangle>(); DragDetectListener dragDetector = new DragDetectListener() { @Override public void dragDetected(DragDetectEvent 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<Image>(); private List<Rectangle> imageRects = new ArrayList<Rectangle>(); 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(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent 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 = new EventHandler() { @Override public void handleEvent(org.osgi.service.event.Event 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(); } } private void track() { Display.getCurrent().syncExec(new Runnable() { @Override public void run() { info.update(); dragAgent.track(info); // Hack: Spin the event loop update(); } }); } protected void startDrag() { // Create a new tracker for this drag instance tracker = new Tracker(Display.getCurrent().getActiveShell(), 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, new Listener() { @Override public void handleEvent(final Event 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(new PaintListener() { @Override public void paintControl(PaintEvent 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(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent 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(); } }