blob: 8f1790dcccac5a6e94fc0ba8f9d0f0b748c7ca83 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2007 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.ui.internal;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jface.util.Geometry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.internal.dnd.DragBorder;
import org.eclipse.ui.internal.dnd.DragUtil;
import org.eclipse.ui.internal.dnd.IDragOverListener;
import org.eclipse.ui.internal.dnd.IDropTarget;
import org.eclipse.ui.internal.dnd.IDropTarget2;
import org.eclipse.ui.internal.layout.IWindowTrim;
import org.eclipse.ui.internal.layout.LayoutUtil;
import org.eclipse.ui.internal.layout.TrimDescriptor;
import org.eclipse.ui.internal.layout.TrimLayout;
import org.eclipse.ui.internal.layout.TrimToolBarBase;
/**
*/
/*package*/class TrimDropTarget implements IDragOverListener {
private final class ActualTrimDropTarget implements IDropTarget2 {
public IWindowTrim draggedTrim;
// tracking parameters
private DragBorder border = null;
private int dockedArea;
// Holder for the position of trim that is 'floating' with the cursor
private int cursorAreaId;
private int initialAreaId;
private IWindowTrim initialInsertBefore;
private Rectangle initialLocation;
/**
* Constructor
*/
private ActualTrimDropTarget() {
super();
draggedTrim = null;
dockedArea = SWT.NONE;
initialAreaId = SWT.NONE;
initialInsertBefore = null;
}
/**
* This method is used to delineate separate trims dragging events. The -first- drag
* event will set this and then it will remain constant until the drag gesture is done;
* either by dropping or escaping. Once the gesture is finished the trim value is set
* back to 'null'.
*
* @param trim The trim item currently being dragged.
*/
public void startDrag(IWindowTrim trim) {
// Are we starting a new drag?
if (draggedTrim != trim) {
// remember the dragged trim
draggedTrim = trim;
// Remember the location that we were in initially so we
// can go back there on an cancel...
initialAreaId = layout.getTrimAreaId(draggedTrim.getControl());
// Determine who we were placed 'before' in the trim
initialInsertBefore = getInsertBefore(initialAreaId, draggedTrim);
// Remember the location that the control used to be at for animation purposes
initialLocation = DragUtil.getDisplayBounds(draggedTrim.getControl());
// The dragged trim is always initially docked
dockedArea = initialAreaId;
}
}
/**
* Determine the trim area from the point. To avoid clashing at the 'corners' due to extending the trim area's
* rectangles we first ensure that the point is not actually -within- a trim area before we check the extended
* rectangles.
*
* @param pos The current cursor pos
* @return the Trim area that the cursor is in or SWT.NONE if the point is not in an area
*/
private int getTrimArea(Point pos) {
// First, check if we're actually -within- a trim area (i.e. no boundary extensions)
int areaId = getTrimArea(pos, 0);
// If we are not inside a trim area...are we 'close' to one?
if (areaId == SWT.NONE) {
areaId = getTrimArea(pos, TrimDragPreferences.getThreshold());
}
// not inside any trim area
return areaId;
}
/**
* Checks the trims areas against the given position. Each trim area is 'extended' into
* the workbench page by the value of <code>extendedBoundaryWidth</code> before the checking
* takes place.
*
* @param pos The point to check against
* @param extendedBoundaryWidth The amount to extend the trim area's 'inner' edge by
*
* @return The trim area or SWT.NONE if the point is not within any extended trim area's rect.
*/
private int getTrimArea(Point pos, int extendedBoundaryWidth) {
int[] areaIds = layout.getAreaIds();
for (int i = 0; i < areaIds.length; i++) {
Rectangle trimRect = layout.getTrimRect(windowComposite, areaIds[i]);
trimRect = Geometry.toControl(windowComposite, trimRect);
// Only check 'valid' sides
if ( (areaIds[i] & getValidSides()) != SWT.NONE) {
// TODO: more confusion binding 'areaIds' to SWT 'sides'
switch (areaIds[i]) {
case SWT.LEFT:
trimRect.width += extendedBoundaryWidth;
if (pos.y >= trimRect.y &&
pos.y <= (trimRect.y+trimRect.height) &&
pos.x <= (trimRect.x+trimRect.width)) {
return areaIds[i];
}
break;
case SWT.RIGHT:
trimRect.x -= extendedBoundaryWidth;
trimRect.width += extendedBoundaryWidth;
if (pos.y >= trimRect.y &&
pos.y <= (trimRect.y+trimRect.height) &&
pos.x >= trimRect.x) {
return areaIds[i];
}
break;
case SWT.TOP:
trimRect.height += extendedBoundaryWidth;
if (pos.x >= trimRect.x &&
pos.x <= (trimRect.x+trimRect.width) &&
pos.y <= (trimRect.y+trimRect.height)) {
return areaIds[i];
}
break;
case SWT.BOTTOM:
trimRect.y -= extendedBoundaryWidth;
trimRect.height += extendedBoundaryWidth;
if (pos.x >= trimRect.x &&
pos.x <= (trimRect.x+trimRect.width) &&
pos.y >= trimRect.y) {
return areaIds[i];
}
break;
}
}
}
// not inside any trim area
return SWT.NONE;
}
/**
* Determine the window trim that the currently dragged trim should be inserted
* before.
* @param areaId The area id that is being checked
* @param pos The position used to determine the correct insertion trim
* @return The trim to 'dock' the draggedTrim before
*/
private IWindowTrim getInsertBefore(int areaId, Point pos) {
boolean isHorizontal = (areaId == SWT.TOP) || (areaId == SWT.BOTTOM);
// Walk the trim area and return the first one that the positon
// is 'after'.
List tDescs = layout.getTrimArea(areaId).getDescriptors();
for (Iterator iter = tDescs.iterator(); iter.hasNext();) {
TrimDescriptor desc = (TrimDescriptor) iter.next();
// Skip ourselves
if (desc.getTrim() == draggedTrim) {
continue;
}
// Now, check
Rectangle bb = desc.getCache().getControl().getBounds();
Point center = Geometry.centerPoint(bb);
if (isHorizontal) {
if (pos.x < center.x) {
return desc.getTrim();
}
}
else {
if (pos.y < center.y) {
return desc.getTrim();
}
}
}
return null;
}
/**
* Returns the trim that is 'before' the given trim in the given area
*
* @param areaId The areaId of the trim
* @param trim The trim to find the element after
*
* @return The trim that the given trim is 'before'
*/
private IWindowTrim getInsertBefore(int areaId, IWindowTrim trim) {
List tDescs = layout.getTrimArea(areaId).getDescriptors();
for (Iterator iter = tDescs.iterator(); iter.hasNext();) {
TrimDescriptor desc = (TrimDescriptor) iter.next();
if (desc.getTrim() == trim) {
if (iter.hasNext()) {
desc = (TrimDescriptor) iter.next();
return desc.getTrim();
}
return null;
}
}
return null;
}
/**
* Recalculates the drop information based on the current cursor pos.
*
* @param pos The cursor position
*/
public void track(Point pos) {
// Convert the mouse positon into 'local' coords
Rectangle r = new Rectangle(pos.x, pos.y, 1,1);
r = Geometry.toControl(windowComposite, r);
pos.x = r.x;
pos.y = r.y;
// Are we 'inside' a trim area ?
cursorAreaId = getTrimArea(pos);
// Provide tracking for the appropriate 'mode'
if (cursorAreaId != SWT.NONE) {
trackInsideTrimArea(pos);
} else {
trackOutsideTrimArea(pos);
}
}
/**
* Perform the feedback used when the cursor is 'inside' a particular trim area.
* The current implementation will place the dragged trim into the trim area at
* the location determined by the supplied point.
*
* @param pos The point to use to determine where in the trim area the dragged trim
* should be located.
*/
private void trackInsideTrimArea(Point pos) {
// Where should we be?
int newArea = getTrimArea(pos);
IWindowTrim newInsertBefore = getInsertBefore(newArea, pos);
// if we're currently undocked then we should dock
boolean shouldDock = dockedArea == SWT.NONE;
// If we're already docked then only update if there's a change in area or position
if (dockedArea != SWT.NONE) {
// Where are we now?
IWindowTrim curInsertBefore = getInsertBefore(dockedArea, draggedTrim);
// If we're already docked we should only update if there's a change
shouldDock = dockedArea != newArea || curInsertBefore != newInsertBefore;
}
// Do we have to do anything?
if (shouldDock) {
// (Re)dock the trim in the new location
dock(newArea, newInsertBefore);
}
}
/**
* Provide the dragging feedback when the cursor is -not- explicitly inside
* a particular trim area.
*
*/
private void trackOutsideTrimArea(Point pos) {
// If we -were- docked then undock
if (dockedArea != SWT.NONE) {
undock();
}
border.setLocation(pos, SWT.BOTTOM);
}
/**
* Return the set of valid sides that a piece of trim can be docked on. We
* arbitrarily extend this to include any areas that won't cause a change in orientation
*
* @return The extended drop 'side' set
*/
private int getValidSides() {
int result = draggedTrim.getValidSides();
if (result == SWT.NONE) {
return result;
}
// For now, if we can dock...we can dock -anywhere-
return SWT.TOP | SWT.BOTTOM | SWT.LEFT | SWT.RIGHT;
}
/**
* The user either cancelled the drag or tried to drop the trim in an invalid
* area...put the trim back in the last location it was in
*/
private void redock() {
// Since the control might move 'far' we'll provide an animation
Rectangle startRect = DragUtil.getDisplayBounds(draggedTrim.getControl());
RectangleAnimation animation = new RectangleAnimation(
windowComposite.getShell(), startRect, initialLocation, 300);
animation.schedule();
dock(initialAreaId, initialInsertBefore);
}
/* (non-Javadoc)
* @see org.eclipse.ui.internal.dnd.IDropTarget#drop()
*/
public void drop() {
// If we aren't docked then restore the initial location
if (dockedArea == SWT.NONE) {
redock();
}
}
/**
* Remove the trim frmo its current 'docked' location and attach it
* to the cursor...
*/
private void undock() {
// Remove the trim from the layout
layout.removeTrim(draggedTrim);
LayoutUtil.resize(draggedTrim.getControl());
// Re-orient the widget to its -original- side and size
draggedTrim.dock(initialAreaId);
draggedTrim.getControl().setSize(initialLocation.width, initialLocation.height);
// Create a new dragging border onto the dragged trim
// Special check for TrimPart...should be generalized
boolean wantsFrame = !(draggedTrim instanceof TrimToolBarBase);
border = new DragBorder(windowComposite, draggedTrim.getControl(), wantsFrame);
dockedArea = SWT.NONE;
}
/**
* Return the 'undocked' trim to its previous location in the layout
*/
private void dock(int areaId, IWindowTrim insertBefore) {
// remove the drag 'border'
if (border != null) {
border.dispose();
border = null;
}
// Update the trim's orientation if necessary
draggedTrim.dock(areaId);
// Add the trim into the layout
layout.addTrim(areaId, draggedTrim, insertBefore);
LayoutUtil.resize(draggedTrim.getControl());
// Remember the area that we're currently docked in
dockedArea = areaId;
}
/* (non-Javadoc)
* @see org.eclipse.ui.internal.dnd.IDropTarget#getCursor()
*/
public Cursor getCursor() {
// If the trim isn't docked then show the 'no smoking' sign
if (cursorAreaId == SWT.NONE) {
return windowComposite.getDisplay().getSystemCursor(SWT.CURSOR_NO);
}
// It's docked; show the four-way arrow cursor
return windowComposite.getDisplay().getSystemCursor(SWT.CURSOR_SIZEALL);
}
/* (non-Javadoc)
* @see org.eclipse.ui.internal.dnd.IDropTarget#getSnapRectangle()
*/
public Rectangle getSnapRectangle() {
// TODO: KLUDGE!! We don't want to show -any- snap rect
// but Tracker won't allow that so place it where it won't be visible
return new Rectangle(100000, 0,0,0);
}
/* (non-Javadoc)
* @see org.eclipse.ui.internal.dnd.IDropTarget2#dragFinished(boolean)
*/
public void dragFinished(boolean dropPerformed) {
// If we didn't perform a drop then restore the original position
if (!dropPerformed && dockedArea == SWT.NONE) {
// Force the dragged trim back into its original position...
redock();
}
// Set the draggedTrim to null. This indicates that we're no longer
// dragging the trim. The first call to the TrimDropTarget's 'drag' method
// will reset this the next time a drag starts.
draggedTrim = null;
}
}
private ActualTrimDropTarget dropTarget;
private TrimLayout layout;
private Composite windowComposite;
/**
* Create a new drop target capable of accepting IWindowTrim items
*
* @param someComposite The control owning the TrimLayout
* @param theWindow the workbenchWindow
*/
public TrimDropTarget(Composite someComposite, WorkbenchWindow theWindow) {
layout = (TrimLayout) someComposite.getLayout();
windowComposite = someComposite;
// Create an instance of a drop target to use
dropTarget = new ActualTrimDropTarget();
}
/* (non-Javadoc)
* @see org.eclipse.ui.internal.dnd.IDragOverListener#drag(org.eclipse.swt.widgets.Control, java.lang.Object, org.eclipse.swt.graphics.Point, org.eclipse.swt.graphics.Rectangle)
*/
public IDropTarget drag(Control currentControl, Object draggedObject,
Point position, final Rectangle dragRectangle) {
// Have to be dragging trim
if (!(draggedObject instanceof IWindowTrim)) {
return null;
}
// OK, we're dragging trim. is it from -this- shell?
IWindowTrim trim = (IWindowTrim) draggedObject;
if (trim.getControl().getShell() != windowComposite.getShell()) {
return null;
}
// If this is the -first- drag then inform the drop target
if (dropTarget.draggedTrim == null) {
dropTarget.startDrag(trim);
}
// Forward on to the 'actual' drop target for feedback
dropTarget.track(position);
// Spin the paint loop after every track
windowComposite.getDisplay().update();
return dropTarget;
}
}