horrible threading glitch resolved, and more!
(I promise, my commit messages will be better once we hit a stable point.)
This commit is contained in:
Родитель
64ed1ca6be
Коммит
4e73a59269
|
@ -56,7 +56,7 @@ const MSG_FLAG_NEW = 0x10000;
|
|||
const MSG_FLAG_ATTACHMENT = 0x10000000;
|
||||
|
||||
const NO_SUCH_MESSAGE_KEY = 0xFFFFFFFF; // nsMsgKey_none
|
||||
const NO_SUCH_VIEW_INDEX = -1;
|
||||
const NO_SUCH_VIEW_INDEX = 0xFFFFFFFF;
|
||||
|
||||
function messageStatusString(aFlags) {
|
||||
if (aFlags & MSG_FLAG_REPLIED)
|
||||
|
@ -103,8 +103,10 @@ function GMTreeNode(aMessage) {
|
|||
GMTreeNode.prototype = {
|
||||
setLevel: function(aLevel) {
|
||||
this.level = aLevel;
|
||||
for (let iChild=0; iChild < this.children; iChild++) {
|
||||
this.children[iChild].setLevel(aLevel+1);
|
||||
if (this.children) {
|
||||
for (let iChild=0; iChild < this.children.length; iChild++) {
|
||||
this.children[iChild].setLevel(aLevel+1);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -129,7 +131,6 @@ GMTreeNode.prototype = {
|
|||
};
|
||||
|
||||
function GlodaMsgDBView() {
|
||||
dump("GlodaMsgDBView constructor entry\n");
|
||||
this._messenger = null;
|
||||
this._msgWindow = null;
|
||||
this._commandUpdater = null;
|
||||
|
@ -138,9 +139,11 @@ dump("GlodaMsgDBView constructor entry\n");
|
|||
this._sortOrder = SORT_DESCENDING;
|
||||
this._viewFlags = Ci.nsMsgViewFlagsType.kNone;
|
||||
|
||||
this._collection = null;
|
||||
this._messages = [];
|
||||
this._toplevelNodes = [];
|
||||
this._rows = [];
|
||||
this._idToNode = null;
|
||||
|
||||
this._customColumns = {};
|
||||
this._customSortColumn = null;
|
||||
|
@ -152,19 +155,17 @@ dump("GlodaMsgDBView constructor entry\n");
|
|||
//: the currently displayed message's node
|
||||
this._displayedNode = null;
|
||||
|
||||
this._suppressDisplay = false;
|
||||
|
||||
// set up our awesome globals!
|
||||
if (Gloda === null) {
|
||||
dump("GlodaMsgDBView loading globals\n");
|
||||
let loadNS = {};
|
||||
Cu.import("resource://gloda/modules/gloda.js", loadNS);
|
||||
Gloda = loadNS.Gloda;
|
||||
Cu.import("resource://gloda/modules/utils.js", loadNS);
|
||||
GlodaUtils = loadNS.GlodaUtils;
|
||||
dump("GlodaMsgDBView globals loaded (Gloda: " + Gloda + ", GlodaUtils:" +
|
||||
GlodaUtils + "\n");
|
||||
}
|
||||
|
||||
dump("GlodaMsgDBView comparison func inits\n");
|
||||
try {
|
||||
this._initComparisonFuncs();
|
||||
}
|
||||
|
@ -172,7 +173,6 @@ dump("GlodaMsgDBView comparison func inits\n");
|
|||
dump("Exception (source: " + ex.fileName + ":" + ex.lineNumber + ") " +
|
||||
ex + "\n");
|
||||
}
|
||||
dump("GlodaMsgDBView constructor completion\n");
|
||||
}
|
||||
|
||||
GlodaMsgDBView.prototype = {
|
||||
|
@ -333,6 +333,14 @@ GlodaMsgDBView.prototype = {
|
|||
return aRec - bRec;
|
||||
};
|
||||
},
|
||||
|
||||
onItemsAdded: function gloda_mdbv_itemsAdded(aItems) {
|
||||
},
|
||||
onItemsModified: function gloda_mdbv_itemsModified(aItems) {
|
||||
},
|
||||
onItemsRemoved: function gloda_mdbv_itemsRemoved(aItems) {
|
||||
},
|
||||
|
||||
_sort: function gloda_mdbv_realSort() {
|
||||
// TODO: kShowIgnored
|
||||
// TODO: kUnreadOnly
|
||||
|
@ -340,14 +348,21 @@ GlodaMsgDBView.prototype = {
|
|||
let allExpanded = false;
|
||||
|
||||
let nodes;
|
||||
let idToNode = {};
|
||||
|
||||
if (this._isThreaded) {
|
||||
nodes = [];
|
||||
// cluster by conversation
|
||||
let conversations = {};
|
||||
let dupeCheck = {};
|
||||
|
||||
for (let iMsg=0; iMsg < this._messages.length; iMsg++) {
|
||||
let message = this._messages[iMsg];
|
||||
if (message.id in dupeCheck) {
|
||||
throw Error("DUPLICATE!");
|
||||
}
|
||||
dupeCheck[message.id] = true;
|
||||
|
||||
if (!(message.conversationID in conversations))
|
||||
conversations[message.conversationID] = [message];
|
||||
else
|
||||
|
@ -355,10 +370,12 @@ GlodaMsgDBView.prototype = {
|
|||
}
|
||||
|
||||
// build hierarchy within each conversation
|
||||
for (let [convID, convMsgs] in Iterator(conversations)) {
|
||||
for each (let convMsgs in conversations) {
|
||||
// fast-track conversations with only one messages
|
||||
if (convMsgs.length == 1) {
|
||||
nodes.push(new GMTreeNode(convMsgs[0]));
|
||||
let node = new GMTreeNode(convMsgs[0]);
|
||||
idToNode[convMsgs[0].id] = node;
|
||||
nodes.push(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -380,13 +397,14 @@ GlodaMsgDBView.prototype = {
|
|||
// we don't need the mapping object's lifetimes so large. (perhaps
|
||||
// silly? worst case is an extra N objects for N messages.)
|
||||
for (let iMsg=0; iMsg < convMsgs.length; iMsg++) {
|
||||
let message = this._messages[iMsg];
|
||||
messageIdMap[message.headerMessageID] = new GMTreeNode(message);
|
||||
let message = convMsgs[iMsg];
|
||||
let node = new GMTreeNode(message);
|
||||
messageIdMap[message.headerMessageID] = node;
|
||||
idToNode[message.id] = node;
|
||||
}
|
||||
// now find their closest parent...
|
||||
for each (let treeNode in messageIdMap) {
|
||||
let msgHdr = treeNode.message.folderMessage;
|
||||
|
||||
// references are ordered from old (0) to new (n-1), so walk backwards
|
||||
for (let iRef=msgHdr.numReferences-1; iRef >= 0; iRef--) {
|
||||
let ref = msgHdr.getStringReference(iRef);
|
||||
|
@ -394,21 +412,23 @@ GlodaMsgDBView.prototype = {
|
|||
// link them to their parent
|
||||
let parentNode = messageIdMap[ref];
|
||||
if (parentNode.children === null)
|
||||
parentNode.children = [treeNode.message];
|
||||
parentNode.children = [treeNode];
|
||||
else
|
||||
parentNode.children.push(treeNode.message);
|
||||
parentNode.children.push(treeNode);
|
||||
treeNode.parent = parentNode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (treeNode.parent === null)
|
||||
if (treeNode.parent === null) {
|
||||
nodes.push(treeNode);
|
||||
}
|
||||
}
|
||||
} // (done building hierarchy)
|
||||
} // (done dealing with threading)
|
||||
else {
|
||||
nodes = [new GMTreeNode(msg) for each (msg in this._messages)];
|
||||
nodes = [(idToNode[msg.id] = new GMTreeNode(msg)) for each
|
||||
(msg in this._messages)];
|
||||
}
|
||||
|
||||
// SORT!
|
||||
|
@ -468,7 +488,7 @@ GlodaMsgDBView.prototype = {
|
|||
groupNode.children.push(curNode);
|
||||
}
|
||||
}
|
||||
|
||||
dump("@@@@@@ GROUPING @@@@@@@@@\n");
|
||||
nodes = groupedNodes;
|
||||
}
|
||||
|
||||
|
@ -477,8 +497,10 @@ GlodaMsgDBView.prototype = {
|
|||
}
|
||||
|
||||
this._toplevelNodes = nodes;
|
||||
this._idToNode = idToNode;
|
||||
for (let iNode=0; iNode < nodes.length; iNode++)
|
||||
nodes[iNode].setLevel(0);
|
||||
// by default, just show the top-level nodes...
|
||||
this._rows = nodes.concat();
|
||||
|
||||
if (this._treeBox) {
|
||||
|
@ -503,7 +525,8 @@ GlodaMsgDBView.prototype = {
|
|||
|
||||
let query = Gloda.newQuery(Gloda.NOUN_MESSAGE);
|
||||
query.folderURI(aFolder.URI);
|
||||
this._messages = query.getAllSync();
|
||||
this._collection = query.getAllSync();
|
||||
this._messages = this._collection.items;
|
||||
this._sort();
|
||||
},
|
||||
openWithHdrs: function gloda_mdbv_openWithHdrs(aHeaders, aSortType,
|
||||
|
@ -515,6 +538,10 @@ GlodaMsgDBView.prototype = {
|
|||
|
||||
this._messages = [Gloda.getMessageForHeader(hdr) for each
|
||||
(hdr in aHeaders)];
|
||||
// we want to create an explicit collection so we at least get notifications
|
||||
// for the ids we already have...
|
||||
this._collection = Gloda.explicitCollection(Gloda.NOUN_MESSAGE,
|
||||
this._messages);
|
||||
this._sort();
|
||||
|
||||
aOutCount.value = this._messages.length;
|
||||
|
@ -572,6 +599,7 @@ GlodaMsgDBView.prototype = {
|
|||
case Ci.nsMsgViewCommandType.deleteMsg:
|
||||
case Ci.nsMsgViewCommandType.deleteNoTrash:
|
||||
case Ci.nsMsgViewCommandType.markThreadRead:
|
||||
|
||||
case Ci.nsMsgViewCommandType.junk:
|
||||
case Ci.nsMsgViewCommandType.unjunk:
|
||||
case Ci.nsMsgViewCommandType.undeleteMsg:
|
||||
|
@ -610,53 +638,102 @@ GlodaMsgDBView.prototype = {
|
|||
}
|
||||
},
|
||||
getCommandStatus: function gloda_mdbv_getCommandStatus(
|
||||
aCommand, aOutIsSelectable, aOutIsSelected) {
|
||||
aCommand, aOutIsSelectable) { // aOutIsSelected...
|
||||
|
||||
let selNodes = this.selectedNodes;
|
||||
let haveSelection = (selNodes.length > 0);
|
||||
let haveNewsMessages = false; // XXX news message specialization
|
||||
|
||||
let canDelete = function() {
|
||||
let lastCheckedFolderID = null;
|
||||
for (let iNode=0; iNode < selNodes.length; iNode++) {
|
||||
let message = selNodes[iNode].message;
|
||||
if (lastCheckedFolderID != message.folderID) {
|
||||
if (message.folderMessage === null ||
|
||||
!message.folderMessage.folder.canDeleteMessages)
|
||||
return false;
|
||||
lastCheckedFolderID = message.folderID;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
let commandOkay;
|
||||
|
||||
switch (aCommand) { // no 5
|
||||
// -- selection related (15,18,19)
|
||||
case Ci.nsMsgViewCommandType.selectAll:
|
||||
case Ci.nsMsgViewCommandType.selectThread:
|
||||
case Ci.nsMsgViewCommandType.selectFlagged:
|
||||
// -- re-dispatch (0-4,7-9,27-29)
|
||||
case Ci.nsMsgViewCommandType.deleteMsg:
|
||||
case Ci.nsMsgViewCommandType.deleteNoTrash:
|
||||
// it's okay if all the messages' folders support deletion. now, news
|
||||
// does't support deletion, but it does support cancelation, and we
|
||||
// use delete to mean cancel right now (even though it's dubious.)
|
||||
// XXX this logic is not exhaustively correct in the face of news
|
||||
// inter-mingled with other message types...
|
||||
commandOkay = haveSelection && (haveNewsMessages || canDelete());
|
||||
break;
|
||||
case Ci.nsMsgViewCommandType.applyFilters:
|
||||
// XXX do server-based check required for applyFilters
|
||||
commandOkay = false;
|
||||
break;
|
||||
case Ci.nsMsgViewCommandType.runJunkControls:
|
||||
commandOkay = haveSelection && !haveNewsMessages;
|
||||
break;
|
||||
case Ci.nsMsgViewCommandType.deleteJunk:
|
||||
commandOkay = haveSelection && canDelete();
|
||||
break;
|
||||
|
||||
case Ci.nsMsgViewCommandType.markMessagesRead:
|
||||
case Ci.nsMsgViewCommandType.markMessagesUnread:
|
||||
case Ci.nsMsgViewCommandType.toggleMessageRead:
|
||||
case Ci.nsMsgViewCommandType.flagMessages:
|
||||
case Ci.nsMsgViewCommandType.unflagMessages:
|
||||
case Ci.nsMsgViewCommandType.deleteMsg:
|
||||
case Ci.nsMsgViewCommandType.deleteNoTrash:
|
||||
case Ci.nsMsgViewCommandType.toggleThreadWatched:
|
||||
case Ci.nsMsgViewCommandType.markThreadRead:
|
||||
case Ci.nsMsgViewCommandType.downloadSelectedForOffline:
|
||||
aOutIsSelectable.value = haveSelection;
|
||||
break;
|
||||
|
||||
|
||||
case Ci.nsMsgViewCommandType.junk:
|
||||
case Ci.nsMsgViewCommandType.unjunk:
|
||||
case Ci.nsMsgViewCommandType.undeleteMsg:
|
||||
|
||||
case Ci.nsMsgViewCommandType.toggleThreadWatched:
|
||||
case Ci.nsMsgViewCommandType.expandAll:
|
||||
case Ci.nsMsgViewCommandType.collapseAll:
|
||||
|
||||
commandOkay = !haveNewsMessages;
|
||||
break;
|
||||
|
||||
case Ci.nsMsgViewCommandType.cmdRequiringMsgBody:
|
||||
// XXX support offline detection/offline message detection...
|
||||
commandOkay = haveSelection; // && (!areOffline() || offlineMessages());
|
||||
break;
|
||||
|
||||
case Ci.nsMsgViewCommandType.downloadFlaggedForOffline:
|
||||
case Ci.nsMsgViewCommandType.markAllRead:
|
||||
commandOkay = true;
|
||||
break;
|
||||
|
||||
// the C++ code doesn't call anything from here on out.. perhaps the
|
||||
// check does't reach us?
|
||||
case Ci.nsMsgViewCommandType.copyMessages:
|
||||
case Ci.nsMsgViewCommandType.moveMessages:
|
||||
case Ci.nsMsgViewCommandType.downloadSelectedForOffline:
|
||||
case Ci.nsMsgViewCommandType.downloadFlaggedForOffline:
|
||||
commandOkay = haveselection;
|
||||
break;
|
||||
|
||||
case Ci.nsMsgViewCommandType.expandAll:
|
||||
case Ci.nsMsgViewCommandType.collapseAll:
|
||||
case Ci.nsMsgViewCommandType.selectAll:
|
||||
case Ci.nsMsgViewCommandType.selectThread:
|
||||
case Ci.nsMsgViewCommandType.selectFlagged:
|
||||
commandOkay = true;
|
||||
break;
|
||||
|
||||
case Ci.nsMsgViewCommandType.cmdRequiringMsgBody:
|
||||
|
||||
case Ci.nsMsgViewCommandType.label0:
|
||||
case Ci.nsMsgViewCommandType.label1:
|
||||
case Ci.nsMsgViewCommandType.label2:
|
||||
case Ci.nsMsgViewCommandType.label3:
|
||||
case Ci.nsMsgViewCommandType.label4:
|
||||
case Ci.nsMsgViewCommandType.label5:
|
||||
|
||||
case Ci.nsMsgViewCommandType.applyFilters:
|
||||
case Ci.nsMsgViewCommandType.runJunkControls:
|
||||
case Ci.nsMsgViewCommandType.deleteJunk:
|
||||
return true;
|
||||
default:
|
||||
commandOkay = false;
|
||||
break;
|
||||
}
|
||||
|
||||
aOutIsSelectable.value = commandOkay;
|
||||
},
|
||||
|
||||
get viewType() {
|
||||
// XXX TODO: do something about the viewtype enumeration issue
|
||||
return 0;
|
||||
return 0; // show all threads...
|
||||
},
|
||||
|
||||
get viewFlags() {
|
||||
|
@ -750,34 +827,41 @@ GlodaMsgDBView.prototype = {
|
|||
},
|
||||
|
||||
get msgFolder() {
|
||||
dump("&&& get msgFolder\n");
|
||||
if (this._messages.length)
|
||||
return this._messages[0].folderMessage.folder;
|
||||
return null;
|
||||
},
|
||||
get viewFolder() {
|
||||
dump("&&& get viewFolder\n");
|
||||
return null;
|
||||
},
|
||||
|
||||
getKeyAt: function gloda_mdbv_getKeyAt(aViewIndex) {
|
||||
dump("&&& getKeyAt\n");
|
||||
return this._rows[aViewIndex].message.folderMessage.messageKey;
|
||||
},
|
||||
getFolderForViewIndex: function gloda_mdbv_getFolderForViewIndex(aViewIndex) {
|
||||
dump("&&& getFolderForViewIndex\n");
|
||||
return this._rows[aViewIndex].message.folderMessage.folder;
|
||||
},
|
||||
getURIForViewIndex: function gloda_mdbv_getURIForViewIndex(aViewIndex) {
|
||||
dump("&&& getURIForViewIndex\n");
|
||||
return this._rows[aViewIndex].message.folderMessageURI;
|
||||
},
|
||||
|
||||
getURIsForSelection: function gloda_mdbv_getURIsForSelection(
|
||||
aOutCount, aOutUris) {
|
||||
aOutCount) {
|
||||
let selNodes = this.selectedNodes;
|
||||
aOutCount.value = selNodes.length;
|
||||
return [node.message.folderMessageURI for each
|
||||
(node in selNodes)];
|
||||
},
|
||||
getIndicesForSelection: function gloda_mdbv_getIndicesForSelection(
|
||||
aOutCount, aOutIndices) {
|
||||
aOutCount) {
|
||||
let selIndices = this.selectedIndices;
|
||||
aOutCount.value = selIndices.length;
|
||||
aOutIndices.value = selIndices;
|
||||
return selIndices;
|
||||
},
|
||||
|
||||
get URIForFirstSelectedMessage() {
|
||||
|
@ -795,17 +879,37 @@ GlodaMsgDBView.prototype = {
|
|||
return null;
|
||||
},
|
||||
|
||||
_clearDisplay: function() {
|
||||
this._msgWindow.windowCommands.clearMsgPane();
|
||||
this._commandUpdater.updateCommandStatus();
|
||||
},
|
||||
|
||||
_displayNode: function(aTreeNode) {
|
||||
if (!this._suppressDisplay &&
|
||||
this._displayedNode !== aTreeNode) {
|
||||
this._displayedNode = aTreeNode;
|
||||
this._messenger.openURL(this._displayedNode.message.folderMessageURI);
|
||||
this._commandUpdater.updateCommandStatus();
|
||||
}
|
||||
},
|
||||
|
||||
loadMessageByMsgKey: function gloda_mdbv_loadMessageByMsgKey(aMsgKey) {
|
||||
dump("&&& loadMessageByMsgKey\n");
|
||||
// ambiguity as to what message key we are dealing with. let's assume it's
|
||||
// a gloda message id for the sake of this method...
|
||||
|
||||
this._messenger.OpenURL()
|
||||
this._displayNode(this._idToNode(aMsgKey));
|
||||
},
|
||||
loadMessageByViewIndex: function gloda_mdbv_loadMessageByViewIndex(
|
||||
aViewIndex) {
|
||||
this._messenger.OpenURL()
|
||||
dump("&&& loadMessageByViewIndex\n");
|
||||
this._displayNode(this._rows[aViewIndex]);
|
||||
},
|
||||
/*
|
||||
* Displays a presumably attached message rather than a message that's
|
||||
* in our list of messages...
|
||||
*/
|
||||
loadMessageByUrl: function gloda_mdbv_loadMessageByUrl(aUrl) {
|
||||
this._messenger.LoadURL(null, aUrl);
|
||||
},
|
||||
|
||||
reloadMessage: function gloda_mdbv_reloadMessage() {
|
||||
|
@ -819,7 +923,11 @@ GlodaMsgDBView.prototype = {
|
|||
get msgToSelectAfterDelete() {
|
||||
},
|
||||
get currentlyDisplayedMessage() {
|
||||
return this._rows.indexOf(this._displayedNode);
|
||||
dump("~~~ get currentDisplayedMessage\n");
|
||||
if (this._rows && this._displayedNode)
|
||||
return this._rows.indexOf(this._displayedNode);
|
||||
else
|
||||
return NO_SUCH_VIEW_INDEX;
|
||||
},
|
||||
|
||||
selectMsgByKey: function gloda_mdbv_selectMsgByKey(aMsgKey) {
|
||||
|
@ -829,11 +937,10 @@ GlodaMsgDBView.prototype = {
|
|||
},
|
||||
|
||||
get suppressMsgDisplay() {
|
||||
// TODO message display issues
|
||||
return null;
|
||||
return this._suppressDisplay;
|
||||
},
|
||||
set suppressMsgDisplay(aSuppress) {
|
||||
// TODO message display issues
|
||||
this._suppressDisplay = aSuppress;
|
||||
},
|
||||
|
||||
get suppressCommandUpdating() {
|
||||
|
@ -845,10 +952,12 @@ GlodaMsgDBView.prototype = {
|
|||
},
|
||||
|
||||
get db() {
|
||||
dump("&&& get db\n");
|
||||
return null;
|
||||
},
|
||||
|
||||
get supportsThreading() {
|
||||
dump("&&& get supportsThreading\n");
|
||||
return true;
|
||||
},
|
||||
|
||||
|
@ -868,14 +977,14 @@ GlodaMsgDBView.prototype = {
|
|||
},
|
||||
|
||||
findIndexFromKey: function gloda_mdbv_findIndexFromKey(aMsgKey, aExpand) {
|
||||
dump("&&& findIndexFromKey\n");
|
||||
},
|
||||
ExpandAndSelectThreadByIndex:
|
||||
function gloda_mdbv_ExpandAndSelectThreadByIndex(aViewIndex, aAugment) {
|
||||
},
|
||||
|
||||
get usingLines() {
|
||||
// XXX I guess we could support lines
|
||||
return false;
|
||||
return true;
|
||||
},
|
||||
|
||||
addColumnHandler: function gloda_mdbv_addColumnHandler(aColumn, aHandler) {
|
||||
|
@ -898,7 +1007,7 @@ GlodaMsgDBView.prototype = {
|
|||
},
|
||||
|
||||
get rowCount() {
|
||||
if (this._rows == null)
|
||||
if (this._rows === null)
|
||||
return 0;
|
||||
return this._rows.length;
|
||||
},
|
||||
|
@ -916,26 +1025,28 @@ GlodaMsgDBView.prototype = {
|
|||
|
||||
let message = this._rows[aRow].message;
|
||||
// if we can't find the underlying message, be sad and return nothing...
|
||||
if (message.folderMessage === null)
|
||||
return ":(";
|
||||
let folderMessage = message.folderMessage;
|
||||
if (folderMessage === null)
|
||||
return "";
|
||||
|
||||
switch(columnId[0]) {
|
||||
case "s": // subject, sender, size, status
|
||||
switch(columnId[1]) {
|
||||
case "u": // subject
|
||||
return message.folderMessage.mime2DecodedSubject;
|
||||
return message.conversation.subject; //folderMessage.mime2DecodedSubject;
|
||||
case "e": // sender
|
||||
return message.from.contact.name;
|
||||
case "i": // size
|
||||
return message.folderMessage.messageSize;
|
||||
return folderMessage.messageSize;
|
||||
case "t": // status
|
||||
return messageStatusString(message.folderMessage.flags);
|
||||
return messageStatusString(folderMessage.flags);
|
||||
}
|
||||
break;
|
||||
case "r": // recipient, received
|
||||
switch(columnId[3]) {
|
||||
case "i": // recipient
|
||||
return message.folderMessage.recipients;
|
||||
return [recip.contact.name for each
|
||||
(recip in message.to)].join(" ") // folderMessage.recipients;
|
||||
case "e": // received
|
||||
let recDate = new Date(1000 *
|
||||
folderMessage.getUint32Property("dateReceived"));
|
||||
|
@ -945,14 +1056,14 @@ GlodaMsgDBView.prototype = {
|
|||
case "d": // date
|
||||
return GlodaUtils.dateFormat(message.date);
|
||||
case "p": // priority
|
||||
return messagePriorityString(message.folderMessage.priority);
|
||||
return messagePriorityString(folderMessage.priority);
|
||||
case "a": // account
|
||||
return message.folderMessage.accountKey;
|
||||
return folderMessage.accountKey;
|
||||
case "t": // total messages in thread, tags
|
||||
switch (columnId[1]) {
|
||||
case "h": // total messages in thread
|
||||
// per idiom, only return this for top-level nodes
|
||||
let node = this._Rows[aRow];
|
||||
let node = this._rows[aRow];
|
||||
if (node.parent === null)
|
||||
return "" + node.nodesInSubTree;
|
||||
else
|
||||
|
@ -969,7 +1080,7 @@ GlodaMsgDBView.prototype = {
|
|||
else
|
||||
return "";
|
||||
case "j": // junk score
|
||||
return message.folderMessage.getStringProperty("junkscore");
|
||||
return folderMessage.getStringProperty("junkscore");
|
||||
case "i": // id
|
||||
// I don't think this is exposed anymore; perhaps only ever for
|
||||
// debugging? (The C++ impl exposes the messageKey, which has no
|
||||
|
@ -978,7 +1089,7 @@ GlodaMsgDBView.prototype = {
|
|||
case "l": // location, previously label too?
|
||||
switch (columnId[1]) {
|
||||
case "o": // location
|
||||
return message.folderMessage.folder.prettiestName;
|
||||
return folderMessage.folder.prettiestName;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -1017,6 +1128,7 @@ GlodaMsgDBView.prototype = {
|
|||
// potentially nicest would be just leveraging our knowledge of the tree to
|
||||
// do our own traversal, although the indexOf has a fair chance of winning
|
||||
// in many cases (assuming it is optimized).
|
||||
dump("$$$ getParentIndex\n");
|
||||
return this._rows.indexOf(selNode.parent);
|
||||
},
|
||||
getLevel: function gloda_mdbv_getLevel(aIndex) {
|
||||
|
@ -1025,6 +1137,7 @@ GlodaMsgDBView.prototype = {
|
|||
|
||||
hasNextSibling: function gloda_mdbv_hasNextSibling(aIndex, aAfterIndex) {
|
||||
let selNode = this._rows[aIndex];
|
||||
dump("~~~ hasNextSibling\n");
|
||||
// if we have no parent or we are the last child, just rule it out.
|
||||
if ((selNode.parent === null) ||
|
||||
(selNode.parent.indexOf(selNode) == selNode.parent.children.length - 1))
|
||||
|
@ -1056,7 +1169,7 @@ GlodaMsgDBView.prototype = {
|
|||
let spliceArgs = [aIndex+1, 0];
|
||||
|
||||
function expandNode(aNode) {
|
||||
for (let iChild=0; iChild < aNode.children; iChild++) {
|
||||
for (let iChild=0; iChild < aNode.children.length; iChild++) {
|
||||
let child = aNode.children[iChild];
|
||||
spliceArgs.push(child);
|
||||
if (child.open)
|
||||
|
@ -1068,8 +1181,10 @@ GlodaMsgDBView.prototype = {
|
|||
let rowsInserted = spliceArgs.length - 2;
|
||||
this._rows.splice.apply(this._rows, spliceArgs);
|
||||
|
||||
if (this._treeBox)
|
||||
if (this._treeBox) {
|
||||
this._treeBox.invalidateRange(aIndex, aIndex);
|
||||
this._treeBox.rowCountChanged(aIndex+1, rowsInserted);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// we're closed now, we were previously open
|
||||
|
@ -1079,15 +1194,18 @@ GlodaMsgDBView.prototype = {
|
|||
// for the first node at or above the given node's level (which must be
|
||||
// a sibling or uncle)
|
||||
let curLevel = selNode.level;
|
||||
for (let iRow=aIndex+1; iRow < this._rows.length; iRow++) {
|
||||
let iRow;
|
||||
for (iRow=aIndex+1; iRow < this._rows.length; iRow++) {
|
||||
if (this._rows[iRow].level <= curLevel)
|
||||
break;
|
||||
}
|
||||
let rowsToDelete = iRow - aIndex+1;
|
||||
let rowsToDelete = iRow - (aIndex+1);
|
||||
if (rowsToDelete) {
|
||||
this._rows.splice(aIndex+1, rowsToDelete);
|
||||
if (this._treeBox)
|
||||
if (this._treeBox) {
|
||||
this._treeBox.invalidateRange(aIndex, aIndex);
|
||||
this._treeBox.rowCountChanged(aIndex+1, -rowsToDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1098,6 +1216,10 @@ GlodaMsgDBView.prototype = {
|
|||
cycleHeader: function(col, elem) {},
|
||||
selectionChanged: function gloda_mdbv_selectionChanged() {
|
||||
this._selectedNodes = null;
|
||||
if (this.selectedNodes.length == 1)
|
||||
this._displayNode(this.selectedNodes[0]);
|
||||
else
|
||||
this._clearDisplay();
|
||||
},
|
||||
cycleCell: function(idx, column) {},
|
||||
performAction: function(action) {},
|
||||
|
@ -1107,7 +1229,12 @@ GlodaMsgDBView.prototype = {
|
|||
getCellProperties: function(idx, column, prop) {
|
||||
},
|
||||
getColumnProperties: function(column, element, prop) {
|
||||
},
|
||||
},
|
||||
// no drag and drop!
|
||||
canDrop: function(aIndex, aOrient) {
|
||||
return false;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
var components = [GlodaMsgDBView];
|
||||
|
|
|
@ -55,7 +55,7 @@ GlodaCollectionManager.prototype = {
|
|||
registerCollection: function gloda_colm_registerCollection(aCollection) {
|
||||
let collections;
|
||||
let nounID = aCollection.query._nounMeta.id;
|
||||
if (nounID in this._collectionsByNoun)
|
||||
if (!(nounID in this._collectionsByNoun))
|
||||
collections = this._collectionsByNoun[nounID] = [];
|
||||
else {
|
||||
// purge dead weak references while we're at it
|
||||
|
@ -148,6 +148,8 @@ GlodaCollectionManager.prototype = {
|
|||
}
|
||||
},
|
||||
}
|
||||
// singleton
|
||||
GlodaCollectionManager = new GlodaCollectionManager();
|
||||
|
||||
/**
|
||||
* A GlodaCollection is intended to be a current view of the set of first-class
|
||||
|
@ -169,6 +171,9 @@ function GlodaCollection(aItems, aQuery, aListener) {
|
|||
}
|
||||
|
||||
GlodaCollection.prototype = {
|
||||
get listener() { return this._listener; },
|
||||
set listener(aListener) { this._listener = aListener; },
|
||||
|
||||
_onItemsAdded: function(aItems) {
|
||||
this.items.push.apply(this.items, aItems);
|
||||
for each (item in aItems) {
|
||||
|
|
|
@ -80,7 +80,7 @@ GlodaAttributeDef.prototype = {
|
|||
|
||||
get isBound() { return this._boundName !== null; },
|
||||
get boundName() { return this._boundName; },
|
||||
get singular() { return this.singular; },
|
||||
get singular() { return this._singular; },
|
||||
|
||||
get isSpecial() { return this._specialColumnName !== null; },
|
||||
get specialColumnName() { return this._specialColumnName; },
|
||||
|
@ -204,7 +204,7 @@ function GlodaMessage(aDatastore, aID, aFolderID, aMessageKey,
|
|||
|
||||
// for now, let's always cache this; they should really be forgetting about us
|
||||
// if they want to forget about the underlying storage anyways...
|
||||
this._folderMessage = null;
|
||||
this._folderMessage = undefined;
|
||||
// the list of attributes, un-processed
|
||||
this._attributes = null;
|
||||
}
|
||||
|
@ -264,11 +264,10 @@ GlodaMessage.prototype = {
|
|||
* null if the message does not exist for one reason or another.
|
||||
*/
|
||||
get folderMessage() {
|
||||
if (this._folderMessage !== null)
|
||||
if (this._folderMessage !== undefined)
|
||||
return this._folderMessage;
|
||||
if (this._folderID === null || this._messageKey === null)
|
||||
return null;
|
||||
|
||||
return this._folderMessage = null;
|
||||
let rdfService = Cc['@mozilla.org/rdf/rdf-service;1'].
|
||||
getService(Ci.nsIRDFService);
|
||||
let folder = rdfService.GetResource(
|
||||
|
@ -282,8 +281,6 @@ GlodaMessage.prototype = {
|
|||
"header! (" + this._headerMessageID + " expected, got " +
|
||||
this._folderMessage.messageId + ")");
|
||||
this._folderMessage = null;
|
||||
// null out our message key to shut us up on future attempts
|
||||
this._messageKey = null;
|
||||
}
|
||||
}
|
||||
return this._folderMessage;
|
||||
|
|
|
@ -51,6 +51,7 @@ Cu.import("resource://gloda/modules/log4moz.js");
|
|||
|
||||
Cu.import("resource://gloda/modules/datamodel.js");
|
||||
Cu.import("resource://gloda/modules/databind.js");
|
||||
Cu.import("resource://gloda/modules/collection.js");
|
||||
|
||||
let GlodaDatastore = {
|
||||
_log: null,
|
||||
|
@ -1124,8 +1125,10 @@ let GlodaDatastore = {
|
|||
items.push(nounMeta.objFromRow.call(nounMeta.datastore, statement.row));
|
||||
}
|
||||
statement.reset();
|
||||
|
||||
return items;
|
||||
|
||||
let collection = new GlodaCollection(items, aQuery);
|
||||
GlodaCollectionManager.registerCollection(collection);
|
||||
return collection;
|
||||
},
|
||||
|
||||
queryMessagesAPV: function gloda_ds_queryMessagesAPV(aAPVs) {
|
||||
|
|
|
@ -46,6 +46,7 @@ Cu.import("resource://gloda/modules/log4moz.js");
|
|||
|
||||
Cu.import("resource://gloda/modules/datastore.js");
|
||||
Cu.import("resource://gloda/modules/datamodel.js");
|
||||
Cu.import("resource://gloda/modules/collection.js");
|
||||
Cu.import("resource://gloda/modules/query.js");
|
||||
Cu.import("resource://gloda/modules/utils.js");
|
||||
|
||||
|
@ -226,8 +227,10 @@ let Gloda = {
|
|||
if (aNounID === undefined)
|
||||
aNounID = this._nextNounID++;
|
||||
aNounMeta.id = aNounID;
|
||||
if (aNounMeta.firstClass)
|
||||
aNounMeta.queryClass = GlodaQueryClassFactory(aNounMeta);
|
||||
if (aNounMeta.firstClass) {
|
||||
[aNounMeta.queryClass, aNounMeta.explicitQueryClass] =
|
||||
GlodaQueryClassFactory(aNounMeta);
|
||||
}
|
||||
this._nounNameToNounID[aNounMeta.name] = aNounID;
|
||||
this._nounIDToMeta[aNounID] = aNounMeta;
|
||||
aNounMeta.actions = [];
|
||||
|
@ -380,23 +383,24 @@ let Gloda = {
|
|||
// should we memoize the value as a getter per-instance?
|
||||
if (aSingular) {
|
||||
getter = function() {
|
||||
if (this[storageName] != undefined)
|
||||
return this[storageName];
|
||||
let val = this[storageName];
|
||||
if (val !== undefined)
|
||||
return val;
|
||||
let instances = this.getAttributeInstances(aAttr);
|
||||
let val;
|
||||
if (instances.length > 0)
|
||||
val = nounMeta.fromParamAndValue(instances[0][1], instances[0][2]);
|
||||
else
|
||||
val = null;
|
||||
this[storageName] = val;
|
||||
//this[storageName] = val;
|
||||
this.__defineGetter__(aBindName, function() val);
|
||||
return val;
|
||||
}
|
||||
} else {
|
||||
getter = function() {
|
||||
if (this[storageName] != undefined)
|
||||
return this[storageName];
|
||||
let values = this[storageName];
|
||||
if (values !== undefined)
|
||||
return values;
|
||||
let instances = this.getAttributeInstances(aAttr);
|
||||
let values;
|
||||
if (instances.length > 0) {
|
||||
values = [];
|
||||
for (let iInst=0; iInst < instances.length; iInst++) {
|
||||
|
@ -407,7 +411,8 @@ let Gloda = {
|
|||
else {
|
||||
values = instances; // empty is empty
|
||||
}
|
||||
this[storageName] = values;
|
||||
//this[storageName] = values;
|
||||
this.__defineGetter__(aBindName, function() values);
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
@ -436,8 +441,6 @@ let Gloda = {
|
|||
return this;
|
||||
};
|
||||
|
||||
dump("binding constraint " + aBindName + " on " + subjectNounMeta.name + " to "+
|
||||
constrainer + "\n");
|
||||
subjectNounMeta.queryClass.prototype[aBindName] = constrainer;
|
||||
|
||||
if (nounMeta.continuous) {
|
||||
|
@ -618,11 +621,28 @@ dump("binding constraint " + aBindName + " on " + subjectNounMeta.name + " to "+
|
|||
return GlodaDatastore.createTableIfNotExists(aTableDef);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new query for the given noun-type.
|
||||
*/
|
||||
newQuery: function gloda_ns_newQuery(aNounID) {
|
||||
let nounMeta = this._nounIDToMeta[aNounID];
|
||||
return new nounMeta.queryClass();
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a collection/query for the given noun-type that only matches the
|
||||
* provided items. This is to be used when you have an explicit set of items
|
||||
* that you would still like to receive updates for.
|
||||
*/
|
||||
explicitCollection: function gloda_ns_explicitCollection(aNounID, aItems) {
|
||||
let nounMeta = this._nounIDToMeta[aNounID];
|
||||
let collection = new GlodaCollection(aItems, null, null)
|
||||
let query = new nounMeta.explicitQueryClass(collection);
|
||||
collection.query = query;
|
||||
GlodaCollectionManager.registerCollection(collection);
|
||||
return colleciton;
|
||||
},
|
||||
|
||||
processMessage: function gloda_ns_processMessage(aMessage, aMsgHdr,
|
||||
aMimeMsg) {
|
||||
// For now, we are ridiculously lazy and simply nuke all existing attributes
|
||||
|
|
|
@ -166,6 +166,23 @@ GlodaQueryClass.prototype = {
|
|||
},
|
||||
};
|
||||
|
||||
function GlodaExplicitQueryClass() {
|
||||
}
|
||||
|
||||
GlodaExplicitQueryClass.prototype = {
|
||||
// don't let people try and mess with us
|
||||
or: function() { return null; },
|
||||
// don't let people try and query on us (until we have a real use case for
|
||||
// that...)
|
||||
getAllSync: function() { return null; },
|
||||
/**
|
||||
* Matches only items that are already in the collection (by id).
|
||||
*/
|
||||
test: function gloda_query_explicit_test(aObj) {
|
||||
return (aObj.id in this.collection._idMap);
|
||||
}
|
||||
};
|
||||
|
||||
function GlodaQueryClassFactory(aNounMeta) {
|
||||
let newQueryClass = function() {
|
||||
GlodaQueryClass.call(this);
|
||||
|
@ -175,5 +192,13 @@ function GlodaQueryClassFactory(aNounMeta) {
|
|||
newQueryClass.prototype._queryClass = newQueryClass;
|
||||
newQueryClass.prototype._nounMeta = aNounMeta;
|
||||
|
||||
return newQueryClass;
|
||||
let newExplicitClass = function(aCollection) {
|
||||
GlodaExplicitQueryClass.call(this);
|
||||
this.collection = aCollection;
|
||||
};
|
||||
newExplicitClass.prototype = new GlodaExplicitQueryClass();
|
||||
newExplicitClass.prototype._queryClass = newExplicitClass;
|
||||
newExplicitClass.prototype._nounMeta = aNounMeta;
|
||||
|
||||
return [newQueryClass, newExplicitClass];
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче