API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPDragServer.j
Go to the documentation of this file.
1 /*
2  * CPDragServer.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 @class CPWindow // This file is imported by CPWindow.j
25 @class _CPDOMDataTransferPasteboard
26 
27 @global CPApp
28 
29 #define DRAGGING_WINDOW(anObject) ([anObject isKindOfClass:[CPWindow class]] ? anObject : [anObject window])
30 
31 var CPDragServerPreviousEvent = nil,
32  CPDragServerPeriodicUpdateInterval = 0.05;
33 
34 var CPSharedDragServer = nil;
35 
36 var CPDragServerSource = nil,
37  CPDragServerDraggingInfo = nil;
38 
39 /*
40  CPDraggingInfo is a container of information about a specific dragging session.
41  @ignore
42 */
43 @implementation CPDraggingInfo : CPObject
44 {
45  id __doxygen__;
46 }
47 
48 - (CPPasteboard)draggingPasteboard
49 {
50  if ([CPPlatform supportsDragAndDrop])
51  return [_CPDOMDataTransferPasteboard DOMDataTransferPasteboard];
52 
53  return [[CPDragServer sharedDragServer] draggingPasteboard];
54 }
55 
56 - (id)draggingSource
57 {
58  return [[CPDragServer sharedDragServer] draggingSource];
59 }
60 
61 /*
62 - (unsigned)draggingSourceOperationMask
63 */
64 
65 - (CGPoint)draggingLocation
66 {
67  return [[CPDragServer sharedDragServer] draggingLocation];
68 }
69 
70 - (CPWindow)draggingDestinationWindow
71 {
72  return DRAGGING_WINDOW([[CPDragServer sharedDragServer] draggingDestination]);
73 }
74 
75 - (CPImage)draggedImage
76 {
77  return [[self draggedView] image];
78 }
79 
80 - (CGPoint)draggedImageLocation
81 {
82  return [self draggedViewLocation];
83 }
84 
85 - (CPView)draggedView
86 {
87  return [[CPDragServer sharedDragServer] draggedView];
88 }
89 
90 - (CGPoint)draggedViewLocation
91 {
92  var dragServer = [CPDragServer sharedDragServer];
93 
94  return [DRAGGING_WINDOW([dragServer draggingDestination]) convertPlatformWindowToBase:[[dragServer draggedView] frame].origin];
95 }
96 
97 @end
98 
99 var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
100  CPDraggingSource_draggedImage_endedAt_operation_ = 1 << 1,
101  CPDraggingSource_draggedView_movedTo_ = 1 << 2,
102  CPDraggingSource_draggedView_endedAt_operation_ = 1 << 3;
103 
104 @implementation CPDragServer : CPObject
105 {
106  BOOL _isDragging;
107 
108  CPWindow _draggedWindow;
109  CPView _draggedView;
110  CPImageView _imageView;
111 
112  BOOL _isDraggingImage;
113 
114  CGSize _draggingOffset;
115 
116  CPPasteboard _draggingPasteboard;
117 
118  id _draggingSource;
119  unsigned _implementedDraggingSourceMethods;
120 
121  CGPoint _draggingLocation;
122  id _draggingDestination;
123  BOOL _draggingDestinationWantsPeriodicUpdates;
124 
125  CGPoint _startDragLocation;
126  BOOL _shouldSlideBack;
127  unsigned _dragOperation;
128 
129  CPTimer _draggingUpdateTimer;
130 }
131 
132 /*
133  Private Objective-J/Cappuccino method
134  @ignore
135 */
136 + (void)initialize
137 {
138  if (self !== [CPDragServer class])
139  return;
140 
141  CPDragServerDraggingInfo = [[CPDraggingInfo alloc] init];
142 }
143 
144 + (CPDragServer)sharedDragServer
145 {
146  if (!CPSharedDragServer)
147  CPSharedDragServer = [[CPDragServer alloc] init];
148 
149  return CPSharedDragServer;
150 }
151 
152 /*
153  @ignore
154 */
155 - (id)init
156 {
157  self = [super init];
158 
159  if (self)
160  {
161  _draggedWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessWindowMask];
162 
163  [_draggedWindow setLevel:CPDraggingWindowLevel];
164  }
165 
166  return self;
167 }
168 
169 - (id)draggingDestination
170 {
171  return _draggingDestination;
172 }
173 
174 - (CGPoint)draggingLocation
175 {
176  return _draggingLocation
177 }
178 
179 - (void)draggingStartedInPlatformWindow:(CPPlatformWindow)aPlatformWindow globalLocation:(CGPoint)aLocation
180 {
181  if (_isDraggingImage)
182  {
183  if ([_draggingSource respondsToSelector:@selector(draggedImage:beganAt:)])
184  [_draggingSource draggedImage:[_draggedView image] beganAt:aLocation];
185  }
186  else
187  {
188  if ([_draggingSource respondsToSelector:@selector(draggedView:beganAt:)])
189  [_draggingSource draggedView:_draggedView beganAt:aLocation];
190  }
191 
192  if (![CPPlatform supportsDragAndDrop])
193  [_draggedWindow orderFront:self];
194 }
195 
196 - (void)draggingSourceUpdatedWithGlobalLocation:(CGPoint)aGlobalLocation
197 {
198  if (![CPPlatform supportsDragAndDrop])
199  {
200  var frame = [_draggedWindow frame];
201  frame.origin.x = aGlobalLocation.x - _draggingOffset.width;
202  frame.origin.y = aGlobalLocation.y - _draggingOffset.height;
203  [_draggedWindow _setFrame:frame display:YES animate:NO constrainWidth:NO constrainHeight:NO];
204  }
205 
206  if (_implementedDraggingSourceMethods & CPDraggingSource_draggedImage_movedTo_)
207  [_draggingSource draggedImage:[_draggedView image] movedTo:aGlobalLocation];
208 
209  else if (_implementedDraggingSourceMethods & CPDraggingSource_draggedView_movedTo_)
210  [_draggingSource draggedView:_draggedView movedTo:aGlobalLocation];
211 }
212 
213 - (CPDragOperation)draggingUpdatedInPlatformWindow:(CPPlatformWindow)aPlatformWindow location:(CGPoint)aLocation
214 {
215  [_draggingUpdateTimer invalidate];
216  _draggingUpdateTimer = nil;
217 
218  var dragOperation = CPDragOperationCopy,
219  // We have to convert base to bridge since the drag event comes from the source window, not the drag window.
220  draggingDestination = [aPlatformWindow _dragHitTest:aLocation pasteboard:[CPDragServerDraggingInfo draggingPasteboard]];
221 
222  if (draggingDestination)
223  _draggingLocation = [DRAGGING_WINDOW(draggingDestination) convertPlatformWindowToBase:aLocation];
224 
225  if (draggingDestination !== _draggingDestination)
226  {
227  if ([_draggingDestination respondsToSelector:@selector(draggingExited:)])
228  [_draggingDestination draggingExited:CPDragServerDraggingInfo];
229 
230  _draggingDestination = draggingDestination;
231 
232  if ([_draggingDestination respondsToSelector:@selector(wantsPeriodicDraggingUpdates)])
233  _draggingDestinationWantsPeriodicUpdates = [_draggingDestination wantsPeriodicDraggingUpdates];
234  else
235  _draggingDestinationWantsPeriodicUpdates = YES;
236 
237  if ([_draggingDestination respondsToSelector:@selector(draggingEntered:)])
238  dragOperation = [_draggingDestination draggingEntered:CPDragServerDraggingInfo];
239  }
240  else if ([_draggingDestination respondsToSelector:@selector(draggingUpdated:)])
241  dragOperation = [_draggingDestination draggingUpdated:CPDragServerDraggingInfo];
242 
243  if (!_draggingDestination)
244  dragOperation = CPDragOperationNone;
245  else
246  {
247  if (_draggingDestinationWantsPeriodicUpdates)
248  _draggingUpdateTimer = [CPTimer scheduledTimerWithTimeInterval:CPDragServerPeriodicUpdateInterval
249  target:self
251  userInfo:@{ "platformWindow":aPlatformWindow, "location":aLocation }
252  repeats:NO];
253 
254  var scrollView = [_draggingDestination isKindOfClass:[CPView class]] ? [_draggingDestination enclosingScrollView] : nil;
255  if (scrollView)
256  {
257  var contentView = [scrollView contentView],
258  bounds = [contentView bounds],
259  insetBounds = CGRectInset(bounds, 30, 30),
260  eventLocation = [contentView convertPoint:_draggingLocation fromView:nil],
261  deltaX = 0,
262  deltaY = 0;
263 
264  if (!CGRectContainsPoint(insetBounds, eventLocation))
265  {
266  if ([scrollView hasVerticalScroller])
267  {
268  if (eventLocation.y < CGRectGetMinY(insetBounds))
269  deltaY = CGRectGetMinY(insetBounds) - eventLocation.y;
270  else if (eventLocation.y > CGRectGetMaxY(insetBounds))
271  deltaY = CGRectGetMaxY(insetBounds) - eventLocation.y;
272  if (deltaY < -insetBounds.size.height)
273  deltaY = -insetBounds.size.height;
274  if (deltaY > insetBounds.size.height)
275  deltaY = insetBounds.size.height;
276  }
277 
278  if ([scrollView hasHorizontalScroller])
279  {
280  if (eventLocation.x < CGRectGetMinX(insetBounds))
281  deltaX = CGRectGetMinX(insetBounds) - eventLocation.x;
282  else if (eventLocation.x > CGRectGetMaxX(insetBounds))
283  deltaX = CGRectGetMaxX(insetBounds) - eventLocation.x;
284  if (deltaX < -insetBounds.size.width)
285  deltaX = -insetBounds.size.width;
286  if (deltaX > insetBounds.size.width)
287  deltaX = insetBounds.size.width;
288  }
289 
290  var scrollPoint = CGPointMake(bounds.origin.x - deltaX, bounds.origin.y - deltaY);
291 
292  [contentView scrollToPoint:scrollPoint];
293  [[scrollView _headerView] scrollPoint:scrollPoint];
294 
295  }
296  }
297  }
298 
299  return dragOperation;
300 }
301 
302 - (void)_sendPeriodicDraggingUpdate:(CPTimer)aTimer
303 {
304  var userInfo = [aTimer userInfo];
305  _dragOperation = [self draggingUpdatedInPlatformWindow:[userInfo objectForKey:@"platformWindow"]
306  location:[userInfo objectForKey:@"location"]];
307 }
308 
309 - (void)draggingEndedInPlatformWindow:(CPPlatformWindow)aPlatformWindow globalLocation:(CGPoint)aLocation operation:(CPDragOperation)anOperation
310 {
311  [_draggingUpdateTimer invalidate];
312  _draggingUpdateTimer = nil;
313 
314  [_draggedView removeFromSuperview];
315 
316  if (![CPPlatform supportsDragAndDrop])
317  [_draggedWindow orderOut:self];
318 
319  if (_implementedDraggingSourceMethods & CPDraggingSource_draggedImage_endedAt_operation_)
320  [_draggingSource draggedImage:[_draggedView image] endedAt:aLocation operation:anOperation];
321  else if (_implementedDraggingSourceMethods & CPDraggingSource_draggedView_endedAt_operation_)
322  [_draggingSource draggedView:_draggedView endedAt:aLocation operation:anOperation];
323 
324  _isDragging = NO;
325 }
326 
327 - (void)performDragOperationInPlatformWindow:(CPPlatformWindow)aPlatformWindow
328 {
329  if (_draggingDestination &&
330  (![_draggingDestination respondsToSelector:@selector(prepareForDragOperation:)] || [_draggingDestination prepareForDragOperation:CPDragServerDraggingInfo]) &&
331  (![_draggingDestination respondsToSelector:@selector(performDragOperation:)] || [_draggingDestination performDragOperation:CPDragServerDraggingInfo]) &&
332  [_draggingDestination respondsToSelector:@selector(concludeDragOperation:)])
333  [_draggingDestination concludeDragOperation:CPDragServerDraggingInfo];
334 }
335 
348 - (void)dragView:(CPView)aView fromWindow:(CPWindow)aWindow at:(CGPoint)viewLocation offset:(CGSize)mouseOffset event:(CPEvent)mouseDownEvent pasteboard:(CPPasteboard)aPasteboard source:(id)aSourceObject slideBack:(BOOL)slideBack
349 {
350  _isDragging = YES;
351 
352  _draggedView = aView;
353  _draggingPasteboard = aPasteboard || [CPPasteboard pasteboardWithName:CPDragPboard];
354  _draggingSource = aSourceObject;
355  _draggingDestination = nil;
356  _shouldSlideBack = slideBack;
357 
358  // The offset is based on the distance from where we want the view to be initially from where the mouse is initially
359  // Hence the use of mouseDownEvent's location and view's location in global coordinates.
360  var mouseDownWindow = [mouseDownEvent window],
361  mouseDownEventLocation = [mouseDownEvent locationInWindow];
362 
363  if (mouseDownEventLocation)
364  {
365  if (mouseDownWindow)
366  mouseDownEventLocation = [mouseDownWindow convertBaseToGlobal:mouseDownEventLocation];
367 
368  _draggingOffset = CGSizeMake(mouseDownEventLocation.x - viewLocation.x, mouseDownEventLocation.y - viewLocation.y);
369  }
370  else
371  _draggingOffset = CGSizeMakeZero();
372 
373  if ([CPPlatform isBrowser])
374  [_draggedWindow setPlatformWindow:[aWindow platformWindow]];
375 
376  [aView setFrameOrigin:CGPointMakeZero()];
377 
378  var mouseLocation = [CPEvent mouseLocation],
379  viewSize = [aView frameSize],
380  startDragLocationX = mouseLocation.x - _draggingOffset.width,
381  startDragLocationY = mouseLocation.y - _draggingOffset.height,
382  draggedWindowFrame = CGRectMake(startDragLocationX, startDragLocationY, viewSize.width, viewSize.height);
383 
384  // Place it where the mouse pointer is.
385  _startDragLocation = CGPointMake(startDragLocationX, startDragLocationY);
386 
387  [_draggedWindow _setFrame:draggedWindowFrame display:YES animate:NO constrainWidth:NO constrainHeight:NO];
388 
389  [[_draggedWindow contentView] addSubview:aView];
390 
391  _implementedDraggingSourceMethods = 0;
392 
393  if (_draggedView === _imageView)
394  {
395  if ([_draggingSource respondsToSelector:@selector(draggedImage:movedTo:)])
396  _implementedDraggingSourceMethods |= CPDraggingSource_draggedImage_movedTo_;
397 
398  if ([_draggingSource respondsToSelector:@selector(draggedImage:endedAt:operation:)])
399  _implementedDraggingSourceMethods |= CPDraggingSource_draggedImage_endedAt_operation_;
400  }
401  else
402  {
403  if ([_draggingSource respondsToSelector:@selector(draggedView:movedTo:)])
404  _implementedDraggingSourceMethods |= CPDraggingSource_draggedView_movedTo_;
405 
406  if ([_draggingSource respondsToSelector:@selector(draggedView:endedAt:operation:)])
407  _implementedDraggingSourceMethods |= CPDraggingSource_draggedView_endedAt_operation_;
408  }
409 
410  if (![CPPlatform supportsDragAndDrop])
411  {
412  [self draggingStartedInPlatformWindow:[aWindow platformWindow] globalLocation:mouseLocation];
413  [self trackDragging:mouseDownEvent];
414  }
415 }
416 
429 - (void)dragImage:(CPImage)anImage fromWindow:(CPWindow)aWindow at:(CGPoint)imageLocation offset:(CGSize)mouseOffset event:(CPEvent)anEvent pasteboard:(CPPasteboard)aPasteboard source:(id)aSourceObject slideBack:(BOOL)slideBack
430 {
431  _isDraggingImage = YES;
432 
433  var imageSize = [anImage size];
434 
435  if (!_imageView)
436  _imageView = [[CPImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, imageSize.width, imageSize.height)];
437 
438  [_imageView setImage:anImage];
439 
440  [self dragView:_imageView fromWindow:aWindow at:imageLocation offset:mouseOffset event:anEvent pasteboard:aPasteboard source:aSourceObject slideBack:slideBack];
441 }
442 
443 - (void)trackDragging:(CPEvent)anEvent
444 {
445  var type = [anEvent type],
446  platformWindow = [_draggedWindow platformWindow],
447  platformWindowLocation = [[anEvent window] convertBaseToPlatformWindow:[anEvent locationInWindow]];
448 
449  if (type === CPLeftMouseUp)
450  {
451  // Make sure we do not finalize (cancel) the drag if the last drag update was disallowed
452  if (_dragOperation !== CPDragOperationNone)
453  [self performDragOperationInPlatformWindow:platformWindow];
454 
455  [self draggingEndedInPlatformWindow:platformWindow globalLocation:platformWindowLocation operation:_dragOperation];
456 
457  // Stop tracking events.
458  return;
459  }
460  else if (type === CPKeyDown)
461  {
462  var characters = [anEvent characters];
463 
464  if (characters === CPEscapeFunctionKey)
465  {
466  _dragOperation = CPDragOperationNone;
467  [self draggingEndedInPlatformWindow:platformWindow globalLocation:CGPointMakeZero() operation:_dragOperation];
468  return;
469  }
470  }
471  else
472  {
473  [self draggingSourceUpdatedWithGlobalLocation:platformWindowLocation];
474  _dragOperation = [self draggingUpdatedInPlatformWindow:platformWindow location:platformWindowLocation];
475  }
476 
477  // If we're not a mouse up, then we're going to want to grab the next event.
478  [CPApp setTarget:self selector:@selector(trackDragging:)
479  forNextEventMatchingMask:CPMouseMovedMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask | CPKeyDownMask
480  untilDate:nil inMode:0 dequeue:YES];
481 }
482 
483 @end
484 
485 @implementation CPDragServer (CPSynthesizedAccessors)
486 
490 - (BOOL)isDragging
491 {
492  return _isDragging;
493 }
494 
498 - (CPWindow)draggedWindow
499 {
500  return _draggedWindow;
501 }
502 
506 - (CPView)draggedView
507 {
508  return _draggedView;
509 }
510 
514 - (CGSize)draggingOffset
515 {
516  return _draggingOffset;
517 }
518 
522 - (CPPasteboard)draggingPasteboard
523 {
524  return _draggingPasteboard;
525 }
526 
530 - (id)draggingSource
531 {
532  return _draggingSource;
533 }
534 
535 @end