diff options
author | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-21 07:28:32 +0000 |
---|---|---|
committer | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-21 07:28:32 +0000 |
commit | b764dae84601f5d47902e74c2095f63f3604356f (patch) | |
tree | bfd2530a0789203f58e21cda14910d7f40d21a2b /ui | |
parent | d9a64606901d01280e56576783922ce1e847eaa3 (diff) | |
download | chromium_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.cc | 153 | ||||
-rw-r--r-- | ui/base/models/list_selection_model.h | 116 | ||||
-rw-r--r-- | ui/base/models/list_selection_model_unittest.cc | 197 | ||||
-rw-r--r-- | ui/ui.gyp | 2 | ||||
-rw-r--r-- | ui/ui_unittests.gypi | 1 |
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 @@ -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', |