forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			897 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			897 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* -*- 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"));
 | |
|       if (!s.canClearItem(name)) {
 | |
|         prefItem.preference = null;
 | |
|         prefItem.checked = false;
 | |
|         prefItem.disabled = true;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     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;
 | |
| 
 | |
|     try {
 | |
|       s.sanitize();
 | |
|     } catch (er) {
 | |
|       Components.utils.reportError("Exception during sanitize: " + er);
 | |
|     }
 | |
|     return true;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * 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"));
 | |
|       if (!s.canClearItem(name)) {
 | |
|         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 atomServ = Cc["@mozilla.org/atom-service;1"].
 | |
|                    getService(Ci.nsIAtomService);
 | |
| 
 | |
|     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, aProps)
 | |
|       {
 | |
|         var grippyRow = that.getGrippyRow();
 | |
|         if (aRow === grippyRow)
 | |
|           aProps.AppendElement(atomServ.getAtom("grippyRow"));
 | |
|         else if (aRow < grippyRow)
 | |
|           this._getCellProperties(aRow, aCol, aProps);
 | |
|         else
 | |
|           this._getCellProperties(aRow - 1, aCol, aProps);
 | |
|       };
 | |
| 
 | |
|     view._getRowProperties = view.getRowProperties;
 | |
|     view.getRowProperties =
 | |
|       function CSTH_View_getRowProperties(aRow, aProps)
 | |
|       {
 | |
|         var grippyRow = that.getGrippyRow();
 | |
|         if (aRow === grippyRow)
 | |
|           aProps.AppendElement(atomServ.getAtom("grippyRow"));
 | |
|         else if (aRow < grippyRow)
 | |
|           this._getRowProperties(aRow, aProps);
 | |
|         else
 | |
|           this._getRowProperties(aRow - 1, aProps);
 | |
|       };
 | |
| 
 | |
|     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
 | 
