summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/host_content_settings_map.cc4
-rw-r--r--chrome/browser/plugin_exceptions_table_model.cc197
-rw-r--r--chrome/browser/plugin_exceptions_table_model.h78
-rw-r--r--chrome/browser/plugin_exceptions_table_model_unittest.cc190
-rw-r--r--chrome/browser/views/options/content_filter_page_view.cc24
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--views/controls/table/table_view.cc38
-rw-r--r--views/controls/table/table_view.h3
9 files changed, 516 insertions, 21 deletions
diff --git a/chrome/browser/host_content_settings_map.cc b/chrome/browser/host_content_settings_map.cc
index 9c01798..c018219 100644
--- a/chrome/browser/host_content_settings_map.cc
+++ b/chrome/browser/host_content_settings_map.cc
@@ -489,8 +489,8 @@ void HostContentSettingsMap::SetContentSetting(
ContentSetting setting) {
DCHECK(kTypeNames[content_type] != NULL); // Don't call this for Geolocation.
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
- DCHECK(RequiresResourceIdentifier(content_type) !=
- resource_identifier.empty());
+ DCHECK_NE(RequiresResourceIdentifier(content_type),
+ resource_identifier.empty());
bool early_exit = false;
std::string pattern_str(pattern.AsString());
diff --git a/chrome/browser/plugin_exceptions_table_model.cc b/chrome/browser/plugin_exceptions_table_model.cc
new file mode 100644
index 0000000..f610432
--- /dev/null
+++ b/chrome/browser/plugin_exceptions_table_model.cc
@@ -0,0 +1,197 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/plugin_exceptions_table_model.h"
+
+#include "app/l10n_util.h"
+#include "app/table_model_observer.h"
+#include "base/auto_reset.h"
+#include "base/sys_string_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/notification_service.h"
+#include "grit/generated_resources.h"
+#include "webkit/glue/plugins/plugin_list.h"
+
+PluginExceptionsTableModel::PluginExceptionsTableModel(
+ HostContentSettingsMap* content_settings_map,
+ HostContentSettingsMap* otr_content_settings_map)
+ : map_(content_settings_map),
+ otr_map_(otr_content_settings_map),
+ updates_disabled_(false),
+ observer_(NULL) {
+ registrar_.Add(this, NotificationType::CONTENT_SETTINGS_CHANGED,
+ NotificationService::AllSources());
+}
+
+bool PluginExceptionsTableModel::CanRemoveRows(const Rows& rows) const {
+ return !rows.empty();
+}
+
+void PluginExceptionsTableModel::RemoveRows(const Rows& rows) {
+ AutoReset<bool> tmp(&updates_disabled_, true);
+ bool reload_all = false;
+ // Iterate in reverse over the rows to get the indexes right.
+ for (Rows::const_reverse_iterator it = rows.rbegin();
+ it != rows.rend(); ++it) {
+ SettingsEntry& entry = settings_[*it];
+ HostContentSettingsMap* map = entry.is_otr ? otr_map_ : map_;
+ map->SetContentSetting(entry.pattern,
+ CONTENT_SETTINGS_TYPE_PLUGINS,
+ resources_[entry.plugin_id],
+ CONTENT_SETTING_DEFAULT);
+ row_counts_[entry.plugin_id]--;
+ // If we remove the last exception for a plugin, recreate all groups to get
+ // correct IDs.
+ if (row_counts_[entry.plugin_id] == 0)
+ reload_all = true;
+ settings_.erase(settings_.begin() + *it);
+ }
+ if (reload_all) {
+ // This also notifies the observer.
+ ReloadSettings();
+ } else if (observer_) {
+ for (Rows::const_reverse_iterator it = rows.rbegin();
+ it != rows.rend(); ++it) {
+ observer_->OnItemsRemoved(*it, 1);
+ }
+ }
+}
+
+void PluginExceptionsTableModel::RemoveAll() {
+ int old_row_count = RowCount();
+ {
+ AutoReset<bool> tmp(&updates_disabled_, true);
+ map_->ClearSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS);
+ if (otr_map_)
+ otr_map_->ClearSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS);
+ }
+ ClearSettings();
+ if (observer_)
+ observer_->OnItemsRemoved(0, old_row_count);
+}
+
+int PluginExceptionsTableModel::RowCount() {
+ return settings_.size();
+}
+
+std::wstring PluginExceptionsTableModel::GetText(int row, int column_id) {
+ DCHECK_GE(row, 0);
+ DCHECK_LT(row, static_cast<int>(settings_.size()));
+ SettingsEntry& entry = settings_[row];
+ switch (column_id) {
+ case IDS_EXCEPTIONS_PATTERN_HEADER:
+ case IDS_EXCEPTIONS_HOSTNAME_HEADER:
+ return UTF8ToWide(entry.pattern.AsString());
+
+ case IDS_EXCEPTIONS_ACTION_HEADER:
+ switch (entry.setting) {
+ case CONTENT_SETTING_ALLOW:
+ return l10n_util::GetString(IDS_EXCEPTIONS_ALLOW_BUTTON);
+ case CONTENT_SETTING_BLOCK:
+ return l10n_util::GetString(IDS_EXCEPTIONS_BLOCK_BUTTON);
+ default:
+ NOTREACHED();
+ }
+ break;
+
+ default:
+ NOTREACHED();
+ }
+
+ return std::wstring();
+}
+
+void PluginExceptionsTableModel::SetObserver(TableModelObserver* observer) {
+ observer_ = observer;
+}
+
+TableModel::Groups PluginExceptionsTableModel::GetGroups() {
+ return groups_;
+}
+
+int PluginExceptionsTableModel::GetGroupID(int row) {
+ DCHECK_LT(row, static_cast<int>(settings_.size()));
+ return settings_[row].plugin_id;
+}
+
+void PluginExceptionsTableModel::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ if (!updates_disabled_)
+ ReloadSettings();
+}
+
+void PluginExceptionsTableModel::ClearSettings() {
+ settings_.clear();
+ groups_.clear();
+ row_counts_.clear();
+ resources_.clear();
+}
+
+void PluginExceptionsTableModel::GetPlugins(
+ std::vector<WebPluginInfo>* plugins) {
+ NPAPI::PluginList::Singleton()->GetPlugins(false, plugins);
+}
+
+void PluginExceptionsTableModel::LoadSettings() {
+ int group_id = 0;
+ std::vector<WebPluginInfo> plugins;
+ GetPlugins(&plugins);
+ for (std::vector<WebPluginInfo>::iterator it = plugins.begin();
+ it != plugins.end(); ++it) {
+#if defined OS_POSIX
+ std::string plugin = it->path.value();
+#elif defined OS_WIN
+ std::string plugin = base::SysWideToUTF8(it->path.value());
+#endif
+ HostContentSettingsMap::SettingsForOneType settings;
+ map_->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS,
+ plugin,
+ &settings);
+ HostContentSettingsMap::SettingsForOneType otr_settings;
+ if (otr_map_) {
+ otr_map_->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_PLUGINS,
+ plugin,
+ &otr_settings);
+ }
+ std::wstring title = UTF16ToWide(it->name);
+ for (HostContentSettingsMap::SettingsForOneType::iterator setting_it =
+ settings.begin(); setting_it != settings.end(); ++setting_it) {
+ SettingsEntry entry = {
+ setting_it->first,
+ group_id,
+ setting_it->second,
+ false
+ };
+ settings_.push_back(entry);
+ }
+ for (HostContentSettingsMap::SettingsForOneType::iterator setting_it =
+ otr_settings.begin();
+ setting_it != otr_settings.end(); ++setting_it) {
+ SettingsEntry entry = {
+ setting_it->first,
+ group_id,
+ setting_it->second,
+ true
+ };
+ settings_.push_back(entry);
+ }
+ int num_plugins = settings.size() + otr_settings.size();
+ if (num_plugins > 0) {
+ Group group = { title, group_id++ };
+ groups_.push_back(group);
+ resources_.push_back(plugin);
+ row_counts_.push_back(num_plugins);
+ }
+ }
+}
+
+void PluginExceptionsTableModel::ReloadSettings() {
+ ClearSettings();
+ LoadSettings();
+
+ if (observer_)
+ observer_->OnModelChanged();
+}
+
diff --git a/chrome/browser/plugin_exceptions_table_model.h b/chrome/browser/plugin_exceptions_table_model.h
new file mode 100644
index 0000000..8732a0d
--- /dev/null
+++ b/chrome/browser/plugin_exceptions_table_model.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PLUGIN_EXCEPTIONS_TABLE_MODEL_H_
+#define CHROME_BROWSER_PLUGIN_EXCEPTIONS_TABLE_MODEL_H_
+#pragma once
+
+#include <deque>
+
+#include "chrome/browser/remove_rows_table_model.h"
+#include "chrome/browser/host_content_settings_map.h"
+#include "chrome/common/notification_observer.h"
+
+struct WebPluginInfo;
+
+class PluginExceptionsTableModel : public RemoveRowsTableModel,
+ public NotificationObserver {
+ public:
+ PluginExceptionsTableModel(HostContentSettingsMap* content_settings_map,
+ HostContentSettingsMap* otr_content_settings_map);
+ virtual ~PluginExceptionsTableModel() {}
+
+ // Load plugin exceptions from the HostContentSettingsMaps. You should call
+ // this method after creating a new PluginExceptionsTableModel.
+ void LoadSettings();
+
+ // RemoveRowsTableModel methods:
+ virtual bool CanRemoveRows(const Rows& rows) const;
+ virtual void RemoveRows(const Rows& rows);
+ virtual void RemoveAll();
+
+ // TableModel methods:
+ virtual int RowCount();
+ virtual std::wstring GetText(int row, int column_id);
+ virtual void SetObserver(TableModelObserver* observer);
+ virtual bool HasGroups() { return true; }
+ virtual Groups GetGroups();
+ virtual int GetGroupID(int row);
+
+ // NotificationObserver methods:
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
+
+ protected:
+ // Subclasses can override this method for testing.
+ virtual void GetPlugins(std::vector<WebPluginInfo>* plugins);
+
+ private:
+ friend class PluginExceptionsTableModelTest;
+
+ struct SettingsEntry {
+ HostContentSettingsMap::Pattern pattern;
+ int plugin_id;
+ ContentSetting setting;
+ bool is_otr;
+ };
+
+ void ClearSettings();
+ void ReloadSettings();
+
+ HostContentSettingsMap* map_;
+ HostContentSettingsMap* otr_map_;
+
+ std::deque<SettingsEntry> settings_;
+ std::deque<int> row_counts_;
+ std::deque<std::string> resources_;
+ TableModel::Groups groups_;
+
+ NotificationRegistrar registrar_;
+ bool updates_disabled_;
+ TableModelObserver* observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(PluginExceptionsTableModel);
+};
+
+#endif // CHROME_BROWSER_PLUGIN_EXCEPTIONS_TABLE_MODEL_H_
diff --git a/chrome/browser/plugin_exceptions_table_model_unittest.cc b/chrome/browser/plugin_exceptions_table_model_unittest.cc
new file mode 100644
index 0000000..09eac8a
--- /dev/null
+++ b/chrome/browser/plugin_exceptions_table_model_unittest.cc
@@ -0,0 +1,190 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "app/table_model_observer.h"
+#include "base/command_line.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/plugin_exceptions_table_model.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/testing_pref_service.h"
+#include "chrome/test/testing_profile.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/glue/plugins/webplugininfo.h"
+
+namespace {
+
+class MockTableModelObserver : public TableModelObserver {
+ public:
+ MOCK_METHOD0(OnModelChanged, void());
+ MOCK_METHOD2(OnItemsChanged, void(int start, int length));
+ MOCK_METHOD2(OnItemsAdded, void(int start, int length));
+ MOCK_METHOD2(OnItemsRemoved, void(int start, int length));
+};
+
+class TestingPluginExceptionsTableModel : public PluginExceptionsTableModel {
+ public:
+ TestingPluginExceptionsTableModel(HostContentSettingsMap* map,
+ HostContentSettingsMap* otr_map)
+ : PluginExceptionsTableModel(map, otr_map) {}
+ virtual ~TestingPluginExceptionsTableModel() {}
+
+ void set_plugins(const std::vector<WebPluginInfo>& plugins) {
+ plugins_ = plugins;
+ }
+
+ protected:
+ virtual void GetPlugins(std::vector<WebPluginInfo>* plugins) {
+ *plugins = plugins_;
+ }
+
+ private:
+ std::vector<WebPluginInfo> plugins_;
+};
+
+}
+
+class PluginExceptionsTableModelTest : public testing::Test {
+ public:
+ PluginExceptionsTableModelTest()
+ : ui_thread_(ChromeThread::UI, &message_loop_),
+ command_line_(*CommandLine::ForCurrentProcess()) {}
+
+ virtual void SetUp() {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableResourceContentSettings);
+
+ profile_.reset(new TestingProfile());
+
+ HostContentSettingsMap* map = profile_->GetHostContentSettingsMap();
+
+ HostContentSettingsMap::Pattern example_com("[*.]example.com");
+ HostContentSettingsMap::Pattern moose_org("[*.]moose.org");
+ map->SetContentSetting(example_com,
+ CONTENT_SETTINGS_TYPE_PLUGINS,
+ "foo",
+ CONTENT_SETTING_ALLOW);
+ map->SetContentSetting(moose_org,
+ CONTENT_SETTINGS_TYPE_PLUGINS,
+ "bar",
+ CONTENT_SETTING_BLOCK);
+ map->SetContentSetting(example_com,
+ CONTENT_SETTINGS_TYPE_PLUGINS,
+ "bar",
+ CONTENT_SETTING_ALLOW);
+
+ table_model_.reset(new TestingPluginExceptionsTableModel(
+ map,
+ NULL));
+
+ std::vector<WebPluginInfo> plugins;
+ WebPluginInfo foo_plugin;
+ foo_plugin.path = FilePath(FILE_PATH_LITERAL("foo"));
+ foo_plugin.name = ASCIIToUTF16("FooPlugin");
+ foo_plugin.enabled = true;
+ plugins.push_back(foo_plugin);
+ WebPluginInfo bar_plugin;
+ bar_plugin.path = FilePath(FILE_PATH_LITERAL("bar"));
+ bar_plugin.name = ASCIIToUTF16("BarPlugin");
+ bar_plugin.enabled = true;
+ plugins.push_back(bar_plugin);
+
+ table_model_->set_plugins(plugins);
+ table_model_->ReloadSettings();
+ }
+
+ virtual void TearDown() {
+ *CommandLine::ForCurrentProcess() = command_line_;
+ }
+
+ protected:
+ void CheckInvariants() {
+ typedef std::deque<PluginExceptionsTableModel::SettingsEntry> Entries;
+ Entries& settings = table_model_->settings_;
+ std::deque<int>& row_counts = table_model_->row_counts_;
+ std::deque<std::string>& resources = table_model_->resources_;
+ TableModel::Groups& groups = table_model_->groups_;
+
+ EXPECT_EQ(groups.size(), row_counts.size());
+ EXPECT_EQ(groups.size(), resources.size());
+
+ int last_plugin = 0;
+ int count = 0;
+ for (Entries::const_iterator it = settings.begin();
+ it != settings.end(); ++it) {
+ // Plugin IDs are indices into |groups|.
+ EXPECT_GE(it->plugin_id, 0);
+ EXPECT_LT(it->plugin_id, static_cast<int>(groups.size()));
+ if (it->plugin_id == last_plugin) {
+ count++;
+ } else {
+ // Plugin IDs are ascending one by one.
+ EXPECT_EQ(last_plugin+1, it->plugin_id);
+
+ // Consecutive runs of plugins with id |x| are |row_counts[x]| long.
+ EXPECT_EQ(count, row_counts[last_plugin]);
+ count = 1;
+ last_plugin = it->plugin_id;
+ }
+ }
+ if (row_counts.size() > 0)
+ EXPECT_EQ(count, row_counts[last_plugin]);
+ }
+
+ protected:
+ MessageLoop message_loop_;
+ ChromeThread ui_thread_;
+
+ scoped_ptr<TestingProfile> profile_;
+ scoped_ptr<TestingPluginExceptionsTableModel> table_model_;
+
+ private:
+ CommandLine command_line_;
+};
+
+TEST_F(PluginExceptionsTableModelTest, Basic) {
+ EXPECT_EQ(3, table_model_->RowCount());
+ CheckInvariants();
+}
+
+TEST_F(PluginExceptionsTableModelTest, RemoveOneRow) {
+ MockTableModelObserver observer;
+ table_model_->SetObserver(&observer);
+
+ EXPECT_CALL(observer, OnItemsRemoved(1, 1));
+ RemoveRowsTableModel::Rows rows;
+ rows.insert(1);
+ table_model_->RemoveRows(rows);
+ EXPECT_EQ(2, table_model_->RowCount());
+ EXPECT_EQ(2, static_cast<int>(table_model_->GetGroups().size()));
+ CheckInvariants();
+ table_model_->SetObserver(NULL);
+}
+
+TEST_F(PluginExceptionsTableModelTest, RemoveLastRowInGroup) {
+ MockTableModelObserver observer;
+ table_model_->SetObserver(&observer);
+
+ EXPECT_CALL(observer, OnModelChanged());
+ RemoveRowsTableModel::Rows rows;
+ rows.insert(0);
+ table_model_->RemoveRows(rows);
+ EXPECT_EQ(2, table_model_->RowCount());
+ EXPECT_EQ(1, static_cast<int>(table_model_->GetGroups().size()));
+ CheckInvariants();
+ table_model_->SetObserver(NULL);
+}
+
+TEST_F(PluginExceptionsTableModelTest, RemoveAllRows) {
+ MockTableModelObserver observer;
+ table_model_->SetObserver(&observer);
+
+ EXPECT_CALL(observer, OnItemsRemoved(0, 3));
+ table_model_->RemoveAll();
+ EXPECT_EQ(0, table_model_->RowCount());
+ EXPECT_EQ(0, static_cast<int>(table_model_->GetGroups().size()));
+ CheckInvariants();
+ table_model_->SetObserver(NULL);
+}
diff --git a/chrome/browser/views/options/content_filter_page_view.cc b/chrome/browser/views/options/content_filter_page_view.cc
index c017558..ac2ab38 100644
--- a/chrome/browser/views/options/content_filter_page_view.cc
+++ b/chrome/browser/views/options/content_filter_page_view.cc
@@ -10,6 +10,7 @@
#include "chrome/browser/geolocation/geolocation_exceptions_table_model.h"
#include "chrome/browser/notifications/desktop_notification_service.h"
#include "chrome/browser/notifications/notification_exceptions_table_model.h"
+#include "chrome/browser/plugin_exceptions_table_model.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/views/options/exceptions_view.h"
#include "chrome/browser/views/options/simple_content_exceptions_view.h"
@@ -190,12 +191,27 @@ void ContentFilterPageView::ButtonPressed(views::Button* sender,
profile()->GetDesktopNotificationService()),
IDS_NOTIFICATIONS_EXCEPTION_TITLE);
} else {
- ExceptionsView::ShowExceptionsWindow(GetWindow()->GetNativeWindow(),
- profile()->GetHostContentSettingsMap(),
+ HostContentSettingsMap* settings = profile()->GetHostContentSettingsMap();
+ HostContentSettingsMap* otr_settings =
profile()->HasOffTheRecordProfile() ?
profile()->GetOffTheRecordProfile()->GetHostContentSettingsMap() :
- NULL,
- content_type_);
+ NULL;
+ if (content_type_ == CONTENT_SETTINGS_TYPE_PLUGINS &&
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableResourceContentSettings)) {
+ PluginExceptionsTableModel* model =
+ new PluginExceptionsTableModel(settings, otr_settings);
+ model->LoadSettings();
+ SimpleContentExceptionsView::ShowExceptionsWindow(
+ GetWindow()->GetNativeWindow(),
+ model,
+ IDS_PLUGINS_EXCEPTION_TITLE);
+ } else {
+ ExceptionsView::ShowExceptionsWindow(GetWindow()->GetNativeWindow(),
+ settings,
+ otr_settings,
+ content_type_);
+ }
}
return;
}
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 83ad314..dff0e57 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -2193,6 +2193,8 @@
'browser/platform_util_common_linux.cc',
'browser/platform_util_mac.mm',
'browser/platform_util_win.cc',
+ 'browser/plugin_exceptions_table_model.cc',
+ 'browser/plugin_exceptions_table_model.h',
'browser/plugin_installer.cc',
'browser/plugin_installer.h',
'browser/plugin_process_host.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index aefbfba..810908d 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1162,6 +1162,7 @@
'browser/password_manager/password_store_default_unittest.cc',
'browser/password_manager/password_store_mac_unittest.cc',
'browser/password_manager/password_store_win_unittest.cc',
+ 'browser/plugin_exceptions_table_model_unittest.cc',
'browser/policy/config_dir_policy_provider_unittest.cc',
'browser/policy/configuration_policy_pref_store_unittest.cc',
'browser/policy/configuration_policy_provider_mac_unittest.cc',
diff --git a/views/controls/table/table_view.cc b/views/controls/table/table_view.cc
index e397ce9..42c137d 100644
--- a/views/controls/table/table_view.cc
+++ b/views/controls/table/table_view.cc
@@ -278,6 +278,8 @@ void TableView::OnModelChanged() {
if (!list_view_)
return;
+ UpdateGroups();
+
int current_row_count = ListView_GetItemCount(list_view_);
if (current_row_count > 0)
OnItemsRemoved(0, current_row_count);
@@ -805,21 +807,7 @@ HWND TableView::CreateNativeControl(HWND parent_container) {
if (model_)
model_->SetObserver(this);
- // Add the groups.
- if (model_ && model_->HasGroups()) {
- ListView_EnableGroupView(list_view_, true);
-
- TableModel::Groups groups = model_->GetGroups();
- LVGROUP group = { 0 };
- group.cbSize = sizeof(LVGROUP);
- group.mask = LVGF_HEADER | LVGF_ALIGN | LVGF_GROUPID;
- group.uAlign = LVGA_HEADER_LEFT;
- for (size_t i = 0; i < groups.size(); ++i) {
- group.pszHeader = const_cast<wchar_t*>(groups[i].title.c_str());
- group.iGroupId = groups[i].id;
- ListView_InsertGroup(list_view_, static_cast<int>(i), &group);
- }
- }
+ UpdateGroups();
// Set the # of rows.
if (model_)
@@ -1535,6 +1523,26 @@ void TableView::UpdateContentOffset() {
content_offset_ = origin.y + header_bounds.bottom - header_bounds.top;
}
+void TableView::UpdateGroups() {
+ // Add the groups.
+ if (model_ && model_->HasGroups()) {
+ ListView_EnableGroupView(list_view_, true);
+
+ ListView_RemoveAllGroups(list_view_);
+
+ TableModel::Groups groups = model_->GetGroups();
+ LVGROUP group = { 0 };
+ group.cbSize = sizeof(LVGROUP);
+ group.mask = LVGF_HEADER | LVGF_ALIGN | LVGF_GROUPID;
+ group.uAlign = LVGA_HEADER_LEFT;
+ for (size_t i = 0; i < groups.size(); ++i) {
+ group.pszHeader = const_cast<wchar_t*>(groups[i].title.c_str());
+ group.iGroupId = groups[i].id;
+ ListView_InsertGroup(list_view_, static_cast<int>(i), &group);
+ }
+ }
+}
+
gfx::Rect TableView::GetAltTextBounds() {
static const int kXOffset = 16;
DCHECK(GetNativeControlHWND());
diff --git a/views/controls/table/table_view.h b/views/controls/table/table_view.h
index 9444d96b..eb557da 100644
--- a/views/controls/table/table_view.h
+++ b/views/controls/table/table_view.h
@@ -402,6 +402,9 @@ class TableView : public NativeControl,
// Updates content_offset_ from the position of the header.
void UpdateContentOffset();
+ // Reloads the groups from the model if there is one and it has groups.
+ void UpdateGroups();
+
// Returns the bounds of the alt text.
gfx::Rect GetAltTextBounds();