API 0.9.5
AppKit/CPDragServer.j
Go to the documentation of this file.
00001 /*
00002  * CPDragServer.j
00003  * AppKit
00004  *
00005  * Created by Francisco Tolmasky.
00006  * Copyright 2008, 280 North, Inc.
00007  *
00008  * This library is free software; you can redistribute it and/or
00009  * modify it under the terms of the GNU Lesser General Public
00010  * License as published by the Free Software Foundation; either
00011  * version 2.1 of the License, or (at your option) any later version.
00012  *
00013  * This library is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00016  * Lesser General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU Lesser General Public
00019  * License along with this library; if not, write to the Free Software
00020  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
00021  */
00022 
00023 
00024 
00025 CPDragOperationNone     = 0;
00026 CPDragOperationCopy     = 1 << 1;
00027 CPDragOperationLink     = 1 << 1;
00028 CPDragOperationGeneric  = 1 << 2;
00029 CPDragOperationPrivate  = 1 << 3;
00030 CPDragOperationMove     = 1 << 4;
00031 CPDragOperationDelete   = 1 << 5;
00032 CPDragOperationEvery    = -1;
00033 
00034 #define DRAGGING_WINDOW(anObject) ([anObject isKindOfClass:[CPWindow class]] ? anObject : [anObject window])
00035 
00036 var CPDragServerPreviousEvent = nil,
00037     CPDragServerPeriodicUpdateInterval = 0.05;
00038 
00039 var CPSharedDragServer = nil;
00040 
00041 var CPDragServerSource             = nil,
00042     CPDragServerDraggingInfo       = nil;
00043 
00044 /*
00045     CPDraggingInfo is a container of information about a specific dragging session.
00046     @ignore
00047 */
00048 @implementation CPDraggingInfo : CPObject
00049 {
00050     id __doxygen__;
00051 }
00052 
00053 - (CPPasteboard)draggingPasteboard
00054 {
00055     if ([CPPlatform supportsDragAndDrop])
00056         return [_CPDOMDataTransferPasteboard DOMDataTransferPasteboard];
00057 
00058     return [[CPDragServer sharedDragServer] draggingPasteboard];
00059 }
00060 
00061 - (id)draggingSource
00062 {
00063     return [[CPDragServer sharedDragServer] draggingSource];
00064 }
00065 
00066 /*
00067 - (unsigned)draggingSourceOperationMask
00068 */
00069 
00070 - (CPPoint)draggingLocation
00071 {
00072     return [[CPDragServer sharedDragServer] draggingLocation];
00073 }
00074 
00075 - (CPWindow)draggingDestinationWindow
00076 {
00077     return DRAGGING_WINDOW([[CPDragServer sharedDragServer] draggingDestination]);
00078 }
00079 
00080 - (CPImage)draggedImage
00081 {
00082     return [[self draggedView] image];
00083 }
00084 
00085 - (CGPoint)draggedImageLocation
00086 {
00087     return [self draggedViewLocation];
00088 }
00089 
00090 - (CPView)draggedView
00091 {
00092     return [[CPDragServer sharedDragServer] draggedView];
00093 }
00094 
00095 - (CGPoint)draggedViewLocation
00096 {
00097     var dragServer = [CPDragServer sharedDragServer];
00098 
00099     return [DRAGGING_WINDOW([dragServer draggingDestination]) convertPlatformWindowToBase:[[dragServer draggedView] frame].origin];
00100 }
00101 
00102 @end
00103 
00104 var CPDraggingSource_draggedImage_movedTo_          = 1 << 0,
00105     CPDraggingSource_draggedImage_endedAt_operation_  = 1 << 1,
00106     CPDraggingSource_draggedView_movedTo_           = 1 << 2,
00107     CPDraggingSource_draggedView_endedAt_operation_ = 1 << 3;
00108 
00109 @implementation CPDragServer : CPObject
00110 {
00111     BOOL            _isDragging;
00112 
00113     CPWindow        _draggedWindow;
00114     CPView          _draggedView;
00115     CPImageView     _imageView;
00116 
00117     BOOL            _isDraggingImage;
00118 
00119     CGSize          _draggingOffset;
00120 
00121     CPPasteboard    _draggingPasteboard;
00122 
00123     id              _draggingSource;
00124     unsigned        _implementedDraggingSourceMethods;
00125 
00126     CGPoint         _draggingLocation;
00127     id              _draggingDestination;
00128     BOOL            _draggingDestinationWantsPeriodicUpdates;
00129 
00130     CGPoint         _startDragLocation;
00131     BOOL            _shouldSlideBack;
00132     unsigned        _dragOperation;
00133 
00134     CPTimer         _draggingUpdateTimer;
00135 }
00136 
00137 /*
00138     Private Objective-J/Cappuccino method
00139     @ignore
00140 */
00141 + (void)initialize
00142 {
00143     if (self !== [CPDragServer class])
00144         return;
00145 
00146     CPDragServerDraggingInfo = [[CPDraggingInfo alloc] init];
00147 }
00148 
00149 + (CPDragServer)sharedDragServer
00150 {
00151     if (!CPSharedDragServer)
00152         CPSharedDragServer = [[CPDragServer alloc] init];
00153 
00154     return CPSharedDragServer;
00155 }
00156 
00157 /*
00158     @ignore
00159 */
00160 - (id)init
00161 {
00162     self = [super init];
00163 
00164     if (self)
00165     {
00166         _draggedWindow = [[CPWindow alloc] initWithContentRect:_CGRectMakeZero() styleMask:CPBorderlessWindowMask];
00167 
00168         [_draggedWindow setLevel:CPDraggingWindowLevel];
00169     }
00170 
00171     return self;
00172 }
00173 
00174 - (id)draggingDestination
00175 {
00176     return _draggingDestination;
00177 }
00178 
00179 - (CGPoint)draggingLocation
00180 {
00181     return _draggingLocation
00182 }
00183 
00184 - (void)draggingStartedInPlatformWindow:(CPPlatformWindow)aPlatformWindow globalLocation:(CGPoint)aLocation
00185 {
00186     if (_isDraggingImage)
00187     {
00188         if ([_draggingSource respondsToSelector:@selector(draggedImage:beganAt:)])
00189             [_draggingSource draggedImage:[_draggedView image] beganAt:aLocation];
00190     }
00191     else
00192     {
00193         if ([_draggingSource respondsToSelector:@selector(draggedView:beganAt:)])
00194             [_draggingSource draggedView:_draggedView beganAt:aLocation];
00195     }
00196 
00197     if (![CPPlatform supportsDragAndDrop])
00198         [_draggedWindow orderFront:self];
00199 }
00200 
00201 - (void)draggingSourceUpdatedWithGlobalLocation:(CGPoint)aGlobalLocation
00202 {
00203     if (![CPPlatform supportsDragAndDrop])
00204         [_draggedWindow setFrameOrigin:_CGPointMake(aGlobalLocation.x - _draggingOffset.width, aGlobalLocation.y - _draggingOffset.height)];
00205 
00206     if (_implementedDraggingSourceMethods & CPDraggingSource_draggedImage_movedTo_)
00207         [_draggingSource draggedImage:[_draggedView image] movedTo:aGlobalLocation];
00208 
00209     else if (_implementedDraggingSourceMethods & CPDraggingSource_draggedView_movedTo_)
00210         [_draggingSource draggedView:_draggedView movedTo:aGlobalLocation];
00211 }
00212 
00213 - (CPDragOperation)draggingUpdatedInPlatformWindow:(CPPlatformWindow)aPlatformWindow location:(CGPoint)aLocation
00214 {
00215     [_draggingUpdateTimer invalidate];
00216     _draggingUpdateTimer = nil;
00217 
00218     var dragOperation = CPDragOperationCopy,
00219     // We have to convert base to bridge since the drag event comes from the source window, not the drag window.
00220         draggingDestination = [aPlatformWindow _dragHitTest:aLocation pasteboard:[CPDragServerDraggingInfo draggingPasteboard]];
00221 
00222     if (draggingDestination)
00223         _draggingLocation = [DRAGGING_WINDOW(draggingDestination) convertPlatformWindowToBase:aLocation];
00224 
00225     if (draggingDestination !== _draggingDestination)
00226     {
00227         if ([_draggingDestination respondsToSelector:@selector(draggingExited:)])
00228             [_draggingDestination draggingExited:CPDragServerDraggingInfo];
00229 
00230         _draggingDestination = draggingDestination;
00231 
00232         if ([_draggingDestination respondsToSelector:@selector(wantsPeriodicDraggingUpdates)])
00233             _draggingDestinationWantsPeriodicUpdates = [_draggingDestination wantsPeriodicDraggingUpdates];
00234         else
00235             _draggingDestinationWantsPeriodicUpdates = YES;
00236 
00237         if ([_draggingDestination respondsToSelector:@selector(draggingEntered:)])
00238             dragOperation = [_draggingDestination draggingEntered:CPDragServerDraggingInfo];
00239     }
00240     else if ([_draggingDestination respondsToSelector:@selector(draggingUpdated:)])
00241         dragOperation = [_draggingDestination draggingUpdated:CPDragServerDraggingInfo];
00242 
00243     if (!_draggingDestination)
00244         dragOperation = CPDragOperationNone;
00245     else
00246     {
00247         if (_draggingDestinationWantsPeriodicUpdates)
00248             _draggingUpdateTimer = [CPTimer scheduledTimerWithTimeInterval:CPDragServerPeriodicUpdateInterval
00249                                                                     target:self
00250                                                                   selector:@selector(_sendPeriodicDraggingUpdate:)
00251                                                                   userInfo:[CPDictionary dictionaryWithJSObject:{platformWindow:aPlatformWindow, location:aLocation}]
00252                                                                    repeats:NO];
00253 
00254         var scrollView = [_draggingDestination isKindOfClass:[CPView class]] ? [_draggingDestination enclosingScrollView] : nil;
00255         if (scrollView)
00256         {
00257             var contentView = [scrollView contentView],
00258                 bounds = [contentView bounds],
00259                 insetBounds = CGRectInset(bounds, 30, 30),
00260                 eventLocation = [contentView convertPoint:_draggingLocation fromView:nil],
00261                 deltaX = 0,
00262                 deltaY = 0;
00263 
00264             if (!CGRectContainsPoint(insetBounds, eventLocation))
00265             {
00266                 if ([scrollView hasVerticalScroller])
00267                 {
00268                     if (eventLocation.y < _CGRectGetMinY(insetBounds))
00269                         deltaY = _CGRectGetMinY(insetBounds) - eventLocation.y;
00270                     else if (eventLocation.y > _CGRectGetMaxY(insetBounds))
00271                         deltaY = _CGRectGetMaxY(insetBounds) - eventLocation.y;
00272                     if (deltaY < -insetBounds.size.height)
00273                         deltaY = -insetBounds.size.height;
00274                     if (deltaY > insetBounds.size.height)
00275                         deltaY = insetBounds.size.height;
00276                 }
00277 
00278                 if ([scrollView hasHorizontalScroller])
00279                 {
00280                     if (eventLocation.x < _CGRectGetMinX(insetBounds))
00281                         deltaX = _CGRectGetMinX(insetBounds) - eventLocation.x;
00282                     else if (eventLocation.x > _CGRectGetMaxX(insetBounds))
00283                         deltaX = _CGRectGetMaxX(insetBounds) - eventLocation.x;
00284                     if (deltaX < -insetBounds.size.width)
00285                         deltaX = -insetBounds.size.width;
00286                     if (deltaX > insetBounds.size.width)
00287                         deltaX = insetBounds.size.width;
00288                 }
00289 
00290                 var scrollPoint = _CGPointMake(bounds.origin.x - deltaX, bounds.origin.y - deltaY);
00291 
00292                 [contentView scrollToPoint:scrollPoint];
00293                 [[scrollView _headerView] scrollPoint:scrollPoint];
00294 
00295             }
00296         }
00297     }
00298 
00299     return dragOperation;
00300 }
00301 
00302 - (void)_sendPeriodicDraggingUpdate:(CPTimer)aTimer
00303 {
00304     var userInfo = [aTimer userInfo];
00305     _dragOperation = [self draggingUpdatedInPlatformWindow:[userInfo objectForKey:@"platformWindow"]
00306                                                   location:[userInfo objectForKey:@"location"]];
00307 }
00308 
00309 - (void)draggingEndedInPlatformWindow:(CPPlatformWindow)aPlatformWindow globalLocation:(CGPoint)aLocation operation:(CPDragOperation)anOperation
00310 {
00311     [_draggingUpdateTimer invalidate];
00312     _draggingUpdateTimer = nil;
00313 
00314     [_draggedView removeFromSuperview];
00315 
00316     if (![CPPlatform supportsDragAndDrop])
00317         [_draggedWindow orderOut:self];
00318 
00319     if (_implementedDraggingSourceMethods & CPDraggingSource_draggedImage_endedAt_operation_)
00320         [_draggingSource draggedImage:[_draggedView image] endedAt:aLocation operation:anOperation];
00321     else if (_implementedDraggingSourceMethods & CPDraggingSource_draggedView_endedAt_operation_)
00322         [_draggingSource draggedView:_draggedView endedAt:aLocation operation:anOperation];
00323 
00324     _isDragging = NO;
00325 }
00326 
00327 - (void)performDragOperationInPlatformWindow:(CPPlatformWindow)aPlatformWindow
00328 {
00329     if (_draggingDestination &&
00330         (![_draggingDestination respondsToSelector:@selector(prepareForDragOperation:)] || [_draggingDestination prepareForDragOperation:CPDragServerDraggingInfo]) &&
00331         (![_draggingDestination respondsToSelector:@selector(performDragOperation:)] || [_draggingDestination performDragOperation:CPDragServerDraggingInfo]) &&
00332         [_draggingDestination respondsToSelector:@selector(concludeDragOperation:)])
00333         [_draggingDestination concludeDragOperation:CPDragServerDraggingInfo];
00334 }
00335 
00348 - (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
00349 {
00350     _isDragging = YES;
00351 
00352     _draggedView = aView;
00353     _draggingPasteboard = aPasteboard || [CPPasteboard pasteboardWithName:CPDragPboard];
00354     _draggingSource = aSourceObject;
00355     _draggingDestination = nil;
00356     _shouldSlideBack = slideBack;
00357 
00358     // The offset is based on the distance from where we want the view to be initially from where the mouse is initially
00359     // Hence the use of mouseDownEvent's location and view's location in global coordinates.
00360     var mouseDownWindow = [mouseDownEvent window],
00361         mouseDownEventLocation = [mouseDownEvent locationInWindow];
00362 
00363     if (mouseDownEventLocation)
00364     {
00365         if (mouseDownWindow)
00366             mouseDownEventLocation = [mouseDownWindow convertBaseToGlobal:mouseDownEventLocation];
00367 
00368         _draggingOffset = _CGSizeMake(mouseDownEventLocation.x - viewLocation.x, mouseDownEventLocation.y - viewLocation.y);
00369     }
00370     else
00371         _draggingOffset = _CGSizeMakeZero();
00372 
00373     if ([CPPlatform isBrowser])
00374         [_draggedWindow setPlatformWindow:[aWindow platformWindow]];
00375 
00376     [aView setFrameOrigin:_CGPointMakeZero()];
00377 
00378     var mouseLocation = [CPEvent mouseLocation];
00379 
00380     // Place it where the mouse pointer is.
00381     _startDragLocation = _CGPointMake(mouseLocation.x - _draggingOffset.width, mouseLocation.y - _draggingOffset.height);
00382     [_draggedWindow setFrameOrigin:_startDragLocation];
00383     [_draggedWindow setFrameSize:[aView frame].size];
00384 
00385     [[_draggedWindow contentView] addSubview:aView];
00386 
00387     _implementedDraggingSourceMethods = 0;
00388 
00389     if (_draggedView === _imageView)
00390     {
00391         if ([_draggingSource respondsToSelector:@selector(draggedImage:movedTo:)])
00392             _implementedDraggingSourceMethods |= CPDraggingSource_draggedImage_movedTo_;
00393 
00394         if ([_draggingSource respondsToSelector:@selector(draggedImage:endedAt:operation:)])
00395             _implementedDraggingSourceMethods |= CPDraggingSource_draggedImage_endedAt_operation_;
00396     }
00397     else
00398     {
00399         if ([_draggingSource respondsToSelector:@selector(draggedView:movedTo:)])
00400             _implementedDraggingSourceMethods |= CPDraggingSource_draggedView_movedTo_;
00401 
00402         if ([_draggingSource respondsToSelector:@selector(draggedView:endedAt:operation:)])
00403             _implementedDraggingSourceMethods |= CPDraggingSource_draggedView_endedAt_operation_;
00404     }
00405 
00406     if (![CPPlatform supportsDragAndDrop])
00407     {
00408         [self draggingStartedInPlatformWindow:[aWindow platformWindow] globalLocation:mouseLocation];
00409         [self trackDragging:mouseDownEvent];
00410     }
00411 }
00412 
00425 - (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
00426 {
00427     _isDraggingImage = YES;
00428 
00429     var imageSize = [anImage size];
00430 
00431     if (!_imageView)
00432         _imageView = [[CPImageView alloc] initWithFrame:_CGRectMake(0.0, 0.0, imageSize.width, imageSize.height)];
00433 
00434     [_imageView setImage:anImage];
00435 
00436     [self dragView:_imageView fromWindow:aWindow at:imageLocation offset:mouseOffset event:anEvent pasteboard:aPasteboard source:aSourceObject slideBack:slideBack];
00437 }
00438 
00439 - (void)trackDragging:(CPEvent)anEvent
00440 {
00441     var type = [anEvent type],
00442         platformWindow = [_draggedWindow platformWindow],
00443         platformWindowLocation = [[anEvent window] convertBaseToPlatformWindow:[anEvent locationInWindow]];
00444 
00445     if (type === CPLeftMouseUp)
00446     {
00447         // Make sure we do not finalize (cancel) the drag if the last drag update was disallowed
00448         if (_dragOperation !== CPDragOperationNone)
00449             [self performDragOperationInPlatformWindow:platformWindow];
00450 
00451         [self draggingEndedInPlatformWindow:platformWindow globalLocation:platformWindowLocation operation:_dragOperation];
00452 
00453         // Stop tracking events.
00454         return;
00455     }
00456     else if (type === CPKeyDown)
00457     {
00458         var characters = [anEvent characters];
00459         if (characters === CPEscapeFunctionKey)
00460         {
00461             _dragOperation = CPDragOperationNone;
00462             [self draggingEndedInPlatformWindow:platformWindow globalLocation:CGPointMakeZero() operation:_dragOperation];
00463             return;
00464         }
00465     }
00466     else
00467     {
00468         [self draggingSourceUpdatedWithGlobalLocation:platformWindowLocation];
00469         _dragOperation = [self draggingUpdatedInPlatformWindow:platformWindow location:platformWindowLocation];
00470     }
00471 
00472     // If we're not a mouse up, then we're going to want to grab the next event.
00473     [CPApp setTarget:self selector:@selector(trackDragging:)
00474         forNextEventMatchingMask:CPMouseMovedMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask | CPKeyDownMask
00475         untilDate:nil inMode:0 dequeue:NO];
00476 }
00477 
00478 @end
00479 
00480 @implementation CPWindow (CPDraggingAdditions)
00481 
00482 /* @ignore */
00483 - (id)_dragHitTest:(CGPoint)aPoint pasteboard:(CPPasteboard)aPasteboard
00484 {
00485     // If none of our views or ourselves has registered for drag events...
00486     if (!_inclusiveRegisteredDraggedTypes)
00487         return nil;
00488 
00489 // We don't need to do this because the only place this gets called
00490 // -_dragHitTest: in CPPlatformWindow does this already. Perhaps to
00491 // be safe?
00492 //    if (![self containsPoint:aPoint])
00493 //        return nil;
00494 
00495     var adjustedPoint = [self convertPlatformWindowToBase:aPoint],
00496         hitView = [_windowView hitTest:adjustedPoint];
00497 
00498     while (hitView && ![aPasteboard availableTypeFromArray:[hitView registeredDraggedTypes]])
00499         hitView = [hitView superview];
00500 
00501     if (hitView)
00502         return hitView;
00503 
00504     if ([aPasteboard availableTypeFromArray:[self registeredDraggedTypes]])
00505         return self;
00506 
00507     return nil;
00508 }
00509 
00510 @end
00511 
00512 @implementation CPDragServer (CPSynthesizedAccessors)
00513 
00517 - (BOOL)isDragging
00518 {
00519     return _isDragging;
00520 }
00521 
00525 - (CPWindow)draggedWindow
00526 {
00527     return _draggedWindow;
00528 }
00529 
00533 - (CPView)draggedView
00534 {
00535     return _draggedView;
00536 }
00537 
00541 - (CGSize)draggingOffset
00542 {
00543     return _draggingOffset;
00544 }
00545 
00549 - (CPPasteboard)draggingPasteboard
00550 {
00551     return _draggingPasteboard;
00552 }
00553 
00557 - (id)draggingSource
00558 {
00559     return _draggingSource;
00560 }
00561 
00562 @end
 All Classes Files Functions Variables Defines