mailnews/base/search/resources/content/searchWidgets.xml
author rkent
Fri, 14 Nov 2008 13:55:14 -0800
changeset 1112 ae10843a24228437953d01dfef0754cdc583766c
parent 1022 74a7415a42a9374076b138cd3495b06fac328659
child 1714 649e4c6a0e38e805c882ef633083927e6642c51d
permissions -rw-r--r--
set tag action broken in mail filters, r/sr=me

<?xml version="1.0"?>

<!-- ***** BEGIN LICENSE BLOCK *****
   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
   -
   - The contents of this file are subject to the Mozilla Public License Version
   - 1.1 (the "License"); you may not use this file except in compliance with
   - the License. You may obtain a copy of the License at
   - http://www.mozilla.org/MPL/
   -
   - Software distributed under the License is distributed on an "AS IS" basis,
   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
   - for the specific language governing rights and limitations under the
   - License.
   -
   - The Original Code is Filter Action Rules.
   -
   - The Initial Developer of the Original Code is
   -    Scott MacGregor <mscott@mozilla.org>.
   - Portions created by the Initial Developer are Copyright (C) 2005
   - the Initial Developer. All Rights Reserved.
   -
   - Contributor(s):
   -    Karsten Düsterloh <mnyromyr@tprac.de>
   -    Kent James <kent@caspia.com>
   -
   - Alternatively, the contents of this file may be used under the terms of
   - either the GNU General Public License Version 2 or later (the "GPL"), or
   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
   - in which case the provisions of the GPL or the LGPL are applicable instead
   - of those above. If you wish to allow use of your version of this file only
   - under the terms of either the GPL or the LGPL, and not to allow others to
   - use your version of this file under the terms of the MPL, indicate your
   - decision by deleting the provisions above and replace them with the notice
   - and other provisions required by the LGPL or the GPL. If you do not delete
   - the provisions above, a recipient may use your version of this file under
   - the terms of any one of the MPL, the GPL or the LGPL.
   -
   - ***** END LICENSE BLOCK ***** -->

<!--
  This file has the following external dependencies:
      -gFilterActionStrings from FilterEditor.js
      -gFilterList from FilterEditor.js
      -gFilter from FilterEditor.js
      -gPromptService from FilterEditor.js
      -gCustomActions from FilterEditor.js
      -gFilterType from FilterEditor.js
      -gMessengerBundle from msgFolderPickerOverlay.js
      -GetMsgFolderFromUri, SetFolderPickerElement from msgFolderPickerOverlay.js
-->

<!DOCTYPE dialog [
  <!ENTITY % filterEditorDTD SYSTEM "chrome://messenger/locale/FilterEditor.dtd" >
%filterEditorDTD;
  <!ENTITY % msgFolderPickerOverlayDTD SYSTEM "chrome://messenger/locale/msgFolderPickerOverlay.dtd" >
%msgFolderPickerOverlayDTD;
]>

<bindings   id="filterBindings"
            xmlns="http://www.mozilla.org/xbl"
            xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
            xmlns:nc="http://home.netscape.com/NC-rdf#"
            xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="ruleactiontype-menulist">
    <content>
      <xul:menulist class="ruleaction-type">
          <xul:menupopup>
            <xul:menuitem label="&moveMessage.label;" value="movemessage" enablefornews="false"/>
            <xul:menuitem label="&copyMessage.label;" value="copymessage"/>
            <xul:menuseparator enablefornews="false"/>
            <xul:menuitem label="&forwardTo.label;" value="forwardmessage" enablefornews="false"/>
            <xul:menuitem label="&replyWithTemplate.label;" value="replytomessage" enablefornews="false"/>
            <xul:menuseparator/>
            <xul:menuitem label="&markMessageRead.label;" value="markasread"/>
            <xul:menuitem label="&markMessageStarred.label;" value="markasflagged"/>
            <xul:menuitem label="&setPriority.label;"  value="setpriorityto"/>
            <xul:menuitem label="&addTag.label;"  value="addtagtomessage"/>
            <xul:menuitem label="&setJunkScore.label;" value="setjunkscore" enablefornews="false"/>
            <xul:menuseparator enableforpop3="true"/>
            <xul:menuitem label="&deleteMessage.label;" value="deletemessage"/>
            <xul:menuitem label="&deleteFromPOP.label;" value="deletefrompopserver" enableforpop3="true"/>
            <xul:menuitem label="&fetchFromPOP.label;"  value="fetchfrompopserver" enableforpop3="true"/>
            <xul:menuseparator enablefornews="true"/>
            <xul:menuitem label="&ignoreThread.label;" value="ignorethread" enablefornews="true"/>
            <xul:menuitem label="&ignoreSubthread.label;"  value="ignoresubthread" enablefornews="true"/>
            <xul:menuitem label="&watchThread.label;"  value="watchthread" enablefornews="true"/>
            <xul:menuseparator/>
            <xul:menuitem label="&stopExecution.label;" value="stopexecution"/>
          </xul:menupopup>
      </xul:menulist>
    </content>

    <implementation>
      <constructor>
        <![CDATA[
          this.addCustomActions();
          this.hideInvalidActions();
          // differentiate between creating a new, next available action,
          // and creating a row which will be initialized with an action
          if (!this.parentNode.hasAttribute('initialActionIndex'))
          {
            var unavailableActions = this.usedActionsList();
            // select the first one that's not in the list
            for (var index = 0; index < this.menuitems.length; index++)
            {
              var menu = this.menuitems[index];
              if (!(menu.value in unavailableActions) && !menu.hidden)
              {
                this.menulist.value = menu.value;
                this.parentNode.setAttribute('value', menu.value);
                break;
              }
            }
          }
          else
          {
            this.parentNode.mActionTypeInitialized = true;
            this.parentNode.clearInitialActionIndex();
          }
        ]]>
      </constructor>

      <field name="menulist">document.getAnonymousNodes(this)[0]</field>
      <field name="menuitems">this.menulist.getElementsByTagNameNS(this.menulist.namespaceURI, 'menuitem')</field>

      <method name="hideInvalidActions">
        <body>
          <![CDATA[
            var menupopup = this.menulist.menupopup;
            var scope = getScopeFromFilterList(gFilterList);

            // walk through the list of filter actions and hide any actions which aren't valid
            // for our given scope (news, imap, pop, etc) and context
            var elements, i;

            // disable / enable all elements in the "filteractionlist"
            // based on the scope and the "enablefornews" attribute
            elements = menupopup.getElementsByAttribute("enablefornews", "true");
            for (i = 0; i < elements.length; i++)
              elements[i].hidden = scope != Components.interfaces.nsMsgSearchScope.newsFilter;

            elements = menupopup.getElementsByAttribute("enablefornews", "false");
            for (i = 0; i < elements.length; i++)
              elements[i].hidden = scope == Components.interfaces.nsMsgSearchScope.newsFilter;

            elements = menupopup.getElementsByAttribute("enableforpop3", "true");
            for (i = 0; i < elements.length; i++)
              elements[i].hidden = scope != Components.interfaces.nsMsgSearchScope.offlineMailFilter;

            elements = menupopup.getElementsByAttribute("isCustom", "true");
            // Note there might be an additional element here as a placeholder
            // for a missing action, so we iterate over the known actions
            // instead of the elements
            for (i = 0; i < gCustomActions.length; i++)
              elements[i].hidden = !gCustomActions[i]
                                     .isValidForType(gFilterType, scope);
          ]]>
        </body>
      </method>

      <method name="addCustomActions">
        <body>
          <![CDATA[
            var menupopup = this.menulist.menupopup;
            for (var i = 0; i < gCustomActions.length; i++)
            {
              var customAction = gCustomActions[i];
              var menuitem = document.createElementNS(
                "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                "xul:menuitem");
              menuitem.setAttribute("label", customAction.name);
              menuitem.setAttribute("value", customAction.id);
              menuitem.setAttribute("isCustom", "true");
              menupopup.appendChild(menuitem);
            }
          ]]>
        </body>
      </method>

      <method name="numVisibleActions">
        <body>
          <![CDATA[
            var numVisibleActions = 0;
            // only count the items that are visible
            for (var index = 0; index < this.menuitems.length; index++)
              if (!this.menuitems[index].hidden)
                numVisibleActions++;
            return numVisibleActions;
          ]]>
        </body>
      </method>

      <!-- returns a hash containing all of the filter actions which are currently being used by other filteractionrows -->
      <method name="usedActionsList">
        <body>
          <![CDATA[
            var usedActions = {};
            var currentFilterActionRow = this.parentNode;
            var listBox = currentFilterActionRow.mListBox; // need to account for the list item
            // now iterate over each list item in the list box
            for (var index = 0; index < listBox.getRowCount(); index++)
            {
              var filterActionRow = listBox.getItemAtIndex(index);
              if (filterActionRow != currentFilterActionRow)
              {
                var actionValue = filterActionRow.getAttribute('value');

                // let custom actions decide if dups are allowed
                var isCustom = false;
                for (var i = 0; i < gCustomActions.length; i++)
                {
                  if (gCustomActions[i].id == actionValue)
                  {
                    isCustom = true;
                    if (!gCustomActions[i].allowDuplicates)
                      usedActions[actionValue] = true;
                    break;
                  }
                }

                // don't add the tag action, several tags can be set
                if (!isCustom && actionValue != 'addtagtomessage')
                  usedActions[actionValue] = true;
              }
            }
            return usedActions;
          ]]>
        </body>
      </method>
    </implementation>

    <handlers>
      <handler event="command">
        <![CDATA[
          this.parentNode.setAttribute('value', this.menulist.value);
        ]]>
      </handler>

      <handler event="popupshowing">
        <![CDATA[
          var unavailableActions = this.usedActionsList();
          for (var index = 0; index < this.menuitems.length; index++)
          {
            var menu = this.menuitems[index];
            menu.setAttribute('disabled', menu.value in unavailableActions);
          }
        ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="ruleaction">
    <content allowevents="true">
      <xul:listcell class="ruleactiontype"/>
      <xul:listcell class="ruleactiontarget" xbl:inherits="type=value"
                    orient="vertical" align="start" pack="center"/>
      <xul:listcell>
        <xul:button class="small-button" label="+" oncommand="this.parentNode.parentNode.addRow();"/>
        <xul:button class="small-button" label="-" oncommand="this.parentNode.parentNode.removeRow();" anonid="removeButton"/>
      </xul:listcell>
      <xul:listcell/>
    </content>

    <implementation>
      <field name="mListBox">this.parentNode</field>
      <field name="mRemoveButton">document.getAnonymousElementByAttribute(this, "anonid", "removeButton")</field>
      <field name="mActionTypeInitialized">false</field>
      <field name="mRuleActionTargetInitialized">false</field>
      <field name="mRuleActionType">document.getAnonymousNodes(this)[0]</field>

      <method name="clearInitialActionIndex">
        <body>
          <![CDATA[
            // we should only remove the initialActionIndex after we have been told that
            // both the rule action type and the rule action target have both been built since they both need
            // this piece of information. This complication arises because both of these child elements are getting
            // bound asynchronously after the search row has been constructed

            if (this.mActionTypeInitialized && this.mRuleActionTargetInitialized)
              this.removeAttribute('initialActionIndex');
          ]]>
        </body>
      </method>

      <method name="initWithAction">
        <parameter name="aFilterAction"/>
        <body>
          <![CDATA[
            var filterActionStr;
            var actionTarget = document.getAnonymousNodes(this)[1];
            var actionItem = document.getAnonymousNodes(actionTarget);
            var nsMsgFilterAction = Components.interfaces.nsMsgFilterAction;
            switch (aFilterAction.type)
            {
              case nsMsgFilterAction.Custom:
                filterActionStr = aFilterAction.customId;
                if (actionItem)
                  actionItem[0].value = aFilterAction.strValue;

                // Make sure the custom action has been added. If not, it
                // probably was from an extension that has been removed. We'll
                // show a dummy menuitem to warn the user.
                var needCustomLabel = true;
                for (var i = 0; i < gCustomActions.length; i++)
                {
                  if (gCustomActions[i].id == filterActionStr)
                  {
                    needCustomLabel = false;
                    break;
                  }
                }
                if (needCustomLabel)
                {
                  var menuitem = document.createElementNS(
                      "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                      "xul:menuitem");
                  menuitem.setAttribute("label",
                      gFilterBundle.getString("filterMissingCustomAction"));
                  menuitem.setAttribute("value", filterActionStr);
                  menuitem.disabled = true;
                  this.mRuleActionType.menulist.menupopup.appendChild(menuitem);
                  var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
                      .getService(Components.interfaces.nsIConsoleService);
                  var scriptError = Components.classes["@mozilla.org/scripterror;1"]
                      .createInstance(Components.interfaces.nsIScriptError);
                  scriptError.init("Missing custom action " + filterActionStr,
                      null, null, 0, 0,
                      Components.interfaces.nsIScriptError.errorFlag,
                      "component javascript");
                  consoleService.logMessage(scriptError);
                }
                break;
              case nsMsgFilterAction.MoveToFolder:
              case nsMsgFilterAction.CopyToFolder:
                actionItem[0].value = aFilterAction.targetFolderUri;
                break;
              case nsMsgFilterAction.Reply:
              case nsMsgFilterAction.Forward:
                actionItem[0].value = aFilterAction.strValue;
                break;
              case nsMsgFilterAction.Label:
                actionItem[0].value = aFilterAction.label;
                break;
              case nsMsgFilterAction.ChangePriority:
                actionItem[0].value = aFilterAction.priority;
                break;
              case nsMsgFilterAction.JunkScore:
                actionItem[0].value = aFilterAction.junkScore;
                break;
              case nsMsgFilterAction.AddTag:
                actionItem[0].value = aFilterAction.strValue;
                break;
              default:
                break;
            }
            if (aFilterAction.type != nsMsgFilterAction.Custom)
              filterActionStr = gFilterActionStrings[aFilterAction.type];
            document.getAnonymousNodes(this.mRuleActionType)[0]
                    .value = filterActionStr;
            this.mRuleActionTargetInitialized = true;
            this.clearInitialActionIndex();
          ]]>
        </body>
      </method>

      <method name="validateAction">
        <body>
          <![CDATA[
            // returns true if this row represents a valid filter action and false otherwise.
            // This routine also prompts the user.
            var filterActionString = this.getAttribute('value');
            var actionTarget = document.getAnonymousNodes(this)[1];
            var errorString, customError;

            switch (filterActionString)
            {
              case "movemessage":
              case "copymessage":
                var msgFolder = actionTarget.uri ? GetMsgFolderFromUri(actionTarget.uri) : null;
                if (!msgFolder || !msgFolder.canFileMessages)
                  errorString = "mustSelectFolder";
                break;
              case "forwardmessage":
                if (document.getAnonymousNodes(actionTarget)[0].value.length < 3 ||
                    document.getAnonymousNodes(actionTarget)[0].value.indexOf('@') < 1)
                  errorString = "enterValidEmailAddress";
                break;
              case "replytomessage":
                if (!document.getAnonymousNodes(actionTarget)[0].selectedItem)
                   errorString = "pickTemplateToReplyWith";
                break;
              default:
                // some custom actions have no action value node
                if (!document.getAnonymousNodes(actionTarget))
                  return true;
                // locate the correct custom action, and check validity
                for (var i = 0; i < gCustomActions.length; i++)
                  if (gCustomActions[i].id == filterActionString)
                  {
                    customError = 
                        gCustomActions[i].validateActionValue(
                          document.getAnonymousNodes(actionTarget)[0].value,
                          gFilterList.folder, gFilterType);
                    break;
                  }
                break;
            }

            if (gPromptService)
            {
              errorString = errorString ?
                              gFilterBundle.getString(errorString) :
                              customError;
              if (errorString)
                gPromptService.alert(window, null, errorString);
            }

            return !errorString;
          ]]>
        </body>
      </method>

      <method name="saveToFilter">
        <parameter name="aFilter"/>
        <body>
          <![CDATA[
            // create a new filter action, fill it in, and then append it to the filter
            var filterAction = aFilter.createAction();
            var filterActionString = this.getAttribute('value');
            filterAction.type = gFilterActionStrings.indexOf(filterActionString);
            var actionTarget = document.getAnonymousNodes(this)[1];
            var actionItem = document.getAnonymousNodes(actionTarget);
            var nsMsgFilterAction = Components.interfaces.nsMsgFilterAction;
            switch (filterAction.type)
            {
              case nsMsgFilterAction.Label:
                filterAction.label = actionItem[0].getAttribute("value");
                break;
              case nsMsgFilterAction.ChangePriority:
                filterAction.priority = actionItem[0].getAttribute("value");
                break;
              case nsMsgFilterAction.MoveToFolder:
              case nsMsgFilterAction.CopyToFolder:
                filterAction.targetFolderUri = actionTarget.uri;
                break;
              case nsMsgFilterAction.JunkScore:
                filterAction.junkScore = actionItem[0].value;
                break;
              case nsMsgFilterAction.Custom:
                filterAction.customId = filterActionString;
                // fall through to set the value
              default:
                if (actionItem)
                  filterAction.strValue = actionItem[0].value;
                break;
              }
            aFilter.appendAction(filterAction);
          ]]>
        </body>
      </method>

      <method name="addRow">
        <body>
          <![CDATA[
            if (this.mListBox.getRowCount() < document.getAnonymousNodes(this)[0].numVisibleActions())
            {
              var listItem = document.createElement('listitem');
              listItem.className = 'ruleaction';
              this.mListBox.insertBefore(listItem, this.nextSibling);
              this.mListBox.ensureElementIsVisible(listItem);

              // make sure the first remove button is enabled
              this.mListBox.getItemAtIndex(0).mRemoveButton.disabled = false;
            }
          ]]>
        </body>
      </method>

      <method name="removeRow">
        <body>
          <![CDATA[
            // this.mListBox will fail after the row is removed, so save
            var listBox = this.mListBox;
            if (listBox.getRowCount() > 1)
              listBox.removeChild(this);
            // if we only have one row left, then disable the remove button for that row
            listBox.getItemAtIndex(0).mRemoveButton.disabled = listBox.getRowCount() == 1;
          ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="ruleactiontarget-base">
    <implementation>
      <constructor>
        <![CDATA[
          if (this.parentNode.hasAttribute('initialActionIndex'))
          {
            var actionIndex = this.parentNode.getAttribute('initialActionIndex');
            var filterAction = gFilter.actionList.QueryElementAt(actionIndex, Components.interfaces.nsIMsgRuleAction);
            this.parentNode.initWithAction(filterAction);
          }
        ]]>
      </constructor>
    </implementation>
  </binding>

  <binding id="ruleactiontarget-tag" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
    <content>
      <xul:menulist class="ruleactionitem">
        <xul:menupopup>
        </xul:menupopup>
      </xul:menulist>
    </content>

    <implementation>
      <constructor>
        <![CDATA[
            var menuPopup = document.getAnonymousNodes(this)[0].menupopup;
            var tagService = Components.classes["@mozilla.org/messenger/tagservice;1"]
                                       .getService(Components.interfaces.nsIMsgTagService);
            var tagArray = tagService.getAllTags({});
            for (var i = 0; i < tagArray.length; ++i)
            {
              var taginfo = tagArray[i];
              var newMenuItem = document.createElement('menuitem');
              newMenuItem.setAttribute('label', taginfo.tag);
              newMenuItem.setAttribute('value', taginfo.key);
              menuPopup.appendChild(newMenuItem);
            }
          // propagating a pre-existing hack to make the tag get displayed correctly in the menulist
          // now that we've changed the tags for each menu list. We need to use the current selectedIndex
          // (if its defined) to handle the case where we were initialized with a filter action already.
          var currentItem = document.getAnonymousNodes(this)[0].selectedItem;
          document.getAnonymousNodes(this)[0].selectedItem = null;
          document.getAnonymousNodes(this)[0].selectedItem = currentItem;
        ]]>
      </constructor>
    </implementation>
  </binding>

  <binding id="ruleactiontarget-priority" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
    <content>
      <xul:menulist class="ruleactionitem">
        <xul:menupopup>
          <xul:menuitem value="6" label="&highestPriorityCmd.label;"/>
          <xul:menuitem value="5" label="&highPriorityCmd.label;"/>
          <xul:menuitem value="4" label="&normalPriorityCmd.label;"/>
          <xul:menuitem value="3" label="&lowPriorityCmd.label;"/>
          <xul:menuitem value="2" label="&lowestPriorityCmd.label;"/>
        </xul:menupopup>
      </xul:menulist>
    </content>
  </binding>

  <binding id="ruleactiontarget-junkscore" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
    <content>
      <xul:menulist class="ruleactionitem">
        <xul:menupopup>
          <xul:menuitem value="100" label="&junk.label;"/>
          <xul:menuitem value="0"   label="&notJunk.label;"/>
        </xul:menupopup>
      </xul:menulist>
    </content>
  </binding>

  <binding id="ruleactiontarget-replyto" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
    <content>
      <xul:menulist class="ruleactionitem">
        <xul:menupopup>
        </xul:menupopup>
      </xul:menulist>
    </content>

    <implementation>
      <constructor>
        <![CDATA[
            var accountManager = Components.classes["@mozilla.org/messenger/account-manager;1"].getService(Components.interfaces.nsIMsgAccountManager);
            var identity = accountManager.getFirstIdentityForServer(gFilterList.folder.server);
            var rdfService = Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(Components.interfaces.nsIRDFService);
            var resource = rdfService.GetResource(identity.stationeryFolder);
            var msgFolder = resource.QueryInterface(Components.interfaces.nsIMsgFolder);
            var msgWindow = GetFilterEditorMsgWindow();
            var msgDatabase = msgFolder.getMsgDatabase(msgWindow);
            var enumerator = msgDatabase.EnumerateMessages();
            var templateListPopup = document.getAnonymousNodes(this)[0].menupopup;

            if ( enumerator )
            {
              while (enumerator.hasMoreElements())
              {
                var header = enumerator.getNext();
                if (header instanceof Components.interfaces.nsIMsgDBHdr)
                {
                  var msgTemplateUri = msgFolder.URI + "?messageId=" + header.messageId + '&subject=' + header.mime2DecodedSubject;
                  var newItem = document.getAnonymousNodes(this)[0].appendItem(header.mime2DecodedSubject, msgTemplateUri);
                }
              }
            }
        ]]>
      </constructor>
    </implementation>
  </binding>

  <binding id="ruleactiontarget-forwardto" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
    <content>
      <xul:textbox class="ruleactionitem"/>
    </content>
  </binding>

  <binding id="ruleactiontarget-folder" extends="chrome://messenger/content/searchWidgets.xml#ruleactiontarget-base">
    <content>
      <xul:menulist class="ruleactionitem">
        <xul:menupopup class="folderTargetPopup"
                       oncommand="SetFolderPickerElement(
                                    this.getAttribute('uri'), this.parentNode);
                                  this.parentNode.setAttribute(
                                    'value', this.getAttribute('uri'));"/>
      </xul:menulist>
    </content>

    <implementation>

      <constructor>
        <![CDATA[
          if (!this.uri)
          {
            if (!this.menulist.value)
              this.menulist.value = this.menulist.firstChild.tree.builderView
                                        .getResourceAtIndex(0).Value;
            SetFolderPickerElement(this.menulist.value, this.menulist);
          }
        ]]>
      </constructor>

      <property name="uri" readonly="true" onget="return document.getAnonymousNodes(this)[0].getAttribute('uri');"/>
      <field name="menulist">document.getAnonymousNodes(this)[0]</field>
    </implementation>
  </binding>

</bindings>