Commit 6b7f519c authored by mnyromyr%tprac.de's avatar mnyromyr%tprac.de
Browse files

Bug 342560: preference panel for new tag feature; r=IanN, sr=Neil

parent ad06231e
Loading
Loading
Loading
Loading
+7 −7
Original line number Diff line number Diff line
@@ -4790,14 +4790,14 @@ to filter unwanted mail.</p>
</ol>

<ul>
  <li><strong>Customize Tags</strong>: Specifies the tag text and the color
    for each tag. You can edit or replace the default tag text with your
    own text (up to 32 characters). To change the tag color, click the color
    chip next to that tag and select a new color. Use the Move Up and Move Down
    buttons to order your tags by descending importance. Messages with
    multiple tags will be colored according to their most important tag.</li>
  <li><strong>Customize Tags</strong>: Specifies name, color and importance of
    each tag. You can change a tag's name, but all tag names must be different.
    To change the tag color, click the colorpicker chip next to the tag name
    and select a new color. Use the Raise Importance and Lower Importance
    buttons to order your tags by descending importance. Messages with multiple
    tags will be colored according to their most important tag.</li>
  <li><strong>Restore Defaults</strong>: Removes all customized tags and
    restores just the default tags' text and colors.</li>
    restores just the default tag names and colors.</li>
</ul>

<p>[<a href="#mail_and_newsgroup_preferences">Return to beginning of
+1 −1
Original line number Diff line number Diff line
@@ -91,7 +91,7 @@
            <treecell url="chrome://messenger/content/pref-junk.xul" label="&junk.label;"/>
          </treerow>
        </treeitem>
        <treeitem>
        <treeitem id="mailtagspref">
          <treerow>
            <treecell url="chrome://messenger/content/pref-labels.xul" label="&tags.label;"/>
          </treerow>
+330 −72
Original line number Diff line number Diff line
@@ -11,16 +11,13 @@
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Mozilla Communicator.
 * The Original Code is SeaMonkey Internet Suite Code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corp.
 * Portions created by the Initial Developer are Copyright (C) 2001
 * The Initial Developer of the Original Code is the SeaMonkey project.
 * Portions created by the Initial Developer are Copyright (C) 2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Sean Su <ssu@netscape.com>
 *   Ian Neal <bugzilla@arlen.demon.co.uk>
 *   Karsten Düsterloh <mnyromyr@tprac.de>
 *
 * Alternatively, the contents of this file may be used under the terms of
@@ -37,105 +34,366 @@
 *
 * ***** END LICENSE BLOCK ***** */

var gTagListBox = null;
// Each tag entry in our list looks like this, where <key> is tag's unique key:
//    <listitem>
//      <listcell>
//        <textbox onfocus='OnFocus(<key>)/>
//      </listcell>
//      <listcell>
//        <colorpicker onfocus='OnFocus(<key>) type='button'/>
//      </listcell>
//    </listitem>
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

function Startup()
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

// init global stuff before the wsm is used
function InitTagPanel()
{
  gTagListBox = document.getElementById('tagList');    
  BuildTagList();
  gTagList      = document.getElementById(TAGLIST_ID);
  gAddButton    = document.getElementById('addTagButton');
  gDeleteButton = document.getElementById('deleteTagButton');
  gRaiseButton  = document.getElementById('raiseTagButton');
  gLowerButton  = document.getElementById('lowerTagButton');
  UpdateButtonStates();
  parent.initPanel(TAGPANEL_URI);
}

function GetCSSValue(aElement, aProperty)
function Startup()
{
  return getComputedStyle(aElement, null).getPropertyCSSValue(aProperty).cssText;
  parent.hPrefWindow.registerOKCallbackFunc(OnOK);
}

// appends the tag to the tag list box
function AppendTagItem(aTagName, aKey, aColor)
// 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')
    {
  var item = gTagListBox.appendItem(aTagName, aKey);
  item.style.color = aColor;
  var listBackColor = GetCSSValue(gTagListBox, "background-color");
  var itemForeColor = GetCSSValue(item, "color");
  if (listBackColor == itemForeColor)
    item.style.color = GetCSSValue(gTagListBox, "color");
  return item;
      // update taginfo with current values from textbox and colorpicker
      var taginfo = entry.taginfo;
      taginfo.tag   = entry.firstChild.firstChild.value;
      taginfo.color = entry.lastChild.lastChild.color;
      tags.push(taginfo);
    }
  aPageData[ACTIVE_TAGS_ID] = tags;

  // store the list of tags to be deleted in the OKHandler
  aPageData[DELETED_TAGS_ID] = gDeletedTags;

function BuildTagList()
  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))
  {
    var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"]
                               .getService(Components.interfaces.nsIMsgTagService);
  var allTags = tagService.tagEnumerator;
  var allKeys = tagService.keyEnumerator;
  while (allTags.hasMore())
    var tagArray = tagService.getAllTags({});
    tags = aPageData[ACTIVE_TAGS_ID] = [];
    for (i = 0; i < tagArray.length; ++i)
    {
    var key = allKeys.getNext();
    AppendTagItem(allTags.getNext(), key, tagService.getColorForKey(key));
      // 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});
    }
  }

function DeleteTag()
  // 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] : {};
}

// set text and color of the listitem
function UpdateTagEntry(aTagInfo, aEntry)
{
  var tagItemToRemove = gTagListBox.getSelectedItem();
  var index = gTagListBox.selectedIndex;
  if (index >= 0)
  aEntry.firstChild.firstChild.value = aTagInfo.tag;
  aEntry.lastChild.lastChild.color   = aTagInfo.color;
}

function AppendTagEntry(aTagInfo, aRefChild)
{
    var itemToRemove = gTagListBox.getItemAtIndex(index);
    var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"]
                               .getService(Components.interfaces.nsIMsgTagService);
    tagService.deleteKey(itemToRemove.value);
    gTagListBox.removeItemAt(index);
    var numItemsInListBox = gTagListBox.getRowCount();
    gTagListBox.selectedIndex = index < numItemsInListBox ? index : numItemsInListBox - 1;
  // Creating a colorpicker dynamically in an onload handler is really sucky.
  // 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');
  tagCell.appendChild(textbox);

  var colorCell = document.createElement('listcell');
  var colorpicker = document.createElement('colorpicker');
  colorpicker.setAttribute('type', 'button');
  colorCell.appendChild(colorpicker);

  var entry = document.createElement('listitem');
  entry.addEventListener('focus', OnFocus, true);
  entry.setAttribute('allowevents', 'true');  // activate textbox and colorpicker
  entry.taginfo = aTagInfo;
  entry.appendChild(tagCell);
  entry.appendChild(colorCell);

  gTagList.insertBefore(entry, aRefChild);
  UpdateTagEntry(aTagInfo, entry);
  return entry;
}

function OnFocus(aEvent)
{
  // walk up until we find the listitem
  var entry = aEvent.target;
  while (entry.localName != 'listitem')
    entry = entry.parentNode;
  gTagList.selectedItem = entry;
  UpdateButtonStates();
}

function FocusTagEntry(aEntry)
{
  // focus the entry's textbox
  gTagList.ensureElementIsVisible(aEntry);
  aEntry.firstChild.firstChild.focus();
}

function UpdateButtonStates()
{
  var entry = gTagList.selectedItem;
  // disable Delete if no selection
  gDeleteButton.disabled = !entry;
  // disable Raise if no selection or first entry
  gRaiseButton.disabled = !entry || !gTagList.getPreviousItem(entry, 1);
  // disable Lower if no selection or last entry
  gLowerButton.disabled = !entry || !gTagList.getNextItem(entry, 1);
}

function DisambiguateTag(aTag, aTagList)
{
  if (aTag in aTagList)
  {
    var suffix = 2;
    while (aTag + ' ' + suffix in aTagList)
      ++suffix;
    aTag += ' ' + suffix;
  }
  return aTag;
}

function AddTag()
{
  var args = {result: "", okCallback: AddTagCallback};
  var dialog = window.openDialog("chrome://messenger/content/newTagDialog.xul",
                                 "",
                                 "chrome,titlebar,modal",
                                 args);
  // Add a new tag to the UI here. It will be only be written to the
  // preference system only if the OKHandler 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;
  var tag = DisambiguateTag(gAddButton.getAttribute('defaulttagname'), dupeList);

  // create new tag list entry
  var tagInfo = {tag: tag, key: '', color: '', ordinal: ''};
  var refChild = gTagList.getNextItem(gTagList.selectedItem, 1);
  var newEntry = AppendTagEntry(tagInfo, refChild);
  FocusTagEntry(newEntry);
}

function AddTagCallback(aName, aColor)
function DeleteTag()
{
  var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"]
                             .getService(Components.interfaces.nsIMsgTagService);
  tagService.addTag(aName, aColor);
  // 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.
  var entry = gTagList.selectedItem;
  var key = entry.taginfo.key;
  if (key)
    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);
  gTagList.removeItemAt(gTagList.getIndexOfItem(entry));
  if (newFocusItem)
    FocusTagEntry(newFocusItem);
  else
    UpdateButtonStates();
}

  var item = AppendTagItem(aName, tagService.getKeyForTag(aName), aColor);
  var tagListBox = document.getElementById('tagList');
  tagListBox.ensureElementIsVisible(item);
  tagListBox.selectItem(item);
  tagListBox.focus();
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.
  var entry = gTagList.selectedItem;
  var successor = aMoveUp ? gTagList.getPreviousItem(entry, 1)
                          : gTagList.getNextItem(entry, 2);
  entry.parentNode.insertBefore(entry, successor);
  UpdateTagEntry(entry.taginfo, entry);
  FocusTagEntry(entry);
}

function RestoreDefaults()
function Restore()
{
  var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"]
                             .getService(Components.interfaces.nsIMsgTagService);
  // remove all existing labels
  var allKeys = tagService.keyEnumerator;
  while (allKeys.hasMore())
  // clear pref panel tag list
  // Remember any known keys for deletion in the OKHandler.
  while (gTagList.getRowCount())
  {
    tagService.deleteKey(allKeys.getNext());
    gTagListBox.removeItemAt(0);
    var key = gTagList.removeItemAt(0).taginfo.key;
    if (key)
      gDeletedTags[key] = true; // dummy value
  }
  // add default items
  // add default items (no ordinal strings for those)
  var prefService = Components.classes["@mozilla.org/preferences-service;1"]
                              .getService(Components.interfaces.nsIPrefService);
  var prefDescription = prefService.getDefaultBranch("mailnews.labels.description.");
  var prefColor       = prefService.getDefaultBranch("mailnews.labels.color.");
  const kLocalizedString = Components.interfaces.nsIPrefLocalizedString;
  for (var i = 1; i <= 5; ++i)
  {
    // mimic nsMsgTagService::MigrateLabelsToTags() and create default tags from
    // the former label defaults
    var tag   = prefDescription.getComplexValue(i, Components.interfaces.nsIPrefLocalizedString).data;
    // create default tags from the former label defaults
    var key   = "$label" + i;
    var tag   = prefDescription.getComplexValue(i, kLocalizedString).data;
    var color = prefColor.getCharPref(i);
    tagService.addTagForKey("$label" + i, tag, color);
    var tagInfo = {tag: tag, key: key, color: color};
    AppendTagEntry(tagInfo, null);
  }
  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);

  // count dupes so that we can eliminate them later
  var dupeCounts = {}; // indexed by tag
  for (i = 0; i < activeTags.length; ++i)
  {
    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)
    {
      var dupeCount = dupeCounts[tagInfo.tag];
      if (dupeCount > 0)
      {
        // ignore the first dupe, but set mark for further processing
        dupeCounts[tagInfo.tag] = -1;
      }
      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, '', '');
        try
        {
          tagInfo.key = tagService.getKeyForTag(tagInfo.tag);
        }
        catch (e) {}
      }

      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);
        }
  BuildTagList();
        lastTagInfo = tagInfo;
      } // have key
    } // have tagInfo
  } // for all active tags
}
+34 −11
Original line number Diff line number Diff line
@@ -48,39 +48,62 @@
<!DOCTYPE page SYSTEM "chrome://messenger/locale/pref-labels.dtd">

<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
      onload="parent.initPanel('chrome://messenger/content/pref-labels.xul');"
      onload="InitTagPanel()"
      headertitle="&pref.tags.title;">
  <script type="application/x-javascript" src="chrome://messenger/content/pref-labels.js"/>

  <script type="application/x-javascript">
  <![CDATA[
    var _elementIDs = ["addTag", "deleteTag", "restoreDefaults"];
    var _elementIDs = ["addTagButton", "deleteTagButton",
                       "raiseTagButton", "lowerTagButton",
                       "restoreDefaultsButton"];
  ]]>
  </script>


  <groupbox flex="1">
    <caption label="&pref.tags.caption;"/>
    <description>&pref.tags.description;</description>
    <hbox flex="1">
      <listbox id="tagList" flex="1"/>
      <listbox id="tagList" flex="1" onselect="UpdateButtonStates();">
        <listcols>
          <listcol flex="1"/>
          <listcol/>
        </listcols>
        <listhead>
          <listheader label="&tagColumn.label;"/>
          <listheader label="&colorColumn.label;"/>
        </listhead>
      </listbox>

      <vbox>
        <button id="addTag"
        <button id="addTagButton"
                label="&addTagButton.label;"
                accesskey="&addTagButton.accesskey;"
                defaulttagname="&defaultTagName.label;"
                prefstring="pref.tags.disable_button.add"
                oncommand="AddTag();"/>
        <button id="deleteTag"
        <button id="deleteTagButton"
                label="&deleteTagButton.label;"
                accesskey="&deleteTagButton.accesskey;"
                prefstring="pref.tags.disable_button.delete"
                oncommand="DeleteTag();"/>
        <spacer flex="1"/>
        <button id="restoreDefaults"
                label="&restoreDefaults.label;"
                accesskey="&restoreDefaults.accesskey;"
        <button id="raiseTagButton"
                label="&raiseTagButton.label;"
                accesskey="&raiseTagButton.accesskey;"
                prefstring="pref.tags.disable_button.raise"
                oncommand="MoveTag(true);"/>
        <button id="lowerTagButton"
                label="&lowerTagButton.label;"
                accesskey="&lowerTagButton.accesskey;"
                prefstring="pref.tags.disable_button.lower"
                oncommand="MoveTag(false);"/>
        <spacer flex="1"/>
        <button id="restoreButton"
                label="&restoreButton.label;"
                accesskey="&restoreButton.accesskey;"
                prefstring="pref.tags.disable_button.restore"
                oncommand="RestoreDefaults();"/>
                oncommand="Restore();"/>
      </vbox>
    </hbox>
  </groupbox>
+10 −3
Original line number Diff line number Diff line
@@ -38,10 +38,17 @@

<!ENTITY pref.tags.title            "Tags">
<!ENTITY pref.tags.caption          "Customize Tags">
<!ENTITY pref.tags.description      "Tags can be used to categorize and prioritize your messages.">
<!ENTITY pref.tags.description      "Tags can be used to categorize and prioritize your messages. Modify the appearance and importance of tags using the settings below. Tags near the top are more important than those further down.">
<!ENTITY tagColumn.label            "Tag">
<!ENTITY colorColumn.label          "Color">
<!ENTITY defaultTagName.label       "Untitled Tag">
<!ENTITY addTagButton.label         "Add">
<!ENTITY addTagButton.accesskey     "A">
<!ENTITY deleteTagButton.label      "Delete">
<!ENTITY deleteTagButton.accesskey  "D">
<!ENTITY restoreDefaults.label      "Restore Defaults">
<!ENTITY restoreDefaults.accesskey  "R">
<!ENTITY raiseTagButton.label       "Raise Importance">
<!ENTITY raiseTagButton.accesskey   "R">
<!ENTITY lowerTagButton.label       "Lower Importance">
<!ENTITY lowerTagButton.accesskey   "L">
<!ENTITY restoreButton.label        "Restore Defaults">
<!ENTITY restoreButton.accesskey    "s">
Loading