sbColumnSpecParser.jsm
Go to the documentation of this file.
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 
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 
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 
193 
194  if (!columns || !columns.columnMap.length) {
195  throw new Error("Couldn't get columnMap!");
196  }
197 
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 
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 }
function getColumns()
this _columns
__defineGetter__("Application", function(){delete Application;Application=Cc["@mozilla.org/fuel/application;1"].getService(Ci.fuelIApplication);return Application;}) function ColumnSpecParser(aMediaList
var Application
Definition: sbAboutDRM.js:37
var pref
Definition: openLocation.js:44
const Cc
var columns
return null
Definition: FeedWriter.js:1143
const Ci
EXPORTED_SYMBOLS