diff options
Diffstat (limited to '')
-rw-r--r-- | comm/suite/mailnews/content/mailWidgets.xml | 1946 |
1 files changed, 1946 insertions, 0 deletions
diff --git a/comm/suite/mailnews/content/mailWidgets.xml b/comm/suite/mailnews/content/mailWidgets.xml new file mode 100644 index 0000000000..29288b3b70 --- /dev/null +++ b/comm/suite/mailnews/content/mailWidgets.xml @@ -0,0 +1,1946 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<bindings id="mailBindings" + 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"> + + <!-- dummy widget to force this file to load --> + <binding id="dummy" extends="xul:box"/> + + <!-- temporary holding place for horizontal list --> + + <binding id="extdescription" extends="chrome://global/content/bindings/listbox.xml#listbox-base"> + <implementation> + <constructor><![CDATA[ + this.children.filter(aChild => aChild.getAttribute("selected") == "true") + .forEach(this.selectedItems.append, this.selectedItems); + ]]></constructor> + + <!-- ///////////////// public members ///////////////// --> + + <property name="itemCount" readonly="true" + onget="return this.children.length;"/> + + <method name="getIndexOfItem"> + <parameter name="item"/> + <body><![CDATA[ + return this.children.indexOf(item); + ]]></body> + </method> + <method name="getItemAtIndex"> + <parameter name="index"/> + <body><![CDATA[ + return this.children[index] || null; + ]]></body> + </method> + <method name="getRowCount"> + <body><![CDATA[ + return this.children.length; + ]]></body> + </method> + <method name="getNumberOfVisibleRows"> + <body><![CDATA[ + var firstItem = this.children[0] || null; + if (!firstItem) + return 0; // nothing to be visible + var itemsPerRow = Math.floor(this.boxObject.width / firstItem.boxObject.width); + var itemsPerCol = Math.floor(this.boxObject.height / firstItem.boxObject.height); + return Math.max(itemsPerRow, 1) * Math.max(itemsPerCol, 1); + ]]></body> + </method> + <method name="getIndexOfFirstVisibleRow"> + <body><![CDATA[ + //XXXzeniko unimplementable without a way to scroll + ]]></body> + </method> + + <method name="ensureIndexIsVisible"> + <parameter name="index"/> + <body><![CDATA[ + this.ensureElementIsVisible(this.getItemAtIndex(index)); + ]]></body> + </method> + <method name="ensureElementIsVisible"> + <parameter name="item"/> + <body><![CDATA[ + //XXXzeniko unimplementable without a way to scroll + ]]></body> + </method> + <method name="scrollToIndex"> + <parameter name="index"/> + <body><![CDATA[ + //XXXzeniko unimplementable without a way to scroll + ]]></body> + </method> + + <method name="appendItem"> + <parameter name="label"/> + <parameter name="value"/> + <body><![CDATA[ + // -1 appends due to the way getItemAtIndex is implemented + return this.insertItemAt(-1, label, value); + ]]></body> + </method> + <method name="insertItemAt"> + <parameter name="index"/> + <parameter name="label"/> + <parameter name="value"/> + <body><![CDATA[ + const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + var item = document.createElementNS(XULNS, "descriptionitem"); + item.setAttribute("label", label); + this.insertBefore(item, this.getItemAtIndex(index)); + return item; + ]]></body> + </method> + + <method name="scrollOnePage"> + <parameter name="direction"/> + <body><![CDATA[ + return direction * this.getNumberOfVisibleRows(); + ]]></body> + </method> + + <!-- ///////////////// private members ///////////////// --> + + <property name="children" readonly="true" + onget="return Array.from(this.getElementsByTagName('descriptionitem'));"/> + + <method name="_fireOnSelect"> + <body><![CDATA[ + if (!this._suppressOnSelect && !this.suppressOnSelect) { + this.dispatchEvent(new Event("select", + { bubbles: false, cancelable: true })); + } + ]]></body> + </method> + </implementation> + + <handlers> + <handler event="keypress" keycode="VK_LEFT" modifiers="control shift any" + action="this.moveByOffset(-1, !event.ctrlKey, event.shiftKey);" + phase="target" preventdefault="true"/> + <handler event="keypress" keycode="VK_RIGHT" modifiers="control shift any" + action="this.moveByOffset(1, !event.ctrlKey, event.shiftKey);" + phase="target" preventdefault="true"/> + <handler event="click" button="0" phase="target"><![CDATA[ + if (this.selType != "multiple" || (!event.ctrlKey && !event.shiftKey && !event.metaKey)) + this.clearSelection(); + ]]></handler> + <!-- make sure we keep the focus... --> + <handler event="mousedown" button="0" + action="if (document.commandDispatcher.focusedElement != this) this.focus();"/> + </handlers> + </binding> + + <binding id="descriptionitem" extends="chrome://global/content/bindings/listbox.xml#listitem"> + <content> + <xul:hbox class="attachmentBox" xbl:inherits="orient" align="start"> + <xul:label class="descriptioncell-label" xbl:inherits="value=label,flex=flexlabel,crop,disabled,context" flex="1" dir="ltr" crop="center"/> + </xul:hbox> + </content> + </binding> + + <binding id="descriptionitem-iconic" extends="chrome://global/content/bindings/listbox.xml#listitem"> + <content> + <xul:hbox class="attachmentBox" xbl:inherits="orient" align="center"> + <xul:image class="descriptioncell-icon" xbl:inherits="src=image"/> + <xul:label class="descriptioncell-label" xbl:inherits="value=label,flex=flexlabel,crop,disabled,context" flex="1" dir="ltr" crop="center"/> + </xul:hbox> + </content> + </binding> + + <!-- Message Pane Widgets --> + + <!-- mail-toggle-headerfield: Non-email addrs headers which have a toggle + associated with them (i.e. the subject). + Use label to set the header name. + Use headerValue to set the header value. --> + <binding id="mail-toggle-headerfield"> + <content> + <xul:hbox class="headerNameBox" align="start"> + <xul:image class="expandHeaderViewButton" xbl:inherits="onclick=ontwistyclick"/> + <xul:spacer flex="1"/> + <xul:label class="headerName" xbl:inherits="value=label" control="headerValue"/> + </xul:hbox> + <xul:hbox class="headerValueBox" flex="1" align="start"> + <xul:textbox class="headerValue plain" anonid="headerValue" flex="1" readonly="true"/> + </xul:hbox> + </content> + + <implementation> + <property name="headerValue" onset="return document.getAnonymousElementByAttribute(this, 'anonid', 'headerValue').value = val;"/> + </implementation> + </binding> + + <!-- mail-headerfield: presents standard text header name & value pairs. Don't use this for email addresses. + use label to set the header name. + use headerValue to set the header value. --> + <binding id="mail-headerfield"> + <content> + <xul:hbox class="headerNameBox" align="start"> + <xul:label class="headerName" xbl:inherits="value=label" control="headerValue" flex="1"/> + </xul:hbox> + <xul:hbox class="headerValueBox" flex="1" align="start"> + <xul:textbox class="headerValue plain" anonid="headerValue" flex="1" readonly="true"/> + </xul:hbox> + </content> + + <implementation> + <property name="headerValue" onset="return document.getAnonymousElementByAttribute(this, 'anonid', 'headerValue').value = val;"/> + </implementation> + </binding> + + <binding id="mail-urlfield" extends="chrome://messenger/content/mailWidgets.xml#mail-headerfield"> + <content> + <xul:hbox class="headerNameBox" align="start"> + <xul:label class="headerName" xbl:inherits="value=label" flex="1"/> + </xul:hbox> + <xul:hbox class="headerValueBox" flex="1" align="start"> + <xul:label onclick="if (event.button != 2) openAsExternal(event.target.value);" + ondragstart="this.parentNode.setDataTransfer(event);" + class="headerValue plain text-link headerValueUrl" + anonid="headerValue" flex="1" readonly="true" context="copyUrlPopup"/> + </xul:hbox> + </content> + + <implementation> + <method name="setDataTransfer"> + <parameter name="aEvent"/> + <body><![CDATA[ + var dt = aEvent.dataTransfer; + var val = aEvent.target.value; + dt.setData('text/x-moz-url', val + "\n" + val); + dt.setData('text/uri-list', val); + dt.setData('text/plain', val); + ]]></body> + </method> + </implementation> + </binding> + + <binding id="mail-emailheaderfield"> + <content> + <xul:hbox class="headerNameBox" align="start"> + <xul:label class="headerName" xbl:inherits="value=label" flex="1"/> + </xul:hbox> + <xul:hbox class="headerValueBox" flex="1" align="start"> + <xul:mail-emailaddress class="headerValue" anonid="emailAddressNode"/> + </xul:hbox> + </content> + + <implementation> + <property name="emailAddressNode" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'emailAddressNode');" + readonly="true"/> + </implementation> + </binding> + + <!-- multi-emailHeaderField: presents multiple emailheaderfields with a toggle --> + <binding id="mail-multi-emailHeaderField"> + <content> + <xul:hbox class="headerNameBox" align="start" pack="end"> + <xul:image class="addresstwisty" anonid="toggleIcon" + collapsed="true" onclick="toggleWrap();"/> + <xul:label class="headerName" xbl:inherits="value=label"/> + </xul:hbox> + + <xul:hbox class="headerValueBox" anonid="longEmailAddresses" flex="1" align="start" + onoverflow="if (event.detail != 1) this.parentNode.toggleIcon.collapsed = false;" + onunderflow="if (event.detail != 1) this.parentNode.toggleIcon.collapsed = true;"> + <xul:description class="headerValue" anonid="emailAddresses" flex="1"/> + </xul:hbox> + </content> + + <implementation> + <constructor> + <![CDATA[ + this.mAddresses = new Array; + ]]> + </constructor> + + <field name="mAddresses"/> + <!-- as a perf optimization we are going to keep a cache of email address nodes which we've + created around for the lifetime of the widget. mSizeOfAddressCache controls how many of these + elements we keep around --> + <field name="mSizeOfAddressCache">3</field> + + <!-- addAddressView: a public method used to add an address to this widget. + aAddresses is an object with 3 properties: displayName, emailAddress and fullAddress + --> + <method name="addAddressView"> + <parameter name="aAddress"/> + <body> + <![CDATA[ + this.mAddresses.push(aAddress); + ]]> + </body> + </method> + + <!-- updateEmailAddressNode: private method used to set properties on an address node --> + <method name="updateEmailAddressNode"> + <parameter name="aEmailNode"/> + <parameter name="aAddress"/> + <body> + <![CDATA[ + if (aEmailNode.parentNode.useShortView && aAddress.displayName) + { + aEmailNode.setAttribute("label", aAddress.displayName); + aEmailNode.setAttribute("tooltiptext", aAddress.fullAddress); + } + else + { + aEmailNode.setAttribute("label", aAddress.fullAddress || aAddress.displayName); + aEmailNode.removeAttribute("tooltiptext"); + } + aEmailNode.setAttribute("emailAddress", aAddress.emailAddress); + aEmailNode.setAttribute("fullAddress", aAddress.fullAddress); + aEmailNode.setAttribute("displayName", aAddress.displayName); + + // Add aria-label with header field type and header field content + // for better accessibility. + // Note: No extra colon and space needed, since it is + // already provided by this object's label attribute. + var ariaLabel = this.getAttribute("label") + + aEmailNode.getAttribute("label"); + aEmailNode.setAttribute("aria-label", ariaLabel); + + try + { + if ("UpdateEmailNodeDetails" in top) + UpdateEmailNodeDetails(aAddress.emailAddress, aEmailNode); + } + catch(ex) + { + dump("UpdateEmailNodeDetails failed: " + ex + "\n"); + } + ]]> + </body> + </method> + + <!-- fillCachedAddresses: private method used to fill up any cached pre-existing + emailAddress fields without creating new email address fields. Returns a remainder + for the # of addresses which require new addresses being created. + Invariants: 1) aNumAddressesToShow >= 0 && it is <= mAddresses.length --> + <method name="fillCachedAddresses"> + <parameter name="aAddressesNode"/> + <parameter name="aNumAddressesToShow"/> + <body> + <![CDATA[ + var numExistingCachedAddresses = aAddressesNode.childNodes.length; + if (!numExistingCachedAddresses) + return this.mAddresses.length; // we couldn't pre fill anything + else if (numExistingCachedAddresses > 1) + numExistingCachedAddresses = (numExistingCachedAddresses + 1)/ 2; + + var index = 0; + var numAddressesAdded = 0; + var emailAddressNode; + var commaNode; + while (numAddressesAdded < numExistingCachedAddresses && numAddressesAdded < aNumAddressesToShow) + { + if (index && numExistingCachedAddresses > 1) + { + commaNode = aAddressesNode.childNodes[index++]; + if (commaNode) + commaNode.hidden = false; + } + + // get the node pointed to by index + emailAddressNode = aAddressesNode.childNodes[index++]; + this.updateEmailAddressNode(emailAddressNode, this.mAddresses[numAddressesAdded]); + emailAddressNode.hidden = false; + numAddressesAdded++; + } + + // if we have added all of our elements but we still have more cached items in this address node + // then make sure the extra cached copies are hidden... + numExistingCachedAddresses = aAddressesNode.childNodes.length; // reset + while (index < numExistingCachedAddresses) + { + aAddressesNode.childNodes[index++].hidden = true; + } + + return this.mAddresses.length - numAddressesAdded; + ]]> + </body> + </method> + + <!-- fillAddressesNode: private method used to create email address nodes for either our short + or long view. aAddressesNode: the div we want to add addresses too. + aNumAddressesToShow: number of addresses to put into the list --> + <method name="fillAddressesNode"> + <parameter name="aAddressesNode"/> + <parameter name="aNumAddressesToShow"/> + <body> + <![CDATA[ + var numAddresses = this.mAddresses.length; + if (aNumAddressesToShow <= 0 || aNumAddressesToShow > numAddresses) // then show all + aNumAddressesToShow = numAddresses; + + // before we try to create email address nodes, try to leverage any cached nodes... + var remainder = this.fillCachedAddresses(aAddressesNode, aNumAddressesToShow); + var index = numAddresses - remainder; + while (index < numAddresses && index < aNumAddressesToShow) + { + var newAddressNode = document.createElement("mail-emailaddress"); + + // Stash the headerName somewhere that UpdateEmailNodeDetails + // will be able to find it. + newAddressNode.setAttribute("headerName", this.headerName); + + if (index) + { + var textNode = document.createElement("text"); + textNode.setAttribute("value", ", "); + textNode.setAttribute("class", "emailSeparator"); + aAddressesNode.appendChild(textNode); + } + + var itemInDocument = aAddressesNode.appendChild(newAddressNode); + this.updateEmailAddressNode(itemInDocument, this.mAddresses[index]); + index++; + } + ]]> + </body> + </method> + + <property name="emailAddresses" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'emailAddresses');" + readonly="true"/> + <property name="longEmailAddresses" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'longEmailAddresses');" + readonly="true"/> + <property name="toggleIcon" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'toggleIcon');" + readonly="true"/> + + <!-- buildView: public method used by callers when they are done adding all the email addresses to the widget + aNumAddressesToShow: total # of addresses to show in the short view --> + <method name="buildViews"> + <body> + <![CDATA[ + this.fillAddressesNode(this.emailAddresses, -1); + ]]> + </body> + </method> + + <!-- Updates the nodes of this field with a call to + UpdateExtraAddressProcessing. The parameters are optional fields + that can contain extra information to be passed to + UpdateExtraAddressProcessing, the implementation of that function + should be checked to determine what it requires --> + <method name="updateExtraAddressProcessing"> + <parameter name="aParam1"/> + <parameter name="aParam2"/> + <parameter name="aParam3"/> + <body> + <![CDATA[ + if (UpdateExtraAddressProcessing) { + var childNodes = this.emailAddresses.childNodes; + for (let i = 0; i < this.mAddresses.length; i++) { + UpdateExtraAddressProcessing(this.mAddresses[i], + childNodes[i * 2], + aParam1, aParam2, aParam3); + } + } + ]]> + </body> + </method> + + <method name="toggleWrap"> + <body> + <![CDATA[ + if (this.toggleIcon.hasAttribute("open")) { + this.toggleIcon.removeAttribute("open"); + this.longEmailAddresses.setAttribute("singleline", "true"); + } else { + this.toggleIcon.setAttribute("open", "true"); + this.longEmailAddresses.removeAttribute("singleline"); + } + ]]> + </body> + </method> + + <!-- internal method used to clear both our divs --> + <method name="clearChildNodes"> + <parameter name="aParentNode"/> + <body> + <![CDATA[ + // we want to keep around the first mSizeOfAddressCache email address nodes + // don't forget that we have comma text nodes in there too so really we want to keep + // around cache size * 2 - 1. + var numItemsToPreserve = this.mSizeOfAddressCache * 2 - 1; + var numItemsInNode = aParentNode.childNodes.length; + + while (numItemsInNode && (numItemsInNode > numItemsToPreserve)) + { + aParentNode.childNodes[numItemsInNode - 1].remove(); + numItemsInNode = numItemsInNode - 1; + } + ]]> + </body> + </method> + + <method name="clearHeaderValues"> + <body> + <![CDATA[ + // clear out our local state + this.mAddresses = new Array; + if (this.toggleIcon.hasAttribute("open")) + // no automatic overflow tracking in this case + this.toggleIcon.collapsed = true; + this.toggleIcon.removeAttribute("open"); + this.longEmailAddresses.setAttribute("singleline", "true"); + // remove anything inside of each of our labels.... + this.clearChildNodes(this.emailAddresses); + ]]> + </body> + </method> + </implementation> + </binding> + + <binding id="mail-emailaddress"> + <content popup="emailAddressPopup" context="emailAddressPopup"> + <xul:description anonid="emailValue" class="emailDisplayButton plain" + xbl:inherits="xbl:text=label,crop,aria-label" flex="1"/> + <xul:image class="emailDisplayImage" anonid="emailImage" + xbl:inherits="src=image"/> + </content> + + <implementation> + <property name="label" onset="this.getPart('emailValue').setAttribute('label',val); return val;" + onget="return this.getPart('emailValue').getAttribute('label');"/> + <property name="crop" onset="this.getPart('emailValue').setAttribute('crop',val); return val;" + onget="return this.getPart('emailValue').getAttribute('crop');"/> + <property name="disabled" onset="this.getPart('emailValue').setAttribute('disabled',val); return val;" + onget="return this.getPart('emailValue').getAttribute('disabled');"/> + <property name="src" onset="this.getPart('emailImage').setAttribute('src',val); return val;" + onget="return this.getPart('emailImage').getAttribute('src');"/> + <property name="imgalign" onset="this.getPart('emailImage').setAttribute('imgalign',val); return val;" + onget="return this.getPart('emailImage').getAttribute('imgalign');"/> + + <method name="getPart"> + <parameter name="aPartId"/> + <body><![CDATA[ + return document.getAnonymousElementByAttribute(this, "anonid", aPartId); + ]]></body> + </method> + </implementation> + </binding> + + <binding id="mail-messageids-headerfield"> + <content> + <xul:hbox class="headerNameBox" align="start" pack="end"> + <xul:image class="addresstwisty" anonid="toggleIcon" + onclick="toggleWrap();"/> + <xul:label class="headerName" xbl:inherits="value=label"/> + </xul:hbox> + <xul:hbox class="headerValueBox" flex="1" align="start"> + <xul:label class="headerValue" anonid="headerValue" flex="1"/> + </xul:hbox> + </content> + + <implementation> + <constructor> + <![CDATA[ + this.mMessageIds = []; + this.showFullMessageIds = false; + ]]> + </constructor> + + <property name="headerValue" readonly="true" + onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'headerValue');"/> + <property name="toggleIcon" readonly="true" + onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'toggleIcon');"/> + + <field name="mMessageIds"/> + + <!-- addMessageIdView: a public method used to add a message-id to this widget. --> + <method name="addMessageIdView"> + <parameter name="aMessageId"/> + <body> + <![CDATA[ + this.mMessageIds.push(aMessageId); + ]]> + </body> + </method> + + <!-- updateMessageIdNode: private method used to set properties on an MessageId node --> + <method name="updateMessageIdNode"> + <parameter name="aMessageIdNode"/> + <parameter name="aIndex"/> + <parameter name="aMessageId"/> + <parameter name="aLastId"/> + <body> + <![CDATA[ + var showFullMessageIds = this.showFullMessageIds; + + if (showFullMessageIds || aIndex == aLastId) + { + aMessageIdNode.setAttribute("label", aMessageId); + aMessageIdNode.removeAttribute("tooltiptext"); + } + else + { + aMessageIdNode.setAttribute("label", aIndex); + aMessageIdNode.setAttribute("tooltiptext", aMessageId); + } + + aMessageIdNode.setAttribute("index", aIndex); + aMessageIdNode.setAttribute("messageid", aMessageId); + ]]> + </body> + </method> + + <method name="fillMessageIdNodes"> + <body> + <![CDATA[ + var headerValue = this.headerValue; + var messageIdNodes = headerValue.childNodes; + var numMessageIds = this.mMessageIds.length; + var index = 0; + + while (messageIdNodes.length > numMessageIds * 2 - 1) + headerValue.lastChild.remove(); + + this.toggleIcon.hidden = numMessageIds <= 1; + + for (var index = 0; index < numMessageIds; index++) + { + if (index * 2 <= messageIdNodes.length - 1) + { + this.updateMessageIdNode(messageIdNodes[index * 2], index + 1, + this.mMessageIds[index], numMessageIds); + } + else + { + var newMessageIdNode = document.createElement("mail-messageid"); + + if (index) + { + var textNode = document.createElement("text"); + textNode.setAttribute("value", ", "); + textNode.setAttribute("class", "messageIdSeparator"); + headerValue.appendChild(textNode); + } + var itemInDocument = headerValue.appendChild(newMessageIdNode); + this.updateMessageIdNode(itemInDocument, index + 1, + this.mMessageIds[index], numMessageIds); + } + } + ]]> + </body> + </method> + + <method name="toggleWrap"> + <body> + <![CDATA[ + var headerValue = this.headerValue; + var messageIdNodes = headerValue.childNodes; + var showFullMessageIds = !this.showFullMessageIds; + var messageIds = this.mMessageIds + + for (var i = 0; i < messageIdNodes.length; i += 2) + { + if (showFullMessageIds) + { + this.toggleIcon.setAttribute("open", "true"); + messageIdNodes[i].setAttribute("label", messageIds[i / 2]); + messageIdNodes[i].removeAttribute("tooltiptext"); + headerValue.removeAttribute("singleline"); + } else + { + this.toggleIcon.removeAttribute("open"); + messageIdNodes[i].setAttribute("label", i / 2 + 1); + messageIdNodes[i].setAttribute("tooltiptext", messageIds[i / 2]); + } + } + + this.showFullMessageIds = showFullMessageIds; + ]]> + </body> + </method> + + <method name="clearHeaderValues"> + <body> + <![CDATA[ + // clear out our local state + this.mMessageIds = new Array; + if (this.showFullMessageIds) + { + this.showFullMessageIds = false; + this.toggleIcon.removeAttribute("open"); + } + ]]> + </body> + </method> + </implementation> + </binding> + + <binding id="mail-messageid"> + <content context="messageIdContext" onclick="MessageIdClick(this, event);"> + <xul:label anonid="messageIdValue" class="messageIdDisplayButton plain" + xbl:inherits="value=label"/> + <xul:image class="messageIdDisplayImage" anonid="messageIdImage"/> + </content> + + <implementation> + <property name="label" onset="this.getPart().setAttribute('label',val); return val;" + onget="return this.getPart('messageIdValue').getAttribute('label');"/> + + <method name="getPart"> + <parameter name="aPartId"/> + <body><![CDATA[ + return document.getAnonymousElementByAttribute(this, "anonid", 'messageIdValue'); + ]]></body> + </method> + </implementation> + </binding> + + <!-- Header field for showing the tags associated with a message --> + <binding id="mail-headerfield-tags"> + <content> + <xul:hbox class="headerNameBox" align="start"> + <xul:label class="headerName" xbl:inherits="value=label" flex="1"/> + </xul:hbox> + <xul:hbox class="headerValueBox" flex="1" align="start"> + <xul:label class="headerValue plain" anonid="headerValue" flex="1"/> + </xul:hbox> + </content> + + <implementation> + <property name="headerValue" onset="return this.buildTags(val);"/> + <method name="buildTags"> + <parameter name="aTags"/> + <body> + <![CDATA[ + // aTags contains a list of actual tag names (not the keys), delimited by spaces + // each tag name is encoded. + + // remove any existing tag items we've appended to the list + var headerValueNode = document.getAnonymousElementByAttribute(this, 'anonid', 'headerValue'); + for (var i = headerValueNode.childNodes.length - 1; i >= 0; --i) + headerValueNode.childNodes[i].remove(); + + // tokenize the keywords based on ' ' + var tagsArray = aTags.split(' '); + for (var index = 0; index < tagsArray.length; index++) + { + // for each tag, create a label, give it the font color that corresponds to the + // color of the tag and append it. + var tagName; + try { + // if we got a bad tag name, getTagForKey will throw an exception, skip it + // and go to the next one. + tagName = MailServices.tags.getTagForKey(tagsArray[index]); + } catch (ex) { continue; } + + var color = MailServices.tags.getColorForKey(tagsArray[index]); + + // now create a label for the tag name, and set the color + var label = document.createElement("label"); + label.setAttribute('value', tagName); + label.style.color = color; + label.className = "tagvalue blc-" + color.substr(1); + headerValueNode.appendChild(label); + } + ]]> + </body> + </method> + <constructor> + <![CDATA[ + var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" + ); + ]]> + </constructor> + </implementation> + </binding> + + <binding id="search-menulist-abstract" name="searchMenulistAbstract" extends="xul:box"> + <content> + <xul:menulist class="search-menulist" xbl:inherits="flex,disabled" oncommand="this.parentNode.onSelect(event)"> + <xul:menupopup class="search-menulist-popup"/> + </xul:menulist> + </content> + + <implementation> + <field name="internalScope">null</field> + <field name="internalValue">-1</field> + <field readonly="true" name="validityManager"> + <![CDATA[ + Cc['@mozilla.org/mail/search/validityManager;1'].getService(Ci.nsIMsgSearchValidityManager); + ]]> + </field> + <property name="searchScope" onget="return this.internalScope;"> + <!-- scope ID - retrieve the table --> + <setter> + <![CDATA[ + // if scope isn't changing this is a noop + if (this.internalScope == val) return val; + + this.internalScope = val; + this.refreshList(); + var targets = this.targets; + if (targets) { + for (var i=0; i< targets.length; i++) { + targets[i].searchScope = val; + } + } + return val; + ]]> + </setter> + </property> + + <property name="validityTable" readonly="true" onget="return this.validityManager.getTable(this.searchScope)"/> + + <property name="targets" readonly="true"> + <getter> + <![CDATA[ + var forAttrs = this.getAttribute("for"); + if (!forAttrs) return null; + var targetIds = forAttrs.split(","); + if (targetIds.length == 0) return null; + + var targets = new Array; + for (let j = 0, i = 0; i < targetIds.length; i++) { + var target = document.getElementById(targetIds[i]); + if (target) targets[j++] = target; + } + return targets; + ]]> + </getter> + </property> + + <property name="optargets" readonly="true"> + <getter> + <![CDATA[ + var forAttrs = this.getAttribute("opfor"); + if (!forAttrs) return null; + var optargetIds = forAttrs.split(","); + if (optargetIds.length == 0) return null; + + var optargets = new Array; + var j=0; + for (var i=0; i<optargetIds.length;i++) { + var optarget = document.getElementById(optargetIds[i]); + if (optarget) optargets[j++] = optarget; + } + return optargets; + ]]> + </getter> + </property> + + <property name="value" onget="return this.internalValue;"> + <setter> + <![CDATA[ + if (this.internalValue == val) + return val; + this.internalValue = val; + var menulist = document.getAnonymousNodes(this)[0]; + menulist.selectedItem = this.validMenuitem; + + // now notify targets of new parent's value + var targets = this.targets; + if (targets) { + for (var i=0; i < targets.length; i++) { + targets[i].parentValue = val; + } + } + + // now notify optargets of new op parent's value + var optargets = this.optargets; + if (optargets) { + for (i=0; i < optargets.length; i++) { + optargets[i].opParentValue = val; + } + } + + return val; + ]]> + </setter> + </property> + <!-- label forwards to the internal menulist's "label" attribute --> + <property name="label" onget="return document.getAnonymousNodes(this)[0].selectedItem.getAttribute('label');"> + </property> + <property name="validMenuitem" readonly="true"> + <!-- Prepare menulist selection, adding a missing hidden menuitem if needed, and + updating the disabled state of the menulist label. --> + <getter> + <![CDATA[ + if (this.value == -1) // -1 means not initialized + return null; + + let menulist = document.getAnonymousNodes(this)[0]; + let isCustom = isNaN(this.value); + let typedValue = isCustom ? this.value : parseInt(this.value); + + // custom attribute to style the unavailable menulist item + menulist.setAttribute("unavailable", + !this.valueIds.includes(typedValue)); + + // add a hidden menulist item if value is missing + let menuitem = menulist.getElementsByAttribute("value", this.value).item(0); + if (!menuitem) + { // need to add a hidden menuitem + menuitem = menulist.appendItem(this.valueLabel, this.value); + menuitem.hidden = true; + } + return menuitem; + ]]> + </getter> + </property> + <method name="refreshList"> + <parameter name="dontRestore"/> <!-- should we not restore old selection? --> + <body> + <![CDATA[ + var menuItemIds = this.valueIds; + var menuItemStrings = this.valueStrings; + + var menulist = document.getAnonymousNodes(this)[0]; + var popup = menulist.firstChild; + + // save our old "value" so we can restore it later + var oldData; + if (!dontRestore) + oldData = menulist.value; + + // remove the old popup children + while (popup.hasChildNodes()) + popup.lastChild.remove(); + + var newSelection; + var customizePos=-1; + for (var i = 0; i < menuItemIds.length; ++i) + { + // create the menuitem + if (Ci.nsMsgSearchAttrib.OtherHeader == menuItemIds[i].toString()) + customizePos = i; + else + { + var menuitem = document.createElement("menuitem"); + menuitem.setAttribute("label", menuItemStrings[i]); + menuitem.setAttribute("value", menuItemIds[i]); + popup.appendChild(menuitem); + // try to restore the selection + if (!newSelection || oldData == menuItemIds[i].toString()) + newSelection = menuitem; + } + } + if (customizePos != -1) + { + var separator = document.createElement("menuseparator"); + popup.appendChild(separator); + menuitem = document.createElement("menuitem"); + menuitem.setAttribute("label", menuItemStrings[customizePos]); + menuitem.setAttribute("value", menuItemIds[customizePos]); + popup.appendChild(menuitem); + } + // + // If we are either uninitialized, or if we are called because + // of a change in our parent, update the value to the + // default stored in newSelection. + // + if ((this.value == -1 || dontRestore) && newSelection) + this.value = newSelection.getAttribute("value"); + menulist.selectedItem = this.validMenuitem; + ]]> + </body> + </method> + <method name="onSelect"> + <parameter name="event"/> + <body> + <![CDATA[ + var menulist = document.getAnonymousNodes(this)[0]; + if (menulist.value == Ci.nsMsgSearchAttrib.OtherHeader) { + // Customize menuitem selected. + let args = {}; + window.openDialog("chrome://messenger/content/CustomHeaders.xul", + "", + "modal,centerscreen,resizable,titlebar,chrome", + args); + // User may have removed the custom header currently selected in + // the menulist so temporarily set the selection to a safe value. + this.value = Ci.nsMsgSearchAttrib.OtherHeader; + // rebuild the menulist + UpdateAfterCustomHeaderChange(); + // Find the created or chosen custom header and select it. + if (args.selectedVal) { + let menuitem = menulist.querySelector('[label="' + + args.selectedVal + '"]'); + this.value = menuitem.value; + } else { + // Nothing was picked in the custom headers editor so just pick + // something instead of the current "Customize" menuitem. + this.value = menulist.getItemAtIndex(0).value; + } + } else { + this.value = menulist.value; + } + ]]> + </body> + </method> + </implementation> + </binding> + + <!-- searchattribute - Subject, Sender, To, CC, etc. --> + <binding id="searchattribute" name="searchAttribute" + extends="chrome://messenger/content/mailWidgets.xml#search-menulist-abstract"> + <implementation> + <field name="stringBundle"> + <![CDATA[ + this.Services.strings.createBundle( + "chrome://messenger/locale/search-attributes.properties"); + ]]> + </field> + <property name="valueLabel" readonly="true"> + <getter> + <![CDATA[ + if (isNaN(this.value)) // is this a custom term? + { + let customTerm = MailServices.filters.getCustomTerm(this.value); + if (customTerm) + return customTerm.name; + // The custom term may be missing after the extension that added + // it was disabled or removed. We need to notify the user. + let scriptError = Cc["@mozilla.org/scripterror;1"] + .createInstance(Ci.nsIScriptError); + scriptError.init("Missing custom search term " + this.value, + null, null, 0, 0, Ci.nsIScriptError.errorFlag, + "component javascript"); + this.Services.console.logMessage(scriptError); + return this.stringBundle.GetStringFromName("MissingCustomTerm"); + } + return this.stringBundle.GetStringFromName( + this.validityManager.getAttributeProperty(parseInt(this.value))); + ]]> + </getter> + </property> + <property name="valueIds" readonly="true"> + <getter> + <![CDATA[ + let result = this.validityTable.getAvailableAttributes(); + // add any available custom search terms + for (let customTerm of MailServices.filters.getCustomTerms()) { + // for custom terms, the array element is a string with the custom id + // instead of the integer attribute + if (customTerm.getAvailable(this.searchScope, null)) + result.push(customTerm.id); + } + return result; + ]]> + </getter> + </property> + <property name="valueStrings" readonly="true"> + <getter> + <![CDATA[ + let strings = new Array; + let ids = this.valueIds; + let hdrsArray = null; + try + { + let hdrs = + this.Services.prefs.getCharPref("mailnews.customHeaders"); + hdrs = hdrs.replace(/\s+/g, ""); //remove white spaces before splitting + hdrsArray = hdrs.match(/[^:]+/g); + } + catch(ex) + { + } + + let j = 0; + for (let i = 0; i < ids.length; i++) + { + if (isNaN(ids[i])) // Is this a custom search term? + { + let customTerm = MailServices.filters.getCustomTerm(ids[i]); + if (customTerm) + strings[i] = customTerm.name; + else + strings[i] = ""; + } + else if(ids[i] > Ci.nsMsgSearchAttrib.OtherHeader && hdrsArray) + strings[i] = hdrsArray[j++]; + else + strings[i] = this.stringBundle.GetStringFromName( + this.validityManager.getAttributeProperty(ids[i])); + } + return strings; + ]]> + </getter> + </property> + <constructor> + <![CDATA[ + var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" + ); + ChromeUtils.import("resource://gre/modules/Services.jsm", this); + initializeTermFromId(this.id); + ]]> + </constructor> + </implementation> + </binding> + + <!-- searchoperator - Contains, Is Less than, etc --> + <binding id="searchoperator" name="searchOperator" + extends="chrome://messenger/content/mailWidgets.xml#search-menulist-abstract"> + <implementation> + <field name="searchAttribute">Ci.nsMsgSearchAttrib.Default</field> + <field name="stringBundle"> + <![CDATA[ + this.Services.strings.createBundle("chrome://messenger/locale/search-operators.properties") + ]]> + </field> + <property name="valueLabel" readonly="true"> + <getter> + <![CDATA[ + return this.stringBundle.GetStringFromName(this.value); + ]]> + </getter> + </property> + <property name="valueIds" readonly="true"> + <getter> + <![CDATA[ + let isCustom = isNaN(this.searchAttribute); + if (isCustom) + { + let customTerm = MailServices.filters.getCustomTerm(this.searchAttribute); + if (customTerm) + return customTerm.getAvailableOperators(this.searchScope); + return [Ci.nsMsgSearchOp.Contains]; + } + return this.validityTable.getAvailableOperators(this.searchAttribute); + ]]> + </getter> + </property> + <property name="valueStrings" readonly="true"> + <getter> + <![CDATA[ + let strings = new Array; + let ids = this.valueIds; + for (let i = 0; i < ids.length; i++) + strings[i] = this.stringBundle.GetStringFromID(ids[i]); + return strings; + ]]> + </getter> + </property> + <property name="parentValue"> + <setter> + <![CDATA[ + if (this.searchAttribute == val && val != Ci.nsMsgSearchAttrib.OtherHeader) return val; + this.searchAttribute = val; + this.refreshList(true); // don't restore the selection, since searchvalue nulls it + if (val == Ci.nsMsgSearchAttrib.AgeInDays) { + // Bug 187741 We want "Age in Days" to default to "is less than". + this.value = Ci.nsMsgSearchOp.IsLessThan; + } + return val; + ]]> + </setter> + <getter> + <![CDATA[ + return this.searchAttribute; + ]]> + </getter> + </property> + <constructor> + <![CDATA[ + var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" + ); + ChromeUtils.import("resource://gre/modules/Services.jsm", this); + ]]> + </constructor> + </implementation> + </binding> + + <!-- searchvalue - a widget which dynamically changes its user interface + depending on what type of data it's supposed to be showing + currently handles arbitrary text entry, and menulists for + priority, status, junk status, tags, hasAttachment status, + and addressbook + --> + <binding id="searchvalue" name="searchValue"> + <content> + <xul:textbox flex="1" class="search-value-textbox" xbl:inherits="disabled"/> + <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled"> + <xul:menupopup class="search-value-popup"> + <xul:menuitem value="6" stringTag="priorityHighest" class="search-value-menuitem"/> + <xul:menuitem value="5" stringTag="priorityHigh" class="search-value-menuitem"/> + <xul:menuitem value="4" stringTag="priorityNormal" class="search-value-menuitem"/> + <xul:menuitem value="3" stringTag="priorityLow" class="search-value-menuitem"/> + <xul:menuitem value="2" stringTag="priorityLowest" class="search-value-menuitem"/> + </xul:menupopup> + </xul:menulist> + <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled"> + <xul:menupopup class="search-value-popup"> + <xul:menuitem value="2" stringTag="replied" class="search-value-menuitem"/> + <xul:menuitem value="1" stringTag="read" class="search-value-menuitem"/> + <xul:menuitem value="65536" stringTag="new" class="search-value-menuitem"/> + <xul:menuitem value="4096" stringTag="forwarded" class="search-value-menuitem"/> + <xul:menuitem value="4" stringTag="flagged" class="search-value-menuitem"/> + </xul:menupopup> + </xul:menulist> + <xul:textbox flex="1" class="search-value-textbox" xbl:inherits="disabled"/> + <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled"> + <xul:menupopup class="search-value-popup addrbooksPopup" localonly="true"/> + </xul:menulist> + <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled"> + <xul:menupopup class="search-value-popup"> + </xul:menupopup> + </xul:menulist> + <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled"> + <xul:menupopup class="search-value-popup"> + <xul:menuitem value="2" stringTag="junk" class="search-value-menuitem"/> + </xul:menupopup> + </xul:menulist> + <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled"> + <xul:menupopup class="search-value-popup"> + <xul:menuitem value="0" stringTag="hasAttachments" class="search-value-menuitem"/> + </xul:menupopup> + </xul:menulist> + <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled"> + <xul:menupopup class="search-value-popup"> + <xul:menuitem value="plugin" stringTag="junkScoreOriginPlugin" + class="search-value-menuitem"/> + <xul:menuitem value="user" stringTag="junkScoreOriginUser" + class="search-value-menuitem"/> + <xul:menuitem value="filter" stringTag="junkScoreOriginFilter" + class="search-value-menuitem"/> + <xul:menuitem value="whitelist" stringTag="junkScoreOriginWhitelist" + class="search-value-menuitem"/> + <xul:menuitem value="imapflag" stringTag="junkScoreOriginImapFlag" + class="search-value-menuitem"/> + </xul:menupopup> + </xul:menulist> + <xul:hbox flex="1" class="search-value-custom" xbl:inherits="disabled"/> + </content> + <implementation> + <field name="internalOperator">null</field> + <field name="internalAttribute">null</field> + <field name="internalValue">null</field> + <property name="opParentValue" onget="return this.internalOperator;"> + <setter> + <![CDATA[ + // noop if we're not changing it + if (this.internalOperator == val) return val; + + // Keywords has the null field IsEmpty + if (this.searchAttribute == Ci.nsMsgSearchAttrib.Keywords) { + if (val == Ci.nsMsgSearchOp.IsEmpty || + val == Ci.nsMsgSearchOp.IsntEmpty) + this.setAttribute("selectedIndex", "-1"); + else + this.setAttribute("selectedIndex", "5"); + } + + // JunkStatus has the null field IsEmpty + if (this.searchAttribute == Ci.nsMsgSearchAttrib.JunkStatus) { + if (val == Ci.nsMsgSearchOp.IsEmpty || + val == Ci.nsMsgSearchOp.IsntEmpty) + this.setAttribute("selectedIndex", "-1"); + else + this.setAttribute("selectedIndex", "6"); + } + + // if it's not sender, to, cc, alladdresses, or toorcc, we don't care + if (this.searchAttribute != Ci.nsMsgSearchAttrib.Sender && + this.searchAttribute != Ci.nsMsgSearchAttrib.To && + this.searchAttribute != Ci.nsMsgSearchAttrib.ToOrCC && + this.searchAttribute != Ci.nsMsgSearchAttrib.AllAddresses && + this.searchAttribute != Ci.nsMsgSearchAttrib.CC ) { + this.internalOperator = val; + return val; + } + + var children = document.getAnonymousNodes(this); + if (val == Ci.nsMsgSearchOp.IsntInAB || + val == Ci.nsMsgSearchOp.IsInAB) { + // if the old internalOperator was + // IsntInAB or IsInAB, and the new internalOperator is + // IsntInAB or IsInAB, noop because the search value + // was an ab type, and it still is. + // otherwise, switch to the ab picker and select the PAB + if (this.internalOperator != Ci.nsMsgSearchOp.IsntInAB && + this.internalOperator != Ci.nsMsgSearchOp.IsInAB) { + var abs = children[4].getElementsByAttribute("value", "moz-abmdbdirectory://abook.mab"); + if (abs.item(0)) + children[4].selectedItem = abs[0]; + this.setAttribute("selectedIndex", "4"); + } + } + else { + // if the old internalOperator wasn't + // IsntInAB or IsInAB, and the new internalOperator isn't + // IsntInAB or IsInAB, noop because the search value + // wasn't an ab type, and it still isn't. + // otherwise, switch to the textbox and clear it + if (this.internalOperator == Ci.nsMsgSearchOp.IsntInAB || + this.internalOperator == Ci.nsMsgSearchOp.IsInAB) { + children[0].value = ""; + this.setAttribute("selectedIndex", "0"); + } + } + + this.internalOperator = val; + return val; + ]]> + </setter> + </property> + <!-- parentValue forwards to the attribute --> + <property name="parentValue" onset="return this.searchAttribute=val;" + onget="return this.searchAttribute;"/> + <property name="searchAttribute" onget="return this.internalAttribute;"> + <setter> + <![CDATA[ + // noop if we're not changing it + if (this.internalAttribute == val) return val; + this.internalAttribute = val; + + // if the searchAttribute changing, null out the internalOperator + this.internalOperator = null; + + // we inherit from a deck, so just use it's index attribute + // to hide/show widgets + if (isNaN(val)) // Is this a custom attribute? + { + this.setAttribute("selectedIndex", "9"); + let customHbox = document.getAnonymousNodes(this)[9]; + if (this.internalValue) + customHbox.setAttribute("value", this.internalValue.str); + // the searchAttribute attribute is intended as a selector in + // CSS for custom search terms to bind a custom value + customHbox.setAttribute("searchAttribute", val); + } + else if (val == Ci.nsMsgSearchAttrib.Priority) + this.setAttribute("selectedIndex", "1"); + else if (val == Ci.nsMsgSearchAttrib.MsgStatus) + this.setAttribute("selectedIndex", "2"); + else if (val == Ci.nsMsgSearchAttrib.Date) + this.setAttribute("selectedIndex", "3"); + else if (val == Ci.nsMsgSearchAttrib.Sender) { + // since the internalOperator is null + // this is the same as the initial state + // the initial state for Sender isn't an ab type search + // it's a text search, so show the textbox + this.setAttribute("selectedIndex", "0"); + } + else if (val == Ci.nsMsgSearchAttrib.Keywords) { + this.setAttribute("selectedIndex", "5"); + } + else if (val == Ci.nsMsgSearchAttrib.JunkStatus) { + this.setAttribute("selectedIndex", "6"); + } + else if (val == Ci.nsMsgSearchAttrib.HasAttachmentStatus) { + this.setAttribute("selectedIndex", "7"); + } + else if (val == Ci.nsMsgSearchAttrib.JunkScoreOrigin) { + this.setAttribute("selectedIndex", "8"); + } + else { + // a normal text field + this.setAttribute("selectedIndex", "0"); + } + return val; + ]]> + </setter> + </property> + <property name="value" onget="return this.internalValue;"> + <setter> + <![CDATA[ + // val is a nsIMsgSearchValue object + this.internalValue = val; + var attrib = this.internalAttribute; + var nsMsgSearchAttrib = Ci.nsMsgSearchAttrib; + var children = document.getAnonymousNodes(this); + this.searchAttribute = attrib; + if (isNaN(attrib)) // a custom term + { + let customHbox = document.getAnonymousNodes(this)[9]; + customHbox.setAttribute("value", val.str); + return val; + } + if (attrib == nsMsgSearchAttrib.Priority) { + var matchingPriority = + children[1].getElementsByAttribute("value", val.priority); + if (matchingPriority.item(0)) + children[1].selectedItem = matchingPriority[0]; + } + else if (attrib == nsMsgSearchAttrib.MsgStatus) { + var matchingStatus = + children[2].getElementsByAttribute("value", val.status); + if (matchingStatus.item(0)) + children[2].selectedItem = matchingStatus[0]; + } + else if (attrib == nsMsgSearchAttrib.AgeInDays) + children[0].value = val.age; + else if (attrib == nsMsgSearchAttrib.Date) + children[3].value = convertPRTimeToString(val.date); + else if (attrib == nsMsgSearchAttrib.Sender || + attrib == nsMsgSearchAttrib.To || + attrib == nsMsgSearchAttrib.CC || + attrib == nsMsgSearchAttrib.AllAddresses || + attrib == nsMsgSearchAttrib.ToOrCC) + { + if (this.internalOperator == Ci.nsMsgSearchOp.IsntInAB || + this.internalOperator == Ci.nsMsgSearchOp.IsInAB) { + var abs = children[4].getElementsByAttribute("value", val.str); + if (abs.item(0)) + children[4].selectedItem = abs[0]; + } + else + children[0].value = val.str; + } + else if (attrib == nsMsgSearchAttrib.Keywords) + { + var keywordVal = children[5].getElementsByAttribute("value", val.str); + if (keywordVal.item(0)) + { + children[5].value = val.str; + children[5].selectedItem = keywordVal[0]; + } + } + else if (attrib == nsMsgSearchAttrib.JunkStatus) { + var junkStatus = + children[6].getElementsByAttribute("value", val.junkStatus); + if (junkStatus.item(0)) + children[6].selectedItem = junkStatus[0]; + } + else if (attrib == nsMsgSearchAttrib.HasAttachmentStatus) { + var hasAttachmentStatus = + children[7].getElementsByAttribute("value", val.hasAttachmentStatus); + if (hasAttachmentStatus.item(0)) + children[7].selectedItem = hasAttachmentStatus[0]; + } + else if (attrib == nsMsgSearchAttrib.JunkScoreOrigin) { + var junkScoreOrigin = + children[8].getElementsByAttribute("value", val.str); + if (junkScoreOrigin.item(0)) + children[8].selectedItem = junkScoreOrigin[0]; + } + else if (attrib == nsMsgSearchAttrib.JunkPercent) { + children[0].value = val.junkPercent; + } + else if (attrib == nsMsgSearchAttrib.Size) { + children[0].value = val.size; + } + else + children[0].value = val.str; + return val; + ]]> + </setter> + </property> + <method name="save"> + <body> + <![CDATA[ + var searchValue = this.value; + var searchAttribute = this.searchAttribute; + var nsMsgSearchAttrib = Ci.nsMsgSearchAttrib; + var children = document.getAnonymousNodes(this); + + searchValue.attrib = searchAttribute; + if (searchAttribute == nsMsgSearchAttrib.Priority) { + searchValue.priority = children[1].selectedItem.value; + } + else if (searchAttribute == nsMsgSearchAttrib.MsgStatus) + searchValue.status = children[2].value; + else if (searchAttribute == nsMsgSearchAttrib.AgeInDays) + searchValue.age = children[0].value; + else if (searchAttribute == nsMsgSearchAttrib.Date) + searchValue.date = convertStringToPRTime(children[3].value); + else if (searchAttribute == nsMsgSearchAttrib.Sender || + searchAttribute == nsMsgSearchAttrib.To || + searchAttribute == nsMsgSearchAttrib.CC || + searchAttribute == nsMsgSearchAttrib.AllAddresses || + searchAttribute == nsMsgSearchAttrib.ToOrCC) + { + if (this.internalOperator == Ci.nsMsgSearchOp.IsntInAB || + this.internalOperator == Ci.nsMsgSearchOp.IsInAB) + searchValue.str = children[4].selectedItem.value; + else + searchValue.str = children[0].value; + } + else if (searchAttribute == nsMsgSearchAttrib.Keywords) + { + searchValue.str = children[5].value; + } + else if (searchAttribute == nsMsgSearchAttrib.JunkStatus) + searchValue.junkStatus = children[6].value; + else if (searchAttribute == nsMsgSearchAttrib.JunkPercent) + searchValue.junkPercent = children[0].value; + else if (searchAttribute == nsMsgSearchAttrib.Size) + searchValue.size = children[0].value; + else if (searchAttribute == nsMsgSearchAttrib.HasAttachmentStatus) + searchValue.status = 0x10000000; // 0x10000000 is MSG_FLAG_ATTACHMENT; + else if (searchAttribute == nsMsgSearchAttrib.JunkScoreOrigin) + searchValue.str = children[8].value; + else if (isNaN(searchAttribute)) // a custom term + { + searchValue.attrib = nsMsgSearchAttrib.Custom; + searchValue.str = children[9].getAttribute("value"); + } + else + searchValue.str = children[0].value; + ]]> + </body> + </method> + <method name="saveTo"> + <parameter name="searchValue"/> + <body> + <![CDATA[ + this.internalValue = searchValue; + this.save(); + ]]> + </body> + </method> + <method name="fillInTags"> + <body> + <![CDATA[ + var children = document.getAnonymousNodes(this); + var popupMenu = children[5].firstChild; + var tagArray = MailServices.tags.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); + popupMenu.appendChild(newMenuItem); + if (!i) + children[5].selectedItem = newMenuItem; + } + ]]> + </body> + </method> + <method name="fillStringsForChildren"> + <parameter name="parentNode"/> + <parameter name="bundle"/> + <body> + <![CDATA[ + var children = parentNode.childNodes; + var len=children.length; + for (var i=0; i<len; i++) { + var node = children[i]; + var stringTag = node.getAttribute("stringTag"); + if (stringTag) { + var attr = (node.tagName == "label") ? "value" : "label"; + node.setAttribute(attr, bundle.GetStringFromName(stringTag)); + } + } + ]]> + </body> + </method> + <method name="initialize"> + <parameter name="menulist"/> + <parameter name="bundle"/> + <body> + <![CDATA[ + this.fillStringsForChildren(menulist.firstChild, bundle); + ]]> + </body> + </method> + <constructor> + <![CDATA[ + var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" + ); + ChromeUtils.import("resource://gre/modules/Services.jsm", this); + + // initialize strings + let bundle = Services.strings.createBundle("chrome://messenger/locale/messenger.properties"); + + // intialize the priority picker + this.initialize(document.getAnonymousNodes(this)[1], bundle); + + // initialize the status picker + this.initialize(document.getAnonymousNodes(this)[2], bundle); + + // initialize the date picker + var datePicker = document.getAnonymousNodes(this)[3]; + var searchAttribute = this.searchAttribute; + var nsMsgSearchAttrib = Ci.nsMsgSearchAttrib; + var time; + if (searchAttribute == nsMsgSearchAttrib.Date) + time = datePicker.value; + else + time = new Date(); + // do .value instead of .setAttribute("value", xxx); + // to work around for bug #179412 + // (caused by bug #157210) + // + // the searchvalue widget has two textboxes + // one for text, one as a placeholder for a date / calendar widget + datePicker.value = convertDateToString(time); + + // initialize the address book picker + this.initialize(document.getAnonymousNodes(this)[4], bundle); + + // initialize the junk status picker + this.initialize(document.getAnonymousNodes(this)[6], bundle); + + // initialize the has attachment status picker + this.initialize(document.getAnonymousNodes(this)[7], bundle); + + // initialize the junk score origin picker + this.initialize(document.getAnonymousNodes(this)[8], bundle); + + // initialize the tag list + fillInTags(); + ]]> + </constructor> + </implementation> + <handlers> + <handler event="keypress" keycode="VK_RETURN" modifiers="accel any" + action="onEnterInSearchTerm(event);" preventdefault="true"/> + </handlers> + </binding> + + <binding id="folderSummary-popup" extends="chrome://global/content/bindings/popup.xml#tooltip"> + <content> + <children> + <xul:folderSummary/> + </children> + </content> + <handlers> + <handler event="popupshowing"> + <![CDATA[ + let msgFolder = gFolderTreeView.getFolderAtCoords(event.clientX, + event.clientY); + if (!msgFolder) + return false; + + let tooltipnode = document.getAnonymousNodes(this)[0]; + let asyncResults = {}; + if (tooltipnode.parseFolder(msgFolder, null, asyncResults)) + return true; + + let row = {}, col = {}; + gFolderTreeView._tree.getCellAt(event.clientX, event.clientY, row, + col, {}); + if (col.value.id == "folderNameCol") { + let cropped = gFolderTreeView._tree.isCellCropped(row.value, + col.value); + if (tooltipnode.addLocationInfo(msgFolder, cropped)) + return true; + } + + let counts = gFolderTreeView.getSummarizedCounts(row.value, + col.value.id); + if (counts) { + if (tooltipnode.addSummarizeExplain(counts)) + return true; + } + + return false; + ]]> + </handler> + + <handler event="popuphiding"> + document.getAnonymousNodes(this)[0].clear(); + </handler> + </handlers> + </binding> + + <binding id="folderSummary"> + <content> + <xul:vbox/> + </content> + + <implementation> + <field name="mMaxMsgHdrsInPopup">8</field> + <property name="hasMessages" readonly="true" onget="return document.getAnonymousNodes(this)[0].hasChildNodes();"/> + <method name="parseFolder"> + <parameter name="aFolder"/> + <parameter name="aUrlListener"/> + <parameter name="aOutAsync"/> + <body> + <![CDATA[ + // Skip servers, Trash and Junk folders, and newgroups. + if (!aFolder || aFolder.isServer || !aFolder.hasNewMessages || + aFolder.getFlag(Ci.nsMsgFolderFlags.Junk) || + aFolder.getFlag(Ci.nsMsgFolderFlags.Trash) || + (aFolder.server instanceof Ci.nsINntpIncomingServer)) + return false; + let showPreviewText = this.Services.prefs.getBoolPref("mail.biff.alert.show_preview"); + let folderArray = []; + let msgDatabase; + try { + msgDatabase = aFolder.msgDatabase; + } catch(e) { + // The database for this folder may be missing + // (e.g. outdated/missing .msf), so just skip this folder. + return false; + } + + if (aFolder.flags & Ci.nsMsgFolderFlags.Virtual) + { + let dbFolderInfo = msgDatabase.dBFolderInfo; + var srchFolderUri = dbFolderInfo.getCharProperty("searchFolderUri"); + var srchFolderUriArray = srchFolderUri.split('|'); + var foldersAdded = 0; + var RDF = Cc['@mozilla.org/rdf/rdf-service;1'] + .getService(Ci.nsIRDFService); + for (var i in srchFolderUriArray) + { + var realFolder = RDF.GetResource(srchFolderUriArray[i]) + .QueryInterface(Ci.nsIMsgFolder); + if (!realFolder.isServer) + folderArray[foldersAdded++] = realFolder; + } + } + else { + folderArray[0] = aFolder; + } + + var foundNewMsg = false; + for (var folderIndex = 0; folderIndex < folderArray.length; folderIndex++) + { + aFolder = folderArray[folderIndex]; + // now get the database + try { + msgDatabase = aFolder.msgDatabase; + } catch(e) { + // The database for this folder may be missing + // (e.g. outdated/missing .msf), then just skip this folder. + continue; + } + + aFolder.msgDatabase = null; + let msgKeys = msgDatabase.getNewList(); + + if (!msgKeys.length) + continue; + + if (showPreviewText) + { + // fetchMsgPreviewText forces the previewText property to get generated + // for each of the message keys. + try { + aOutAsync.value = aFolder.fetchMsgPreviewText(msgKeys, aUrlListener); + aFolder.msgDatabase = null; + } + catch (ex) + { + // fetchMsgPreviewText throws an error when we call it on a news folder, we should just not show + // the tooltip if this method returns an error. + aFolder.msgDatabase = null; + continue; + } + } + // if fetching the preview text is going to be an asynch operation and the caller + // is set up to handle that fact, then don't bother filling in any of the fields since + // we'll have to do this all over again when the fetch for the preview text completes. + // We don't expect to get called with a urlListener if we're doing a virtual folder. + if (aOutAsync.value && aUrlListener) + return false; + var unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] + .createInstance(Ci.nsIScriptableUnicodeConverter); + unicodeConverter.charset = "UTF-8"; + foundNewMsg = true; + + var index = 0; + while (document.getAnonymousNodes(this)[0].childNodes.length < this.mMaxMsgHdrsInPopup && index < msgKeys.length) + { + var msgPopup = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "folderSummaryMessage"); + var msgHdr = msgDatabase.GetMsgHdrForKey(msgKeys[index++]); + + var msgSubject = msgHdr.mime2DecodedSubject; + const kMsgFlagHasRe = 0x0010; // MSG_FLAG_HAS_RE + if(msgHdr.flags & kMsgFlagHasRe) + msgSubject = (msgSubject) ? "Re: " + msgSubject : "Re: "; + + msgPopup.setAttribute('subject', msgSubject); + + var previewText = msgHdr.getStringProperty('preview'); + // convert the preview text from utf-8 to unicode + if (previewText) + { + try + { + var text = unicodeConverter.ConvertToUnicode(previewText); + if (text) + msgPopup.setAttribute('previewText', text); + } + catch (ex) { } + } + + var names = {}; + var emails = {}; + var numAddresses = MailServices.headerParser.parseHeadersWithArray(msgHdr.mime2DecodedAuthor, emails, names, {}); + msgPopup.setAttribute('sender', names.value[0] ? names.value[0] : emails.value[0]); + msgPopup.messageUri = aFolder.getUriForMsg(msgHdr); + msgPopup.folderUri = aFolder.URI; + msgPopup.msgKey = msgHdr.messageKey; + document.getAnonymousNodes(this)[0].appendChild(msgPopup); + } + if (document.getAnonymousNodes(this)[0].childNodes.length >= this.mMaxMsgHdrsInPopup) + return true; + } + return foundNewMsg; + ]]> + </body> + </method> + + <method name="addLocationInfo"> + <parameter name="aFolder"/> + <parameter name="aCropped"/> + <body> + <![CDATA[ + let popupValue = null; + // Display also server name for items that are on level 0 and are + // not server names by themselves and do not have server name + // already appended in their label. + let folderIndex = gFolderTreeView.getIndexOfFolder(aFolder); + if (!aFolder.isServer && + gFolderTreeView.getLevel(folderIndex) == 0 && + !gFolderTreeView.getServerNameAdded(folderIndex)) { + let midPath = ""; + let midFolder = aFolder.parent; + while (aFolder.server.rootFolder != midFolder) { + midPath = midFolder.name + " - " + midPath; + midFolder = midFolder.parent; + } + popupValue = aFolder.server.prettyName + " - " + midPath + + aFolder.name; + } + // If folder name is cropped or is a newsgroup and abbreviated per + // pref, use the full name as a tooltip. + else if (aCropped || + ((aFolder.server instanceof Ci.nsINntpIncomingServer) && + !(aFolder.flags & Ci.nsMsgFolderFlags.Virtual) && + aFolder.server.abbreviate) && !aFolder.isServer) { + popupValue = aFolder.name; + } + + if (popupValue) { + let loc = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "folderSummaryLocation"); + loc.setAttribute("location", popupValue); + document.getAnonymousNodes(this)[0].appendChild(loc); + return true; + } + + return false; + ]]> + </body> + </method> + + <method name="addSummarizeExplain"> + <parameter name="aCounts"/> + <body> + <![CDATA[ + if (!aCounts || !aCounts[1]) + return false; + let expl = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "folderSummarySubfoldersSummary"); + let sumString = document.getElementById("bundle_messenger") + .getFormattedString("subfoldersExplanation", [aCounts[0], aCounts[1]], 2); + expl.setAttribute("subfolders", sumString); + document.getAnonymousNodes(this)[0].appendChild(expl); + return true; + ]]> + </body> + </method> + + <method name="clear"> + <body> + <![CDATA[ + var containingBox = document.getAnonymousNodes(this)[0]; + while (containingBox.hasChildNodes()) + containingBox.lastChild.remove(); + ]]> + </body> + </method> + <constructor> + <![CDATA[ + var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" + ); + ChromeUtils.import("resource://gre/modules/Services.jsm", this); + ]]> + </constructor> + </implementation> + </binding> + + <binding id="folderSummary-location"> + <content> + <xul:hbox> + <xul:label anonid="location" xbl:inherits="value=location"/> + </xul:hbox> + </content> + </binding> + + <binding id="folderSummary-subfoldersSummary"> + <content> + <xul:hbox> + <xul:label anonid="subfolders" xbl:inherits="value=subfolders"/> + </xul:hbox> + </content> + </binding> + + <binding id="folderSummary-message"> + <content> + <xul:vbox class="folderSummaryMessage"> + <xul:hbox class="folderSummary-message-row"> + <xul:label anonid="subject" flex="1" class="folderSummary-subject" xbl:inherits="value=subject" crop="right"/> + <xul:label anonid="sender" class="folderSummary-sender" xbl:inherits="value=sender" crop="right"/> + <xul:spring anonid="spring" flex="100%"/> + </xul:hbox> + <xul:description anonid="preview" class="folderSummary-message-row folderSummary-previewText" xbl:inherits="value=previewText" crop="right"></xul:description> + </xul:vbox> + </content> + <implementation> + <constructor> + <![CDATA[ + var { MailServices } = ChromeUtils.import( + "resource:///modules/MailServices.jsm" + ); + ChromeUtils.import("resource://gre/modules/Services.jsm", this); + + if (!this.Services.prefs.getBoolPref("mail.biff.alert.show_preview")) + document.getAnonymousElementByAttribute(this, "anonid", "preview").hidden = true; + var hideSubject = !this.Services.prefs.getBoolPref("mail.biff.alert.show_subject"); + var hideSender = !this.Services.prefs.getBoolPref("mail.biff.alert.show_sender"); + if (hideSubject) + document.getAnonymousElementByAttribute(this, "anonid", "subject").hidden = true; + if (hideSender) + document.getAnonymousElementByAttribute(this, "anonid", "sender").hidden = true; + if (hideSubject && hideSender) + document.getAnonymousElementByAttribute(this, "anonid", "spring").hidden = true; + ]]> + </constructor> + </implementation> + <handlers> + <handler event="click" button="0"> + <![CDATA[ + var topmostMsgWindow; + try { + topmostMsgWindow = MailServices.mailSession.topmostMsgWindow; + } catch (ex) {} + + if (topmostMsgWindow) + { + // Bring window to the front + topmostMsgWindow.domWindow.focus(); + + try { + // SelectFolder throws an exception if the folder is not in the current folder view + MailServices.mailSession.topmostMsgWindow.windowCommands.selectFolder(this.folderUri); + MailServices.mailSession.topmostMsgWindow.windowCommands.selectMessage(this.messageUri); + } catch (ex) {} + } + else + { + // open a new window + var mailWindowService = Cc["@mozilla.org/messenger/windowservice;1"]. + getService(Ci.nsIMessengerWindowService); + mailWindowService.openMessengerWindowWithUri("mail:3pane", this.folderUri, this.msgKey); + } + + if (gAlertListener) + gAlertListener.observe(null, "alertclicksimplecallback", ""); + ]]> + </handler> + </handlers> + </binding> +</bindings> |