home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February / PCWorld_2008-02_cd.bin / temacd / songbird / Songbird_0.4_windows-i686.exe / components / sbFaceplateManager.js < prev    next >
Text File  |  2007-12-21  |  14KB  |  510 lines

  1. /** vim: ts=2 sw=2 expandtab
  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. /**
  28.  * \file sbFaceplateManager.js
  29.  * \brief Manages the lifecycle of faceplate pane bindings
  30.  */
  31.  
  32.  
  33. const Cc = Components.classes;
  34. const Ci = Components.interfaces;
  35. const Cr = Components.results;
  36. const Ce = Components.Exception;
  37.  
  38. const URL_BINDING_DEFAULT_PANE = "chrome://songbird/content/bindings/facePlate.xml#default-pane";
  39. const URL_BINDING_DASHBOARD_PANE = "chrome://songbird/content/bindings/facePlate.xml#playback-pane"; 
  40.  
  41. const DATAREMOTE_PLAYBACK = "faceplate.playing";
  42.  
  43.  
  44.  
  45. /**
  46.  * \class ArrayEnumerator
  47.  * \brief Wraps a js array in an nsISimpleEnumerator
  48.  */
  49. function ArrayEnumerator(array) 
  50. {
  51.   this.data = array;
  52. }
  53. ArrayEnumerator.prototype = {
  54.     
  55.   index: 0,
  56.     
  57.   getNext: function() {
  58.     return this.data[this.index++];
  59.   },
  60.  
  61.   /**
  62.    *  Gah. nsISimpleEnumerator uses hasMoreElements, but 
  63.    *  nsIStringEnumerator uses hasMore.  
  64.    */
  65.   hasMoreElements: function() {
  66.     if (this.index < this.data.length)
  67.       return true;
  68.     else
  69.       return false;
  70.   },
  71.   hasMore: function () {
  72.     return this.hasMoreElements();
  73.   },
  74.   
  75.   QueryInterface: function(iid)
  76.   {
  77.     if (!iid.equals(Ci.nsISimpleEnumerator) &&
  78.         !iid.equals(Ci.nsIStringEnumerator) &&        
  79.         !iid.equals(Ci.nsISupports))
  80.       throw Components.results.NS_ERROR_NO_INTERFACE;
  81.     return this;
  82.   }
  83. }
  84.  
  85.  
  86.  
  87.  
  88.  
  89.  
  90. /**
  91.  * \class FaceplatePane
  92.  * \brief An implementation of nsIFaceplatePane.  Acts as a single
  93.  *        representation the many instances of a faceplate pane.
  94.  * \sa sbIFaceplatePane 
  95.  */
  96. function FaceplatePane(aID, aName, aBindingURL) 
  97. {
  98.   this._id = aID;
  99.   this._name = aName;
  100.   this._bindingURL = aBindingURL;
  101.   this._data = {};
  102.   this._observers = [];
  103. }
  104. FaceplatePane.prototype = {
  105.  
  106.   _id: null,
  107.   _name: null,
  108.   _bindingURL: null,
  109.  
  110.   _data: null,
  111.   
  112.   _observers: null,
  113.   
  114.   get id() { return this._id; },
  115.   get name() { return this._name; },
  116.   get bindingURL() { return this._bindingURL; }, 
  117.   
  118.   /**
  119.    * \sa sbIFaceplatePane
  120.    */
  121.   
  122.   setData: function FaceplatePane_setData(aKey, aValue) {
  123.     if (!aKey) {
  124.       throw Components.results.NS_ERROR_INVALID_ARG;      
  125.     }
  126.     
  127.     this._data[aKey] = aValue;
  128.     this._notify(aKey);
  129.   },
  130.  
  131.   getData: function FaceplatePane_getData(aKey) {
  132.     return this._data[aKey];
  133.   },
  134.  
  135.   getKeys: function FaceplatePane_getKeys() {
  136.     // Copy all the data keys into an array, and then return an enumerator
  137.     return new ArrayEnumerator( [key for (key in this._data)] );     
  138.   },
  139.   
  140.   addObserver: function FaceplatePane_addObserver(aObserver) {
  141.     if (! (aObserver instanceof Ci.nsIObserver)) {
  142.       throw Components.results.NS_ERROR_INVALID_ARG;
  143.     }
  144.     if (this._observers.indexOf(aObserver) == -1) {
  145.       this._observers.push(aObserver);
  146.     }
  147.   },
  148.   
  149.   removeObserver: function FaceplatePane_removeObserver(aObserver) {
  150.     var index = this._observers.indexOf(aObserver);
  151.     if (index > -1) {
  152.       this._observers.splice(index,1);
  153.     }     
  154.   },
  155.   
  156.   _notify: function FaceplatePane_notify(topic) {
  157.     var thisPane = this.QueryInterface(Ci.sbIFaceplatePane);
  158.     this._observers.forEach( function (observer) {
  159.       observer.observe(thisPane, topic, null);
  160.     });    
  161.   },
  162.   
  163.   QueryInterface: function(iid)
  164.   {
  165.     if (!iid.equals(Components.interfaces.sbIFaceplatePane) &&
  166.         !iid.equals(Components.interfaces.nsISupports))
  167.       throw Components.results.NS_ERROR_NO_INTERFACE;
  168.     return this;
  169.   }
  170. }
  171.  
  172.  
  173.  
  174.  
  175.  
  176.  
  177. /**
  178.  * \class FaceplateManager
  179.  * \brief Manages the lifecycle of faceplate panes
  180.  * \sa sbIFaceplateManager
  181.  */
  182. function FaceplateManager() {
  183.  
  184.   var os      = Components.classes["@mozilla.org/observer-service;1"]
  185.                       .getService(Components.interfaces.nsIObserverService);
  186.   
  187.   // We want to wait till profile-after-change to initialize
  188.   os.addObserver(this, 'profile-after-change', false);
  189.  
  190.   // We need to unhook things on shutdown
  191.   os.addObserver(this, "quit-application", false);
  192.   
  193.   this._listeners = [];
  194.   this._panes = {};
  195.  
  196. };
  197. FaceplateManager.prototype = {
  198.   constructor: FaceplateManager,
  199.  
  200.   _listeners: null,
  201.   _paneCount: 0,
  202.   
  203.   // Map of pane IDs to sbIFaceplatePanes
  204.   _panes: null,
  205.   
  206.   // Most recently shown pane ID
  207.   _defaultPaneID: null,
  208.   
  209.   /**
  210.    * Initialize the faceplate manager. Called after profile load.
  211.    * Sets up the intro pane and playback dashboard pane.
  212.    */
  213.   _init: function init() {
  214.  
  215.     // Come up with some localized names for the default panes.
  216.     var strings = Cc["@mozilla.org/intl/stringbundle;1"]
  217.                   .getService(Ci.nsIStringBundleService)
  218.                   .createBundle("chrome://songbird/locale/songbird.properties"); 
  219.     var getString = function(aStringId, aDefault) {
  220.       try {
  221.         return strings.GetStringFromName(aStringId);
  222.       } catch (e) {
  223.         return aDefault;
  224.       }
  225.     }
  226.     var introName = getString("faceplate.pane.intro.name", "Intro");
  227.     var dashboardName = getString("faceplate.pane.dashboard.name", "Dashboard");
  228.     
  229.     // Create the panes
  230.     this.createPane("songbird-intro", introName, URL_BINDING_DEFAULT_PANE);
  231.     this.createPane("songbird-dashboard", dashboardName, URL_BINDING_DASHBOARD_PANE);
  232.     
  233.     // Set up a dataremote to show the dashboard on first playback.
  234.     // XXX: This needs to change. There shouldn't be an faceplate dataremotes.
  235.     // The PlaylistPlayback service should not know about faceplates.
  236.     var createDataRemote =  new Components.Constructor(
  237.                                    "@songbirdnest.com/Songbird/DataRemote;1",
  238.                                    Components.interfaces.sbIDataRemote, "init");
  239.     
  240.     this._playbackDataRemote = createDataRemote(DATAREMOTE_PLAYBACK, null);
  241.     this._playbackDataRemote.bindObserver(this, true);
  242.   },
  243.   
  244.   /**
  245.    * Called on xpcom-shutdown
  246.    */
  247.   _deinit: function deinit() {
  248.     this._listeners = null;
  249.     this._panes = null;
  250.     if (this._playbackDataRemote) {
  251.       this._playbackDataRemote.unbind();
  252.       this._playbackDataRemote = null;
  253.     }
  254.   },
  255.  
  256.   
  257.   /**
  258.    * \sa sbIFaceplateManager
  259.    */  
  260.   
  261.   get paneCount() {
  262.     return this._paneCount;
  263.   },
  264.     
  265.   createPane:  function FaceplateManager_createPane(aID, aName, aBindingURL) {
  266.     if (!aID || !aName || !aBindingURL) {
  267.       throw Components.results.NS_ERROR_INVALID_ARG;
  268.     }
  269.     if (this._panes[aID]) {
  270.       throw Components.results.NS_ERROR_FAILURE;
  271.     }
  272.     
  273.     var pane = new FaceplatePane(aID, aName, aBindingURL);
  274.     this._panes[aID] = pane;
  275.     this._paneCount++;
  276.     
  277.     // Announce this faceplate to the world.  All listening faceplate
  278.     // widgets should receive this message and then instantiate
  279.     // the requested binding
  280.     this._onCreate(pane);
  281.     
  282.     return pane;
  283.   },
  284.     
  285.   showPane: function FaceplateManager_showPane(aPane) {
  286.     if (!aPane || !aPane.id || !this._panes[aPane.id]) {
  287.       throw Components.results.NS_ERROR_INVALID_ARG;
  288.     }
  289.     this._defaultPaneID = aPane.id;
  290.     this._onShow(this._panes[aPane.id]);
  291.   },
  292.   
  293.   destroyPane: function FaceplateManager_destroyPane(aPane) {
  294.     if (!aPane || !aPane.id || !this._panes[aPane.id]) {
  295.       throw Components.results.NS_ERROR_INVALID_ARG;
  296.     }
  297.     var pane = this._panes[aPane.id];
  298.     delete this._panes[pane.id];
  299.     this._paneCount--;
  300.     this._onDestroy(pane);
  301.   },
  302.   
  303.   getPane: function FaceplateManager_getPane(aID) {
  304.     return this._panes[aID];
  305.   },
  306.   
  307.   getPanes: function FaceplateManager_getPanes() {
  308.     // Copy all the faceplates into an array, and then return an enumerator
  309.     return new ArrayEnumerator( [this._panes[key] for (key in this._panes)] ); 
  310.   },
  311.   
  312.   getDefaultPane: function FaceplateManager_getDefaultPane() {
  313.     var pane = this.getPane(this._defaultPaneID);
  314.     if (!pane) {
  315.       pane = this.getPane("songbird-intro");
  316.     }
  317.     return pane;
  318.   },
  319.   
  320.   addListener: function FaceplateManager_addListener(aListener) {
  321.     if (! (aListener instanceof Ci.sbIFaceplateManagerListener)) {
  322.       throw Components.results.NS_ERROR_INVALID_ARG;
  323.     }
  324.     if (this._listeners.indexOf(aListener) == -1) {
  325.       this._listeners.push(aListener);
  326.     }
  327.   },
  328.  
  329.   removeListener: function FaceplateManager_removeListener(aListener) {
  330.     var index = this._listeners.indexOf(aListener);
  331.     if (index > -1) {
  332.       this._listeners.splice(index,1);
  333.     }    
  334.   },
  335.     
  336.    
  337.   /**
  338.    * Broadcasts a notification to create the given faceplate pane.
  339.    */
  340.   _onCreate: function FaceplateManager__onCreate(aFaceplatePane) {
  341.     aFaceplatePane = aFaceplatePane.QueryInterface(Ci.sbIFaceplatePane);
  342.     this._listeners.forEach( function (listener) {
  343.       listener.onCreatePane(aFaceplatePane);
  344.     });
  345.   },
  346.  
  347.   /**
  348.    * Broadcasts a notification to show the given faceplate pane.
  349.    */
  350.   _onShow: function FaceplateManager__onShow(aFaceplatePane) {
  351.     aFaceplatePane = aFaceplatePane.QueryInterface(Ci.sbIFaceplatePane);    
  352.     this._listeners.forEach( function (listener) {
  353.       listener.onShowPane(aFaceplatePane);
  354.     });
  355.   },
  356.   
  357.   /**
  358.    * Broadcasts a notification to destory the given faceplate.
  359.    */
  360.   _onDestroy: function FaceplateManager__onDestroy(aFaceplatePane) {
  361.     aFaceplatePane = aFaceplatePane.QueryInterface(Ci.sbIFaceplatePane);    
  362.     this._listeners.forEach( function (listener) {
  363.       listener.onDestroyPane(aFaceplatePane);
  364.     });
  365.   },
  366.   
  367.   
  368.   /**
  369.    * Cause the playback dashboard pane to show in all 
  370.    * faceplates.  Called by a dataremote when playback 
  371.    * starts for the first time.
  372.    */  
  373.   _showDashboardPane: function FaceplateManager__showDashboardPane() {
  374.     var pane = this.getPane("songbird-dashboard");
  375.     if (pane) {
  376.       this.showPane(pane);
  377.     } else {
  378.       dump("FaceplateManager__showDashboardPane: dashboard not found\n");
  379.     }
  380.   },
  381.   
  382.   
  383.   /**
  384.    * Called by dataremotes and the observer service.
  385.    */
  386.   observe: function(subject, topic, data) {
  387.     var os      = Components.classes["@mozilla.org/observer-service;1"]
  388.                       .getService(Components.interfaces.nsIObserverService);
  389.     switch (topic) {
  390.     case "profile-after-change":
  391.       os.removeObserver(this, "profile-after-change");
  392.       this._init();
  393.       break;
  394.     case "quit-application":
  395.       os.removeObserver(this, "quit-application");
  396.       this._deinit();
  397.       break;
  398.     // When playback begins for the first time, jump
  399.     // to the playback dashboard
  400.     case DATAREMOTE_PLAYBACK:
  401.       if (this._playbackDataRemote.boolValue) {
  402.         this._playbackDataRemote.unbind();
  403.         this._playbackDataRemote = null;
  404.         this._showDashboardPane();
  405.       }
  406.     }
  407.   },
  408.  
  409.   /**
  410.    * See nsISupports.idl
  411.    */
  412.   QueryInterface: function(iid) {
  413.     if (!iid.equals(Components.interfaces.sbIFaceplateManager) &&
  414.         !iid.equals(Components.interfaces.nsIObserver) && 
  415.         !iid.equals(Components.interfaces.nsISupports))
  416.       throw Components.results.NS_ERROR_NO_INTERFACE;
  417.     return this;
  418.   }
  419. }; // FaceplateManager.prototype
  420.  
  421.  
  422.  
  423.  
  424.  
  425.  
  426. /**
  427.  * \brief XPCOM initialization code
  428.  */
  429. function makeGetModule(CONSTRUCTOR, CID, CLASSNAME, CONTRACTID, CATEGORIES) {
  430.   return function (comMgr, fileSpec) {
  431.     return {
  432.       registerSelf : function (compMgr, fileSpec, location, type) {
  433.         compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  434.         compMgr.registerFactoryLocation(CID,
  435.                         CLASSNAME,
  436.                         CONTRACTID,
  437.                         fileSpec,
  438.                         location,
  439.                         type);
  440.         if (CATEGORIES && CATEGORIES.length) {
  441.           var catman =  Cc["@mozilla.org/categorymanager;1"]
  442.               .getService(Ci.nsICategoryManager);
  443.           for (var i=0; i<CATEGORIES.length; i++) {
  444.             var e = CATEGORIES[i];
  445.             catman.addCategoryEntry(e.category, e.entry, e.value, 
  446.               true, true);
  447.           }
  448.         }
  449.       },
  450.  
  451.       getClassObject : function (compMgr, cid, iid) {
  452.         if (!cid.equals(CID)) {
  453.           throw Cr.NS_ERROR_NO_INTERFACE;
  454.         }
  455.  
  456.         if (!iid.equals(Ci.nsIFactory)) {
  457.           throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  458.         }
  459.  
  460.         return this._factory;
  461.       },
  462.  
  463.       _factory : {
  464.         createInstance : function (outer, iid) {
  465.           if (outer != null) {
  466.             throw Cr.NS_ERROR_NO_AGGREGATION;
  467.           }
  468.           return (new CONSTRUCTOR()).QueryInterface(iid);
  469.         }
  470.       },
  471.  
  472.       unregisterSelf : function (compMgr, location, type) {
  473.         compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  474.         compMgr.unregisterFactoryLocation(CID, location);
  475.         if (CATEGORIES && CATEGORIES.length) {
  476.           var catman =  Cc["@mozilla.org/categorymanager;1"]
  477.               .getService(Ci.nsICategoryManager);
  478.           for (var i=0; i<CATEGORIES.length; i++) {
  479.             var e = CATEGORIES[i];
  480.             catman.deleteCategoryEntry(e.category, e.entry, true);
  481.           }
  482.         }
  483.       },
  484.  
  485.       canUnload : function (compMgr) {
  486.         return true;
  487.       },
  488.  
  489.       QueryInterface : function (iid) {
  490.         if ( !iid.equals(Ci.nsIModule) ||
  491.              !iid.equals(Ci.nsISupports) )
  492.           throw Cr.NS_ERROR_NO_INTERFACE;
  493.         return this;
  494.       }
  495.  
  496.     };
  497.   }
  498. }
  499.  
  500. var NSGetModule = makeGetModule (
  501.   FaceplateManager,
  502.   Components.ID("{eb5c665a-bfe2-49f0-a747-cd3554e55606}"),
  503.   "Songbird Faceplate Pane Manager Service",
  504.   "@songbirdnest.com/faceplate/manager;1",
  505.   [{
  506.     category: 'app-startup',
  507.     entry: 'faceplate-pane-manager',
  508.     value: 'service,@songbirdnest.com/faceplate/manager;1'
  509.   }]);
  510.