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 / sbSearchSuggester.js < prev    next >
Text File  |  2007-12-21  |  15KB  |  536 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. /**
  28.  * \file sbSearchSuggester.js
  29.  * Provides autocomplete suggestions based on player state/context
  30.  * Originally based on the Mozilla nsSearchSuggestions.js implementation
  31.  */ 
  32.  
  33.  
  34.  
  35. const BROWSER_SUGGEST_PREF = "browser.search.suggest.enabled";
  36. const XPCOM_SHUTDOWN_TOPIC              = "xpcom-shutdown";
  37. const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
  38.  
  39. const SONGBIRD_DATAREMOTE_CONTRACTID = "@songbirdnest.com/Songbird/DataRemote;1";
  40. const sbIDataRemote            = Components.interfaces.sbIDataRemote;
  41.  
  42.  
  43. const SEARCH_SUGGEST_CONTRACTID =
  44.   "@mozilla.org/autocomplete/search;1?name=songbird-autocomplete";
  45. const SEARCH_SUGGEST_CLASSNAME = "Songbird Search Suggestions";
  46. const SEARCH_SUGGEST_CLASSID =
  47.   Components.ID("{0be64502-ee00-11db-8314-0800200c9a66}");
  48.  
  49. const SEARCH_BUNDLE = "chrome://songbird/locale/songbird.properties";
  50.  
  51. const Cc = Components.classes;
  52. const Ci = Components.interfaces;
  53. const Cr = Components.results;
  54.  
  55.  
  56. /**
  57.  * AutoCompleteResult contains the results returned by the Suggest
  58.  * service - it implements nsIAutoCompleteResult and is used by the auto-
  59.  * complete controller to populate the front end.
  60.  * @constructor
  61.  */
  62. function AutoCompleteResult(searchString,
  63.                                    defaultIndex,
  64.                                    errorDescription,
  65.                                    results,
  66.                                    comments) {
  67.   this._searchString = searchString;
  68.   this._defaultIndex = defaultIndex;
  69.   this._errorDescription = errorDescription;
  70.   this._results = results;
  71.   this._comments = comments;
  72. }
  73. AutoCompleteResult.prototype = {
  74.   /**
  75.    * The user's query string
  76.    * @private
  77.    */
  78.   _searchString: "",
  79.  
  80.   /**
  81.    * The default item that should be entered if none is selected
  82.    * @private
  83.    */
  84.   _defaultIndex: 0,
  85.  
  86.   /**
  87.    * The reason the search failed
  88.    * @private
  89.    */
  90.   _errorDescription: "",
  91.  
  92.   /**
  93.    * The list of words returned by the Suggest Service
  94.    * @private
  95.    */
  96.   _results: [],
  97.  
  98.   /**
  99.    * The list of Comments (number of results - or page titles) returned by the
  100.    * Suggest Service.
  101.    * @private
  102.    */
  103.   _comments: [],
  104.  
  105.  
  106.   /**
  107.    * @return the user's query string
  108.    */
  109.   get searchString() {
  110.     return this._searchString;
  111.   },
  112.  
  113.   /**
  114.    * @return the result code of this result object, either:
  115.    *         RESULT_IGNORED   (invalid searchString)
  116.    *         RESULT_FAILURE   (failure)
  117.    *         RESULT_NOMATCH   (no matches found)
  118.    *         RESULT_SUCCESS   (matches found)
  119.    */
  120.   get searchResult() {
  121.     if (this._results.length > 0) {
  122.       return Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
  123.     } else {
  124.       Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
  125.     }
  126.   },
  127.  
  128.   /**
  129.    * @return the default item that should be entered if none is selected
  130.    */
  131.   get defaultIndex() {
  132.     return this._defaultIndex;
  133.   },
  134.  
  135.   /**
  136.    * @return the reason the search failed
  137.    */
  138.   get errorDescription() {
  139.     return this._errorDescription;
  140.   },
  141.  
  142.   /**
  143.    * @return the number of results
  144.    */
  145.   get matchCount() {
  146.     return this._results.length;
  147.   },
  148.  
  149.   /**
  150.    * Retrieves a result
  151.    * @param  index    the index of the result requested
  152.    * @return          the result at the specified index
  153.    */
  154.   getValueAt: function(index) {
  155.     return this._results[index];
  156.   },
  157.  
  158.   /**
  159.    * Retrieves a comment (metadata instance)
  160.    * @param  index    the index of the comment requested
  161.    * @return          the comment at the specified index
  162.    */
  163.   getCommentAt: function(index) {
  164.     return this._comments[index];
  165.   },
  166.  
  167.   /**
  168.    * Retrieves a style hint specific to a particular index.
  169.    * @param  index    the index of the style hint requested
  170.    * @return          the style hint at the specified index
  171.    */
  172.   getStyleAt: function(index) {
  173.     if (!this._comments[index])
  174.       return null;  // not a category label, so no special styling
  175.  
  176.     if (index == 0)
  177.       return "suggestfirst";  // category label on first line of results
  178.  
  179.     return "suggesthint";   // category label on any other line of results
  180.   },
  181.  
  182.   /** 
  183.    * Retrieves an image url. 
  184.    * @param  index    the index of the image url requested 
  185.    * @return          the image url at the specified index 
  186.    */ 
  187.   getImageAt: function(index) { 
  188.     return ""; 
  189.   },
  190.      
  191.   /**
  192.    * Removes a result from the resultset
  193.    * @param  index    the index of the result to remove
  194.    */
  195.   removeValueAt: function(index, removeFromDatabase) {
  196.     this._results.splice(index, 1);
  197.     this._comments.splice(index, 1);
  198.   },
  199.  
  200.   /**
  201.    * Part of nsISupports implementation.
  202.    * @param   iid     requested interface identifier
  203.    * @return  this object (XPConnect handles the magic of telling the caller that
  204.    *                       we're the type it requested)
  205.    */
  206.   QueryInterface: function(iid) {
  207.     if (!iid.equals(Ci.nsIAutoCompleteResult) &&
  208.         !iid.equals(Ci.nsISupports))
  209.       throw Cr.NS_ERROR_NO_INTERFACE;
  210.     return this;
  211.   }
  212. };
  213.  
  214.  
  215.  
  216.  
  217.  
  218.  
  219. /**
  220.  * Implements nsIAutoCompleteSearch to provide suggestions based 
  221.  * on Songbird's state.
  222.  *
  223.  * To access this suggester set autocompletesearch="songbird-autocomplete"
  224.  * on an autocomplete textbox.  See the search.xml binding for details.
  225.  *
  226.  * @constructor
  227.  */
  228. function SearchSuggester() {
  229.   this._addObservers();
  230.   this._loadSuggestPref();
  231.   this._loadDataRemotes();
  232. }
  233. SearchSuggester.prototype = {
  234.  
  235.   /**
  236.    * this._strings is the string bundle for message internationalization.
  237.    */
  238.   get _strings() {
  239.     if (!this.__strings) {
  240.       var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
  241.                 getService(Ci.nsIStringBundleService);
  242.  
  243.       this.__strings = sbs.createBundle(SEARCH_BUNDLE);
  244.     }
  245.     return this.__strings;
  246.   },
  247.   __strings: null,
  248.  
  249.   /**
  250.    * Search suggestions will be shown if this._suggestEnabled is true.
  251.    */
  252.   _loadSuggestPref: function SAC_loadSuggestPref() {
  253.     var prefService = Cc["@mozilla.org/preferences-service;1"].
  254.                       getService(Ci.nsIPrefBranch);
  255.     this._suggestEnabled = prefService.getBoolPref(BROWSER_SUGGEST_PREF);
  256.   },
  257.   _suggestEnabled: null,
  258.  
  259.   // Metadata for the current playing track in Songbird
  260.   _metadataTitle:  null,
  261.   _metadataArtist: null,
  262.   _metadataAlbum:  null,
  263.   
  264.   /**
  265.    * Load metadata dataremotes
  266.    */
  267.   _loadDataRemotes: function SAC_loadDataRemotes() {
  268.   
  269.     // TODO: Is this an issue?  Should I be waiting for profile load before doing this?
  270.     var createDataRemote = new Components.Constructor( 
  271.                 SONGBIRD_DATAREMOTE_CONTRACTID, sbIDataRemote, "init");
  272.     this._metadataTitle   = createDataRemote("metadata.title", null);
  273.     this._metadataArtist  = createDataRemote("metadata.artist", null);
  274.     this._metadataAlbum   = createDataRemote("metadata.album", null);
  275.   },  
  276.   
  277.   /**
  278.    * Let go of metadata dataremotes just in case
  279.    */
  280.   _releaseDataRemotes: function() {
  281.     this._metadataTitle.unbind();
  282.     this._metadataArtist.unbind();
  283.     this._metadataAlbum.unbind();
  284.   },  
  285.  
  286.  
  287.   /**
  288.    * The object implementing nsIAutoCompleteObserver that we notify when
  289.    * we have found results
  290.    * @private
  291.    */
  292.   _listener: null,
  293.  
  294.  
  295.   /**
  296.    * Notifies the front end of new results.
  297.    * @param searchString  the user's query string
  298.    * @param results       an array of results to the search
  299.    * @param comments      an array of metadata corresponding to the results
  300.    * @private
  301.    */
  302.   onSearchResult: function(searchString, results, comments) {
  303.     if (this._listener) {
  304.       var result = new AutoCompleteResult(
  305.           searchString,
  306.           0,
  307.           "",
  308.           results,
  309.           comments);
  310.  
  311.       this._listener.onSearchResult(this, result);
  312.  
  313.       // Null out listener to make sure we don't notify it twice, in case our
  314.       // timer callback still hasn't run.
  315.       this._listener = null;
  316.     }
  317.   },
  318.  
  319.  
  320.   /**
  321.    * Initiates the search result gathering process. Part of
  322.    * nsIAutoCompleteSearch implementation.
  323.    *
  324.    * @param searchString    the user's query string
  325.    * @param searchParam     unused, "an extra parameter"; even though
  326.    *                        this parameter and the next are unused, pass
  327.    *                        them through in case the form history
  328.    *                        service wants them
  329.    * @param previousResult  unused, a client-cached store of the previous
  330.    *                        generated resultset for faster searching.
  331.    * @param listener        object implementing nsIAutoCompleteObserver which
  332.    *                        we notify when results are ready.
  333.    */
  334.   startSearch: function(searchString, searchParam, previousResult, listener) {
  335.     this.stopSearch();
  336.     
  337.     var searchService = Cc["@mozilla.org/browser/search-service;1"].
  338.                         getService(Ci.nsIBrowserSearchService);
  339.  
  340.     // If there's an existing request, stop it
  341.     this.stopSearch();
  342.  
  343.     this._listener = listener;
  344.  
  345.     var results = [];
  346.  
  347.     var engine = searchService.currentEngine;
  348.  
  349.     // Normally we would do something asynchronous, but since 
  350.     // for now all we're returning is dataremote values, we
  351.     // might as well just do it immediately.
  352.     
  353.     // If there is no search query then get some default suggestions
  354.     if (searchString == "") {
  355.       results = this._getPlayerContextSuggestions();    
  356.     }
  357.     
  358.     // TODO Add a localized comment
  359.     var comments = [];
  360.     for (var i = 0; i < results.length; i++) {
  361.       comments.push("");
  362.     }
  363.  
  364.     this.onSearchResult(searchString, results, comments);    
  365.   },
  366.  
  367.   /**
  368.    * Ends the search result gathering process. Part of nsIAutoCompleteSearch
  369.    * implementation.
  370.    */
  371.   stopSearch: function() {
  372.     // Nothing to do since we return our searches immediately.
  373.   },
  374.  
  375.  
  376.   /**
  377.    * Get a list of suggestions to display regardless of the search query
  378.    */
  379.   _getPlayerContextSuggestions: function() {
  380.     var results = [];
  381.     
  382.     // TODO: Do not return metadata unless playing or paused!
  383.     // Currently returns metadata from the previous session.
  384.     
  385.     if (this._metadataTitle.stringValue != "") {
  386.       results.push(this._metadataTitle.stringValue);
  387.     }
  388.     if (this._metadataAlbum.stringValue != "") {
  389.       results.push(this._metadataAlbum.stringValue);
  390.     }
  391.     if (this._metadataArtist.stringValue != "") {
  392.       results.push(this._metadataArtist.stringValue);
  393.     }
  394.     return results;
  395.   },
  396.  
  397.  
  398.   /**
  399.    * nsIObserver
  400.    */
  401.   observe: function SAC_observe(aSubject, aTopic, aData) {
  402.     switch (aTopic) {
  403.       case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
  404.         this._loadSuggestPref();
  405.         break;
  406.       case XPCOM_SHUTDOWN_TOPIC:
  407.         this.stopSearch();
  408.         this._removeObservers();
  409.         this._releaseDataRemotes();
  410.         break;
  411.     }
  412.   },
  413.  
  414.   _addObservers: function SAC_addObservers() {
  415.     var prefService2 = Cc["@mozilla.org/preferences-service;1"].
  416.                        getService(Ci.nsIPrefBranch2);
  417.     prefService2.addObserver(BROWSER_SUGGEST_PREF, this, false);
  418.  
  419.     var os = Cc["@mozilla.org/observer-service;1"].
  420.              getService(Ci.nsIObserverService);
  421.     os.addObserver(this, XPCOM_SHUTDOWN_TOPIC, false);
  422.   },
  423.  
  424.   _removeObservers: function SAC_removeObservers() {
  425.     var prefService2 = Cc["@mozilla.org/preferences-service;1"].
  426.                        getService(Ci.nsIPrefBranch2);
  427.     prefService2.removeObserver(BROWSER_SUGGEST_PREF, this);
  428.  
  429.     var os = Cc["@mozilla.org/observer-service;1"].
  430.              getService(Ci.nsIObserverService);
  431.     os.removeObserver(this, XPCOM_SHUTDOWN_TOPIC);
  432.   },
  433.  
  434.   /**
  435.    * Part of nsISupports implementation.
  436.    * @param   iid     requested interface identifier
  437.    * @return  this object (XPConnect handles the magic of telling the caller that
  438.    *                       we're the type it requested)
  439.    */
  440.   QueryInterface: function(iid) {
  441.     if (!iid.equals(Ci.nsIAutoCompleteSearch) &&
  442.         !iid.equals(Ci.nsIObserver) &&
  443.         !iid.equals(Ci.nsISupports))
  444.       throw Cr.NS_ERROR_NO_INTERFACE;
  445.     return this;
  446.   }
  447. };
  448.  
  449.  
  450.  
  451. var gModule = {
  452.   /**
  453.    * Registers all the components supplied by this module. Part of nsIModule
  454.    * implementation.
  455.    * @param componentManager  the XPCOM component manager
  456.    * @param location          the location of the module on disk
  457.    * @param loaderString      opaque loader specific string
  458.    * @param type              loader type being used to load this module
  459.    */
  460.   registerSelf: function(componentManager, location, loaderString, type) {
  461.     if (this._firstTime) {
  462.       this._firstTime = false;
  463.       throw Cr.NS_ERROR_FACTORY_REGISTER_AGAIN;
  464.     }
  465.     componentManager =
  466.       componentManager.QueryInterface(Ci.nsIComponentRegistrar);
  467.  
  468.     for (var key in this.objects) {
  469.       var obj = this.objects[key];
  470.       componentManager.registerFactoryLocation(obj.CID, obj.className, obj.contractID,
  471.                                                location, loaderString, type);
  472.     }
  473.   },
  474.  
  475.   /**
  476.    * Retrieves a Factory for the given ClassID. Part of nsIModule
  477.    * implementation.
  478.    * @param componentManager  the XPCOM component manager
  479.    * @param cid               the ClassID of the object for which a factory
  480.    *                          has been requested
  481.    * @param iid               the IID of the interface requested
  482.    */
  483.   getClassObject: function(componentManager, cid, iid) {
  484.     if (!iid.equals(Ci.nsIFactory))
  485.       throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  486.  
  487.     for (var key in this.objects) {
  488.       if (cid.equals(this.objects[key].CID))
  489.         return this.objects[key].factory;
  490.     }
  491.  
  492.     throw Cr.NS_ERROR_NO_INTERFACE;
  493.   },
  494.  
  495.   /**
  496.    * Create a Factory object that can construct an instance of an object.
  497.    * @param constructor   the constructor used to create the object
  498.    * @private
  499.    */
  500.   _makeFactory: function(constructor) {
  501.     function createInstance(outer, iid) {
  502.       if (outer != null)
  503.         throw Cr.NS_ERROR_NO_AGGREGATION;
  504.       return (new constructor()).QueryInterface(iid);
  505.     }
  506.     return { createInstance: createInstance };
  507.   },
  508.  
  509.   /**
  510.    * Determines whether or not this module can be unloaded.
  511.    * @return returning true indicates that this module can be unloaded.
  512.    */
  513.   canUnload: function(componentManager) {
  514.     return true;
  515.   }
  516. };
  517.  
  518. /**
  519.  * Entry point for registering the components supplied by this JavaScript
  520.  * module.
  521.  * @param componentManager  the XPCOM component manager
  522.  * @param location          the location of this module on disk
  523.  */
  524. function NSGetModule(componentManager, location) {
  525.   // Metadata about the objects this module can construct
  526.   gModule.objects = {
  527.     search: {
  528.       CID: SEARCH_SUGGEST_CLASSID,
  529.       contractID: SEARCH_SUGGEST_CONTRACTID,
  530.       className: SEARCH_SUGGEST_CLASSNAME,
  531.       factory: gModule._makeFactory(SearchSuggester)
  532.     },
  533.   };
  534.   return gModule;
  535. }
  536.