Back to index

lightning-sunbird  0.9+nobinonly
nsDragHelperService.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either the GNU General Public License Version 2 or later (the "GPL"), or
00026  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #include "nsDragHelperService.h"
00039 
00040 #include "nsGUIEvent.h"
00041 #include "nsIDOMNode.h"
00042 #include "nsIDragSessionMac.h"
00043 #include "nsIServiceManager.h"
00044 
00045 #define kDragServiceContractID "@mozilla.org/widget/dragservice;1"
00046 
00047 
00048 NS_IMPL_ADDREF(nsDragHelperService)
00049 NS_IMPL_RELEASE(nsDragHelperService)
00050 NS_IMPL_QUERY_INTERFACE1(nsDragHelperService, nsIDragHelperService)
00051 
00052 
00053 //
00054 // nsDragHelperService constructor
00055 //
00056 nsDragHelperService::nsDragHelperService()
00057 : mDragOverTimer(nsnull)
00058 , mDragOverDragRef(nsnull)
00059 {
00060 }
00061 
00062 
00063 //
00064 // nsDragHelperService destructor
00065 //
00066 nsDragHelperService::~nsDragHelperService()
00067 {
00068   if (mDragOverTimer)
00069     ::RemoveEventLoopTimer(mDragOverTimer);
00070 
00071   NS_ASSERTION(!mDragService.get(),
00072                "A drag was not correctly ended by shutdown");
00073 }
00074 
00075 
00076 //
00077 // Enter
00078 //
00079 // Called when the mouse has entered the rectangle bounding the browser
00080 // during a drag. Cache the drag service so we don't have to fetch it
00081 // repeatedly, and handles setting up the drag reference and sending an
00082 // enter event.
00083 //
00084 NS_IMETHODIMP
00085 nsDragHelperService::Enter(DragReference inDragRef, nsIEventSink *inSink)
00086 {
00087   // get our drag service for the duration of the drag.
00088   mDragService = do_GetService(kDragServiceContractID);
00089   NS_ASSERTION(mDragService,
00090                "Couldn't get a drag service, we're in biiig trouble");
00091   if (!mDragService || !inSink)
00092     return NS_ERROR_FAILURE;
00093 
00094   // tell the session about this drag
00095   mDragService->StartDragSession();
00096   nsCOMPtr<nsIDragSessionMac> macSession(do_QueryInterface(mDragService));
00097   if (macSession)
00098     macSession->SetDragReference(inDragRef);
00099 
00100   DoDragAction(NS_DRAGDROP_ENTER, inDragRef, inSink);
00101 
00102   // Start sending regular NS_DRAGDROP_OVER events.
00103   SetDragOverTimer(inSink, inDragRef);
00104 
00105   return NS_OK;
00106 }
00107 
00108 
00109 //
00110 // Tracking
00111 //
00112 // Called while the mouse is moved inside the rectangle bounding the browser
00113 // during a drag. The important thing done here is to clear the |canDrop|
00114 // property of the drag session every time through. The event handlers
00115 // will reset it if appropriate.
00116 //
00117 NS_IMETHODIMP
00118 nsDragHelperService::Tracking(DragReference inDragRef, nsIEventSink *inSink,
00119                               PRBool* outDropAllowed)
00120 {
00121   NS_ASSERTION(mDragService,
00122                "Couldn't get a drag service, we're in biiig trouble");
00123   if (!mDragService || !inSink) {
00124     *outDropAllowed = PR_FALSE;
00125     return NS_ERROR_FAILURE;
00126   }
00127 
00128   // clear out the |canDrop| property of the drag session. If it's meant to
00129   // be, it will be set again.
00130   nsCOMPtr<nsIDragSession> session;
00131   mDragService->GetCurrentSession(getter_AddRefs(session));
00132   NS_ASSERTION(session, "If we don't have a drag session, we're fucked");
00133   if (session)
00134     session->SetCanDrop(PR_FALSE);
00135 
00136   // mDragOverTimer is running, but an NS_DRAGDROP_OVER event is about to be
00137   // sent.  Make sure that the timer doesn't send another event too quickly.
00138   SetDragOverTimer(inSink, inDragRef);
00139 
00140   DoDragAction(NS_DRAGDROP_OVER, inDragRef, inSink);
00141 
00142   // check if gecko has since allowed the drop and return it
00143   if (session)
00144     session->GetCanDrop(outDropAllowed);
00145 
00146   return NS_OK;
00147 }
00148 
00149 
00150 //
00151 // Leave
00152 //
00153 // Called when the mouse leaves the rectangle bounding the browser
00154 // during a drag. Cleans up the drag service and releases it.
00155 //
00156 NS_IMETHODIMP
00157 nsDragHelperService::Leave(DragReference inDragRef, nsIEventSink *inSink)
00158 {
00159   // Stop sending NS_DRAGDROP_OVER events.
00160   SetDragOverTimer(nsnull, nsnull);
00161 
00162   NS_ASSERTION(mDragService,
00163                "Couldn't get a drag service, we're in biiig trouble");
00164   if (!mDragService || !inSink)
00165     return NS_ERROR_FAILURE;
00166 
00167   // clear out the dragRef in the drag session. We are guaranteed that
00168   // this will be called _after_ the drop has been processed (if there
00169   // is one), so we're not destroying valuable information if the drop
00170   // was in our window.
00171   nsCOMPtr<nsIDragSessionMac> macSession(do_QueryInterface(mDragService));
00172   if (macSession)
00173     macSession->SetDragReference(0);
00174 
00175   DoDragAction(NS_DRAGDROP_EXIT, inDragRef, inSink);
00176 
00177 #ifndef MOZ_WIDGET_COCOA
00178   ::HideDragHilite(inDragRef);
00179 #endif
00180 
00181   nsCOMPtr<nsIDragSession> currentDragSession;
00182   mDragService->GetCurrentSession(getter_AddRefs(currentDragSession));
00183 
00184   if (currentDragSession) {
00185     nsCOMPtr<nsIDOMNode> sourceNode;
00186     currentDragSession->GetSourceNode(getter_AddRefs(sourceNode));
00187 
00188     if (!sourceNode) {
00189       // We're leaving a window while doing a drag that was
00190       // initiated in a differnt app. End the drag session,
00191       // since we're done with it for now (until the user
00192       // drags back into mozilla).
00193       mDragService->EndDragSession();
00194     }
00195   }
00196 
00197   // we're _really_ done with it, so let go of the service.
00198   mDragService = nsnull;
00199 
00200   return NS_OK;
00201 }
00202 
00203 
00204 //
00205 // Drop
00206 //
00207 // Called when a drop occurs within the rectangle bounding the browser
00208 // during a drag. Cleans up the drag service and releases it.
00209 //
00210 NS_IMETHODIMP
00211 nsDragHelperService::Drop(DragReference inDragRef, nsIEventSink *inSink,
00212                           PRBool* outAccepted)
00213 {
00214   // Stop sending NS_DRAGDROP_OVER events.
00215   SetDragOverTimer(nsnull, nsnull);
00216 
00217   NS_ASSERTION(mDragService,
00218                "Couldn't get a drag service, we're in biiig trouble");
00219   if (!mDragService || !inSink) {
00220     *outAccepted = PR_FALSE;
00221     return NS_ERROR_FAILURE;
00222   }
00223 
00224   // We make the assuption that the dragOver handlers have correctly set
00225   // the |canDrop| property of the Drag Session. Before we dispatch the event
00226   // into Gecko, check that value and either dispatch it or set the result
00227   // code to "spring-back" and show the user the drag failed.
00228   OSErr result = noErr;
00229   nsCOMPtr<nsIDragSession> dragSession;
00230   mDragService->GetCurrentSession(getter_AddRefs(dragSession));
00231   if (dragSession) {
00232     // if the target has set that it can accept the drag, pass along
00233     // to gecko, otherwise set phasers for failure.
00234     PRBool canDrop = PR_FALSE;
00235     if (NS_SUCCEEDED(dragSession->GetCanDrop(&canDrop)))
00236       if (canDrop)
00237         DoDragAction(NS_DRAGDROP_DROP, inDragRef, inSink);
00238       else
00239         result = dragNotAcceptedErr;
00240   } // if a valid drag session
00241 
00242 #ifdef MOZ_WIDGET_COCOA
00243   // we don't need the drag session anymore, the user has released the
00244   // mouse and the event has already gone to gecko.
00245   mDragService->EndDragSession();
00246   mDragService = nsnull;
00247 #endif  // MOZ_WIDGET_COCOA
00248 
00249   // if there was any kind of error, the drag wasn't accepted
00250   *outAccepted = (result == noErr);
00251 
00252   return NS_OK;
00253 }
00254 
00255 
00256 // DoDragAction
00257 //
00258 // Common routine for NS_DRAGDROP_(ENTER|EXIT|OVER|DROP) events.  Obtains
00259 // information about the drag in progress and dispatches the event.
00260 //
00261 // protected
00262 PRBool
00263 nsDragHelperService::DoDragAction(PRUint32      aMessage,
00264                                   DragReference aDragRef,
00265                                   nsIEventSink* aSink)
00266 {
00267   // Get information about the drag.
00268   Point mouseLocGlobal;
00269   ::GetDragMouse(aDragRef, &mouseLocGlobal, nsnull);
00270   short modifiers;
00271   ::GetDragModifiers(aDragRef, &modifiers, nsnull, nsnull);
00272 
00273   // Set the drag action on the service so the frames know what is going on.
00274   SetDragActionBasedOnModifiers(modifiers);
00275 
00276   // Pass into Gecko for handling.
00277   PRBool handled = PR_FALSE;
00278   aSink->DragEvent(aMessage, mouseLocGlobal.h, mouseLocGlobal.v,
00279                    modifiers, &handled);
00280 
00281   return handled;
00282 }
00283 
00284 
00285 //
00286 // SetDragActionsBasedOnModifiers
00287 //
00288 // Examines the MacOS modifier keys and sets the appropriate drag action on the
00289 // drag session to copy/move/etc
00290 //
00291 void
00292 nsDragHelperService::SetDragActionBasedOnModifiers(short inModifiers)
00293 {
00294   nsCOMPtr<nsIDragSession> dragSession;
00295   mDragService->GetCurrentSession(getter_AddRefs(dragSession));
00296   if (dragSession) {
00297     PRUint32 action = nsIDragService::DRAGDROP_ACTION_MOVE;
00298 
00299     // force copy = option, alias = cmd-option, default is move
00300     if (inModifiers & optionKey) {
00301       if (inModifiers & cmdKey)
00302         action = nsIDragService::DRAGDROP_ACTION_LINK;
00303       else
00304         action = nsIDragService::DRAGDROP_ACTION_COPY;
00305     }
00306 
00307     dragSession->SetDragAction(action);
00308   }
00309 
00310 } // SetDragActionBasedOnModifiers
00311 
00312 
00313 // SetDragOverTimer
00314 //
00315 // Sets a timer to send NS_DRAGDROP_OVER events into Gecko at regular
00316 // intervals.  If a timer is already running, the interval is reset.
00317 // If either argument is null, any running timer is stopped.
00318 //
00319 // protected
00320 void
00321 nsDragHelperService::SetDragOverTimer(nsIEventSink* aSink,
00322                                       DragRef       aDragRef)
00323 {
00324   // win32 seems to use 16Hz internally, gtk2 has a 10Hz timer
00325   const EventTimerInterval kDragOverInterval = 1.0/10.0;
00326 
00327   if (!aSink || !aDragRef) {
00328     // Cancel the timer.
00329     if (mDragOverTimer) {
00330       ::RemoveEventLoopTimer(mDragOverTimer);
00331       mDragOverTimer = nsnull;
00332       mDragOverSink = nsnull;
00333     }
00334     return;
00335   }
00336 
00337   if (mDragOverTimer) {
00338     // A timer is already running, just bump its next-fire time.
00339     ::SetEventLoopTimerNextFireTime(mDragOverTimer, kDragOverInterval);
00340     return;
00341   }
00342 
00343   mDragOverSink = aSink;
00344   mDragOverDragRef = aDragRef;
00345 
00346   static EventLoopTimerUPP sDragOverTimerUPP;
00347   if (!sDragOverTimerUPP)
00348     sDragOverTimerUPP = ::NewEventLoopTimerUPP(DragOverTimerHandler);
00349  
00350   ::InstallEventLoopTimer(::GetCurrentEventLoop(),
00351                           kDragOverInterval,
00352                           kDragOverInterval,
00353                           sDragOverTimerUPP,
00354                           this,
00355                           &mDragOverTimer);
00356 }
00357 
00358 
00359 // DragOverTimerHandler
00360 //
00361 // Called at regular intervals to send NS_DRAGDROP_OVER events.
00362 //
00363 // protected static
00364 void
00365 nsDragHelperService::DragOverTimerHandler(EventLoopTimerRef aTimer,
00366                                           void* aUserData)
00367 {
00368   nsDragHelperService* self = NS_STATIC_CAST(nsDragHelperService*, aUserData);
00369 
00370   self->DoDragAction(NS_DRAGDROP_OVER,
00371                      self->mDragOverDragRef, self->mDragOverSink);
00372 }