Bug 1439900 - Move unifinder related functions to calUnifinderUtils.jsm. r=MakeMyDay

MozReview-Commit-ID: Hi53o8AJlv2
This commit is contained in:
Philipp Kewisch 2018-02-21 13:33:24 +01:00
Родитель f36f2d3e07
Коммит fa37452098
10 изменённых файлов: 381 добавлений и 220 удалений

Просмотреть файл

@ -485,6 +485,7 @@ module.exports = {
files: [
"base/modules/calEmailUtils.jsm",
"base/modules/calItipUtils.jsm",
"base/modules/calUnifinderUtils.jsm",
],
rules: {
"require-jsdoc": [2, { require: { ClassDeclaration: true } }],

Просмотреть файл

@ -989,18 +989,11 @@
<method name="sortItems">
<body><![CDATA[
if (this.mTreeView.selectedColumn) {
let modifier = (this.mTreeView.sortDirection == "descending" ? -1 : 1);
let column = this.mTreeView.selectedColumn;
cal.sortEntry.mSortKey = column.getAttribute("sortKey")
? column.getAttribute("sortKey")
: column.getAttribute("itemproperty");
let sortType = cal.getSortTypeForSortKey(cal.sortEntry.mSortKey);
let modifier = this.mTreeView.sortDirection == "descending" ? -1 : 1;
let sortKey = column.getAttribute("sortKey") || column.getAttribute("itemproperty");
// sort (key,item) entries
cal.sortEntry.mSortStartedDate = cal.dtz.now();
let entries = this.mTaskArray.map(cal.sortEntry, cal.sortEntry);
entries.sort(cal.sortEntryComparer(sortType, modifier));
this.mTaskArray = entries.map(cal.sortEntryItem);
cal.unifinder.sortItems(this.mTaskArray, sortKey, modifier);
}
this.recreateHashTable();

Просмотреть файл

@ -549,13 +549,8 @@ var unifinderTreeView = {
if (this.selectedColumn) {
let modifier = (this.sortDirection == "descending" ? -1 : 1);
let sortKey = unifinderTreeView.selectedColumn.getAttribute("itemproperty");
let sortType = cal.getSortTypeForSortKey(sortKey);
// sort (key,item) entries
cal.sortEntry.mSortKey = sortKey;
cal.sortEntry.mSortStartedDate = cal.dtz.now();
let entries = this.eventArray.map(cal.sortEntry, cal.sortEntry);
entries.sort(cal.sortEntryComparer(sortType, modifier));
this.eventArray = entries.map(cal.sortEntryItem);
cal.unifinder.sortItems(this.eventArray, sortKey, modifier);
}
this.calculateIndexMap();
},

Просмотреть файл

@ -0,0 +1,206 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
ChromeUtils.import("resource:///modules/mailServices.js");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "cal", "resource://calendar/modules/calUtils.jsm", "cal");
this.EXPORTED_SYMBOLS = ["calunifinder"]; /* exported calunifinder */
var calunifinder = {
/**
* Retrieves the value that is used for comparison for the item with the given
* property.
*
* @param {calIItemBaes} aItem The item to retrieve the sort key for
* @param {String} aKey The property name that should be sorted
* @return {*} The value used in sort comparison
*/
getItemSortKey: function(aItem, aKey) {
const taskStatus = ["NEEDS-ACTION", "IN-PROCESS", "COMPLETED", "CANCELLED"];
const eventStatus = ["TENTATIVE", "CONFIRMED", "CANCELLED"];
switch (aKey) {
case "priority":
return aItem.priority || 5;
case "title":
return aItem.title || "";
case "entryDate":
case "startDate":
case "dueDate":
case "endDate":
case "completedDate":
if (aItem[aKey] == null) {
return -62168601600000000; // ns value for (0000/00/00 00:00:00)
}
return aItem[aKey].nativeTime;
case "percentComplete":
return aItem.percentComplete;
case "categories":
return aItem.getCategories({}).join(", ");
case "location":
return aItem.getProperty("LOCATION") || "";
case "status": {
let statusSet = cal.item.isEvent(aItem) ? eventStatus : taskStatus;
return statusSet.indexOf(aItem.status);
}
case "calendar":
return aItem.calendar.name || "";
default:
return null;
}
},
/**
* Returns a sort function for the given sort type.
*
* @param {String} aSortKey The sort key to get the compare function for
* @return {Function} The function to be used for sorting values of the type
*/
sortEntryComparer: function(aSortKey) {
switch (aSortKey) {
case "title":
case "categories":
case "location":
case "calendar":
return sortCompare.string;
// All dates use "date_filled"
case "completedDate":
case "startDate":
case "endDate":
case "dueDate":
case "entryDate":
return sortCompare.date_filled;
case "priority":
case "percentComplete":
case "status":
return sortCompare.number;
default:
return sortCompare.unknown;
}
},
/**
* Sort the unifinder items by the given sort key, using the modifier to flip direction. The
* items are sorted in place.
*
* @param {calIItemBase[]} aItems The items to sort
* @param {String} aSortKey The item sort key
* @param {?Number} aModifier Either 1 or -1, to indicate sort direction
*/
sortItems: function(aItems, aSortKey, aModifier=1) {
let comparer = calunifinder.sortEntryComparer(aSortKey);
aItems.sort((a, b) => {
let sortvalA = calunifinder.getItemSortKey(a, aSortKey);
let sortvalB = calunifinder.getItemSortKey(b, aSortKey);
return comparer(sortvalA, sortvalB, aModifier);
});
}
};
/**
* Sort compare functions that can be used with Array sort(). The modifier can flip the sort
* direction by passing -1 or 1.
*/
const sortCompare = calunifinder.sortEntryComparer._sortCompare = {
/**
* Compare two things as if they were numbers.
*
* @param {*} a The first thing to compare
* @param {*} b The second thing to compare
* @param {Number} modifier -1 to flip direction, or 1
* @return {Number} Either -1, 0, or 1
*/
number: function(a, b, modifier=1) {
return sortCompare.general(Number(a), Number(b), modifier);
},
/**
* Compare two things as if they were dates.
*
* @param {*} a The first thing to compare
* @param {*} b The second thing to compare
* @param {Number} modifier -1 to flip direction, or 1
* @return {Number} Either -1, 0, or 1
*/
date: function(a, b, modifier=1) {
return sortCompare.general(a, b, modifier);
},
/**
* Compare two things generally, using the typical ((a > b) - (a < b))
*
* @param {*} a The first thing to compare
* @param {*} b The second thing to compare
* @param {Number} modifier -1 to flip direction, or 1
* @return {Number} Either -1, 0, or 1
*/
general: function(a, b, modifier=1) {
return ((a > b) - (a < b)) * modifier;
},
/**
* Compare two dates, keeping the nativeTime zero date in mind.
*
* @param {*} a The first date to compare
* @param {*} b The second date to compare
* @param {Number} modifier -1 to flip direction, or 1
* @return {Number} Either -1, 0, or 1
*/
date_filled: function(a, b, modifier=1) {
const NULL_DATE = -62168601600000000;
if (a == b) {
return 0;
} else if (a == NULL_DATE) {
return 1;
} else if (b == NULL_DATE) {
return -1;
} else {
return sortCompare.general(a, b, modifier);
}
},
/**
* Compare two strings, sorting empty values to the end by default
*
* @param {*} a The first string to compare
* @param {*} b The second string to compare
* @param {Number} modifier -1 to flip direction, or 1
* @return {Number} Either -1, 0, or 1
*/
string: function(a, b, modifier=1) {
if (a.length == 0 || b.length == 0) {
// sort empty values to end (so when users first sort by a
// column, they can see and find the desired values in that
// column without scrolling past all the empty values).
return -(a.length - b.length) * modifier;
}
let collator = cal.createLocaleCollator();
return collator.compareString(0, a, b) * modifier;
},
/**
* Catch-all function to compare two unknown values. Will return 0.
*
* @param {*} a The first thing to compare
* @param {*} b The second thing to compare
* @param {Number} modifier Provided for consistency, but unused
* @return {Number} Will always return 0
*/
unknown: function(a, b, modifier=1) {
return 0;
}
};

Просмотреть файл

@ -50,6 +50,13 @@ var cal = {
createRecurrenceInfo: _instance("@mozilla.org/calendar/recurrence-info;1",
Components.interfaces.calIRecurrenceInfo,
"item"),
createLocaleCollator: function() {
return Components.classes["@mozilla.org/intl/collation-factory;1"]
.getService(Components.interfaces.nsICollationFactory)
.CreateCollation();
},
getCalendarManager: _service("@mozilla.org/calendar/manager;1",
Components.interfaces.calICalendarManager),
getIcsService: _service("@mozilla.org/calendar/ics-service;1",
@ -186,205 +193,6 @@ var cal = {
return gCalThreadingEnabled;
},
// The below functions will move to some different place once the
// unifinder tress are consolidated.
compareNativeTime: function(a, b) {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
},
compareNativeTimeFilledAsc: function(a, b) {
if (a == b) {
return 0;
}
// In this filter, a zero time (not set) is always at the end.
if (a == -62168601600000000) { // value for (0000/00/00 00:00:00)
return 1;
}
if (b == -62168601600000000) { // value for (0000/00/00 00:00:00)
return -1;
}
return (a < b ? -1 : 1);
},
compareNativeTimeFilledDesc: function(a, b) {
if (a == b) {
return 0;
}
// In this filter, a zero time (not set) is always at the end.
if (a == -62168601600000000) { // value for (0000/00/00 00:00:00)
return 1;
}
if (b == -62168601600000000) { // value for (0000/00/00 00:00:00)
return -1;
}
return (a < b ? 1 : -1);
},
compareNumber: function(a, b) {
a = Number(a);
b = Number(b);
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
},
sortEntryComparer: function(sortType, modifier) {
switch (sortType) {
case "number":
return function(sortEntryA, sortEntryB) {
let nsA = cal.sortEntryKey(sortEntryA);
let nsB = cal.sortEntryKey(sortEntryB);
return cal.compareNumber(nsA, nsB) * modifier;
};
case "date":
return function(sortEntryA, sortEntryB) {
let nsA = cal.sortEntryKey(sortEntryA);
let nsB = cal.sortEntryKey(sortEntryB);
return cal.compareNativeTime(nsA, nsB) * modifier;
};
case "date_filled":
return function(sortEntryA, sortEntryB) {
let nsA = cal.sortEntryKey(sortEntryA);
let nsB = cal.sortEntryKey(sortEntryB);
if (modifier == 1) {
return cal.compareNativeTimeFilledAsc(nsA, nsB);
} else {
return cal.compareNativeTimeFilledDesc(nsA, nsB);
}
};
case "string":
return function(sortEntryA, sortEntryB) {
let seA = cal.sortEntryKey(sortEntryA);
let seB = cal.sortEntryKey(sortEntryB);
if (seA.length == 0 || seB.length == 0) {
// sort empty values to end (so when users first sort by a
// column, they can see and find the desired values in that
// column without scrolling past all the empty values).
return -(seA.length - seB.length) * modifier;
}
let collator = cal.createLocaleCollator();
let comparison = collator.compareString(0, seA, seB);
return comparison * modifier;
};
default:
return function(sortEntryA, sortEntryB) {
return 0;
};
}
},
getItemSortKey: function(aItem, aKey, aStartTime) {
function nativeTime(calDateTime) {
if (calDateTime == null) {
return -62168601600000000; // ns value for (0000/00/00 00:00:00)
}
return calDateTime.nativeTime;
}
switch (aKey) {
case "priority":
return aItem.priority || 5;
case "title":
return aItem.title || "";
case "entryDate":
return nativeTime(aItem.entryDate);
case "startDate":
return nativeTime(aItem.startDate);
case "dueDate":
return nativeTime(aItem.dueDate);
case "endDate":
return nativeTime(aItem.endDate);
case "completedDate":
return nativeTime(aItem.completedDate);
case "percentComplete":
return aItem.percentComplete;
case "categories":
return aItem.getCategories({}).join(", ");
case "location":
return aItem.getProperty("LOCATION") || "";
case "status":
if (cal.item.isToDo(aItem)) {
return ["NEEDS-ACTION", "IN-PROCESS", "COMPLETED", "CANCELLED"].indexOf(aItem.status);
} else {
return ["TENTATIVE", "CONFIRMED", "CANCELLED"].indexOf(aItem.status);
}
case "calendar":
return aItem.calendar.name || "";
default:
return null;
}
},
getSortTypeForSortKey: function(aSortKey) {
switch (aSortKey) {
case "title":
case "categories":
case "location":
case "calendar":
return "string";
// All dates use "date_filled"
case "completedDate":
case "startDate":
case "endDate":
case "dueDate":
case "entryDate":
return "date_filled";
case "priority":
case "percentComplete":
case "status":
return "number";
default:
return "unknown";
}
},
sortEntry: function(aItem) {
let key = cal.getItemSortKey(aItem, this.mSortKey, this.mSortStartedDate);
return { mSortKey: key, mItem: aItem };
},
sortEntryItem: function(sortEntry) {
return sortEntry.mItem;
},
sortEntryKey: function(sortEntry) {
return sortEntry.mSortKey;
},
createLocaleCollator: function() {
return Components.classes["@mozilla.org/intl/collation-factory;1"]
.getService(Components.interfaces.nsICollationFactory)
.CreateCollation();
},
/**
* Sort an array of strings according to the current locale.
* Modifies aStringArray, returning it sorted.
@ -495,6 +303,7 @@ XPCOMUtils.defineLazyModuleGetter(cal, "dtz", "resource://calendar/modules/calDa
XPCOMUtils.defineLazyModuleGetter(cal, "email", "resource://calendar/modules/calEmailUtils.jsm", "calemail");
XPCOMUtils.defineLazyModuleGetter(cal, "item", "resource://calendar/modules/calItemUtils.jsm", "calitem");
XPCOMUtils.defineLazyModuleGetter(cal, "itip", "resource://calendar/modules/calItipUtils.jsm", "calitip");
XPCOMUtils.defineLazyModuleGetter(cal, "unifinder", "resource://calendar/modules/calUnifinderUtils.jsm", "calunifinder");
XPCOMUtils.defineLazyModuleGetter(cal, "view", "resource://calendar/modules/calViewUtils.jsm", "calview");
XPCOMUtils.defineLazyModuleGetter(cal, "window", "resource://calendar/modules/calWindowUtils.jsm", "calwindow");

Просмотреть файл

@ -83,6 +83,13 @@ var migrations = {
getInvitedAttendee: "getInvitedAttendee",
getAttendeesBySender: "getAttendeesBySender"
},
unifinder: {
sortEntryComparer: "sortEntryComparer",
getItemSortKey: "getItemSortKey",
// compareNative*, compareNumber, sortEntry, sortEntryItem, sortEntryKey and
// getSortTypeForSortKey are no longer available. There is a new
// cal.unifinder.sortItems though that should do everything necessary.
},
view: {
isMouseOverBox: "isMouseOverBox",
calRadioGroupSelectItem: "radioGroupSelectItem",

Просмотреть файл

@ -20,6 +20,7 @@ EXTRA_JS_MODULES += [
'calPrintUtils.jsm',
'calProviderUtils.jsm',
'calRecurrenceUtils.jsm',
'calUnifinderUtils.jsm',
'calUtils.jsm',
'calUtilsCompat.jsm',
'calViewUtils.jsm',

Просмотреть файл

@ -2,10 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* exported do_calendar_startup, do_load_calmgr, do_load_timezoneservice,
* readJSONFile, ics_unfoldline, compareItemsSpecific, getStorageCal,
* getMemoryCal, createTodoFromIcalString, createEventFromIcalString,
* createDate, Cc, Ci, Cr, Cu
/* exported do_calendar_startup, do_load_calmgr, do_load_timezoneservice, readJSONFile,
* ics_unfoldline, dedent, compareItemsSpecific, getStorageCal, getMemoryCal,
* createTodoFromIcalString, createEventFromIcalString, createDate, Cc, Ci, Cr, Cu
*/
ChromeUtils.import("resource://gre/modules/Services.jsm");
@ -190,6 +189,43 @@ function ics_unfoldline(aLine) {
return aLine.replace(/\r?\n[ \t]/g, "");
}
/**
* Dedent the template string tagged with this function to make indented data
* easier to read. Usage:
*
* let data = dedent`
* This is indented data it will be unindented so that the first line has
* no leading spaces and the second is indented by two spaces.
* `;
*
* @param strings The string fragments from the template string
* @param ...values The interpolated values
* @return The interpolated, dedented string
*/
function dedent(strings, ...values) {
let parts = [];
// Perform variable interpolation
for (let [i, string] of strings.entries()) {
parts.push(string);
if (i < values.length) {
parts.push(values[i]);
}
}
let lines = parts.join("").split("\n");
// The first and last line is empty as in above example.
lines.shift();
lines.pop();
let minIndent = lines.reduce((min, line) => {
let match = line.match(/^(\s*)\S*/);
return Math.min(min, match[1].length);
}, Infinity);
return lines.map(line => line.substr(minIndent)).join("\n");
}
/**
* Read a JSON file and return the JS object
*/

Просмотреть файл

@ -0,0 +1,112 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
function run_test() {
test_get_item_sort_key();
test_sort_items();
}
function test_get_item_sort_key() {
let event = cal.createEvent(dedent`
BEGIN:VEVENT
PRIORITY:8
SUMMARY:summary
DTSTART:20180102T030405Z
DTEND:20180607T080910Z
CATEGORIES:a,b,c
LOCATION:location
STATUS:CONFIRMED
END:VEVENT
`);
strictEqual(cal.unifinder.getItemSortKey(event, "nothing"), null);
equal(cal.unifinder.getItemSortKey(event, "priority"), 8);
equal(cal.unifinder.getItemSortKey(event, "title"), "summary");
equal(cal.unifinder.getItemSortKey(event, "startDate"), 1514862245000000);
equal(cal.unifinder.getItemSortKey(event, "endDate"), 1528358950000000);
equal(cal.unifinder.getItemSortKey(event, "categories"), "a, b, c");
equal(cal.unifinder.getItemSortKey(event, "location"), "location");
equal(cal.unifinder.getItemSortKey(event, "status"), 1);
let task = cal.createTodo(dedent`
BEGIN:VTODO
DTSTART:20180102T030405Z
DUE:20180607T080910Z
PERCENT-COMPLETE:20
STATUS:COMPLETED
END:VTODO
`);
equal(cal.unifinder.getItemSortKey(task, "priority"), 5);
strictEqual(cal.unifinder.getItemSortKey(task, "title"), "");
equal(cal.unifinder.getItemSortKey(task, "entryDate"), 1514862245000000);
equal(cal.unifinder.getItemSortKey(task, "dueDate"), 1528358950000000);
equal(cal.unifinder.getItemSortKey(task, "completedDate"), -62168601600000000);
equal(cal.unifinder.getItemSortKey(task, "percentComplete"), 20);
strictEqual(cal.unifinder.getItemSortKey(task, "categories"), "");
strictEqual(cal.unifinder.getItemSortKey(task, "location"), "");
equal(cal.unifinder.getItemSortKey(task, "status"), 2);
let task2 = cal.createTodo(dedent`
BEGIN:VTODO
STATUS:GETTIN' THERE
END:VTODO
`);
equal(cal.unifinder.getItemSortKey(task2, "percentComplete"), 0);
equal(cal.unifinder.getItemSortKey(task2, "status"), -1);
}
function test_sort_items() {
// string comparison
let summaries = ["", "a", "b"];
let items = summaries.map(summary => {
return cal.createEvent(dedent`
BEGIN:VEVENT
SUMMARY:${summary}
END:VEVENT
`);
});
cal.unifinder.sortItems(items, "title", 1);
deepEqual(items.map(item => item.title), ["a", "b", null]);
cal.unifinder.sortItems(items, "title", -1);
deepEqual(items.map(item => item.title), [null, "b", "a"]);
// date comparison
let dates = ["20180101T000002Z", "20180101T000000Z", "20180101T000001Z"];
items = dates.map(date => {
return cal.createEvent(dedent`
BEGIN:VEVENT
DTSTART:${date}
END:VEVENT
`);
});
cal.unifinder.sortItems(items, "startDate", 1);
deepEqual(items.map(item => item.startDate.icalString),
["20180101T000000Z", "20180101T000001Z", "20180101T000002Z"]);
cal.unifinder.sortItems(items, "startDate", -1);
deepEqual(items.map(item => item.startDate.icalString),
["20180101T000002Z", "20180101T000001Z", "20180101T000000Z"]);
// number comparison
let percents = [3, 1, 2];
items = percents.map(percent => {
return cal.createTodo(dedent`
BEGIN:VTODO
PERCENT-COMPLETE:${percent}
END:VTODO
`);
});
cal.unifinder.sortItems(items, "percentComplete", 1);
deepEqual(items.map(item => item.percentComplete), [1, 2, 3]);
cal.unifinder.sortItems(items, "percentComplete", -1);
deepEqual(items.map(item => item.percentComplete), [3, 2, 1]);
}

Просмотреть файл

@ -47,6 +47,7 @@
[test_storage.js]
[test_timezone.js]
[test_timezone_definition.js]
[test_unifinder_utils.js]
[test_utils.js]
[test_view_utils.js]
[test_webcal.js]