home *** CD-ROM | disk | FTP | other *** search
/ Freelog 112 / FreelogNo112-NovembreDecembre2012.iso / Multimedia / Songbird / Songbird_2.0.0-2311_windows-i686-msvc8.exe / components / sbAddToPlaylist.jsm < prev    next >
Text File  |  2012-06-08  |  24KB  |  780 lines

  1. /*
  2. //
  3. // BEGIN SONGBIRD GPL
  4. //
  5. // This file is part of the Songbird web player.
  6. //
  7. // Copyright(c) 2005-2008 POTI, Inc.
  8. // http://songbirdnest.com
  9. //
  10. // This file may be licensed under the terms of of the
  11. // GNU General Public License Version 2 (the "GPL").
  12. //
  13. // Software distributed under the License is distributed
  14. // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
  15. // express or implied. See the GPL for the specific language
  16. // governing rights and limitations.
  17. //
  18. // You should have received a copy of the GPL along with this
  19. // program. If not, go to http://www.gnu.org/licenses/gpl.html
  20. // or write to the Free Software Foundation, Inc.,
  21. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. //
  23. // END SONGBIRD GPL
  24. //
  25.  */
  26.  
  27. Components.utils.import("resource://app/jsmodules/sbProperties.jsm");
  28. Components.utils.import("resource://app/jsmodules/DropHelper.jsm");
  29. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  30.  
  31. const Ci = Components.interfaces;
  32. const Cc = Components.classes;
  33. const Cr = Components.results;
  34.  
  35. const ADDTOPLAYLIST_MENU_TYPE      = "submenu";
  36. const ADDTOPLAYLIST_MENU_ID        = "library_cmd_addtoplaylist";
  37. const ADDTOPLAYLIST_MENU_NAME      = "&command.addtoplaylist";
  38. const ADDTOPLAYLIST_MENU_TOOLTIP   = "&command.tooltip.addtoplaylist";
  39. const ADDTOPLAYLIST_MENU_KEY       = "&command.shortcut.key.addtoplaylist";
  40. const ADDTOPLAYLIST_MENU_KEYCODE   = "&command.shortcut.keycode.addtoplaylist";
  41. const ADDTOPLAYLIST_MENU_MODIFIERS = "&command.shortcut.modifiers.addtoplaylist";
  42. const DOWNLOADTOPLAYLIST_MENU_NAME      = "&command.downloadtoplaylist";
  43. const DOWNLOADTOPLAYLIST_MENU_TOOLTIP   = "&command.tooltip.downloadtoplaylist";
  44. const DOWNLOADTOPLAYLIST_MENU_KEY       = "&command.shortcut.key.downloadtoplaylist";
  45. const DOWNLOADTOPLAYLIST_MENU_KEYCODE   = "&command.shortcut.keycode.downloadtoplaylist";
  46. const DOWNLOADTOPLAYLIST_MENU_MODIFIERS = "&command.shortcut.modifiers.downloadtoplaylist";
  47.  
  48.  
  49. const ADDTOPLAYLIST_COMMAND_ID = "library_cmd_addtoplaylist:";
  50. const ADDTOPLAYLIST_NEWPLAYLIST_COMMAND_ID = "library_cmd_addtoplaylist_createnew";
  51.  
  52. EXPORTED_SYMBOLS = [ "addToPlaylistHelper",
  53.                      "SBPlaylistCommand_AddToPlaylist",
  54.                      "SBPlaylistCommand_DownloadToPlaylist" ];
  55.  
  56. /**
  57.  * \brief Creates a new unwrapper helper object which ensures
  58.  *        downloadStatusTarget is always set when adding items
  59.  *        to another library or playlist on a device.
  60.  * \param aSelection An sbIMediaListViewSelection.selectedMediaItems enumerator
  61.  * \return A new unwrapper helper object.
  62.  */
  63. function createUnwrapper(aSelection) {
  64.   var unwrapper = Cc["@songbirdnest.com/Songbird/Library/EnumeratorWrapper;1"]
  65.                     .createInstance(Ci.sbIMediaListEnumeratorWrapper);
  66.   unwrapper.initialize(aSelection);
  67.  
  68.   return unwrapper;
  69. }
  70.  
  71. // ----------------------------------------------------------------------------
  72. // The "Add to playlist" dynamic command object
  73. // ----------------------------------------------------------------------------
  74. var SBPlaylistCommand_AddToPlaylist =
  75. {
  76.   m_Context: {
  77.     m_Playlist: null,
  78.     m_Window: null
  79.   },
  80.  
  81.   m_addToPlaylist: null,
  82.  
  83.   m_root_commands :
  84.   {
  85.     m_Types: new Array
  86.     (
  87.       ADDTOPLAYLIST_MENU_TYPE
  88.     ),
  89.  
  90.     m_Ids: new Array
  91.     (
  92.       ADDTOPLAYLIST_MENU_ID
  93.     ),
  94.  
  95.     m_Names: new Array
  96.     (
  97.       ADDTOPLAYLIST_MENU_NAME
  98.     ),
  99.  
  100.     m_Tooltips: new Array
  101.     (
  102.       ADDTOPLAYLIST_MENU_TOOLTIP
  103.     ),
  104.  
  105.     m_Keys: new Array
  106.     (
  107.       ADDTOPLAYLIST_MENU_KEY
  108.     ),
  109.  
  110.     m_Keycodes: new Array
  111.     (
  112.       ADDTOPLAYLIST_MENU_KEYCODE
  113.     ),
  114.  
  115.     m_Enableds: new Array
  116.     (
  117.       true
  118.     ),
  119.  
  120.     m_Modifiers: new Array
  121.     (
  122.       ADDTOPLAYLIST_MENU_MODIFIERS
  123.     ),
  124.  
  125.     m_PlaylistCommands: new Array
  126.     (
  127.       null
  128.     )
  129.   },
  130.  
  131.   _getMenu: function(aSubMenu)
  132.   {
  133.     var cmds;
  134.  
  135.     cmds = this.m_addToPlaylist.handleGetMenu(aSubMenu);
  136.     if (cmds) return cmds;
  137.  
  138.     switch (aSubMenu) {
  139.       default:
  140.         cmds = this.m_root_commands;
  141.         break;
  142.     }
  143.     return cmds;
  144.   },
  145.  
  146.   getVisible: function( aHost )
  147.   {
  148.     return true;
  149.   },
  150.  
  151.   getNumCommands: function( aSubMenu, aHost )
  152.   {
  153.     var cmds = this._getMenu(aSubMenu);
  154.     return cmds.m_Ids.length;
  155.   },
  156.  
  157.   getCommandId: function( aSubMenu, aIndex, aHost )
  158.   {
  159.     var cmds = this._getMenu(aSubMenu);
  160.     if ( aIndex >= cmds.m_Ids.length ) return "";
  161.     return cmds.m_Ids[ aIndex ];
  162.   },
  163.  
  164.   getCommandType: function( aSubMenu, aIndex, aHost )
  165.   {
  166.     var cmds = this._getMenu(aSubMenu);
  167.     if ( aIndex >= cmds.m_Ids.length ) return "";
  168.     return cmds.m_Types[ aIndex ];
  169.   },
  170.  
  171.   getCommandText: function( aSubMenu, aIndex, aHost )
  172.   {
  173.     var cmds = this._getMenu(aSubMenu);
  174.     if ( aIndex >= cmds.m_Names.length ) return "";
  175.     return cmds.m_Names[ aIndex ];
  176.   },
  177.  
  178.   getCommandFlex: function( aSubMenu, aIndex, aHost )
  179.   {
  180.     var cmds = this._getMenu(aSubMenu);
  181.     if ( cmds.m_Types[ aIndex ] == "separator" ) return 1;
  182.     return 0;
  183.   },
  184.  
  185.   getCommandToolTipText: function( aSubMenu, aIndex, aHost )
  186.   {
  187.     var cmds = this._getMenu(aSubMenu);
  188.     if ( aIndex >= cmds.m_Tooltips.length ) return "";
  189.     return cmds.m_Tooltips[ aIndex ];
  190.   },
  191.  
  192.   getCommandValue: function( aSubMenu, aIndex, aHost )
  193.   {
  194.   },
  195.  
  196.   instantiateCustomCommand: function( aDocument, aId, aHost )
  197.   {
  198.     return null;
  199.   },
  200.  
  201.   refreshCustomCommand: function( aElement, aId, aHost )
  202.   {
  203.   },
  204.  
  205.   getCommandVisible: function( aSubMenu, aIndex, aHost )
  206.   {
  207.     return true;
  208.   },
  209.  
  210.   getCommandFlag: function( aSubmenu, aIndex, aHost )
  211.   {
  212.     return false;
  213.   },
  214.  
  215.   getCommandChoiceItem: function( aChoiceMenu, aHost )
  216.   {
  217.     return "";
  218.   },
  219.  
  220.   getCommandEnabled: function( aSubMenu, aIndex, aHost )
  221.   {
  222.     if (this.m_Context.m_Playlist.mediaListView.selection.count == 0)
  223.       return false;
  224.     var cmds = this._getMenu(aSubMenu);
  225.     return (aIndex in cmds.m_Enableds) && cmds.m_Enableds[ aIndex ];
  226.   },
  227.  
  228.   getCommandShortcutModifiers: function ( aSubMenu, aIndex, aHost )
  229.   {
  230.     var cmds = this._getMenu(aSubMenu);
  231.     if ( aIndex >= cmds.m_Modifiers.length ) return "";
  232.     return cmds.m_Modifiers[ aIndex ];
  233.   },
  234.  
  235.   getCommandShortcutKey: function ( aSubMenu, aIndex, aHost )
  236.   {
  237.     var cmds = this._getMenu(aSubMenu);
  238.     if ( aIndex >= cmds.m_Keys.length ) return "";
  239.     return cmds.m_Keys[ aIndex ];
  240.   },
  241.  
  242.   getCommandShortcutKeycode: function ( aSubMenu, aIndex, aHost )
  243.   {
  244.     var cmds = this._getMenu(aSubMenu);
  245.     if ( aIndex >= cmds.m_Keycodes.length ) return "";
  246.     return cmds.m_Keycodes[ aIndex ];
  247.   },
  248.  
  249.   getCommandShortcutLocal: function ( aSubMenu, aIndex, aHost )
  250.   {
  251.     return true;
  252.   },
  253.  
  254.   getCommandSubObject: function ( aSubMenu, aIndex, aHost )
  255.   {
  256.     var cmds = this._getMenu(aSubMenu);
  257.     if ( aIndex >= cmds.m_PlaylistCommands.length ) return null;
  258.     return cmds.m_PlaylistCommands[ aIndex ];
  259.   },
  260.  
  261.   onCommand: function( aSubMenu, aIndex, aHost, id, value )
  262.   {
  263.     if ( id )
  264.     {
  265.       // ADDTOPLAYLIST
  266.       if (this.m_addToPlaylist.handleCommand(id)) return;
  267.  
  268.       // ...
  269.     }
  270.   },
  271.  
  272.   // The object registered with the sbIPlaylistCommandsManager interface acts
  273.   // as a template for instances bound to specific playlist elements
  274.  
  275.   dupObject: function (obj) {
  276.     var r = {};
  277.     for ( var i in obj )
  278.     {
  279.       r[ i ] = obj[ i ];
  280.     }
  281.     return r;
  282.   },
  283.  
  284.   duplicate: function()
  285.   {
  286.     var obj = this.dupObject(this);
  287.     obj.m_Context = this.dupObject(this.m_Context);
  288.     return obj;
  289.   },
  290.  
  291.   initCommands: function(aHost) {
  292.     if (!this.m_addToPlaylist) {
  293.       this.m_addToPlaylist = new addToPlaylistHelper();
  294.       this.m_addToPlaylist.init(this);
  295.     }
  296.   },
  297.  
  298.   shutdownCommands: function() {
  299.     if (!this.m_addToPlaylist) {
  300.       dump("this.m_addToPlaylist is null in SBPlaylistCommand_AddToPlaylist ?!!\n");
  301.       return;
  302.     }
  303.     this.m_addToPlaylist.shutdown();
  304.     this.m_addToPlaylist = null;
  305.     this.m_Context = null;
  306.   },
  307.  
  308.   setContext: function( context )
  309.   {
  310.     var playlist = context.playlist;
  311.     var window = context.window;
  312.  
  313.     // Ah.  Sometimes, things are being secure.
  314.  
  315.     if ( playlist && playlist.wrappedJSObject )
  316.       playlist = playlist.wrappedJSObject;
  317.  
  318.     if ( window && window.wrappedJSObject )
  319.       window = window.wrappedJSObject;
  320.  
  321.     this.m_Context.m_Playlist = playlist;
  322.     this.m_Context.m_Window = window;
  323.   },
  324.  
  325.   QueryInterface : function(aIID)
  326.   {
  327.     if (!aIID.equals(Components.interfaces.sbIPlaylistCommands) &&
  328.         !aIID.equals(Components.interfaces.nsISupportsWeakReference) &&
  329.         !aIID.equals(Components.interfaces.nsISupports))
  330.     {
  331.       throw Components.results.NS_ERROR_NO_INTERFACE;
  332.     }
  333.  
  334.     return this;
  335.   }
  336. }; // SBPlaylistCommand_AddToPlaylist declaration
  337.  
  338. // Same object, different display text.
  339. var SBPlaylistCommand_DownloadToPlaylist = SBPlaylistCommand_AddToPlaylist.duplicate();
  340. SBPlaylistCommand_DownloadToPlaylist.m_root_commands = {
  341.   m_Types: new Array
  342.   (
  343.     ADDTOPLAYLIST_MENU_TYPE
  344.   ),
  345.  
  346.   m_Ids: new Array
  347.   (
  348.     ADDTOPLAYLIST_MENU_ID
  349.   ),
  350.  
  351.   m_Names: new Array
  352.   (
  353.     DOWNLOADTOPLAYLIST_MENU_NAME
  354.   ),
  355.  
  356.   m_Tooltips: new Array
  357.   (
  358.     DOWNLOADTOPLAYLIST_MENU_TOOLTIP
  359.   ),
  360.  
  361.   m_Keys: new Array
  362.   (
  363.     DOWNLOADTOPLAYLIST_MENU_KEY
  364.   ),
  365.  
  366.   m_Keycodes: new Array
  367.   (
  368.     DOWNLOADTOPLAYLIST_MENU_KEYCODE
  369.   ),
  370.  
  371.   m_Enableds: new Array
  372.   (
  373.     true
  374.   ),
  375.  
  376.   m_Modifiers: new Array
  377.   (
  378.     DOWNLOADTOPLAYLIST_MENU_MODIFIERS
  379.   ),
  380.  
  381.   m_PlaylistCommands: new Array
  382.   (
  383.     null
  384.   )
  385. };
  386.  
  387.  
  388. function addToPlaylistHelper() {
  389. }
  390.  
  391. addToPlaylistHelper.prototype.constructor = addToPlaylistHelper;
  392.  
  393. addToPlaylistHelper.prototype = {
  394.   m_listofplaylists: null,
  395.   m_commands: null,
  396.   m_reglist: null,
  397.   m_libraryManager: null,
  398.  
  399.   LOG: function(str) {
  400.     var consoleService = Components.classes['@mozilla.org/consoleservice;1']
  401.                             .getService(Components.interfaces.nsIConsoleService);
  402.     consoleService.logStringMessage(str);
  403.   },
  404.   init: function(aCommands) {
  405.     this.m_libraryManager = Components.classes["@songbirdnest.com/Songbird/library/Manager;1"]
  406.                             .getService(Components.interfaces.sbILibraryManager);
  407.     this.m_libraryManager.addListener(this);
  408.     this.m_commands = aCommands;
  409.     this.makeListOfPlaylists();
  410.   },
  411.  
  412.   shutdown: function() {
  413.     this.m_libraryManager.removeListener(this);
  414.     this.removeLibraryListeners();
  415.     this.m_libraryManager = null;
  416.   },
  417.  
  418.   removeLibraryListeners: function() {
  419.     if (this.m_reglist) {
  420.       for (var i in this.m_reglist) {
  421.         this.m_reglist[i].removeListener(this);
  422.       }
  423.     }
  424.     this.m_reglist = new Array();
  425.   },
  426.  
  427.   makeListOfPlaylists: function( ) {
  428.     // remove previous listeners
  429.     this.removeLibraryListeners();
  430.  
  431.     // todo: make this smarter :(
  432.     var typearray = new Array('simple');
  433.  
  434.     this.m_listofplaylists = {};
  435.     this.m_listofplaylists.m_Types = new Array();
  436.     this.m_listofplaylists.m_Ids = new Array();
  437.     this.m_listofplaylists.m_Names = new Array();
  438.     this.m_listofplaylists.m_Tooltips = new Array();
  439.     this.m_listofplaylists.m_Enableds = new Array();
  440.     this.m_listofplaylists.m_Modifiers = new Array();
  441.     this.m_listofplaylists.m_Keys = new Array();
  442.     this.m_listofplaylists.m_Keycodes = new Array();
  443.     this.m_listofplaylists.m_PlaylistCommands = new Array();
  444.  
  445.     var libs = this.m_libraryManager.getLibraries();
  446.     while (libs.hasMoreElements()) {
  447.       var library = libs.getNext().QueryInterface(
  448.         Components.interfaces.sbILibrary);
  449.       library.addListener(this, false);
  450.       this.m_reglist.push(library);
  451.       this.makePlaylistsForLibrary(library, typearray);
  452.     }
  453.  
  454.     if (this.m_listofplaylists.m_Types.length == 0) {
  455.       this.m_listofplaylists.m_Types.push("action");
  456.       this.m_listofplaylists.m_Ids.push("noplaylist");
  457.       this.m_listofplaylists.m_Names.push("&command.addtoplaylist.noexistingplaylist");
  458.       this.m_listofplaylists.m_Tooltips.push("&command.tooltip.addtoplaylist.noexistingplaylist");
  459.       this.m_listofplaylists.m_Enableds.push(false);
  460.       this.m_listofplaylists.m_Modifiers.push("");
  461.       this.m_listofplaylists.m_Keys.push("");
  462.       this.m_listofplaylists.m_Keycodes.push("");
  463.       this.m_listofplaylists.m_PlaylistCommands.push(null);
  464.     }
  465.  
  466.     this.m_listofplaylists.m_Types.push("separator");
  467.     this.m_listofplaylists.m_Ids.push("separator");
  468.     this.m_listofplaylists.m_Names.push("separator");
  469.     this.m_listofplaylists.m_Tooltips.push("separator");
  470.     this.m_listofplaylists.m_Enableds.push(true);
  471.     this.m_listofplaylists.m_Modifiers.push("");
  472.     this.m_listofplaylists.m_Keys.push("");
  473.     this.m_listofplaylists.m_Keycodes.push("");
  474.     this.m_listofplaylists.m_PlaylistCommands.push(null);
  475.  
  476.     this.m_listofplaylists.m_Types.push("action");
  477.     this.m_listofplaylists.m_Ids.push(ADDTOPLAYLIST_NEWPLAYLIST_COMMAND_ID);
  478.     this.m_listofplaylists.m_Names.push("&command.addtoplaylist.createnew");
  479.     this.m_listofplaylists.m_Tooltips.push("&command.addtoplaylist.createnew");
  480.     this.m_listofplaylists.m_Enableds.push(true);
  481.     this.m_listofplaylists.m_Modifiers.push("");
  482.     this.m_listofplaylists.m_Keys.push("");
  483.     this.m_listofplaylists.m_Keycodes.push("");
  484.     this.m_listofplaylists.m_PlaylistCommands.push(null);
  485.   },
  486.  
  487.   makePlaylistsForLibrary: function(aLibrary, typearray) {
  488.     this._makingList = true;
  489.     var listener = {
  490.       obj: this,
  491.       items: [],
  492.       _downloadListGUID: null,
  493.       onEnumerationBegin: function() {
  494.         var ddh =
  495.           Components.classes["@songbirdnest.com/Songbird/DownloadDeviceHelper;1"]
  496.                     .getService(Components.interfaces.sbIDownloadDeviceHelper);
  497.         var downloadMediaList = ddh.getDownloadMediaList();
  498.         if (downloadMediaList)
  499.           this._downloadListGUID = downloadMediaList.guid;
  500.  
  501.         this._libraryServicePane =
  502.           Components.classes['@songbirdnest.com/servicepane/library;1']
  503.           .getService(Components.interfaces.sbILibraryServicePaneService);
  504.       },
  505.       onEnumerationEnd: function() { },
  506.       onEnumeratedItem: function(list, item) {
  507.         var hidden = item.getProperty("http://songbirdnest.com/data/1.0#hidden");
  508.         if (hidden == "1") {
  509.           return Components.interfaces.sbIMediaListEnumerationListener.CONTINUE;
  510.         }
  511.         var goodtype = false;
  512.         for (var i in typearray) {
  513.           if (typearray[i] == item.type) {
  514.             goodtype = true;
  515.             break;
  516.           }
  517.         }
  518.         if (!goodtype) {
  519.           return Components.interfaces.sbIMediaListEnumerationListener.CONTINUE;
  520.         }
  521.  
  522.         // XXXsteve Prevent the download playlist from appearing in the list.
  523.         // this should be fixable once we close bug 4017 and have a way to
  524.         // interrogate the policy on each playlist to see if it should be
  525.         // put in this menu
  526.         if (item.guid == this._downloadListGUID) {
  527.           return Components.interfaces.sbIMediaListEnumerationListener.CONTINUE;
  528.         }
  529.  
  530.         // XXXlone also prevent playlists that do not have a corresponding node in
  531.         // the service pane from appearing (or those whose node, or parent nodes are
  532.         // hidden). This filters out remote playlists, as well as 'utility' extension
  533.         // playlists. This should be a fairly good test for discriminating which
  534.         // playlists are useful as sento targets, since it mirror the user's ability
  535.         // to drag and drop to them. Eventually this should be fixed by testing the
  536.         // policy on the playlist once we close bug 4017.
  537.         function isHidden(node) {
  538.           while (node) {
  539.             if (node.hidden) return true;
  540.             node = node.parentNode;
  541.           }
  542.           return false;
  543.         }
  544.         var node = this._libraryServicePane.getNodeForLibraryResource(item);
  545.         if (!node || isHidden(node)) {
  546.           return Components.interfaces.sbIMediaListEnumerationListener.CONTINUE;
  547.         }
  548.  
  549.         this.obj.m_listofplaylists.m_Types.push("action");
  550.         this.obj.m_listofplaylists.m_Ids.push(ADDTOPLAYLIST_COMMAND_ID + item.library.guid + ";" + item.guid);
  551.         this.obj.m_listofplaylists.m_Names.push(item.name ? item.name : "Unnamed Playlist");
  552.         this.obj.m_listofplaylists.m_Tooltips.push(item.name ? item.name : "Unnamed Playlist");
  553.         this.obj.m_listofplaylists.m_Enableds.push(item.userEditable);
  554.         this.obj.m_listofplaylists.m_Modifiers.push("");
  555.         this.obj.m_listofplaylists.m_Keys.push("");
  556.         this.obj.m_listofplaylists.m_Keycodes.push("");
  557.         this.obj.m_listofplaylists.m_PlaylistCommands.push(null);
  558.  
  559.         return Components.interfaces.sbIMediaListEnumerationListener.CONTINUE;
  560.       }
  561.     };
  562.  
  563.     try {
  564.  
  565.       // Enumerate all lists in this library
  566.       aLibrary.enumerateItemsByProperty("http://songbirdnest.com/data/1.0#isList", "1",
  567.                                         listener );
  568.     } catch (e) {
  569.       // this may happen if a playlist was leaked, and is still there
  570.       // in the aether refreshing its commands, ignore failure, this list
  571.       // will never show up anymore anyway
  572.     }
  573.     this._makingList = false;
  574.   },
  575.  
  576.   handleGetMenu: function(aSubMenu) {
  577.     if (this.m_listofplaylists == null) {
  578.       // handleGetMenu called before makeListOfPlaylists, this would cause infinite recursion :
  579.       // the command object would not find the menu either, would return null to getMenu which
  580.       // corresponds to the root menu, and it'd recurse infinitly.
  581.       throw Components.results.NS_ERROR_FAILURE;
  582.     }
  583.     if (aSubMenu == ADDTOPLAYLIST_MENU_ID) return this.m_listofplaylists;
  584.     return null;
  585.   },
  586.  
  587.   handleCommand: function(id) {
  588.     try {
  589.       var context = this.m_commands.m_Context;
  590.       if (id == ADDTOPLAYLIST_NEWPLAYLIST_COMMAND_ID) {
  591.         var newMediaList = context.m_Window.makeNewPlaylist("simple");
  592.         this.addToPlaylist(newMediaList.library.guid, newMediaList.guid, context.m_Playlist);
  593.         return true;
  594.       }
  595.       var addtoplstr = ADDTOPLAYLIST_COMMAND_ID;
  596.       if ( id.slice(0, addtoplstr.length) == addtoplstr) {
  597.         var r = id.slice(addtoplstr.length);
  598.         var guids = r.split(';');
  599.         if (guids.length >= 2) {
  600.           var libraryguid = guids[0];
  601.           var playlistguid = guids[1];
  602.  
  603.           this.addToPlaylist(libraryguid, playlistguid, context.m_Playlist);
  604.  
  605.           return true;
  606.         }
  607.       }
  608.     } catch (e) {
  609.       Components.utils.reportError(e);
  610.     }
  611.     return false;
  612.   },
  613.  
  614.   addToPlaylist: function(libraryguid, playlistguid, sourceplaylist) {
  615.     var library = this.m_libraryManager.getLibrary(libraryguid);
  616.     var medialist;
  617.     if (libraryguid == playlistguid)
  618.       medialist = library;
  619.     else
  620.       medialist = library.getMediaItem(playlistguid);
  621.  
  622.     if (medialist) {
  623.  
  624.       var oldLength = medialist.length;
  625.       var selection = sourceplaylist.mediaListView.selection.selectedIndexedMediaItems;
  626.  
  627.       // Create an enumerator that wraps the enumerator we were handed since
  628.       // the enumerator we get hands back sbIIndexedMediaItem, not just plain
  629.       // 'ol sbIMediaItems
  630.       var unwrapper = createUnwrapper(selection);
  631.  
  632.       var asyncListener = {
  633.         onProgress: function(aItemsProcessed, aComplete) {
  634.           DNDUtils.reportAddedTracks(aItemsProcessed,
  635.                                      0, /* no duplicate reporting */
  636.                                      0, /* no unsupported reporting */
  637.                                      medialist.name);
  638.         },
  639.         onItemAdded: function(aMediaItem) {},
  640.         onComplete: function() {},
  641.         QueryInterface: XPCOMUtils.generateQI([Ci.sbIMediaListAsyncListener])
  642.       }
  643.  
  644.       medialist.addMediaItems(unwrapper, asyncListener, true);
  645.     }
  646.   },
  647.  
  648.   //-----------------------------------------------------------------------------
  649.   _inbatch       : false,
  650.   _deferredevent : false,
  651.   _makingList    : false,
  652.  
  653.   refreshCommands: function() {
  654.  
  655.     // Bug fixers beware! This code gets called very frequently and can have
  656.     // difficult-to-debug side effects. Proceed with caution.
  657.  
  658.     var self = this;
  659.     function ensureRefreshExists() {
  660.       // Explicitly return true or false. Otherwise, JS will complain to the
  661.       // error console if any of the checks finds an undefined property.
  662.       return (self.m_commands &&
  663.               self.m_commands.m_Context &&
  664.               self.m_commands.m_Context.m_Playlist &&
  665.               self.m_commands.m_Context.m_Playlist.refreshCommands) ?
  666.               true : false;
  667.     }
  668.  
  669.     /* We need to ensure that the context and playlist have been initialized
  670.      * and are ready to display commands before calling refreshCommands.
  671.      *
  672.      * It is possible for an event handler to fire before the binding
  673.      * is created or after it is destroyed, so it is possible for this to
  674.      * be triggered before m_Playlist is fully instantiated or after pieces
  675.      * of it have gone away.  Thus, we need to also check that refreshCommands
  676.      * is present to ensure the playlist binding is in a good state. */
  677.  
  678.     if (ensureRefreshExists()) {
  679.       this.makeListOfPlaylists();
  680.       // Check again, as the playlist binding can be destroyed during the
  681.       // previous function call.
  682.       if (ensureRefreshExists()) {
  683.         this.m_commands.m_Context.m_Playlist.refreshCommands();
  684.       }
  685.     }
  686.   },
  687.  
  688.   onUpdateEvent: function(item) {
  689.     if (this._makingList) return;
  690.     if (item instanceof Components.interfaces.sbIMediaList) {
  691.       if (this._inbatch) {
  692.         // if we are in a batch, remember that we saw a playlist event in it
  693.         this._deferredevent = true;
  694.       } else {
  695.         // if we're not in a batch, proceed with refreshing the commands
  696.         this.refreshCommands();
  697.       }
  698.     }
  699.   },
  700.  
  701.   QueryInterface: function QueryInterface(iid) {
  702.     if (!iid.equals(Components.interfaces.sbIMediaListListener) &&
  703.         !iid.equals(Components.interfaces.sbILibraryManagerListener) &&
  704.         !iid.equals(Components.interfaces.nsISupports))
  705.       throw Components.results.NS_ERROR_NO_INTERFACE;
  706.     return this;
  707.   },
  708.  
  709.   onItemAdded: function onItemAdded(list, item, index) {
  710.     // If we are in a batch, ignore future notifications
  711.     if (!this._inbatch) {
  712.       this.onUpdateEvent(item);
  713.     }
  714.     return true;
  715.   },
  716.  
  717.   onBeforeItemRemoved: function onBeforeItemRemoved(list, item, index) {
  718.     return true;
  719.   },
  720.  
  721.   onAfterItemRemoved: function onAfterItemRemoved(list, item, index) {
  722.     // If we are in a batch, ignore future notifications
  723.     if (!this._inbatch) {
  724.       this.onUpdateEvent(item);
  725.     }
  726.     return true;
  727.   },
  728.  
  729.   onItemUpdated: function onItemUpdated(list, item, properties) {
  730.     // If we are in a batch, ignore future notifications
  731.     if (!this._inbatch) {
  732.       this.onUpdateEvent(item);
  733.     }
  734.     return true;
  735.   },
  736.  
  737.   onItemMoved: function onItemMoved(list, fromIndex, toIndex) {
  738.     // XXXsteve Do we need to do anything here?
  739.     return true;
  740.   },
  741.  
  742.   onBeforeListCleared: function onBeforeListCleared(list, excludeLists) {
  743.     return true;
  744.   },
  745.  
  746.   onListCleared: function onListCleared(list, excludeLists) {
  747.     // If we are in a batch, ignore future notifications
  748.     if (!this._inbatch) {
  749.       this.onUpdateEvent(list);
  750.     }
  751.     return true;
  752.   },
  753.  
  754.   onBatchBegin: function onBatchBegin(list) {
  755.     // start deferring the events
  756.     this._inbatch = true;
  757.     this._deferredevent = false;
  758.   },
  759.  
  760.   onBatchEnd: function onBatchEnd(list) {
  761.     // stop deferring the events
  762.     this._inbatch = false;
  763.     // if an event was deferred, handle it
  764.     if (this._deferredevent) {
  765.       // since we're no longer in a batch, this does call refreshCommands
  766.       this.onUpdateEvent(list);
  767.     }
  768.     this._deferredevent = false;
  769.   },
  770.  
  771.   onLibraryRegistered: function onLibraryRegistered(library) {
  772.     this.onUpdateEvent(library);
  773.   },
  774.  
  775.   onLibraryUnregistered: function onLibraryUnregistered(library) {
  776.     this.onUpdateEvent(library);
  777.   }
  778.  
  779. };
  780.