1 # ***** BEGIN LICENSE BLOCK *****
2 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 # The contents of this file are subject to the Mozilla Public License Version
5 # 1.1 (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
7 # http://www.mozilla.org/MPL/
9 # Software distributed under the License is distributed on an "AS IS" basis,
10 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 # for the specific language governing rights and limitations under the
14 # The Original Code is the Browser Search Service.
16 # The Initial Developer of the Original Code is
18 # Portions created by the Initial Developer are Copyright (C) 2005
19 # the Initial Developer. All Rights Reserved.
22 # Ben Goodger <beng@google.com> (Original author)
23 # Gavin Sharp <gavin@gavinsharp.com>
24 # Pamela Greene <pamg.bugs@gmail.com>
25 # Ryan Flint <rflint@dslr.net>
27 # Alternatively, the contents of this file may be used under the terms of
28 # either the GNU General Public License Version 2 or later (the "GPL"), or
29 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 # in which case the provisions of the GPL or the LGPL are applicable instead
31 # of those above. If you wish to allow use of your version of this file only
32 # under the terms of either the GPL or the LGPL, and not to allow others to
33 # use your version of this file under the terms of the MPL, indicate your
34 # decision by deleting the provisions above and replace them with the notice
35 # and other provisions required by the GPL or the LGPL. If you do not delete
36 # the provisions above, a recipient may use your version of this file under
37 # the terms of any one of the MPL, the GPL or the LGPL.
39 # ***** END LICENSE BLOCK *****
41 const Ci = Components.interfaces;
42 const Cc = Components.classes;
44 const ENGINE_FLAVOR = "text/x-moz-search-engine";
46 const BROWSER_SUGGEST_PREF = "browser.search.suggest.enabled";
48 var gEngineView = null;
50 var gEngineManagerDialog = {
51 init: function engineManager_init() {
52 gEngineView = new EngineView(new EngineStore());
54 var prefService = Cc["@mozilla.org/preferences-service;1"].
55 getService(Ci.nsIPrefBranch);
56 var suggestEnabled = prefService.getBoolPref(BROWSER_SUGGEST_PREF);
57 document.getElementById("enableSuggest").checked = suggestEnabled;
59 var tree = document.getElementById("engineList");
60 tree.view = gEngineView;
62 var os = Cc["@mozilla.org/observer-service;1"].
63 getService(Ci.nsIObserverService);
64 os.addObserver(this, "browser-search-engine-modified", false);
67 observe: function engineManager_observe(aEngine, aTopic, aVerb) {
68 if (aTopic == "browser-search-engine-modified") {
69 aEngine.QueryInterface(Ci.nsISearchEngine)
72 gEngineView._engineStore.addEngine(aEngine);
73 gEngineView.rowCountChanged(gEngineView.lastIndex, 1);
75 case "engine-changed":
76 gEngineView._engineStore.reloadIcons();
78 case "engine-removed":
79 case "engine-current":
83 gEngineView.invalidate();
87 onOK: function engineManager_onOK() {
88 // Remove the observer
89 var os = Cc["@mozilla.org/observer-service;1"].
90 getService(Ci.nsIObserverService);
91 os.removeObserver(this, "browser-search-engine-modified");
94 var newSuggestEnabled = document.getElementById("enableSuggest").checked;
95 var prefService = Cc["@mozilla.org/preferences-service;1"].
96 getService(Ci.nsIPrefBranch);
97 prefService.setBoolPref(BROWSER_SUGGEST_PREF, newSuggestEnabled);
100 gEngineView._engineStore.commit();
103 onCancel: function engineManager_onCancel() {
104 // Remove the observer
105 var os = Cc["@mozilla.org/observer-service;1"].
106 getService(Ci.nsIObserverService);
107 os.removeObserver(this, "browser-search-engine-modified");
110 onRestoreDefaults: function engineManager_onRestoreDefaults() {
111 var num = gEngineView._engineStore.restoreDefaultEngines();
112 gEngineView.rowCountChanged(0, num);
113 gEngineView.invalidate();
116 showRestoreDefaults: function engineManager_showRestoreDefaults(val) {
117 document.documentElement.getButton("extra2").disabled = !val;
120 loadAddEngines: function engineManager_loadAddEngines() {
122 window.opener.BrowserSearch.loadAddEngines();
126 remove: function engineManager_remove() {
127 gEngineView._engineStore.removeEngine(gEngineView.selectedEngine);
128 var index = gEngineView.selectedIndex;
129 gEngineView.rowCountChanged(index, -1);
130 gEngineView.invalidate();
131 gEngineView.selection.select(Math.min(index, gEngineView.lastIndex));
132 gEngineView.ensureRowIsVisible(Math.min(index, gEngineView.lastIndex));
133 document.getElementById("engineList").focus();
137 * Moves the selected engine either up or down in the engine list
139 * -1 to move the selected engine down, +1 to move it up.
141 bump: function engineManager_move(aDir) {
142 var selectedEngine = gEngineView.selectedEngine;
143 var newIndex = gEngineView.selectedIndex - aDir;
145 gEngineView._engineStore.moveEngine(selectedEngine, newIndex);
147 gEngineView.invalidate();
148 gEngineView.selection.select(newIndex);
149 gEngineView.ensureRowIsVisible(newIndex);
150 this.showRestoreDefaults(true);
151 document.getElementById("engineList").focus();
154 editKeyword: function engineManager_editKeyword() {
155 var selectedEngine = gEngineView.selectedEngine;
159 var prompt = Cc["@mozilla.org/embedcomp/prompt-service;1"].
160 getService(Ci.nsIPromptService);
161 var alias = { value: selectedEngine.alias };
162 var strings = document.getElementById("engineManagerBundle");
163 var title = strings.getString("editTitle");
164 var msg = strings.getFormattedString("editMsg", [selectedEngine.name]);
166 while (prompt.prompt(window, title, msg, alias, null, { })) {
167 var bduplicate = false;
168 var eduplicate = false;
170 if (alias.value != "") {
172 let bmserv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
173 getService(Ci.nsINavBookmarksService);
174 if (bmserv.getURIForKeyword(alias.value))
178 // Check for duplicates in changes we haven't committed yet
179 let engines = gEngineView._engineStore.engines;
180 for each (let engine in engines) {
181 if (engine.alias == alias.value &&
182 engine.name != selectedEngine.name) {
189 // Notify the user if they have chosen an existing engine/bookmark keyword
190 if (eduplicate || bduplicate) {
191 var dtitle = strings.getString("duplicateTitle");
192 var bmsg = strings.getString("duplicateBookmarkMsg");
193 var emsg = strings.getFormattedString("duplicateEngineMsg",
196 prompt.alert(window, dtitle, (eduplicate) ? emsg : bmsg);
198 gEngineView._engineStore.changeEngine(selectedEngine, "alias",
200 gEngineView.invalidate();
206 onSelect: function engineManager_onSelect() {
207 // buttons only work if an engine is selected and it's not the last engine
208 var disableButtons = (gEngineView.selectedIndex == -1) ||
209 (gEngineView.lastIndex == 0);
210 var lastSelected = (gEngineView.selectedIndex == gEngineView.lastIndex);
211 var firstSelected = (gEngineView.selectedIndex == 0);
212 var noSelection = (gEngineView.selectedIndex == -1);
214 document.getElementById("cmd_remove").setAttribute("disabled",
217 document.getElementById("cmd_moveup").setAttribute("disabled",
218 disableButtons || firstSelected);
220 document.getElementById("cmd_movedown").setAttribute("disabled",
221 disableButtons || lastSelected);
222 document.getElementById("cmd_editkeyword").setAttribute("disabled",
227 var gDragObserver = {
228 onDragStart: function (aEvent, aXferData, aDragAction) {
229 var selectedIndex = gEngineView.selectedIndex;
230 if (selectedIndex == -1)
233 aXferData.data = new TransferData();
234 aXferData.data.addDataForFlavour(ENGINE_FLAVOR, selectedIndex.toString());
236 aDragAction.action = Ci.nsIDragService.DRAGDROP_ACTION_MOVE;
238 onDrop: function (aEvent, aXferData, aDragSession) { },
239 onDragExit: function (aEvent, aDragSession) { },
240 onDragOver: function (aEvent, aFlavour, aDragSession) { },
241 getSupportedFlavours: function() { return null; }
244 // "Operation" objects
245 function EngineMoveOp(aEngineClone, aNewIndex) {
247 throw new Error("bad args to new EngineMoveOp!");
248 this._engine = aEngineClone.originalEngine;
249 this._newIndex = aNewIndex;
251 EngineMoveOp.prototype = {
254 commit: function EMO_commit() {
255 var searchService = Cc["@mozilla.org/browser/search-service;1"].
256 getService(Ci.nsIBrowserSearchService);
257 searchService.moveEngine(this._engine, this._newIndex);
261 function EngineRemoveOp(aEngineClone) {
263 throw new Error("bad args to new EngineRemoveOp!");
264 this._engine = aEngineClone.originalEngine;
266 EngineRemoveOp.prototype = {
268 commit: function ERO_commit() {
269 var searchService = Cc["@mozilla.org/browser/search-service;1"].
270 getService(Ci.nsIBrowserSearchService);
271 searchService.removeEngine(this._engine);
275 function EngineUnhideOp(aEngineClone, aNewIndex) {
277 throw new Error("bad args to new EngineUnhideOp!");
278 this._engine = aEngineClone.originalEngine;
279 this._newIndex = aNewIndex;
281 EngineUnhideOp.prototype = {
284 commit: function EUO_commit() {
285 this._engine.hidden = false;
286 var searchService = Cc["@mozilla.org/browser/search-service;1"].
287 getService(Ci.nsIBrowserSearchService);
288 searchService.moveEngine(this._engine, this._newIndex);
292 function EngineChangeOp(aEngineClone, aProp, aValue) {
294 throw new Error("bad args to new EngineChangeOp!");
296 this._engine = aEngineClone.originalEngine;
298 this._newValue = aValue;
300 EngineChangeOp.prototype = {
304 commit: function ECO_commit() {
305 this._engine[this._prop] = this._newValue;
309 function EngineStore() {
310 var searchService = Cc["@mozilla.org/browser/search-service;1"].
311 getService(Ci.nsIBrowserSearchService);
312 this._engines = searchService.getVisibleEngines({}).map(this._cloneEngine);
313 this._defaultEngines = searchService.getDefaultEngines({}).map(this._cloneEngine);
317 // check if we need to disable the restore defaults button
318 var someHidden = this._defaultEngines.some(function (e) {return e.hidden;});
319 gEngineManagerDialog.showRestoreDefaults(someHidden);
321 EngineStore.prototype = {
323 _defaultEngines: null,
327 return this._engines;
334 _getIndexForEngine: function ES_getIndexForEngine(aEngine) {
335 return this._engines.indexOf(aEngine);
338 _getEngineByName: function ES_getEngineByName(aName) {
339 for each (var engine in this._engines)
340 if (engine.name == aName)
346 _cloneEngine: function ES_cloneObj(aEngine) {
348 for (var i in aEngine)
349 newO[i] = aEngine[i];
350 newO.originalEngine = aEngine;
354 // Callback for Array's some(). A thisObj must be passed to some()
355 _isSameEngine: function ES_isSameEngine(aEngineClone) {
356 return aEngineClone.originalEngine == this.originalEngine;
359 commit: function ES_commit() {
360 var searchService = Cc["@mozilla.org/browser/search-service;1"].
361 getService(Ci.nsIBrowserSearchService);
362 var currentEngine = this._cloneEngine(searchService.currentEngine);
363 for (var i = 0; i < this._ops.length; i++)
364 this._ops[i].commit();
366 // Restore currentEngine if it is a default engine that is still visible.
367 // Needed if the user deletes currentEngine and then restores it.
368 if (this._defaultEngines.some(this._isSameEngine, currentEngine) &&
369 !currentEngine.originalEngine.hidden)
370 searchService.currentEngine = currentEngine.originalEngine;
373 addEngine: function ES_addEngine(aEngine) {
374 this._engines.push(this._cloneEngine(aEngine));
377 moveEngine: function ES_moveEngine(aEngine, aNewIndex) {
378 if (aNewIndex < 0 || aNewIndex > this._engines.length - 1)
379 throw new Error("ES_moveEngine: invalid aNewIndex!");
380 var index = this._getIndexForEngine(aEngine);
382 throw new Error("ES_moveEngine: invalid engine?");
384 if (index == aNewIndex)
385 return; // nothing to do
387 // Move the engine in our internal store
388 var removedEngine = this._engines.splice(index, 1)[0];
389 this._engines.splice(aNewIndex, 0, removedEngine);
391 this._ops.push(new EngineMoveOp(aEngine, aNewIndex));
394 removeEngine: function ES_removeEngine(aEngine) {
395 var index = this._getIndexForEngine(aEngine);
397 throw new Error("invalid engine?");
399 this._engines.splice(index, 1);
400 this._ops.push(new EngineRemoveOp(aEngine));
401 if (this._defaultEngines.some(this._isSameEngine, aEngine))
402 gEngineManagerDialog.showRestoreDefaults(true);
405 restoreDefaultEngines: function ES_restoreDefaultEngines() {
408 for (var i = 0; i < this._defaultEngines.length; ++i) {
409 var e = this._defaultEngines[i];
411 // If the engine is already in the list, just move it.
412 if (this._engines.some(this._isSameEngine, e)) {
413 this.moveEngine(this._getEngineByName(e.name), i);
415 // Otherwise, add it back to our internal store
416 this._engines.splice(i, 0, e);
417 this._ops.push(new EngineUnhideOp(e, i));
421 gEngineManagerDialog.showRestoreDefaults(false);
425 changeEngine: function ES_changeEngine(aEngine, aProp, aNewValue) {
426 var index = this._getIndexForEngine(aEngine);
428 throw new Error("invalid engine?");
430 this._engines[index][aProp] = aNewValue;
431 this._ops.push(new EngineChangeOp(aEngine, aProp, aNewValue));
434 reloadIcons: function ES_reloadIcons() {
435 this._engines.forEach(function (e) {
436 e.uri = e.originalEngine.uri;
441 function EngineView(aEngineStore) {
442 this._engineStore = aEngineStore;
444 EngineView.prototype = {
449 return this.rowCount - 1;
451 get selectedIndex() {
452 var seln = this.selection;
453 if (seln.getRangeCount() > 0) {
455 seln.getRangeAt(0, min, { });
460 get selectedEngine() {
461 return this._engineStore.engines[this.selectedIndex];
465 rowCountChanged: function (index, count) {
466 this.tree.rowCountChanged(index, count);
469 invalidate: function () {
470 this.tree.invalidate();
473 ensureRowIsVisible: function (index) {
474 this.tree.ensureRowIsVisible(index);
477 getSourceIndexFromDrag: function () {
478 var dragService = Cc["@mozilla.org/widget/dragservice;1"].
479 getService().QueryInterface(Ci.nsIDragService);
480 var dragSession = dragService.getCurrentSession();
481 var transfer = Cc["@mozilla.org/widget/transferable;1"].
482 createInstance(Ci.nsITransferable);
484 transfer.addDataFlavor(ENGINE_FLAVOR);
485 dragSession.getData(transfer, 0);
489 var sourceIndex = -1;
491 transfer.getAnyTransferData({}, dataObj, len);
495 sourceIndex = dataObj.value.QueryInterface(Ci.nsISupportsString).data;
496 sourceIndex = parseInt(sourceIndex.substring(0, len.value));
504 return this._engineStore.engines.length;
507 getImageSrc: function(index, column) {
508 if (column.id == "engineName" && this._engineStore.engines[index].iconURI)
509 return this._engineStore.engines[index].iconURI.spec;
513 getCellText: function(index, column) {
514 if (column.id == "engineName")
515 return this._engineStore.engines[index].name;
516 else if (column.id == "engineKeyword")
517 return this._engineStore.engines[index].alias;
521 setTree: function(tree) {
525 canDrop: function(targetIndex, orientation) {
526 var sourceIndex = this.getSourceIndexFromDrag();
527 return (sourceIndex != -1 &&
528 sourceIndex != targetIndex &&
529 sourceIndex != (targetIndex + orientation));
532 drop: function(dropIndex, orientation) {
533 var sourceIndex = this.getSourceIndexFromDrag();
534 var sourceEngine = this._engineStore.engines[sourceIndex];
536 if (dropIndex > sourceIndex) {
537 if (orientation == Ci.nsITreeView.DROP_BEFORE)
540 if (orientation == Ci.nsITreeView.DROP_AFTER)
544 this._engineStore.moveEngine(sourceEngine, dropIndex);
545 gEngineManagerDialog.showRestoreDefaults(true);
547 // Redraw, and adjust selection
549 this.selection.clearSelection();
550 this.selection.select(dropIndex);
554 getRowProperties: function(index, properties) { },
555 getCellProperties: function(index, column, properties) { },
556 getColumnProperties: function(column, properties) { },
557 isContainer: function(index) { return false; },
558 isContainerOpen: function(index) { return false; },
559 isContainerEmpty: function(index) { return false; },
560 isSeparator: function(index) { return false; },
561 isSorted: function(index) { return false; },
562 getParentIndex: function(index) { return -1; },
563 hasNextSibling: function(parentIndex, index) { return false; },
564 getLevel: function(index) { return 0; },
565 getProgressMode: function(index, column) { },
566 getCellValue: function(index, column) { },
567 toggleOpenState: function(index) { },
568 cycleHeader: function(column) { },
569 selectionChanged: function() { },
570 cycleCell: function(row, column) { },
571 isEditable: function(index, column) { return false; },
572 isSelectable: function(index, column) { return false; },
573 setCellValue: function(index, column, value) { },
574 setCellText: function(index, column, value) { },
575 performAction: function(action) { },
576 performActionOnRow: function(action, index) { },
577 performActionOnCell: function(action, index, column) { }