michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: Components.utils.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: const Ci = Components.interfaces; michael@0: const Cc = Components.classes; michael@0: michael@0: const ENGINE_FLAVOR = "text/x-moz-search-engine"; michael@0: michael@0: const BROWSER_SUGGEST_PREF = "browser.search.suggest.enabled"; michael@0: michael@0: var gEngineView = null; michael@0: michael@0: var gEngineManagerDialog = { michael@0: init: function engineManager_init() { michael@0: gEngineView = new EngineView(new EngineStore()); michael@0: michael@0: var suggestEnabled = Services.prefs.getBoolPref(BROWSER_SUGGEST_PREF); michael@0: document.getElementById("enableSuggest").checked = suggestEnabled; michael@0: michael@0: var tree = document.getElementById("engineList"); michael@0: tree.view = gEngineView; michael@0: michael@0: Services.obs.addObserver(this, "browser-search-engine-modified", false); michael@0: }, michael@0: michael@0: destroy: function engineManager_destroy() { michael@0: // Remove the observer michael@0: Services.obs.removeObserver(this, "browser-search-engine-modified"); michael@0: }, michael@0: michael@0: observe: function engineManager_observe(aEngine, aTopic, aVerb) { michael@0: if (aTopic == "browser-search-engine-modified") { michael@0: aEngine.QueryInterface(Ci.nsISearchEngine); michael@0: switch (aVerb) { michael@0: case "engine-added": michael@0: gEngineView._engineStore.addEngine(aEngine); michael@0: gEngineView.rowCountChanged(gEngineView.lastIndex, 1); michael@0: break; michael@0: case "engine-changed": michael@0: gEngineView._engineStore.reloadIcons(); michael@0: gEngineView.invalidate(); michael@0: break; michael@0: case "engine-removed": michael@0: case "engine-current": michael@0: case "engine-default": michael@0: // Not relevant michael@0: break; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: onOK: function engineManager_onOK() { michael@0: // Set the preference michael@0: var newSuggestEnabled = document.getElementById("enableSuggest").checked; michael@0: Services.prefs.setBoolPref(BROWSER_SUGGEST_PREF, newSuggestEnabled); michael@0: michael@0: // Commit the changes michael@0: gEngineView._engineStore.commit(); michael@0: }, michael@0: michael@0: onRestoreDefaults: function engineManager_onRestoreDefaults() { michael@0: var num = gEngineView._engineStore.restoreDefaultEngines(); michael@0: gEngineView.rowCountChanged(0, num); michael@0: gEngineView.invalidate(); michael@0: }, michael@0: michael@0: showRestoreDefaults: function engineManager_showRestoreDefaults(val) { michael@0: document.documentElement.getButton("extra2").disabled = !val; michael@0: }, michael@0: michael@0: loadAddEngines: function engineManager_loadAddEngines() { michael@0: this.onOK(); michael@0: window.opener.BrowserSearch.loadAddEngines(); michael@0: window.close(); michael@0: }, michael@0: michael@0: remove: function engineManager_remove() { michael@0: gEngineView._engineStore.removeEngine(gEngineView.selectedEngine); michael@0: var index = gEngineView.selectedIndex; michael@0: gEngineView.rowCountChanged(index, -1); michael@0: gEngineView.invalidate(); michael@0: gEngineView.selection.select(Math.min(index, gEngineView.lastIndex)); michael@0: gEngineView.ensureRowIsVisible(gEngineView.currentIndex); michael@0: document.getElementById("engineList").focus(); michael@0: }, michael@0: michael@0: /** michael@0: * Moves the selected engine either up or down in the engine list michael@0: * @param aDir michael@0: * -1 to move the selected engine down, +1 to move it up. michael@0: */ michael@0: bump: function engineManager_move(aDir) { michael@0: var selectedEngine = gEngineView.selectedEngine; michael@0: var newIndex = gEngineView.selectedIndex - aDir; michael@0: michael@0: gEngineView._engineStore.moveEngine(selectedEngine, newIndex); michael@0: michael@0: gEngineView.invalidate(); michael@0: gEngineView.selection.select(newIndex); michael@0: gEngineView.ensureRowIsVisible(newIndex); michael@0: this.showRestoreDefaults(true); michael@0: document.getElementById("engineList").focus(); michael@0: }, michael@0: michael@0: editKeyword: function engineManager_editKeyword() { michael@0: var selectedEngine = gEngineView.selectedEngine; michael@0: if (!selectedEngine) michael@0: return; michael@0: michael@0: var alias = { value: selectedEngine.alias }; michael@0: var strings = document.getElementById("engineManagerBundle"); michael@0: var title = strings.getString("editTitle"); michael@0: var msg = strings.getFormattedString("editMsg", [selectedEngine.name]); michael@0: michael@0: while (Services.prompt.prompt(window, title, msg, alias, null, {})) { michael@0: var bduplicate = false; michael@0: var eduplicate = false; michael@0: var dupName = ""; michael@0: michael@0: if (alias.value != "") { michael@0: try { michael@0: let bmserv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. michael@0: getService(Ci.nsINavBookmarksService); michael@0: if (bmserv.getURIForKeyword(alias.value)) michael@0: bduplicate = true; michael@0: } catch(ex) {} michael@0: michael@0: // Check for duplicates in changes we haven't committed yet michael@0: let engines = gEngineView._engineStore.engines; michael@0: for each (let engine in engines) { michael@0: if (engine.alias == alias.value && michael@0: engine.name != selectedEngine.name) { michael@0: eduplicate = true; michael@0: dupName = engine.name; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Notify the user if they have chosen an existing engine/bookmark keyword michael@0: if (eduplicate || bduplicate) { michael@0: var dtitle = strings.getString("duplicateTitle"); michael@0: var bmsg = strings.getString("duplicateBookmarkMsg"); michael@0: var emsg = strings.getFormattedString("duplicateEngineMsg", [dupName]); michael@0: michael@0: Services.prompt.alert(window, dtitle, eduplicate ? emsg : bmsg); michael@0: } else { michael@0: gEngineView._engineStore.changeEngine(selectedEngine, "alias", michael@0: alias.value); michael@0: gEngineView.invalidate(); michael@0: break; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: onSelect: function engineManager_onSelect() { michael@0: // Buttons only work if an engine is selected and it's not the last engine, michael@0: // the latter is true when the selected is first and last at the same time. michael@0: var lastSelected = (gEngineView.selectedIndex == gEngineView.lastIndex); michael@0: var firstSelected = (gEngineView.selectedIndex == 0); michael@0: var noSelection = (gEngineView.selectedIndex == -1); michael@0: michael@0: document.getElementById("cmd_remove") michael@0: .setAttribute("disabled", noSelection || michael@0: (firstSelected && lastSelected)); michael@0: michael@0: document.getElementById("cmd_moveup") michael@0: .setAttribute("disabled", noSelection || firstSelected); michael@0: michael@0: document.getElementById("cmd_movedown") michael@0: .setAttribute("disabled", noSelection || lastSelected); michael@0: michael@0: document.getElementById("cmd_editkeyword") michael@0: .setAttribute("disabled", noSelection); michael@0: } michael@0: }; michael@0: michael@0: function onDragEngineStart(event) { michael@0: var selectedIndex = gEngineView.selectedIndex; michael@0: if (selectedIndex >= 0) { michael@0: event.dataTransfer.setData(ENGINE_FLAVOR, selectedIndex.toString()); michael@0: event.dataTransfer.effectAllowed = "move"; michael@0: } michael@0: } michael@0: michael@0: // "Operation" objects michael@0: function EngineMoveOp(aEngineClone, aNewIndex) { michael@0: if (!aEngineClone) michael@0: throw new Error("bad args to new EngineMoveOp!"); michael@0: this._engine = aEngineClone.originalEngine; michael@0: this._newIndex = aNewIndex; michael@0: } michael@0: EngineMoveOp.prototype = { michael@0: _engine: null, michael@0: _newIndex: null, michael@0: commit: function EMO_commit() { michael@0: Services.search.moveEngine(this._engine, this._newIndex); michael@0: } michael@0: } michael@0: michael@0: function EngineRemoveOp(aEngineClone) { michael@0: if (!aEngineClone) michael@0: throw new Error("bad args to new EngineRemoveOp!"); michael@0: this._engine = aEngineClone.originalEngine; michael@0: } michael@0: EngineRemoveOp.prototype = { michael@0: _engine: null, michael@0: commit: function ERO_commit() { michael@0: Services.search.removeEngine(this._engine); michael@0: } michael@0: } michael@0: michael@0: function EngineUnhideOp(aEngineClone, aNewIndex) { michael@0: if (!aEngineClone) michael@0: throw new Error("bad args to new EngineUnhideOp!"); michael@0: this._engine = aEngineClone.originalEngine; michael@0: this._newIndex = aNewIndex; michael@0: } michael@0: EngineUnhideOp.prototype = { michael@0: _engine: null, michael@0: _newIndex: null, michael@0: commit: function EUO_commit() { michael@0: this._engine.hidden = false; michael@0: Services.search.moveEngine(this._engine, this._newIndex); michael@0: } michael@0: } michael@0: michael@0: function EngineChangeOp(aEngineClone, aProp, aValue) { michael@0: if (!aEngineClone) michael@0: throw new Error("bad args to new EngineChangeOp!"); michael@0: michael@0: this._engine = aEngineClone.originalEngine; michael@0: this._prop = aProp; michael@0: this._newValue = aValue; michael@0: } michael@0: EngineChangeOp.prototype = { michael@0: _engine: null, michael@0: _prop: null, michael@0: _newValue: null, michael@0: commit: function ECO_commit() { michael@0: this._engine[this._prop] = this._newValue; michael@0: } michael@0: } michael@0: michael@0: function EngineStore() { michael@0: this._engines = Services.search.getVisibleEngines().map(this._cloneEngine); michael@0: this._defaultEngines = Services.search.getDefaultEngines().map(this._cloneEngine); michael@0: michael@0: this._ops = []; michael@0: michael@0: // check if we need to disable the restore defaults button michael@0: var someHidden = this._defaultEngines.some(function (e) e.hidden); michael@0: gEngineManagerDialog.showRestoreDefaults(someHidden); michael@0: } michael@0: EngineStore.prototype = { michael@0: _engines: null, michael@0: _defaultEngines: null, michael@0: _ops: null, michael@0: michael@0: get engines() { michael@0: return this._engines; michael@0: }, michael@0: set engines(val) { michael@0: this._engines = val; michael@0: return val; michael@0: }, michael@0: michael@0: _getIndexForEngine: function ES_getIndexForEngine(aEngine) { michael@0: return this._engines.indexOf(aEngine); michael@0: }, michael@0: michael@0: _getEngineByName: function ES_getEngineByName(aName) { michael@0: for each (var engine in this._engines) michael@0: if (engine.name == aName) michael@0: return engine; michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: _cloneEngine: function ES_cloneEngine(aEngine) { michael@0: var clonedObj={}; michael@0: for (var i in aEngine) michael@0: clonedObj[i] = aEngine[i]; michael@0: clonedObj.originalEngine = aEngine; michael@0: return clonedObj; michael@0: }, michael@0: michael@0: // Callback for Array's some(). A thisObj must be passed to some() michael@0: _isSameEngine: function ES_isSameEngine(aEngineClone) { michael@0: return aEngineClone.originalEngine == this.originalEngine; michael@0: }, michael@0: michael@0: commit: function ES_commit() { michael@0: var currentEngine = this._cloneEngine(Services.search.currentEngine); michael@0: for (var i = 0; i < this._ops.length; i++) michael@0: this._ops[i].commit(); michael@0: michael@0: // Restore currentEngine if it is a default engine that is still visible. michael@0: // Needed if the user deletes currentEngine and then restores it. michael@0: if (this._defaultEngines.some(this._isSameEngine, currentEngine) && michael@0: !currentEngine.originalEngine.hidden) michael@0: Services.search.currentEngine = currentEngine.originalEngine; michael@0: }, michael@0: michael@0: addEngine: function ES_addEngine(aEngine) { michael@0: this._engines.push(this._cloneEngine(aEngine)); michael@0: }, michael@0: michael@0: moveEngine: function ES_moveEngine(aEngine, aNewIndex) { michael@0: if (aNewIndex < 0 || aNewIndex > this._engines.length - 1) michael@0: throw new Error("ES_moveEngine: invalid aNewIndex!"); michael@0: var index = this._getIndexForEngine(aEngine); michael@0: if (index == -1) michael@0: throw new Error("ES_moveEngine: invalid engine?"); michael@0: michael@0: if (index == aNewIndex) michael@0: return; // nothing to do michael@0: michael@0: // Move the engine in our internal store michael@0: var removedEngine = this._engines.splice(index, 1)[0]; michael@0: this._engines.splice(aNewIndex, 0, removedEngine); michael@0: michael@0: this._ops.push(new EngineMoveOp(aEngine, aNewIndex)); michael@0: }, michael@0: michael@0: removeEngine: function ES_removeEngine(aEngine) { michael@0: var index = this._getIndexForEngine(aEngine); michael@0: if (index == -1) michael@0: throw new Error("invalid engine?"); michael@0: michael@0: this._engines.splice(index, 1); michael@0: this._ops.push(new EngineRemoveOp(aEngine)); michael@0: if (this._defaultEngines.some(this._isSameEngine, aEngine)) michael@0: gEngineManagerDialog.showRestoreDefaults(true); michael@0: }, michael@0: michael@0: restoreDefaultEngines: function ES_restoreDefaultEngines() { michael@0: var added = 0; michael@0: michael@0: for (var i = 0; i < this._defaultEngines.length; ++i) { michael@0: var e = this._defaultEngines[i]; michael@0: michael@0: // If the engine is already in the list, just move it. michael@0: if (this._engines.some(this._isSameEngine, e)) { michael@0: this.moveEngine(this._getEngineByName(e.name), i); michael@0: } else { michael@0: // Otherwise, add it back to our internal store michael@0: this._engines.splice(i, 0, e); michael@0: this._ops.push(new EngineUnhideOp(e, i)); michael@0: added++; michael@0: } michael@0: } michael@0: gEngineManagerDialog.showRestoreDefaults(false); michael@0: return added; michael@0: }, michael@0: michael@0: changeEngine: function ES_changeEngine(aEngine, aProp, aNewValue) { michael@0: var index = this._getIndexForEngine(aEngine); michael@0: if (index == -1) michael@0: throw new Error("invalid engine?"); michael@0: michael@0: this._engines[index][aProp] = aNewValue; michael@0: this._ops.push(new EngineChangeOp(aEngine, aProp, aNewValue)); michael@0: }, michael@0: michael@0: reloadIcons: function ES_reloadIcons() { michael@0: this._engines.forEach(function (e) { michael@0: e.uri = e.originalEngine.uri; michael@0: }); michael@0: } michael@0: } michael@0: michael@0: function EngineView(aEngineStore) { michael@0: this._engineStore = aEngineStore; michael@0: } michael@0: EngineView.prototype = { michael@0: _engineStore: null, michael@0: tree: null, michael@0: michael@0: get lastIndex() { michael@0: return this.rowCount - 1; michael@0: }, michael@0: get selectedIndex() { michael@0: var seln = this.selection; michael@0: if (seln.getRangeCount() > 0) { michael@0: var min = {}; michael@0: seln.getRangeAt(0, min, {}); michael@0: return min.value; michael@0: } michael@0: return -1; michael@0: }, michael@0: get selectedEngine() { michael@0: return this._engineStore.engines[this.selectedIndex]; michael@0: }, michael@0: michael@0: // Helpers michael@0: rowCountChanged: function (index, count) { michael@0: this.tree.rowCountChanged(index, count); michael@0: }, michael@0: michael@0: invalidate: function () { michael@0: this.tree.invalidate(); michael@0: }, michael@0: michael@0: ensureRowIsVisible: function (index) { michael@0: this.tree.ensureRowIsVisible(index); michael@0: }, michael@0: michael@0: getSourceIndexFromDrag: function (dataTransfer) { michael@0: return parseInt(dataTransfer.getData(ENGINE_FLAVOR)); michael@0: }, michael@0: michael@0: // nsITreeView michael@0: get rowCount() { michael@0: return this._engineStore.engines.length; michael@0: }, michael@0: michael@0: getImageSrc: function(index, column) { michael@0: if (column.id == "engineName" && this._engineStore.engines[index].iconURI) michael@0: return this._engineStore.engines[index].iconURI.spec; michael@0: return ""; michael@0: }, michael@0: michael@0: getCellText: function(index, column) { michael@0: if (column.id == "engineName") michael@0: return this._engineStore.engines[index].name; michael@0: else if (column.id == "engineKeyword") michael@0: return this._engineStore.engines[index].alias; michael@0: return ""; michael@0: }, michael@0: michael@0: setTree: function(tree) { michael@0: this.tree = tree; michael@0: }, michael@0: michael@0: canDrop: function(targetIndex, orientation, dataTransfer) { michael@0: var sourceIndex = this.getSourceIndexFromDrag(dataTransfer); michael@0: return (sourceIndex != -1 && michael@0: sourceIndex != targetIndex && michael@0: sourceIndex != targetIndex + orientation); michael@0: }, michael@0: michael@0: drop: function(dropIndex, orientation, dataTransfer) { michael@0: var sourceIndex = this.getSourceIndexFromDrag(dataTransfer); michael@0: var sourceEngine = this._engineStore.engines[sourceIndex]; michael@0: michael@0: if (dropIndex > sourceIndex) { michael@0: if (orientation == Ci.nsITreeView.DROP_BEFORE) michael@0: dropIndex--; michael@0: } else { michael@0: if (orientation == Ci.nsITreeView.DROP_AFTER) michael@0: dropIndex++; michael@0: } michael@0: michael@0: this._engineStore.moveEngine(sourceEngine, dropIndex); michael@0: gEngineManagerDialog.showRestoreDefaults(true); michael@0: michael@0: // Redraw, and adjust selection michael@0: this.invalidate(); michael@0: this.selection.select(dropIndex); michael@0: }, michael@0: michael@0: selection: null, michael@0: getRowProperties: function(index) { return ""; }, michael@0: getCellProperties: function(index, column) { return ""; }, michael@0: getColumnProperties: function(column) { return ""; }, michael@0: isContainer: function(index) { return false; }, michael@0: isContainerOpen: function(index) { return false; }, michael@0: isContainerEmpty: function(index) { return false; }, michael@0: isSeparator: function(index) { return false; }, michael@0: isSorted: function(index) { return false; }, michael@0: getParentIndex: function(index) { return -1; }, michael@0: hasNextSibling: function(parentIndex, index) { return false; }, michael@0: getLevel: function(index) { return 0; }, michael@0: getProgressMode: function(index, column) { }, michael@0: getCellValue: function(index, column) { }, michael@0: toggleOpenState: function(index) { }, michael@0: cycleHeader: function(column) { }, michael@0: selectionChanged: function() { }, michael@0: cycleCell: function(row, column) { }, michael@0: isEditable: function(index, column) { return false; }, michael@0: isSelectable: function(index, column) { return false; }, michael@0: setCellValue: function(index, column, value) { }, michael@0: setCellText: function(index, column, value) { }, michael@0: performAction: function(action) { }, michael@0: performActionOnRow: function(action, index) { }, michael@0: performActionOnCell: function(action, index, column) { } michael@0: };