diff options
author | stuartmorgan@chromium.org <stuartmorgan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-17 14:55:34 +0000 |
---|---|---|
committer | stuartmorgan@chromium.org <stuartmorgan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-17 14:55:34 +0000 |
commit | 2b88c1a7c83e8acee10e0b2c3cc40d3447d3fd59 (patch) | |
tree | 905a1fe5571d9c63219ae3903883239a27c548e1 | |
parent | 93933868a50ee54cefbae87260565e0251a2d98e (diff) | |
download | chromium_src-2b88c1a7c83e8acee10e0b2c3cc40d3447d3fd59.zip chromium_src-2b88c1a7c83e8acee10e0b2c3cc40d3447d3fd59.tar.gz chromium_src-2b88c1a7c83e8acee10e0b2c3cc40d3447d3fd59.tar.bz2 |
DOMUI prefs: Implement the non-editing parts of search engine management.
BUG=49091
TEST=Open DOMUI prefs, and click the "Manage..." button for search engines. Search engines should be listed, the default should be changeable, and entries should be removable.
Review URL: http://codereview.chromium.org/3191002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@56346 0039d316-1c4b-4281-b951-d872f2087c98
8 files changed, 489 insertions, 5 deletions
diff --git a/chrome/browser/dom_ui/search_engine_manager_handler.cc b/chrome/browser/dom_ui/search_engine_manager_handler.cc index 12ffe82..9b65346c 100644 --- a/chrome/browser/dom_ui/search_engine_manager_handler.cc +++ b/chrome/browser/dom_ui/search_engine_manager_handler.cc @@ -6,8 +6,14 @@ #include "app/l10n_util.h" #include "base/callback.h" +#include "base/singleton.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" #include "base/values.h" +#include "chrome/browser/dom_ui/dom_ui_favicon_source.h" #include "chrome/browser/profile.h" +#include "chrome/browser/search_engines/keyword_editor_controller.h" +#include "chrome/browser/search_engines/template_url_table_model.h" #include "chrome/common/url_constants.h" #include "grit/generated_resources.h" #include "grit/locale_settings.h" @@ -16,6 +22,21 @@ SearchEngineManagerHandler::SearchEngineManagerHandler() { } SearchEngineManagerHandler::~SearchEngineManagerHandler() { + if (controller_.get() && controller_->table_model()) + controller_->table_model()->SetObserver(NULL); +} + +void SearchEngineManagerHandler::Initialize() { + // Create our favicon data source. + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + NewRunnableMethod( + Singleton<ChromeURLDataManager>::get(), + &ChromeURLDataManager::AddDataSource, + make_scoped_refptr(new DOMUIFavIconSource(dom_ui_->GetProfile())))); + + controller_.reset(new KeywordEditorController(dom_ui_->GetProfile())); + controller_->table_model()->SetObserver(this); } void SearchEngineManagerHandler::GetLocalizedValues( @@ -24,7 +45,126 @@ void SearchEngineManagerHandler::GetLocalizedValues( localized_strings->SetString("searchEngineManagerPage", l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_WINDOW_TITLE)); + localized_strings->SetString("searchEngineTableNameHeader", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN)); + localized_strings->SetString("searchEngineTableKeywordHeader", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN)); + localized_strings->SetString("addSearchEngineButton", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_NEW_BUTTON)); + localized_strings->SetString("removeSearchEngineButton", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_REMOVE_BUTTON)); + localized_strings->SetString("editSearchEngineButton", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_EDIT_BUTTON)); + localized_strings->SetString("makeDefaultSearchEngineButton", + l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_MAKE_DEFAULT_BUTTON)); } void SearchEngineManagerHandler::RegisterMessages() { + dom_ui_->RegisterMessageCallback( + "managerSetDefaultSearchEngine", + NewCallback(this, &SearchEngineManagerHandler::SetDefaultSearchEngine)); + dom_ui_->RegisterMessageCallback( + "removeSearchEngine", + NewCallback(this, &SearchEngineManagerHandler::RemoveSearchEngine)); +} + +void SearchEngineManagerHandler::OnModelChanged() { + ListValue engine_list; + + // Find the default engine. + const TemplateURL* default_engine = + controller_->url_model()->GetDefaultSearchProvider(); + int default_index = controller_->table_model()->IndexOfTemplateURL( + default_engine); + + // Add the first group (default search engine options). + engine_list.Append(CreateDictionaryForHeading(0)); + int last_default_engine_index = + controller_->table_model()->last_search_engine_index(); + for (int i = 0; i < last_default_engine_index; ++i) { + engine_list.Append(CreateDictionaryForEngine(i, i == default_index)); + } + + // Add the second group (other search templates). + engine_list.Append(CreateDictionaryForHeading(1)); + if (last_default_engine_index < 0) + last_default_engine_index = 0; + int engine_count = controller_->table_model()->RowCount(); + for (int i = last_default_engine_index; i < engine_count; ++i) { + engine_list.Append(CreateDictionaryForEngine(i, i == default_index)); + } + + dom_ui_->CallJavascriptFunction(L"SearchEngineManager.updateSearchEngineList", + engine_list); +} + +void SearchEngineManagerHandler::OnItemsChanged(int start, int length) { + OnModelChanged(); +} + +void SearchEngineManagerHandler::OnItemsAdded(int start, int length) { + OnModelChanged(); +} + +void SearchEngineManagerHandler::OnItemsRemoved(int start, int length) { + OnModelChanged(); +} + +DictionaryValue* SearchEngineManagerHandler::CreateDictionaryForHeading( + int group_index) { + TableModel::Groups groups = controller_->table_model()->GetGroups(); + + DictionaryValue* dict = new DictionaryValue(); + dict->SetString("heading", WideToUTF16Hack(groups[group_index].title)); + return dict; +} + +DictionaryValue* SearchEngineManagerHandler::CreateDictionaryForEngine( + int index, bool is_default) { + TemplateURLTableModel* table_model = controller_->table_model(); + + DictionaryValue* dict = new DictionaryValue(); + dict->SetString("name", WideToUTF16Hack(table_model->GetText( + index, IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN))); + dict->SetString("keyword", WideToUTF16Hack(table_model->GetText( + index, IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN))); + const TemplateURL* template_url = controller_->GetTemplateURL(index); + GURL icon_url = template_url->GetFavIconURL(); + if (icon_url.is_valid()) + dict->SetString("iconURL", icon_url.spec()); + dict->SetString("modelIndex", base::IntToString(index)); + + if (controller_->CanRemove(template_url)) + dict->SetString("canBeRemoved", "1"); + if (controller_->CanMakeDefault(template_url)) + dict->SetString("canBeDefault", "1"); + if (is_default) + dict->SetString("default", "1"); + + return dict; +} + +void SearchEngineManagerHandler::SetDefaultSearchEngine(const Value* value) { + int index; + if (!ExtractIntegerValue(value, &index)) { + NOTREACHED(); + return; + } + if (index < 0 || index >= controller_->table_model()->RowCount()) + return; + + controller_->MakeDefaultTemplateURL(index); +} + +void SearchEngineManagerHandler::RemoveSearchEngine(const Value* value) { + int index; + if (!ExtractIntegerValue(value, &index)) { + NOTREACHED(); + return; + } + if (index < 0 || index >= controller_->table_model()->RowCount()) + return; + + if (controller_->CanRemove(controller_->GetTemplateURL(index))) + controller_->RemoveTemplateURL(index); } diff --git a/chrome/browser/dom_ui/search_engine_manager_handler.h b/chrome/browser/dom_ui/search_engine_manager_handler.h index 371554a..62012e0 100644 --- a/chrome/browser/dom_ui/search_engine_manager_handler.h +++ b/chrome/browser/dom_ui/search_engine_manager_handler.h @@ -5,19 +5,44 @@ #ifndef CHROME_BROWSER_DOM_UI_SEARCH_ENGINE_MANAGER_HANDLER_H_ #define CHROME_BROWSER_DOM_UI_SEARCH_ENGINE_MANAGER_HANDLER_H_ +#include "app/table_model_observer.h" #include "chrome/browser/dom_ui/options_ui.h" -class SearchEngineManagerHandler : public OptionsPageUIHandler { +class KeywordEditorController; + +class SearchEngineManagerHandler : public OptionsPageUIHandler, + public TableModelObserver { public: SearchEngineManagerHandler(); virtual ~SearchEngineManagerHandler(); + virtual void Initialize(); + // OptionsUIHandler implementation. virtual void GetLocalizedValues(DictionaryValue* localized_strings); + // TableModelObserver implementation. + virtual void OnModelChanged(); + virtual void OnItemsChanged(int start, int length); + virtual void OnItemsAdded(int start, int length); + virtual void OnItemsRemoved(int start, int length); + virtual void RegisterMessages(); private: + scoped_ptr<KeywordEditorController> controller_; + + // Removes the search engine at the given index. Called from DOMUI. + void RemoveSearchEngine(const Value* value); + + // Sets the search engine at the given index to be default. Called from DOMUI. + void SetDefaultSearchEngine(const Value* value); + + // Returns a dictionary to pass to DOMUI representing the given group heading. + DictionaryValue* CreateDictionaryForHeading(int group_index); + // Returns a dictionary to pass to DOMUI representing the given search engine. + DictionaryValue* CreateDictionaryForEngine(int index, bool is_default); + DISALLOW_COPY_AND_ASSIGN(SearchEngineManagerHandler); }; diff --git a/chrome/browser/resources/options.html b/chrome/browser/resources/options.html index 5dfe1b7..75f8766 100644 --- a/chrome/browser/resources/options.html +++ b/chrome/browser/resources/options.html @@ -175,6 +175,7 @@ window.onpopstate = function(e) { <link rel="stylesheet" href="options/content_settings_exceptions_area.css"> <link rel="stylesheet" href="options/passwords_exceptions_list.css"> <link rel="stylesheet" href="options/import_data_overlay.css"> +<link rel="stylesheet" href="options/search_engine_manager.css"> <link rel="stylesheet" href="options/subpages_tab_controls.css"> <if expr="pp_ifdef('chromeos')"> <link rel="stylesheet" href="options/chromeos_accounts_options_page.css"> diff --git a/chrome/browser/resources/options/options_page.css b/chrome/browser/resources/options/options_page.css index e5357d9..fde15d2 100644 --- a/chrome/browser/resources/options/options_page.css +++ b/chrome/browser/resources/options/options_page.css @@ -224,3 +224,11 @@ input[type="checkbox"] { #contentSettingsPage :invalid { background-color: pink; } + +.left-side-table { + display: -webkit-box; +} + +.left-side-table > div:first-child { + -webkit-box-flex: 1; +} diff --git a/chrome/browser/resources/options/search_engine_manager.css b/chrome/browser/resources/options/search_engine_manager.css new file mode 100644 index 0000000..6061079 --- /dev/null +++ b/chrome/browser/resources/options/search_engine_manager.css @@ -0,0 +1,37 @@ +#searchEngineList > div, #searchEngineHeading { + display: -webkit-box; +} + +#searchEngineManagerPage .name { + -webkit-box-sizing: border-box; + width: 50%; +} + +#searchEngineManagerPage .keyword { + -webkit-box-flex: 1; +} + +#searchEngineList .name { + background-position: left; + background-repeat: no-repeat; + padding-left: 18px; +} + +#searchEngineList .keyword { + color: #666666; +} + +#searchEngineList .default { + font-weight: bold; +} + +#searchEngineHeading { + font-weight: bold; + padding: 4px; + border-bottom: solid 1px black; + border-top: 1px solid black; +} + +.left-side-table :last-child button { + width: 100%; +} diff --git a/chrome/browser/resources/options/search_engine_manager.html b/chrome/browser/resources/options/search_engine_manager.html index 1bcf35a..44e99ab 100644 --- a/chrome/browser/resources/options/search_engine_manager.html +++ b/chrome/browser/resources/options/search_engine_manager.html @@ -4,4 +4,24 @@ > <span i18n-content="searchEngineManagerPage"></span> </h1> + <div class="left-side-table"> + <div> + <div id="searchEngineHeading"> + <div class="name" i18n-content="searchEngineTableNameHeader"></div> + <div class="keyword" + i18n-content="searchEngineTableKeywordHeader"></div> + </div> + <list id="searchEngineList"></list> + </div> + <div> + <div><button id="addSearchEngineButton" disabled + i18n-content="addSearchEngineButton"></div> + <div><button id="removeSearchEngineButton" disabled + i18n-content="removeSearchEngineButton"></div> + <div><button id="editSearchEngineButton" disabled + i18n-content="editSearchEngineButton"></div> + <div><button id="makeDefaultSearchEngineButton" disabled + i18n-content="makeDefaultSearchEngineButton"></div> + </div> + </div> </div> diff --git a/chrome/browser/resources/options/search_engine_manager.js b/chrome/browser/resources/options/search_engine_manager.js index f24859b..61374cf 100644 --- a/chrome/browser/resources/options/search_engine_manager.js +++ b/chrome/browser/resources/options/search_engine_manager.js @@ -3,8 +3,179 @@ // found in the LICENSE file. cr.define('options', function() { + const OptionsPage = options.OptionsPage; + const ArrayDataModel = cr.ui.ArrayDataModel; + const List = cr.ui.List; + const ListItem = cr.ui.ListItem; + const ListSelectionController = cr.ui.ListSelectionController; + const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; - var OptionsPage = options.OptionsPage; + ///////////////////////////////////////////////////////////////////////////// + // SearchEngineList class: + + /** + * Creates a new search engine list item. + * @param {Object} searchEnigne The search engine this represents. + * @constructor + * @extends {cr.ui.ListItem} + */ + function SearchEngineListItem(searchEngine) { + var el = cr.doc.createElement('div'); + el.searchEngine_ = searchEngine; + SearchEngineListItem.decorate(el); + return el; + } + + /** + * Decorates an element as a search engine list item. + * @param {!HTMLElement} el The element to decorate. + */ + SearchEngineListItem.decorate = function(el) { + el.__proto__ = SearchEngineListItem.prototype; + el.decorate(); + }; + + SearchEngineListItem.prototype = { + __proto__: ListItem.prototype, + + /** @inheritDoc */ + decorate: function() { + ListItem.prototype.decorate.call(this); + + var engine = this.searchEngine_; + if (engine['heading']) { + var titleEl = this.ownerDocument.createElement('div'); + titleEl.className = 'heading'; + titleEl.textContent = engine['heading']; + this.appendChild(titleEl); + } else { + var nameEl = this.ownerDocument.createElement('div'); + nameEl.className = 'name'; + nameEl.textContent = engine['name']; + nameEl.style.backgroundImage = url('chrome://favicon/iconurl/' + + engine['iconURL']); + + var keywordEl = this.ownerDocument.createElement('div'); + keywordEl.className = 'keyword'; + keywordEl.textContent = engine['keyword']; + + this.appendChild(nameEl); + this.appendChild(keywordEl); + + if (engine['default']) + this.classList.add('default'); + } + }, + }; + + /** + * Creates a selection controller with a delegate that controls whether or + * not individual items are selectable. This is used for lists containing + * subgroups with headings that are in the list, since the headers themselves + * should not be selectable. + * + * @param {cr.ui.ListSelectionModel} selectionModel The selection model to + * interact with. + * @param {*} selectabilityDelegate A delegate responding to + * canSelectIndex(index). + * + * @constructor + * @extends {!cr.ui.ListSelectionController} + */ + function InlineHeaderSelectionController(selectionModel, + selectabilityDelegate) { + ListSelectionController.call(this, selectionModel); + this.selectabilityDelegate_ = selectabilityDelegate; + } + + InlineHeaderSelectionController.prototype = { + __proto__: ListSelectionController.prototype, + + /** @inheritDoc */ + getIndexBelow: function(index) { + var next = ListSelectionController.prototype.getIndexBelow.call(this, + index); + if (next == -1 || this.canSelect(next)) + return next; + return this.getIndexBelow(next); + }, + + /** @inheritDoc */ + getNextIndex: function(index) { + return this.getIndexBelow(index); + }, + + /** @inheritDoc */ + getIndexAbove: function(index) { + var previous = ListSelectionController.prototype.getIndexAbove.call( + this, index); + if (previous == -1 || this.canSelect(previous)) + return previous; + return this.getIndexAbove(previous); + }, + + /** @inheritDoc */ + getPreviousIndex: function(index) { + return this.getIndexAbove(index); + }, + + /** @inheritDoc */ + getFirstIndex: function(index) { + var first = ListSelectionController.prototype.getFirstIndex.call(this); + if (this.canSelect(first)) + return first; + return this.getNextIndex(first); + }, + + /** @inheritDoc */ + getLastIndex: function(index) { + var last = ListSelectionController.prototype.getLastIndex.call(this); + if (this.canSelect(last)) + return last; + return this.getPreviousIndex(last); + }, + + /** @inheritDoc */ + handleMouseDownUp: function(e, index) { + if (this.canSelect(index)) { + ListSelectionController.prototype.handleMouseDownUp.call( + this, e, index); + } + }, + + /** + * Returns true if the given index is selectable. + * @private + * @param {number} index The index to check. + */ + canSelect: function(index) { + return this.selectabilityDelegate_.canSelectIndex(index); + } + }; + + var SearchEngineList = cr.ui.define('list'); + + SearchEngineList.prototype = { + __proto__: List.prototype, + + /** @inheritDoc */ + createItem: function(searchEngine) { + return new SearchEngineListItem(searchEngine); + }, + + /** @inheritDoc */ + createSelectionController: function(sm) { + return new InlineHeaderSelectionController(sm, this); + }, + + /** + * Returns true if the given item is selectable. + * @param {number} index The index to check. + */ + canSelectIndex: function(index) { + return !this.dataModel.item(index).hasOwnProperty('heading'); + } + }; ///////////////////////////////////////////////////////////////////////////// // SearchEngineManager class: @@ -24,12 +195,84 @@ cr.define('options', function() { SearchEngineManager.prototype = { __proto__: OptionsPage.prototype, + list_: null, initializePage: function() { OptionsPage.prototype.initializePage.call(this); - // TODO(stuartmorgan): Add initialization here. - } + this.list_ = $('searchEngineList') + SearchEngineList.decorate(this.list_); + var selectionModel = new ListSingleSelectionModel + this.list_.selectionModel = selectionModel; + + selectionModel.addEventListener('change', + cr.bind(this.selectionChanged_, this)); + + var self = this; + $('addSearchEngineButton').onclick = function(event) { + // TODO(stuartmorgan): Show an overlay to edit the new search engine. + }; + $('removeSearchEngineButton').onclick = function(event) { + chrome.send('removeSearchEngine', [self.selectedModelIndex_]); + }; + $('editSearchEngineButton').onclick = function(event) { + // TODO(stuartmorgan): Show an overlay to edit the selected + // search engine. + }; + $('makeDefaultSearchEngineButton').onclick = function(event) { + chrome.send('managerSetDefaultSearchEngine', + [self.selectedModelIndex_]); + }; + + // Remove Windows-style accelerators from button labels. + // TODO(stuartmorgan): Remove this once the strings are updated. + $('addSearchEngineButton').textContent = + localStrings.getStringWithoutAccelerator('addSearchEngineButton'); + $('removeSearchEngineButton').textContent = + localStrings.getStringWithoutAccelerator('removeSearchEngineButton'); + + this.addEventListener('visibleChange', function(event) { + $('searchEngineList').redraw(); + }); + }, + + /** + * Updates the search engine list with the given entries. + * @private + * @param {Array} engineList List of available search engines. + */ + updateSearchEngineList_: function(engineList) { + this.list_.dataModel = new ArrayDataModel(engineList); + }, + + /** + * Returns the currently selected list item's underlying model index. + * @private + */ + get selectedModelIndex_() { + var listIndex = this.list_.selectionModel.selectedIndex; + return this.list_.dataModel.item(listIndex)['modelIndex']; + }, + + /** + * Callback from the selection model when the selection changes. + * @private + * @param {!cr.Event} e Event with change info. + */ + selectionChanged_: function(e) { + var selectedIndex = this.list_.selectionModel.selectedIndex; + var engine = selectedIndex != -1 ? + this.list_.dataModel.item(selectedIndex) : null; + + $('removeSearchEngineButton').disabled = + !(engine && engine['canBeRemoved']); + $('makeDefaultSearchEngineButton').disabled = + !(engine && engine['canBeDefault']); + }, + }; + + SearchEngineManager.updateSearchEngineList = function(engineList) { + SearchEngineManager.getInstance().updateSearchEngineList_(engineList); }; // Export diff --git a/chrome/browser/resources/shared/js/cr/ui/list.js b/chrome/browser/resources/shared/js/cr/ui/list.js index 9ee1cbb..234d8db 100644 --- a/chrome/browser/resources/shared/js/cr/ui/list.js +++ b/chrome/browser/resources/shared/js/cr/ui/list.js @@ -150,7 +150,7 @@ cr.define('cr.ui', function() { } this.selectionModel_ = sm; - this.selectionController_ = new ListSelectionController(sm); + this.selectionController_ = this.createSelectionController(sm); if (sm) { sm.addEventListener('change', this.boundHandleOnChange_); @@ -420,6 +420,16 @@ cr.define('cr.ui', function() { }, /** + * Creates the selection controller to use internally. + * @param {cr.ui.ListSelectionModel} sm The underlying selection model. + * @return {!cr.ui.ListSelectionModel} The newly created selection + * controller. + */ + createSelectionController: function(sm) { + return new ListSelectionController(sm); + }, + + /** * Redraws the viewport. */ redraw: function() { |