mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-08 16:58:38 +09:00
Add Pale Moon
This commit is contained in:
parent
dcd9973243
commit
fe8028fa2e
1173 changed files with 143053 additions and 934 deletions
910
application/palemoon/base/content/sanitizeDialog.js
Normal file
910
application/palemoon/base/content/sanitizeDialog.js
Normal file
|
|
@ -0,0 +1,910 @@
|
|||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
var gSanitizePromptDialog = {
|
||||
|
||||
get bundleBrowser()
|
||||
{
|
||||
if (!this._bundleBrowser)
|
||||
this._bundleBrowser = document.getElementById("bundleBrowser");
|
||||
return this._bundleBrowser;
|
||||
},
|
||||
|
||||
get selectedTimespan()
|
||||
{
|
||||
var durList = document.getElementById("sanitizeDurationChoice");
|
||||
return parseInt(durList.value);
|
||||
},
|
||||
|
||||
get sanitizePreferences()
|
||||
{
|
||||
if (!this._sanitizePreferences) {
|
||||
this._sanitizePreferences =
|
||||
document.getElementById("sanitizePreferences");
|
||||
}
|
||||
return this._sanitizePreferences;
|
||||
},
|
||||
|
||||
get warningBox()
|
||||
{
|
||||
return document.getElementById("sanitizeEverythingWarningBox");
|
||||
},
|
||||
|
||||
init: function ()
|
||||
{
|
||||
// This is used by selectByTimespan() to determine if the window has loaded.
|
||||
this._inited = true;
|
||||
|
||||
var s = new Sanitizer();
|
||||
s.prefDomain = "privacy.cpd.";
|
||||
|
||||
let sanitizeItemList = document.querySelectorAll("#itemList > [preference]");
|
||||
for (let i = 0; i < sanitizeItemList.length; i++) {
|
||||
let prefItem = sanitizeItemList[i];
|
||||
let name = s.getNameFromPreference(prefItem.getAttribute("preference"));
|
||||
s.canClearItem(name, function canClearCallback(aItem, aCanClear, aPrefItem) {
|
||||
if (!aCanClear) {
|
||||
aPrefItem.preference = null;
|
||||
aPrefItem.checked = false;
|
||||
aPrefItem.disabled = true;
|
||||
}
|
||||
}, prefItem);
|
||||
}
|
||||
|
||||
document.documentElement.getButton("accept").label =
|
||||
this.bundleBrowser.getString("sanitizeButtonOK");
|
||||
|
||||
if (this.selectedTimespan === Sanitizer.TIMESPAN_EVERYTHING) {
|
||||
this.prepareWarning();
|
||||
this.warningBox.hidden = false;
|
||||
document.title =
|
||||
this.bundleBrowser.getString("sanitizeDialog2.everything.title");
|
||||
}
|
||||
else
|
||||
this.warningBox.hidden = true;
|
||||
},
|
||||
|
||||
selectByTimespan: function ()
|
||||
{
|
||||
// This method is the onselect handler for the duration dropdown. As a
|
||||
// result it's called a couple of times before onload calls init().
|
||||
if (!this._inited)
|
||||
return;
|
||||
|
||||
var warningBox = this.warningBox;
|
||||
|
||||
// If clearing everything
|
||||
if (this.selectedTimespan === Sanitizer.TIMESPAN_EVERYTHING) {
|
||||
this.prepareWarning();
|
||||
if (warningBox.hidden) {
|
||||
warningBox.hidden = false;
|
||||
window.resizeBy(0, warningBox.boxObject.height);
|
||||
}
|
||||
window.document.title =
|
||||
this.bundleBrowser.getString("sanitizeDialog2.everything.title");
|
||||
return;
|
||||
}
|
||||
|
||||
// If clearing a specific time range
|
||||
if (!warningBox.hidden) {
|
||||
window.resizeBy(0, -warningBox.boxObject.height);
|
||||
warningBox.hidden = true;
|
||||
}
|
||||
window.document.title =
|
||||
window.document.documentElement.getAttribute("noneverythingtitle");
|
||||
},
|
||||
|
||||
sanitize: function ()
|
||||
{
|
||||
// Update pref values before handing off to the sanitizer (bug 453440)
|
||||
this.updatePrefs();
|
||||
var s = new Sanitizer();
|
||||
s.prefDomain = "privacy.cpd.";
|
||||
|
||||
s.range = Sanitizer.getClearRange(this.selectedTimespan);
|
||||
s.ignoreTimespan = !s.range;
|
||||
|
||||
// As the sanitize is async, we disable the buttons, update the label on
|
||||
// the 'accept' button to indicate things are happening and return false -
|
||||
// once the async operation completes (either with or without errors)
|
||||
// we close the window.
|
||||
let docElt = document.documentElement;
|
||||
let acceptButton = docElt.getButton("accept");
|
||||
acceptButton.disabled = true;
|
||||
acceptButton.setAttribute("label",
|
||||
this.bundleBrowser.getString("sanitizeButtonClearing"));
|
||||
docElt.getButton("cancel").disabled = true;
|
||||
try {
|
||||
s.sanitize().then(window.close, window.close);
|
||||
} catch (er) {
|
||||
Components.utils.reportError("Exception during sanitize: " + er);
|
||||
return true; // We *do* want to close immediately on error.
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* If the panel that displays a warning when the duration is "Everything" is
|
||||
* not set up, sets it up. Otherwise does nothing.
|
||||
*
|
||||
* @param aDontShowItemList Whether only the warning message should be updated.
|
||||
* True means the item list visibility status should not
|
||||
* be changed.
|
||||
*/
|
||||
prepareWarning: function (aDontShowItemList) {
|
||||
// If the date and time-aware locale warning string is ever used again,
|
||||
// initialize it here. Currently we use the no-visits warning string,
|
||||
// which does not include date and time. See bug 480169 comment 48.
|
||||
|
||||
var warningStringID;
|
||||
if (this.hasNonSelectedItems()) {
|
||||
warningStringID = "sanitizeSelectedWarning";
|
||||
if (!aDontShowItemList)
|
||||
this.showItemList();
|
||||
}
|
||||
else {
|
||||
warningStringID = "sanitizeEverythingWarning2";
|
||||
}
|
||||
|
||||
var warningDesc = document.getElementById("sanitizeEverythingWarning");
|
||||
warningDesc.textContent =
|
||||
this.bundleBrowser.getString(warningStringID);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the value of a preference element is synced from the actual
|
||||
* pref. Enables or disables the OK button appropriately.
|
||||
*/
|
||||
onReadGeneric: function ()
|
||||
{
|
||||
var found = false;
|
||||
|
||||
// Find any other pref that's checked and enabled.
|
||||
var i = 0;
|
||||
while (!found && i < this.sanitizePreferences.childNodes.length) {
|
||||
var preference = this.sanitizePreferences.childNodes[i];
|
||||
|
||||
found = !!preference.value &&
|
||||
!preference.disabled;
|
||||
i++;
|
||||
}
|
||||
|
||||
try {
|
||||
document.documentElement.getButton("accept").disabled = !found;
|
||||
}
|
||||
catch (e) { }
|
||||
|
||||
// Update the warning prompt if needed
|
||||
this.prepareWarning(true);
|
||||
|
||||
return undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sanitizer.prototype.sanitize() requires the prefs to be up-to-date.
|
||||
* Because the type of this prefwindow is "child" -- and that's needed because
|
||||
* without it the dialog has no OK and Cancel buttons -- the prefs are not
|
||||
* updated on dialogaccept on platforms that don't support instant-apply
|
||||
* (i.e., Windows). We must therefore manually set the prefs from their
|
||||
* corresponding preference elements.
|
||||
*/
|
||||
updatePrefs : function ()
|
||||
{
|
||||
var tsPref = document.getElementById("privacy.sanitize.timeSpan");
|
||||
Sanitizer.prefs.setIntPref("timeSpan", this.selectedTimespan);
|
||||
|
||||
// Keep the pref for the download history in sync with the history pref.
|
||||
document.getElementById("privacy.cpd.downloads").value =
|
||||
document.getElementById("privacy.cpd.history").value;
|
||||
|
||||
// Now manually set the prefs from their corresponding preference
|
||||
// elements.
|
||||
var prefs = this.sanitizePreferences.rootBranch;
|
||||
for (let i = 0; i < this.sanitizePreferences.childNodes.length; ++i) {
|
||||
var p = this.sanitizePreferences.childNodes[i];
|
||||
prefs.setBoolPref(p.name, p.value);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if all of the history items have been selected like the default status.
|
||||
*/
|
||||
hasNonSelectedItems: function () {
|
||||
let checkboxes = document.querySelectorAll("#itemList > [preference]");
|
||||
for (let i = 0; i < checkboxes.length; ++i) {
|
||||
let pref = document.getElementById(checkboxes[i].getAttribute("preference"));
|
||||
if (!pref.value)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the history items list.
|
||||
*/
|
||||
showItemList: function () {
|
||||
var itemList = document.getElementById("itemList");
|
||||
var expanderButton = document.getElementById("detailsExpander");
|
||||
|
||||
if (itemList.collapsed) {
|
||||
expanderButton.className = "expander-up";
|
||||
itemList.setAttribute("collapsed", "false");
|
||||
if (document.documentElement.boxObject.height)
|
||||
window.resizeBy(0, itemList.boxObject.height);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the history items list.
|
||||
*/
|
||||
hideItemList: function () {
|
||||
var itemList = document.getElementById("itemList");
|
||||
var expanderButton = document.getElementById("detailsExpander");
|
||||
|
||||
if (!itemList.collapsed) {
|
||||
expanderButton.className = "expander-down";
|
||||
window.resizeBy(0, -itemList.boxObject.height);
|
||||
itemList.setAttribute("collapsed", "true");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called by the item list expander button to toggle the list's visibility.
|
||||
*/
|
||||
toggleItemList: function ()
|
||||
{
|
||||
var itemList = document.getElementById("itemList");
|
||||
|
||||
if (itemList.collapsed)
|
||||
this.showItemList();
|
||||
else
|
||||
this.hideItemList();
|
||||
}
|
||||
|
||||
#ifdef CRH_DIALOG_TREE_VIEW
|
||||
// A duration value; used in the same context as Sanitizer.TIMESPAN_HOUR,
|
||||
// Sanitizer.TIMESPAN_2HOURS, et al. This should match the value attribute
|
||||
// of the sanitizeDurationCustom menuitem.
|
||||
get TIMESPAN_CUSTOM()
|
||||
{
|
||||
return -1;
|
||||
},
|
||||
|
||||
get placesTree()
|
||||
{
|
||||
if (!this._placesTree)
|
||||
this._placesTree = document.getElementById("placesTree");
|
||||
return this._placesTree;
|
||||
},
|
||||
|
||||
init: function ()
|
||||
{
|
||||
// This is used by selectByTimespan() to determine if the window has loaded.
|
||||
this._inited = true;
|
||||
|
||||
var s = new Sanitizer();
|
||||
s.prefDomain = "privacy.cpd.";
|
||||
|
||||
let sanitizeItemList = document.querySelectorAll("#itemList > [preference]");
|
||||
for (let i = 0; i < sanitizeItemList.length; i++) {
|
||||
let prefItem = sanitizeItemList[i];
|
||||
let name = s.getNameFromPreference(prefItem.getAttribute("preference"));
|
||||
s.canClearItem(name, function canClearCallback(aCanClear) {
|
||||
if (!aCanClear) {
|
||||
prefItem.preference = null;
|
||||
prefItem.checked = false;
|
||||
prefItem.disabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.documentElement.getButton("accept").label =
|
||||
this.bundleBrowser.getString("sanitizeButtonOK");
|
||||
|
||||
this.selectByTimespan();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets up the hashes this.durationValsToRows, which maps duration values
|
||||
* to rows in the tree, this.durationRowsToVals, which maps rows in
|
||||
* the tree to duration values, and this.durationStartTimes, which maps
|
||||
* duration values to their corresponding start times.
|
||||
*/
|
||||
initDurationDropdown: function ()
|
||||
{
|
||||
// First, calculate the start times for each duration.
|
||||
this.durationStartTimes = {};
|
||||
var durVals = [];
|
||||
var durPopup = document.getElementById("sanitizeDurationPopup");
|
||||
var durMenuitems = durPopup.childNodes;
|
||||
for (let i = 0; i < durMenuitems.length; i++) {
|
||||
let durMenuitem = durMenuitems[i];
|
||||
let durVal = parseInt(durMenuitem.value);
|
||||
if (durMenuitem.localName === "menuitem" &&
|
||||
durVal !== Sanitizer.TIMESPAN_EVERYTHING &&
|
||||
durVal !== this.TIMESPAN_CUSTOM) {
|
||||
durVals.push(durVal);
|
||||
let durTimes = Sanitizer.getClearRange(durVal);
|
||||
this.durationStartTimes[durVal] = durTimes[0];
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the duration values ascending. Because one tree index can map to
|
||||
// more than one duration, this ensures that this.durationRowsToVals maps
|
||||
// a row index to the largest duration possible in the code below.
|
||||
durVals.sort();
|
||||
|
||||
// Now calculate the rows in the tree of the durations' start times. For
|
||||
// each duration, we are looking for the node in the tree whose time is the
|
||||
// smallest time greater than or equal to the duration's start time.
|
||||
this.durationRowsToVals = {};
|
||||
this.durationValsToRows = {};
|
||||
var view = this.placesTree.view;
|
||||
// For all rows in the tree except the grippy row...
|
||||
for (let i = 0; i < view.rowCount - 1; i++) {
|
||||
let unfoundDurVals = [];
|
||||
let nodeTime = view.QueryInterface(Ci.nsINavHistoryResultTreeViewer).
|
||||
nodeForTreeIndex(i).time;
|
||||
// For all durations whose rows have not yet been found in the tree, see
|
||||
// if index i is their index. An index may map to more than one duration,
|
||||
// in which case the final duration (the largest) wins.
|
||||
for (let j = 0; j < durVals.length; j++) {
|
||||
let durVal = durVals[j];
|
||||
let durStartTime = this.durationStartTimes[durVal];
|
||||
if (nodeTime < durStartTime) {
|
||||
this.durationValsToRows[durVal] = i - 1;
|
||||
this.durationRowsToVals[i - 1] = durVal;
|
||||
}
|
||||
else
|
||||
unfoundDurVals.push(durVal);
|
||||
}
|
||||
durVals = unfoundDurVals;
|
||||
}
|
||||
|
||||
// If any durations were not found above, then every node in the tree has a
|
||||
// time greater than or equal to the duration. In other words, those
|
||||
// durations include the entire tree (except the grippy row).
|
||||
for (let i = 0; i < durVals.length; i++) {
|
||||
let durVal = durVals[i];
|
||||
this.durationValsToRows[durVal] = view.rowCount - 2;
|
||||
this.durationRowsToVals[view.rowCount - 2] = durVal;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* If the Places tree is not set up, sets it up. Otherwise does nothing.
|
||||
*/
|
||||
ensurePlacesTreeIsInited: function ()
|
||||
{
|
||||
if (this._placesTreeIsInited)
|
||||
return;
|
||||
|
||||
this._placesTreeIsInited = true;
|
||||
|
||||
// Either "Last Four Hours" or "Today" will have the most history. If
|
||||
// it's been more than 4 hours since today began, "Today" will. Otherwise
|
||||
// "Last Four Hours" will.
|
||||
var times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_TODAY);
|
||||
|
||||
// If it's been less than 4 hours since today began, use the past 4 hours.
|
||||
if (times[1] - times[0] < 14400000000) { // 4*60*60*1000000
|
||||
times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_4HOURS);
|
||||
}
|
||||
|
||||
var histServ = Cc["@mozilla.org/browser/nav-history-service;1"].
|
||||
getService(Ci.nsINavHistoryService);
|
||||
var query = histServ.getNewQuery();
|
||||
query.beginTimeReference = query.TIME_RELATIVE_EPOCH;
|
||||
query.beginTime = times[0];
|
||||
query.endTimeReference = query.TIME_RELATIVE_EPOCH;
|
||||
query.endTime = times[1];
|
||||
var opts = histServ.getNewQueryOptions();
|
||||
opts.sortingMode = opts.SORT_BY_DATE_DESCENDING;
|
||||
opts.queryType = opts.QUERY_TYPE_HISTORY;
|
||||
var result = histServ.executeQuery(query, opts);
|
||||
|
||||
var view = gContiguousSelectionTreeHelper.setTree(this.placesTree,
|
||||
new PlacesTreeView());
|
||||
result.addObserver(view, false);
|
||||
this.initDurationDropdown();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called on select of the duration dropdown and when grippyMoved() sets a
|
||||
* duration based on the location of the grippy row. Selects all the nodes in
|
||||
* the tree that are contained in the selected duration. If clearing
|
||||
* everything, the warning panel is shown instead.
|
||||
*/
|
||||
selectByTimespan: function ()
|
||||
{
|
||||
// This method is the onselect handler for the duration dropdown. As a
|
||||
// result it's called a couple of times before onload calls init().
|
||||
if (!this._inited)
|
||||
return;
|
||||
|
||||
var durDeck = document.getElementById("durationDeck");
|
||||
var durList = document.getElementById("sanitizeDurationChoice");
|
||||
var durVal = parseInt(durList.value);
|
||||
var durCustom = document.getElementById("sanitizeDurationCustom");
|
||||
|
||||
// If grippy row is not at a duration boundary, show the custom menuitem;
|
||||
// otherwise, hide it. Since the user cannot specify a custom duration by
|
||||
// using the dropdown, this conditional is true only when this method is
|
||||
// called onselect from grippyMoved(), so no selection need be made.
|
||||
if (durVal === this.TIMESPAN_CUSTOM) {
|
||||
durCustom.hidden = false;
|
||||
return;
|
||||
}
|
||||
durCustom.hidden = true;
|
||||
|
||||
// If clearing everything, show the warning and change the dialog's title.
|
||||
if (durVal === Sanitizer.TIMESPAN_EVERYTHING) {
|
||||
this.prepareWarning();
|
||||
durDeck.selectedIndex = 1;
|
||||
window.document.title =
|
||||
this.bundleBrowser.getString("sanitizeDialog2.everything.title");
|
||||
document.documentElement.getButton("accept").disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise -- if clearing a specific time range -- select that time range
|
||||
// in the tree.
|
||||
this.ensurePlacesTreeIsInited();
|
||||
durDeck.selectedIndex = 0;
|
||||
window.document.title =
|
||||
window.document.documentElement.getAttribute("noneverythingtitle");
|
||||
var durRow = this.durationValsToRows[durVal];
|
||||
gContiguousSelectionTreeHelper.rangedSelect(durRow);
|
||||
gContiguousSelectionTreeHelper.scrollToGrippy();
|
||||
|
||||
// If duration is empty (there are no selected rows), disable the dialog's
|
||||
// OK button.
|
||||
document.documentElement.getButton("accept").disabled = durRow < 0;
|
||||
},
|
||||
|
||||
sanitize: function ()
|
||||
{
|
||||
// Update pref values before handing off to the sanitizer (bug 453440)
|
||||
this.updatePrefs();
|
||||
var s = new Sanitizer();
|
||||
s.prefDomain = "privacy.cpd.";
|
||||
|
||||
var durList = document.getElementById("sanitizeDurationChoice");
|
||||
var durValue = parseInt(durList.value);
|
||||
s.ignoreTimespan = durValue === Sanitizer.TIMESPAN_EVERYTHING;
|
||||
|
||||
// Set the sanitizer's time range if we're not clearing everything.
|
||||
if (!s.ignoreTimespan) {
|
||||
// If user selected a custom timespan, use that.
|
||||
if (durValue === this.TIMESPAN_CUSTOM) {
|
||||
var view = this.placesTree.view;
|
||||
var now = Date.now() * 1000;
|
||||
// We disable the dialog's OK button if there's no selection, but we'll
|
||||
// handle that case just in... case.
|
||||
if (view.selection.getRangeCount() === 0)
|
||||
s.range = [now, now];
|
||||
else {
|
||||
var startIndexRef = {};
|
||||
// Tree sorted by visit date DEscending, so start time time comes last.
|
||||
view.selection.getRangeAt(0, {}, startIndexRef);
|
||||
view.QueryInterface(Ci.nsINavHistoryResultTreeViewer);
|
||||
var startNode = view.nodeForTreeIndex(startIndexRef.value);
|
||||
s.range = [startNode.time, now];
|
||||
}
|
||||
}
|
||||
// Otherwise use the predetermined range.
|
||||
else
|
||||
s.range = [this.durationStartTimes[durValue], Date.now() * 1000];
|
||||
}
|
||||
|
||||
try {
|
||||
s.sanitize();
|
||||
} catch (er) {
|
||||
Components.utils.reportError("Exception during sanitize: " + er);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* In order to mark the custom Places tree view and its nsINavHistoryResult
|
||||
* for garbage collection, we need to break the reference cycle between the
|
||||
* two.
|
||||
*/
|
||||
unload: function ()
|
||||
{
|
||||
let result = this.placesTree.getResult();
|
||||
result.removeObserver(this.placesTree.view);
|
||||
this.placesTree.view = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the user moves the grippy by dragging it, clicking in the tree,
|
||||
* or on keypress. Updates the duration dropdown so that it displays the
|
||||
* appropriate specific or custom duration.
|
||||
*
|
||||
* @param aEventName
|
||||
* The name of the event whose handler called this method, e.g.,
|
||||
* "ondragstart", "onkeypress", etc.
|
||||
* @param aEvent
|
||||
* The event captured in the event handler.
|
||||
*/
|
||||
grippyMoved: function (aEventName, aEvent)
|
||||
{
|
||||
gContiguousSelectionTreeHelper[aEventName](aEvent);
|
||||
var lastSelRow = gContiguousSelectionTreeHelper.getGrippyRow() - 1;
|
||||
var durList = document.getElementById("sanitizeDurationChoice");
|
||||
var durValue = parseInt(durList.value);
|
||||
|
||||
// Multiple durations can map to the same row. Don't update the dropdown
|
||||
// if the current duration is valid for lastSelRow.
|
||||
if ((durValue !== this.TIMESPAN_CUSTOM ||
|
||||
lastSelRow in this.durationRowsToVals) &&
|
||||
(durValue === this.TIMESPAN_CUSTOM ||
|
||||
this.durationValsToRows[durValue] !== lastSelRow)) {
|
||||
// Setting durList.value causes its onselect handler to fire, which calls
|
||||
// selectByTimespan().
|
||||
if (lastSelRow in this.durationRowsToVals)
|
||||
durList.value = this.durationRowsToVals[lastSelRow];
|
||||
else
|
||||
durList.value = this.TIMESPAN_CUSTOM;
|
||||
}
|
||||
|
||||
// If there are no selected rows, disable the dialog's OK button.
|
||||
document.documentElement.getButton("accept").disabled = lastSelRow < 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
|
||||
#ifdef CRH_DIALOG_TREE_VIEW
|
||||
/**
|
||||
* A helper for handling contiguous selection in the tree.
|
||||
*/
|
||||
var gContiguousSelectionTreeHelper = {
|
||||
|
||||
/**
|
||||
* Gets the tree associated with this helper.
|
||||
*/
|
||||
get tree()
|
||||
{
|
||||
return this._tree;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the tree that this module handles. The tree is assigned a new view
|
||||
* that is equipped to handle contiguous selection. You can pass in an
|
||||
* object that will be used as the prototype of the new view. Otherwise
|
||||
* the tree's current view is used as the prototype.
|
||||
*
|
||||
* @param aTreeElement
|
||||
* The tree element
|
||||
* @param aProtoTreeView
|
||||
* If defined, this will be used as the prototype of the tree's new
|
||||
* view
|
||||
* @return The new view
|
||||
*/
|
||||
setTree: function CSTH_setTree(aTreeElement, aProtoTreeView)
|
||||
{
|
||||
this._tree = aTreeElement;
|
||||
var newView = this._makeTreeView(aProtoTreeView || aTreeElement.view);
|
||||
aTreeElement.view = newView;
|
||||
return newView;
|
||||
},
|
||||
|
||||
/**
|
||||
* The index of the row that the grippy occupies. Note that the index of the
|
||||
* last selected row is getGrippyRow() - 1. If getGrippyRow() is 0, then
|
||||
* no selection exists.
|
||||
*
|
||||
* @return The row index of the grippy
|
||||
*/
|
||||
getGrippyRow: function CSTH_getGrippyRow()
|
||||
{
|
||||
var sel = this.tree.view.selection;
|
||||
var rangeCount = sel.getRangeCount();
|
||||
if (rangeCount === 0)
|
||||
return 0;
|
||||
if (rangeCount !== 1) {
|
||||
throw "contiguous selection tree helper: getGrippyRow called with " +
|
||||
"multiple selection ranges";
|
||||
}
|
||||
var max = {};
|
||||
sel.getRangeAt(0, {}, max);
|
||||
return max.value + 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function for the dragover event. Your dragover listener should
|
||||
* call this. It updates the selection in the tree under the mouse.
|
||||
*
|
||||
* @param aEvent
|
||||
* The observed dragover event
|
||||
*/
|
||||
ondragover: function CSTH_ondragover(aEvent)
|
||||
{
|
||||
// Without this when dragging on Windows the mouse cursor is a "no" sign.
|
||||
// This makes it a drop symbol.
|
||||
var ds = Cc["@mozilla.org/widget/dragservice;1"].
|
||||
getService(Ci.nsIDragService).
|
||||
getCurrentSession();
|
||||
ds.canDrop = true;
|
||||
ds.dragAction = 0;
|
||||
|
||||
var tbo = this.tree.treeBoxObject;
|
||||
aEvent.QueryInterface(Ci.nsIDOMMouseEvent);
|
||||
var hoverRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
|
||||
|
||||
if (hoverRow < 0)
|
||||
return;
|
||||
|
||||
this.rangedSelect(hoverRow - 1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function for the dragstart event. Your dragstart listener should
|
||||
* call this. It starts a drag session.
|
||||
*
|
||||
* @param aEvent
|
||||
* The observed dragstart event
|
||||
*/
|
||||
ondragstart: function CSTH_ondragstart(aEvent)
|
||||
{
|
||||
var tbo = this.tree.treeBoxObject;
|
||||
var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
|
||||
|
||||
if (clickedRow !== this.getGrippyRow())
|
||||
return;
|
||||
|
||||
// This part is a hack. What we really want is a grab and slide, not
|
||||
// drag and drop. Start a move drag session with dummy data and a
|
||||
// dummy region. Set the region's coordinates to (Infinity, Infinity)
|
||||
// so it's drawn offscreen and its size to (1, 1).
|
||||
var arr = Cc["@mozilla.org/supports-array;1"].
|
||||
createInstance(Ci.nsISupportsArray);
|
||||
var trans = Cc["@mozilla.org/widget/transferable;1"].
|
||||
createInstance(Ci.nsITransferable);
|
||||
trans.init(null);
|
||||
trans.setTransferData('dummy-flavor', null, 0);
|
||||
arr.AppendElement(trans);
|
||||
var reg = Cc["@mozilla.org/gfx/region;1"].
|
||||
createInstance(Ci.nsIScriptableRegion);
|
||||
reg.setToRect(Infinity, Infinity, 1, 1);
|
||||
var ds = Cc["@mozilla.org/widget/dragservice;1"].
|
||||
getService(Ci.nsIDragService);
|
||||
ds.invokeDragSession(aEvent.target, arr, reg, ds.DRAGDROP_ACTION_MOVE);
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function for the keypress event. Your keypress listener should
|
||||
* call this. Users can use Up, Down, Page Up/Down, Home, and End to move
|
||||
* the bottom of the selection window.
|
||||
*
|
||||
* @param aEvent
|
||||
* The observed keypress event
|
||||
*/
|
||||
onkeypress: function CSTH_onkeypress(aEvent)
|
||||
{
|
||||
var grippyRow = this.getGrippyRow();
|
||||
var tbo = this.tree.treeBoxObject;
|
||||
var rangeEnd;
|
||||
switch (aEvent.keyCode) {
|
||||
case aEvent.DOM_VK_HOME:
|
||||
rangeEnd = 0;
|
||||
break;
|
||||
case aEvent.DOM_VK_PAGE_UP:
|
||||
rangeEnd = grippyRow - tbo.getPageLength();
|
||||
break;
|
||||
case aEvent.DOM_VK_UP:
|
||||
rangeEnd = grippyRow - 2;
|
||||
break;
|
||||
case aEvent.DOM_VK_DOWN:
|
||||
rangeEnd = grippyRow;
|
||||
break;
|
||||
case aEvent.DOM_VK_PAGE_DOWN:
|
||||
rangeEnd = grippyRow + tbo.getPageLength();
|
||||
break;
|
||||
case aEvent.DOM_VK_END:
|
||||
rangeEnd = this.tree.view.rowCount - 2;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
aEvent.stopPropagation();
|
||||
|
||||
// First, clip rangeEnd. this.rangedSelect() doesn't clip the range if we
|
||||
// select past the ends of the tree.
|
||||
if (rangeEnd < 0)
|
||||
rangeEnd = -1;
|
||||
else if (this.tree.view.rowCount - 2 < rangeEnd)
|
||||
rangeEnd = this.tree.view.rowCount - 2;
|
||||
|
||||
// Next, (de)select.
|
||||
this.rangedSelect(rangeEnd);
|
||||
|
||||
// Finally, scroll the tree. We always want one row above and below the
|
||||
// grippy row to be visible if possible.
|
||||
if (rangeEnd < grippyRow) // moved up
|
||||
tbo.ensureRowIsVisible(rangeEnd < 0 ? 0 : rangeEnd);
|
||||
else { // moved down
|
||||
if (rangeEnd + 2 < this.tree.view.rowCount)
|
||||
tbo.ensureRowIsVisible(rangeEnd + 2);
|
||||
else if (rangeEnd + 1 < this.tree.view.rowCount)
|
||||
tbo.ensureRowIsVisible(rangeEnd + 1);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function for the mousedown event. Your mousedown listener should
|
||||
* call this. Users can click on individual rows to make the selection
|
||||
* jump to them immediately.
|
||||
*
|
||||
* @param aEvent
|
||||
* The observed mousedown event
|
||||
*/
|
||||
onmousedown: function CSTH_onmousedown(aEvent)
|
||||
{
|
||||
var tbo = this.tree.treeBoxObject;
|
||||
var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
|
||||
|
||||
if (clickedRow < 0 || clickedRow >= this.tree.view.rowCount)
|
||||
return;
|
||||
|
||||
if (clickedRow < this.getGrippyRow())
|
||||
this.rangedSelect(clickedRow);
|
||||
else if (clickedRow > this.getGrippyRow())
|
||||
this.rangedSelect(clickedRow - 1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Selects range [0, aEndRow] in the tree. The grippy row will then be at
|
||||
* index aEndRow + 1. aEndRow may be -1, in which case the selection is
|
||||
* cleared and the grippy row will be at index 0.
|
||||
*
|
||||
* @param aEndRow
|
||||
* The range [0, aEndRow] will be selected.
|
||||
*/
|
||||
rangedSelect: function CSTH_rangedSelect(aEndRow)
|
||||
{
|
||||
var tbo = this.tree.treeBoxObject;
|
||||
if (aEndRow < 0)
|
||||
this.tree.view.selection.clearSelection();
|
||||
else
|
||||
this.tree.view.selection.rangedSelect(0, aEndRow, false);
|
||||
tbo.invalidateRange(tbo.getFirstVisibleRow(), tbo.getLastVisibleRow());
|
||||
},
|
||||
|
||||
/**
|
||||
* Scrolls the tree so that the grippy row is in the center of the view.
|
||||
*/
|
||||
scrollToGrippy: function CSTH_scrollToGrippy()
|
||||
{
|
||||
var rowCount = this.tree.view.rowCount;
|
||||
var tbo = this.tree.treeBoxObject;
|
||||
var pageLen = tbo.getPageLength() ||
|
||||
parseInt(this.tree.getAttribute("rows")) ||
|
||||
10;
|
||||
|
||||
// All rows fit on a single page.
|
||||
if (rowCount <= pageLen)
|
||||
return;
|
||||
|
||||
var scrollToRow = this.getGrippyRow() - Math.ceil(pageLen / 2.0);
|
||||
|
||||
// Grippy row is in first half of first page.
|
||||
if (scrollToRow < 0)
|
||||
scrollToRow = 0;
|
||||
|
||||
// Grippy row is in last half of last page.
|
||||
else if (rowCount < scrollToRow + pageLen)
|
||||
scrollToRow = rowCount - pageLen;
|
||||
|
||||
tbo.scrollToRow(scrollToRow);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new tree view suitable for contiguous selection. If
|
||||
* aProtoTreeView is specified, it's used as the new view's prototype.
|
||||
* Otherwise the tree's current view is used as the prototype.
|
||||
*
|
||||
* @param aProtoTreeView
|
||||
* Used as the new view's prototype if specified
|
||||
*/
|
||||
_makeTreeView: function CSTH__makeTreeView(aProtoTreeView)
|
||||
{
|
||||
var view = aProtoTreeView;
|
||||
var that = this;
|
||||
|
||||
//XXXadw: When Alex gets the grippy icon done, this may or may not change,
|
||||
// depending on how we style it.
|
||||
view.isSeparator = function CSTH_View_isSeparator(aRow)
|
||||
{
|
||||
return aRow === that.getGrippyRow();
|
||||
};
|
||||
|
||||
// rowCount includes the grippy row.
|
||||
view.__defineGetter__("_rowCount", view.__lookupGetter__("rowCount"));
|
||||
view.__defineGetter__("rowCount",
|
||||
function CSTH_View_rowCount()
|
||||
{
|
||||
return this._rowCount + 1;
|
||||
});
|
||||
|
||||
// This has to do with visual feedback in the view itself, e.g., drawing
|
||||
// a small line underneath the dropzone. Not what we want.
|
||||
view.canDrop = function CSTH_View_canDrop() { return false; };
|
||||
|
||||
// No clicking headers to sort the tree or sort feedback on columns.
|
||||
view.cycleHeader = function CSTH_View_cycleHeader() {};
|
||||
view.sortingChanged = function CSTH_View_sortingChanged() {};
|
||||
|
||||
// Override a bunch of methods to account for the grippy row.
|
||||
|
||||
view._getCellProperties = view.getCellProperties;
|
||||
view.getCellProperties =
|
||||
function CSTH_View_getCellProperties(aRow, aCol)
|
||||
{
|
||||
var grippyRow = that.getGrippyRow();
|
||||
if (aRow === grippyRow)
|
||||
return "grippyRow";
|
||||
if (aRow < grippyRow)
|
||||
return this._getCellProperties(aRow, aCol);
|
||||
|
||||
return this._getCellProperties(aRow - 1, aCol);
|
||||
};
|
||||
|
||||
view._getRowProperties = view.getRowProperties;
|
||||
view.getRowProperties =
|
||||
function CSTH_View_getRowProperties(aRow)
|
||||
{
|
||||
var grippyRow = that.getGrippyRow();
|
||||
if (aRow === grippyRow)
|
||||
return "grippyRow";
|
||||
|
||||
if (aRow < grippyRow)
|
||||
return this._getRowProperties(aRow);
|
||||
|
||||
return this._getRowProperties(aRow - 1);
|
||||
};
|
||||
|
||||
view._getCellText = view.getCellText;
|
||||
view.getCellText =
|
||||
function CSTH_View_getCellText(aRow, aCol)
|
||||
{
|
||||
var grippyRow = that.getGrippyRow();
|
||||
if (aRow === grippyRow)
|
||||
return "";
|
||||
aRow = aRow < grippyRow ? aRow : aRow - 1;
|
||||
return this._getCellText(aRow, aCol);
|
||||
};
|
||||
|
||||
view._getImageSrc = view.getImageSrc;
|
||||
view.getImageSrc =
|
||||
function CSTH_View_getImageSrc(aRow, aCol)
|
||||
{
|
||||
var grippyRow = that.getGrippyRow();
|
||||
if (aRow === grippyRow)
|
||||
return "";
|
||||
aRow = aRow < grippyRow ? aRow : aRow - 1;
|
||||
return this._getImageSrc(aRow, aCol);
|
||||
};
|
||||
|
||||
view.isContainer = function CSTH_View_isContainer(aRow) { return false; };
|
||||
view.getParentIndex = function CSTH_View_getParentIndex(aRow) { return -1; };
|
||||
view.getLevel = function CSTH_View_getLevel(aRow) { return 0; };
|
||||
view.hasNextSibling = function CSTH_View_hasNextSibling(aRow, aAfterIndex)
|
||||
{
|
||||
return aRow < this.rowCount - 1;
|
||||
};
|
||||
|
||||
return view;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue