summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorstuartmorgan@chromium.org <stuartmorgan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-17 14:55:34 +0000
committerstuartmorgan@chromium.org <stuartmorgan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-17 14:55:34 +0000
commit2b88c1a7c83e8acee10e0b2c3cc40d3447d3fd59 (patch)
tree905a1fe5571d9c63219ae3903883239a27c548e1
parent93933868a50ee54cefbae87260565e0251a2d98e (diff)
downloadchromium_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
-rw-r--r--chrome/browser/dom_ui/search_engine_manager_handler.cc140
-rw-r--r--chrome/browser/dom_ui/search_engine_manager_handler.h27
-rw-r--r--chrome/browser/resources/options.html1
-rw-r--r--chrome/browser/resources/options/options_page.css8
-rw-r--r--chrome/browser/resources/options/search_engine_manager.css37
-rw-r--r--chrome/browser/resources/options/search_engine_manager.html20
-rw-r--r--chrome/browser/resources/options/search_engine_manager.js249
-rw-r--r--chrome/browser/resources/shared/js/cr/ui/list.js12
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 @@
&gt;
<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() {