summaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authorsky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-12-21 07:28:32 +0000
committersky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-12-21 07:28:32 +0000
commitb764dae84601f5d47902e74c2095f63f3604356f (patch)
treebfd2530a0789203f58e21cda14910d7f40d21a2b /ui
parentd9a64606901d01280e56576783922ce1e847eaa3 (diff)
downloadchromium_src-b764dae84601f5d47902e74c2095f63f3604356f.zip
chromium_src-b764dae84601f5d47902e74c2095f63f3604356f.tar.gz
chromium_src-b764dae84601f5d47902e74c2095f63f3604356f.tar.bz2
Moves TabStripSelectionModel to ui/base/models and renames
ListSelectionModel. I plan on using it in TableView. I would have liked to rename to SelectionModel, but there is already such a class in ui/gfx. BUG=none TEST=none R=sadrul@chromium.org TBR=aa@chromium.org, avi@chromium.org Review URL: https://chromiumcodereview.appspot.com/11618047 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@174350 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui')
-rw-r--r--ui/base/models/list_selection_model.cc153
-rw-r--r--ui/base/models/list_selection_model.h116
-rw-r--r--ui/base/models/list_selection_model_unittest.cc197
-rw-r--r--ui/ui.gyp2
-rw-r--r--ui/ui_unittests.gypi1
5 files changed, 469 insertions, 0 deletions
diff --git a/ui/base/models/list_selection_model.cc b/ui/base/models/list_selection_model.cc
new file mode 100644
index 0000000..5b9fc86
--- /dev/null
+++ b/ui/base/models/list_selection_model.cc
@@ -0,0 +1,153 @@
+// Copyright (c) 2012 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 "ui/base/models/list_selection_model.h"
+
+#include <algorithm>
+#include <valarray>
+
+#include "base/logging.h"
+
+namespace ui {
+
+// static
+const int ListSelectionModel::kUnselectedIndex = -1;
+
+static void IncrementFromImpl(int index, int* value) {
+ if (*value >= index)
+ (*value)++;
+}
+
+static bool DecrementFromImpl(int index, int* value) {
+ if (*value == index) {
+ *value = ListSelectionModel::kUnselectedIndex;
+ return true;
+ }
+ if (*value > index)
+ (*value)--;
+ return false;
+}
+
+ListSelectionModel::ListSelectionModel()
+ : active_(kUnselectedIndex),
+ anchor_(kUnselectedIndex) {
+}
+
+ListSelectionModel::~ListSelectionModel() {
+}
+
+void ListSelectionModel::IncrementFrom(int index) {
+ // Shift the selection to account for the newly inserted tab.
+ for (SelectedIndices::iterator i = selected_indices_.begin();
+ i != selected_indices_.end(); ++i) {
+ IncrementFromImpl(index, &(*i));
+ }
+ IncrementFromImpl(index, &anchor_);
+ IncrementFromImpl(index, &active_);
+}
+
+void ListSelectionModel::DecrementFrom(int index) {
+ for (SelectedIndices::iterator i = selected_indices_.begin();
+ i != selected_indices_.end(); ) {
+ if (DecrementFromImpl(index, &(*i)))
+ i = selected_indices_.erase(i);
+ else
+ ++i;
+ }
+ DecrementFromImpl(index, &anchor_);
+ DecrementFromImpl(index, &active_);
+}
+
+void ListSelectionModel::SetSelectedIndex(int index) {
+ anchor_ = active_ = index;
+ selected_indices_.clear();
+ if (index != kUnselectedIndex)
+ selected_indices_.push_back(index);
+}
+
+bool ListSelectionModel::IsSelected(int index) const {
+ return std::find(selected_indices_.begin(), selected_indices_.end(), index) !=
+ selected_indices_.end();
+}
+
+void ListSelectionModel::AddIndexToSelection(int index) {
+ if (!IsSelected(index)) {
+ selected_indices_.push_back(index);
+ std::sort(selected_indices_.begin(), selected_indices_.end());
+ }
+}
+
+void ListSelectionModel::RemoveIndexFromSelection(int index) {
+ SelectedIndices::iterator i = std::find(selected_indices_.begin(),
+ selected_indices_.end(), index);
+ if (i != selected_indices_.end())
+ selected_indices_.erase(i);
+}
+
+void ListSelectionModel::SetSelectionFromAnchorTo(int index) {
+ if (anchor_ == kUnselectedIndex) {
+ SetSelectedIndex(index);
+ } else {
+ int delta = std::abs(index - anchor_);
+ SelectedIndices new_selection(delta + 1, 0);
+ for (int i = 0, min = std::min(index, anchor_); i <= delta; ++i)
+ new_selection[i] = i + min;
+ selected_indices_.swap(new_selection);
+ active_ = index;
+ }
+}
+
+void ListSelectionModel::AddSelectionFromAnchorTo(int index) {
+ if (anchor_ == kUnselectedIndex) {
+ SetSelectedIndex(index);
+ } else {
+ for (int i = std::min(index, anchor_), end = std::max(index, anchor_);
+ i <= end; ++i) {
+ if (!IsSelected(i))
+ selected_indices_.push_back(i);
+ }
+ std::sort(selected_indices_.begin(), selected_indices_.end());
+ active_ = index;
+ }
+}
+
+void ListSelectionModel::Move(int from, int to) {
+ DCHECK_NE(to, from);
+ bool was_anchor = from == anchor_;
+ bool was_active = from == active_;
+ bool was_selected = IsSelected(from);
+ if (to < from) {
+ IncrementFrom(to);
+ DecrementFrom(from + 1);
+ } else {
+ DecrementFrom(from);
+ IncrementFrom(to);
+ }
+ if (was_active)
+ active_ = to;
+ if (was_anchor)
+ anchor_ = to;
+ if (was_selected)
+ AddIndexToSelection(to);
+}
+
+void ListSelectionModel::Clear() {
+ anchor_ = active_ = kUnselectedIndex;
+ SelectedIndices empty_selection;
+ selected_indices_.swap(empty_selection);
+}
+
+void ListSelectionModel::Copy(const ListSelectionModel& source) {
+ selected_indices_ = source.selected_indices_;
+ active_ = source.active_;
+ anchor_ = source.anchor_;
+}
+
+bool ListSelectionModel::Equals(const ListSelectionModel& rhs) const {
+ return active_ == rhs.active() &&
+ anchor_ == rhs.anchor() &&
+ selected_indices() == rhs.selected_indices();
+}
+
+} // namespace ui
diff --git a/ui/base/models/list_selection_model.h b/ui/base/models/list_selection_model.h
new file mode 100644
index 0000000..95194700
--- /dev/null
+++ b/ui/base/models/list_selection_model.h
@@ -0,0 +1,116 @@
+// Copyright (c) 2012 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 UI_BASE_MODELS_LIST_SELECTION_MODEL_H_
+#define UI_BASE_MODELS_LIST_SELECTION_MODEL_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "ui/base/ui_export.h"
+
+namespace ui {
+
+// Selection model represented as a list of ints. Used by the TabStrip. In
+// addition to the set of selected indices ListSelectionModel maintains the
+// following:
+// active: the index of the currently visible tab in the tab strip.
+// anchor: the index of the last tab the user clicked on. Extending the
+// selection extends it from this index.
+//
+// Typically there is only one selected item, in which case the anchor and
+// active index correspond to the same thing.
+class UI_EXPORT ListSelectionModel {
+ public:
+ typedef std::vector<int> SelectedIndices;
+
+ // Used to identify no selection.
+ static const int kUnselectedIndex;
+
+ ListSelectionModel();
+ ~ListSelectionModel();
+
+ // See class description for details of the anchor.
+ void set_anchor(int anchor) { anchor_ = anchor; }
+ int anchor() const { return anchor_; }
+
+ // See class description for details of active.
+ void set_active(int active) { active_ = active; }
+ int active() const { return active_; }
+
+ // True if nothing is selected.
+ bool empty() const { return selected_indices_.empty(); }
+
+ // Number of selected indices.
+ size_t size() const { return selected_indices_.size(); }
+
+ // Increments all indices >= |index|. For example, if the selection consists
+ // of [0, 1, 5] and this is invoked with 1, it results in [0, 2, 6]. This also
+ // updates the anchor and active indices.
+ // This is used when a new tab is inserted into the tabstrip.
+ void IncrementFrom(int index);
+
+ // Shifts all indices < |index| down by 1. If |index| is selected, it is
+ // removed. For example, if the selection consists of [0, 1, 5] and this is
+ // invoked with 1, it results in [0, 4]. This is used when a tab is removed
+ // from the tabstrip.
+ void DecrementFrom(int index);
+
+ // Sets the anchor, active and selection to |index|.
+ void SetSelectedIndex(int index);
+
+ // Returns true if |index| is selected.
+ bool IsSelected(int index) const;
+
+ // Adds |index| to the selection. This does not change the active or anchor
+ // indices.
+ void AddIndexToSelection(int index);
+
+ // Removes |index| from the selection. This does not change the active or
+ // anchor indices.
+ void RemoveIndexFromSelection(int index);
+
+ // Extends the selection from the anchor to |index|. If the anchor is empty,
+ // this sets the anchor, selection and active indices to |index|.
+ void SetSelectionFromAnchorTo(int index);
+
+ // Makes sure the indices from the anchor to |index| are selected. This only
+ // adds to the selection.
+ void AddSelectionFromAnchorTo(int index);
+
+ // Invoked when an item moves. |from| is the original index, and |to| the
+ // target index.
+ // NOTE: this matches the TabStripModel API. If moving to a greater index,
+ // |to| should be the index *after* removing |from|. For example, consider
+ // three tabs 'A B C', to move A to the end of the list, this should be
+ // invoked with '0, 2'.
+ void Move(int from, int to);
+
+ // Sets the anchor and active to kUnselectedIndex, and removes all the
+ // selected indices.
+ void Clear();
+
+ // Returns the selected indices. The selection is always ordered in acending
+ // order.
+ const SelectedIndices& selected_indices() const { return selected_indices_; }
+
+ // Copies the selection from |source| to this.
+ void Copy(const ListSelectionModel& source);
+
+ // Compares this selection with |rhs|.
+ bool Equals(const ListSelectionModel& rhs) const;
+
+ private:
+ SelectedIndices selected_indices_;
+
+ int active_;
+
+ int anchor_;
+
+ DISALLOW_COPY_AND_ASSIGN(ListSelectionModel);
+};
+
+} // namespace ui
+
+#endif // UI_BASE_MODELS_LIST_SELECTION_MODEL_H_
diff --git a/ui/base/models/list_selection_model_unittest.cc b/ui/base/models/list_selection_model_unittest.cc
new file mode 100644
index 0000000..870e7ba
--- /dev/null
+++ b/ui/base/models/list_selection_model_unittest.cc
@@ -0,0 +1,197 @@
+// Copyright (c) 2012 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 "ui/base/models/list_selection_model.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/string_number_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ui {
+
+typedef testing::Test ListSelectionModelTest;
+
+// Returns the state of the selection model as a string. The format is:
+// 'active=X anchor=X selection=X X X...'.
+static std::string StateAsString(const ListSelectionModel& model) {
+ std::string result = "active=" + base::IntToString(model.active()) +
+ " anchor=" + base::IntToString(model.anchor()) +
+ " selection=";
+ const ListSelectionModel::SelectedIndices& selection(
+ model.selected_indices());
+ for (size_t i = 0; i < selection.size(); ++i) {
+ if (i != 0)
+ result += " ";
+ result += base::IntToString(selection[i]);
+ }
+ return result;
+}
+
+TEST_F(ListSelectionModelTest, InitialState) {
+ ListSelectionModel model;
+ EXPECT_EQ("active=-1 anchor=-1 selection=", StateAsString(model));
+ EXPECT_TRUE(model.empty());
+}
+
+TEST_F(ListSelectionModelTest, SetSelectedIndex) {
+ ListSelectionModel model;
+ model.SetSelectedIndex(2);
+ EXPECT_EQ("active=2 anchor=2 selection=2", StateAsString(model));
+ EXPECT_FALSE(model.empty());
+}
+
+TEST_F(ListSelectionModelTest, SetSelectedIndexToEmpty) {
+ ListSelectionModel model;
+ model.SetSelectedIndex(-1);
+ EXPECT_EQ("active=-1 anchor=-1 selection=", StateAsString(model));
+ EXPECT_TRUE(model.empty());
+}
+
+TEST_F(ListSelectionModelTest, IncrementFrom) {
+ ListSelectionModel model;
+ model.SetSelectedIndex(1);
+ model.IncrementFrom(1);
+ EXPECT_EQ("active=2 anchor=2 selection=2", StateAsString(model));
+
+ // Increment from 4. This shouldn't effect the selection as its past the
+ // end of the selection.
+ model.IncrementFrom(4);
+ EXPECT_EQ("active=2 anchor=2 selection=2", StateAsString(model));
+}
+
+TEST_F(ListSelectionModelTest, DecrementFrom) {
+ ListSelectionModel model;
+ model.SetSelectedIndex(2);
+ model.DecrementFrom(0);
+ EXPECT_EQ("active=1 anchor=1 selection=1", StateAsString(model));
+
+ // Shift down from 1. As the selection as the index being removed, this should
+ // clear the selection.
+ model.DecrementFrom(1);
+ EXPECT_EQ("active=-1 anchor=-1 selection=", StateAsString(model));
+
+ // Reset the selection to 2, and shift down from 4. This shouldn't do
+ // anything.
+ model.SetSelectedIndex(2);
+ model.DecrementFrom(4);
+ EXPECT_EQ("active=2 anchor=2 selection=2", StateAsString(model));
+}
+
+TEST_F(ListSelectionModelTest, IsSelected) {
+ ListSelectionModel model;
+ model.SetSelectedIndex(2);
+ EXPECT_FALSE(model.IsSelected(0));
+ EXPECT_TRUE(model.IsSelected(2));
+}
+
+TEST_F(ListSelectionModelTest, AddIndexToSelected) {
+ ListSelectionModel model;
+ model.AddIndexToSelection(2);
+ EXPECT_EQ("active=-1 anchor=-1 selection=2", StateAsString(model));
+
+ model.AddIndexToSelection(4);
+ EXPECT_EQ("active=-1 anchor=-1 selection=2 4", StateAsString(model));
+}
+
+TEST_F(ListSelectionModelTest, RemoveIndexFromSelection) {
+ ListSelectionModel model;
+ model.SetSelectedIndex(2);
+ model.AddIndexToSelection(4);
+ EXPECT_EQ("active=2 anchor=2 selection=2 4", StateAsString(model));
+
+ model.RemoveIndexFromSelection(4);
+ EXPECT_EQ("active=2 anchor=2 selection=2", StateAsString(model));
+
+ model.RemoveIndexFromSelection(2);
+ EXPECT_EQ("active=2 anchor=2 selection=", StateAsString(model));
+}
+
+TEST_F(ListSelectionModelTest, SetSelectionFromAnchorTo) {
+ ListSelectionModel model;
+ model.SetSelectedIndex(2);
+ model.SetSelectionFromAnchorTo(7);
+ EXPECT_EQ("active=7 anchor=2 selection=2 3 4 5 6 7", StateAsString(model));
+
+ model.Clear();
+ model.SetSelectedIndex(7);
+ model.SetSelectionFromAnchorTo(2);
+ EXPECT_EQ("active=2 anchor=7 selection=2 3 4 5 6 7", StateAsString(model));
+
+ model.Clear();
+ model.SetSelectionFromAnchorTo(7);
+ EXPECT_EQ("active=7 anchor=7 selection=7", StateAsString(model));
+}
+
+TEST_F(ListSelectionModelTest, Clear) {
+ ListSelectionModel model;
+ model.SetSelectedIndex(2);
+
+ model.Clear();
+ EXPECT_EQ("active=-1 anchor=-1 selection=", StateAsString(model));
+}
+
+TEST_F(ListSelectionModelTest, MoveToLeft) {
+ ListSelectionModel model;
+ model.SetSelectedIndex(0);
+ model.AddIndexToSelection(4);
+ model.AddIndexToSelection(10);
+ model.set_anchor(4);
+ model.set_active(4);
+ model.Move(4, 0);
+ EXPECT_EQ("active=0 anchor=0 selection=0 1 10", StateAsString(model));
+}
+
+TEST_F(ListSelectionModelTest, MoveToRight) {
+ ListSelectionModel model;
+ model.SetSelectedIndex(0);
+ model.AddIndexToSelection(4);
+ model.AddIndexToSelection(10);
+ model.set_anchor(0);
+ model.set_active(0);
+ model.Move(0, 3);
+ EXPECT_EQ("active=3 anchor=3 selection=3 4 10", StateAsString(model));
+}
+
+TEST_F(ListSelectionModelTest, Copy) {
+ ListSelectionModel model;
+ model.SetSelectedIndex(0);
+ model.AddIndexToSelection(4);
+ model.AddIndexToSelection(10);
+ EXPECT_EQ("active=0 anchor=0 selection=0 4 10", StateAsString(model));
+ ListSelectionModel model2;
+ model2.Copy(model);
+ EXPECT_EQ("active=0 anchor=0 selection=0 4 10", StateAsString(model2));
+}
+
+TEST_F(ListSelectionModelTest, AddSelectionFromAnchorTo) {
+ ListSelectionModel model;
+ model.SetSelectedIndex(2);
+
+ model.AddSelectionFromAnchorTo(4);
+ EXPECT_EQ("active=4 anchor=2 selection=2 3 4", StateAsString(model));
+
+ model.AddSelectionFromAnchorTo(0);
+ EXPECT_EQ("active=0 anchor=2 selection=0 1 2 3 4", StateAsString(model));
+}
+
+TEST_F(ListSelectionModelTest, Equals) {
+ ListSelectionModel model1;
+ model1.SetSelectedIndex(0);
+ model1.AddSelectionFromAnchorTo(4);
+
+ ListSelectionModel model2;
+ model2.SetSelectedIndex(0);
+ model2.AddSelectionFromAnchorTo(4);
+
+ EXPECT_TRUE(model1.Equals(model2));
+ EXPECT_TRUE(model2.Equals(model1));
+
+ model2.SetSelectedIndex(0);
+ EXPECT_FALSE(model1.Equals(model2));
+ EXPECT_FALSE(model2.Equals(model1));
+}
+
+} // namespace ui
diff --git a/ui/ui.gyp b/ui/ui.gyp
index d173184..508ba2c 100644
--- a/ui/ui.gyp
+++ b/ui/ui.gyp
@@ -244,6 +244,8 @@
'base/models/combobox_model.h',
'base/models/list_model.h',
'base/models/list_model_observer.h',
+ 'base/models/list_selection_model.cc',
+ 'base/models/list_selection_model.h',
'base/models/menu_model.cc',
'base/models/menu_model.h',
'base/models/menu_model_delegate.h',
diff --git a/ui/ui_unittests.gypi b/ui/ui_unittests.gypi
index 36a1afa..eb345f0 100644
--- a/ui/ui_unittests.gypi
+++ b/ui/ui_unittests.gypi
@@ -117,6 +117,7 @@
'base/gtk/menu_label_accelerator_util_unittest.cc',
'base/keycodes/usb_keycode_map_unittest.cc',
'base/models/list_model_unittest.cc',
+ 'base/models/list_selection_model_unittest.cc',
'base/models/tree_node_model_unittest.cc',
'base/test/data/resource.h',
'base/text/bytes_formatting_unittest.cc',