home *** CD-ROM | disk | FTP | other *** search
/ PC World 2003 May / PCWorld_2003-05_cd.bin / Komunik / phoenix / chrome / toolkit.jar / content / global / nsDragAndDrop.js < prev    next >
Text File  |  2002-10-13  |  18KB  |  534 lines

  1.  
  2. /** 
  3.  *  nsTransferable - a wrapper for nsITransferable that simplifies
  4.  *                   javascript clipboard and drag&drop. for use in
  5.  *                   these situations you should use the nsClipboard
  6.  *                   and nsDragAndDrop wrappers for more convenience
  7.  **/ 
  8.  
  9. var nsTransferable = {
  10.   /**
  11.    * nsITransferable set (TransferData aTransferData) ;
  12.    *
  13.    * Creates a transferable with data for a list of supported types ("flavours")
  14.    * 
  15.    * @param TransferData aTransferData
  16.    *        a javascript object in the format described above 
  17.    **/ 
  18.   set: function (aTransferDataSet)
  19.     {
  20.       var trans = this.createTransferable();
  21.       for (var i = 0; i < aTransferDataSet.dataList.length; ++i) 
  22.         {
  23.           var currData = aTransferDataSet.dataList[i];
  24.           var currFlavour = currData.flavour.contentType;
  25.           trans.addDataFlavor(currFlavour);
  26.           var supports = null; // nsISupports data
  27.           var length = 0;
  28.           if (currData.flavour.dataIIDKey == "nsISupportsString")
  29.             {
  30.               supports = Components.classes["@mozilla.org/supports-string;1"]
  31.                                    .createInstance(Components.interfaces.nsISupportsString);
  32.  
  33.               supports.data = currData.supports;
  34.               length = supports.data.length;
  35.             }
  36.           else 
  37.             {
  38.               // non-string data. TBD!
  39.             }
  40.           trans.setTransferData(currFlavour, supports, length * 2);
  41.         }
  42.       return trans;
  43.     },
  44.   
  45.   /**
  46.    * TransferData/TransferDataSet get (FlavourSet aFlavourSet, 
  47.    *                                   Function aRetrievalFunc, Boolean aAnyFlag) ;
  48.    *
  49.    * Retrieves data from the transferable provided in aRetrievalFunc, formatted
  50.    * for more convenient access.
  51.    *
  52.    * @param FlavourSet aFlavourSet
  53.    *        a FlavourSet object that contains a list of supported flavours.
  54.    * @param Function aRetrievalFunc
  55.    *        a reference to a function that returns a nsISupportsArray of nsITransferables
  56.    *        for each item from the specified source (clipboard/drag&drop etc)
  57.    * @param Boolean aAnyFlag
  58.    *        a flag specifying whether or not a specific flavour is requested. If false,
  59.    *        data of the type of the first flavour in the flavourlist parameter is returned,
  60.    *        otherwise the best flavour supported will be returned.
  61.    **/
  62.   get: function (aFlavourSet, aRetrievalFunc, aAnyFlag)
  63.     {
  64.       if (!aRetrievalFunc) 
  65.         throw "No data retrieval handler provided!";
  66.       
  67.       var supportsArray = aRetrievalFunc(aFlavourSet);
  68.       var dataArray = [];
  69.       var count = supportsArray.Count();
  70.       
  71.       // Iterate over the number of items returned from aRetrievalFunc. For
  72.       // clipboard operations, this is 1, for drag and drop (where multiple
  73.       // items may have been dragged) this could be >1.
  74.       for (var i = 0; i < count; i++)
  75.         {
  76.           var trans = supportsArray.GetElementAt(i);
  77.           if (!trans) continue;
  78.           trans = trans.QueryInterface(Components.interfaces.nsITransferable);
  79.             
  80.           var data = { };
  81.           var length = { };
  82.           
  83.           var currData = null;
  84.           if (aAnyFlag)
  85.             { 
  86.               var flavour = { };
  87.               trans.getAnyTransferData(flavour, data, length);
  88.               if (data && flavour)
  89.                 {
  90.                   var selectedFlavour = aFlavourSet.flavourTable[flavour.value];
  91.                   if (selectedFlavour) 
  92.                     dataArray[i] = FlavourToXfer(data.value, length.value, selectedFlavour);
  93.                 }
  94.             }
  95.           else
  96.             {
  97.               var firstFlavour = aFlavourSet.flavours[0];
  98.               trans.getTransferData(firstFlavour, data, length);
  99.               if (data && firstFlavour)
  100.                 dataArray[i] = FlavourToXfer(data.value, length.value, firstFlavour);
  101.             }
  102.         }
  103.       return new TransferDataSet(dataArray);
  104.     },
  105.  
  106.   /** 
  107.    * nsITransferable createTransferable (void) ;
  108.    *
  109.    * Creates and returns a transferable object.
  110.    **/    
  111.   createTransferable: function ()
  112.     {
  113.       const kXferableContractID = "@mozilla.org/widget/transferable;1";
  114.       const kXferableIID = Components.interfaces.nsITransferable;
  115.       return Components.classes[kXferableContractID].createInstance(kXferableIID);
  116.     }
  117. };  
  118.  
  119. /** 
  120.  * A FlavourSet is a simple type that represents a collection of Flavour objects.
  121.  * FlavourSet is constructed from an array of Flavours, and stores this list as
  122.  * an array and a hashtable. The rationale for the dual storage is as follows:
  123.  * 
  124.  * Array: Ordering is important when adding data flavours to a transferable. 
  125.  *        Flavours added first are deemed to be 'preferred' by the client. 
  126.  * Hash:  Convenient lookup of flavour data using the content type (MIME type)
  127.  *        of data as a key. 
  128.  */
  129. function FlavourSet(aFlavourList)
  130. {
  131.   this.flavours = aFlavourList || [];
  132.   this.flavourTable = { };
  133.  
  134.   this._XferID = "FlavourSet";
  135.   
  136.   for (var i = 0; i < this.flavours.length; ++i)
  137.     this.flavourTable[this.flavours[i].contentType] = this.flavours[i];
  138. }
  139.  
  140. FlavourSet.prototype = {
  141.   appendFlavour: function (aFlavour, aFlavourIIDKey)
  142.   {
  143.     var flavour = new Flavour (aFlavour, aFlavourIIDKey);
  144.     this.flavours.push(flavour);
  145.     this.flavourTable[flavour.contentType] = flavour;
  146.   }
  147. };
  148.  
  149. /** 
  150.  * A Flavour is a simple type that represents a data type that can be handled. 
  151.  * It takes a content type (MIME type) which is used when storing data on the
  152.  * system clipboard/drag and drop, and an IIDKey (string interface name
  153.  * which is used to QI data to an appropriate form. The default interface is
  154.  * assumed to be wide-string.
  155.  */ 
  156. function Flavour(aContentType, aDataIIDKey)
  157. {
  158.   this.contentType = aContentType;
  159.   this.dataIIDKey = aDataIIDKey || "nsISupportsString";
  160.  
  161.   this._XferID = "Flavour";
  162. }
  163.  
  164. function TransferDataBase() {}
  165. TransferDataBase.prototype = {
  166.   push: function (aItems)
  167.   {
  168.     this.dataList.push(aItems);
  169.   },
  170.  
  171.   get first ()
  172.   {
  173.     return "dataList" in this && this.dataList.length ? this.dataList[0] : null;
  174.   }
  175. };
  176.  
  177. /** 
  178.  * TransferDataSet is a list (array) of TransferData objects, which represents
  179.  * data dragged from one or more elements. 
  180.  */
  181. function TransferDataSet(aTransferDataList)
  182. {
  183.   this.dataList = aTransferDataList || [];
  184.  
  185.   this._XferID = "TransferDataSet";
  186. }
  187. TransferDataSet.prototype = TransferDataBase.prototype;
  188.  
  189. /** 
  190.  * TransferData is a list (array) of FlavourData for all the applicable content
  191.  * types associated with a drag from a single item. 
  192.  */
  193. function TransferData(aFlavourDataList)
  194. {
  195.   this.dataList = aFlavourDataList || [];
  196.  
  197.   this._XferID = "TransferData";
  198. }
  199. TransferData.prototype = {
  200.   __proto__: TransferDataBase.prototype,
  201.   
  202.   addDataForFlavour: function (aFlavourString, aData, aLength, aDataIIDKey)
  203.   {
  204.     this.dataList.push(new FlavourData(aData, aLength, 
  205.                        new Flavour(aFlavourString, aDataIIDKey)));
  206.   }
  207. };
  208.  
  209. /** 
  210.  * FlavourData is a type that represents data retrieved from the system 
  211.  * clipboard or drag and drop. It is constructed internally by the Transferable
  212.  * using the raw (nsISupports) data from the clipboard, the length of the data,
  213.  * and an object of type Flavour representing the type. Clients implementing
  214.  * IDragDropObserver receive an object of this type in their implementation of
  215.  * onDrop. They access the 'data' property to retrieve data, which is either data 
  216.  * QI'ed to a usable form, or unicode string. 
  217.  */
  218. function FlavourData(aData, aLength, aFlavour) 
  219. {
  220.   this.supports = aData;
  221.   this.contentLength = aLength;
  222.   this.flavour = aFlavour || null;
  223.   
  224.   this._XferID = "FlavourData";
  225. }
  226.  
  227. FlavourData.prototype = {
  228.   get data ()
  229.   {
  230.     if (this.flavour && 
  231.         this.flavour.dataIIDKey != "nsISupportsString" )
  232.       return this.supports.QueryInterface(Components.interfaces[this.flavour.dataIIDKey]); 
  233.     else {
  234.       var unicode = this.supports.QueryInterface(Components.interfaces.nsISupportsString);
  235.       if (unicode) 
  236.         return unicode.data.substring(0, this.contentLength/2);
  237.      
  238.       return this.supports;
  239.     }
  240.     return "";
  241.   }
  242. }
  243.  
  244. /** 
  245.  * Create a TransferData object with a single FlavourData entry. Used when 
  246.  * unwrapping data of a specific flavour from the drag service. 
  247.  */
  248. function FlavourToXfer(aData, aLength, aFlavour) 
  249. {
  250.   return new TransferData([new FlavourData(aData, aLength, aFlavour)]);
  251. }
  252.  
  253. var transferUtils = {
  254.  
  255.   retrieveURLFromData: function (aData, flavour)
  256.   {
  257.     switch (flavour) {
  258.       case "text/unicode":
  259.         return aData;
  260.       case "text/x-moz-url":
  261.         return aData.toString().split("\n")[0];
  262.       case "application/x-moz-file":
  263.         var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  264.                                   .getService(Components.interfaces.nsIIOService);
  265.         var fileHandler = ioService.getProtocolHandler("file")
  266.                                    .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
  267.         return fileHandler.getURLSpecFromFile(aData);
  268.     }
  269.     return null;                                                   
  270.   }
  271.  
  272. }
  273.  
  274. /**
  275.  * nsDragAndDrop - a convenience wrapper for nsTransferable, nsITransferable
  276.  *                 and nsIDragService/nsIDragSession. 
  277.  *
  278.  * USAGE INFORMATION: see 'README-nsDragAndDrop.html' in the same source directory
  279.  *                    as this file (typically xpfe/global/resources/content)
  280.  */
  281.  
  282. var nsDragAndDrop = {
  283.   
  284.   _mDS: null,
  285.   get mDragService()
  286.     {
  287.       if (!this._mDS) 
  288.         {
  289.           const kDSContractID = "@mozilla.org/widget/dragservice;1";
  290.           const kDSIID = Components.interfaces.nsIDragService;
  291.           this._mDS = Components.classes[kDSContractID].getService(kDSIID);
  292.         }
  293.       return this._mDS;
  294.     },
  295.  
  296.   /**
  297.    * void startDrag (DOMEvent aEvent, Object aDragDropObserver) ;
  298.    *
  299.    * called when a drag on an element is started.
  300.    *
  301.    * @param DOMEvent aEvent
  302.    *        the DOM event fired by the drag init
  303.    * @param Object aDragDropObserver
  304.    *        javascript object of format described above that specifies
  305.    *        the way in which the element responds to drag events.
  306.    **/  
  307.   startDrag: function (aEvent, aDragDropObserver)
  308.     {
  309.       if (!("onDragStart" in aDragDropObserver))
  310.         return;
  311.  
  312.       const kDSIID = Components.interfaces.nsIDragService;
  313.       var dragAction = { action: kDSIID.DRAGDROP_ACTION_COPY + kDSIID.DRAGDROP_ACTION_MOVE + kDSIID.DRAGDROP_ACTION_LINK };
  314.  
  315.       var transferData = { data: null };
  316.       try 
  317.         {
  318.           aDragDropObserver.onDragStart(aEvent, transferData, dragAction);
  319.         }
  320.       catch (e) 
  321.         {
  322.           return;  // not a draggable item, bail!
  323.         }
  324.  
  325.       if (!transferData.data) return;
  326.       transferData = transferData.data;
  327.       
  328.       var transArray = Components.classes["@mozilla.org/supports-array;1"]
  329.                                  .createInstance(Components.interfaces.nsISupportsArray);
  330.  
  331.       var region = null;
  332.       if (aEvent.originalTarget.localName == "treechildren") {
  333.         // let's build the drag region
  334.         var tree = aEvent.originalTarget.parentNode;
  335.         try {
  336.           region = Components.classes["@mozilla.org/gfx/region;1"].createInstance(Components.interfaces.nsIScriptableRegion);
  337.           region.init();
  338.           var obo = tree.treeBoxObject;
  339.           var bo = obo.treeBody.boxObject;
  340.           var obosel= obo.selection;
  341.  
  342.           var rowX = bo.x;
  343.           var rowY = bo.y;
  344.           var rowHeight = obo.rowHeight;
  345.           var rowWidth = bo.width;
  346.  
  347.           //add a rectangle for each visible selected row
  348.           for (var i = obo.getFirstVisibleRow(); i <= obo.getLastVisibleRow(); i ++)
  349.           {
  350.             if (obosel.isSelected(i))
  351.               region.unionRect(rowX, rowY, rowWidth, rowHeight);
  352.             rowY = rowY + rowHeight;
  353.           }
  354.       
  355.           //and finally, clip the result to be sure we don't spill over...
  356.           region.intersectRect(bo.x, bo.y, bo.width, bo.height);
  357.         } catch(ex) {
  358.           dump("Error while building selection region: " + ex + "\n");
  359.           region = null;
  360.         }
  361.       }
  362.  
  363.       var count = 0;
  364.       do 
  365.         {
  366.           var trans = nsTransferable.set(transferData._XferID == "TransferData" 
  367.                                          ? transferData 
  368.                                          : transferData.dataList[count++]);
  369.           transArray.AppendElement(trans.QueryInterface(Components.interfaces.nsISupports));
  370.         }
  371.       while (transferData._XferID == "TransferDataSet" && 
  372.              count < transferData.dataList.length);
  373.       
  374.       try {
  375.         this.mDragService.invokeDragSession(aEvent.target, transArray, region, dragAction.action);
  376.       }
  377.       catch(ex) {
  378.         // this could be because the user pressed escape to
  379.         // cancel the drag. even if it's not, there's not much
  380.         // we can do, so be silent.
  381.       }
  382.       aEvent.preventBubble();
  383.     },
  384.  
  385.   /** 
  386.    * void dragOver (DOMEvent aEvent, Object aDragDropObserver) ;
  387.    *
  388.    * called when a drag passes over this element
  389.    *
  390.    * @param DOMEvent aEvent
  391.    *        the DOM event fired by passing over the element
  392.    * @param Object aDragDropObserver
  393.    *        javascript object of format described above that specifies
  394.    *        the way in which the element responds to drag events.
  395.    **/
  396.   dragOver: function (aEvent, aDragDropObserver)
  397.     { 
  398.       if (!("onDragOver" in aDragDropObserver)) 
  399.         return;
  400.       if (!this.checkCanDrop(aEvent, aDragDropObserver))
  401.         return;
  402.       var flavourSet = aDragDropObserver.getSupportedFlavours();
  403.       for (var flavour in flavourSet.flavourTable)
  404.         {
  405.           if (this.mDragSession.isDataFlavorSupported(flavour))
  406.             {
  407.               aDragDropObserver.onDragOver(aEvent, 
  408.                                            flavourSet.flavourTable[flavour], 
  409.                                            this.mDragSession);
  410.               aEvent.preventBubble();
  411.               break;
  412.             }
  413.         }
  414.     },
  415.  
  416.   mDragSession: null,
  417.  
  418.   /** 
  419.    * void drop (DOMEvent aEvent, Object aDragDropObserver) ;
  420.    *
  421.    * called when the user drops on the element
  422.    *
  423.    * @param DOMEvent aEvent
  424.    *        the DOM event fired by the drop
  425.    * @param Object aDragDropObserver
  426.    *        javascript object of format described above that specifies
  427.    *        the way in which the element responds to drag events.
  428.    **/
  429.   drop: function (aEvent, aDragDropObserver)
  430.     {
  431.       if (!("onDrop" in aDragDropObserver))
  432.         return;
  433.       if (!this.checkCanDrop(aEvent, aDragDropObserver))
  434.         return;  
  435.       if (this.mDragSession.canDrop) {
  436.         var flavourSet = aDragDropObserver.getSupportedFlavours();
  437.         var transferData = nsTransferable.get(flavourSet, this.getDragData, true);
  438.         // hand over to the client to respond to dropped data
  439.         var multiple = "canHandleMultipleItems" in aDragDropObserver && aDragDropObserver.canHandleMultipleItems;
  440.         var dropData = multiple ? transferData : transferData.first.first;
  441.         aDragDropObserver.onDrop(aEvent, dropData, this.mDragSession);
  442.       }
  443.       aEvent.preventBubble();
  444.     },
  445.  
  446.   /** 
  447.    * void dragExit (DOMEvent aEvent, Object aDragDropObserver) ;
  448.    *
  449.    * called when a drag leaves this element
  450.    *
  451.    * @param DOMEvent aEvent
  452.    *        the DOM event fired by leaving the element
  453.    * @param Object aDragDropObserver
  454.    *        javascript object of format described above that specifies
  455.    *        the way in which the element responds to drag events.
  456.    **/
  457.   dragExit: function (aEvent, aDragDropObserver)
  458.     {
  459.       if (!this.checkCanDrop(aEvent, aDragDropObserver))
  460.         return;
  461.       if ("onDragExit" in aDragDropObserver)
  462.         aDragDropObserver.onDragExit(aEvent, this.mDragSession);
  463.     },  
  464.     
  465.   /** 
  466.    * void dragEnter (DOMEvent aEvent, Object aDragDropObserver) ;
  467.    *
  468.    * called when a drag enters in this element
  469.    *
  470.    * @param DOMEvent aEvent
  471.    *        the DOM event fired by entering in the element
  472.    * @param Object aDragDropObserver
  473.    *        javascript object of format described above that specifies
  474.    *        the way in which the element responds to drag events.
  475.    **/
  476.   dragEnter: function (aEvent, aDragDropObserver)
  477.     {
  478.       if (!this.checkCanDrop(aEvent, aDragDropObserver))
  479.         return;
  480.       if ("onDragEnter" in aDragDropObserver)
  481.         aDragDropObserver.onDragEnter(aEvent, this.mDragSession);
  482.     },  
  483.     
  484.   /** 
  485.    * nsISupportsArray getDragData (Object aFlavourList)
  486.    *
  487.    * Creates a nsISupportsArray of all droppable items for the given
  488.    * set of supported flavours.
  489.    * 
  490.    * @param FlavourSet aFlavourSet
  491.    *        formatted flavour list.
  492.    **/  
  493.   getDragData: function (aFlavourSet)
  494.     {
  495.       var supportsArray = Components.classes["@mozilla.org/supports-array;1"]
  496.                                     .createInstance(Components.interfaces.nsISupportsArray);
  497.  
  498.       for (var i = 0; i < nsDragAndDrop.mDragSession.numDropItems; ++i)
  499.         {
  500.           var trans = nsTransferable.createTransferable();
  501.           for (var j = 0; j < aFlavourSet.flavours.length; ++j)
  502.             trans.addDataFlavor(aFlavourSet.flavours[j].contentType);
  503.           nsDragAndDrop.mDragSession.getData(trans, i);
  504.           supportsArray.AppendElement(trans);
  505.         }
  506.       return supportsArray;
  507.     },
  508.  
  509.   /** 
  510.    * Boolean checkCanDrop (DOMEvent aEvent, Object aDragDropObserver) ;
  511.    *
  512.    * Sets the canDrop attribute for the drag session.
  513.    * returns false if there is no current drag session.
  514.    *
  515.    * @param DOMEvent aEvent
  516.    *        the DOM event fired by the drop
  517.    * @param Object aDragDropObserver
  518.    *        javascript object of format described above that specifies
  519.    *        the way in which the element responds to drag events.
  520.    **/
  521.   checkCanDrop: function (aEvent, aDragDropObserver)
  522.     {
  523.       if (!this.mDragSession) 
  524.         this.mDragSession = this.mDragService.getCurrentSession();
  525.       if (!this.mDragSession) 
  526.         return false;
  527.       this.mDragSession.canDrop = this.mDragSession.sourceNode != aEvent.target;
  528.       if ("canDrop" in aDragDropObserver)
  529.         this.mDragSession.canDrop &= aDragDropObserver.canDrop(aEvent, this.mDragSession);
  530.       return true;
  531.     } 
  532. };
  533.  
  534.