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 ***** -->
-
-
-
+
-
+
+
-
-
+
+
+
+
+
+
+
-
+
+