Back to index

lightning-sunbird  0.9+nobinonly
nsDragService.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  *   Mike Pinkerton (pinkerton@netscape.com)
00024  *   Mark Hammond (MarkH@ActiveState.com)
00025  *   David Gardiner <david.gardiner@unisa.edu.au>
00026  *
00027  * Alternatively, the contents of this file may be used under the terms of
00028  * either the GNU General Public License Version 2 or later (the "GPL"), or
00029  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00030  * in which case the provisions of the GPL or the LGPL are applicable instead
00031  * of those above. If you wish to allow use of your version of this file only
00032  * under the terms of either the GPL or the LGPL, and not to allow others to
00033  * use your version of this file under the terms of the MPL, indicate your
00034  * decision by deleting the provisions above and replace them with the notice
00035  * and other provisions required by the GPL or the LGPL. If you do not delete
00036  * the provisions above, a recipient may use your version of this file under
00037  * the terms of any one of the MPL, the GPL or the LGPL.
00038  *
00039  * ***** END LICENSE BLOCK ***** */
00040 
00041 #include <ole2.h>
00042 #include <oleidl.h>
00043 #include <shlobj.h>
00044 
00045 // shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
00046 #include <shellapi.h>
00047 
00048 #include "nsDragService.h"
00049 #include "nsITransferable.h"
00050 #include "nsDataObj.h"
00051 
00052 #include "nsWidgetsCID.h"
00053 #include "nsNativeDragTarget.h"
00054 #include "nsNativeDragSource.h"
00055 #include "nsClipboard.h"
00056 #include "nsISupportsArray.h"
00057 #include "nsIDocument.h"
00058 #include "nsDataObjCollection.h"
00059 
00060 #include "nsAutoPtr.h"
00061 
00062 #include "nsToolkit.h"
00063 
00064 //-------------------------------------------------------------------------
00065 //
00066 // DragService constructor
00067 //
00068 //-------------------------------------------------------------------------
00069 nsDragService::nsDragService()
00070   : mNativeDragSrc(nsnull), mNativeDragTarget(nsnull), mDataObject(nsnull)
00071 {
00072 }
00073 
00074 //-------------------------------------------------------------------------
00075 //
00076 // DragService destructor
00077 //
00078 //-------------------------------------------------------------------------
00079 nsDragService::~nsDragService()
00080 {
00081   NS_IF_RELEASE(mNativeDragSrc);
00082   NS_IF_RELEASE(mNativeDragTarget);
00083   NS_IF_RELEASE(mDataObject);
00084 }
00085 
00086 
00087 //-------------------------------------------------------------------------
00088 NS_IMETHODIMP
00089 nsDragService::InvokeDragSession(nsIDOMNode *aDOMNode,
00090                                  nsISupportsArray *anArrayTransferables,
00091                                  nsIScriptableRegion *aRegion,
00092                                  PRUint32 aActionType)
00093 {
00094   nsresult rv = nsBaseDragService::InvokeDragSession(aDOMNode,
00095                                                      anArrayTransferables,
00096                                                      aRegion, aActionType);
00097   NS_ENSURE_SUCCESS(rv, rv);
00098 
00099   // Try and get source URI of the items that are being dragged
00100   nsIURI *uri = nsnull;
00101 
00102   nsCOMPtr<nsIDocument> doc(do_QueryInterface(mSourceDocument));
00103   if (doc) {
00104     uri = doc->GetDocumentURI();
00105   }
00106 
00107   PRUint32 numItemsToDrag = 0;
00108   rv = anArrayTransferables->Count(&numItemsToDrag);
00109   if (!numItemsToDrag)
00110     return NS_ERROR_FAILURE;
00111 
00112   // The clipboard class contains some static utility methods that we
00113   // can use to create an IDataObject from the transferable
00114 
00115   // if we're dragging more than one item, we need to create a
00116   // "collection" object to fake out the OS. This collection contains
00117   // one |IDataObject| for each transerable. If there is just the one
00118   // (most cases), only pass around the native |IDataObject|.
00119   nsRefPtr<IDataObject> itemToDrag;
00120   if (numItemsToDrag > 1) {
00121     nsDataObjCollection * dataObjCollection = new nsDataObjCollection();
00122     if (!dataObjCollection)
00123       return NS_ERROR_OUT_OF_MEMORY;
00124     itemToDrag = dataObjCollection;
00125     for (PRUint32 i=0; i<numItemsToDrag; ++i) {
00126       nsCOMPtr<nsISupports> supports;
00127       anArrayTransferables->GetElementAt(i, getter_AddRefs(supports));
00128       nsCOMPtr<nsITransferable> trans(do_QueryInterface(supports));
00129       if (trans) {
00130         nsRefPtr<IDataObject> dataObj;
00131         rv = nsClipboard::CreateNativeDataObject(trans,
00132                                                  getter_AddRefs(dataObj), uri);
00133         NS_ENSURE_SUCCESS(rv, rv);
00134 
00135         dataObjCollection->AddDataObject(dataObj);
00136       }
00137     }
00138   } // if dragging multiple items
00139   else {
00140     nsCOMPtr<nsISupports> supports;
00141     anArrayTransferables->GetElementAt(0, getter_AddRefs(supports));
00142     nsCOMPtr<nsITransferable> trans(do_QueryInterface(supports));
00143     if (trans) {
00144       rv = nsClipboard::CreateNativeDataObject(trans,
00145                                                getter_AddRefs(itemToDrag),
00146                                                uri);
00147       NS_ENSURE_SUCCESS(rv, rv);
00148     }
00149   } // else dragging a single object
00150 
00151   return StartInvokingDragSession(itemToDrag, aActionType);
00152 }
00153 
00154 //-------------------------------------------------------------------------
00155 NS_IMETHODIMP
00156 nsDragService::StartInvokingDragSession(IDataObject * aDataObj,
00157                                         PRUint32 aActionType)
00158 {
00159   // To do the drag we need to create an object that
00160   // implements the IDataObject interface (for OLE)
00161   NS_IF_RELEASE(mNativeDragSrc);
00162   mNativeDragSrc = (IDropSource *)new nsNativeDragSource();
00163   if (!mNativeDragSrc)
00164     return NS_ERROR_OUT_OF_MEMORY;
00165 
00166   mNativeDragSrc->AddRef();
00167 
00168   // Now figure out what the native drag effect should be
00169   DWORD dropRes;
00170   DWORD effects = DROPEFFECT_SCROLL;
00171   if (aActionType & DRAGDROP_ACTION_COPY) {
00172     effects |= DROPEFFECT_COPY;
00173   }
00174   if (aActionType & DRAGDROP_ACTION_MOVE) {
00175     effects |= DROPEFFECT_MOVE;
00176   }
00177   if (aActionType & DRAGDROP_ACTION_LINK) {
00178     effects |= DROPEFFECT_LINK;
00179   }
00180 
00181   // XXX not sure why we bother to cache this, it can change during
00182   // the drag
00183   mDragAction = aActionType;
00184   mDoingDrag  = PR_TRUE;
00185 
00186   // Start dragging
00187   StartDragSession();
00188 
00189   // Call the native D&D method
00190   HRESULT res = ::DoDragDrop(aDataObj, mNativeDragSrc, effects, &dropRes);
00191 
00192   // We're done dragging
00193   EndDragSession();
00194 
00195   // For some drag/drop interactions, IDataObject::SetData doesn't get
00196   // called with a CFSTR_PERFORMEDDROPEFFECT format and the
00197   // intermediate file (if it was created) isn't deleted.  See
00198   // http://bugzilla.mozilla.org/show_bug.cgi?id=203847#c4 for a
00199   // detailed description of the different cases.  Now that we know
00200   // that the drag/drop operation has ended, call SetData() so that
00201   // the intermediate file is deleted.
00202   static CLIPFORMAT PerformedDropEffect =
00203     ::RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT);
00204 
00205   FORMATETC fmte =
00206     {
00207       (CLIPFORMAT)PerformedDropEffect,
00208       NULL,
00209       DVASPECT_CONTENT,
00210       -1,
00211       TYMED_NULL
00212     };
00213 
00214   STGMEDIUM medium;
00215   medium.tymed = TYMED_NULL;
00216   medium.pUnkForRelease = NULL;
00217   aDataObj->SetData(&fmte, &medium, FALSE);
00218 
00219   mDoingDrag = PR_FALSE;
00220 
00221   return DRAGDROP_S_DROP == res ? NS_OK : NS_ERROR_FAILURE;
00222 }
00223 
00224 //-------------------------------------------------------------------------
00225 // Make Sure we have the right kind of object
00226 nsDataObjCollection*
00227 nsDragService::GetDataObjCollection(IDataObject* aDataObj)
00228 {
00229   nsDataObjCollection * dataObjCol = nsnull;
00230   if (aDataObj) {
00231     nsIDataObjCollection* dataObj;
00232     if (aDataObj->QueryInterface(IID_IDataObjCollection,
00233                                  (void**)&dataObj) == S_OK) {
00234       dataObjCol = NS_STATIC_CAST(nsDataObjCollection*, aDataObj);
00235       dataObj->Release();
00236     }
00237   }
00238 
00239   return dataObjCol;
00240 }
00241 
00242 //-------------------------------------------------------------------------
00243 NS_IMETHODIMP
00244 nsDragService::GetNumDropItems(PRUint32 * aNumItems)
00245 {
00246   if (!mDataObject) {
00247     *aNumItems = 0;
00248     return NS_OK;
00249   }
00250 
00251   if (IsCollectionObject(mDataObject)) {
00252     nsDataObjCollection * dataObjCol = GetDataObjCollection(mDataObject);
00253     if (dataObjCol)
00254       *aNumItems = dataObjCol->GetNumDataObjects();
00255   }
00256   else {
00257     // Next check if we have a file drop. Return the number of files in
00258     // the file drop as the number of items we have, pretending like we
00259     // actually have > 1 drag item.
00260     FORMATETC fe2;
00261     SET_FORMATETC(fe2, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
00262     if (mDataObject->QueryGetData(&fe2) == S_OK) {
00263       STGMEDIUM stm;
00264       if (mDataObject->GetData(&fe2, &stm) == S_OK) {
00265         HDROP hdrop = (HDROP)GlobalLock(stm.hGlobal);
00266         *aNumItems = nsToolkit::mDragQueryFile(hdrop, 0xFFFFFFFF, NULL, 0);
00267         ::GlobalUnlock(stm.hGlobal);
00268         ::ReleaseStgMedium(&stm);
00269       }
00270     }
00271     else
00272       *aNumItems = 1;
00273   }
00274 
00275   return NS_OK;
00276 }
00277 
00278 //-------------------------------------------------------------------------
00279 NS_IMETHODIMP
00280 nsDragService::GetData(nsITransferable * aTransferable, PRUint32 anItem)
00281 {
00282   // This typcially happens on a drop, the target would be asking
00283   // for it's transferable to be filled in
00284   // Use a static clipboard utility method for this
00285   if (!mDataObject)
00286     return NS_ERROR_FAILURE;
00287 
00288   nsresult dataFound = NS_ERROR_FAILURE;
00289 
00290   if (IsCollectionObject(mDataObject)) {
00291     // multiple items, use |anItem| as an index into our collection
00292     nsDataObjCollection * dataObjCol = GetDataObjCollection(mDataObject);
00293     PRUint32 cnt = dataObjCol->GetNumDataObjects();
00294     if (anItem >= 0 && anItem < cnt) {
00295       IDataObject * dataObj = dataObjCol->GetDataObjectAt(anItem);
00296       dataFound = nsClipboard::GetDataFromDataObject(dataObj, 0, nsnull,
00297                                                      aTransferable);
00298     }
00299     else
00300       NS_WARNING("Index out of range!");
00301   }
00302   else {
00303     // If they are asking for item "0", we can just get it...
00304     if (anItem == 0) {
00305        dataFound = nsClipboard::GetDataFromDataObject(mDataObject, anItem,
00306                                                       nsnull, aTransferable);
00307     } else {
00308       // It better be a file drop, or else non-zero indexes are invalid!
00309       FORMATETC fe2;
00310       SET_FORMATETC(fe2, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
00311       if (mDataObject->QueryGetData(&fe2) == S_OK)
00312         dataFound = nsClipboard::GetDataFromDataObject(mDataObject, anItem,
00313                                                        nsnull, aTransferable);
00314       else
00315         NS_WARNING("Reqesting non-zero index, but clipboard data is not a collection!");
00316     }
00317   }
00318   return dataFound;
00319 }
00320 
00321 //---------------------------------------------------------
00322 NS_IMETHODIMP
00323 nsDragService::SetIDataObject(IDataObject * aDataObj)
00324 {
00325   // When the native drag starts the DragService gets
00326   // the IDataObject that is being dragged
00327   NS_IF_RELEASE(mDataObject);
00328   mDataObject = aDataObj;
00329   NS_IF_ADDREF(mDataObject);
00330 
00331   return NS_OK;
00332 }
00333 
00334 //-------------------------------------------------------------------------
00335 NS_IMETHODIMP
00336 nsDragService::IsDataFlavorSupported(const char *aDataFlavor, PRBool *_retval)
00337 {
00338   if (!aDataFlavor || !mDataObject || !_retval)
00339     return NS_ERROR_FAILURE;
00340 
00341 #ifdef NS_DEBUG
00342   if (strcmp(aDataFlavor, kTextMime) == 0)
00343     NS_WARNING("DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode INSTEAD");
00344 #endif
00345 
00346   *_retval = PR_FALSE;
00347 
00348   FORMATETC fe;
00349   UINT format = 0;
00350 
00351   if (IsCollectionObject(mDataObject)) {
00352     // We know we have one of our special collection objects.
00353     format = nsClipboard::GetFormat(aDataFlavor);
00354     SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1,
00355                   TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI);
00356 
00357     // See if any one of the IDataObjects in the collection supports
00358     // this data type
00359     nsDataObjCollection* dataObjCol = GetDataObjCollection(mDataObject);
00360     if (dataObjCol) {
00361       PRUint32 cnt = dataObjCol->GetNumDataObjects();
00362       for (PRUint32 i=0;i<cnt;++i) {
00363         IDataObject * dataObj = dataObjCol->GetDataObjectAt(i);
00364         if (S_OK == dataObj->QueryGetData(&fe))
00365           *_retval = PR_TRUE;             // found it!
00366       }
00367     }
00368   } // if special collection object
00369   else {
00370     // Ok, so we have a single object. Check to see if has the correct
00371     // data type. Since this can come from an outside app, we also
00372     // need to see if we need to perform text->unicode conversion if
00373     // the client asked for unicode and it wasn't available.
00374     format = nsClipboard::GetFormat(aDataFlavor);
00375     SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1,
00376                   TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI);
00377     if (mDataObject->QueryGetData(&fe) == S_OK)
00378       *_retval = PR_TRUE;                 // found it!
00379     else {
00380       // We haven't found the exact flavor the client asked for, but
00381       // maybe we can still find it from something else that's on the
00382       // clipboard
00383       if (strcmp(aDataFlavor, kUnicodeMime) == 0) {
00384         // client asked for unicode and it wasn't present, check if we
00385         // have CF_TEXT.  We'll handle the actual data substitution in
00386         // the data object.
00387         format = nsClipboard::GetFormat(kTextMime);
00388         SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1,
00389                       TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI);
00390         if (mDataObject->QueryGetData(&fe) == S_OK)
00391           *_retval = PR_TRUE;                 // found it!
00392       }
00393       else if (strcmp(aDataFlavor, kURLMime) == 0) {
00394         // client asked for a url and it wasn't present, but if we
00395         // have a file, then we have a URL to give them (the path, or
00396         // the internal URL if an InternetShortcut).
00397         format = nsClipboard::GetFormat(kFileMime);
00398         SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1,
00399                       TYMED_HGLOBAL | TYMED_FILE | TYMED_GDI);
00400         if (mDataObject->QueryGetData(&fe) == S_OK)
00401           *_retval = PR_TRUE;                 // found it!
00402       }
00403     } // else try again
00404   }
00405 
00406   return NS_OK;
00407 }
00408 
00409 
00410 //
00411 // IsCollectionObject
00412 //
00413 // Determine if this is a single |IDataObject| or one of our private
00414 // collection objects. We know the difference because our collection
00415 // object will respond to supporting the private |MULTI_MIME| format.
00416 //
00417 PRBool
00418 nsDragService::IsCollectionObject(IDataObject* inDataObj)
00419 {
00420   PRBool isCollection = PR_FALSE;
00421 
00422   // setup the format object to ask for the MULTI_MIME format. We only
00423   // need to do this once
00424   static UINT sFormat = 0;
00425   static FORMATETC sFE;
00426   if (!sFormat) {
00427     sFormat = nsClipboard::GetFormat(MULTI_MIME);
00428     SET_FORMATETC(sFE, sFormat, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
00429   }
00430 
00431   // ask the object if it supports it. If yes, we have a collection
00432   // object
00433   if (inDataObj->QueryGetData(&sFE) == S_OK)
00434     isCollection = PR_TRUE;
00435 
00436   return isCollection;
00437 
00438 } // IsCollectionObject
00439 
00440 
00441 //
00442 // EndDragSession
00443 //
00444 // Override the default to make sure that we release the data object
00445 // when the drag ends. It seems that OLE doesn't like to let apps quit
00446 // w/out crashing when we're still holding onto their data
00447 //
00448 NS_IMETHODIMP
00449 nsDragService::EndDragSession()
00450 {
00451   nsBaseDragService::EndDragSession();
00452   NS_IF_RELEASE(mDataObject);
00453 
00454   return NS_OK;
00455 }