/******************************************************************************* * 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.List; import org.eclipse.e4.ui.model.application.MApplicationElement; import org.eclipse.e4.ui.model.application.ui.MUIElement; import org.eclipse.e4.ui.model.application.ui.advanced.MArea; import org.eclipse.e4.ui.model.application.ui.advanced.MPerspective; import org.eclipse.e4.ui.model.application.ui.basic.MPartSashContainerElement; import org.eclipse.e4.ui.model.application.ui.basic.MPartStack; import org.eclipse.e4.ui.model.application.ui.basic.MStackElement; import org.eclipse.e4.ui.model.application.ui.basic.MWindow; import org.eclipse.e4.ui.model.application.ui.basic.impl.BasicFactoryImpl; import org.eclipse.e4.ui.workbench.IPresentationEngine; import org.eclipse.e4.ui.workbench.Selector; import org.eclipse.e4.ui.workbench.modeling.EModelService; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; /** * */ public class SplitDropAgent2 extends DropAgent { private static final int TOLERANCE = 35; private MUIElement relToElement = null; private SplitFeedbackOverlay feedback = null; private int dragElementLocation; private int where; private float ratio = 0.5f; private Rectangle trackRect; private boolean wasModified = false; /** * @param manager * the DnDManager using this agent */ public SplitDropAgent2(DnDManager manager) { super(manager); } @Override public boolean canDrop(MUIElement dragElement, DnDInfo info) { if (!(dragElement instanceof MStackElement) && !(dragElement instanceof MPartStack)) { return false; } relToElement = getTargetElement(dragElement, info); return relToElement != null; } private MUIElement getTargetElement(MUIElement dragElement, DnDInfo info) { MUIElement target = null; dragElementLocation = dndManager.getModelService().getElementLocation(dragElement); // If we're inside a perspective somewhere check the shared area's edges if (dragElementLocation == EModelService.IN_SHARED_AREA || dragElementLocation == EModelService.IN_ACTIVE_PERSPECTIVE) { target = checkAreaEdge(dragElement, info); } if (target == null && dragElementLocation == EModelService.IN_ACTIVE_PERSPECTIVE || dragElementLocation == EModelService.OUTSIDE_PERSPECTIVE) { target = checkPerspectiveEdge(dragElement, info); } if (target == null) { target = checkStacks(dragElement, info); } return target; } private boolean isInCursorShell(DnDInfo info, Control ctrl) { if (ctrl == null || info.curCtrl == null || info.curCtrl.isDisposed()) { return false; } Shell infoShell = (Shell) (info.curCtrl instanceof Shell ? info.curCtrl : info.curCtrl .getShell()); return ctrl.getShell() == infoShell; } private MPartStack checkStacks(final MUIElement dragElement, final DnDInfo info) { MPartStack candidateStack = null; // Collect up the elements we can be split 'relative to' final EModelService ms = dndManager.getModelService(); List<MPartStack> stacks = ms.findElements(ms.getTopLevelWindowFor(dragElement), MPartStack.class, EModelService.PRESENTATION, new Selector() { @Override public boolean select(MApplicationElement element) { MPartStack stack = (MPartStack) element; // Has to be visible... if (!stack.isVisible() || !(stack.getWidget() instanceof CTabFolder)) { return false; } // ...not disposed... CTabFolder ctf = (CTabFolder) stack.getWidget(); if (ctf.isDisposed()) { return false; } // ...and in the shell the cursor is over if (!isInCursorShell(info, ctf)) { return false; } // ...and the cursor must be in the CTF's client area Rectangle bb = ctf.getClientArea(); bb = ctf.getDisplay().map(ctf, null, bb); if (!bb.contains(info.cursorPos)) { return false; } // Can't split with ourselves if we're dragging a stack if (dragElement instanceof MPartStack && stack == dragElement) { return false; } // Can't split with ourselves if we're dragging the only visible element in // a stack MUIElement deParent = dragElement.getParent(); if (dragElement instanceof MStackElement && stack == deParent && ms.countRenderableChildren(deParent) == 1) { return false; } return true; } }); if (stacks.size() > 0) { candidateStack = stacks.get(0); if (candidateStack.getWidget() instanceof CTabFolder) { CTabFolder ctf = (CTabFolder) (candidateStack.getWidget()); trackRect = ctf.getClientArea(); trackRect = ctf.getDisplay().map(ctf, null, trackRect); } } return candidateStack; } private MUIElement checkAreaEdge(MUIElement dragElement, DnDInfo info) { MPerspective persp = dndManager.getModelService().getPerspectiveFor(dragElement); List<MArea> areaList = dndManager.getModelService().findElements(persp, null, MArea.class, null, EModelService.IN_SHARED_AREA); if (areaList.size() > 0) { MArea area = areaList.get(0); Control ctrl = (Control) area.getWidget(); if (checkEdge(info, ctrl)) { return area; } } return null; } private MUIElement checkPerspectiveEdge(MUIElement dragElement, DnDInfo info) { MWindow win = dndManager.getModelService().getTopLevelWindowFor(dragElement); MPerspective persp = dndManager.getModelService().getActivePerspective(win); if (persp != null && persp.getWidget() instanceof Control) { Control ctrl = (Control) persp.getWidget(); if (checkEdge(info, ctrl)) { return persp; } } return null; } private boolean checkEdge(DnDInfo info, Control ctrl) { boolean onEdge = false; if (!isInCursorShell(info, ctrl)) { return false; } Rectangle bb = ctrl.getBounds(); bb = ctrl.getDisplay().map(ctrl.getParent(), null, bb); if (bb.contains(info.cursorPos)) { Point p = info.cursorPos; if (p.x - bb.x < TOLERANCE) { where = EModelService.LEFT_OF; trackRect = new Rectangle(bb.x, bb.y, TOLERANCE, bb.height); onEdge = true; // } else if (p.y - bb.y < TOLERANCE) { // where = EModelService.ABOVE; // trackRect = new Rectangle(bb.x, bb.y, bb.width, TOLERANCE); // onEdge = true; } else if ((bb.x + bb.width) - p.x < TOLERANCE) { where = EModelService.RIGHT_OF; trackRect = new Rectangle(bb.x + (bb.width - TOLERANCE), bb.y, TOLERANCE, bb.height); onEdge = true; } else if ((bb.y + bb.height) - p.y < TOLERANCE) { where = EModelService.BELOW; trackRect = new Rectangle(bb.x, bb.y + (bb.height - TOLERANCE), bb.width, TOLERANCE); onEdge = true; } } return onEdge; } @Override public void dragEnter(final MUIElement dragElement, DnDInfo info) { super.dragEnter(dragElement, info); where = setRelToInfo(info); showFeedback(); } private int setRelToInfo(DnDInfo info) { if (relToElement instanceof MPerspective || relToElement instanceof MArea) { ratio = 0.33f; // 'where' has already been set return where; } else if (relToElement instanceof MPartStack) { ratio = 0.5f; Point p = info.cursorPos; int dTop = p.y - trackRect.y; int dBottom = (trackRect.y + trackRect.height) - p.y; int dLeft = p.x - trackRect.x; int dRight = (trackRect.x + trackRect.width) - p.x; if (trackRect.width >= trackRect.height) { int thirdOfWidth = trackRect.width / 3; // System.out.println("tow: " + thirdOfWidth + " dLeft: " + dLeft + " dRight: " // + dRight); if (dLeft < thirdOfWidth) { return EModelService.LEFT_OF; } else if (dRight < thirdOfWidth) { return EModelService.RIGHT_OF; } else { return dTop < dBottom ? EModelService.ABOVE : EModelService.BELOW; } } int thirdOfHeight = trackRect.height / 3; if (dTop < thirdOfHeight) { return EModelService.ABOVE; } else if (dBottom < thirdOfHeight) { return EModelService.BELOW; } else if (relToElement != null) { return dLeft < dRight ? EModelService.LEFT_OF : EModelService.RIGHT_OF; } } return -1; } @Override public void dragLeave(MUIElement dragElement, DnDInfo info) { dndManager.clearOverlay(); clearFeedback(); relToElement = null; reactivatePart(dragElement); super.dragLeave(dragElement, info); } @Override public boolean drop(MUIElement dragElement, DnDInfo info) { MPartSashContainerElement toInsert = (MPartSashContainerElement) dragElement; if (dragElement instanceof MPartStack) { // Ensure we restore the stack to the presentation first if (toInsert.getTags().contains(IPresentationEngine.MINIMIZED)) { toInsert.getTags().remove(IPresentationEngine.MINIMIZED); } toInsert.getParent().getChildren().remove(toInsert); } else { // wrap it in a stack if it's a part MStackElement stackElement = (MStackElement) dragElement; MPartStack newStack = BasicFactoryImpl.eINSTANCE.createPartStack(); newStack.getChildren().add(stackElement); newStack.setSelectedElement(stackElement); toInsert = newStack; } // treat the lone editor area stack as if it were the area if (dndManager.getModelService().isLastEditorStack(relToElement)) { MUIElement targetParent = relToElement.getParent(); while (!(targetParent instanceof MArea)) { targetParent = targetParent.getParent(); } relToElement = targetParent; } // Adjust the relToElement based on the location of the dragElement if (relToElement instanceof MArea) { // make it difficult to drag outside parts into the shared area boolean fromSharedArea = dragElementLocation == EModelService.IN_SHARED_AREA; // if from shared area and no modifier, is ok // if not from shared area and modifier is on, then ok boolean shouldBePlacedInSharedArea = fromSharedArea == !isModified(); if (shouldBePlacedInSharedArea) { MArea area = (MArea) relToElement; relToElement = area.getChildren().get(0); } } else if (relToElement instanceof MPerspective) { if (dragElementLocation == EModelService.IN_ACTIVE_PERSPECTIVE) { MPerspective persp = (MPerspective) relToElement; relToElement = persp.getChildren().get(0); } } dndManager.getModelService().insert(toInsert, (MPartSashContainerElement) relToElement, where, ratio); // reactivatePart(dragElement); return true; } private boolean isModified() { return dndManager.isModified && (relToElement instanceof MArea || relToElement instanceof MPerspective || dndManager .getModelService().isLastEditorStack(relToElement)); } @Override public boolean track(MUIElement dragElement, DnDInfo info) { if (getTargetElement(dragElement, info) != relToElement) { dndManager.setCursor(Display.getCurrent().getSystemCursor(SWT.CURSOR_NO)); return false; } if (!trackRect.contains(info.cursorPos)) { dndManager.setCursor(Display.getCurrent().getSystemCursor(SWT.CURSOR_NO)); return false; } dndManager.setCursor(Display.getCurrent().getSystemCursor(SWT.CURSOR_HAND)); int curWhere = where; where = setRelToInfo(info); if (where == curWhere && wasModified == isModified()) { return true; } wasModified = isModified(); showFeedback(); return true; } private void showFeedback() { if (relToElement == null || !(relToElement.getWidget() instanceof Control)) { return; } int side = -1; if (where == EModelService.ABOVE) { side = SWT.TOP; } else if (where == EModelService.BELOW) { side = SWT.BOTTOM; } else if (where == EModelService.LEFT_OF) { side = SWT.LEFT; } else if (where == EModelService.RIGHT_OF) { side = SWT.RIGHT; } if (feedback != null) { feedback.dispose(); } Control ctrl = (Control) relToElement.getWidget(); Rectangle bb = ctrl.getBounds(); bb = ctrl.getDisplay().map(ctrl.getParent(), null, bb); feedback = new SplitFeedbackOverlay(ctrl.getShell(), bb, side, ratio, isModified(), isModified()); feedback.setVisible(true); } private void clearFeedback() { if (feedback == null) { return; } feedback.dispose(); feedback = null; } }