910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// 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 <optional>
#include <valarray>

#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"

namespace ui {

namespace {

// Determines the new index for `original_index` after inserting an item at
// `insert_position`. This is called for the selected indices.
size_t GetIndexAfterInsertion(size_t insert_position, size_t original_index) {
  return (original_index >= insert_position) ? original_index + 1
                                             : original_index;
}

// Determines the new index for `original_index` after inserting an item at
// `insert_position`. This is called for the active and anchor indexes.
std::optional<size_t> GetIndexAfterInsertion(
    size_t insert_position,
    std::optional<size_t> original_index) {
  return original_index.has_value()
             ? std::optional<size_t>(GetIndexAfterInsertion(
                   insert_position, original_index.value()))
             : std::nullopt;
}

// Determines the new index for `original_index` after removing an item at
// `remove_position`. This is called for the selected indices. Returns
// std::nullopt if `original_index` should be erased from its container.
std::optional<size_t> GetIndexAfterRemoval(size_t remove_position,
                                           size_t original_index) {
  if (original_index == remove_position) {
    return std::nullopt;
  }

  if (original_index > remove_position) {
    return original_index - 1;
  }

  return original_index;
}

// Determines the new index for `original_index` after removing an item at
// `remove_position`. This is called for active and anchor indexes. Returns
// std::nullopt if `original_index` should be erased from its container.
std::optional<size_t> GetIndexAfterRemoval(
    size_t remove_position,
    std::optional<size_t> original_index) {
  return original_index.has_value()
             ? std::optional<size_t>(GetIndexAfterRemoval(
                   remove_position, original_index.value()))
             : std::nullopt;
}

// Returns the new index for `original_index` when a range of items are moved
// from `source_position` to `destination_position`. This assumes that the items
// are moved to a lower index. This is called for active and anchor indexes.
std::optional<size_t> GetIndexAfterMove(size_t source_position,
                                        size_t destination_position,
                                        size_t range_size,
                                        std::optional<size_t> original_index) {
  DCHECK_LE(destination_position, source_position);

  if (!original_index.has_value()) {
    return std::nullopt;
  }

  // When a range of items moves to a lower index, the only affected indices
  // are those in the interval [destination_position, source_position +
  // range_size).
  size_t original_index_val = original_index.value();
  if (destination_position <= original_index_val &&
      original_index_val < source_position + range_size) {
    if (original_index_val < source_position) {
      // The items originally in the interval [destination_position,
      // source_position) see `range_size` many items inserted before them, so
      // their indices increase.
      return original_index_val + range_size;
    } else {
      // The items originally in the interval [source_position, source_position
      // + range_size) are shifted downward by (source_position -
      // destination_position) many spots, so their indices decrease.
      return original_index_val - (source_position - destination_position);
    }
  }

  return original_index;
}
}  // namespace

ListSelectionModel::ListSelectionModel() = default;
ListSelectionModel::ListSelectionModel(const ListSelectionModel&) = default;
ListSelectionModel::ListSelectionModel(ListSelectionModel&&) noexcept = default;

ListSelectionModel::~ListSelectionModel() = default;

ListSelectionModel& ListSelectionModel::operator=(const ListSelectionModel&) =
    default;
ListSelectionModel& ListSelectionModel::operator=(ListSelectionModel&&) =
    default;

void ListSelectionModel::IncrementFrom(size_t index) {
  // Shift the selection to account for a newly inserted item at |index|.
  for (size_t& selected_index : selected_indices_) {
    selected_index = GetIndexAfterInsertion(index, selected_index);
  }

  anchor_ = GetIndexAfterInsertion(index, anchor_);
  set_active(GetIndexAfterInsertion(index, active_));
}

void ListSelectionModel::DecrementFrom(size_t index) {
  for (auto it = selected_indices_.begin(); it != selected_indices_.end();) {
    std::optional<size_t> new_value = GetIndexAfterRemoval(index, *it);
    if (new_value == std::nullopt) {
      it = selected_indices_.erase(it);
    } else {
      *it = new_value.value();
      ++it;
    }
  }

  anchor_ = GetIndexAfterRemoval(index, anchor_);
  set_active(GetIndexAfterRemoval(index, active_));
}

void ListSelectionModel::SetSelectedIndex(std::optional<size_t> index) {
  anchor_ = index;
  set_active(index);

  selected_indices_.clear();
  if (index.has_value()) {
    selected_indices_.insert(index.value());
  }
}

bool ListSelectionModel::IsSelected(size_t index) const {
  return base::Contains(selected_indices_, index);
}

void ListSelectionModel::AddIndexToSelection(size_t index) {
  selected_indices_.insert(index);
}

void ListSelectionModel::AddIndexRangeToSelection(size_t index_start,
                                                  size_t index_end) {
  DCHECK_LE(index_start, index_end);

  if (index_start == index_end)
    return AddIndexToSelection(index_start);

  for (size_t i = index_start; i <= index_end; ++i) {
    selected_indices_.insert(i);
  }
}

void ListSelectionModel::RemoveIndexFromSelection(size_t index) {
  selected_indices_.erase(index);
}

void ListSelectionModel::SetSelectionFromAnchorTo(size_t index) {
  if (!anchor_.has_value()) {
    SetSelectedIndex(index);
  } else {
    SelectedIndices new_selection;
    for (size_t min = std::min(index, anchor_.value()),
                delta = std::max(index, anchor_.value()) - min, i = min;
         i <= min + delta; ++i) {
      new_selection.insert(i);
    }
    selected_indices_.swap(new_selection);
    set_active(index);
  }
}

void ListSelectionModel::AddSelectionFromAnchorTo(size_t index) {
  if (!anchor_.has_value()) {
    SetSelectedIndex(index);
  } else {
    for (size_t i = std::min(index, anchor_.value()),
                end = std::max(index, anchor_.value());
         i <= end; ++i) {
      selected_indices_.insert(i);
    }
    set_active(index);
  }
}

void ListSelectionModel::Move(size_t old_index,
                              size_t new_index,
                              size_t length) {
  // |length| many items are moving from index |old_index| to index |new_index|.
  DCHECK_NE(old_index, new_index);
  DCHECK_GT(length, 0u);

  // Remap move-to-higher-index operations to the equivalent move-to-lower-index
  // operation. As an example, the permutation "ABCDEFG" -> "CDEFABG" can be
  // thought of either as shifting 'AB' higher by 4, or by shifting 'CDEF' lower
  // by 2.
  if (new_index > old_index) {
    Move(old_index + length, old_index, new_index - old_index);
    return;
  }

  // We know that |old_index| > |new_index|, so this is a move to a lower index.
  // Start by transforming |anchor_| and |active_|.
  anchor_ = GetIndexAfterMove(old_index, new_index, length, anchor_);
  set_active(GetIndexAfterMove(old_index, new_index, length, active_));

  // When a range of items moves to a lower index, the affected items are those
  // in the interval [new_index, old_index + length). Search within
  // |selected_indices_| for indices that fall into that range.
  auto low = std::lower_bound(selected_indices_.begin(),
                              selected_indices_.end(), new_index);
  auto high =
      std::lower_bound(low, selected_indices_.end(), old_index + length);

  // The items originally in the interval [new_index, old_index) will see
  // |length| many items inserted before them, so their indices increase.
  auto middle = std::lower_bound(low, high, old_index);
  size_t pivot_value = new_index + length;
  for (auto it = low; it != middle; ++it) {
    (*it) += length;
    DCHECK_LE(pivot_value, *it);
    DCHECK_LT(*it, old_index + length);
  }

  // The items originally in the interval [old_index, old_index + length) are
  // shifted downward by (old_index - new_index) many spots, so their indices
  // decrease.
  for (auto it = middle; it != high; ++it) {
    (*it) -= (old_index - new_index);
    DCHECK_LE(new_index, *it);
    DCHECK_LT(*it, pivot_value);
  }

  // Reorder the ranges [low, middle), and [middle, high) so that the elements
  // in [middle, high) appear first, followed by [low, middle). This suffices to
  // restore the sort condition on |selected_indices_|, because each range is
  // still sorted piecewise, and |pivot_value| is a lower bound for elements in
  // [low, middle), and an upper bound for [middle, high).
  std::rotate(low, middle, high);
}

void ListSelectionModel::Clear() {
  anchor_ = active_ = std::nullopt;
  selected_indices_.clear();
}

std::string ListSelectionModel::ToString() const {
  const auto optional_to_string = [](const auto& opt) {
    return opt.has_value() ? base::NumberToString(opt.value())
                           : std::string("<none>");
  };
  std::vector<std::string> index_strings;
  std::ranges::transform(
      selected_indices_, std::back_inserter(index_strings),
      [](const auto& index) { return base::NumberToString(index); });
  return "active=" + optional_to_string(active_) +
         " anchor=" + optional_to_string(anchor_) +
         " selection=" + base::JoinString(index_strings, " ");
}

}  // namespace ui