Dactyloidae/mailnews/db/gloda/content/glodacomplete.xml

644 lines
24 KiB
XML

<?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="autocompleteBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="glodacomplete-rich-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-rich-result-popup">
<implementation implements="nsIAutoCompletePopup">
<method name="_appendCurrentResult">
<body>
<![CDATA[
var controller = this.mInput.controller;
// Process maxRows per chunk to improve performance and user experience
for (let i = 0; i < this.maxRows; i++) {
if (this._currentIndex >= this._matchCount)
return;
var existingItemsCount = this.richlistbox.childNodes.length;
var item;
// trim the leading/trailing whitespace
var trimmedSearchString = controller.searchString.trim();
// Unescape the URI spec for showing as an entry in the popup
let url = Components.classes["@mozilla.org/intl/texttosuburi;1"].
getService(Components.interfaces.nsITextToSubURI).
unEscapeURIForUI("UTF-8", controller.getValueAt(this._currentIndex));
// Unlike our superclass, we create nodes every time because we have
// heterogeneous results and we cannot rely on the XBL bindings to
// to change fast enough.
item = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "richlistitem");
var glodaCompleter = Components.
classes["@mozilla.org/autocomplete/search;1?name=gloda"].
getService(). //Components.interfaces.nsIAutoCompleteSearch)
wrappedJSObject;
var result = glodaCompleter.curResult;
// set these attributes before we set the class
// so that we can use them from the contructor
var row = result.getObjectAt(this._currentIndex);
var obj = row.item;
item.setAttribute("text", trimmedSearchString);
item.setAttribute("type", result.getStyleAt(this._currentIndex));
item.row = row;
// set the class at the end so we can use the attributes
// in the xbl constructor
item.className = "autocomplete-richlistitem";
this.richlistbox.appendChild(item);
this._currentIndex++;
}
// yield after each batch of items so that typing the url bar is responsive
setTimeout(() => this._appendCurrentResult(), 0);
]]>
</body>
</method>
<method name="_invalidate">
<body>
<![CDATA[
setTimeout(() => this.adjustHeight(), 0);
// remove all child nodes because we never want to reuse them.
while (this.richlistbox.hasChildNodes())
this.richlistbox.lastChild.remove();
this._currentIndex = 0;
this._appendCurrentResult();
]]>
</body>
</method>
</implementation>
</binding>
<!-- This is autocomplete.xml's autocomplete-richlistitem duplicated and
modified to include its useful helper functions, but eliminating anything
that assumes specific content sub-items. Namely, url/title/etc. -->
<binding id="glodacomplete-base-richlistitem" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
<implementation implements="nsIDOMXULSelectControlItemElement">
<field name="_boundaryCutoff">null</field>
<property name="boundaryCutoff" readonly="true">
<getter>
<![CDATA[
if (!this._boundaryCutoff) {
this._boundaryCutoff =
Services.prefs
.getIntPref("toolkit.autocomplete.richBoundaryCutoff");
}
return this._boundaryCutoff;
]]>
</getter>
</property>
<method name="_getBoundaryIndices">
<parameter name="aText"/>
<parameter name="aSearchTokens"/>
<body>
<![CDATA[
// Short circuit for empty search ([""] == "")
if (aSearchTokens == "")
return [0, aText.length];
// Find which regions of text match the search terms
let regions = [];
for (let search of aSearchTokens) {
let matchIndex;
let startIndex = 0;
let searchLen = search.length;
// Find all matches of the search terms, but stop early for perf
let lowerText = aText.toLowerCase().substr(0, this.boundaryCutoff);
while ((matchIndex = lowerText.indexOf(search, startIndex)) >= 0) {
// Start the next search from where this one finished
startIndex = matchIndex + searchLen;
regions.push([matchIndex, startIndex]);
}
}
// Sort the regions by start position then end position
regions = regions.sort(function(a, b) {
let start = a[0] - b[0];
return (start == 0) ? a[1] - b[1] : start;
});
// Generate the boundary indices from each region
let start = 0;
let end = 0;
let boundaries = [];
let len = regions.length;
for (let i = 0; i < len; i++) {
// We have a new boundary if the start of the next is past the end
let region = regions[i];
if (region[0] > end) {
// First index is the beginning of match
boundaries.push(start);
// Second index is the beginning of non-match
boundaries.push(end);
// Track the new region now that we've stored the previous one
start = region[0];
}
// Push back the end index for the current or new region
end = Math.max(end, region[1]);
}
// Add the last region
boundaries.push(start);
boundaries.push(end);
// Put on the end boundary if necessary
if (end < aText.length)
boundaries.push(aText.length);
// Skip the first item because it's always 0
return boundaries.slice(1);
]]>
</body>
</method>
<method name="_getSearchTokens">
<parameter name="aSearch"/>
<body>
<![CDATA[
let search = aSearch.toLowerCase();
return search.split(/\s+/);
]]>
</body>
</method>
<method name="_needsAlternateEmphasis">
<parameter name="aText"/>
<body>
<![CDATA[
for (let i = aText.length; --i >= 0; ) {
let charCode = aText.charCodeAt(i);
// Arabic, Syriac, Indic languages are likely to have ligatures
// that are broken when using the main emphasis styling
if (0x0600 <= charCode && charCode <= 0x109F)
return true;
}
return false;
]]>
</body>
</method>
<method name="_setUpDescription">
<parameter name="aDescriptionElement"/>
<parameter name="aText"/>
<body>
<![CDATA[
// Get rid of all previous text
while (aDescriptionElement.hasChildNodes())
aDescriptionElement.lastChild.remove();
// Get the indices that separate match and non-match text
let search = this.getAttribute("text");
let tokens = this._getSearchTokens(search);
let indices = this._getBoundaryIndices(aText, tokens);
// If we're searching for something that needs alternate emphasis,
// we'll need to check the text that we match
let checkAlt = this._needsAlternateEmphasis(search);
let next;
let start = 0;
let len = indices.length;
// Even indexed boundaries are matches, so skip the 0th if it's empty
for (let i = indices[0] == 0 ? 1 : 0; i < len; i++) {
next = indices[i];
let text = aText.substr(start, next - start);
start = next;
if (i % 2 == 0) {
// Emphasize the text for even indices
let span = aDescriptionElement.appendChild(
document.createElementNS("http://www.w3.org/1999/xhtml", "span"));
span.className = checkAlt && this._needsAlternateEmphasis(text) ?
"ac-emphasize-alt" : "ac-emphasize-text";
span.textContent = text;
} else {
// Otherwise, it's plain text
aDescriptionElement.appendChild(document.createTextNode(text));
}
}
]]>
</body>
</method>
<method name="_setUpOverflow">
<parameter name="aParentBox"/>
<parameter name="aEllipsis"/>
<body>
<![CDATA[
// Hide the ellipsis incase there's just enough to not underflow
aEllipsis.hidden = true;
// Start with the parent's width and subtract off its children
let tooltip = [];
let children = aParentBox.childNodes;
let widthDiff = aParentBox.boxObject.width;
for (let i = 0; i < children.length; i++) {
// Only consider a child if it actually takes up space
let childWidth = children[i].boxObject.width;
if (childWidth > 0) {
// Subtract a little less to account for subpixel rounding
widthDiff -= childWidth - .5;
// Add to the tooltip if it's not hidden and has text
let childText = children[i].textContent;
if (childText)
tooltip.push(childText);
}
}
// If the children take up more space than the parent.. overflow!
if (widthDiff < 0) {
// Re-show the ellipsis now that we know it's needed
aEllipsis.hidden = false;
// Separate text components with a ndash --
aParentBox.tooltipText = tooltip.join(" \u2013 ");
}
]]>
</body>
</method>
<method name="_doUnderflow">
<parameter name="aName"/>
<body>
<![CDATA[
// Hide the ellipsis right when we know we're underflowing instead of
// waiting for the timeout to trigger the _setUpOverflow calculations
this[aName + "Box"].tooltipText = "";
this[aName + "OverflowEllipsis"].hidden = true;
]]>
</body>
</method>
</implementation>
</binding>
<binding id="gloda-single-tag-item" extends="chrome://gloda/content/glodacomplete.xml#glodacomplete-base-richlistitem">
<content orient="vertical">
<xul:description anonid="explanation" class="explanation gloda-single"/>
</content>
<implementation implements="nsIDOMXULSelectControlItemElement">
<constructor>
<![CDATA[
this._explanation = document.getAnonymousElementByAttribute(this, "anonid", "explanation");
this._adjustAcItem();
]]>
</constructor>
<property name="label" readonly="true">
<getter>
<![CDATA[
return "tag " + this.row.item.tag;
]]>
</getter>
</property>
<method name="_adjustAcItem">
<body>
<![CDATA[
let label = gGlodaCompleteStrings.get("glodaComplete.messagesTagged.label");
this._explanation.value = label.replace("#1", this.row.item.tag);
]]>
</body>
</method>
</implementation>
</binding>
<binding id="gloda-fulltext-single-item" extends="chrome://gloda/content/glodacomplete.xml#glodacomplete-base-richlistitem">
<content orient="vertical">
<xul:description anonid="explanation" class="explanation gloda-fulltext-single"/>
<xul:description anonid="parameters"/>
</content>
<implementation implements="nsIDOMXULSelectControlItemElement">
<constructor>
<![CDATA[
this._explanation = document.getAnonymousElementByAttribute(this, "anonid", "explanation");
this._adjustAcItem();
]]>
</constructor>
<property name="label" readonly="true">
<getter>
<![CDATA[
return "full text search: " + this.row.item;
]]>
</getter>
</property>
<method name="_adjustAcItem">
<body>
<![CDATA[
let label = gGlodaCompleteStrings.get("glodaComplete.messagesMentioning.label");
this._explanation.value = label.replace("#1", this.row.item);
]]>
</body>
</method>
</implementation>
</binding>
<binding id="gloda-fulltext-all-item" extends="chrome://gloda/content/glodacomplete.xml#glodacomplete-base-richlistitem">
<content orient="vertical">
<xul:description anonid="explanation" class="explanation"/>
</content>
<implementation implements="nsIDOMXULSelectControlItemElement">
<constructor>
<![CDATA[
this._explanation = document.getAnonymousElementByAttribute(this, "anonid", "explanation");
this._adjustAcItem();
]]>
</constructor>
<property name="label" readonly="true">
<getter>
<![CDATA[
return "full text search: " + this.row.item; // what is this for? l10n?
]]>
</getter>
</property>
<method name="_adjustAcItem">
<body>
<![CDATA[
let label = gGlodaCompleteStrings.get("glodaComplete.messagesMentioningMany.label");
this._explanation.value = label.replace("#1", this.row.words.join(", "));
]]>
</body>
</method>
</implementation>
</binding>
<binding id="gloda-single-identity-item" extends="chrome://gloda/content/glodacomplete.xml#glodacomplete-base-richlistitem">
<content>
<xul:hbox class="gloda-single-identity">
<xul:image anonid="picture" class="picture"/>
<xul:vbox>
<xul:hbox>
<xul:hbox anonid="name-box" class="ac-title" flex="1"
onunderflow="_doUnderflow('_name');">
<xul:description anonid="name" class="ac-normal-text ac-comment"
xbl:inherits="selected"/>
</xul:hbox>
<xul:label anonid="name-overflow-ellipsis" xbl:inherits="selected"
class="ac-ellipsis-after ac-comment" hidden="true"/>
</xul:hbox>
<xul:hbox>
<xul:hbox anonid="identity-box" class="ac-url" flex="1"
onunderflow="_doUnderflow('_identity');">
<xul:description anonid="identity" class="ac-normal-text ac-url-text"
xbl:inherits="selected"/>
</xul:hbox>
<xul:label anonid="identity-overflow-ellipsis" xbl:inherits="selected"
class="ac-ellipsis-after ac-url-text" hidden="true"/>
<xul:image anonid="type-image" class="ac-type-icon"/>
</xul:hbox>
</xul:vbox>
</xul:hbox>
</content>
<implementation implements="nsIDOMXULSelectControlItemElement">
<constructor>
<![CDATA[
let ellipsis = "\u2026";
try {
ellipsis = Services.prefs.getComplexValue("intl.ellipsis",
Components.interfaces.nsIPrefLocalizedString).data;
} catch (ex) {
// Do nothing.. we already have a default
}
this._identityOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "identity-overflow-ellipsis");
this._nameOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "name-overflow-ellipsis");
this._identityOverflowEllipsis.value = ellipsis;
this._nameOverflowEllipsis.value = ellipsis;
this._typeImage = document.getAnonymousElementByAttribute(this, "anonid", "type-image");
this._identityBox = document.getAnonymousElementByAttribute(this, "anonid", "identity-box");
this._identity = document.getAnonymousElementByAttribute(this, "anonid", "identity");
this._nameBox = document.getAnonymousElementByAttribute(this, "anonid", "name-box");
this._name = document.getAnonymousElementByAttribute(this, "anonid", "name");
this._picture = document.getAnonymousElementByAttribute(this, "anonid", "picture");
this._adjustAcItem();
]]>
</constructor>
<property name="label" readonly="true">
<getter>
<![CDATA[
var identity = this.row.item;
return identity.accessibleLabel;
]]>
</getter>
</property>
<method name="_adjustAcItem">
<body>
<![CDATA[
var identity = this.row.item;
if (identity == null)
return;
// I guess we should get the picture size from CSS or something?
this._picture.src = identity.pictureURL(32);
// Emphasize the matching search terms for the description
this._setUpDescription(this._name, identity.contact.name);
this._setUpDescription(this._identity, identity.value);
// Set up overflow on a timeout because the contents of the box
// might not have a width yet even though we just changed them
setTimeout(this._setUpOverflow, 0, this._nameBox, this._nameOverflowEllipsis);
setTimeout(this._setUpOverflow, 0, this._identityBox, this._identityOverflowEllipsis);
]]>
</body>
</method>
</implementation>
</binding>
<binding id="gloda-contact-chunk" extends="chrome://gloda/content/glodacomplete.xml#glodacomplete-base-richlistitem">
<content orient="horizontal">
<xul:image anonid="picture"/>
<xul:vbox>
<xul:hbox>
<xul:hbox anonid="name-box" class="ac-title" flex="1"
onunderflow="_doUnderflow('_name');">
<xul:description anonid="name" class="ac-normal-text ac-comment"
xbl:inherits="selected"/>
</xul:hbox>
<xul:label anonid="name-overflow-ellipsis" xbl:inherits="selected"
class="ac-ellipsis-after ac-comment" hidden="true"/>
</xul:hbox>
<xul:hbox>
<xul:hbox anonid="identity-box" class="ac-url" flex="1"
onunderflow="_doUnderflow('_identity');">
<xul:description anonid="identity" class="ac-normal-text ac-url-text"
xbl:inherits="selected"/>
</xul:hbox>
<xul:label anonid="identity-overflow-ellipsis" xbl:inherits="selected"
class="ac-ellipsis-after ac-url-text" hidden="true"/>
<xul:image anonid="type-image" class="ac-type-icon"/>
</xul:hbox>
</xul:vbox>
</content>
<implementation>
<constructor>
<![CDATA[
let ellipsis = "\u2026";
try {
ellipsis = Services.prefs.getComplexValue("intl.ellipsis",
Components.interfaces.nsIPrefLocalizedString).data;
} catch (ex) {
// Do nothing.. we already have a default
}
this._identityOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "identity-overflow-ellipsis");
this._nameOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "name-overflow-ellipsis");
this._identityOverflowEllipsis.value = ellipsis;
this._nameOverflowEllipsis.value = ellipsis;
this._typeImage = document.getAnonymousElementByAttribute(this, "anonid", "type-image");
this._identityBox = document.getAnonymousElementByAttribute(this, "anonid", "identity-box");
this._identity = document.getAnonymousElementByAttribute(this, "anonid", "identity");
this._nameBox = document.getAnonymousElementByAttribute(this, "anonid", "name-box");
this._name = document.getAnonymousElementByAttribute(this, "anonid", "name");
this._picture = document.getAnonymousElementByAttribute(this, "anonid", "picture");
this._adjustAcItem();
]]>
</constructor>
<property name="label" readonly="true">
<getter>
<![CDATA[
var identity = this.obj;
return identity.accessibleLabel;
]]>
</getter>
</property>
<method name="_adjustAcItem">
<body>
<![CDATA[
var contact = this.obj;
if (contact == null)
return;
var identity = contact.identities[0];
// I guess we should get the picture size from CSS or something?
this._picture.src = identity.pictureURL(32);
// Emphasize the matching search terms for the description
this._setUpDescription(this._name, contact.name);
this._setUpDescription(this._identity, identity.value);
// Set up overflow on a timeout because the contents of the box
// might not have a width yet even though we just changed them
setTimeout(this._setUpOverflow, 0, this._nameBox, this._nameOverflowEllipsis);
setTimeout(this._setUpOverflow, 0, this._identityBox, this._identityOverflowEllipsis);
]]>
</body>
</method>
</implementation>
</binding>
<binding id="gloda-multi-item" extends="chrome://gloda/content/glodacomplete.xml#glodacomplete-base-richlistitem">
<content orient="vertical">
<xul:description anonid="explanation"/>
<xul:hbox anonid="identity-holder" flex="1">
</xul:hbox>
</content>
<implementation implements="nsIDOMXULSelectControlItemElement">
<constructor>
<![CDATA[
this._explanation = document.getAnonymousElementByAttribute(this, "anonid", "explanation");
this._identityHolder = document.getAnonymousElementByAttribute(this, "anonid", "identity-holder");
this._adjustAcItem();
]]>
</constructor>
<property name="label" readonly="true">
<getter>
<![CDATA[
return this._explanation.value;
]]>
</getter>
</property>
<method name="renderItem">
<parameter name="aObj"/>
<body>
var node = document.createElementNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"richlistitem");
node.obj = aObj;
node.setAttribute("type",
"gloda-" + this.row.nounDef.name + "-chunk");
this._identityHolder.appendChild(node);
</body>
</method>
<method name="_adjustAcItem">
<body>
<![CDATA[
// clear out any lingering children
while (this._identityHolder.hasChildNodes())
this._identityHolder.lastChild.remove();
var row = this.row;
if (row == null)
return;
this._explanation.value = row.nounDef.name + "s " +
row.criteriaType + "ed " + row.criteria;
// render anyone already in there
for (let item of row.collection.items) {
this.renderItem(item);
}
// listen up, yo.
row.renderer = this;
]]>
</body>
</method>
</implementation>
</binding>
</bindings>