From 131d90cb012423bdfbb4e032ea7264a430842f7c Mon Sep 17 00:00:00 2001 From: "mnyromyr%tprac.de" Date: Tue, 8 Apr 2008 19:16:31 +0000 Subject: [PATCH] Bug 416548: tags pref panel migration; r=IanN, sr=neil git-svn-id: svn://10.0.0.236/trunk@249842 18797224-902f-48f8-a5cc-f745e15eee43 --- .../resources/content/mailPrefsOverlay.xul | 14 +- .../prefs/resources/content/pref-mailnews.xul | 2 +- .../base/prefs/resources/content/pref-tags.js | 494 +++++++++++------- .../prefs/resources/content/pref-tags.xul | 135 ++--- .../resources/content/mailWindowOverlay.xul | 8 +- mozilla/mailnews/jar.mn | 4 +- mozilla/suite/common/pref/pref-help.js | 2 +- mozilla/suite/locales/jar.mn | 2 +- .../themes/classic/messenger/prefPanels.css | 24 +- .../themes/modern/messenger/prefPanels.css | 24 +- 10 files changed, 412 insertions(+), 297 deletions(-) diff --git a/mozilla/mailnews/base/prefs/resources/content/mailPrefsOverlay.xul b/mozilla/mailnews/base/prefs/resources/content/mailPrefsOverlay.xul index 6a91ed8d589..63be657b86f 100644 --- a/mozilla/mailnews/base/prefs/resources/content/mailPrefsOverlay.xul +++ b/mozilla/mailnews/base/prefs/resources/content/mailPrefsOverlay.xul @@ -57,12 +57,20 @@ - + @@ -107,7 +115,7 @@ - + diff --git a/mozilla/mailnews/base/prefs/resources/content/pref-mailnews.xul b/mozilla/mailnews/base/prefs/resources/content/pref-mailnews.xul index dd547ee576b..4b1cca308c8 100644 --- a/mozilla/mailnews/base/prefs/resources/content/pref-mailnews.xul +++ b/mozilla/mailnews/base/prefs/resources/content/pref-mailnews.xul @@ -50,7 +50,7 @@ ]> - diff --git a/mozilla/mailnews/base/prefs/resources/content/pref-tags.js b/mozilla/mailnews/base/prefs/resources/content/pref-tags.js index 3f4a6e235a3..590e675d964 100644 --- a/mozilla/mailnews/base/prefs/resources/content/pref-tags.js +++ b/mozilla/mailnews/base/prefs/resources/content/pref-tags.js @@ -34,7 +34,7 @@ * * ***** END LICENSE BLOCK ***** */ -// Each tag entry in our list looks like this, where is tag's unique key: +// Each tag entry in our list looks like this: // // // @@ -43,104 +43,72 @@ // // // -const TAGPANEL_URI = 'chrome://messenger/content/pref-labels.xul'; -const TAGLIST_ID = 'tagList'; // UI element -const ACTIVE_TAGS_ID = TAGLIST_ID + '.active'; // wsm element -const DELETED_TAGS_ID = TAGLIST_ID + '.deleted'; // wsm element +// For ease of handling, all tag data is stored in .tagInfo also. +const kOrdinalCharLow = "a"; +const kOrdinalCharHigh = "z"; +const kOrdinalPadding = String.fromCharCode(kOrdinalCharLow.charCodeAt(0) - 1); + +var gInstantApply = document.documentElement.instantApply; // read only once +var gTagService = Components.classes["@mozilla.org/messenger/tagservice;1"] + .getService(Components.interfaces.nsIMsgTagService); var gTagList = null; // tagList root element var gAddButton = null; var gDeleteButton = null; var gRaiseButton = null; var gLowerButton = null; -var gDeletedTags = null; // tags to be deleted by the tagService +var gDeletedTags = null; // tags marked for deletion in non-instant apply mode -// init global stuff before the wsm is used -function InitTagPanel() + +function Startup() { - gTagList = document.getElementById(TAGLIST_ID); + gTagList = document.getElementById('tagList'); gAddButton = document.getElementById('addTagButton'); gDeleteButton = document.getElementById('deleteTagButton'); gRaiseButton = document.getElementById('raiseTagButton'); gLowerButton = document.getElementById('lowerTagButton'); + InitTagList(); + if (!gInstantApply) + window.addEventListener("dialogaccept", this.OnOK, true); UpdateButtonStates(); - parent.initPanel(TAGPANEL_URI); } -function Startup() +function InitTagList() { - parent.hPrefWindow.registerOKCallbackFunc(OnOK); -} - -// store pref values in the wsm -function GetFields(aPageData) -{ - // collect the tag definitions from the UI and store them in the wsm - var tags = []; - for (var entry = gTagList.firstChild; entry; entry = entry.nextSibling) - if (entry.localName == 'listitem') - { - // update taginfo with current values from textbox and colorpicker - UpdateTagInfo(entry.taginfo, entry); - tags.push(entry.taginfo); - } - aPageData[ACTIVE_TAGS_ID] = tags; - - // store the list of tags to be deleted in the OKHandler - aPageData[DELETED_TAGS_ID] = gDeletedTags; - - return aPageData; -} - -// read pref values stored in the wsm -function SetFields(aPageData) -{ - var i, tags; - // If the wsm has no tag data yet, get the list from the tag service. - if (!(ACTIVE_TAGS_ID in aPageData)) + // Read the tags from preferences via the tag service. + var tagArray = gTagService.getAllTags({}); + for (var i = 0; i < tagArray.length; ++i) { - var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"] - .getService(Components.interfaces.nsIMsgTagService); - var tagArray = tagService.getAllTags({}); - tags = aPageData[ACTIVE_TAGS_ID] = []; - for (i = 0; i < tagArray.length; ++i) - { - // The nsMsgTag items are readonly, but we may need to change them. - // And we don't care for the current ordinal strings, we'll create new - // ones in the OKHandler if necessary - var t = tagArray[i]; - tags.push({tag: t.tag, key: t.key, color: t.color}); - } + var t = tagArray[i]; + var tagInfo = {tag: t.tag, + key: t.key, + color: t.color, + ordinal: t.ordinal, + new: false, // not added in this run + changed: false}; // not changed (yet) + AppendTagEntry(tagInfo, null); } - - // now create the dynamic elements - tags = aPageData[ACTIVE_TAGS_ID]; - - // Listitems we append to the "end" of the listbox and would be rendered - // outside the clipping area don't get their text and color set! - // (See also 354065.) - // So we stuff them in bottom-up... :-| - var beforeTag = null; - for (i = tags.length - 1; i >= 0; --i) - beforeTag = AppendTagEntry(tags[i], beforeTag); - - // grab the list of tags to be deleted in the OKHandler - gDeletedTags = (DELETED_TAGS_ID in aPageData) ? aPageData[DELETED_TAGS_ID] : {}; } // read text and color from the listitem function UpdateTagInfo(aTagInfo, aEntry) { - aTagInfo.tag = aEntry.firstChild.firstChild.value; - aTagInfo.color = aEntry.lastChild.lastChild.color; + var tag = aEntry.firstChild.firstChild.value; + var color = aEntry.lastChild.lastChild.color; + if (tag != aTagInfo.tag || color != aTagInfo.color) + { + aTagInfo.changed = true; // never unset changed flag here! + aTagInfo.tag = tag; + aTagInfo.color = color; + } } // set text and color of the listitem function UpdateTagEntry(aTagInfo, aEntry) { aEntry.firstChild.firstChild.value = aTagInfo.tag; - aEntry.lastChild.lastChild.color = aTagInfo.color; + aEntry.lastChild.lastChild.color = aTagInfo.color || 'inherit'; } function AppendTagEntry(aTagInfo, aRefChild) @@ -149,10 +117,9 @@ function AppendTagEntry(aTagInfo, aRefChild) // You MUST first set its type attribute (to select the correct binding), then // add the element to the DOM (to bind the binding) and finally set the color // property(!) afterwards. Try in any other order and fail... :-( - var key = aTagInfo.key; - var tagCell = document.createElement('listcell'); var textbox = document.createElement('textbox'); + textbox.setAttribute('flex', 1); tagCell.appendChild(textbox); var colorCell = document.createElement('listcell'); @@ -162,8 +129,9 @@ function AppendTagEntry(aTagInfo, aRefChild) var entry = document.createElement('listitem'); entry.addEventListener('focus', OnFocus, true); + entry.addEventListener('change', OnChange, false); entry.setAttribute('allowevents', 'true'); // activate textbox and colorpicker - entry.taginfo = aTagInfo; + entry.tagInfo = aTagInfo; entry.appendChild(tagCell); entry.appendChild(colorCell); @@ -185,6 +153,194 @@ function FocusTagEntry(aEntry) aEntry.firstChild.firstChild.focus(); } +function GetTagOrdinal(aTagInfo) +{ + if (aTagInfo.ordinal) + return aTagInfo.ordinal; + return aTagInfo.key; +} + +function SetTagOrdinal(aTagInfo, aOrdinal) +{ + var ordinal = aTagInfo.ordinal; + aTagInfo.ordinal = (aTagInfo.key != aOrdinal) ? aOrdinal : ''; + if (aTagInfo.ordinal != ordinal) + aTagInfo.changed = true; +} + +function BisectString(aPrev, aNext) +{ + // find a string which is lexically greater than aPrev and lesser than aNext: + // - copy leading parts common to aPrev and aNext into the result + // - find the first position where aPrev and aNext differ: + // - if we can squeeze a character in between there: fine, done! + // - if not: + // - if the rest of aNext is longer than one character, we can squeeze + // in just the first aNext rest-character and be done! + // - else we try to "increment" aPrev a bit to fit in + if ((aPrev >= aNext) || (aPrev + kOrdinalCharLow >= aNext)) + return ''; // no such string exists + + // pad the shorter string + var lenPrev = aPrev.length; + var lenNext = aNext.length; + var lenMax = Math.max(lenPrev, lenNext); + + // loop over both strings at once, padding if necessary + var constructing = false; + var result = ''; + for (var i = 0; i < lenMax; ++i) + { + var prevChar = (i < lenPrev) ? aPrev[i] : kOrdinalPadding; + var nextChar = constructing ? kOrdinalCharHigh + : (i < lenNext) ? aNext[i] + : kOrdinalPadding; + var prevCode = prevChar.charCodeAt(0); + var nextCode = nextChar.charCodeAt(0); + if (prevCode == nextCode) + { + // copy common characters + result += prevChar; + } + else if (prevCode + 1 < nextCode) + { + // found a real bisecting string + result += String.fromCharCode((prevCode + nextCode) / 2); + return result; + } + else + { + // nextCode is greater than prevCode, but there's no place in between. + // But if aNext[i+1] exists, then nextChar will suffice and we're done! + // ("x" < "xsomething") + if (i + 1 < lenNext) + { + // found a real bisecting string + return result + nextChar; + } + // just copy over prevChar and enter construction mode + result += prevChar; + constructing = true; + } + } + return ''; // nothing found +} + +function RecalculateOrdinal(aEntry) +{ + // Calculate a new ordinal for the given entry, assuming that both its + // predecessor's and successor's are correct, i.e. ord(p) < ord(s)! + var tagInfo = aEntry.tagInfo; + var ordinal = tagInfo.key; + // get neighbouring ordinals + var prevOrdinal = '', nextOrdinal = ''; + var prev = aEntry.previousSibling; + if (prev && prev.nodeName == 'listitem') // first.prev == listhead + prevOrdinal = GetTagOrdinal(prev.tagInfo); + var next = aEntry.nextSibling; + if (next) + { + nextOrdinal = GetTagOrdinal(next.tagInfo); + } + else + { + // ensure key < nextOrdinal if entry is the last/only entry + nextOrdinal = prevOrdinal || ordinal; + nextOrdinal = String.fromCharCode(nextOrdinal.charCodeAt(0) + 2); + } + + if (prevOrdinal < ordinal && ordinal < nextOrdinal) + { + // no ordinal needed, just clear it + SetTagOrdinal(tagInfo, '') + return; + } + + // so we need a new ordinal, because key <= prevOrdinal or key >= nextOrdinal + ordinal = BisectString(prevOrdinal, nextOrdinal); + if (ordinal) + { + // found a new ordinal + SetTagOrdinal(tagInfo, ordinal) + return; + } + + // couldn't find an ordinal before the nextOrdinal, so take that instead + // and recalculate a new one for the next entry + SetTagOrdinal(tagInfo, nextOrdinal); + if (next) + ApplyChange(next); +} + +function OnChange(aEvent) +{ + ApplyChange(aEvent.currentTarget); +} + +function ApplyChange(aEntry) +{ + if (!aEntry) + { + dump('ApplyChange: aEntry is null! (called by ' + ApplyChange.caller.name + ')\n'); + return; + } + + // the tag data got changed, so write it back to the system + var tagInfo = aEntry.tagInfo; + UpdateTagInfo(tagInfo, aEntry); + // ensure unique tag name + var dupeList = ReadTagListFromUI(aEntry); + var uniqueTag = DisambiguateTag(tagInfo.tag, dupeList); + if (tagInfo.tag != uniqueTag) + { + tagInfo.tag = uniqueTag; + tagInfo.changed = true; + UpdateTagEntry(tagInfo, aEntry); + } + + if (gInstantApply) + { + // If the item was newly added, we still can rename the key, + // so that it's in sync with the actual tag. + if (tagInfo.new && tagInfo.key) + { + // Do not clear the "new" flag! + // The key will only stick after closing the dialog. + gTagService.deleteKey(tagInfo.key); + tagInfo.key = ''; + } + if (!tagInfo.key) + { + // create a new key, based upon the new tag + gTagService.addTag(tagInfo.tag, '', ''); + tagInfo.key = gTagService.getKeyForTag(tagInfo.tag); + } + + // Recalculate the sort ordinal, if necessary. + // We assume that the neighbour's ordinals are correct, + // i.e. that ordinal(pos - 1) < ordinal(pos + 1)! + RecalculateOrdinal(aEntry); + WriteTag(tagInfo); + } +} + +function WriteTag(aTagInfo) +{ +//dump('********** WriteTag: ' + aTagInfo.toSource() + '\n'); + try + { + gTagService.addTagForKey(aTagInfo.key, + aTagInfo.tag, + aTagInfo.color, + aTagInfo.ordinal); + aTagInfo.changed = false; + } + catch (e) + { + dump('WriteTag: update exception:\n' + e); + } +} + function UpdateButtonStates() { var entry = gTagList.selectedItem; @@ -196,6 +352,16 @@ function UpdateButtonStates() gLowerButton.disabled = !entry || !gTagList.getNextItem(entry, 1); } +function ReadTagListFromUI(aIgnoreEntry) +{ + // reads distinct tag names from the UI + var dupeList = {}; // indexed by tag + for (var entry = gTagList.firstChild; entry; entry = entry.nextSibling) + if ((entry != aIgnoreEntry) && (entry.localName == 'listitem')) + dupeList[entry.firstChild.firstChild.value] = true; + return dupeList; +} + function DisambiguateTag(aTag, aTagList) { if (aTag in aTagList) @@ -210,20 +376,25 @@ function DisambiguateTag(aTag, aTagList) function AddTag() { - // Add a new tag to the UI here. It will be only be written to the - // preference system only if the OKHandler is executed! + // Add a new tag to the UI here. + // It will be be written to the preference system + // (a) directly on each change for instant apply, or + // (b) only if the dialogaccept handler is executed. - // create unique tag name - var dupeList = {}; // indexed by tag - for (var entry = gTagList.firstChild; entry; entry = entry.nextSibling) - if (entry.localName == 'listitem') - dupeList[entry.firstChild.firstChild.value] = true; + // create new unique tag name + var dupeList = ReadTagListFromUI(); var tag = DisambiguateTag(gAddButton.getAttribute('defaulttagname'), dupeList); // create new tag list entry - var tagInfo = {tag: tag, key: '', color: '', ordinal: ''}; + var tagInfo = {tag: tag, + key: '', + color: 'inherit', + ordinal: '', + new: true, + changed: true}; var refChild = gTagList.getNextItem(gTagList.selectedItem, 1); var newEntry = AppendTagEntry(tagInfo, refChild); + ApplyChange(newEntry); FocusTagEntry(newEntry); } @@ -231,11 +402,16 @@ function DeleteTag() { // Delete the selected tag from the UI here. If it was added during this // preference dialog session, we can drop it at once; if it was read from - // the preferences system, we need to remember killing it in the OKHandler. + // the preferences system, we may need to remember killing it in OnOK. var entry = gTagList.selectedItem; - var key = entry.taginfo.key; + var key = entry.tagInfo.key; if (key) - gDeletedTags[key] = true; // dummy value + { + if (gInstantApply) + gTagService.deleteKey(key); + else + gDeletedTags[key] = true; // dummy value + } // after removing, move focus to next entry, if it exist, else try previous var newFocusItem = gTagList.getNextItem(entry, 1) || gTagList.getPreviousItem(entry, 1); @@ -249,15 +425,17 @@ function DeleteTag() function MoveTag(aMoveUp) { // Move the selected tag one position up or down in the tagList's child order. - // This reordering may require changing ordinal strings, which will happen - // when we write tag data to the preferences system in the OKHandler. + // This reordering may require changing ordinal strings. var entry = gTagList.selectedItem; - UpdateTagInfo(entry.taginfo, entry); // remember changed values + var tagInfo = entry.tagInfo; + UpdateTagInfo(tagInfo, entry); // remember changed values var successor = aMoveUp ? gTagList.getPreviousItem(entry, 1) : gTagList.getNextItem(entry, 2); entry.parentNode.insertBefore(entry, successor); FocusTagEntry(entry); - UpdateTagEntry(entry.taginfo, entry); // needs to be visible + tagInfo.changed = true; + UpdateTagEntry(tagInfo, entry); // needs to be visible + ApplyChange(entry); } function Restore() @@ -266,9 +444,14 @@ function Restore() // Remember any known keys for deletion in the OKHandler. while (gTagList.getRowCount()) { - var key = gTagList.removeItemAt(0).taginfo.key; + var key = gTagList.removeItemAt(0).tagInfo.key; if (key) - gDeletedTags[key] = true; // dummy value + { + if (gInstantApply) + gTagService.deleteKey(key); + else + gDeletedTags[key] = true; // dummy value + } } // add default items (no ordinal strings for those) var prefService = Components.classes["@mozilla.org/preferences-service;1"] @@ -282,116 +465,49 @@ function Restore() var key = "$label" + i; var tag = prefDescription.getComplexValue(i, kLocalizedString).data; var color = prefColor.getCharPref(i); - var tagInfo = {tag: tag, key: key, color: color}; - AppendTagEntry(tagInfo, null); + var tagInfo = {tag: tag, + key: key, + color: color, + ordinal: '', + new: false, + changed: true}; + var newEntry = AppendTagEntry(tagInfo, null); + ApplyChange(newEntry); } FocusTagEntry(gTagList.getItemAtIndex(0)); } function OnOK() { - var i; - var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"] - .getService(Components.interfaces.nsIMsgTagService); - // we may be called in another page's context, so get the stored data from the - // wsm the hard way - var pageData = parent.hPrefWindow.wsm.dataManager.pageData[TAGPANEL_URI]; - var activeTags = pageData[ACTIVE_TAGS_ID]; - var deletedTags = pageData[DELETED_TAGS_ID]; - // remove all deleted tags from the preferences system - for (var key in deletedTags) - tagService.deleteKey(key); + for (var key in gDeletedTags) + gTagService.deleteKey(key); - // count dupes so that we can eliminate them later - var dupeCounts = {}; // indexed by tag - for (i = 0; i < activeTags.length; ++i) + // Write tags to the preferences system, creating keys and ordinal strings. + for (var entry = gTagList.firstChild; entry; entry = entry.nextSibling) { - var tag = activeTags[i].tag; - if (tag in dupeCounts) - ++dupeCounts[tag]; - else - dupeCounts[tag] = 0; // no dupes found yet - } - - // Now write tags to the preferences system, create keys and ordinal strings. - // Manually set ordinal strings are NOT retained! - var lastTagInfo = null; - for (i = 0; i < activeTags.length; ++i) - { - var tagInfo = activeTags[i]; - if (tagInfo) + if (entry.localName == 'listitem') { - var dupeCount = dupeCounts[tagInfo.tag]; - if (dupeCount > 0) + // only write listitems which have changed (this includes new ones) + var tagInfo = entry.tagInfo; + if (tagInfo.changed) { - // ignore the first dupe, but set mark for further processing - dupeCounts[tagInfo.tag] = -1; + if (!tagInfo.key) + { + // newly added tag, need to create a key and read it + gTagService.addTag(tagInfo.tag, '', ''); + tagInfo.key = gTagService.getKeyForTag(tagInfo.tag); + } + if (tagInfo.key) + { + // Recalculate the sort ordinal, if necessary. + // We assume that the neighbour's ordinals are correct, + // i.e. that ordinal(pos - 1) < ordinal(pos + 1)! + RecalculateOrdinal(entry); + // update the tag definition + WriteTag(tagInfo); + } } - else if (dupeCount < 0) - { - tagInfo.tag = DisambiguateTag(tagInfo.tag, dupeCounts); - dupeCounts[tagInfo.tag] = 0; // new tag name is unique - } - - if (!tagInfo.key) - { - // newly added tag, need to create a key and read it - tagService.addTag(tagInfo.tag, '', ''); - tagInfo.key = tagService.getKeyForTag(tagInfo.tag); - } - - if (tagInfo.key) - { - if (!lastTagInfo) - { - // the first tag list entry needs no ordinal string - lastTagInfo = tagInfo; - tagInfo.ordinal = ''; - } - else - { - // if tagInfo's key is lower than that of its predecessor, - // it needs an ordinal string - var lastOrdinal = lastTagInfo.ordinal || lastTagInfo.key; - if (lastOrdinal >= tagInfo.key) - { - // create new ordinal - var tail = lastOrdinal.length - 1; - if (('a' <= lastOrdinal[tail]) && (lastOrdinal[tail] < 'z')) - { - // increment last character - lastOrdinal = lastOrdinal.substr(0, tail) + - String.fromCharCode(lastOrdinal.charCodeAt(tail) + 1); - } - else - { - // just begin a new increment position - lastOrdinal += 'a'; - } - tagInfo.ordinal = lastOrdinal; - } - else - { - // no ordinal necessary - tagInfo.ordinal = ''; - } - } - - // Update the tag definition - try - { - tagService.addTagForKey(tagInfo.key, - tagInfo.tag, - tagInfo.color, - tagInfo.ordinal); - } - catch (e) - { - dump('Could not update tag:\n' + e); - } - lastTagInfo = tagInfo; - } // have key - } // have tagInfo - } // for all active tags + } + } } diff --git a/mozilla/mailnews/base/prefs/resources/content/pref-tags.xul b/mozilla/mailnews/base/prefs/resources/content/pref-tags.xul index e549399a8d6..579ebd2a572 100644 --- a/mozilla/mailnews/base/prefs/resources/content/pref-tags.xul +++ b/mozilla/mailnews/base/prefs/resources/content/pref-tags.xul @@ -39,73 +39,80 @@ - - ***** END LICENSE BLOCK ***** --> - - - + - + + - - + + + &pref.tags.description; + + + + + + + + + + + + + +