| /******************************************************************************* |
| * 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; |
| } |
| } |