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

  1. /*
  2.  *=BEGIN SONGBIRD GPL
  3.  *
  4.  * This file is part of the Songbird web player.
  5.  *
  6.  * Copyright(c) 2005-2010 POTI, Inc.
  7.  * http://www.songbirdnest.com
  8.  *
  9.  * This file may be licensed under the terms of of the
  10.  * GNU General Public License Version 2 (the ``GPL'').
  11.  *
  12.  * Software distributed under the License is distributed
  13.  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
  14.  * express or implied. See the GPL for the specific language
  15.  * governing rights and limitations.
  16.  *
  17.  * You should have received a copy of the GPL along with this
  18.  * program. If not, go to http://www.gnu.org/licenses/gpl.html
  19.  * or write to the Free Software Foundation, Inc.,
  20.  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21.  *
  22.  *=END SONGBIRD GPL
  23.  */
  24.  
  25. Components.utils.import("resource://app/jsmodules/sbProperties.jsm");
  26.  
  27. EXPORTED_SYMBOLS = ["ColumnSpecParser"];
  28.  
  29. const Cc = Components.classes;
  30. const Ci = Components.interfaces;
  31.  
  32. // FUEL is not built-in in JSMs
  33. __defineGetter__("Application", function() {
  34.   delete Application;
  35.   Application = Cc["@mozilla.org/fuel/application;1"]
  36.                   .getService(Ci.fuelIApplication);
  37.   return Application;
  38. })
  39.  
  40. /**
  41.  * Construct a column spec parser and read the column specification immediately
  42.  *
  43.  * @param aMediaList The media list to find columns to display for
  44.  * @param aPlaylist the playlist binding
  45.  * @param aMask a bitfield containing the source to look for; see the
  46.  *              ColumnSpecParser.ORIGIN_* constants
  47.  * @param [optional] aConstraint the media type constraint to use, if any
  48.  */
  49. function ColumnSpecParser(aMediaList, aPlaylist, aMask, aConstraint) {
  50.  
  51.   if (!aMask) {
  52.     aMask = ~0 >>> 0;
  53.   }
  54.  
  55.   // Our new "column spec" is a whitespace separated string that looks like
  56.   // this:
  57.   //
  58.   // "propertyID [20] [a] propertyID [40] [d]"
  59.   //
  60.   // The width and sort direction are optional.
  61.  
  62.   // Try to get this info from the following places:
  63.   //      1: "columnSpec+(constraint)" property on the media list
  64.   //      2: if a constraint is given, preference indicated by
  65.   //         XUL "useColumnSpecPreference" attribute
  66.   //      3: "defaultColumnSpec+(constraint)" property on the medialist
  67.   //      4: "columnSpec+(constraint)" property on the medialist library
  68.   //      5: "defaultColumnSpec+(constraint)" property on the medialist library
  69.   //      6: "columnSpec" property on the medialist
  70.   //      7: Preference indicated by XUL "useColumnSpecPreference" attribute
  71.   //      8: "defaultColumnSpec" property on the medialist
  72.   //      9: "columnSpec" property on the medialist library
  73.   //     10: "defaultColumnSpec" property on the medialist library
  74.   //     11: XUL "columnSpec" attribute
  75.   //     12: Our last-ditch hardcoded list
  76.  
  77.   // use a function so we can do early returns from it
  78.   var self = this;
  79.   function getColumns() {
  80.     var cols = null;
  81.  
  82.     /**
  83.      * check for a column spec, with a given constraint
  84.      * @param aPossibleConstraint the constraint to use; may be empty string
  85.      *                            to look for a column spec with no constraints
  86.      * @param aFlag the flag to be added to the column spec origin
  87.      * @return the parsed column spec, or null if not found
  88.      */
  89.     function checkWithConstraint(aPossibleConstraint, aFlag) {
  90.  
  91.       if (aMask & self.ORIGIN_PROPERTY) {
  92.         let prop = aMediaList.getProperty(SBProperties.columnSpec +
  93.                                             aPossibleConstraint);
  94.         cols = self._getColumnMap(prop, self.ORIGIN_PROPERTY | aFlag);
  95.         if (cols && cols.columnMap.length) {
  96.           return cols;
  97.         }
  98.       }
  99.  
  100.       // we explicitly do not want to support using constraints with attributes
  101.       // hence we also don't add the flag, since no constraint would be used
  102.       if (aMask & self.ORIGIN_PREFERENCES) {
  103.         if (aPlaylist && aPlaylist.hasAttribute("useColumnSpecPreference")) {
  104.           let pref = aPlaylist.getAttribute("useColumnSpecPreference");
  105.           cols = self._getColumnMap(Application.prefs.getValue(pref, null),
  106.                                     self.ORIGIN_PREFERENCES);
  107.           if (cols && cols.columnMap.length) {
  108.             return cols;
  109.           }
  110.         }
  111.       }
  112.  
  113.       if (aMask & self.ORIGIN_MEDIALISTDEFAULT) {
  114.         let prop = aMediaList.getProperty(SBProperties.defaultColumnSpec +
  115.                                             aPossibleConstraint);
  116.         cols = self._getColumnMap(prop, self.ORIGIN_MEDIALISTDEFAULT | aFlag);
  117.         if (cols && cols.columnMap.length) {
  118.           return cols;
  119.         }
  120.       }
  121.  
  122.       if (aMask & self.ORIGIN_LIBRARY) {
  123.         let prop = aMediaList.library
  124.                              .getProperty(SBProperties.columnSpec +
  125.                                             aPossibleConstraint)
  126.         cols = self._getColumnMap(prop, self.ORIGIN_LIBRARY | aFlag);
  127.         if (cols && cols.columnMap.length) {
  128.           return cols;
  129.         }
  130.       }
  131.  
  132.       if (aMask & self.ORIGIN_LIBRARYDEFAULT) {
  133.         let prop = aMediaList.library
  134.                              .getProperty(SBProperties.defaultColumnSpec +
  135.                                             aPossibleConstraint);
  136.         cols = self._getColumnMap(prop, self.ORIGIN_LIBRARYDEFAULT | aFlag);
  137.         if (cols && cols.columnMap.length) {
  138.           return cols;
  139.         }
  140.       }
  141.       return null;
  142.     }
  143.  
  144.     if (aConstraint) {
  145.       cols = checkWithConstraint("+(" + aConstraint + ")",
  146.                                  self.ORIGIN_ONLY_CONSTRAINT);
  147.       if (cols && cols.columnMap.length) {
  148.         return cols;
  149.       }
  150.     }
  151.  
  152.     cols = checkWithConstraint("", 0);
  153.     if (cols && cols.columnMap.length) {
  154.       return cols;
  155.     }
  156.  
  157.     if (aMask & self.ORIGIN_ATTRIBUTE) {
  158.       if (aPlaylist) {
  159.         cols = self._getColumnMap(aPlaylist.getAttribute("columnSpec"),
  160.                                   self.ORIGIN_ATTRIBUTE);
  161.       }
  162.       if (cols && cols.columnMap.length) {
  163.         return cols;
  164.       }
  165.     }
  166.  
  167.     // nope, can't find anything at all; use hard coded fallback
  168.     switch (aConstraint) {
  169.       case "video":
  170.         return self._getColumnMap([SBProperties.trackName, 229, "a",
  171.                                    SBProperties.duration, 45,
  172.                                    SBProperties.genre, 101,
  173.                                    SBProperties.year, 45,
  174.                                    SBProperties.rating, 90,
  175.                                    SBProperties.comment, 291,
  176.                                   ].join(" "),
  177.                                   self.ORIGIN_DEFAULT);
  178.       // Show the source column by default except for video
  179.       default:
  180.         return self._getColumnMap([SBProperties.trackName, 229,
  181.                                    SBProperties.duration, 45,
  182.                                    SBProperties.artistName, 137, "a",
  183.                                    SBProperties.albumName, 210,
  184.                                    SBProperties.genre, 90,
  185.                                    SBProperties.rating, 90,
  186.                                    SBProperties.trackType, 78,
  187.                                   ].join(" "),
  188.                                   self.ORIGIN_DEFAULT);
  189.     }
  190.   }
  191.  
  192.   var columns = getColumns();
  193.  
  194.   if (!columns || !columns.columnMap.length) {
  195.     throw new Error("Couldn't get columnMap!");
  196.   }
  197.  
  198.   this._columns = columns;
  199. }
  200.  
  201. ColumnSpecParser.prototype = {
  202.  
  203.   _columns: null,
  204.   _columnSpecOrigin: null,
  205.  
  206.   ORIGIN_PROPERTY:         1 << 0,
  207.   ORIGIN_PREFERENCES:      1 << 1,
  208.   ORIGIN_MEDIALISTDEFAULT: 1 << 2,
  209.   ORIGIN_LIBRARYDEFAULT:   1 << 3,
  210.   ORIGIN_LIBRARY:          1 << 4,
  211.   ORIGIN_ATTRIBUTE:        1 << 5,
  212.   ORIGIN_DEFAULT:          1 << 6,
  213.  
  214.   // add this bit mask to enable only column spec sources that depend on the
  215.   // constraint given
  216.   ORIGIN_ONLY_CONSTRAINT:  1 << 31,
  217.  
  218.   get columnMap() {
  219.     return this._columns.columnMap;
  220.   },
  221.  
  222.   get origin() {
  223.     return this._columnSpecOrigin;
  224.   },
  225.  
  226.   get sortID() {
  227.     return this._columns.sortID;
  228.   },
  229.  
  230.   get sortIsAscending() {
  231.     return this._columns.sortIsAscending;
  232.   },
  233.  
  234.   _getColumnMap: function(columnSpec, columnSpecOrigin) {
  235.     var columns = {
  236.       columnMap: [],
  237.       sortID: null,
  238.       sortIsAscending: null
  239.     }
  240.  
  241.     if (columnSpec) {
  242.       try {
  243.         columns = ColumnSpecParser.parseColumnSpec(columnSpec);
  244.         this._columnSpecOrigin = columnSpecOrigin;
  245.       }
  246.       catch (e) {
  247.         Components.utils.reportError(e);
  248.       }
  249.     }
  250.  
  251.     return columns;
  252.   }
  253.  
  254. }
  255.  
  256. ColumnSpecParser.parseColumnSpec = function(spec) {
  257.  
  258.   var sortID;
  259.   var sortIsAscending;
  260.  
  261.   // strip leading and trailing whitespace.
  262.   var strippedSpec = spec.match(/^\s*(.*?)\s*$/);
  263.   if (!strippedSpec.length > 0)
  264.     throw new Error("RegEx failed to match string");
  265.  
  266.   // split based on whitespace.
  267.   var tokens = strippedSpec[1].split(/\s+/);
  268.  
  269.   var columns = [];
  270.   var columnIndex = -1;
  271.   var seenSort = false;
  272.  
  273.   for (var index = 0; index < tokens.length; index++) {
  274.     var token = tokens[index];
  275.     if (!token)
  276.       throw new Error("Zero-length token");
  277.  
  278.     if (isNaN(parseInt(token))) {
  279.       if (token.length == 1 && (token == "a" || token == "d")) {
  280.         // This is a sort specifier
  281.         if (columnIndex < 0) {
  282.           throw new Error("You passed in a bunk string!");
  283.         }
  284.         // Multiple sorts will be ignored!
  285.         if (!seenSort) {
  286.           var column = columns[columnIndex];
  287.           column.sort = token == "a" ? "ascending" : "descending";
  288.           seenSort = true;
  289.           sortID = column.property;
  290.           sortIsAscending = token == "a";
  291.         }
  292.       }
  293.       else {
  294.         // This is a property name.
  295.         columns[++columnIndex] = { property: token, sort: null };
  296.       }
  297.     }
  298.     else {
  299.       // This is a width specifier
  300.       if (columnIndex < 0) {
  301.         throw new Error("You passed in a bunk string!");
  302.       }
  303.       var column = columns[columnIndex];
  304.       column.width = token;
  305.     }
  306.   }
  307.  
  308.   var result = {
  309.     columnMap: columns,
  310.     sortID: sortID,
  311.     sortIsAscending: sortIsAscending
  312.   }
  313.  
  314.   return result;
  315. }
  316.  
  317. /**
  318.  * The playlist columns are no longer locked to 100% of the screen width,
  319.  * so we often need to resize all columns when appending new ones.
  320.  *
  321.  * \param aColumnsArray Array of column objects produced by parseColumnSpec
  322.  * \param aNeededWidth Amount of room to free up in the column array
  323.  */
  324. ColumnSpecParser.reduceWidthsProportionally = function(aColumnsArray,
  325.                                                        aNeededWidth)
  326. {
  327.   var fullWidth = 0;
  328.   for each (var col in aColumnsArray) {
  329.     if (!col.width || col.width < 80) continue;
  330.     fullWidth += parseInt(col.width);
  331.   }
  332.   for each (var col in aColumnsArray) {
  333.     if (!col.width || col.width < 80) continue;
  334.     var fraction = parseInt(col.width)/fullWidth;
  335.     var subtract = fraction * aNeededWidth;
  336.     // Round down, since it is better to be too small than to overflow
  337.     // and require a scroll bar
  338.     col.width = Math.floor(parseInt(col.width) - subtract);
  339.   }
  340. }
  341.