001: /*******************************************************************************
002: * Copyright (c) 2004, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal;
011:
012: import java.util.Iterator;
013: import java.util.List;
014:
015: import org.eclipse.jface.util.Geometry;
016: import org.eclipse.swt.SWT;
017: import org.eclipse.swt.graphics.Cursor;
018: import org.eclipse.swt.graphics.Point;
019: import org.eclipse.swt.graphics.Rectangle;
020: import org.eclipse.swt.widgets.Composite;
021: import org.eclipse.swt.widgets.Control;
022: import org.eclipse.ui.internal.dnd.DragBorder;
023: import org.eclipse.ui.internal.dnd.DragUtil;
024: import org.eclipse.ui.internal.dnd.IDragOverListener;
025: import org.eclipse.ui.internal.dnd.IDropTarget;
026: import org.eclipse.ui.internal.dnd.IDropTarget2;
027: import org.eclipse.ui.internal.layout.IWindowTrim;
028: import org.eclipse.ui.internal.layout.LayoutUtil;
029: import org.eclipse.ui.internal.layout.TrimDescriptor;
030: import org.eclipse.ui.internal.layout.TrimLayout;
031: import org.eclipse.ui.internal.layout.TrimToolBarBase;
032:
033: /**
034: */
035: /*package*/class TrimDropTarget implements IDragOverListener {
036:
037: private final class ActualTrimDropTarget implements IDropTarget2 {
038: public IWindowTrim draggedTrim;
039:
040: // tracking parameters
041: private DragBorder border = null;
042: private int dockedArea;
043:
044: // Holder for the position of trim that is 'floating' with the cursor
045: private int cursorAreaId;
046: private int initialAreaId;
047: private IWindowTrim initialInsertBefore;
048: private Rectangle initialLocation;
049:
050: /**
051: * Constructor
052: */
053: private ActualTrimDropTarget() {
054: super ();
055:
056: draggedTrim = null;
057: dockedArea = SWT.NONE;
058:
059: initialAreaId = SWT.NONE;
060: initialInsertBefore = null;
061: }
062:
063: /**
064: * This method is used to delineate separate trims dragging events. The -first- drag
065: * event will set this and then it will remain constant until the drag gesture is done;
066: * either by dropping or escaping. Once the gesture is finished the trim value is set
067: * back to 'null'.
068: *
069: * @param trim The trim item currently being dragged.
070: */
071: public void startDrag(IWindowTrim trim) {
072: // Are we starting a new drag?
073: if (draggedTrim != trim) {
074: // remember the dragged trim
075: draggedTrim = trim;
076:
077: // Remember the location that we were in initially so we
078: // can go back there on an cancel...
079: initialAreaId = layout.getTrimAreaId(draggedTrim
080: .getControl());
081:
082: // Determine who we were placed 'before' in the trim
083: initialInsertBefore = getInsertBefore(initialAreaId,
084: draggedTrim);
085:
086: // Remember the location that the control used to be at for animation purposes
087: initialLocation = DragUtil.getDisplayBounds(draggedTrim
088: .getControl());
089:
090: // The dragged trim is always initially docked
091: dockedArea = initialAreaId;
092: }
093: }
094:
095: /**
096: * Determine the trim area from the point. To avoid clashing at the 'corners' due to extending the trim area's
097: * rectangles we first ensure that the point is not actually -within- a trim area before we check the extended
098: * rectangles.
099: *
100: * @param pos The current cursor pos
101: * @return the Trim area that the cursor is in or SWT.NONE if the point is not in an area
102: */
103: private int getTrimArea(Point pos) {
104: // First, check if we're actually -within- a trim area (i.e. no boundary extensions)
105: int areaId = getTrimArea(pos, 0);
106:
107: // If we are not inside a trim area...are we 'close' to one?
108: if (areaId == SWT.NONE) {
109: areaId = getTrimArea(pos, TrimDragPreferences
110: .getThreshold());
111: }
112:
113: // not inside any trim area
114: return areaId;
115: }
116:
117: /**
118: * Checks the trims areas against the given position. Each trim area is 'extended' into
119: * the workbench page by the value of <code>extendedBoundaryWidth</code> before the checking
120: * takes place.
121: *
122: * @param pos The point to check against
123: * @param extendedBoundaryWidth The amount to extend the trim area's 'inner' edge by
124: *
125: * @return The trim area or SWT.NONE if the point is not within any extended trim area's rect.
126: */
127: private int getTrimArea(Point pos, int extendedBoundaryWidth) {
128: int[] areaIds = layout.getAreaIds();
129: for (int i = 0; i < areaIds.length; i++) {
130: Rectangle trimRect = layout.getTrimRect(
131: windowComposite, areaIds[i]);
132: trimRect = Geometry
133: .toControl(windowComposite, trimRect);
134:
135: // Only check 'valid' sides
136: if ((areaIds[i] & getValidSides()) != SWT.NONE) {
137: // TODO: more confusion binding 'areaIds' to SWT 'sides'
138: switch (areaIds[i]) {
139: case SWT.LEFT:
140: trimRect.width += extendedBoundaryWidth;
141:
142: if (pos.y >= trimRect.y
143: && pos.y <= (trimRect.y + trimRect.height)
144: && pos.x <= (trimRect.x + trimRect.width)) {
145: return areaIds[i];
146: }
147: break;
148: case SWT.RIGHT:
149: trimRect.x -= extendedBoundaryWidth;
150: trimRect.width += extendedBoundaryWidth;
151:
152: if (pos.y >= trimRect.y
153: && pos.y <= (trimRect.y + trimRect.height)
154: && pos.x >= trimRect.x) {
155: return areaIds[i];
156: }
157: break;
158: case SWT.TOP:
159: trimRect.height += extendedBoundaryWidth;
160:
161: if (pos.x >= trimRect.x
162: && pos.x <= (trimRect.x + trimRect.width)
163: && pos.y <= (trimRect.y + trimRect.height)) {
164: return areaIds[i];
165: }
166: break;
167: case SWT.BOTTOM:
168: trimRect.y -= extendedBoundaryWidth;
169: trimRect.height += extendedBoundaryWidth;
170:
171: if (pos.x >= trimRect.x
172: && pos.x <= (trimRect.x + trimRect.width)
173: && pos.y >= trimRect.y) {
174: return areaIds[i];
175: }
176: break;
177: }
178: }
179: }
180:
181: // not inside any trim area
182: return SWT.NONE;
183: }
184:
185: /**
186: * Determine the window trim that the currently dragged trim should be inserted
187: * before.
188: * @param areaId The area id that is being checked
189: * @param pos The position used to determine the correct insertion trim
190: * @return The trim to 'dock' the draggedTrim before
191: */
192: private IWindowTrim getInsertBefore(int areaId, Point pos) {
193: boolean isHorizontal = (areaId == SWT.TOP)
194: || (areaId == SWT.BOTTOM);
195:
196: // Walk the trim area and return the first one that the positon
197: // is 'after'.
198: List tDescs = layout.getTrimArea(areaId).getDescriptors();
199: for (Iterator iter = tDescs.iterator(); iter.hasNext();) {
200: TrimDescriptor desc = (TrimDescriptor) iter.next();
201:
202: // Skip ourselves
203: if (desc.getTrim() == draggedTrim) {
204: continue;
205: }
206:
207: // Now, check
208: Rectangle bb = desc.getCache().getControl().getBounds();
209: Point center = Geometry.centerPoint(bb);
210: if (isHorizontal) {
211: if (pos.x < center.x) {
212: return desc.getTrim();
213: }
214: } else {
215: if (pos.y < center.y) {
216: return desc.getTrim();
217: }
218: }
219: }
220:
221: return null;
222: }
223:
224: /**
225: * Returns the trim that is 'before' the given trim in the given area
226: *
227: * @param areaId The areaId of the trim
228: * @param trim The trim to find the element after
229: *
230: * @return The trim that the given trim is 'before'
231: */
232: private IWindowTrim getInsertBefore(int areaId, IWindowTrim trim) {
233: List tDescs = layout.getTrimArea(areaId).getDescriptors();
234: for (Iterator iter = tDescs.iterator(); iter.hasNext();) {
235: TrimDescriptor desc = (TrimDescriptor) iter.next();
236: if (desc.getTrim() == trim) {
237: if (iter.hasNext()) {
238: desc = (TrimDescriptor) iter.next();
239: return desc.getTrim();
240: }
241: return null;
242: }
243: }
244:
245: return null;
246: }
247:
248: /**
249: * Recalculates the drop information based on the current cursor pos.
250: *
251: * @param pos The cursor position
252: */
253: public void track(Point pos) {
254: // Convert the mouse positon into 'local' coords
255: Rectangle r = new Rectangle(pos.x, pos.y, 1, 1);
256: r = Geometry.toControl(windowComposite, r);
257: pos.x = r.x;
258: pos.y = r.y;
259:
260: // Are we 'inside' a trim area ?
261: cursorAreaId = getTrimArea(pos);
262:
263: // Provide tracking for the appropriate 'mode'
264: if (cursorAreaId != SWT.NONE) {
265: trackInsideTrimArea(pos);
266: } else {
267: trackOutsideTrimArea(pos);
268: }
269: }
270:
271: /**
272: * Perform the feedback used when the cursor is 'inside' a particular trim area.
273: * The current implementation will place the dragged trim into the trim area at
274: * the location determined by the supplied point.
275: *
276: * @param pos The point to use to determine where in the trim area the dragged trim
277: * should be located.
278: */
279: private void trackInsideTrimArea(Point pos) {
280: // Where should we be?
281: int newArea = getTrimArea(pos);
282: IWindowTrim newInsertBefore = getInsertBefore(newArea, pos);
283:
284: // if we're currently undocked then we should dock
285: boolean shouldDock = dockedArea == SWT.NONE;
286:
287: // If we're already docked then only update if there's a change in area or position
288: if (dockedArea != SWT.NONE) {
289: // Where are we now?
290: IWindowTrim curInsertBefore = getInsertBefore(
291: dockedArea, draggedTrim);
292:
293: // If we're already docked we should only update if there's a change
294: shouldDock = dockedArea != newArea
295: || curInsertBefore != newInsertBefore;
296: }
297:
298: // Do we have to do anything?
299: if (shouldDock) {
300: // (Re)dock the trim in the new location
301: dock(newArea, newInsertBefore);
302: }
303: }
304:
305: /**
306: * Provide the dragging feedback when the cursor is -not- explicitly inside
307: * a particular trim area.
308: *
309: */
310: private void trackOutsideTrimArea(Point pos) {
311: // If we -were- docked then undock
312: if (dockedArea != SWT.NONE) {
313: undock();
314: }
315:
316: border.setLocation(pos, SWT.BOTTOM);
317: }
318:
319: /**
320: * Return the set of valid sides that a piece of trim can be docked on. We
321: * arbitrarily extend this to include any areas that won't cause a change in orientation
322: *
323: * @return The extended drop 'side' set
324: */
325: private int getValidSides() {
326: int result = draggedTrim.getValidSides();
327: if (result == SWT.NONE) {
328: return result;
329: }
330:
331: // For now, if we can dock...we can dock -anywhere-
332: return SWT.TOP | SWT.BOTTOM | SWT.LEFT | SWT.RIGHT;
333: }
334:
335: /**
336: * The user either cancelled the drag or tried to drop the trim in an invalid
337: * area...put the trim back in the last location it was in
338: */
339: private void redock() {
340: // Since the control might move 'far' we'll provide an animation
341: Rectangle startRect = DragUtil.getDisplayBounds(draggedTrim
342: .getControl());
343: RectangleAnimation animation = new RectangleAnimation(
344: windowComposite.getShell(), startRect,
345: initialLocation, 300);
346: animation.schedule();
347:
348: dock(initialAreaId, initialInsertBefore);
349: }
350:
351: /* (non-Javadoc)
352: * @see org.eclipse.ui.internal.dnd.IDropTarget#drop()
353: */
354: public void drop() {
355: // If we aren't docked then restore the initial location
356: if (dockedArea == SWT.NONE) {
357: redock();
358: }
359: }
360:
361: /**
362: * Remove the trim frmo its current 'docked' location and attach it
363: * to the cursor...
364: */
365: private void undock() {
366: // Remove the trim from the layout
367: layout.removeTrim(draggedTrim);
368: LayoutUtil.resize(draggedTrim.getControl());
369:
370: // Re-orient the widget to its -original- side and size
371: draggedTrim.dock(initialAreaId);
372: draggedTrim.getControl().setSize(initialLocation.width,
373: initialLocation.height);
374:
375: // Create a new dragging border onto the dragged trim
376: // Special check for TrimPart...should be generalized
377: boolean wantsFrame = !(draggedTrim instanceof TrimToolBarBase);
378: border = new DragBorder(windowComposite, draggedTrim
379: .getControl(), wantsFrame);
380:
381: dockedArea = SWT.NONE;
382: }
383:
384: /**
385: * Return the 'undocked' trim to its previous location in the layout
386: */
387: private void dock(int areaId, IWindowTrim insertBefore) {
388: // remove the drag 'border'
389: if (border != null) {
390: border.dispose();
391: border = null;
392: }
393:
394: // Update the trim's orientation if necessary
395: draggedTrim.dock(areaId);
396:
397: // Add the trim into the layout
398: layout.addTrim(areaId, draggedTrim, insertBefore);
399: LayoutUtil.resize(draggedTrim.getControl());
400:
401: // Remember the area that we're currently docked in
402: dockedArea = areaId;
403: }
404:
405: /* (non-Javadoc)
406: * @see org.eclipse.ui.internal.dnd.IDropTarget#getCursor()
407: */
408: public Cursor getCursor() {
409: // If the trim isn't docked then show the 'no smoking' sign
410: if (cursorAreaId == SWT.NONE) {
411: return windowComposite.getDisplay().getSystemCursor(
412: SWT.CURSOR_NO);
413: }
414:
415: // It's docked; show the four-way arrow cursor
416: return windowComposite.getDisplay().getSystemCursor(
417: SWT.CURSOR_SIZEALL);
418: }
419:
420: /* (non-Javadoc)
421: * @see org.eclipse.ui.internal.dnd.IDropTarget#getSnapRectangle()
422: */
423: public Rectangle getSnapRectangle() {
424: // TODO: KLUDGE!! We don't want to show -any- snap rect
425: // but Tracker won't allow that so place it where it won't be visible
426: return new Rectangle(100000, 0, 0, 0);
427: }
428:
429: /* (non-Javadoc)
430: * @see org.eclipse.ui.internal.dnd.IDropTarget2#dragFinished(boolean)
431: */
432: public void dragFinished(boolean dropPerformed) {
433: // If we didn't perform a drop then restore the original position
434: if (!dropPerformed && dockedArea == SWT.NONE) {
435: // Force the dragged trim back into its original position...
436: redock();
437: }
438:
439: // Set the draggedTrim to null. This indicates that we're no longer
440: // dragging the trim. The first call to the TrimDropTarget's 'drag' method
441: // will reset this the next time a drag starts.
442: draggedTrim = null;
443: }
444: }
445:
446: private ActualTrimDropTarget dropTarget;
447:
448: private TrimLayout layout;
449: private Composite windowComposite;
450:
451: /**
452: * Create a new drop target capable of accepting IWindowTrim items
453: *
454: * @param someComposite The control owning the TrimLayout
455: * @param theWindow the workbenchWindow
456: */
457: public TrimDropTarget(Composite someComposite,
458: WorkbenchWindow theWindow) {
459: layout = (TrimLayout) someComposite.getLayout();
460: windowComposite = someComposite;
461:
462: // Create an instance of a drop target to use
463: dropTarget = new ActualTrimDropTarget();
464: }
465:
466: /* (non-Javadoc)
467: * @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)
468: */
469: public IDropTarget drag(Control currentControl,
470: Object draggedObject, Point position,
471: final Rectangle dragRectangle) {
472:
473: // Have to be dragging trim
474: if (!(draggedObject instanceof IWindowTrim)) {
475: return null;
476: }
477:
478: // OK, we're dragging trim. is it from -this- shell?
479: IWindowTrim trim = (IWindowTrim) draggedObject;
480: if (trim.getControl().getShell() != windowComposite.getShell()) {
481: return null;
482: }
483:
484: // If this is the -first- drag then inform the drop target
485: if (dropTarget.draggedTrim == null) {
486: dropTarget.startDrag(trim);
487: }
488:
489: // Forward on to the 'actual' drop target for feedback
490: dropTarget.track(position);
491:
492: // Spin the paint loop after every track
493: windowComposite.getDisplay().update();
494:
495: return dropTarget;
496: }
497: }
|