diff options
Diffstat (limited to 'chromium/ui/views/controls')
83 files changed, 2427 insertions, 1620 deletions
diff --git a/chromium/ui/views/controls/animated_image_view.cc b/chromium/ui/views/controls/animated_image_view.cc index 6bc5217e38f..ba605157123 100644 --- a/chromium/ui/views/controls/animated_image_view.cc +++ b/chromium/ui/views/controls/animated_image_view.cc @@ -7,6 +7,7 @@ #include <utility> #include "base/check.h" +#include "base/trace_event/trace_event.h" #include "cc/paint/skottie_wrapper.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/compositor/compositor.h" @@ -48,25 +49,24 @@ void AnimatedImageView::SetAnimatedImage( SchedulePaint(); } -void AnimatedImageView::Play(lottie::Animation::Style style) { - DCHECK(animated_image_); - Play(base::TimeDelta(), animated_image_->GetAnimationDuration(), style); -} - -void AnimatedImageView::Play(base::TimeDelta start_offset, - base::TimeDelta duration, - lottie::Animation::Style style) { +void AnimatedImageView::Play( + absl::optional<lottie::Animation::PlaybackConfig> playback_config) { DCHECK(animated_image_); if (state_ == State::kPlaying) return; state_ = State::kPlaying; - set_check_active_duration(style != lottie::Animation::Style::kLoop); + if (!playback_config) { + playback_config = + lottie::Animation::PlaybackConfig::CreateDefault(*animated_image_); + } + set_check_active_duration(playback_config->style != + lottie::Animation::Style::kLoop); SetCompositorFromWidget(); - animated_image_->StartSubsection(start_offset, duration, style); + animated_image_->Start(std::move(playback_config)); } void AnimatedImageView::Stop() { @@ -126,6 +126,8 @@ void AnimatedImageView::RemovedFromWidget() { } void AnimatedImageView::OnAnimationStep(base::TimeTicks timestamp) { + TRACE_EVENT1("views", "AnimatedImageView::OnAnimationStep", "timestamp", + timestamp); previous_timestamp_ = timestamp; SchedulePaint(); } diff --git a/chromium/ui/views/controls/animated_image_view.h b/chromium/ui/views/controls/animated_image_view.h index 04d6dea759c..4c880b7b0d2 100644 --- a/chromium/ui/views/controls/animated_image_view.h +++ b/chromium/ui/views/controls/animated_image_view.h @@ -10,6 +10,7 @@ #include "base/memory/raw_ptr.h" #include "base/time/time.h" +#include "third_party/abseil-cpp/absl/types/optional.h" #include "ui/base/metadata/metadata_header_macros.h" #include "ui/compositor/compositor_animation_observer.h" #include "ui/gfx/geometry/vector2d.h" @@ -22,10 +23,6 @@ namespace gfx { class Canvas; } -namespace lottie { -class Animation; -} - namespace ui { class Compositor; } @@ -61,13 +58,12 @@ class VIEWS_EXPORT AnimatedImageView : public ImageViewBase, // will result in stopping the current animation. void SetAnimatedImage(std::unique_ptr<lottie::Animation> animated_image); - // Plays the animation in loop and must only be called when this view has + // Plays the animation and must only be called when this view has // access to a widget. - void Play(lottie::Animation::Style style = lottie::Animation::Style::kLoop); - // Version of the above that mirrors lottie::Animation::StartSubsection(). - void Play(base::TimeDelta start_offset, - base::TimeDelta duration, - lottie::Animation::Style style = lottie::Animation::Style::kLoop); + // + // If a null |playback_config| is provided, the default one is used. + void Play(absl::optional<lottie::Animation::PlaybackConfig> playback_config = + absl::nullopt); // Stops any animation and resets it to the start frame. void Stop(); diff --git a/chromium/ui/views/controls/animated_image_view_unittest.cc b/chromium/ui/views/controls/animated_image_view_unittest.cc index 6659862015d..fa41cc67d1f 100644 --- a/chromium/ui/views/controls/animated_image_view_unittest.cc +++ b/chromium/ui/views/controls/animated_image_view_unittest.cc @@ -58,10 +58,8 @@ class AnimatedImageViewTest : public ViewsTestBase { params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; widget_.Init(std::move(params)); - auto view = std::make_unique<AnimatedImageView>(); - view->SetUseDefaultFillLayout(true); - view_ = view.get(); - widget_.SetContentsView(std::move(view)); + view_ = widget_.SetContentsView(std::make_unique<AnimatedImageView>()); + view_->SetUseDefaultFillLayout(true); widget_.Show(); } @@ -97,7 +95,7 @@ TEST_F(AnimatedImageViewTest, PaintsWithAdditionalTranslation) { view_->SetAnimatedImage(CreateAnimationWithSize(gfx::Size(80, 80))); view_->SetVerticalAlignment(ImageViewBase::Alignment::kCenter); view_->SetHorizontalAlignment(ImageViewBase::Alignment::kCenter); - widget_.GetContentsView()->Layout(); + RunScheduledLayout(view_); view_->Play(); static constexpr float kExpectedDefaultOrigin = diff --git a/chromium/ui/views/controls/button/label_button_unittest.cc b/chromium/ui/views/controls/button/label_button_unittest.cc index 0524fca9e01..97dedf98af5 100644 --- a/chromium/ui/views/controls/button/label_button_unittest.cc +++ b/chromium/ui/views/controls/button/label_button_unittest.cc @@ -90,8 +90,12 @@ class LabelButtonTest : public test::WidgetTest { // Windows is platform-dependent. test_widget_->Show(); - button_ = test_widget_->GetContentsView()->AddChildView( - std::make_unique<TestLabelButton>()); + // Place the button into a separate container view which itself does no + // layouts. This will isolate the button from the client view which does + // a fill layout by default. + auto* container = + test_widget_->client_view()->AddChildView(std::make_unique<View>()); + button_ = container->AddChildView(std::make_unique<TestLabelButton>()); // Establish the expected text colors for testing changes due to state. themed_normal_text_color_ = @@ -425,12 +429,12 @@ TEST_F(LabelButtonTest, ImageAlignmentWithMultilineLabel) { button_->SetImage(Button::STATE_NORMAL, image); button_->SetBoundsRect(gfx::Rect(button_->GetPreferredSize())); - button_->Layout(); + RunScheduledLayout(button_); int y_origin_centered = button_->image()->origin().y(); button_->SetBoundsRect(gfx::Rect(button_->GetPreferredSize())); button_->SetImageCentered(false); - button_->Layout(); + RunScheduledLayout(button_); int y_origin_not_centered = button_->image()->origin().y(); EXPECT_LT(y_origin_not_centered, y_origin_centered); @@ -463,17 +467,17 @@ TEST_F(LabelButtonTest, LabelAndImage) { gfx::Size button_size = button_->GetPreferredSize(); button_size.Enlarge(50, 0); button_->SetSize(button_size); - button_->Layout(); + RunScheduledLayout(button_); EXPECT_LT(button_->image()->bounds().right(), button_->label()->bounds().x()); int left_align_label_midpoint = button_->label()->bounds().CenterPoint().x(); button_->SetHorizontalAlignment(gfx::ALIGN_CENTER); - button_->Layout(); + RunScheduledLayout(button_); EXPECT_LT(button_->image()->bounds().right(), button_->label()->bounds().x()); int center_align_label_midpoint = button_->label()->bounds().CenterPoint().x(); EXPECT_LT(left_align_label_midpoint, center_align_label_midpoint); button_->SetHorizontalAlignment(gfx::ALIGN_RIGHT); - button_->Layout(); + RunScheduledLayout(button_); EXPECT_LT(button_->label()->bounds().right(), button_->image()->bounds().x()); button_->SetText(std::u16string()); @@ -521,7 +525,7 @@ TEST_F(LabelButtonTest, LabelWrapAndImageAlignment) { gfx::Size preferred_size = button_->GetPreferredSize(); preferred_size.set_height(button_->GetHeightForWidth(preferred_size.width())); button_->SetSize(preferred_size); - button_->Layout(); + RunScheduledLayout(button_); EXPECT_EQ(preferred_size.width(), image.width() + image_spacing + text_wrap_width); @@ -631,7 +635,7 @@ TEST_F(LabelButtonTest, ChangeTextSize) { // is increased. button_->SetText(longer_text); EXPECT_TRUE(ViewTestApi(button_).needs_layout()); - button_->Layout(); + RunScheduledLayout(button_); EXPECT_GT(button_->label()->bounds().width(), original_label_width * 2); EXPECT_GT(button_->GetPreferredSize().width(), original_width * 2); @@ -639,7 +643,7 @@ TEST_F(LabelButtonTest, ChangeTextSize) { // text is restored. button_->SetText(text); EXPECT_TRUE(ViewTestApi(button_).needs_layout()); - button_->Layout(); + RunScheduledLayout(button_); EXPECT_EQ(original_label_width, button_->label()->bounds().width()); EXPECT_EQ(original_width, button_->GetPreferredSize().width()); } @@ -723,7 +727,7 @@ TEST_F(LabelButtonTest, ImageOrLabelGetClipped) { // The border size + the content height is more than button's preferred size. button_->SetBorder(CreateEmptyBorder( gfx::Insets::TLBR(image_size / 2, 0, image_size / 2, 0))); - button_->Layout(); + RunScheduledLayout(button_); // Ensure that content (image and label) doesn't get clipped by the border. EXPECT_GE(button_->image()->height(), image_size); diff --git a/chromium/ui/views/controls/combobox/combobox.cc b/chromium/ui/views/controls/combobox/combobox.cc index b8d41cfeb67..8f521332e66 100644 --- a/chromium/ui/views/controls/combobox/combobox.cc +++ b/chromium/ui/views/controls/combobox/combobox.cc @@ -18,7 +18,6 @@ #include "ui/base/ime/input_method.h" #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/base/models/image_model.h" -#include "ui/base/models/menu_model.h" #include "ui/base/ui_base_types.h" #include "ui/color/color_id.h" #include "ui/color/color_provider.h" @@ -33,6 +32,7 @@ #include "ui/views/background.h" #include "ui/views/controls/button/button.h" #include "ui/views/controls/button/button_controller.h" +#include "ui/views/controls/combobox/combobox_menu_model.h" #include "ui/views/controls/combobox/combobox_util.h" #include "ui/views/controls/combobox/empty_combobox_model.h" #include "ui/views/controls/focus_ring.h" @@ -44,15 +44,13 @@ #include "ui/views/mouse_constants.h" #include "ui/views/style/platform_style.h" #include "ui/views/style/typography.h" +#include "ui/views/view_utils.h" #include "ui/views/widget/widget.h" namespace views { namespace { -// Used to indicate that no item is currently selected by the user. -constexpr int kNoSelection = -1; - SkColor GetTextColorForEnableState(const Combobox& combobox, bool enabled) { const int style = enabled ? style::STYLE_PRIMARY : style::STYLE_DISABLED; return style::GetColor(combobox, style::CONTEXT_TEXTFIELD, style); @@ -71,6 +69,15 @@ class TransparentButton : public Button { SetHasInkDropActionOnClick(true); InkDrop::UseInkDropForSquareRipple(InkDrop::Get(this), /*highlight_on_hover=*/false); + views::InkDrop::Get(this)->SetBaseColorCallback(base::BindRepeating( + [](Button* host) { + // This button will be used like a LabelButton, so use the same + // foreground base color as a label button. + return color_utils::DeriveDefaultIconColor( + views::style::GetColor(*host, views::style::CONTEXT_BUTTON, + views::style::STYLE_PRIMARY)); + }, + this)); InkDrop::Get(this)->SetCreateRippleCallback(base::BindRepeating( [](Button* host) -> std::unique_ptr<views::InkDropRipple> { return std::make_unique<views::FloodFillInkDropRipple>( @@ -97,124 +104,23 @@ class TransparentButton : public Button { double GetAnimationValue() const { return hover_animation().GetCurrentValue(); } -}; - -#if !BUILDFLAG(IS_MAC) -// Returns the next or previous valid index (depending on |increment|'s value). -// Skips separator or disabled indices. Returns -1 if there is no valid adjacent -// index. -int GetAdjacentIndex(ui::ComboboxModel* model, int increment, int index) { - DCHECK(increment == -1 || increment == 1); - - index += increment; - while (index >= 0 && index < model->GetItemCount()) { - if (!model->IsItemSeparatorAt(index) || !model->IsItemEnabledAt(index)) - return index; - index += increment; - } - return kNoSelection; -} -#endif - -} // namespace - -// Adapts a ui::ComboboxModel to a ui::MenuModel. -class Combobox::ComboboxMenuModel : public ui::MenuModel { - public: - ComboboxMenuModel(Combobox* owner, ui::ComboboxModel* model) - : owner_(owner), model_(model) {} - ComboboxMenuModel(const ComboboxMenuModel&) = delete; - ComboboxMenuModel& operator&(const ComboboxMenuModel&) = delete; - ~ComboboxMenuModel() override = default; - - private: - bool UseCheckmarks() const { - return MenuConfig::instance().check_selected_combobox_item; - } - // Overridden from MenuModel: - bool HasIcons() const override { - for (int i = 0; i < GetItemCount(); ++i) { - if (!GetIconAt(i).IsEmpty()) - return true; + void UpdateInkDrop(bool show_on_press_and_hover) { + if (show_on_press_and_hover) { + // We must use UseInkDropForFloodFillRipple here because + // UseInkDropForSquareRipple hides the InkDrop when the ripple effect is + // active instead of layering underneath it causing flashing. + InkDrop::UseInkDropForFloodFillRipple(InkDrop::Get(this), + /*highlight_on_hover=*/true); + } else { + InkDrop::UseInkDropForSquareRipple(InkDrop::Get(this), + /*highlight_on_hover=*/false); } - return false; - } - - int GetItemCount() const override { return model_->GetItemCount(); } - - ItemType GetTypeAt(int index) const override { - if (model_->IsItemSeparatorAt(index)) - return TYPE_SEPARATOR; - return UseCheckmarks() ? TYPE_CHECK : TYPE_COMMAND; - } - - ui::MenuSeparatorType GetSeparatorTypeAt(int index) const override { - return ui::NORMAL_SEPARATOR; - } - - int GetCommandIdAt(int index) const override { - // Define the id of the first item in the menu (since it needs to be > 0) - constexpr int kFirstMenuItemId = 1000; - return index + kFirstMenuItemId; - } - - std::u16string GetLabelAt(int index) const override { - // Inserting the Unicode formatting characters if necessary so that the - // text is displayed correctly in right-to-left UIs. - std::u16string text = model_->GetDropDownTextAt(index); - base::i18n::AdjustStringForLocaleDirection(&text); - return text; - } - - std::u16string GetSecondaryLabelAt(int index) const override { - std::u16string text = model_->GetDropDownSecondaryTextAt(index); - base::i18n::AdjustStringForLocaleDirection(&text); - return text; } - - bool IsItemDynamicAt(int index) const override { return true; } - - const gfx::FontList* GetLabelFontListAt(int index) const override { - return &owner_->GetFontList(); - } - - bool GetAcceleratorAt(int index, - ui::Accelerator* accelerator) const override { - return false; - } - - bool IsItemCheckedAt(int index) const override { - return UseCheckmarks() && index == owner_->selected_index_; - } - - int GetGroupIdAt(int index) const override { return -1; } - - ui::ImageModel GetIconAt(int index) const override { - return model_->GetDropDownIconAt(index); - } - - ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override { - return nullptr; - } - - bool IsEnabledAt(int index) const override { - return model_->IsItemEnabledAt(index); - } - - void ActivatedAt(int index) override { - owner_->SetSelectedIndex(index); - owner_->OnPerformAction(); - } - - void ActivatedAt(int index, int event_flags) override { ActivatedAt(index); } - - MenuModel* GetSubmenuModelAt(int index) const override { return nullptr; } - - raw_ptr<Combobox> owner_; // Weak. Owns this. - raw_ptr<ui::ComboboxModel> model_; // Weak. }; +} // namespace + //////////////////////////////////////////////////////////////////////////////// // Combobox, public: @@ -241,9 +147,10 @@ Combobox::Combobox(ui::ComboboxModel* model, int text_context, int text_style) SetFocusBehavior(FocusBehavior::ALWAYS); #endif + SetBackgroundColorId(ui::kColorTextfieldBackground); UpdateBorder(); - arrow_button_->SetVisible(true); + arrow_button_->SetVisible(should_show_arrow_); AddChildView(arrow_button_.get()); // A layer is applied to make sure that canvas bounds are snapped to pixel @@ -265,7 +172,7 @@ const gfx::FontList& Combobox::GetFontList() const { return style::GetFont(text_context_, text_style_); } -void Combobox::SetSelectedIndex(int index) { +void Combobox::SetSelectedIndex(absl::optional<size_t> index) { if (selected_index_ == index) return; // TODO(pbos): Add (D)CHECKs to validate the selected index. @@ -284,7 +191,7 @@ base::CallbackListSubscription Combobox::AddSelectedIndexChangedCallback( } bool Combobox::SelectValue(const std::u16string& value) { - for (int i = 0; i < GetModel()->GetItemCount(); ++i) { + for (size_t i = 0; i < GetModel()->GetItemCount(); ++i) { if (value == GetModel()->GetItemAt(i)) { SetSelectedIndex(i); return true; @@ -353,6 +260,22 @@ void Combobox::SetInvalid(bool invalid) { OnPropertyChanged(&selected_index_, kPropertyEffectsPaint); } +void Combobox::SetBorderColorId(ui::ColorId color_id) { + border_color_id_ = color_id; + UpdateBorder(); +} + +void Combobox::SetBackgroundColorId(ui::ColorId color_id) { + SetBackground(CreateThemedRoundedRectBackground( + color_id, FocusableBorder::kCornerRadiusDp)); +} + +void Combobox::SetEventHighlighting(bool should_highlight) { + should_highlight_ = should_highlight; + AsViewClass<TransparentButton>(arrow_button_) + ->UpdateInkDrop(should_highlight); +} + void Combobox::SetSizeToLargestLabel(bool size_to_largest_label) { if (size_to_largest_label_ == size_to_largest_label) return; @@ -368,29 +291,25 @@ bool Combobox::IsMenuRunning() const { void Combobox::OnThemeChanged() { View::OnThemeChanged(); - SetBackground( - CreateBackgroundFromPainter(Painter::CreateSolidRoundRectPainter( - GetColorProvider()->GetColor(ui::kColorTextfieldBackground), - FocusableBorder::kCornerRadiusDp))); OnContentSizeMaybeChanged(); } -int Combobox::GetRowCount() { +size_t Combobox::GetRowCount() { return GetModel()->GetItemCount(); } -int Combobox::GetSelectedRow() { +absl::optional<size_t> Combobox::GetSelectedRow() { return selected_index_; } -void Combobox::SetSelectedRow(int row) { - int prev_index = selected_index_; +void Combobox::SetSelectedRow(absl::optional<size_t> row) { + absl::optional<size_t> prev_index = selected_index_; SetSelectedIndex(row); if (selected_index_ != prev_index) OnPerformAction(); } -std::u16string Combobox::GetTextForRow(int row) { +std::u16string Combobox::GetTextForRow(size_t row) { return GetModel()->IsItemSeparatorAt(row) ? std::u16string() : GetModel()->GetItemAt(row); } @@ -404,11 +323,17 @@ gfx::Size Combobox::CalculatePreferredSize() const { // The preferred size will drive the local bounds which in turn is used to set // the minimum width for the dropdown list. - const int width = std::max(kMinComboboxWidth, content_size_.width()) + - LayoutProvider::Get()->GetDistanceMetric( - DISTANCE_TEXTFIELD_HORIZONTAL_TEXT_PADDING) * - 2 + - kComboboxArrowContainerWidth + GetInsets().width(); + int width = std::max(kMinComboboxWidth, content_size_.width()) + + LayoutProvider::Get()->GetDistanceMetric( + DISTANCE_TEXTFIELD_HORIZONTAL_TEXT_PADDING) * + 2 + + GetInsets().width(); + + // If an arrow is being shown, add extra width to include that arrow. + if (should_show_arrow_) { + width += kComboboxArrowContainerWidth; + } + const int height = LayoutProvider::GetControlHeightForFont( text_context_, text_style_, GetFontList()); return gfx::Size(width, height); @@ -431,76 +356,86 @@ bool Combobox::OnKeyPressed(const ui::KeyEvent& e) { // TODO(oshima): handle IME. DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED); - // TODO(pbos): Do we need to handle selected_index_ == -1 for unselected here? - // Ditto on handling an empty model? - DCHECK_GE(selected_index_, 0); - DCHECK_LT(selected_index_, GetModel()->GetItemCount()); - if (selected_index_ < 0 || selected_index_ >= GetModel()->GetItemCount()) - SetSelectedIndex(0); + DCHECK(selected_index_.has_value()); + DCHECK_LT(selected_index_.value(), GetModel()->GetItemCount()); - bool show_menu = false; - int new_index = kNoSelection; - switch (e.key_code()) { #if BUILDFLAG(IS_MAC) - case ui::VKEY_DOWN: - case ui::VKEY_UP: - case ui::VKEY_SPACE: - case ui::VKEY_HOME: - case ui::VKEY_END: - // On Mac, navigation keys should always just show the menu first. - show_menu = true; - break; + if (e.key_code() != ui::VKEY_DOWN && e.key_code() != ui::VKEY_UP && + e.key_code() != ui::VKEY_SPACE && e.key_code() != ui::VKEY_HOME && + e.key_code() != ui::VKEY_END) { + return false; + } + ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD); + return true; #else + const auto index_at_or_after = [](ui::ComboboxModel* model, + size_t index) -> absl::optional<size_t> { + for (; index < model->GetItemCount(); ++index) { + if (!model->IsItemSeparatorAt(index) && model->IsItemEnabledAt(index)) + return index; + } + return absl::nullopt; + }; + const auto index_before = [](ui::ComboboxModel* model, + size_t index) -> absl::optional<size_t> { + for (; index > 0; --index) { + const auto prev = index - 1; + if (!model->IsItemSeparatorAt(prev) && model->IsItemEnabledAt(prev)) + return prev; + } + return absl::nullopt; + }; + + absl::optional<size_t> new_index; + switch (e.key_code()) { // Show the menu on F4 without modifiers. case ui::VKEY_F4: if (e.IsAltDown() || e.IsAltGrDown() || e.IsControlDown()) return false; - show_menu = true; - break; + ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD); + return true; // Move to the next item if any, or show the menu on Alt+Down like Windows. case ui::VKEY_DOWN: - if (e.IsAltDown()) - show_menu = true; - else - new_index = GetAdjacentIndex(GetModel(), 1, selected_index_); + if (e.IsAltDown()) { + ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD); + return true; + } + new_index = index_at_or_after(GetModel(), selected_index_.value() + 1); break; // Move to the end of the list. case ui::VKEY_END: case ui::VKEY_NEXT: // Page down. - new_index = GetAdjacentIndex(GetModel(), -1, GetModel()->GetItemCount()); + new_index = index_before(GetModel(), GetModel()->GetItemCount()); break; // Move to the beginning of the list. case ui::VKEY_HOME: case ui::VKEY_PRIOR: // Page up. - new_index = GetAdjacentIndex(GetModel(), 1, -1); + new_index = index_at_or_after(GetModel(), 0); break; // Move to the previous item if any. case ui::VKEY_UP: - new_index = GetAdjacentIndex(GetModel(), -1, selected_index_); + new_index = index_before(GetModel(), selected_index_.value()); break; case ui::VKEY_RETURN: case ui::VKEY_SPACE: - show_menu = true; - break; -#endif // BUILDFLAG(IS_MAC) + ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD); + return true; + default: return false; } - if (show_menu) { - ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD); - } else if (new_index != selected_index_ && new_index != kNoSelection) { - DCHECK(!GetModel()->IsItemSeparatorAt(new_index)); + if (new_index.has_value()) { SetSelectedIndex(new_index); OnPerformAction(); } - return true; +#endif // BUILDFLAG(IS_MAC) } void Combobox::OnPaint(gfx::Canvas* canvas) { @@ -540,14 +475,14 @@ void Combobox::GetAccessibleNodeData(ui::AXNodeData* node_data) { } node_data->SetName(accessible_name_); - node_data->SetValue(model_->GetItemAt(selected_index_)); + node_data->SetValue(model_->GetItemAt(selected_index_.value())); if (GetEnabled()) { node_data->SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kOpen); } node_data->AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, - selected_index_); + base::checked_cast<int>(selected_index_.value())); node_data->AddIntAttribute(ax::mojom::IntAttribute::kSetSize, - model_->GetItemCount()); + base::checked_cast<int>(model_->GetItemCount())); } bool Combobox::HandleAccessibleAction(const ui::AXActionData& action_data) { @@ -571,7 +506,7 @@ void Combobox::OnComboboxModelChanged(ui::ComboboxModel* model) { // default index. if (selected_index_ >= model_->GetItemCount() || model_->GetItemCount() == 0 || - model_->IsItemSeparatorAt(selected_index_)) { + model_->IsItemSeparatorAt(selected_index_.value())) { SetSelectedIndex(model_->GetDefaultIndex()); } @@ -592,6 +527,8 @@ const std::unique_ptr<ui::ComboboxModel>& Combobox::GetOwnedModel() const { void Combobox::UpdateBorder() { std::unique_ptr<FocusableBorder> border(new FocusableBorder()); + if (border_color_id_.has_value()) + border->SetColorId(border_color_id_.value()); if (invalid_) border->SetColorId(ui::kColorAlertHighSeverity); SetBorder(std::move(border)); @@ -613,46 +550,53 @@ void Combobox::PaintIconAndText(gfx::Canvas* canvas) { int y = insets.top(); int contents_height = height() - insets.height(); + DCHECK(selected_index_.has_value()); + DCHECK_LT(selected_index_.value(), GetModel()->GetItemCount()); + // Draw the icon. - ui::ImageModel icon = GetModel()->GetIconAt(selected_index_); + ui::ImageModel icon = GetModel()->GetIconAt(selected_index_.value()); if (!icon.IsEmpty()) { gfx::ImageSkia icon_skia = icon.Rasterize(GetColorProvider()); int icon_y = y + (contents_height - icon_skia.height()) / 2; gfx::Rect icon_bounds(x, icon_y, icon_skia.width(), icon_skia.height()); AdjustBoundsForRTLUI(&icon_bounds); canvas->DrawImageInt(icon_skia, icon_bounds.x(), icon_bounds.y()); - x += icon_skia.width() + LayoutProvider::Get()->GetDistanceMetric( - DISTANCE_RELATED_LABEL_HORIZONTAL); + x += icon_skia.width(); } // Draw the text. SkColor text_color = GetTextColorForEnableState(*this, GetEnabled()); - // TODO(pbos): Do we need to handle selected_index_ == -1 for unselected here? - // Ditto on handling an empty model? - DCHECK_GE(selected_index_, 0); - DCHECK_LT(selected_index_, GetModel()->GetItemCount()); - if (selected_index_ < 0 || selected_index_ >= GetModel()->GetItemCount()) - SetSelectedIndex(0); - - std::u16string text = GetModel()->GetItemAt(selected_index_); + std::u16string text = GetModel()->GetItemAt(selected_index_.value()); + const gfx::FontList& font_list = GetFontList(); - int disclosure_arrow_offset = width() - kComboboxArrowContainerWidth; + // If the text is not empty, add padding between it and the icon. If there + // was an empty icon, this padding is not necessary. + if (!text.empty() && !icon.IsEmpty()) { + x += LayoutProvider::Get()->GetDistanceMetric( + DISTANCE_RELATED_LABEL_HORIZONTAL); + } - const gfx::FontList& font_list = GetFontList(); + // The total width of the text is the minimum of either the string width, + // or the available space, accounting for optional arrow. int text_width = gfx::GetStringWidth(text, font_list); - text_width = - std::min(text_width, disclosure_arrow_offset - insets.right() - x); + int available_width = width() - x - insets.right(); + if (should_show_arrow_) { + available_width -= kComboboxArrowContainerWidth; + } + text_width = std::min(text_width, available_width); gfx::Rect text_bounds(x, y, text_width, contents_height); AdjustBoundsForRTLUI(&text_bounds); canvas->DrawStringRect(text, font_list, text_color, text_bounds); - gfx::Rect arrow_bounds(disclosure_arrow_offset, 0, - kComboboxArrowContainerWidth, height()); - arrow_bounds.ClampToCenteredSize(ComboboxArrowSize()); - AdjustBoundsForRTLUI(&arrow_bounds); - - PaintComboboxArrow(text_color, arrow_bounds, canvas); + // Draw the arrow. + if (should_show_arrow_) { + gfx::Rect arrow_bounds(width() - kComboboxArrowContainerWidth, 0, + kComboboxArrowContainerWidth, height()); + arrow_bounds.ClampToCenteredSize(ComboboxArrowSize()); + AdjustBoundsForRTLUI(&arrow_bounds); + PaintComboboxArrow(text_color, arrow_bounds, canvas); + } } void Combobox::ArrowButtonPressed(const ui::Event& event) { @@ -678,6 +622,10 @@ void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) { View::ConvertPointToScreen(this, &menu_position); gfx::Rect bounds(menu_position, lb.size()); + // If check marks exist in the combobox, adjust with bounds width to account + // for them. + if (!size_to_largest_label_) + bounds.set_width(MaybeAdjustWidthForCheckmarks(bounds.width())); Button::ButtonState original_state = arrow_button_->GetState(); arrow_button_->SetState(Button::STATE_PRESSED); @@ -690,18 +638,34 @@ void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) { base::BindRepeating(&Combobox::OnMenuClosed, base::Unretained(this), original_state)); } + if (should_highlight_) { + InkDrop::Get(arrow_button_) + ->AnimateToState(InkDropState::ACTIVATED, nullptr); + } menu_runner_->RunMenuAt(GetWidget(), nullptr, bounds, MenuAnchorPosition::kTopLeft, source_type); NotifyAccessibilityEvent(ax::mojom::Event::kExpandedChanged, true); } void Combobox::OnMenuClosed(Button::ButtonState original_button_state) { + if (should_highlight_) { + InkDrop::Get(arrow_button_) + ->AnimateToState(InkDropState::DEACTIVATED, nullptr); + InkDrop::Get(arrow_button_)->GetInkDrop()->SetHovered(IsMouseHovered()); + } menu_runner_.reset(); arrow_button_->SetState(original_button_state); closed_time_ = base::TimeTicks::Now(); NotifyAccessibilityEvent(ax::mojom::Event::kExpandedChanged, true); } +void Combobox::MenuSelectionAt(size_t index) { + if (!menu_selection_at_callback_ || !menu_selection_at_callback_.Run(index)) { + SetSelectedIndex(index); + OnPerformAction(); + } +} + void Combobox::OnPerformAction() { NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true); SchedulePaint(); @@ -716,33 +680,50 @@ gfx::Size Combobox::GetContentSize() const { const gfx::FontList& font_list = GetFontList(); int height = font_list.GetHeight(); int width = 0; - for (int i = 0; i < GetModel()->GetItemCount(); ++i) { + for (size_t i = 0; i < GetModel()->GetItemCount(); ++i) { if (model_->IsItemSeparatorAt(i)) continue; if (size_to_largest_label_ || i == selected_index_) { - int item_width = gfx::GetStringWidth(GetModel()->GetItemAt(i), font_list); + int item_width = 0; ui::ImageModel icon = GetModel()->GetIconAt(i); + std::u16string text = GetModel()->GetItemAt(i); if (!icon.IsEmpty()) { gfx::ImageSkia icon_skia; if (GetWidget()) icon_skia = icon.Rasterize(GetColorProvider()); - item_width += - icon_skia.width() + LayoutProvider::Get()->GetDistanceMetric( - DISTANCE_RELATED_LABEL_HORIZONTAL); - if (MenuConfig::instance().check_selected_combobox_item) { - item_width += - kMenuCheckSize + LayoutProvider::Get()->GetDistanceMetric( - DISTANCE_RELATED_BUTTON_HORIZONTAL); - } + item_width += icon_skia.width(); height = std::max(height, icon_skia.height()); + + // If both the text and icon are not empty, include padding between. + // We do not include this padding if there is no icon present. + if (!text.empty()) { + item_width += LayoutProvider::Get()->GetDistanceMetric( + DISTANCE_RELATED_LABEL_HORIZONTAL); + } + } + + // If text is not empty, the content size needs to include the text width + if (!text.empty()) { + item_width += gfx::GetStringWidth(GetModel()->GetItemAt(i), font_list); } + + if (size_to_largest_label_) + item_width = MaybeAdjustWidthForCheckmarks(item_width); width = std::max(width, item_width); } } return gfx::Size(width, height); } +int Combobox::MaybeAdjustWidthForCheckmarks(int original_width) const { + return MenuConfig::instance().check_selected_combobox_item + ? original_width + kMenuCheckSize + + LayoutProvider::Get()->GetDistanceMetric( + DISTANCE_RELATED_BUTTON_HORIZONTAL) + : original_width; +} + void Combobox::OnContentSizeMaybeChanged() { content_size_ = GetContentSize(); PreferredSizeChanged(); @@ -758,7 +739,7 @@ BEGIN_METADATA(Combobox, View) ADD_PROPERTY_METADATA(base::RepeatingClosure, Callback) ADD_PROPERTY_METADATA(std::unique_ptr<ui::ComboboxModel>, OwnedModel) ADD_PROPERTY_METADATA(ui::ComboboxModel*, Model) -ADD_PROPERTY_METADATA(int, SelectedIndex) +ADD_PROPERTY_METADATA(absl::optional<size_t>, SelectedIndex) ADD_PROPERTY_METADATA(bool, Invalid) ADD_PROPERTY_METADATA(bool, SizeToLargestLabel) ADD_PROPERTY_METADATA(std::u16string, AccessibleName) diff --git a/chromium/ui/views/controls/combobox/combobox.h b/chromium/ui/views/controls/combobox/combobox.h index 1a3f533256e..cebf64d887f 100644 --- a/chromium/ui/views/controls/combobox/combobox.h +++ b/chromium/ui/views/controls/combobox/combobox.h @@ -14,6 +14,8 @@ #include "base/time/time.h" #include "ui/base/models/combobox_model.h" #include "ui/base/models/combobox_model_observer.h" +#include "ui/base/models/menu_model.h" +#include "ui/color/color_id.h" #include "ui/views/controls/button/button.h" #include "ui/views/controls/prefix_delegate.h" #include "ui/views/metadata/view_factory.h" @@ -43,6 +45,8 @@ class VIEWS_EXPORT Combobox : public View, public: METADATA_HEADER(Combobox); + using MenuSelectionAtCallback = base::RepeatingCallback<bool(size_t index)>; + static constexpr int kDefaultComboboxTextContext = style::CONTEXT_BUTTON; static constexpr int kDefaultComboboxTextStyle = style::STYLE_PRIMARY; @@ -69,12 +73,20 @@ class VIEWS_EXPORT Combobox : public View, callback_ = std::move(callback); } + // Set menu model. + void SetMenuModel(std::unique_ptr<ui::MenuModel> menu_model) { + menu_model_ = std::move(menu_model); + } + // Gets/Sets the selected index. - int GetSelectedIndex() const { return selected_index_; } - void SetSelectedIndex(int index); + absl::optional<size_t> GetSelectedIndex() const { return selected_index_; } + void SetSelectedIndex(absl::optional<size_t> index); [[nodiscard]] base::CallbackListSubscription AddSelectedIndexChangedCallback( views::PropertyChangedCallback callback); + // Called when there has been a selection from the menu. + void MenuSelectionAt(size_t index); + // Looks for the first occurrence of |value| in |model()|. If found, selects // the found index and returns true. Otherwise simply noops and returns false. bool SelectValue(const std::u16string& value); @@ -99,10 +111,25 @@ class VIEWS_EXPORT Combobox : public View, void SetInvalid(bool invalid); bool GetInvalid() const { return invalid_; } + void SetBorderColorId(ui::ColorId color_id); + void SetBackgroundColorId(ui::ColorId color_id); + + // Sets whether there should be ink drop highlighting on hover/press. + void SetEventHighlighting(bool should_highlight); + // Whether the combobox should use the largest label as the content size. void SetSizeToLargestLabel(bool size_to_largest_label); bool GetSizeToLargestLabel() const { return size_to_largest_label_; } + void SetMenuSelectionAtCallback(MenuSelectionAtCallback callback) { + menu_selection_at_callback_ = std::move(callback); + } + + // Set whether the arrow should be shown to the user. + void SetShouldShowArrow(bool should_show_arrow) { + should_show_arrow_ = should_show_arrow; + } + // Use the time when combobox was closed in order for parent view to not // treat a user event already treated by the combobox. base::TimeTicks GetClosedTime() { return closed_time_; } @@ -123,10 +150,10 @@ class VIEWS_EXPORT Combobox : public View, void OnThemeChanged() override; // Overridden from PrefixDelegate: - int GetRowCount() override; - int GetSelectedRow() override; - void SetSelectedRow(int row) override; - std::u16string GetTextForRow(int row) override; + size_t GetRowCount() override; + absl::optional<size_t> GetSelectedRow() override; + void SetSelectedRow(absl::optional<size_t> row) override; + std::u16string GetTextForRow(size_t row) override; protected: // Overridden from ComboboxModelObserver: @@ -140,8 +167,6 @@ class VIEWS_EXPORT Combobox : public View, private: friend class test::ComboboxTestApi; - class ComboboxMenuModel; - // Updates the border according to the current node_data. void UpdateBorder(); @@ -166,6 +191,10 @@ class VIEWS_EXPORT Combobox : public View, // Finds the size of the largest menu label. gfx::Size GetContentSize() const; + // Returns the width needed to accommodate the provided width and checkmarks + // and padding if checkmarks should be shown. + int MaybeAdjustWidthForCheckmarks(int original_width) const; + void OnContentSizeMaybeChanged(); // Handles the clicking event. @@ -191,12 +220,27 @@ class VIEWS_EXPORT Combobox : public View, // Callback notified when the selected index changes. base::RepeatingClosure callback_; - // The current selected index; -1 and means no selection. - int selected_index_ = -1; + // Callback notified when the selected index is triggered to change. If set, + // when a selection is made in the combobox this callback is called. If it + // returns true no other action is taken, if it returns false then the model + // will updated based on the selection. + MenuSelectionAtCallback menu_selection_at_callback_; + + // The current selected index; nullopt means no selection. + absl::optional<size_t> selected_index_ = absl::nullopt; // True when the selection is visually denoted as invalid. bool invalid_ = false; + // True when there should be ink drop highlighting on hover and press. + bool should_highlight_ = false; + + // True when the combobox should display the arrow during paint. + bool should_show_arrow_ = true; + + // Overriding ColorId for the combobox border. + absl::optional<ui::ColorId> border_color_id_; + // The accessible name of this combobox. std::u16string accessible_name_; @@ -226,9 +270,10 @@ class VIEWS_EXPORT Combobox : public View, // destroyed. std::unique_ptr<MenuRunner> menu_runner_; - // When true, the size of contents is defined by the selected label. - // Otherwise, it's defined by the widest label in the menu. If this is set to - // true, the parent view must relayout in ChildPreferredSizeChanged(). + // When true, the size of contents is defined by the widest label in the menu. + // If this is set to true, the parent view must relayout in + // ChildPreferredSizeChanged(). When false, the size of contents is defined by + // the selected label bool size_to_largest_label_ = true; base::ScopedObservation<ui::ComboboxModel, ui::ComboboxModelObserver> @@ -239,7 +284,7 @@ BEGIN_VIEW_BUILDER(VIEWS_EXPORT, Combobox, View) VIEW_BUILDER_PROPERTY(base::RepeatingClosure, Callback) VIEW_BUILDER_PROPERTY(std::unique_ptr<ui::ComboboxModel>, OwnedModel) VIEW_BUILDER_PROPERTY(ui::ComboboxModel*, Model) -VIEW_BUILDER_PROPERTY(int, SelectedIndex) +VIEW_BUILDER_PROPERTY(absl::optional<size_t>, SelectedIndex) VIEW_BUILDER_PROPERTY(bool, Invalid) VIEW_BUILDER_PROPERTY(bool, SizeToLargestLabel) VIEW_BUILDER_PROPERTY(std::u16string, AccessibleName) diff --git a/chromium/ui/views/controls/combobox/combobox_menu_model.cc b/chromium/ui/views/controls/combobox/combobox_menu_model.cc new file mode 100644 index 00000000000..156c1784882 --- /dev/null +++ b/chromium/ui/views/controls/combobox/combobox_menu_model.cc @@ -0,0 +1,105 @@ +// Copyright 2022 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/views/controls/combobox/combobox_menu_model.h" + +ComboboxMenuModel::ComboboxMenuModel(views::Combobox* owner, + ui::ComboboxModel* model) + : owner_(owner), model_(model) {} + +ComboboxMenuModel::~ComboboxMenuModel() = default; + +bool ComboboxMenuModel::UseCheckmarks() const { + return views::MenuConfig::instance().check_selected_combobox_item; +} + +// Overridden from MenuModel: +bool ComboboxMenuModel::HasIcons() const { + for (size_t i = 0; i < GetItemCount(); ++i) { + if (!GetIconAt(i).IsEmpty()) + return true; + } + return false; +} + +size_t ComboboxMenuModel::GetItemCount() const { + return model_->GetItemCount(); +} + +ui::MenuModel::ItemType ComboboxMenuModel::GetTypeAt(size_t index) const { + if (model_->IsItemSeparatorAt(index)) + return TYPE_SEPARATOR; + return UseCheckmarks() ? TYPE_CHECK : TYPE_COMMAND; +} + +ui::MenuSeparatorType ComboboxMenuModel::GetSeparatorTypeAt( + size_t index) const { + return ui::NORMAL_SEPARATOR; +} + +int ComboboxMenuModel::GetCommandIdAt(size_t index) const { + // Define the id of the first item in the menu (since it needs to be > 0) + constexpr int kFirstMenuItemId = 1000; + return static_cast<int>(index) + kFirstMenuItemId; +} + +std::u16string ComboboxMenuModel::GetLabelAt(size_t index) const { + // Inserting the Unicode formatting characters if necessary so that the + // text is displayed correctly in right-to-left UIs. + std::u16string text = model_->GetDropDownTextAt(index); + base::i18n::AdjustStringForLocaleDirection(&text); + return text; +} + +std::u16string ComboboxMenuModel::GetSecondaryLabelAt(size_t index) const { + std::u16string text = model_->GetDropDownSecondaryTextAt(index); + base::i18n::AdjustStringForLocaleDirection(&text); + return text; +} + +bool ComboboxMenuModel::IsItemDynamicAt(size_t index) const { + return true; +} + +const gfx::FontList* ComboboxMenuModel::GetLabelFontListAt(size_t index) const { + return &owner_->GetFontList(); +} + +bool ComboboxMenuModel::GetAcceleratorAt(size_t index, + ui::Accelerator* accelerator) const { + return false; +} + +bool ComboboxMenuModel::IsItemCheckedAt(size_t index) const { + return UseCheckmarks() && index == owner_->GetSelectedIndex(); +} + +int ComboboxMenuModel::GetGroupIdAt(size_t index) const { + return -1; +} + +ui::ImageModel ComboboxMenuModel::GetIconAt(size_t index) const { + return model_->GetDropDownIconAt(index); +} + +ui::ButtonMenuItemModel* ComboboxMenuModel::GetButtonMenuItemAt( + size_t index) const { + return nullptr; +} + +bool ComboboxMenuModel::IsEnabledAt(size_t index) const { + return model_->IsItemEnabledAt(index); +} + +void ComboboxMenuModel::ActivatedAt(size_t index) { + owner_->MenuSelectionAt(index); +} + +void ComboboxMenuModel::ActivatedAt(size_t index, int event_flags) { + ActivatedAt(index); +} + +ui::MenuModel* ComboboxMenuModel::GetSubmenuModelAt(size_t index) const { + return nullptr; +} diff --git a/chromium/ui/views/controls/combobox/combobox_menu_model.h b/chromium/ui/views/controls/combobox/combobox_menu_model.h new file mode 100644 index 00000000000..0656b735a3a --- /dev/null +++ b/chromium/ui/views/controls/combobox/combobox_menu_model.h @@ -0,0 +1,54 @@ +// Copyright 2022 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_VIEWS_CONTROLS_COMBOBOX_COMBOBOX_MENU_MODEL_H_ +#define UI_VIEWS_CONTROLS_COMBOBOX_COMBOBOX_MENU_MODEL_H_ + +#include "base/i18n/rtl.h" +#include "ui/base/models/combobox_model.h" +#include "ui/base/models/image_model.h" +#include "ui/base/models/menu_model.h" +#include "ui/views/controls/combobox/combobox.h" +#include "ui/views/controls/menu/menu_config.h" + +// Adapts a ui::ComboboxModel to a ui::MenuModel. +class VIEWS_EXPORT ComboboxMenuModel : public ui::MenuModel { + public: + ComboboxMenuModel(views::Combobox* owner, ui::ComboboxModel* model); + ComboboxMenuModel(const ComboboxMenuModel&) = delete; + ComboboxMenuModel& operator&(const ComboboxMenuModel&) = delete; + ~ComboboxMenuModel() override; + + protected: + ui::ComboboxModel* GetModel() const { return model_; } + + private: + bool UseCheckmarks() const; + + // Overridden from MenuModel: + bool HasIcons() const override; + size_t GetItemCount() const override; + ui::MenuModel::ItemType GetTypeAt(size_t index) const override; + ui::MenuSeparatorType GetSeparatorTypeAt(size_t index) const override; + int GetCommandIdAt(size_t index) const override; + std::u16string GetLabelAt(size_t index) const override; + std::u16string GetSecondaryLabelAt(size_t index) const override; + bool IsItemDynamicAt(size_t index) const override; + const gfx::FontList* GetLabelFontListAt(size_t index) const override; + bool GetAcceleratorAt(size_t index, + ui::Accelerator* accelerator) const override; + bool IsItemCheckedAt(size_t index) const override; + int GetGroupIdAt(size_t index) const override; + ui::ImageModel GetIconAt(size_t index) const override; + ui::ButtonMenuItemModel* GetButtonMenuItemAt(size_t index) const override; + bool IsEnabledAt(size_t index) const override; + void ActivatedAt(size_t index) override; + void ActivatedAt(size_t index, int event_flags) override; + ui::MenuModel* GetSubmenuModelAt(size_t index) const override; + + raw_ptr<views::Combobox> owner_; // Weak. Owns this. + raw_ptr<ui::ComboboxModel> model_; // Weak. +}; + +#endif // UI_VIEWS_CONTROLS_COMBOBOX_COMBOBOX_MENU_MODEL_H_ diff --git a/chromium/ui/views/controls/combobox/combobox_unittest.cc b/chromium/ui/views/controls/combobox/combobox_unittest.cc index 91661f33074..43be08d5b9c 100644 --- a/chromium/ui/views/controls/combobox/combobox_unittest.cc +++ b/chromium/ui/views/controls/combobox/combobox_unittest.cc @@ -59,24 +59,21 @@ class TestComboboxModel : public ui::ComboboxModel { ~TestComboboxModel() override = default; - enum { kItemCount = 10 }; + static constexpr size_t kItemCount = 10; // ui::ComboboxModel: - int GetItemCount() const override { return item_count_; } - std::u16string GetItemAt(int index) const override { - if (IsItemSeparatorAt(index)) { - NOTREACHED(); - return u"SEPARATOR"; - } + size_t GetItemCount() const override { return item_count_; } + std::u16string GetItemAt(size_t index) const override { + DCHECK(!IsItemSeparatorAt(index)); return ASCIIToUTF16(index % 2 == 0 ? "PEANUT BUTTER" : "JELLY"); } - bool IsItemSeparatorAt(int index) const override { + bool IsItemSeparatorAt(size_t index) const override { return separators_.find(index) != separators_.end(); } - int GetDefaultIndex() const override { + absl::optional<size_t> GetDefaultIndex() const override { // Return the first index that is not a separator. - for (int index = 0; index < kItemCount; ++index) { + for (size_t index = 0; index < kItemCount; ++index) { if (separators_.find(index) == separators_.end()) return index; } @@ -84,12 +81,12 @@ class TestComboboxModel : public ui::ComboboxModel { return 0; } - void SetSeparators(const std::set<int>& separators) { + void SetSeparators(const std::set<size_t>& separators) { separators_ = separators; OnModelChanged(); } - void set_item_count(int item_count) { + void set_item_count(size_t item_count) { item_count_ = item_count; OnModelChanged(); } @@ -100,8 +97,8 @@ class TestComboboxModel : public ui::ComboboxModel { observer.OnComboboxModelChanged(this); } - std::set<int> separators_; - int item_count_ = kItemCount; + std::set<size_t> separators_; + size_t item_count_ = kItemCount; }; // A combobox model which refers to a vector. @@ -115,17 +112,19 @@ class VectorComboboxModel : public ui::ComboboxModel { ~VectorComboboxModel() override = default; - void set_default_index(int default_index) { default_index_ = default_index; } + void set_default_index(size_t default_index) { + default_index_ = default_index; + } // ui::ComboboxModel: - int GetItemCount() const override { - return static_cast<int>(values_->size()); + size_t GetItemCount() const override { return values_->size(); } + std::u16string GetItemAt(size_t index) const override { + return ASCIIToUTF16((*values_)[index]); } - std::u16string GetItemAt(int index) const override { - return ASCIIToUTF16(values_->at(index)); + bool IsItemSeparatorAt(size_t index) const override { return false; } + absl::optional<size_t> GetDefaultIndex() const override { + return default_index_; } - bool IsItemSeparatorAt(int index) const override { return false; } - int GetDefaultIndex() const override { return default_index_; } void ValuesChanged() { for (auto& observer : observers()) @@ -133,7 +132,7 @@ class VectorComboboxModel : public ui::ComboboxModel { } private: - int default_index_ = 0; + size_t default_index_ = 0; const raw_ptr<std::vector<std::string>> values_; }; @@ -173,7 +172,9 @@ class TestComboboxListener { actions_performed_++; } - int perform_action_index() const { return perform_action_index_; } + absl::optional<size_t> perform_action_index() const { + return perform_action_index_; + } bool on_perform_action_called() const { return actions_performed_ > 0; } @@ -181,7 +182,7 @@ class TestComboboxListener { private: raw_ptr<Combobox> combobox_; - int perform_action_index_ = -1; + absl::optional<size_t> perform_action_index_ = absl::nullopt; int actions_performed_ = 0; }; @@ -199,7 +200,7 @@ class ComboboxTest : public ViewsTestBase { ViewsTestBase::TearDown(); } - void InitCombobox(const std::set<int>* separators) { + void InitCombobox(const std::set<size_t>* separators) { model_ = std::make_unique<TestComboboxModel>(); if (separators) @@ -283,43 +284,43 @@ class ComboboxTest : public ViewsTestBase { TEST_F(ComboboxTest, KeyTestMac) { InitCombobox(nullptr); PressKey(ui::VKEY_END); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); EXPECT_EQ(1, menu_show_count_); PressKey(ui::VKEY_HOME); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); EXPECT_EQ(2, menu_show_count_); PressKey(ui::VKEY_UP, ui::EF_COMMAND_DOWN); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); EXPECT_EQ(3, menu_show_count_); PressKey(ui::VKEY_DOWN, ui::EF_COMMAND_DOWN); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); EXPECT_EQ(4, menu_show_count_); PressKey(ui::VKEY_DOWN); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); EXPECT_EQ(5, menu_show_count_); PressKey(ui::VKEY_RIGHT); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); EXPECT_EQ(5, menu_show_count_); PressKey(ui::VKEY_LEFT); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); EXPECT_EQ(5, menu_show_count_); PressKey(ui::VKEY_UP); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); EXPECT_EQ(6, menu_show_count_); PressKey(ui::VKEY_PRIOR); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); EXPECT_EQ(6, menu_show_count_); PressKey(ui::VKEY_NEXT); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); EXPECT_EQ(6, menu_show_count_); } #endif @@ -358,153 +359,153 @@ TEST_F(ComboboxTest, DisabilityTest) { TEST_F(ComboboxTest, KeyTest) { InitCombobox(nullptr); PressKey(ui::VKEY_END); - EXPECT_EQ(model_->GetItemCount(), combobox_->GetSelectedIndex() + 1); + EXPECT_EQ(model_->GetItemCount() - 1, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_HOME); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_DOWN); PressKey(ui::VKEY_DOWN); - EXPECT_EQ(2, combobox_->GetSelectedIndex()); + EXPECT_EQ(2u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_RIGHT); - EXPECT_EQ(2, combobox_->GetSelectedIndex()); + EXPECT_EQ(2u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_LEFT); - EXPECT_EQ(2, combobox_->GetSelectedIndex()); + EXPECT_EQ(2u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_UP); - EXPECT_EQ(1, combobox_->GetSelectedIndex()); + EXPECT_EQ(1u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_PRIOR); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_NEXT); - EXPECT_EQ(model_->GetItemCount(), combobox_->GetSelectedIndex() + 1); + EXPECT_EQ(model_->GetItemCount() - 1, combobox_->GetSelectedIndex()); } // Verifies that we don't select a separator line in combobox when navigating // through keyboard. TEST_F(ComboboxTest, SkipSeparatorSimple) { - std::set<int> separators; + std::set<size_t> separators; separators.insert(2); InitCombobox(&separators); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_DOWN); - EXPECT_EQ(1, combobox_->GetSelectedIndex()); + EXPECT_EQ(1u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_DOWN); - EXPECT_EQ(3, combobox_->GetSelectedIndex()); + EXPECT_EQ(3u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_UP); - EXPECT_EQ(1, combobox_->GetSelectedIndex()); + EXPECT_EQ(1u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_HOME); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_PRIOR); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_END); - EXPECT_EQ(9, combobox_->GetSelectedIndex()); + EXPECT_EQ(9u, combobox_->GetSelectedIndex()); } // Verifies that we never select the separator that is in the beginning of the // combobox list when navigating through keyboard. TEST_F(ComboboxTest, SkipSeparatorBeginning) { - std::set<int> separators; + std::set<size_t> separators; separators.insert(0); InitCombobox(&separators); - EXPECT_EQ(1, combobox_->GetSelectedIndex()); + EXPECT_EQ(1u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_DOWN); - EXPECT_EQ(2, combobox_->GetSelectedIndex()); + EXPECT_EQ(2u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_DOWN); - EXPECT_EQ(3, combobox_->GetSelectedIndex()); + EXPECT_EQ(3u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_UP); - EXPECT_EQ(2, combobox_->GetSelectedIndex()); + EXPECT_EQ(2u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_HOME); - EXPECT_EQ(1, combobox_->GetSelectedIndex()); + EXPECT_EQ(1u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_PRIOR); - EXPECT_EQ(1, combobox_->GetSelectedIndex()); + EXPECT_EQ(1u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_END); - EXPECT_EQ(9, combobox_->GetSelectedIndex()); + EXPECT_EQ(9u, combobox_->GetSelectedIndex()); } // Verifies that we never select the separator that is in the end of the // combobox list when navigating through keyboard. TEST_F(ComboboxTest, SkipSeparatorEnd) { - std::set<int> separators; + std::set<size_t> separators; separators.insert(TestComboboxModel::kItemCount - 1); InitCombobox(&separators); combobox_->SetSelectedIndex(8); PressKey(ui::VKEY_DOWN); - EXPECT_EQ(8, combobox_->GetSelectedIndex()); + EXPECT_EQ(8u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_UP); - EXPECT_EQ(7, combobox_->GetSelectedIndex()); + EXPECT_EQ(7u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_END); - EXPECT_EQ(8, combobox_->GetSelectedIndex()); + EXPECT_EQ(8u, combobox_->GetSelectedIndex()); } // Verifies that we never select any of the adjacent separators (multiple // consecutive) that appear in the beginning of the combobox list when // navigating through keyboard. TEST_F(ComboboxTest, SkipMultipleSeparatorsAtBeginning) { - std::set<int> separators; + std::set<size_t> separators; separators.insert(0); separators.insert(1); separators.insert(2); InitCombobox(&separators); - EXPECT_EQ(3, combobox_->GetSelectedIndex()); + EXPECT_EQ(3u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_DOWN); - EXPECT_EQ(4, combobox_->GetSelectedIndex()); + EXPECT_EQ(4u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_UP); - EXPECT_EQ(3, combobox_->GetSelectedIndex()); + EXPECT_EQ(3u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_NEXT); - EXPECT_EQ(9, combobox_->GetSelectedIndex()); + EXPECT_EQ(9u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_HOME); - EXPECT_EQ(3, combobox_->GetSelectedIndex()); + EXPECT_EQ(3u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_END); - EXPECT_EQ(9, combobox_->GetSelectedIndex()); + EXPECT_EQ(9u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_PRIOR); - EXPECT_EQ(3, combobox_->GetSelectedIndex()); + EXPECT_EQ(3u, combobox_->GetSelectedIndex()); } // Verifies that we never select any of the adjacent separators (multiple // consecutive) that appear in the middle of the combobox list when navigating // through keyboard. TEST_F(ComboboxTest, SkipMultipleAdjacentSeparatorsAtMiddle) { - std::set<int> separators; + std::set<size_t> separators; separators.insert(4); separators.insert(5); separators.insert(6); InitCombobox(&separators); combobox_->SetSelectedIndex(3); PressKey(ui::VKEY_DOWN); - EXPECT_EQ(7, combobox_->GetSelectedIndex()); + EXPECT_EQ(7u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_UP); - EXPECT_EQ(3, combobox_->GetSelectedIndex()); + EXPECT_EQ(3u, combobox_->GetSelectedIndex()); } // Verifies that we never select any of the adjacent separators (multiple // consecutive) that appear in the end of the combobox list when navigating // through keyboard. TEST_F(ComboboxTest, SkipMultipleSeparatorsAtEnd) { - std::set<int> separators; + std::set<size_t> separators; separators.insert(7); separators.insert(8); separators.insert(9); InitCombobox(&separators); combobox_->SetSelectedIndex(6); PressKey(ui::VKEY_DOWN); - EXPECT_EQ(6, combobox_->GetSelectedIndex()); + EXPECT_EQ(6u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_UP); - EXPECT_EQ(5, combobox_->GetSelectedIndex()); + EXPECT_EQ(5u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_HOME); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_NEXT); - EXPECT_EQ(6, combobox_->GetSelectedIndex()); + EXPECT_EQ(6u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_PRIOR); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); PressKey(ui::VKEY_END); - EXPECT_EQ(6, combobox_->GetSelectedIndex()); + EXPECT_EQ(6u, combobox_->GetSelectedIndex()); } #endif // !BUILDFLAG(IS_MAC) TEST_F(ComboboxTest, GetTextForRowTest) { - std::set<int> separators; + std::set<size_t> separators; separators.insert(0); separators.insert(1); separators.insert(9); InitCombobox(&separators); - for (int i = 0; i < combobox_->GetRowCount(); ++i) { + for (size_t i = 0; i < combobox_->GetRowCount(); ++i) { if (separators.count(i) != 0) { EXPECT_TRUE(combobox_->GetTextForRow(i).empty()) << i; } else { @@ -520,11 +521,11 @@ TEST_F(ComboboxTest, SelectValue) { InitCombobox(nullptr); ASSERT_EQ(model_->GetDefaultIndex(), combobox_->GetSelectedIndex()); EXPECT_TRUE(combobox_->SelectValue(u"PEANUT BUTTER")); - EXPECT_EQ(0, combobox_->GetSelectedIndex()); + EXPECT_EQ(0u, combobox_->GetSelectedIndex()); EXPECT_TRUE(combobox_->SelectValue(u"JELLY")); - EXPECT_EQ(1, combobox_->GetSelectedIndex()); + EXPECT_EQ(1u, combobox_->GetSelectedIndex()); EXPECT_FALSE(combobox_->SelectValue(u"BANANAS")); - EXPECT_EQ(1, combobox_->GetSelectedIndex()); + EXPECT_EQ(1u, combobox_->GetSelectedIndex()); } TEST_F(ComboboxTest, ListenerHandlesDelete) { @@ -541,7 +542,7 @@ TEST_F(ComboboxTest, Click) { TestComboboxListener listener(combobox_); combobox_->SetCallback(base::BindRepeating( &TestComboboxListener::OnPerformAction, base::Unretained(&listener))); - combobox_->Layout(); + RunScheduledLayout(combobox_); // Click the left side. The menu is shown. EXPECT_EQ(0, menu_show_count_); @@ -558,7 +559,7 @@ TEST_F(ComboboxTest, ClickButDisabled) { combobox_->SetCallback(base::BindRepeating( &TestComboboxListener::OnPerformAction, base::Unretained(&listener))); - combobox_->Layout(); + RunScheduledLayout(combobox_); combobox_->SetEnabled(false); // Click the left side, but nothing happens since the combobox is disabled. @@ -636,7 +637,7 @@ TEST_F(ComboboxTest, NotifyOnClickWithMouse) { combobox_->SetCallback(base::BindRepeating( &TestComboboxListener::OnPerformAction, base::Unretained(&listener))); - combobox_->Layout(); + RunScheduledLayout(combobox_); // Click the right side (arrow button). The menu is shown. const gfx::Point right_point(combobox_->x() + combobox_->width() - 1, @@ -659,7 +660,7 @@ TEST_F(ComboboxTest, NotifyOnClickWithMouse) { // Both the text and the arrow may toggle the menu. EXPECT_EQ(2, menu_show_count_); - EXPECT_EQ(-1, listener.perform_action_index()); // Nothing selected. + EXPECT_FALSE(listener.perform_action_index().has_value()); } TEST_F(ComboboxTest, ConsumingPressKeyEvents) { @@ -743,39 +744,39 @@ TEST_F(ComboboxTest, ContentWidth) { TEST_F(ComboboxTest, ModelChanged) { InitCombobox(nullptr); - EXPECT_EQ(0, combobox_->GetSelectedRow()); - EXPECT_EQ(10, combobox_->GetRowCount()); + EXPECT_EQ(0u, combobox_->GetSelectedRow()); + EXPECT_EQ(10u, combobox_->GetRowCount()); combobox_->SetSelectedIndex(4); - EXPECT_EQ(4, combobox_->GetSelectedRow()); + EXPECT_EQ(4u, combobox_->GetSelectedRow()); model_->set_item_count(5); - EXPECT_EQ(5, combobox_->GetRowCount()); - EXPECT_EQ(4, combobox_->GetSelectedRow()); // Unchanged. + EXPECT_EQ(5u, combobox_->GetRowCount()); + EXPECT_EQ(4u, combobox_->GetSelectedRow()); // Unchanged. model_->set_item_count(4); - EXPECT_EQ(4, combobox_->GetRowCount()); - EXPECT_EQ(0, combobox_->GetSelectedRow()); // Resets. + EXPECT_EQ(4u, combobox_->GetRowCount()); + EXPECT_EQ(0u, combobox_->GetSelectedRow()); // Resets. // Restore a non-zero selection. combobox_->SetSelectedIndex(2); - EXPECT_EQ(2, combobox_->GetSelectedRow()); + EXPECT_EQ(2u, combobox_->GetSelectedRow()); // Make the selected index a separator. - std::set<int> separators; + std::set<size_t> separators; separators.insert(2); model_->SetSeparators(separators); - EXPECT_EQ(4, combobox_->GetRowCount()); - EXPECT_EQ(0, combobox_->GetSelectedRow()); // Resets. + EXPECT_EQ(4u, combobox_->GetRowCount()); + EXPECT_EQ(0u, combobox_->GetSelectedRow()); // Resets. // Restore a non-zero selection. combobox_->SetSelectedIndex(1); - EXPECT_EQ(1, combobox_->GetSelectedRow()); + EXPECT_EQ(1u, combobox_->GetSelectedRow()); // Test an empty model. model_->set_item_count(0); - EXPECT_EQ(0, combobox_->GetRowCount()); - EXPECT_EQ(0, combobox_->GetSelectedRow()); // Resets. + EXPECT_EQ(0u, combobox_->GetRowCount()); + EXPECT_EQ(0u, combobox_->GetSelectedRow()); // Resets. } TEST_F(ComboboxTest, TypingPrefixNotifiesListener) { @@ -793,7 +794,7 @@ TEST_F(ComboboxTest, TypingPrefixNotifiesListener) { input_client->InsertChar(key_event); EXPECT_EQ(1, listener.actions_performed()); - EXPECT_EQ(1, listener.perform_action_index()); + EXPECT_EQ(1u, listener.perform_action_index()); // Type the second character of "JELLY", item shouldn't change and // OnPerformAction() shouldn't be re-called. @@ -802,7 +803,7 @@ TEST_F(ComboboxTest, TypingPrefixNotifiesListener) { ui::DomKey::FromCharacter('E'), ui::EventTimeForNow()); input_client->InsertChar(key_event); EXPECT_EQ(1, listener.actions_performed()); - EXPECT_EQ(1, listener.perform_action_index()); + EXPECT_EQ(1u, listener.perform_action_index()); // Clears the typed text. combobox_->OnBlur(); @@ -815,13 +816,13 @@ TEST_F(ComboboxTest, TypingPrefixNotifiesListener) { ui::DomKey::FromCharacter('P'), ui::EventTimeForNow()); input_client->InsertChar(key_event); EXPECT_EQ(2, listener.actions_performed()); - EXPECT_EQ(2, listener.perform_action_index()); + EXPECT_EQ(2u, listener.perform_action_index()); } // Test properties on the Combobox menu model. TEST_F(ComboboxTest, MenuModel) { const int kSeparatorIndex = 3; - std::set<int> separators; + std::set<size_t> separators; separators.insert(kSeparatorIndex); InitCombobox(&separators); @@ -898,29 +899,31 @@ class ConfigurableComboboxModel final : public ui::ComboboxModel { } // ui::ComboboxModel: - int GetItemCount() const override { return item_count_; } - std::u16string GetItemAt(int index) const override { + size_t GetItemCount() const override { return item_count_; } + std::u16string GetItemAt(size_t index) const override { DCHECK_LT(index, item_count_); return base::NumberToString16(index); } - int GetDefaultIndex() const override { return default_index_; } + absl::optional<size_t> GetDefaultIndex() const override { + return default_index_; + } - void SetItemCount(int item_count) { item_count_ = item_count; } + void SetItemCount(size_t item_count) { item_count_ = item_count; } - void SetDefaultIndex(int default_index) { default_index_ = default_index; } + void SetDefaultIndex(size_t default_index) { default_index_ = default_index; } private: const raw_ptr<bool> destroyed_; - int item_count_ = 0; - int default_index_ = -1; + size_t item_count_ = 0; + absl::optional<size_t> default_index_; }; } // namespace TEST_F(ComboboxDefaultTest, Default) { auto combobox = std::make_unique<Combobox>(); - EXPECT_EQ(0, combobox->GetRowCount()); - EXPECT_EQ(-1, combobox->GetSelectedRow()); + EXPECT_EQ(0u, combobox->GetRowCount()); + EXPECT_FALSE(combobox->GetSelectedRow().has_value()); } TEST_F(ComboboxDefaultTest, SetModel) { @@ -932,8 +935,8 @@ TEST_F(ComboboxDefaultTest, SetModel) { { auto combobox = std::make_unique<Combobox>(); combobox->SetModel(model.get()); - EXPECT_EQ(42, combobox->GetRowCount()); - EXPECT_EQ(27, combobox->GetSelectedRow()); + EXPECT_EQ(42u, combobox->GetRowCount()); + EXPECT_EQ(27u, combobox->GetSelectedRow()); } EXPECT_FALSE(destroyed); } @@ -947,8 +950,8 @@ TEST_F(ComboboxDefaultTest, SetOwnedModel) { { auto combobox = std::make_unique<Combobox>(); combobox->SetOwnedModel(std::move(model)); - EXPECT_EQ(42, combobox->GetRowCount()); - EXPECT_EQ(27, combobox->GetSelectedRow()); + EXPECT_EQ(42u, combobox->GetRowCount()); + EXPECT_EQ(27u, combobox->GetSelectedRow()); } EXPECT_TRUE(destroyed); } diff --git a/chromium/ui/views/controls/combobox/empty_combobox_model.cc b/chromium/ui/views/controls/combobox/empty_combobox_model.cc index becec4d176b..9c1c3edfd1e 100644 --- a/chromium/ui/views/controls/combobox/empty_combobox_model.cc +++ b/chromium/ui/views/controls/combobox/empty_combobox_model.cc @@ -14,17 +14,17 @@ namespace internal { EmptyComboboxModel::EmptyComboboxModel() = default; EmptyComboboxModel::~EmptyComboboxModel() = default; -int EmptyComboboxModel::GetItemCount() const { +size_t EmptyComboboxModel::GetItemCount() const { return 0; } -std::u16string EmptyComboboxModel::GetItemAt(int index) const { +std::u16string EmptyComboboxModel::GetItemAt(size_t index) const { NOTREACHED(); return std::u16string(); } -int EmptyComboboxModel::GetDefaultIndex() const { - return -1; +absl::optional<size_t> EmptyComboboxModel::GetDefaultIndex() const { + return absl::nullopt; } } // namespace internal diff --git a/chromium/ui/views/controls/combobox/empty_combobox_model.h b/chromium/ui/views/controls/combobox/empty_combobox_model.h index d7f5ca6d768..ec1a57db70a 100644 --- a/chromium/ui/views/controls/combobox/empty_combobox_model.h +++ b/chromium/ui/views/controls/combobox/empty_combobox_model.h @@ -19,9 +19,9 @@ class EmptyComboboxModel final : public ui::ComboboxModel { ~EmptyComboboxModel() override; // ui::ComboboxModel: - int GetItemCount() const override; - std::u16string GetItemAt(int index) const override; - int GetDefaultIndex() const override; + size_t GetItemCount() const override; + std::u16string GetItemAt(size_t index) const override; + absl::optional<size_t> GetDefaultIndex() const override; }; } // namespace internal diff --git a/chromium/ui/views/controls/editable_combobox/editable_combobox.cc b/chromium/ui/views/controls/editable_combobox/editable_combobox.cc index faac607fefc..2acc0d9f511 100644 --- a/chromium/ui/views/controls/editable_combobox/editable_combobox.cc +++ b/chromium/ui/views/controls/editable_combobox/editable_combobox.cc @@ -156,12 +156,11 @@ class EditableCombobox::EditableComboboxMenuModel return; items_shown_.clear(); if (show_on_empty_ || !owner_->GetText().empty()) { - for (int i = 0; i < combobox_model_->GetItemCount(); ++i) { + for (size_t i = 0; i < combobox_model_->GetItemCount(); ++i) { if (!filter_on_edit_ || base::StartsWith(combobox_model_->GetItemAt(i), owner_->GetText(), base::CompareCase::INSENSITIVE_ASCII)) { - items_shown_.push_back( - {static_cast<size_t>(i), combobox_model_->IsItemEnabledAt(i)}); + items_shown_.push_back({i, combobox_model_->IsItemEnabledAt(i)}); } } } @@ -177,8 +176,8 @@ class EditableCombobox::EditableComboboxMenuModel return MenuConfig::instance().check_selected_combobox_item; } - std::u16string GetItemTextAt(int index, bool showing_password_text) const { - int index_in_model = items_shown_[index].index; + std::u16string GetItemTextAt(size_t index, bool showing_password_text) const { + size_t index_in_model = items_shown_[index].index; std::u16string text = combobox_model_->GetItemAt(index_in_model); return showing_password_text ? text @@ -186,7 +185,7 @@ class EditableCombobox::EditableComboboxMenuModel gfx::RenderText::kPasswordReplacementChar); } - ui::ImageModel GetIconAt(int index) const override { + ui::ImageModel GetIconAt(size_t index) const override { return combobox_model_->GetDropDownIconAt(items_shown_[index].index); } @@ -199,7 +198,7 @@ class EditableCombobox::EditableComboboxMenuModel observation_.Reset(); } - int GetItemCount() const override { return items_shown_.size(); } + size_t GetItemCount() const override { return items_shown_.size(); } private: struct ShownItem { @@ -207,62 +206,62 @@ class EditableCombobox::EditableComboboxMenuModel bool enabled; }; bool HasIcons() const override { - for (int i = 0; i < GetItemCount(); ++i) { + for (size_t i = 0; i < GetItemCount(); ++i) { if (!GetIconAt(i).IsEmpty()) return true; } return false; } - ItemType GetTypeAt(int index) const override { + ItemType GetTypeAt(size_t index) const override { return UseCheckmarks() ? TYPE_CHECK : TYPE_COMMAND; } - ui::MenuSeparatorType GetSeparatorTypeAt(int index) const override { + ui::MenuSeparatorType GetSeparatorTypeAt(size_t index) const override { return ui::NORMAL_SEPARATOR; } - int GetCommandIdAt(int index) const override { + int GetCommandIdAt(size_t index) const override { constexpr int kFirstMenuItemId = 1000; - return index + kFirstMenuItemId; + return static_cast<int>(index) + kFirstMenuItemId; } - std::u16string GetLabelAt(int index) const override { + std::u16string GetLabelAt(size_t index) const override { std::u16string text = GetItemTextAt(index, owner_->showing_password_text_); base::i18n::AdjustStringForLocaleDirection(&text); return text; } - bool IsItemDynamicAt(int index) const override { return false; } + bool IsItemDynamicAt(size_t index) const override { return false; } - const gfx::FontList* GetLabelFontListAt(int index) const override { + const gfx::FontList* GetLabelFontListAt(size_t index) const override { return &owner_->GetFontList(); } - bool GetAcceleratorAt(int index, + bool GetAcceleratorAt(size_t index, ui::Accelerator* accelerator) const override { return false; } - bool IsItemCheckedAt(int index) const override { + bool IsItemCheckedAt(size_t index) const override { return UseCheckmarks() && combobox_model_->GetItemAt(items_shown_[index].index) == owner_->GetText(); } - int GetGroupIdAt(int index) const override { return -1; } + int GetGroupIdAt(size_t index) const override { return -1; } - ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override { + ui::ButtonMenuItemModel* GetButtonMenuItemAt(size_t index) const override { return nullptr; } - bool IsEnabledAt(int index) const override { + bool IsEnabledAt(size_t index) const override { return items_shown_[index].enabled; } - void ActivatedAt(int index) override { owner_->OnItemSelected(index); } + void ActivatedAt(size_t index) override { owner_->OnItemSelected(index); } - MenuModel* GetSubmenuModelAt(int index) const override { return nullptr; } + MenuModel* GetSubmenuModelAt(size_t index) const override { return nullptr; } raw_ptr<EditableCombobox> owner_; // Weak. Owns |this|. raw_ptr<ui::ComboboxModel> combobox_model_; // Weak. @@ -419,15 +418,15 @@ void EditableCombobox::RevealPasswords(bool revealed) { menu_model_->UpdateItemsShown(); } -int EditableCombobox::GetItemCountForTest() { +size_t EditableCombobox::GetItemCountForTest() { return menu_model_->GetItemCount(); } -std::u16string EditableCombobox::GetItemForTest(int index) { +std::u16string EditableCombobox::GetItemForTest(size_t index) { return menu_model_->GetItemTextAt(index, showing_password_text_); } -ui::ImageModel EditableCombobox::GetIconForTest(int index) { +ui::ImageModel EditableCombobox::GetIconForTest(size_t index) { return menu_model_->GetIconAt(index); } @@ -493,7 +492,7 @@ void EditableCombobox::CloseMenu() { pre_target_handler_.reset(); } -void EditableCombobox::OnItemSelected(int index) { +void EditableCombobox::OnItemSelected(size_t index) { // |textfield_| can hide the characters on its own so we read the actual // characters instead of gfx::RenderText::kPasswordReplacementChar characters. std::u16string selected_item_text = diff --git a/chromium/ui/views/controls/editable_combobox/editable_combobox.h b/chromium/ui/views/controls/editable_combobox/editable_combobox.h index df5143b72df..ef96793b1bf 100644 --- a/chromium/ui/views/controls/editable_combobox/editable_combobox.h +++ b/chromium/ui/views/controls/editable_combobox/editable_combobox.h @@ -110,9 +110,9 @@ class VIEWS_EXPORT EditableCombobox // Accessors of private members for tests. ui::ComboboxModel* GetComboboxModelForTest() { return combobox_model_.get(); } - int GetItemCountForTest(); - std::u16string GetItemForTest(int index); - ui::ImageModel GetIconForTest(int index); + size_t GetItemCountForTest(); + std::u16string GetItemForTest(size_t index); + ui::ImageModel GetIconForTest(size_t index); MenuRunner* GetMenuRunnerForTest() { return menu_runner_.get(); } Textfield* GetTextfieldForTest() { return textfield_; } @@ -123,7 +123,7 @@ class VIEWS_EXPORT EditableCombobox void CloseMenu(); // Called when an item is selected from the menu. - void OnItemSelected(int index); + void OnItemSelected(size_t index); // Notifies listener of new content and updates the menu items to show. void HandleNewContent(const std::u16string& new_content); diff --git a/chromium/ui/views/controls/editable_combobox/editable_combobox_unittest.cc b/chromium/ui/views/controls/editable_combobox/editable_combobox_unittest.cc index 2a69777a01c..7fe52c54585 100644 --- a/chromium/ui/views/controls/editable_combobox/editable_combobox_unittest.cc +++ b/chromium/ui/views/controls/editable_combobox/editable_combobox_unittest.cc @@ -655,7 +655,7 @@ TEST_F(EditableComboboxTest, MAYBE_RefocusingReopensMenuBasedOnLatestContent) { combobox_->GetTextfieldForTest()->RequestFocus(); SendKeyEvent(ui::VKEY_B); - ASSERT_EQ(3, combobox_->GetItemCountForTest()); + ASSERT_EQ(3u, combobox_->GetItemCountForTest()); SendKeyEvent(ui::VKEY_DOWN); SendKeyEvent(ui::VKEY_RETURN); @@ -669,7 +669,7 @@ TEST_F(EditableComboboxTest, MAYBE_RefocusingReopensMenuBasedOnLatestContent) { dummy_focusable_view_->RequestFocus(); ClickArrow(); EXPECT_TRUE(IsMenuOpen()); - ASSERT_EQ(2, combobox_->GetItemCountForTest()); + ASSERT_EQ(2u, combobox_->GetItemCountForTest()); } TEST_F(EditableComboboxTest, GetItemsWithoutFiltering) { @@ -677,7 +677,7 @@ TEST_F(EditableComboboxTest, GetItemsWithoutFiltering) { InitEditableCombobox(items, /*filter_on_edit=*/false, /*show_on_empty=*/true); combobox_->SetText(u"z"); - ASSERT_EQ(2, combobox_->GetItemCountForTest()); + ASSERT_EQ(2u, combobox_->GetItemCountForTest()); ASSERT_EQ(u"item0", combobox_->GetItemForTest(0)); ASSERT_EQ(u"item1", combobox_->GetItemForTest(1)); } @@ -686,22 +686,22 @@ TEST_F(EditableComboboxTest, FilteringEffectOnGetItems) { std::vector<std::u16string> items = {u"abc", u"abd", u"bac", u"bad"}; InitEditableCombobox(items, /*filter_on_edit=*/true, /*show_on_empty=*/true); - ASSERT_EQ(4, combobox_->GetItemCountForTest()); + ASSERT_EQ(4u, combobox_->GetItemCountForTest()); ASSERT_EQ(u"abc", combobox_->GetItemForTest(0)); ASSERT_EQ(u"abd", combobox_->GetItemForTest(1)); ASSERT_EQ(u"bac", combobox_->GetItemForTest(2)); ASSERT_EQ(u"bad", combobox_->GetItemForTest(3)); combobox_->SetText(u"b"); - ASSERT_EQ(2, combobox_->GetItemCountForTest()); + ASSERT_EQ(2u, combobox_->GetItemCountForTest()); ASSERT_EQ(u"bac", combobox_->GetItemForTest(0)); ASSERT_EQ(u"bad", combobox_->GetItemForTest(1)); combobox_->SetText(u"bc"); - ASSERT_EQ(0, combobox_->GetItemCountForTest()); + ASSERT_EQ(0u, combobox_->GetItemCountForTest()); combobox_->SetText(std::u16string()); - ASSERT_EQ(4, combobox_->GetItemCountForTest()); + ASSERT_EQ(4u, combobox_->GetItemCountForTest()); ASSERT_EQ(u"abc", combobox_->GetItemForTest(0)); ASSERT_EQ(u"abd", combobox_->GetItemForTest(1)); ASSERT_EQ(u"bac", combobox_->GetItemForTest(2)); @@ -721,18 +721,18 @@ TEST_F(EditableComboboxTest, FilteringEffectOnIcons) { /*filter_on_edit=*/true, /*show_on_empty=*/true); - ASSERT_EQ(2, combobox_->GetItemCountForTest()); + ASSERT_EQ(2u, combobox_->GetItemCountForTest()); EXPECT_EQ(16, combobox_->GetComboboxModelForTest()->GetIconAt(0).Size().width()); EXPECT_EQ(20, combobox_->GetComboboxModelForTest()->GetIconAt(1).Size().width()); combobox_->SetText(u"a"); - ASSERT_EQ(1, combobox_->GetItemCountForTest()); + ASSERT_EQ(1u, combobox_->GetItemCountForTest()); EXPECT_EQ(16, combobox_->GetIconForTest(0).Size().width()); combobox_->SetText(u"d"); - ASSERT_EQ(1, combobox_->GetItemCountForTest()); + ASSERT_EQ(1u, combobox_->GetItemCountForTest()); EXPECT_EQ(20, combobox_->GetIconForTest(0).Size().width()); } @@ -740,18 +740,18 @@ TEST_F(EditableComboboxTest, FilteringWithMismatchedCase) { std::vector<std::u16string> items = {u"AbCd", u"aBcD", u"xyz"}; InitEditableCombobox(items, /*filter_on_edit=*/true, /*show_on_empty=*/true); - ASSERT_EQ(3, combobox_->GetItemCountForTest()); + ASSERT_EQ(3u, combobox_->GetItemCountForTest()); ASSERT_EQ(u"AbCd", combobox_->GetItemForTest(0)); ASSERT_EQ(u"aBcD", combobox_->GetItemForTest(1)); ASSERT_EQ(u"xyz", combobox_->GetItemForTest(2)); combobox_->SetText(u"abcd"); - ASSERT_EQ(2, combobox_->GetItemCountForTest()); + ASSERT_EQ(2u, combobox_->GetItemCountForTest()); ASSERT_EQ(u"AbCd", combobox_->GetItemForTest(0)); ASSERT_EQ(u"aBcD", combobox_->GetItemForTest(1)); combobox_->SetText(u"ABCD"); - ASSERT_EQ(2, combobox_->GetItemCountForTest()); + ASSERT_EQ(2u, combobox_->GetItemCountForTest()); ASSERT_EQ(u"AbCd", combobox_->GetItemForTest(0)); ASSERT_EQ(u"aBcD", combobox_->GetItemForTest(1)); } @@ -761,9 +761,9 @@ TEST_F(EditableComboboxTest, DontShowOnEmpty) { InitEditableCombobox(items, /*filter_on_edit=*/false, /*show_on_empty=*/false); - ASSERT_EQ(0, combobox_->GetItemCountForTest()); + ASSERT_EQ(0u, combobox_->GetItemCountForTest()); combobox_->SetText(u"a"); - ASSERT_EQ(2, combobox_->GetItemCountForTest()); + ASSERT_EQ(2u, combobox_->GetItemCountForTest()); ASSERT_EQ(u"item0", combobox_->GetItemForTest(0)); ASSERT_EQ(u"item1", combobox_->GetItemForTest(1)); } @@ -797,7 +797,7 @@ TEST_F(EditableComboboxTest, PasswordCanBeHiddenAndRevealed) { InitEditableCombobox(items, /*filter_on_edit=*/false, /*show_on_empty=*/true, EditableCombobox::Type::kPassword); - ASSERT_EQ(2, combobox_->GetItemCountForTest()); + ASSERT_EQ(2u, combobox_->GetItemCountForTest()); ASSERT_EQ(std::u16string(5, gfx::RenderText::kPasswordReplacementChar), combobox_->GetItemForTest(0)); ASSERT_EQ(std::u16string(5, gfx::RenderText::kPasswordReplacementChar), @@ -909,24 +909,24 @@ class ConfigurableComboboxModel final : public ui::ComboboxModel { } // ui::ComboboxModel: - int GetItemCount() const override { return item_count_; } - std::u16string GetItemAt(int index) const override { + size_t GetItemCount() const override { return item_count_; } + std::u16string GetItemAt(size_t index) const override { DCHECK_LT(index, item_count_); return base::NumberToString16(index); } - void SetItemCount(int item_count) { item_count_ = item_count; } + void SetItemCount(size_t item_count) { item_count_ = item_count; } private: const raw_ptr<bool> destroyed_; - int item_count_ = 0; + size_t item_count_ = 0; }; } // namespace TEST_F(EditableComboboxDefaultTest, Default) { auto combobox = std::make_unique<EditableCombobox>(); - EXPECT_EQ(0, combobox->GetItemCountForTest()); + EXPECT_EQ(0u, combobox->GetItemCountForTest()); } TEST_F(EditableComboboxDefaultTest, SetModel) { @@ -935,7 +935,7 @@ TEST_F(EditableComboboxDefaultTest, SetModel) { model->SetItemCount(42); auto combobox = std::make_unique<EditableCombobox>(); combobox->SetModel(std::move(model)); - EXPECT_EQ(42, combobox->GetItemCountForTest()); + EXPECT_EQ(42u, combobox->GetItemCountForTest()); } TEST_F(EditableComboboxDefaultTest, SetModelOverwrite) { diff --git a/chromium/ui/views/controls/focus_ring.cc b/chromium/ui/views/controls/focus_ring.cc index eb9373cef02..be0388f8f88 100644 --- a/chromium/ui/views/controls/focus_ring.cc +++ b/chromium/ui/views/controls/focus_ring.cc @@ -47,11 +47,12 @@ bool IsPathUsable(const SkPath& path) { path.isRRect(nullptr)); } -SkColor GetColor(View* focus_ring, bool valid) { - if (!valid) { - return focus_ring->GetColorProvider()->GetColor( - ui::kColorAlertHighSeverity); - } +SkColor GetPaintColor(FocusRing* focus_ring, bool valid) { + const auto* cp = focus_ring->GetColorProvider(); + if (!valid) + return cp->GetColor(ui::kColorAlertHighSeverity); + if (auto color_id = focus_ring->GetColorId(); color_id.has_value()) + return cp->GetColor(color_id.value()); return GetCascadingAccentColor(focus_ring); } @@ -137,19 +138,37 @@ void FocusRing::SetHasFocusPredicate(const ViewPredicate& predicate) { RefreshLayer(); } -void FocusRing::SetColor(absl::optional<SkColor> color) { - color_ = color; - SchedulePaint(); +absl::optional<ui::ColorId> FocusRing::GetColorId() const { + return color_id_; +} + +void FocusRing::SetColorId(absl::optional<ui::ColorId> color_id) { + if (color_id_ == color_id) + return; + color_id_ = color_id; + OnPropertyChanged(&color_id_, PropertyEffects::kPropertyEffectsPaint); +} + +float FocusRing::GetHaloThickness() const { + return halo_thickness_; +} + +float FocusRing::GetHaloInset() const { + return halo_inset_; } void FocusRing::SetHaloThickness(float halo_thickness) { + if (halo_thickness_ == halo_thickness) + return; halo_thickness_ = halo_thickness; - SchedulePaint(); + OnPropertyChanged(&halo_thickness_, PropertyEffects::kPropertyEffectsPaint); } void FocusRing::SetHaloInset(float halo_inset) { + if (halo_inset_ == halo_inset) + return; halo_inset_ = halo_inset; - SchedulePaint(); + OnPropertyChanged(&halo_inset_, PropertyEffects::kPropertyEffectsPaint); } void FocusRing::Layout() { @@ -232,7 +251,7 @@ void FocusRing::OnPaint(gfx::Canvas* canvas) { canvas->sk_canvas()->drawRRect(ring_rect, paint); } - paint.setColor(color_.value_or(GetColor(this, !invalid_))); + paint.setColor(GetPaintColor(this, !invalid_)); paint.setStrokeWidth(halo_thickness_); canvas->sk_canvas()->drawRRect(ring_rect, paint); } @@ -273,6 +292,12 @@ void FocusRing::GetAccessibleNodeData(ui::AXNodeData* node_data) { node_data->AddState(ax::mojom::State::kIgnored); } +void FocusRing::OnThemeChanged() { + View::OnThemeChanged(); + if (invalid_ || color_id_.has_value()) + SchedulePaint(); +} + void FocusRing::OnViewFocused(View* view) { RefreshLayer(); } @@ -354,6 +379,9 @@ SkPath GetHighlightPath(const View* view, float halo_thickness) { } BEGIN_METADATA(FocusRing, View) +ADD_PROPERTY_METADATA(absl::optional<ui::ColorId>, ColorId) +ADD_PROPERTY_METADATA(float, HaloInset) +ADD_PROPERTY_METADATA(float, HaloThickness) END_METADATA } // namespace views diff --git a/chromium/ui/views/controls/focus_ring.h b/chromium/ui/views/controls/focus_ring.h index 37ad485bc85..e35f2eb8b06 100644 --- a/chromium/ui/views/controls/focus_ring.h +++ b/chromium/ui/views/controls/focus_ring.h @@ -9,6 +9,7 @@ #include "base/scoped_observation.h" #include "ui/base/class_property.h" +#include "ui/color/color_id.h" #include "ui/native_theme/native_theme.h" #include "ui/views/controls/focusable_border.h" #include "ui/views/view.h" @@ -77,11 +78,11 @@ class VIEWS_EXPORT FocusRing : public View, public ViewObserver { // focus, but the FocusRing sits on the parent instead of the inner view. void SetHasFocusPredicate(const ViewPredicate& predicate); - absl::optional<SkColor> color() const { return color_; } - void SetColor(absl::optional<SkColor> color); + absl::optional<ui::ColorId> GetColorId() const; + void SetColorId(absl::optional<ui::ColorId> color_id); - float halo_thickness() const { return halo_thickness_; } - float halo_inset() const { return halo_inset_; } + float GetHaloThickness() const; + float GetHaloInset() const; void SetHaloThickness(float halo_thickness); void SetHaloInset(float halo_inset); @@ -91,6 +92,7 @@ class VIEWS_EXPORT FocusRing : public View, public ViewObserver { const ViewHierarchyChangedDetails& details) override; void OnPaint(gfx::Canvas* canvas) override; void GetAccessibleNodeData(ui::AXNodeData* node_data) override; + void OnThemeChanged() override; // ViewObserver: void OnViewFocused(View* view) override; @@ -119,8 +121,8 @@ class VIEWS_EXPORT FocusRing : public View, public ViewObserver { // the focus ring shows an invalid appearance (usually a different color). bool invalid_ = false; - // Overriding color for the focus ring. - absl::optional<SkColor> color_; + // Overriding color_id for the focus ring. + absl::optional<ui::ColorId> color_id_; // The predicate used to determine whether the parent has focus. absl::optional<ViewPredicate> has_focus_predicate_; diff --git a/chromium/ui/views/controls/image_view.cc b/chromium/ui/views/controls/image_view.cc index 1cbd6988ac6..f9a6b527e45 100644 --- a/chromium/ui/views/controls/image_view.cc +++ b/chromium/ui/views/controls/image_view.cc @@ -9,6 +9,7 @@ #include "base/check_op.h" #include "base/i18n/rtl.h" #include "base/numerics/safe_conversions.h" +#include "base/trace_event/trace_event.h" #include "cc/paint/paint_flags.h" #include "skia/ext/image_operations.h" #include "ui/base/metadata/metadata_impl_macros.h" @@ -85,8 +86,13 @@ gfx::Size ImageView::GetImageSize() const { } void ImageView::OnPaint(gfx::Canvas* canvas) { - View::OnPaint(canvas); + // This inlines View::OnPaint in order to OnPaintBorder() after OnPaintImage + // so the border can paint over content (for rounded corners that overlap + // content). + TRACE_EVENT1("views", "ImageView::OnPaint", "class", GetClassName()); + OnPaintBackground(canvas); OnPaintImage(canvas); + OnPaintBorder(canvas); } void ImageView::OnThemeChanged() { diff --git a/chromium/ui/views/controls/image_view_unittest.cc b/chromium/ui/views/controls/image_view_unittest.cc index 5efb0842dbd..60ea497767b 100644 --- a/chromium/ui/views/controls/image_view_unittest.cc +++ b/chromium/ui/views/controls/image_view_unittest.cc @@ -102,7 +102,7 @@ TEST_P(ImageViewTest, CenterAlignment) { bitmap.allocN32Pixels(kImageSkiaSize, kImageSkiaSize); gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(bitmap); image_view()->SetImage(image_skia); - widget()->GetContentsView()->Layout(); + RunScheduledLayout(image_view()); EXPECT_NE(gfx::Size(), image_skia.size()); // With no changes to the size / padding of |image_view|, the origin of @@ -112,11 +112,11 @@ TEST_P(ImageViewTest, CenterAlignment) { // Test insets are always respected in LTR and RTL. constexpr int kInset = 5; image_view()->SetBorder(CreateEmptyBorder(kInset)); - widget()->GetContentsView()->Layout(); + RunScheduledLayout(image_view()); EXPECT_EQ(kInset, CurrentImageOriginForParam()); SetRTL(true); - widget()->GetContentsView()->Layout(); + RunScheduledLayout(image_view()); EXPECT_EQ(kInset, CurrentImageOriginForParam()); // Check this still holds true when the insets are asymmetrical. @@ -124,11 +124,11 @@ TEST_P(ImageViewTest, CenterAlignment) { constexpr int kTrailingInset = 6; image_view()->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR( kLeadingInset, kLeadingInset, kTrailingInset, kTrailingInset))); - widget()->GetContentsView()->Layout(); + RunScheduledLayout(image_view()); EXPECT_EQ(kLeadingInset, CurrentImageOriginForParam()); SetRTL(false); - widget()->GetContentsView()->Layout(); + RunScheduledLayout(image_view()); EXPECT_EQ(kLeadingInset, CurrentImageOriginForParam()); } diff --git a/chromium/ui/views/controls/label.cc b/chromium/ui/views/controls/label.cc index c748b7ed1a9..dacb2199b93 100644 --- a/chromium/ui/views/controls/label.cc +++ b/chromium/ui/views/controls/label.cc @@ -350,11 +350,11 @@ void Label::SetMultiLine(bool multi_line) { OnPropertyChanged(&multi_line_, kPropertyEffectsPreferredSizeChanged); } -int Label::GetMaxLines() const { +size_t Label::GetMaxLines() const { return max_lines_; } -void Label::SetMaxLines(int max_lines) { +void Label::SetMaxLines(size_t max_lines) { if (max_lines_ == max_lines) return; max_lines_ = max_lines; @@ -576,6 +576,11 @@ int Label::GetBaseline() const { } gfx::Size Label::CalculatePreferredSize() const { + return CalculatePreferredSize({width(), {}}); +} + +gfx::Size Label::CalculatePreferredSize( + const SizeBounds& available_size) const { // Return a size of (0, 0) if the label is not visible and if the // |collapse_when_hidden_| flag is set. // TODO(munjal): This logic probably belongs to the View class. But for now, @@ -587,7 +592,7 @@ gfx::Size Label::CalculatePreferredSize() const { if (GetMultiLine() && fixed_width_ != 0 && !GetText().empty()) return gfx::Size(fixed_width_, GetHeightForWidth(fixed_width_)); - gfx::Size size(GetTextSize()); + gfx::Size size(GetBoundedTextSize(available_size)); const gfx::Insets insets = GetInsets(); size.Enlarge(insets.width(), insets.height()); @@ -638,7 +643,8 @@ int Label::GetHeightForWidth(int w) const { if (!GetMultiLine() || GetText().empty() || w < 0) { height = base_line_height; } else if (w == 0) { - height = std::max(GetMaxLines(), 1) * base_line_height; + height = + std::max(base::checked_cast<int>(GetMaxLines()), 1) * base_line_height; } else { // SetDisplayRect() has a side effect for later calls of GetStringSize(). // Be careful to invoke full_text_->SetDisplayRect(gfx::Rect()) to @@ -650,7 +656,9 @@ int Label::GetHeightForWidth(int w) const { int string_height = full_text_->GetStringSize().height(); // Cap the number of lines to GetMaxLines() if that's set. height = GetMaxLines() > 0 - ? std::min(GetMaxLines() * base_line_height, string_height) + ? std::min(base::checked_cast<int>(GetMaxLines()) * + base_line_height, + string_height) : string_height; } height -= gfx::ShadowValue::GetMargin(full_text_->shadows()).height(); @@ -711,7 +719,7 @@ std::unique_ptr<gfx::RenderText> Label::CreateRenderText() const { render_text->set_shadows(GetShadows()); const bool multiline = GetMultiLine(); render_text->SetMultiline(multiline); - render_text->SetMaxLines(multiline ? GetMaxLines() : 0); + render_text->SetMaxLines(multiline ? GetMaxLines() : size_t{0}); render_text->SetWordWrapBehavior(full_text_->word_wrap_behavior()); // Setup render text for selection controller. @@ -1131,6 +1139,10 @@ void Label::MaybeBuildDisplayText() const { } gfx::Size Label::GetTextSize() const { + return GetBoundedTextSize({width(), {}}); +} + +gfx::Size Label::GetBoundedTextSize(const SizeBounds& available_size) const { gfx::Size size; if (GetText().empty()) { size = gfx::Size(0, GetLineHeight()); @@ -1140,17 +1152,20 @@ gfx::Size Label::GetTextSize() const { // to report an accurate width given the constraints and how it determines // to elide the text. If we simply clamp the width to the max after the // fact, then there may be some empty space left over *after* an ellipsis. + // TODO(kerenzhu): `available_size` should be respected, but doing so will + // break tests. Fix that. full_text_->SetDisplayRect( gfx::Rect(0, 0, max_width_single_line_ - GetInsets().width(), 0)); size = full_text_->GetStringSize(); } else { - // Cancel the display rect of |full_text_|. The display rect may be - // specified in GetHeightForWidth(), and specifying empty Rect cancels - // its effect. See also the comment in GetHeightForWidth(). - // TODO(mukai): use gfx::Rect() to compute the ideal size rather than - // the current width(). See crbug.com/468494, crbug.com/467526, and - // the comment for MultilinePreferredSizeTest in label_unittest.cc. - full_text_->SetDisplayRect(gfx::Rect(0, 0, width(), 0)); + const int width = available_size.width().is_bounded() + ? available_size.width().value() + : 0; + // SetDisplayRect() has side-effect. The text height will change to respect + // width. + // TODO(crbug.com/1347330): `width` should respect insets, but doing so + // will break LabelTest.MultiLineSizing. Fix that. + full_text_->SetDisplayRect(gfx::Rect(0, 0, width, 0)); size = full_text_->GetStringSize(); } const gfx::Insets shadow_margin = -gfx::ShadowValue::GetMargin(GetShadows()); @@ -1286,7 +1301,7 @@ ADD_PROPERTY_METADATA(gfx::HorizontalAlignment, HorizontalAlignment) ADD_PROPERTY_METADATA(gfx::VerticalAlignment, VerticalAlignment) ADD_PROPERTY_METADATA(int, LineHeight) ADD_PROPERTY_METADATA(bool, MultiLine) -ADD_PROPERTY_METADATA(int, MaxLines) +ADD_PROPERTY_METADATA(size_t, MaxLines) ADD_PROPERTY_METADATA(bool, Obscured) ADD_PROPERTY_METADATA(bool, AllowCharacterBreak) ADD_PROPERTY_METADATA(std::u16string, TooltipText) diff --git a/chromium/ui/views/controls/label.h b/chromium/ui/views/controls/label.h index 912c7d20eb0..28c9b4fc484 100644 --- a/chromium/ui/views/controls/label.h +++ b/chromium/ui/views/controls/label.h @@ -193,8 +193,8 @@ class VIEWS_EXPORT Label : public View, // If multi-line, a non-zero value will cap the number of lines rendered, and // elide the rest (currently only ELIDE_TAIL supported). See gfx::RenderText. - int GetMaxLines() const; - void SetMaxLines(int max_lines); + size_t GetMaxLines() const; + void SetMaxLines(size_t max_lines); // If single-line, a non-zero value will help determine the amount of space // needed *after* elision, which may be less than the passed |max_width|. @@ -309,6 +309,8 @@ class VIEWS_EXPORT Label : public View, // View: int GetBaseline() const override; gfx::Size CalculatePreferredSize() const override; + gfx::Size CalculatePreferredSize( + const SizeBounds& available_size) const override; gfx::Size GetMinimumSize() const override; int GetHeightForWidth(int w) const override; View* GetTooltipHandlerForPoint(const gfx::Point& point) override; @@ -407,6 +409,10 @@ class VIEWS_EXPORT Label : public View, // Get the text size for the current layout. gfx::Size GetTextSize() const; + // Get the text size that ignores the current layout and respects + // `available_size`. + gfx::Size GetBoundedTextSize(const SizeBounds& available_size) const; + // Returns the appropriate foreground color to use given the proposed // |foreground| and |background| colors. SkColor GetForegroundColor(SkColor foreground, SkColor background) const; @@ -475,7 +481,7 @@ class VIEWS_EXPORT Label : public View, bool auto_color_readability_enabled_ = true; // TODO(mukai): remove |multi_line_| when all RenderText can render multiline. bool multi_line_ = false; - int max_lines_ = 0; + size_t max_lines_ = 0; std::u16string tooltip_text_; bool handles_tooltips_ = true; // Whether to collapse the label when it's not visible. diff --git a/chromium/ui/views/controls/label_unittest.cc b/chromium/ui/views/controls/label_unittest.cc index bc26014c209..b8f0524c2a6 100644 --- a/chromium/ui/views/controls/label_unittest.cc +++ b/chromium/ui/views/controls/label_unittest.cc @@ -36,6 +36,7 @@ #include "ui/views/border.h" #include "ui/views/controls/base_control_test_widget.h" #include "ui/views/controls/link.h" +#include "ui/views/layout/layout_types.h" #include "ui/views/style/typography.h" #include "ui/views/test/ax_event_counter.h" #include "ui/views/test/focus_manager_test.h" @@ -474,8 +475,8 @@ TEST_F(LabelTest, ObscuredSurrogatePair) { // this behavior, therefore this behavior will have to be kept until the code // with this assumption is fixed. See http://crbug.com/468494 and // http://crbug.com/467526. -// TODO(mukai): fix the code assuming this behavior and then fix Label -// implementation, and remove this test case. +// TODO(crbug.com/1346889): convert all callsites of GetPreferredSize() to +// call GetPreferredSize(SizeBounds) instead. TEST_F(LabelTest, MultilinePreferredSizeTest) { label()->SetText(u"This is an example."); @@ -492,6 +493,37 @@ TEST_F(LabelTest, MultilinePreferredSizeTest) { EXPECT_LT(multi_line_size.height(), new_size.height()); } +TEST_F(LabelTest, MultilinePreferredSizeWithConstraintTest) { + label()->SetText(u"This is an example."); + + const gfx::Size single_line_size = + label()->GetPreferredSize({/* Unbounded */}); + + // Test the preferred size when the label is not yet laid out. + label()->SetMultiLine(true); + const gfx::Size multi_line_size_unbounded = + label()->GetPreferredSize({/* Unbounded */}); + EXPECT_EQ(single_line_size, multi_line_size_unbounded); + + const gfx::Size multi_line_size_bounded = label()->GetPreferredSize( + {single_line_size.width() / 2, {/* Unbounded */}}); + EXPECT_GT(multi_line_size_unbounded.width(), multi_line_size_bounded.width()); + EXPECT_LT(multi_line_size_unbounded.height(), + multi_line_size_bounded.height()); + + // Test the preferred size after the label is laid out. + // GetPreferredSize(SizeBounds) should ignore the existing bounds. + const int layout_width = multi_line_size_unbounded.width() / 3; + label()->SetBounds(0, 0, layout_width, + label()->GetHeightForWidth(layout_width)); + const gfx::Size multi_line_size_unbounded2 = + label()->GetPreferredSize({/* Unbounded */}); + const gfx::Size multi_line_size_bounded2 = label()->GetPreferredSize( + {single_line_size.width() / 2, {/* Unbounded */}}); + EXPECT_EQ(multi_line_size_unbounded, multi_line_size_unbounded2); + EXPECT_EQ(multi_line_size_bounded, multi_line_size_bounded2); +} + TEST_F(LabelTest, SingleLineGetHeightForWidth) { // Even an empty label should take one line worth of height. const int line_height = label()->GetLineHeight(); diff --git a/chromium/ui/views/controls/link.cc b/chromium/ui/views/controls/link.cc index b8d70208bd8..522d723a454 100644 --- a/chromium/ui/views/controls/link.cc +++ b/chromium/ui/views/controls/link.cc @@ -59,6 +59,10 @@ void Link::SetForceUnderline(bool force_underline) { RecalculateFont(); } +bool Link::GetForceUnderline() const { + return force_underline_; +} + ui::Cursor Link::GetCursor(const ui::MouseEvent& event) { if (!GetEnabled()) return ui::Cursor(); @@ -231,6 +235,7 @@ void Link::ConfigureFocus() { BEGIN_METADATA(Link, Label) ADD_READONLY_PROPERTY_METADATA(SkColor, Color, ui::metadata::SkColorConverter) +ADD_PROPERTY_METADATA(bool, ForceUnderline) END_METADATA } // namespace views diff --git a/chromium/ui/views/controls/link.h b/chromium/ui/views/controls/link.h index 405b605e539..115976c55b5 100644 --- a/chromium/ui/views/controls/link.h +++ b/chromium/ui/views/controls/link.h @@ -61,6 +61,7 @@ class VIEWS_EXPORT Link : public Label { SkColor GetColor() const; void SetForceUnderline(bool force_underline); + bool GetForceUnderline() const; // Label: ui::Cursor GetCursor(const ui::MouseEvent& event) override; @@ -84,12 +85,12 @@ class VIEWS_EXPORT Link : public Label { bool IsSelectionSupported() const override; private: + virtual void RecalculateFont(); + void SetPressed(bool pressed); void OnClick(const ui::Event& event); - void RecalculateFont(); - void ConfigureFocus(); ClickedCallback callback_; diff --git a/chromium/ui/views/controls/link_fragment.cc b/chromium/ui/views/controls/link_fragment.cc new file mode 100644 index 00000000000..9f44db50d9e --- /dev/null +++ b/chromium/ui/views/controls/link_fragment.cc @@ -0,0 +1,88 @@ +// Copyright 2022 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/views/controls/link_fragment.h" + +#include <string> + +#include "ui/base/metadata/metadata_impl_macros.h" +#include "ui/views/controls/link.h" +#include "ui/views/style/typography.h" + +namespace views { + +LinkFragment::LinkFragment(const std::u16string& title, + int text_context, + int text_style, + LinkFragment* other_fragment) + : Link(title, text_context, text_style), + prev_fragment_(this), + next_fragment_(this) { + // Connect to the previous fragment if it exists. + if (other_fragment) + Connect(other_fragment); +} + +LinkFragment::~LinkFragment() { + Disconnect(); +} + +void LinkFragment::Connect(LinkFragment* other_fragment) { + DCHECK(prev_fragment_ == this); + DCHECK(next_fragment_ == this); + DCHECK(other_fragment); + + next_fragment_ = other_fragment->next_fragment_; + other_fragment->next_fragment_->prev_fragment_ = this; + prev_fragment_ = other_fragment; + other_fragment->next_fragment_ = this; +} + +void LinkFragment::Disconnect() { + DCHECK((prev_fragment_ != this) == (next_fragment_ != this)); + if (prev_fragment_ != this) { + prev_fragment_->next_fragment_ = next_fragment_; + next_fragment_->prev_fragment_ = prev_fragment_; + } +} + +bool LinkFragment::IsUnderlined() const { + return GetEnabled() && + (HasFocus() || IsMouseHovered() || GetForceUnderline()); +} + +void LinkFragment::RecalculateFont() { + // Check whether any link fragment should be underlined. + bool should_be_underlined = IsUnderlined(); + for (LinkFragment* current_fragment = next_fragment_; + !should_be_underlined && current_fragment != this; + current_fragment = current_fragment->next_fragment_) { + should_be_underlined = current_fragment->IsUnderlined(); + } + + // If the style differs from the current one, update. + if ((font_list().GetFontStyle() & gfx::Font::UNDERLINE) != + should_be_underlined) { + auto MaybeUpdateStyle = [should_be_underlined](LinkFragment* fragment) { + const int style = fragment->font_list().GetFontStyle(); + const int intended_style = should_be_underlined + ? (style | gfx::Font::UNDERLINE) + : (style & ~gfx::Font::UNDERLINE); + fragment->Label::SetFontList( + fragment->font_list().DeriveWithStyle(intended_style)); + fragment->SchedulePaint(); + }; + MaybeUpdateStyle(this); + for (LinkFragment* current_fragment = next_fragment_; + current_fragment != this; + current_fragment = current_fragment->next_fragment_) { + MaybeUpdateStyle(current_fragment); + } + } +} + +BEGIN_METADATA(LinkFragment, Link) +END_METADATA + +} // namespace views diff --git a/chromium/ui/views/controls/link_fragment.h b/chromium/ui/views/controls/link_fragment.h new file mode 100644 index 00000000000..20a7b87d534 --- /dev/null +++ b/chromium/ui/views/controls/link_fragment.h @@ -0,0 +1,57 @@ +// Copyright 2022 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_VIEWS_CONTROLS_LINK_FRAGMENT_H_ +#define UI_VIEWS_CONTROLS_LINK_FRAGMENT_H_ + +#include "base/memory/raw_ptr.h" +#include "ui/views/controls/link.h" +#include "ui/views/metadata/view_factory.h" +#include "ui/views/style/typography.h" + +namespace views { + +// A `LinkFragment` can be used to represent a logical link that spans across +// multiple lines. Connected `LinkFragment`s adjust their style if any single +// one of them is hovered over of focused. +class VIEWS_EXPORT LinkFragment : public Link { + public: + METADATA_HEADER(LinkFragment); + + explicit LinkFragment(const std::u16string& title = std::u16string(), + int text_context = style::CONTEXT_LABEL, + int text_style = style::STYLE_LINK, + LinkFragment* other_fragment = nullptr); + ~LinkFragment() override; + + LinkFragment(const LinkFragment&) = delete; + LinkFragment& operator=(const LinkFragment&) = delete; + + private: + // Returns whether this fragment indicates that the entire link represented + // by it should be underlined. + bool IsUnderlined() const; + + // Connects `this` to the `other_fragment`. + void Connect(LinkFragment* other_fragment); + + // Disconnects `this` from any other fragments that it may be connected to. + void Disconnect(); + + // Recalculates the font style for this link fragment and, if it is changed, + // updates both this fragment and all other that are connected to it. + void RecalculateFont() override; + + // Pointers to the previous and the next `LinkFragment` if the logical link + // represented by `this` consists of multiple such fragments (e.g. due to + // line breaks). + // If the logical link is just a single `LinkFragment` component, then these + // pointers point to `this`. + raw_ptr<LinkFragment> prev_fragment_; + raw_ptr<LinkFragment> next_fragment_; +}; + +} // namespace views + +#endif // UI_VIEWS_CONTROLS_LINK_FRAGMENT_H_ diff --git a/chromium/ui/views/controls/link_fragment_unittest.cc b/chromium/ui/views/controls/link_fragment_unittest.cc new file mode 100644 index 00000000000..99ee66231ad --- /dev/null +++ b/chromium/ui/views/controls/link_fragment_unittest.cc @@ -0,0 +1,140 @@ +// Copyright 2022 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/views/controls/link_fragment.h" + +#include <array> +#include <memory> + +#include "base/memory/raw_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/test/event_generator.h" +#include "ui/views/controls/base_control_test_widget.h" +#include "ui/views/test/view_metadata_test_utils.h" +#include "ui/views/widget/widget.h" + +namespace views { + +namespace { + +constexpr char16_t kLinkLabel[] = u"Test label"; + +class LinkFragmentTest : public test::BaseControlTestWidget { + public: + LinkFragmentTest() { + for (auto& fragment : fragments_) { + fragment = nullptr; + } + } + ~LinkFragmentTest() override = default; + + void SetUp() override { + test::BaseControlTestWidget::SetUp(); + + event_generator_ = std::make_unique<ui::test::EventGenerator>( + GetContext(), widget()->GetNativeWindow()); + } + + protected: + void CreateWidgetContent(View* container) override { + // Fragment 0 is stand-alone. + fragments_[0] = + container->AddChildView(std::make_unique<LinkFragment>(kLinkLabel)); + gfx::Rect current_rect = + gfx::ScaleToEnclosedRect(container->GetLocalBounds(), 0.3f); + fragments_[0]->SetBoundsRect(current_rect); + int width = current_rect.width(); + + // Fragments 1 and 2 are connected. + current_rect.Offset(width, 0); + fragments_[1] = + container->AddChildView(std::make_unique<LinkFragment>(kLinkLabel)); + fragments_[1]->SetBoundsRect(current_rect); + + current_rect.Offset(width, 0); + fragments_[2] = container->AddChildView(std::make_unique<LinkFragment>( + kLinkLabel, style::CONTEXT_LABEL, style::STYLE_LINK, fragment(1))); + fragments_[2]->SetBoundsRect(current_rect); + } + + LinkFragment* fragment(size_t index) { + DCHECK_LT(index, 3u); + return fragments_[index]; + } + ui::test::EventGenerator* event_generator() { return event_generator_.get(); } + + // Returns bounds of the fragment. + gfx::Rect GetBoundsForFragment(size_t index) { + return fragment(index)->GetBoundsInScreen(); + } + + private: + std::array<raw_ptr<LinkFragment>, 3> fragments_; + std::unique_ptr<ui::test::EventGenerator> event_generator_; +}; + +} // namespace + +TEST_F(LinkFragmentTest, Metadata) { + for (size_t index = 0; index < 3; ++index) { + // Needed to avoid failing DCHECK when setting maximum width. + fragment(index)->SetMultiLine(true); + test::TestViewMetadata(fragment(index)); + } +} + +// Tests that hovering and unhovering a link adds and removes an underline +// under all connected fragments. +TEST_F(LinkFragmentTest, TestUnderlineOnHover) { + // A non-hovered link fragment should not be underlined. + const gfx::Point point_outside = + GetBoundsForFragment(2).bottom_right() + gfx::Vector2d(1, 1); + event_generator()->MoveMouseTo(point_outside); + EXPECT_FALSE(fragment(0)->IsMouseHovered()); + + const auto is_underlined = [this](size_t index) { + return !!(fragment(index)->font_list().GetFontStyle() & + gfx::Font::UNDERLINE); + }; + EXPECT_FALSE(is_underlined(0)); + + // Hovering the first link fragment underlines it. + event_generator()->MoveMouseTo(GetBoundsForFragment(0).CenterPoint()); + EXPECT_TRUE(fragment(0)->IsMouseHovered()); + EXPECT_TRUE(is_underlined(0)); + // The other link fragments stay non-hovered. + EXPECT_FALSE(is_underlined(1)); + EXPECT_FALSE(is_underlined(2)); + + // Un-hovering the link removes the underline again. + event_generator()->MoveMouseTo(point_outside); + EXPECT_FALSE(fragment(0)->IsMouseHovered()); + EXPECT_FALSE(is_underlined(0)); + EXPECT_FALSE(is_underlined(1)); + EXPECT_FALSE(is_underlined(2)); + + // Hovering the second link fragment underlines both the second and the + // third fragment. + event_generator()->MoveMouseTo(GetBoundsForFragment(1).CenterPoint()); + EXPECT_TRUE(fragment(1)->IsMouseHovered()); + EXPECT_FALSE(fragment(2)->IsMouseHovered()); + EXPECT_FALSE(is_underlined(0)); + EXPECT_TRUE(is_underlined(1)); + EXPECT_TRUE(is_underlined(2)); + + // The same is true for hovering the third fragment. + event_generator()->MoveMouseTo(GetBoundsForFragment(2).CenterPoint()); + EXPECT_TRUE(fragment(2)->IsMouseHovered()); + EXPECT_FALSE(is_underlined(0)); + EXPECT_TRUE(is_underlined(1)); + EXPECT_TRUE(is_underlined(2)); + + // Moving outside removes the underline again. + event_generator()->MoveMouseTo(point_outside); + EXPECT_FALSE(is_underlined(0)); + EXPECT_FALSE(is_underlined(1)); + EXPECT_FALSE(is_underlined(2)); +} + +} // namespace views diff --git a/chromium/ui/views/controls/menu/menu_closure_animation_mac.h b/chromium/ui/views/controls/menu/menu_closure_animation_mac.h index dc5c283b86d..7d6d1f5fd03 100644 --- a/chromium/ui/views/controls/menu/menu_closure_animation_mac.h +++ b/chromium/ui/views/controls/menu/menu_closure_animation_mac.h @@ -7,6 +7,7 @@ #include <memory> +#include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/timer/timer.h" #include "ui/gfx/animation/animation.h" @@ -83,8 +84,8 @@ class VIEWS_EXPORT MenuClosureAnimationMac base::OnceClosure callback_; base::OneShotTimer timer_; std::unique_ptr<gfx::Animation> fade_animation_; - MenuItemView* item_; - SubmenuView* menu_; + raw_ptr<MenuItemView> item_; + raw_ptr<SubmenuView> menu_; AnimationStep step_; }; diff --git a/chromium/ui/views/controls/menu/menu_controller.cc b/chromium/ui/views/controls/menu/menu_controller.cc index 1f1ea9ec4b5..b46a42d9ee9 100644 --- a/chromium/ui/views/controls/menu/menu_controller.cc +++ b/chromium/ui/views/controls/menu/menu_controller.cc @@ -317,7 +317,8 @@ static void RepostEventImpl(const ui::LocatedEvent* event, window_y = pt.y; } - WPARAM target = client_area ? event->native_event().wParam : nc_hit_result; + WPARAM target = client_area ? event->native_event().wParam + : static_cast<WPARAM>(nc_hit_result); LPARAM window_coords = MAKELPARAM(window_x, window_y); PostMessage(target_window, event_type, target, window_coords); return; @@ -335,7 +336,7 @@ static void RepostEventImpl(const ui::LocatedEvent* event, gfx::Point root_loc(screen_loc); spc->ConvertPointFromScreen(root, &root_loc); - std::unique_ptr<ui::Event> clone = ui::Event::Clone(*event); + std::unique_ptr<ui::Event> clone = event->Clone(); std::unique_ptr<ui::LocatedEvent> located_event( static_cast<ui::LocatedEvent*>(clone.release())); located_event->set_location(root_loc); @@ -434,18 +435,18 @@ struct MenuController::SelectByCharDetails { SelectByCharDetails() = default; // Index of the first menu with the specified mnemonic. - int first_match = -1; + absl::optional<size_t> first_match; // If true there are multiple menu items with the same mnemonic. bool has_multiple = false; - // Index of the selected item; may remain -1. - int index_of_item = -1; + // Index of the selected item; may remain nullopt. + absl::optional<size_t> index_of_item; // If there are multiple matches this is the index of the item after the - // currently selected item whose mnemonic matches. This may remain -1 even - // though there are matches. - int next_match = -1; + // currently selected item whose mnemonic matches. This may remain nullopt + // even though there are matches. + absl::optional<size_t> next_match; }; // MenuController:State ------------------------------------------------------ @@ -937,7 +938,13 @@ void MenuController::OnGestureEvent(SubmenuView* source, } else if (event->type() == ui::ET_GESTURE_TAP) { if (!part.is_scroll() && part.menu && !(part.should_submenu_show && part.menu->HasSubmenu())) { - if (part.menu->GetDelegate()->IsTriggerableEvent(part.menu, *event)) { + const int command = part.menu->GetCommand(); + if (part.menu->GetDelegate()->ShouldExecuteCommandWithoutClosingMenu( + command, *event)) { + item_selected_by_touch_ = true; + part.menu->GetDelegate()->ExecuteCommand(command, 0); + } else if (part.menu->GetDelegate()->IsTriggerableEvent(part.menu, + *event)) { item_selected_by_touch_ = true; Accept(part.menu, event->flags()); } @@ -1605,7 +1612,13 @@ bool MenuController::OnKeyPressed(const ui::KeyEvent& event) { handled_key_code = true; if (!SendAcceleratorToHotTrackedView(event.flags()) && pending_state_.item->GetEnabled()) { - Accept(pending_state_.item, event.flags()); + const int command = pending_state_.item->GetCommand(); + if (pending_state_.item->GetDelegate() + ->ShouldExecuteCommandWithoutClosingMenu(command, event)) { + pending_state_.item->GetDelegate()->ExecuteCommand(command, 0); + } else { + Accept(pending_state_.item, event.flags()); + } } } } @@ -2059,14 +2072,14 @@ void MenuController::CommitPendingSelection() { state_ = pending_state_; state_.open_leading.swap(pending_open_direction); - int menu_depth = MenuDepth(state_.item); + size_t menu_depth = MenuDepth(state_.item); if (menu_depth == 0) { state_.open_leading.clear(); } else { - int cached_size = static_cast<int>(state_.open_leading.size()); - DCHECK_GE(menu_depth, 0); - while (cached_size-- >= menu_depth) + for (size_t cached_size = state_.open_leading.size(); + cached_size >= menu_depth; --cached_size) { state_.open_leading.pop_back(); + } } if (!state_.item) { @@ -2256,10 +2269,10 @@ void MenuController::BuildPathsAndCalculateDiff( BuildMenuItemPath(old_item, old_path); BuildMenuItemPath(new_item, new_path); - *first_diff_at = std::distance( + *first_diff_at = static_cast<size_t>(std::distance( old_path->cbegin(), std::mismatch(old_path->cbegin(), old_path->cend(), new_path->cbegin(), new_path->cend()) - .first); + .first)); } void MenuController::BuildMenuItemPath(MenuItemView* item, @@ -2752,8 +2765,8 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds( } // static -int MenuController::MenuDepth(MenuItemView* item) { - return item ? (MenuDepth(item->GetParentMenuItem()) + 1) : 0; +size_t MenuController::MenuDepth(MenuItemView* item) { + return item ? (MenuDepth(item->GetParentMenuItem()) + 1) : size_t{0}; } void MenuController::IncrementSelection( @@ -2813,9 +2826,11 @@ void MenuController::SetSelectionIndices(MenuItemView* parent) { if (ordering.empty()) return; - const int set_size = ordering.size(); - for (int i = 0; i < set_size; ++i) - ordering[i]->GetViewAccessibility().OverridePosInSet(i + 1, set_size); + const size_t set_size = ordering.size(); + for (size_t i = 0; i < set_size; ++i) { + ordering[i]->GetViewAccessibility().OverridePosInSet( + static_cast<int>(i + 1), static_cast<int>(set_size)); + } } void MenuController::MoveSelectionToFirstOrLastItem( @@ -2842,38 +2857,20 @@ void MenuController::MoveSelectionToFirstOrLastItem( MenuItemView* MenuController::FindInitialSelectableMenuItem( MenuItemView* parent, SelectionIncrementDirectionType direction) { - return FindNextSelectableMenuItem( - parent, direction == INCREMENT_SELECTION_DOWN ? -1 : 0, direction, true); -} - -MenuItemView* MenuController::FindNextSelectableMenuItem( - MenuItemView* parent, - int index, - SelectionIncrementDirectionType direction, - bool is_initial) { - int parent_count = - static_cast<int>(parent->GetSubmenu()->GetMenuItems().size()); - int stop_index = (index + parent_count) % parent_count; - bool include_all_items = - (index == -1 && direction == INCREMENT_SELECTION_DOWN) || - (index == 0 && direction == INCREMENT_SELECTION_UP); - int delta = direction == INCREMENT_SELECTION_UP ? -1 : 1; - // Loop through the menu items skipping any invisible menus. The loop stops - // when we wrap or find a visible and enabled child. - do { - if (!MenuConfig::instance().arrow_key_selection_wraps && !is_initial) { - if (index == 0 && direction == INCREMENT_SELECTION_UP) - return nullptr; - if (index == parent_count - 1 && direction == INCREMENT_SELECTION_DOWN) - return nullptr; - } - index = (index + delta + parent_count) % parent_count; - if (index == stop_index && !include_all_items) - return nullptr; - MenuItemView* child = parent->GetSubmenu()->GetMenuItemAt(index); - if (child->IsTraversableByKeyboard()) - return child; - } while (index != stop_index); + const size_t parent_count = parent->GetSubmenu()->GetMenuItems().size(); + if (direction == INCREMENT_SELECTION_DOWN) { + for (size_t index = 0; index < parent_count; ++index) { + MenuItemView* child = parent->GetSubmenu()->GetMenuItemAt(index); + if (child->IsTraversableByKeyboard()) + return child; + } + } else { + for (size_t index = parent_count; index > 0; --index) { + MenuItemView* child = parent->GetSubmenu()->GetMenuItemAt(index - 1); + if (child->IsTraversableByKeyboard()) + return child; + } + } return nullptr; } @@ -2920,15 +2917,16 @@ MenuController::SelectByCharDetails MenuController::FindChildForMnemonic( MenuItemView* child = menu_items[i]; if (child->GetEnabled() && child->GetVisible()) { if (child == pending_state_.item) - details.index_of_item = static_cast<int>(i); + details.index_of_item = i; if (match_function(child, key)) { - if (details.first_match == -1) - details.first_match = static_cast<int>(i); - else + if (!details.first_match.has_value()) { + details.first_match = i; + } else { details.has_multiple = true; - if (details.next_match == -1 && details.index_of_item != -1 && - static_cast<int>(i) > details.index_of_item) - details.next_match = static_cast<int>(i); + } + if (!details.next_match.has_value() && + details.index_of_item.has_value() && i > details.index_of_item) + details.next_match = i; } } } @@ -2938,23 +2936,25 @@ MenuController::SelectByCharDetails MenuController::FindChildForMnemonic( void MenuController::AcceptOrSelect(MenuItemView* parent, const SelectByCharDetails& details) { // This should only be invoked if there is a match. - DCHECK_NE(details.first_match, -1); + DCHECK(details.first_match.has_value()); DCHECK(parent->HasSubmenu()); SubmenuView* submenu = parent->GetSubmenu(); DCHECK(submenu); if (!details.has_multiple) { // There's only one match, activate it (or open if it has a submenu). - if (submenu->GetMenuItemAt(details.first_match)->HasSubmenu()) { - SetSelection(submenu->GetMenuItemAt(details.first_match), + if (submenu->GetMenuItemAt(details.first_match.value())->HasSubmenu()) { + SetSelection(submenu->GetMenuItemAt(details.first_match.value()), SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); } else { - Accept(submenu->GetMenuItemAt(details.first_match), 0); + Accept(submenu->GetMenuItemAt(details.first_match.value()), 0); } - } else if (details.index_of_item == -1 || details.next_match == -1) { - SetSelection(submenu->GetMenuItemAt(details.first_match), + } else if (!details.index_of_item.has_value() || + !details.next_match.has_value()) { + SetSelection(submenu->GetMenuItemAt(details.first_match.value()), SELECTION_DEFAULT); } else { - SetSelection(submenu->GetMenuItemAt(details.next_match), SELECTION_DEFAULT); + SetSelection(submenu->GetMenuItemAt(details.next_match.value()), + SELECTION_DEFAULT); } } @@ -2979,7 +2979,7 @@ void MenuController::SelectByChar(char16_t character) { // Look for matches based on mnemonic first. SelectByCharDetails details = FindChildForMnemonic(item, key, &MatchesMnemonic); - if (details.first_match != -1) { + if (details.first_match.has_value()) { AcceptOrSelect(item, details); return; } @@ -2992,7 +2992,7 @@ void MenuController::SelectByChar(char16_t character) { } else { // If no mnemonics found, look at first character of titles. details = FindChildForMnemonic(item, key, &TitleMatchesMnemonic); - if (details.first_match != -1) + if (details.first_match.has_value()) AcceptOrSelect(item, details); } } @@ -3317,13 +3317,33 @@ void MenuController::SetNextHotTrackedView( if (!parent) return; const auto menu_items = parent->GetSubmenu()->GetMenuItems(); - if (menu_items.size() <= 1) + const size_t num_menu_items = menu_items.size(); + if (num_menu_items <= 1) return; const auto i = std::find(menu_items.cbegin(), menu_items.cend(), item); DCHECK(i != menu_items.cend()); - MenuItemView* to_select = FindNextSelectableMenuItem( - parent, std::distance(menu_items.cbegin(), i), direction, false); - SetInitialHotTrackedView(to_select, direction); + auto index = static_cast<size_t>(std::distance(menu_items.cbegin(), i)); + + // Loop through the menu items in the desired direction. Assume we can wrap + // all the way back to this item. + size_t stop_index = index; + if (!MenuConfig::instance().arrow_key_selection_wraps) { + // Don't want to allow wrapping, so stop as soon as it happens. + stop_index = direction == INCREMENT_SELECTION_UP ? (num_menu_items - 1) : 0; + } + const size_t delta = + direction == INCREMENT_SELECTION_UP ? (num_menu_items - 1) : 1; + while (true) { + index = (index + delta) % num_menu_items; + if (index == stop_index) + return; + // Stop on the next keyboard-traversable item. + MenuItemView* child = parent->GetSubmenu()->GetMenuItemAt(index); + if (child->IsTraversableByKeyboard()) { + SetInitialHotTrackedView(child, direction); + return; + } + } } void MenuController::SetHotTrackedButton(Button* new_hot_button) { @@ -3403,7 +3423,6 @@ void MenuController::SetAnchorParametersForItem(MenuItemView* item, anchor->anchor_gravity = ui::OwnedWindowAnchorGravity::kBottomRight; anchor->constraint_adjustment = ui::OwnedWindowConstraintAdjustment::kAdjustmentSlideX | - ui::OwnedWindowConstraintAdjustment::kAdjustmentSlideY | ui::OwnedWindowConstraintAdjustment::kAdjustmentFlipY | ui::OwnedWindowConstraintAdjustment::kAdjustmentRezizeY; } else { diff --git a/chromium/ui/views/controls/menu/menu_controller.h b/chromium/ui/views/controls/menu/menu_controller.h index a4958eb4244..ed374eab8e4 100644 --- a/chromium/ui/views/controls/menu/menu_controller.h +++ b/chromium/ui/views/controls/menu/menu_controller.h @@ -521,7 +521,7 @@ class VIEWS_EXPORT MenuController ui::OwnedWindowAnchor* anchor); // Returns the depth of the menu. - static int MenuDepth(MenuItemView* item); + static size_t MenuDepth(MenuItemView* item); // Selects the next or previous (depending on |direction|) menu item. void IncrementSelection(SelectionIncrementDirectionType direction); @@ -545,15 +545,6 @@ class VIEWS_EXPORT MenuController MenuItemView* parent, SelectionIncrementDirectionType direction); - // Returns the next or previous selectable child menu item of |parent| - // starting at |index| and incrementing or decrementing index by 1 depending - // on |direction|. If there are no more selectable items NULL is returned. - MenuItemView* FindNextSelectableMenuItem( - MenuItemView* parent, - int index, - SelectionIncrementDirectionType direction, - bool is_initial); - // If the selected item has a submenu and it isn't currently open, the // the selection is changed such that the menu opens immediately. void OpenSubmenuChangeSelectionIfCan(); diff --git a/chromium/ui/views/controls/menu/menu_controller_unittest.cc b/chromium/ui/views/controls/menu/menu_controller_unittest.cc index 0b68148ce1e..298f2fe71ac 100644 --- a/chromium/ui/views/controls/menu/menu_controller_unittest.cc +++ b/chromium/ui/views/controls/menu/menu_controller_unittest.cc @@ -721,17 +721,6 @@ class MenuControllerTest : public ViewsTestBase, parent, MenuController::INCREMENT_SELECTION_UP); } - MenuItemView* FindNextSelectableMenuItem(MenuItemView* parent, int index) { - return menu_controller_->FindNextSelectableMenuItem( - parent, index, MenuController::INCREMENT_SELECTION_DOWN, false); - } - - MenuItemView* FindPreviousSelectableMenuItem(MenuItemView* parent, - int index) { - return menu_controller_->FindNextSelectableMenuItem( - parent, index, MenuController::INCREMENT_SELECTION_UP, false); - } - internal::MenuControllerDelegate* GetCurrentDelegate() { return menu_controller_->delegate_; } @@ -768,6 +757,10 @@ class MenuControllerTest : public ViewsTestBase, // Note that coordinates of events passed to MenuController must be in that of // the MenuScrollViewContainer. + void ProcessGestureEvent(SubmenuView* source, ui::GestureEvent& event) { + menu_controller_->OnGestureEvent(source, &event); + } + void ProcessMousePressed(SubmenuView* source, const ui::MouseEvent& event) { menu_controller_->OnMousePressed(source, event); } @@ -1065,11 +1058,6 @@ TEST_F(MenuControllerTest, InitialSelectedItem) { ASSERT_NE(nullptr, last_selectable); EXPECT_EQ(2, last_selectable->GetCommand()); - // There should be no next or previous selectable item since there is only a - // single enabled item in the menu. - EXPECT_EQ(nullptr, FindNextSelectableMenuItem(menu_item(), 1)); - EXPECT_EQ(nullptr, FindPreviousSelectableMenuItem(menu_item(), 1)); - // Clear references in menu controller to the menu item that is going away. ResetSelection(); } @@ -2839,7 +2827,6 @@ TEST_F(MenuControllerTest, ContextMenuInitializesAuraWindowWhenShown) { anchor->anchor_position); EXPECT_EQ(ui::OwnedWindowAnchorGravity::kBottomRight, anchor->anchor_gravity); EXPECT_EQ((ui::OwnedWindowConstraintAdjustment::kAdjustmentSlideX | - ui::OwnedWindowConstraintAdjustment::kAdjustmentSlideY | ui::OwnedWindowConstraintAdjustment::kAdjustmentFlipY | ui::OwnedWindowConstraintAdjustment::kAdjustmentRezizeY), anchor->constraint_adjustment); @@ -3256,5 +3243,75 @@ TEST_F(MenuControllerTest, BrowserHotkeysCancelMenusAndAreRedispatched) { } #endif +class ExecuteCommandWithoutClosingMenuTest : public MenuControllerTest { + public: + void SetUp() override { + MenuControllerTest::SetUp(); + + views::test::DisableMenuClosureAnimations(); + menu_controller()->Run(owner(), nullptr, menu_item(), gfx::Rect(), + MenuAnchorPosition::kTopLeft, false, false); + + MenuHost::InitParams params; + params.parent = owner(); + params.bounds = gfx::Rect(0, 0, 100, 100); + params.do_capture = false; + menu_item()->GetSubmenu()->ShowAt(params); + + menu_delegate()->set_should_execute_command_without_closing_menu(true); + } +}; + +TEST_F(ExecuteCommandWithoutClosingMenuTest, OnClick) { + TestMenuControllerDelegate* delegate = menu_controller_delegate(); + EXPECT_EQ(0, delegate->on_menu_closed_called()); + + MenuItemView* menu_item_view = menu_item()->GetSubmenu()->GetMenuItemAt(0); + gfx::Point press_location(menu_item_view->bounds().CenterPoint()); + ui::MouseEvent press_event(ui::ET_MOUSE_PRESSED, press_location, + press_location, ui::EventTimeForNow(), + ui::EF_LEFT_MOUSE_BUTTON, 0); + ui::MouseEvent release_event(ui::ET_MOUSE_RELEASED, press_location, + press_location, ui::EventTimeForNow(), + ui::EF_LEFT_MOUSE_BUTTON, 0); + ProcessMousePressed(menu_item()->GetSubmenu(), press_event); + ProcessMouseReleased(menu_item()->GetSubmenu(), release_event); + + EXPECT_EQ(0, delegate->on_menu_closed_called()); + EXPECT_TRUE(IsShowing()); + EXPECT_EQ(menu_delegate()->execute_command_id(), + menu_item_view->GetCommand()); +} + +TEST_F(ExecuteCommandWithoutClosingMenuTest, OnTap) { + TestMenuControllerDelegate* delegate = menu_controller_delegate(); + EXPECT_EQ(0, delegate->on_menu_closed_called()); + + MenuItemView* menu_item_view = menu_item()->GetSubmenu()->GetMenuItemAt(0); + gfx::Point tap_location(menu_item_view->bounds().CenterPoint()); + ui::GestureEvent event(tap_location.x(), tap_location.y(), 0, + ui::EventTimeForNow(), + ui::GestureEventDetails(ui::ET_GESTURE_TAP)); + ProcessGestureEvent(menu_item()->GetSubmenu(), event); + + EXPECT_EQ(0, delegate->on_menu_closed_called()); + EXPECT_TRUE(IsShowing()); + EXPECT_EQ(menu_delegate()->execute_command_id(), + menu_item_view->GetCommand()); +} + +TEST_F(ExecuteCommandWithoutClosingMenuTest, OnReturnKey) { + TestMenuControllerDelegate* delegate = menu_controller_delegate(); + EXPECT_EQ(0, delegate->on_menu_closed_called()); + + DispatchKey(ui::VKEY_DOWN); + DispatchKey(ui::VKEY_RETURN); + + EXPECT_EQ(0, delegate->on_menu_closed_called()); + EXPECT_TRUE(IsShowing()); + EXPECT_EQ(menu_delegate()->execute_command_id(), + menu_item()->GetSubmenu()->GetMenuItemAt(0)->GetCommand()); +} + } // namespace test } // namespace views diff --git a/chromium/ui/views/controls/menu/menu_insertion_delegate_win.h b/chromium/ui/views/controls/menu/menu_insertion_delegate_win.h index 461620c55e4..235c34e7eee 100644 --- a/chromium/ui/views/controls/menu/menu_insertion_delegate_win.h +++ b/chromium/ui/views/controls/menu/menu_insertion_delegate_win.h @@ -12,7 +12,7 @@ namespace views { class MenuInsertionDelegateWin { public: // Returns the index to insert items into the menu at. - virtual int GetInsertionIndex(HMENU native_menu) = 0; + virtual size_t GetInsertionIndex(HMENU native_menu) = 0; protected: virtual ~MenuInsertionDelegateWin() = default; diff --git a/chromium/ui/views/controls/menu/menu_item_view.cc b/chromium/ui/views/controls/menu/menu_item_view.cc index 6da48e9716b..854398921a3 100644 --- a/chromium/ui/views/controls/menu/menu_item_view.cc +++ b/chromium/ui/views/controls/menu/menu_item_view.cc @@ -12,6 +12,7 @@ #include <numeric> #include <utility> +#include "base/auto_reset.h" #include "base/containers/adapters.h" #include "base/containers/contains.h" #include "base/i18n/case_conversion.h" @@ -97,9 +98,6 @@ class VerticalSeparator : public Separator { VerticalSeparator(const VerticalSeparator&) = delete; VerticalSeparator& operator=(const VerticalSeparator&) = delete; ~VerticalSeparator() override = default; - - // Separator: - void OnThemeChanged() override; }; VerticalSeparator::VerticalSeparator() { @@ -109,15 +107,11 @@ VerticalSeparator::VerticalSeparator() { gfx::Size(config.actionable_submenu_vertical_separator_width, config.actionable_submenu_vertical_separator_height)); SetCanProcessEventsWithinSubtree(false); -} - -void VerticalSeparator::OnThemeChanged() { - Separator::OnThemeChanged(); ui::ColorId id = ui::kColorMenuSeparator; #if BUILDFLAG(IS_CHROMEOS_ASH) id = ui::kColorAshSystemUIMenuSeparator; #endif - SetColor(GetColorProvider()->GetColor(id)); + SetColorId(id); } BEGIN_METADATA(VerticalSeparator, Separator) @@ -171,8 +165,10 @@ void MenuItemView::ViewHierarchyChanged( const ViewHierarchyChangedDetails& details) { // Whether the selection is painted may change based on the number of // children. - if (details.parent == this) + if (details.parent == this && + update_selection_based_state_in_view_herarchy_changed_) { UpdateSelectionBasedStateIfChanged(PaintMode::kNormal); + } } std::u16string MenuItemView::GetTooltipText(const gfx::Point& p) const { @@ -356,7 +352,7 @@ void MenuItemView::Cancel() { } MenuItemView* MenuItemView::AddMenuItemAt( - int index, + size_t index, int item_id, const std::u16string& label, const std::u16string& secondary_label, @@ -366,10 +362,9 @@ MenuItemView* MenuItemView::AddMenuItemAt( Type type, ui::MenuSeparatorType separator_style) { DCHECK_NE(type, Type::kEmpty); - DCHECK_GE(index, 0); if (!submenu_) CreateSubmenu(); - DCHECK_LE(static_cast<size_t>(index), submenu_->children().size()); + DCHECK_LE(index, submenu_->children().size()); if (type == Type::kSeparator) { submenu_->AddChildViewAt(std::make_unique<MenuSeparator>(separator_style), index); @@ -429,7 +424,7 @@ void MenuItemView::AppendSeparator() { AppendMenuItemImpl(0, std::u16string(), ui::ImageModel(), Type::kSeparator); } -void MenuItemView::AddSeparatorAt(int index) { +void MenuItemView::AddSeparatorAt(size_t index) { AddMenuItemAt(index, /*item_id=*/0, /*label=*/std::u16string(), /*secondary_label=*/std::u16string(), /*minor_text=*/std::u16string(), @@ -443,8 +438,7 @@ MenuItemView* MenuItemView::AppendMenuItemImpl(int item_id, const std::u16string& label, const ui::ImageModel& icon, Type type) { - const int index = - submenu_ ? static_cast<int>(submenu_->children().size()) : 0; + const size_t index = submenu_ ? submenu_->children().size() : size_t{0}; return AddMenuItemAt(index, item_id, label, std::u16string(), std::u16string(), ui::ImageModel(), icon, type, ui::NORMAL_SEPARATOR); @@ -559,13 +553,22 @@ void MenuItemView::SetIcon(const ui::ImageModel& icon) { } void MenuItemView::SetIconView(std::unique_ptr<ImageView> icon_view) { - if (icon_view_) { - RemoveChildViewT(icon_view_.get()); - icon_view_ = nullptr; + { + // See comment in `update_selection_based_state_in_view_herarchy_changed_` + // as to why setting the field and explicitly calling + // UpdateSelectionBasedStateIfChanged() is necessary. + base::AutoReset setter( + &update_selection_based_state_in_view_herarchy_changed_, false); + if (icon_view_) { + RemoveChildViewT(icon_view_.get()); + icon_view_ = nullptr; + } + + if (icon_view) + icon_view_ = AddChildView(std::move(icon_view)); } - if (icon_view) - icon_view_ = AddChildView(std::move(icon_view)); + UpdateSelectionBasedStateIfChanged(PaintMode::kNormal); InvalidateLayout(); SchedulePaint(); @@ -1433,6 +1436,11 @@ gfx::Insets MenuItemView::GetContainerMargins() const { } int MenuItemView::NonIconChildViewsCount() const { + // WARNING: if adding a new field that is checked here you may need to + // set `update_selection_based_state_in_view_herarchy_changed_` to false + // when setting the field and explicitly call + // UpdateSelectionBasedStateIfChanged(). See comment in header + // for details. return static_cast<int>(children().size()) - (icon_view_ ? 1 : 0) - (radio_check_image_view_ ? 1 : 0) - (submenu_arrow_image_view_ ? 1 : 0) - (vertical_separator_ ? 1 : 0); diff --git a/chromium/ui/views/controls/menu/menu_item_view.h b/chromium/ui/views/controls/menu/menu_item_view.h index 4fb6b50dd06..d531717529e 100644 --- a/chromium/ui/views/controls/menu/menu_item_view.h +++ b/chromium/ui/views/controls/menu/menu_item_view.h @@ -154,7 +154,7 @@ class VIEWS_EXPORT MenuItemView : public View { // Add an item to the menu at a specified index. ChildrenChanged() should // called after adding menu items if the menu may be active. - MenuItemView* AddMenuItemAt(int index, + MenuItemView* AddMenuItemAt(size_t index, int item_id, const std::u16string& label, const std::u16string& secondary_label, @@ -194,7 +194,7 @@ class VIEWS_EXPORT MenuItemView : public View { void AppendSeparator(); // Adds a separator to this menu at the specified position. - void AddSeparatorAt(int index); + void AddSeparatorAt(size_t index); // All the AppendXXX methods funnel into this. MenuItemView* AppendMenuItemImpl(int item_id, @@ -369,7 +369,6 @@ class VIEWS_EXPORT MenuItemView : public View { bool is_alerted() const { return is_alerted_; } // Returns whether or not a "new" badge should be shown on this menu item. - // Takes into account whether the badging feature is enabled. bool ShouldShowNewBadge() const; // Returns whether keyboard navigation through the menu should stop on this @@ -580,8 +579,8 @@ class VIEWS_EXPORT MenuItemView : public View { // Command id. int command_ = 0; - // Whether the menu item should be badged as "New" (if badging is enabled) as - // a way to highlight a new feature for users. + // Whether the menu item should be badged as "New" as a way to highlight a new + // feature for users. bool is_new_ = false; // Whether the menu item contains user-created text. @@ -663,6 +662,19 @@ class VIEWS_EXPORT MenuItemView : public View { // Whether this menu item is rendered differently to draw attention to it. bool is_alerted_ = false; + + // If true, ViewHierarchyChanged() will call + // UpdateSelectionBasedStateIfChanged(). + // UpdateSelectionBasedStateIfChanged() calls to NonIconChildViewsCount(). + // NonIconChildViewsCount() accesses fields of type View as part of the + // implementation. A common pattern for assigning a field is: + // icon_view_ = AddChildView(icon_view); + // The problem is ViewHierarchyChanged() is called during AddChildView() and + // before `icon_view_` is set. This means NonIconChildViewsCount() may return + // the wrong thing. In this case + // `update_selection_based_state_in_view_herarchy_changed_` is set to false + // and SetIconView() explicitly calls UpdateSelectionBasedStateIfChanged(). + bool update_selection_based_state_in_view_herarchy_changed_ = true; }; } // namespace views diff --git a/chromium/ui/views/controls/menu/menu_item_view_unittest.cc b/chromium/ui/views/controls/menu/menu_item_view_unittest.cc index e963380320d..419ff6d08d1 100644 --- a/chromium/ui/views/controls/menu/menu_item_view_unittest.cc +++ b/chromium/ui/views/controls/menu/menu_item_view_unittest.cc @@ -541,6 +541,24 @@ TEST_F(MenuItemViewPaintUnitTest, EXPECT_FALSE(submenu_child->last_paint_as_selected_for_testing()); } +TEST_F(MenuItemViewPaintUnitTest, SelectionBasedStateUpdatedWhenIconChanges) { + MenuItemView* child_menu_item = + menu_item_view()->AppendMenuItem(1, u"menu item"); + + menu_runner()->RunMenuAt(widget(), nullptr, gfx::Rect(), + MenuAnchorPosition::kTopLeft, + ui::MENU_SOURCE_KEYBOARD); + + EXPECT_FALSE(child_menu_item->last_paint_as_selected_for_testing()); + child_menu_item->SetSelected(true); + EXPECT_TRUE(child_menu_item->IsSelected()); + EXPECT_TRUE(child_menu_item->last_paint_as_selected_for_testing()); + + child_menu_item->SetIconView(std::make_unique<ImageView>()); + EXPECT_TRUE(child_menu_item->IsSelected()); + EXPECT_TRUE(child_menu_item->last_paint_as_selected_for_testing()); +} + // Sets up a custom MenuDelegate that expects functions aren't called. See // DontAskForFontsWhenAddingSubmenu. class MenuItemViewAccessTest : public MenuItemViewPaintUnitTest { diff --git a/chromium/ui/views/controls/menu/menu_model_adapter.cc b/chromium/ui/views/controls/menu/menu_model_adapter.cc index fb5d5e6a79a..285e4a829ed 100644 --- a/chromium/ui/views/controls/menu/menu_model_adapter.cc +++ b/chromium/ui/views/controls/menu/menu_model_adapter.cc @@ -68,9 +68,9 @@ MenuItemView* MenuModelAdapter::CreateMenu() { // Static. MenuItemView* MenuModelAdapter::AddMenuItemFromModelAt(ui::MenuModel* model, - int model_index, + size_t model_index, MenuItemView* menu, - int menu_index, + size_t menu_index, int item_id) { absl::optional<MenuItemView::Type> type; ui::MenuModel::ItemType menu_type = model->GetTypeAt(model_index); @@ -133,19 +133,17 @@ MenuItemView* MenuModelAdapter::AddMenuItemFromModelAt(ui::MenuModel* model, // Static. MenuItemView* MenuModelAdapter::AppendMenuItemFromModel(ui::MenuModel* model, - int model_index, + size_t model_index, MenuItemView* menu, int item_id) { - const int menu_index = - menu->HasSubmenu() - ? static_cast<int>(menu->GetSubmenu()->children().size()) - : 0; + const size_t menu_index = + menu->HasSubmenu() ? menu->GetSubmenu()->children().size() : size_t{0}; return AddMenuItemFromModelAt(model, model_index, menu, menu_index, item_id); } MenuItemView* MenuModelAdapter::AppendMenuItem(MenuItemView* menu, ui::MenuModel* model, - int index) { + size_t index) { return AppendMenuItemFromModel(model, index, menu, model->GetCommandIdAt(index)); } @@ -154,7 +152,7 @@ MenuItemView* MenuModelAdapter::AppendMenuItem(MenuItemView* menu, void MenuModelAdapter::ExecuteCommand(int id) { ui::MenuModel* model = menu_model_; - int index = 0; + size_t index = 0; if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) { model->ActivatedAt(index); return; @@ -165,7 +163,7 @@ void MenuModelAdapter::ExecuteCommand(int id) { void MenuModelAdapter::ExecuteCommand(int id, int mouse_event_flags) { ui::MenuModel* model = menu_model_; - int index = 0; + size_t index = 0; if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) { model->ActivatedAt(index, mouse_event_flags); return; @@ -184,7 +182,7 @@ bool MenuModelAdapter::IsTriggerableEvent(MenuItemView* source, bool MenuModelAdapter::GetAccelerator(int id, ui::Accelerator* accelerator) const { ui::MenuModel* model = menu_model_; - int index = 0; + size_t index = 0; if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) return model->GetAcceleratorAt(index, accelerator); @@ -194,7 +192,7 @@ bool MenuModelAdapter::GetAccelerator(int id, std::u16string MenuModelAdapter::GetLabel(int id) const { ui::MenuModel* model = menu_model_; - int index = 0; + size_t index = 0; if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) return model->GetLabelAt(index); @@ -204,7 +202,7 @@ std::u16string MenuModelAdapter::GetLabel(int id) const { const gfx::FontList* MenuModelAdapter::GetLabelFontList(int id) const { ui::MenuModel* model = menu_model_; - int index = 0; + size_t index = 0; if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) { const gfx::FontList* font_list = model->GetLabelFontListAt(index); if (font_list) @@ -217,7 +215,7 @@ const gfx::FontList* MenuModelAdapter::GetLabelFontList(int id) const { bool MenuModelAdapter::IsCommandEnabled(int id) const { ui::MenuModel* model = menu_model_; - int index = 0; + size_t index = 0; if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) return model->IsEnabledAt(index); @@ -227,7 +225,7 @@ bool MenuModelAdapter::IsCommandEnabled(int id) const { bool MenuModelAdapter::IsCommandVisible(int id) const { ui::MenuModel* model = menu_model_; - int index = 0; + size_t index = 0; if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) return model->IsVisibleAt(index); @@ -237,7 +235,7 @@ bool MenuModelAdapter::IsCommandVisible(int id) const { bool MenuModelAdapter::IsItemChecked(int id) const { ui::MenuModel* model = menu_model_; - int index = 0; + size_t index = 0; if (ui::MenuModel::GetModelAndIndexForCommandId(id, &model, &index)) return model->IsItemCheckedAt(index); @@ -290,8 +288,8 @@ void MenuModelAdapter::BuildMenuImpl(MenuItemView* menu, ui::MenuModel* model) { DCHECK(menu); DCHECK(model); bool has_icons = model->HasIcons(); - const int item_count = model->GetItemCount(); - for (int i = 0; i < item_count; ++i) { + const size_t item_count = model->GetItemCount(); + for (size_t i = 0; i < item_count; ++i) { MenuItemView* item = AppendMenuItem(menu, model, i); if (item) { // Enabled state should be ignored for titles as they are non-interactive. diff --git a/chromium/ui/views/controls/menu/menu_model_adapter.h b/chromium/ui/views/controls/menu/menu_model_adapter.h index b7c7474fb59..ebbc36a305a 100644 --- a/chromium/ui/views/controls/menu/menu_model_adapter.h +++ b/chromium/ui/views/controls/menu/menu_model_adapter.h @@ -53,15 +53,15 @@ class VIEWS_EXPORT MenuModelAdapter : public MenuDelegate, // Creates a menu item for the specified entry in the model and adds it as // a child to |menu| at the specified |menu_index|. static MenuItemView* AddMenuItemFromModelAt(ui::MenuModel* model, - int model_index, + size_t model_index, MenuItemView* menu, - int menu_index, + size_t menu_index, int item_id); // Creates a menu item for the specified entry in the model and appends it as // a child to |menu|. static MenuItemView* AppendMenuItemFromModel(ui::MenuModel* model, - int model_index, + size_t model_index, MenuItemView* menu, int item_id); @@ -76,7 +76,7 @@ class VIEWS_EXPORT MenuModelAdapter : public MenuDelegate, // menu. virtual MenuItemView* AppendMenuItem(MenuItemView* menu, ui::MenuModel* model, - int index); + size_t index); // views::MenuDelegate implementation. void ExecuteCommand(int id) override; diff --git a/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc b/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc index e193f357398..3b2dc294c55 100644 --- a/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc +++ b/chromium/ui/views/controls/menu/menu_model_adapter_unittest.cc @@ -27,7 +27,7 @@ constexpr int kActionableSubmenuIdBase = 300; class MenuModelBase : public ui::MenuModel { public: explicit MenuModelBase(int command_id_base) - : command_id_base_(command_id_base), last_activation_(-1) {} + : command_id_base_(command_id_base) {} MenuModelBase(const MenuModelBase&) = delete; MenuModelBase& operator=(const MenuModelBase&) = delete; @@ -38,62 +38,70 @@ class MenuModelBase : public ui::MenuModel { bool HasIcons() const override { return false; } - int GetItemCount() const override { return static_cast<int>(items_.size()); } + size_t GetItemCount() const override { return items_.size(); } - ItemType GetTypeAt(int index) const override { return items_[index].type; } + ItemType GetTypeAt(size_t index) const override { return items_[index].type; } - ui::MenuSeparatorType GetSeparatorTypeAt(int index) const override { + ui::MenuSeparatorType GetSeparatorTypeAt(size_t index) const override { return ui::NORMAL_SEPARATOR; } - int GetCommandIdAt(int index) const override { - return index + command_id_base_; + int GetCommandIdAt(size_t index) const override { + return static_cast<int>(index) + command_id_base_; } - std::u16string GetLabelAt(int index) const override { + std::u16string GetLabelAt(size_t index) const override { return items_[index].label; } - bool IsItemDynamicAt(int index) const override { return false; } + bool IsItemDynamicAt(size_t index) const override { return false; } - const gfx::FontList* GetLabelFontListAt(int index) const override { + const gfx::FontList* GetLabelFontListAt(size_t index) const override { return nullptr; } - bool GetAcceleratorAt(int index, + bool GetAcceleratorAt(size_t index, ui::Accelerator* accelerator) const override { return false; } - bool IsItemCheckedAt(int index) const override { return false; } + bool IsItemCheckedAt(size_t index) const override { return false; } - int GetGroupIdAt(int index) const override { return 0; } + int GetGroupIdAt(size_t index) const override { return 0; } - ui::ImageModel GetIconAt(int index) const override { + ui::ImageModel GetIconAt(size_t index) const override { return ui::ImageModel(); } - ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override { + ui::ButtonMenuItemModel* GetButtonMenuItemAt(size_t index) const override { return nullptr; } - bool IsEnabledAt(int index) const override { return items_[index].enabled; } + bool IsEnabledAt(size_t index) const override { + return items_[index].enabled; + } - bool IsVisibleAt(int index) const override { return items_[index].visible; } + bool IsVisibleAt(size_t index) const override { + return items_[index].visible; + } - bool IsAlertedAt(int index) const override { return items_[index].alerted; } + bool IsAlertedAt(size_t index) const override { + return items_[index].alerted; + } - bool IsNewFeatureAt(int index) const override { + bool IsNewFeatureAt(size_t index) const override { return items_[index].new_feature; } - MenuModel* GetSubmenuModelAt(int index) const override { + MenuModel* GetSubmenuModelAt(size_t index) const override { return items_[index].submenu; } - void ActivatedAt(int index) override { set_last_activation(index); } + void ActivatedAt(size_t index) override { set_last_activation(index); } - void ActivatedAt(int index, int event_flags) override { ActivatedAt(index); } + void ActivatedAt(size_t index, int event_flags) override { + ActivatedAt(index); + } void MenuWillShow() override {} @@ -133,8 +141,8 @@ class MenuModelBase : public ui::MenuModel { const Item& GetItemDefinition(size_t index) { return items_[index]; } // Access index argument to ActivatedAt(). - int last_activation() const { return last_activation_; } - void set_last_activation(int last_activation) { + absl::optional<size_t> last_activation() const { return last_activation_; } + void set_last_activation(absl::optional<size_t> last_activation) { last_activation_ = last_activation; } @@ -143,7 +151,7 @@ class MenuModelBase : public ui::MenuModel { private: int command_id_base_; - int last_activation_; + absl::optional<size_t> last_activation_; }; class SubmenuModel : public MenuModelBase { @@ -204,7 +212,7 @@ void CheckSubmenu(const RootModel& model, views::MenuModelAdapter* delegate, int submenu_id, size_t expected_children, - int submenu_model_index, + size_t submenu_model_index, int id) { views::MenuItemView* submenu = menu->GetMenuItemByID(submenu_id); views::SubmenuView* subitem_container = submenu->GetSubmenu(); @@ -222,7 +230,7 @@ void CheckSubmenu(const RootModel& model, continue; } // Check placement. - EXPECT_EQ(i, static_cast<size_t>(submenu->GetSubmenu()->GetIndexOf(item))); + EXPECT_EQ(i, submenu->GetSubmenu()->GetIndexOf(item)); // Check type. switch (model_item.type) { @@ -267,8 +275,8 @@ void CheckSubmenu(const RootModel& model, // Check activation. static_cast<views::MenuDelegate*>(delegate)->ExecuteCommand(id); - EXPECT_EQ(i, static_cast<size_t>(submodel->last_activation())); - submodel->set_last_activation(-1); + EXPECT_EQ(i, submodel->last_activation()); + submodel->set_last_activation(absl::nullopt); } } @@ -306,7 +314,7 @@ TEST_F(MenuModelAdapterTest, BasicTest) { } // Check placement. - EXPECT_EQ(i, static_cast<size_t>(menu->GetSubmenu()->GetIndexOf(item))); + EXPECT_EQ(i, menu->GetSubmenu()->GetIndexOf(item)); // Check type. switch (model_item.type) { @@ -351,8 +359,8 @@ TEST_F(MenuModelAdapterTest, BasicTest) { // Check activation. static_cast<views::MenuDelegate*>(&delegate)->ExecuteCommand(id); - EXPECT_EQ(i, static_cast<size_t>(model.last_activation())); - model.set_last_activation(-1); + EXPECT_EQ(i, model.last_activation()); + model.set_last_activation(absl::nullopt); } // Check the submenu. diff --git a/chromium/ui/views/controls/menu/menu_pre_target_handler_mac.h b/chromium/ui/views/controls/menu/menu_pre_target_handler_mac.h index a4eedef51d7..bd63b29c5f7 100644 --- a/chromium/ui/views/controls/menu/menu_pre_target_handler_mac.h +++ b/chromium/ui/views/controls/menu/menu_pre_target_handler_mac.h @@ -7,6 +7,7 @@ #include <memory> +#include "base/memory/raw_ptr.h" #include "ui/views/cocoa/native_widget_mac_ns_window_host.h" #include "ui/views/controls/menu/menu_pre_target_handler.h" @@ -34,7 +35,7 @@ class MenuPreTargetHandlerMac : public MenuPreTargetHandler, bool* was_handled) final; std::unique_ptr<NativeWidgetMacEventMonitor> monitor_; - MenuController* const controller_; // Weak. Owns |this|. + const raw_ptr<MenuController> controller_; // Weak. Owns |this|. }; } // namespace views diff --git a/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm b/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm index 06022dd6404..95b0d17154a 100644 --- a/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm +++ b/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/memory/raw_ptr.h" + #import "ui/views/controls/menu/menu_runner_impl_cocoa.h" #import <Cocoa/Cocoa.h> @@ -69,7 +71,7 @@ class TestModel : public ui::SimpleMenuModel { } private: - TestModel* model_; + raw_ptr<TestModel> model_; }; private: @@ -130,7 +132,7 @@ class MenuRunnerCocoaTest : public ViewsTestBase, if (runner_) { runner_->Release(); - runner_ = NULL; + runner_ = nullptr; } parent_->CloseNow(); @@ -247,8 +249,8 @@ class MenuRunnerCocoaTest : public ViewsTestBase, protected: std::unique_ptr<TestModel> menu_; - internal::MenuRunnerImplInterface* runner_ = nullptr; - views::Widget* parent_ = nullptr; + raw_ptr<internal::MenuRunnerImplInterface> runner_ = nullptr; + raw_ptr<views::Widget> parent_ = nullptr; NSRect last_anchor_frame_ = NSZeroRect; NSUInteger native_view_subview_count_ = 0; int menu_close_count_ = 0; @@ -384,7 +386,7 @@ TEST_P(MenuRunnerCocoaTest, CancelWithoutRunning) { TEST_P(MenuRunnerCocoaTest, DeleteWithoutRunning) { runner_->Release(); - runner_ = NULL; + runner_ = nullptr; EXPECT_EQ(0, menu_close_count_); } diff --git a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm index 6f92eb0c4f6..a6dec3b24f5 100644 --- a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm +++ b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm @@ -9,6 +9,7 @@ #include "base/i18n/rtl.h" #include "base/mac/mac_util.h" #import "base/message_loop/message_pump_mac.h" +#include "base/numerics/safe_conversions.h" #import "skia/ext/skia_utils_mac.h" #import "ui/base/cocoa/cocoa_base_utils.h" #import "ui/base/cocoa/menu_controller.h" @@ -212,7 +213,7 @@ NSImage* IPHDotImage(const ui::ColorProvider* color_provider) { - (void)controllerWillAddItem:(NSMenuItem*)menuItem fromModel:(ui::MenuModel*)model - atIndex:(NSInteger)index + atIndex:(size_t)index withColorProvider:(const ui::ColorProvider*)colorProvider { if (model->IsNewFeatureAt(index)) { NSMutableAttributedString* attrTitle = [[[NSMutableAttributedString alloc] @@ -242,12 +243,12 @@ NSImage* IPHDotImage(const ui::ColorProvider* color_provider) { } - (void)controllerWillAddMenu:(NSMenu*)menu fromModel:(ui::MenuModel*)model { - int alerted_index = -1; + absl::optional<size_t> alerted_index; IdentifierContainer* const element_ids = [[[IdentifierContainer alloc] init] autorelease]; - for (int i = 0; i < model->GetItemCount(); ++i) { + for (size_t i = 0; i < model->GetItemCount(); ++i) { if (model->IsAlertedAt(i)) { - DCHECK_LT(alerted_index, 0); + DCHECK(!alerted_index.has_value()); alerted_index = i; } const ui::ElementIdentifier identifier = model->GetElementIdentifierAt(i); @@ -255,14 +256,16 @@ NSImage* IPHDotImage(const ui::ColorProvider* color_provider) { [element_ids ids].push_back(identifier); } - if (alerted_index >= 0 || ![element_ids ids].empty()) { + if (alerted_index.has_value() || ![element_ids ids].empty()) { auto shown_callback = ^(NSNotification* note) { NSMenu* const menu_obj = note.object; - if (alerted_index >= 0) { + if (alerted_index.has_value()) { if ([menu respondsToSelector:@selector(_menuImpl)]) { NSCarbonMenuImpl* menuImpl = [menu_obj _menuImpl]; if ([menuImpl respondsToSelector:@selector(highlightItemAtIndex:)]) { - [menuImpl highlightItemAtIndex:alerted_index]; + const auto index = + base::checked_cast<NSInteger>(alerted_index.value()); + [menuImpl highlightItemAtIndex:index]; } } } @@ -366,8 +369,10 @@ namespace { // Returns the first item in |menu_controller|'s menu that will be checked. NSMenuItem* FirstCheckedItem(MenuControllerCocoa* menu_controller) { for (NSMenuItem* item in [[menu_controller menu] itemArray]) { - if ([menu_controller model]->IsItemCheckedAt([item tag])) + if ([menu_controller model]->IsItemCheckedAt( + base::checked_cast<size_t>([item tag]))) { return item; + } } return nil; } diff --git a/chromium/ui/views/controls/menu/native_menu_win.cc b/chromium/ui/views/controls/menu/native_menu_win.cc index 3a62f75fdbf..3a5fdbab314 100644 --- a/chromium/ui/views/controls/menu/native_menu_win.cc +++ b/chromium/ui/views/controls/menu/native_menu_win.cc @@ -31,7 +31,7 @@ struct NativeMenuWin::ItemData { raw_ptr<NativeMenuWin> native_menu_win; // The index of the item within the menu's model. - int model_index; + size_t model_index; }; // Returns the NativeMenuWin for a particular HMENU. @@ -68,10 +68,10 @@ void NativeMenuWin::Rebuild(MenuInsertionDelegateWin* delegate) { items_.clear(); owner_draw_ = model_->HasIcons() || owner_draw_; - first_item_index_ = delegate ? delegate->GetInsertionIndex(menu_) : 0; - for (int model_index = 0; model_index < model_->GetItemCount(); + first_item_index_ = delegate ? delegate->GetInsertionIndex(menu_) : size_t{0}; + for (size_t model_index = 0; model_index < model_->GetItemCount(); ++model_index) { - int menu_index = model_index + first_item_index_; + size_t menu_index = model_index + first_item_index_; if (model_->GetTypeAt(model_index) == ui::MenuModel::TYPE_SEPARATOR) AddSeparatorItemAt(menu_index, model_index); else @@ -81,9 +81,9 @@ void NativeMenuWin::Rebuild(MenuInsertionDelegateWin* delegate) { void NativeMenuWin::UpdateStates() { // A depth-first walk of the menu items, updating states. - int model_index = 0; + size_t model_index = 0; for (const auto& item : items_) { - int menu_index = model_index + first_item_index_; + size_t menu_index = model_index + first_item_index_; SetMenuItemState(menu_index, model_->IsEnabledAt(model_index), model_->IsItemCheckedAt(model_index), false); if (model_->IsItemDynamicAt(model_index)) { @@ -101,7 +101,7 @@ void NativeMenuWin::UpdateStates() { //////////////////////////////////////////////////////////////////////////////// // NativeMenuWin, private: -bool NativeMenuWin::IsSeparatorItemAt(int menu_index) const { +bool NativeMenuWin::IsSeparatorItemAt(size_t menu_index) const { MENUITEMINFO mii = {0}; mii.cbSize = sizeof(mii); mii.fMask = MIIM_FTYPE; @@ -109,7 +109,7 @@ bool NativeMenuWin::IsSeparatorItemAt(int menu_index) const { return !!(mii.fType & MF_SEPARATOR); } -void NativeMenuWin::AddMenuItemAt(int menu_index, int model_index) { +void NativeMenuWin::AddMenuItemAt(size_t menu_index, size_t model_index) { MENUITEMINFO mii = {0}; mii.cbSize = sizeof(mii); mii.fMask = MIIM_FTYPE | MIIM_ID | MIIM_DATA; @@ -131,29 +131,31 @@ void NativeMenuWin::AddMenuItemAt(int menu_index, int model_index) { } else { if (type == ui::MenuModel::TYPE_RADIO) mii.fType |= MFT_RADIOCHECK; - mii.wID = model_->GetCommandIdAt(model_index); + mii.wID = static_cast<UINT>(model_->GetCommandIdAt(model_index)); } item_data->native_menu_win = this; item_data->model_index = model_index; mii.dwItemData = reinterpret_cast<ULONG_PTR>(item_data.get()); - items_.insert(items_.begin() + model_index, std::move(item_data)); + items_.insert(items_.begin() + static_cast<ptrdiff_t>(model_index), + std::move(item_data)); UpdateMenuItemInfoForString(&mii, model_index, model_->GetLabelAt(model_index)); InsertMenuItem(menu_, menu_index, TRUE, &mii); } -void NativeMenuWin::AddSeparatorItemAt(int menu_index, int model_index) { +void NativeMenuWin::AddSeparatorItemAt(size_t menu_index, size_t model_index) { MENUITEMINFO mii = {0}; mii.cbSize = sizeof(mii); mii.fMask = MIIM_FTYPE; mii.fType = MFT_SEPARATOR; // Insert a dummy entry into our label list so we can index directly into it // using item indices if need be. - items_.insert(items_.begin() + model_index, std::make_unique<ItemData>()); - InsertMenuItem(menu_, menu_index, TRUE, &mii); + items_.insert(items_.begin() + static_cast<ptrdiff_t>(model_index), + std::make_unique<ItemData>()); + InsertMenuItem(menu_, static_cast<UINT>(menu_index), TRUE, &mii); } -void NativeMenuWin::SetMenuItemState(int menu_index, +void NativeMenuWin::SetMenuItemState(size_t menu_index, bool enabled, bool checked, bool is_default) { @@ -170,11 +172,11 @@ void NativeMenuWin::SetMenuItemState(int menu_index, mii.cbSize = sizeof(mii); mii.fMask = MIIM_STATE; mii.fState = state; - SetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii); + SetMenuItemInfo(menu_, static_cast<UINT>(menu_index), MF_BYPOSITION, &mii); } -void NativeMenuWin::SetMenuItemLabel(int menu_index, - int model_index, +void NativeMenuWin::SetMenuItemLabel(size_t menu_index, + size_t model_index, const std::u16string& label) { if (IsSeparatorItemAt(menu_index)) return; @@ -182,11 +184,11 @@ void NativeMenuWin::SetMenuItemLabel(int menu_index, MENUITEMINFO mii = {0}; mii.cbSize = sizeof(mii); UpdateMenuItemInfoForString(&mii, model_index, label); - SetMenuItemInfo(menu_, menu_index, MF_BYPOSITION, &mii); + SetMenuItemInfo(menu_, static_cast<UINT>(menu_index), MF_BYPOSITION, &mii); } void NativeMenuWin::UpdateMenuItemInfoForString(MENUITEMINFO* mii, - int model_index, + size_t model_index, const std::u16string& label) { std::u16string formatted = label; ui::MenuModel::ItemType type = model_->GetTypeAt(model_index); diff --git a/chromium/ui/views/controls/menu/native_menu_win.h b/chromium/ui/views/controls/menu/native_menu_win.h index c4c8341d3ff..89f78b8f3a6 100644 --- a/chromium/ui/views/controls/menu/native_menu_win.h +++ b/chromium/ui/views/controls/menu/native_menu_win.h @@ -51,21 +51,21 @@ class VIEWS_EXPORT NativeMenuWin { // code in the functions in this class. // Returns true if the item at the specified index is a separator. - bool IsSeparatorItemAt(int menu_index) const; + bool IsSeparatorItemAt(size_t menu_index) const; // Add items. See note above about indices. - void AddMenuItemAt(int menu_index, int model_index); - void AddSeparatorItemAt(int menu_index, int model_index); + void AddMenuItemAt(size_t menu_index, size_t model_index); + void AddSeparatorItemAt(size_t menu_index, size_t model_index); // Sets the state of the item at the specified index. - void SetMenuItemState(int menu_index, + void SetMenuItemState(size_t menu_index, bool enabled, bool checked, bool is_default); // Sets the label of the item at the specified index. - void SetMenuItemLabel(int menu_index, - int model_index, + void SetMenuItemLabel(size_t menu_index, + size_t model_index, const std::u16string& label); // Updates the local data structure with the correctly formatted version of @@ -73,7 +73,7 @@ class VIEWS_EXPORT NativeMenuWin { // the menu is not owner-draw. That's a mouthful. This function exists because // of the peculiarities of the Windows menu API. void UpdateMenuItemInfoForString(MENUITEMINFO* mii, - int model_index, + size_t model_index, const std::u16string& label); // Resets the native menu stored in |menu_| by destroying any old menu then @@ -99,7 +99,7 @@ class VIEWS_EXPORT NativeMenuWin { HWND system_menu_for_; // The index of the first item in the model in the menu. - int first_item_index_; + size_t first_item_index_; // If we're a submenu, this is our parent. raw_ptr<NativeMenuWin> parent_; diff --git a/chromium/ui/views/controls/menu/submenu_view.cc b/chromium/ui/views/controls/menu/submenu_view.cc index 680a66049fa..ccff3c17e0a 100644 --- a/chromium/ui/views/controls/menu/submenu_view.cc +++ b/chromium/ui/views/controls/menu/submenu_view.cc @@ -87,10 +87,9 @@ SubmenuView::MenuItems SubmenuView::GetMenuItems() const { return menu_items; } -MenuItemView* SubmenuView::GetMenuItemAt(int index) { +MenuItemView* SubmenuView::GetMenuItemAt(size_t index) { const MenuItems menu_items = GetMenuItems(); - DCHECK_GE(index, 0); - DCHECK_LT(static_cast<size_t>(index), menu_items.size()); + DCHECK_LT(index, menu_items.size()); return menu_items[index]; } @@ -363,24 +362,26 @@ void SubmenuView::OnGestureEvent(ui::GestureEvent* event) { event->SetHandled(); } -int SubmenuView::GetRowCount() { - return static_cast<int>(GetMenuItems().size()); +size_t SubmenuView::GetRowCount() { + return GetMenuItems().size(); } -int SubmenuView::GetSelectedRow() { +absl::optional<size_t> SubmenuView::GetSelectedRow() { const auto menu_items = GetMenuItems(); const auto i = std::find_if(menu_items.cbegin(), menu_items.cend(), [](const MenuItemView* item) { return item->IsSelected(); }); - return (i == menu_items.cend()) ? -1 : std::distance(menu_items.cbegin(), i); + return (i == menu_items.cend()) ? absl::nullopt + : absl::make_optional(static_cast<size_t>( + std::distance(menu_items.cbegin(), i))); } -void SubmenuView::SetSelectedRow(int row) { +void SubmenuView::SetSelectedRow(absl::optional<size_t> row) { parent_menu_item_->GetMenuController()->SetSelection( - GetMenuItemAt(row), MenuController::SELECTION_DEFAULT); + GetMenuItemAt(row.value()), MenuController::SELECTION_DEFAULT); } -std::u16string SubmenuView::GetTextForRow(int row) { +std::u16string SubmenuView::GetTextForRow(size_t row) { return MenuItemView::GetAccessibleNameForMenuItem( GetMenuItemAt(row)->title(), std::u16string(), GetMenuItemAt(row)->ShouldShowNewBadge()); diff --git a/chromium/ui/views/controls/menu/submenu_view.h b/chromium/ui/views/controls/menu/submenu_view.h index cfbf5771ae8..2ee2a6ccbf2 100644 --- a/chromium/ui/views/controls/menu/submenu_view.h +++ b/chromium/ui/views/controls/menu/submenu_view.h @@ -72,7 +72,7 @@ class VIEWS_EXPORT SubmenuView : public View, MenuItems GetMenuItems() const; // Returns the MenuItemView at the specified index. - MenuItemView* GetMenuItemAt(int index); + MenuItemView* GetMenuItemAt(size_t index); PrefixSelector* GetPrefixSelector(); @@ -106,10 +106,10 @@ class VIEWS_EXPORT SubmenuView : public View, void OnGestureEvent(ui::GestureEvent* event) override; // Overridden from PrefixDelegate. - int GetRowCount() override; - int GetSelectedRow() override; - void SetSelectedRow(int row) override; - std::u16string GetTextForRow(int row) override; + size_t GetRowCount() override; + absl::optional<size_t> GetSelectedRow() override; + void SetSelectedRow(absl::optional<size_t> row) override; + std::u16string GetTextForRow(size_t row) override; // Returns true if the menu is showing. virtual bool IsShowing() const; diff --git a/chromium/ui/views/controls/native/native_view_host_mac.h b/chromium/ui/views/controls/native/native_view_host_mac.h index 5ec9cfc0d81..32a529b53b5 100644 --- a/chromium/ui/views/controls/native/native_view_host_mac.h +++ b/chromium/ui/views/controls/native/native_view_host_mac.h @@ -8,6 +8,7 @@ #include <memory> #include "base/mac/scoped_nsobject.h" +#include "base/memory/raw_ptr.h" #include "ui/base/cocoa/views_hostable.h" #include "ui/views/controls/native/native_view_host_wrapper.h" #include "ui/views/views_export.h" @@ -71,7 +72,7 @@ class NativeViewHostMac : public NativeViewHostWrapper, NativeWidgetMacNSWindowHost* GetNSWindowHost() const; // Our associated NativeViewHost. Owns this. - NativeViewHost* host_; + raw_ptr<NativeViewHost> host_; // Retain the native view as it may be destroyed at an unpredictable time. base::scoped_nsobject<NSView> native_view_; @@ -79,7 +80,7 @@ class NativeViewHostMac : public NativeViewHostWrapper, // If |native_view| supports the ViewsHostable protocol, then this is the // the corresponding ViewsHostableView interface (which is implemeted only // by WebContents and tests). - ui::ViewsHostableView* native_view_hostable_ = nullptr; + raw_ptr<ui::ViewsHostableView> native_view_hostable_ = nullptr; }; } // namespace views diff --git a/chromium/ui/views/controls/prefix_delegate.h b/chromium/ui/views/controls/prefix_delegate.h index dc37a068b7c..5995386d410 100644 --- a/chromium/ui/views/controls/prefix_delegate.h +++ b/chromium/ui/views/controls/prefix_delegate.h @@ -5,6 +5,7 @@ #ifndef UI_VIEWS_CONTROLS_PREFIX_DELEGATE_H_ #define UI_VIEWS_CONTROLS_PREFIX_DELEGATE_H_ +#include "third_party/abseil-cpp/absl/types/optional.h" #include "ui/views/view.h" #include "ui/views/views_export.h" @@ -14,17 +15,17 @@ namespace views { class VIEWS_EXPORT PrefixDelegate { public: // Returns the total number of selectable items. - virtual int GetRowCount() = 0; + virtual size_t GetRowCount() = 0; // Returns the row of the currently selected item, or -1 if no item is // selected. - virtual int GetSelectedRow() = 0; + virtual absl::optional<size_t> GetSelectedRow() = 0; // Sets the selection to the specified row. - virtual void SetSelectedRow(int row) = 0; + virtual void SetSelectedRow(absl::optional<size_t> row) = 0; // Returns the item at the specified row. - virtual std::u16string GetTextForRow(int row) = 0; + virtual std::u16string GetTextForRow(size_t row) = 0; }; } // namespace views diff --git a/chromium/ui/views/controls/prefix_selector.cc b/chromium/ui/views/controls/prefix_selector.cc index 67c49f30b95..a65c9e24918 100644 --- a/chromium/ui/views/controls/prefix_selector.cc +++ b/chromium/ui/views/controls/prefix_selector.cc @@ -5,6 +5,7 @@ #include "ui/views/controls/prefix_selector.h" #include <algorithm> +#include <limits> #include "base/i18n/case_conversion.h" #include "base/time/default_tick_clock.h" @@ -43,8 +44,8 @@ bool PrefixSelector::ShouldContinueSelection() const { void PrefixSelector::SetCompositionText( const ui::CompositionText& composition) {} -uint32_t PrefixSelector::ConfirmCompositionText(bool keep_selection) { - return UINT32_MAX; +size_t PrefixSelector::ConfirmCompositionText(bool keep_selection) { + return std::numeric_limits<size_t>::max(); } void PrefixSelector::ClearCompositionText() {} @@ -92,7 +93,7 @@ gfx::Rect PrefixSelector::GetSelectionBoundingBox() const { return gfx::Rect(); } -bool PrefixSelector::GetCompositionCharacterBounds(uint32_t index, +bool PrefixSelector::GetCompositionCharacterBounds(size_t index, gfx::Rect* rect) const { // TextInputClient::GetCompositionCharacterBounds is expected to fill |rect| // in screen coordinates and GetCaretBounds returns screen coordinates. @@ -129,9 +130,11 @@ bool PrefixSelector::SetEditableSelectionRange(const gfx::Range& range) { return false; } +#if BUILDFLAG(IS_MAC) bool PrefixSelector::DeleteRange(const gfx::Range& range) { return false; } +#endif bool PrefixSelector::GetTextFromRange(const gfx::Range& range, std::u16string* text) const { @@ -220,7 +223,7 @@ void PrefixSelector::OnTextInput(const std::u16string& text) { (text[0] == L'\t' || text[0] == L'\r' || text[0] == L'\n')) return; - const int row_count = prefix_delegate_->GetRowCount(); + const size_t row_count = prefix_delegate_->GetRowCount(); if (row_count == 0) return; @@ -228,17 +231,17 @@ void PrefixSelector::OnTextInput(const std::u16string& text) { // append |text| to |current_text_| and search for that. If it has been a // while search after the current row, otherwise search starting from the // current row. - int row = std::max(0, prefix_delegate_->GetSelectedRow()); + size_t row = prefix_delegate_->GetSelectedRow().value_or(0); if (ShouldContinueSelection()) { current_text_ += text; } else { current_text_ = text; - if (prefix_delegate_->GetSelectedRow() >= 0) + if (prefix_delegate_->GetSelectedRow().has_value()) row = (row + 1) % row_count; } time_of_last_key_ = tick_clock_->NowTicks(); - const int start_row = row; + const size_t start_row = row; const std::u16string lower_text(base::i18n::ToLower(current_text_)); do { if (TextAtRowMatchesText(row, lower_text)) { @@ -249,7 +252,7 @@ void PrefixSelector::OnTextInput(const std::u16string& text) { } while (row != start_row); } -bool PrefixSelector::TextAtRowMatchesText(int row, +bool PrefixSelector::TextAtRowMatchesText(size_t row, const std::u16string& lower_text) { const std::u16string model_text( base::i18n::ToLower(prefix_delegate_->GetTextForRow(row))); diff --git a/chromium/ui/views/controls/prefix_selector.h b/chromium/ui/views/controls/prefix_selector.h index f132b73ea4c..ce19d4d8d1c 100644 --- a/chromium/ui/views/controls/prefix_selector.h +++ b/chromium/ui/views/controls/prefix_selector.h @@ -47,7 +47,7 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient { // ui::TextInputClient: void SetCompositionText(const ui::CompositionText& composition) override; - uint32_t ConfirmCompositionText(bool keep_selection) override; + size_t ConfirmCompositionText(bool keep_selection) override; void ClearCompositionText() override; void InsertText(const std::u16string& text, InsertTextCursorBehavior cursor_behavior) override; @@ -59,7 +59,7 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient { bool CanComposeInline() const override; gfx::Rect GetCaretBounds() const override; gfx::Rect GetSelectionBoundingBox() const override; - bool GetCompositionCharacterBounds(uint32_t index, + bool GetCompositionCharacterBounds(size_t index, gfx::Rect* rect) const override; bool HasCompositionText() const override; FocusReason GetFocusReason() const override; @@ -67,7 +67,9 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient { bool GetCompositionTextRange(gfx::Range* range) const override; bool GetEditableSelectionRange(gfx::Range* range) const override; bool SetEditableSelectionRange(const gfx::Range& range) override; +#if BUILDFLAG(IS_MAC) bool DeleteRange(const gfx::Range& range) override; +#endif bool GetTextFromRange(const gfx::Range& range, std::u16string* text) const override; void OnInputMethodChanged() override; @@ -115,7 +117,7 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient { void OnTextInput(const std::u16string& text); // Returns true if the text of the node at |row| starts with |lower_text|. - bool TextAtRowMatchesText(int row, const std::u16string& lower_text); + bool TextAtRowMatchesText(size_t row, const std::u16string& lower_text); // Clears |current_text_| and resets |time_of_last_key_|. void ClearText(); diff --git a/chromium/ui/views/controls/prefix_selector_unittest.cc b/chromium/ui/views/controls/prefix_selector_unittest.cc index 1bccca752b8..caa514bf5a1 100644 --- a/chromium/ui/views/controls/prefix_selector_unittest.cc +++ b/chromium/ui/views/controls/prefix_selector_unittest.cc @@ -30,17 +30,19 @@ class TestPrefixDelegate : public View, public PrefixDelegate { ~TestPrefixDelegate() override = default; - int GetRowCount() override { return static_cast<int>(rows_.size()); } + size_t GetRowCount() override { return rows_.size(); } - int GetSelectedRow() override { return selected_row_; } + absl::optional<size_t> GetSelectedRow() override { return selected_row_; } - void SetSelectedRow(int row) override { selected_row_ = row; } + void SetSelectedRow(absl::optional<size_t> row) override { + selected_row_ = row; + } - std::u16string GetTextForRow(int row) override { return rows_[row]; } + std::u16string GetTextForRow(size_t row) override { return rows_[row]; } private: std::vector<std::u16string> rows_; - int selected_row_ = 0; + absl::optional<size_t> selected_row_ = 0; }; class PrefixSelectorTest : public ViewsTestBase { @@ -67,20 +69,20 @@ TEST_F(PrefixSelectorTest, PrefixSelect) { selector_->InsertText( u"an", ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText); - EXPECT_EQ(1, delegate_.GetSelectedRow()); + EXPECT_EQ(1u, delegate_.GetSelectedRow()); // Invoke OnViewBlur() to reset time. selector_->OnViewBlur(); selector_->InsertText( u"a", ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText); - EXPECT_EQ(0, delegate_.GetSelectedRow()); + EXPECT_EQ(0u, delegate_.GetSelectedRow()); selector_->OnViewBlur(); selector_->InsertText( u"g", ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText); - EXPECT_EQ(3, delegate_.GetSelectedRow()); + EXPECT_EQ(3u, delegate_.GetSelectedRow()); selector_->OnViewBlur(); selector_->InsertText( @@ -89,7 +91,7 @@ TEST_F(PrefixSelectorTest, PrefixSelect) { selector_->InsertText( u"a", ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText); - EXPECT_EQ(2, delegate_.GetSelectedRow()); + EXPECT_EQ(2u, delegate_.GetSelectedRow()); selector_->OnViewBlur(); selector_->InsertText( @@ -101,7 +103,7 @@ TEST_F(PrefixSelectorTest, PrefixSelect) { selector_->InsertText( u"a", ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText); - EXPECT_EQ(2, delegate_.GetSelectedRow()); + EXPECT_EQ(2u, delegate_.GetSelectedRow()); } } // namespace views diff --git a/chromium/ui/views/controls/progress_bar.cc b/chromium/ui/views/controls/progress_bar.cc index ff59dd87df6..56c9fd01a74 100644 --- a/chromium/ui/views/controls/progress_bar.cc +++ b/chromium/ui/views/controls/progress_bar.cc @@ -147,11 +147,20 @@ void ProgressBar::SetValue(double value) { MaybeNotifyAccessibilityValueChanged(); } +void ProgressBar::SetPaused(bool is_paused) { + if (is_paused_ == is_paused) + return; + + is_paused_ = is_paused; + OnPropertyChanged(&is_paused_, kPropertyEffectsPaint); +} + SkColor ProgressBar::GetForegroundColor() const { if (foreground_color_) return foreground_color_.value(); - return GetColorProvider()->GetColor(ui::kColorProgressBar); + return GetColorProvider()->GetColor(GetPaused() ? ui::kColorProgressBarPaused + : ui::kColorProgressBar); } void ProgressBar::SetForegroundColor(SkColor color) { @@ -266,6 +275,7 @@ void ProgressBar::MaybeNotifyAccessibilityValueChanged() { BEGIN_METADATA(ProgressBar, View) ADD_PROPERTY_METADATA(SkColor, ForegroundColor, ui::metadata::SkColorConverter) ADD_PROPERTY_METADATA(SkColor, BackgroundColor, ui::metadata::SkColorConverter) +ADD_PROPERTY_METADATA(bool, Paused) END_METADATA } // namespace views diff --git a/chromium/ui/views/controls/progress_bar.h b/chromium/ui/views/controls/progress_bar.h index 05b5b708858..2c95b733bf7 100644 --- a/chromium/ui/views/controls/progress_bar.h +++ b/chromium/ui/views/controls/progress_bar.h @@ -44,6 +44,9 @@ class VIEWS_EXPORT ProgressBar : public View, public gfx::AnimationDelegate { // be displayed with an infinite loading animation. void SetValue(double value); + // Sets whether the progress bar is paused. + void SetPaused(bool is_paused); + // The color of the progress portion. SkColor GetForegroundColor() const; void SetForegroundColor(SkColor color); @@ -61,6 +64,7 @@ class VIEWS_EXPORT ProgressBar : public View, public gfx::AnimationDelegate { void AnimationEnded(const gfx::Animation* animation) override; bool IsIndeterminate(); + bool GetPaused() const { return is_paused_; } void OnPaintIndeterminate(gfx::Canvas* canvas); // Fire an accessibility event if visible and the progress has changed. @@ -69,6 +73,9 @@ class VIEWS_EXPORT ProgressBar : public View, public gfx::AnimationDelegate { // Current progress to display, should be in the range 0.0 to 1.0. double current_value_ = 0.0; + // Is the progress bar paused. + bool is_paused_ = false; + // In DP, the preferred height of this progress bar. const int preferred_height_; diff --git a/chromium/ui/views/controls/scroll_view.cc b/chromium/ui/views/controls/scroll_view.cc index e594c891e0a..e9fc96a1262 100644 --- a/chromium/ui/views/controls/scroll_view.cc +++ b/chromium/ui/views/controls/scroll_view.cc @@ -904,6 +904,7 @@ void ScrollView::GetAccessibleNodeData(ui::AXNodeData* node_data) { if (!contents_) return; + node_data->role = ax::mojom::Role::kScrollView; node_data->AddIntAttribute(ax::mojom::IntAttribute::kScrollX, CurrentOffset().x()); node_data->AddIntAttribute(ax::mojom::IntAttribute::kScrollXMin, diff --git a/chromium/ui/views/controls/scroll_view_unittest.cc b/chromium/ui/views/controls/scroll_view_unittest.cc index 47334b0d0c1..dd0d3386efb 100644 --- a/chromium/ui/views/controls/scroll_view_unittest.cc +++ b/chromium/ui/views/controls/scroll_view_unittest.cc @@ -113,7 +113,9 @@ class ObserveViewDeletion : public ViewObserver { private: base::ScopedObservation<View, ViewObserver> observer_{this}; - raw_ptr<View> deleted_view_ = nullptr; + // TODO(crbug.com/1298696): views_unittests breaks with MTECheckedPtr + // enabled. Triage. + raw_ptr<View, DegradeToNoOpWhenMTE> deleted_view_ = nullptr; }; } // namespace test @@ -343,7 +345,7 @@ class WidgetScrollViewTest : public test::WidgetTest, ScrollView* scroll_view = widget_->SetContentsView(std::make_unique<ScrollView>()); scroll_view->SetContents(std::move(contents)); - scroll_view->Layout(); + RunScheduledLayout(scroll_view); widget_->GetCompositor()->AddObserver(this); @@ -486,7 +488,7 @@ std::string UiConfigToString(const testing::TestParamInfo<UiConfig>& info) { // Verifies the viewport is sized to fit the available space. TEST_F(ScrollViewTest, ViewportSizedToFit) { View* contents = InstallContents(); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ("0,0 100x100", contents->parent()->bounds().ToString()); } @@ -496,7 +498,7 @@ TEST_F(ScrollViewTest, BoundedViewportSizedToFit) { View* contents = InstallContents(); scroll_view_->ClipHeightTo(100, 200); scroll_view_->SetBorder(CreateSolidBorder(2, 0)); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ("2,2 96x96", contents->parent()->bounds().ToString()); // Make sure the width of |contents| is set properly not to overflow the @@ -510,7 +512,7 @@ TEST_F(ScrollViewTest, VerticalScrollbarDoesNotAppearUnnecessarily) { const gfx::Rect default_outer_bounds(0, 0, 100, 100); scroll_view_->SetContents(std::make_unique<VerticalResizingView>()); scroll_view_->SetBoundsRect(default_outer_bounds); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_FALSE(scroll_view_->vertical_scroll_bar()->GetVisible()); EXPECT_TRUE(scroll_view_->horizontal_scroll_bar()->GetVisible()); } @@ -522,7 +524,7 @@ TEST_F(ScrollViewTest, HorizontalScrollbarDoesNotAppearIfHidden) { ScrollView::ScrollBarMode::kHiddenButEnabled); scroll_view_->SetContents(std::make_unique<VerticalResizingView>()); scroll_view_->SetBoundsRect(default_outer_bounds); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_FALSE(scroll_view_->vertical_scroll_bar()->GetVisible()); EXPECT_FALSE(scroll_view_->horizontal_scroll_bar()->GetVisible()); } @@ -534,7 +536,7 @@ TEST_F(ScrollViewTest, VerticalScrollbarDoesNotAppearIfHidden) { ScrollView::ScrollBarMode::kHiddenButEnabled); scroll_view_->SetContents(std::make_unique<HorizontalResizingView>()); scroll_view_->SetBoundsRect(default_outer_bounds); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_FALSE(scroll_view_->vertical_scroll_bar()->GetVisible()); EXPECT_FALSE(scroll_view_->horizontal_scroll_bar()->GetVisible()); } @@ -546,7 +548,7 @@ TEST_F(ScrollViewTest, HorizontalScrollbarDoesNotAppearIfDisabled) { ScrollView::ScrollBarMode::kDisabled); scroll_view_->SetContents(std::make_unique<VerticalResizingView>()); scroll_view_->SetBoundsRect(default_outer_bounds); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_FALSE(scroll_view_->vertical_scroll_bar()->GetVisible()); EXPECT_FALSE(scroll_view_->horizontal_scroll_bar()->GetVisible()); } @@ -557,7 +559,7 @@ TEST_F(ScrollViewTest, VerticallScrollbarDoesNotAppearIfDisabled) { scroll_view_->SetVerticalScrollBarMode(ScrollView::ScrollBarMode::kDisabled); scroll_view_->SetContents(std::make_unique<HorizontalResizingView>()); scroll_view_->SetBoundsRect(default_outer_bounds); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_FALSE(scroll_view_->vertical_scroll_bar()->GetVisible()); EXPECT_FALSE(scroll_view_->horizontal_scroll_bar()->GetVisible()); } @@ -569,7 +571,7 @@ TEST_F(ScrollViewTest, ScrollBars) { // Size the contents such that vertical scrollbar is needed. contents->SetBounds(0, 0, 50, 400); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutWidth(), contents->parent()->width()); EXPECT_EQ(100, contents->parent()->height()); @@ -582,7 +584,7 @@ TEST_F(ScrollViewTest, ScrollBars) { // Size the contents such that horizontal scrollbar is needed. contents->SetBounds(0, 0, 400, 50); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(100, contents->parent()->width()); EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutHeight(), contents->parent()->height()); @@ -591,7 +593,7 @@ TEST_F(ScrollViewTest, ScrollBars) { // Both horizontal and vertical. contents->SetBounds(0, 0, 300, 400); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutWidth(), contents->parent()->width()); EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutHeight(), @@ -607,7 +609,7 @@ TEST_F(ScrollViewTest, ScrollBars) { scroll_view_->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR( kTopPadding, kLeftPadding, kBottomPadding, kRightPadding))); contents->SetBounds(0, 0, 50, 400); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutWidth() - kLeftPadding - kRightPadding, contents->parent()->width()); @@ -624,7 +626,7 @@ TEST_F(ScrollViewTest, ScrollBars) { // Horizontal with border. contents->SetBounds(0, 0, 400, 50); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(100 - kLeftPadding - kRightPadding, contents->parent()->width()); EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutHeight() - kTopPadding - kBottomPadding, @@ -641,7 +643,7 @@ TEST_F(ScrollViewTest, ScrollBars) { // Both horizontal and vertical with border. contents->SetBounds(0, 0, 300, 400); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutWidth() - kLeftPadding - kRightPadding, contents->parent()->width()); @@ -722,7 +724,7 @@ TEST_F(ScrollViewTest, ScrollBarsWithHeader) { // Size the contents such that vertical scrollbar is needed. contents->SetBounds(0, 0, 50, 400); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(0, contents->parent()->x()); EXPECT_EQ(20, contents->parent()->y()); EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutWidth(), @@ -748,7 +750,7 @@ TEST_F(ScrollViewTest, ScrollBarsWithHeader) { // Size the contents such that horizontal scrollbar is needed. contents->SetBounds(0, 0, 400, 50); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(0, contents->parent()->x()); EXPECT_EQ(20, contents->parent()->y()); EXPECT_EQ(100, contents->parent()->width()); @@ -765,7 +767,7 @@ TEST_F(ScrollViewTest, ScrollBarsWithHeader) { // Both horizontal and vertical. contents->SetBounds(0, 0, 300, 400); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(0, contents->parent()->x()); EXPECT_EQ(20, contents->parent()->y()); EXPECT_EQ(100 - scroll_view_->GetScrollBarLayoutWidth(), @@ -819,7 +821,7 @@ TEST_F(ScrollViewTest, ScrollToPositionUpdatesScrollBar) { // Scroll the horizontal scrollbar, after which, the scroll bar thumb position // should be updated (i.e. it should be non-zero). contents->SetBounds(0, 0, 400, 50); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); auto* scroll_bar = test_api.GetScrollBar(HORIZONTAL); ASSERT_TRUE(scroll_bar); EXPECT_TRUE(scroll_bar->GetVisible()); @@ -829,7 +831,7 @@ TEST_F(ScrollViewTest, ScrollToPositionUpdatesScrollBar) { // Scroll the vertical scrollbar. contents->SetBounds(0, 0, 50, 400); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); scroll_bar = test_api.GetScrollBar(VERTICAL); ASSERT_TRUE(scroll_bar); EXPECT_TRUE(scroll_bar->GetVisible()); @@ -847,7 +849,7 @@ TEST_F(ScrollViewTest, ScrollToPositionUpdatesWithHiddenHorizontalScrollBar) { View* contents = InstallContents(); contents->SetBounds(0, 0, 400, 50); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); auto* scroll_bar = test_api.GetScrollBar(HORIZONTAL); ASSERT_TRUE(scroll_bar); EXPECT_FALSE(scroll_bar->GetVisible()); @@ -867,7 +869,7 @@ TEST_F(ScrollViewTest, ScrollToPositionUpdatesWithHiddenVerticalScrollBar) { View* contents = InstallContents(); contents->SetBounds(0, 0, 50, 400); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); auto* scroll_bar = test_api.GetScrollBar(VERTICAL); ASSERT_TRUE(scroll_bar); EXPECT_FALSE(scroll_bar->GetVisible()); @@ -886,7 +888,7 @@ TEST_F(ScrollViewTest, ScrollRectToVisible) { auto* contents_ptr = scroll_view_->SetContents(std::move(contents)); scroll_view_->SetBoundsRect(gfx::Rect(0, 0, 100, 100)); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(gfx::Vector2d(0, 0), test_api.IntegralViewOffset()); // Scroll to y=405 height=10, this should make the y position of the content @@ -916,7 +918,7 @@ TEST_F(ScrollViewTest, ScrollRectToVisibleWithHiddenHorizontalScrollbar) { auto* contents_ptr = scroll_view_->SetContents(std::move(contents)); scroll_view_->SetBoundsRect(gfx::Rect(0, 0, 100, 100)); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(gfx::Vector2d(0, 0), test_api.IntegralViewOffset()); // Scroll to x=305 width=10, this should make the x position of the content @@ -946,7 +948,7 @@ TEST_F(ScrollViewTest, ScrollRectToVisibleWithHiddenVerticalScrollbar) { auto* contents_ptr = scroll_view_->SetContents(std::move(contents)); scroll_view_->SetBoundsRect(gfx::Rect(0, 0, 100, 100)); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(gfx::Vector2d(0, 0), test_api.IntegralViewOffset()); // Scroll to y=305 height=10, this should make the y position of the content @@ -977,7 +979,7 @@ TEST_F(ScrollViewTest, ScrollChildToVisibleOnFocus) { auto* child_ptr = contents_ptr->AddChildView(std::move(child)); scroll_view_->SetBoundsRect(gfx::Rect(0, 0, 100, 100)); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(gfx::Vector2d(), test_api.IntegralViewOffset()); // Set focus to the child control. This should cause the control to scroll to @@ -1012,11 +1014,11 @@ TEST_F(ScrollViewTest, ScrollViewToVisibleOnContentsRootFocus) { inner_scroll_view_ptr->SetContents(std::move(inner_contents)); inner_scroll_view_ptr->SetBoundsRect(gfx::Rect(0, 510, 100, 100)); - inner_scroll_view_ptr->Layout(); + RunScheduledLayout(inner_scroll_view_ptr); EXPECT_EQ(gfx::Vector2d(), inner_test_api.IntegralViewOffset()); scroll_view_->SetBoundsRect(gfx::Rect(0, 0, 200, 200)); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(gfx::Vector2d(), outer_test_api.IntegralViewOffset()); // Scroll the inner scroll view to y=405 height=10. This should make the y @@ -1056,7 +1058,7 @@ TEST_F(ScrollViewTest, ClipHeightToNormalContentHeight) { scroll_view_->GetPreferredSize()); scroll_view_->SizeToPreferredSize(); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(gfx::Size(kWidth, kNormalContentHeight), scroll_view_->contents()->size()); @@ -1076,7 +1078,7 @@ TEST_F(ScrollViewTest, ClipHeightToShortContentHeight) { EXPECT_EQ(gfx::Size(kWidth, kMinHeight), scroll_view_->GetPreferredSize()); scroll_view_->SizeToPreferredSize(); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); // Layered scrolling requires the contents to fill the viewport. if (contents->layer()) { @@ -1100,7 +1102,7 @@ TEST_F(ScrollViewTest, ClipHeightToTallContentHeight) { EXPECT_EQ(gfx::Size(kWidth, kMaxHeight), scroll_view_->GetPreferredSize()); scroll_view_->SizeToPreferredSize(); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); // The width may be less than kWidth if the scroll bar takes up some width. EXPECT_GE(kWidth, scroll_view_->contents()->width()); @@ -1123,7 +1125,7 @@ TEST_F(ScrollViewTest, ClipHeightToScrollbarUsesWidth) { gfx::Size new_size(kWidth, scroll_view_->GetHeightForWidth(kWidth)); scroll_view_->SetSize(new_size); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); int expected_width = kWidth - scroll_view_->GetScrollBarLayoutWidth(); EXPECT_EQ(scroll_view_->contents()->size().width(), expected_width); @@ -1155,7 +1157,7 @@ TEST_F(ScrollViewTest, CornerViewVisibility) { View* corner_view = ScrollViewTestApi(scroll_view_.get()).corner_view(); contents->SetBounds(0, 0, 200, 200); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); // Corner view should not exist if using overlay scrollbars. if (scroll_view_->vertical_scroll_bar()->OverlapsContent()) { @@ -1175,22 +1177,22 @@ TEST_F(ScrollViewTest, CornerViewVisibility) { // Corner view should be removed when only the vertical scrollbar is visible. contents->SetBounds(0, 0, 50, 200); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_FALSE(corner_view->parent()); // ... or when only the horizontal scrollbar is visible. contents->SetBounds(0, 0, 200, 50); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_FALSE(corner_view->parent()); // ... or when no scrollbar is visible. contents->SetBounds(0, 0, 50, 50); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_FALSE(corner_view->parent()); // Corner view should reappear when both scrollbars reappear. contents->SetBounds(0, 0, 200, 200); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(scroll_view_.get(), corner_view->parent()); EXPECT_TRUE(corner_view->GetVisible()); } @@ -1392,7 +1394,7 @@ TEST_F(ScrollViewTest, CocoaOverlayScrollBars) { // Size the contents such that vertical scrollbar is needed. // Since it is overlaid, the ViewPort size should match the ScrollView. contents->SetBounds(0, 0, 50, 400); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(100, contents->parent()->width()); EXPECT_EQ(100, contents->parent()->height()); EXPECT_EQ(0, scroll_view_->GetScrollBarLayoutWidth()); @@ -1401,7 +1403,7 @@ TEST_F(ScrollViewTest, CocoaOverlayScrollBars) { // Size the contents such that horizontal scrollbar is needed. contents->SetBounds(0, 0, 400, 50); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(100, contents->parent()->width()); EXPECT_EQ(100, contents->parent()->height()); EXPECT_EQ(0, scroll_view_->GetScrollBarLayoutHeight()); @@ -1410,7 +1412,7 @@ TEST_F(ScrollViewTest, CocoaOverlayScrollBars) { // Both horizontal and vertical scrollbars. contents->SetBounds(0, 0, 300, 400); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(100, contents->parent()->width()); EXPECT_EQ(100, contents->parent()->height()); EXPECT_EQ(0, scroll_view_->GetScrollBarLayoutWidth()); @@ -1428,7 +1430,7 @@ TEST_F(ScrollViewTest, CocoaOverlayScrollBars) { // to be smaller, and ScrollbarWidth and ScrollbarHeight are non-zero. SetOverlayScrollersEnabled(false); EXPECT_TRUE(ViewTestApi(scroll_view_.get()).needs_layout()); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(100 - VerticalScrollBarWidth(), contents->parent()->width()); EXPECT_EQ(100 - HorizontalScrollBarHeight(), contents->parent()->height()); EXPECT_NE(0, VerticalScrollBarWidth()); @@ -1557,7 +1559,7 @@ TEST_F(ScrollViewTest, ConstrainScrollToBounds) { View* contents = InstallContents(); contents->SetBoundsRect(gfx::Rect(0, 0, 300, 300)); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(gfx::PointF(), test_api.CurrentOffset()); @@ -1568,14 +1570,14 @@ TEST_F(ScrollViewTest, ConstrainScrollToBounds) { // Making the viewport 55 pixels taller should scroll up the same amount. scroll_view_->SetBoundsRect(gfx::Rect(0, 0, 100, 155)); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(fully_scrolled.y() - 55, test_api.CurrentOffset().y()); EXPECT_EQ(fully_scrolled.x(), test_api.CurrentOffset().x()); // And 77 pixels wider should scroll left. Also make it short again: the y- // offset from the last change should remain. scroll_view_->SetBoundsRect(gfx::Rect(0, 0, 177, 100)); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(fully_scrolled.y() - 55, test_api.CurrentOffset().y()); EXPECT_EQ(fully_scrolled.x() - 77, test_api.CurrentOffset().x()); } @@ -1597,19 +1599,19 @@ TEST_F(ScrollViewTest, ContentScrollNotResetOnLayout) { scroll_view_->ScrollToPosition(test_api.GetScrollBar(VERTICAL), 25); EXPECT_EQ(25, test_api.CurrentOffset().y()); // Call Layout; no change to scroll position. - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(25, test_api.CurrentOffset().y()); // Change contents of |contents|, call Layout; still no change to scroll // position. contents->SetPreferredSize(gfx::Size(300, 500)); contents->InvalidateLayout(); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(25, test_api.CurrentOffset().y()); // Change |contents| to be shorter than the ScrollView's clipped height. // This /will/ change the scroll location due to ConstrainScrollToBounds. contents->SetPreferredSize(gfx::Size(300, 50)); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ(0, test_api.CurrentOffset().y()); } @@ -2058,7 +2060,7 @@ TEST_F(ScrollViewTest, IgnoreOverlapWithDisabledHorizontalScroll) { View* contents = InstallContents(); contents->SetBoundsRect(gfx::Rect(0, 0, 300, 300)); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); gfx::Size expected_size = scroll_view_->size(); expected_size.Enlarge(-kThickness, 0); @@ -2084,7 +2086,7 @@ TEST_F(ScrollViewTest, IgnoreOverlapWithHiddenHorizontalScroll) { View* contents = InstallContents(); contents->SetBoundsRect(gfx::Rect(0, 0, 300, 300)); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); gfx::Size expected_size = scroll_view_->size(); expected_size.Enlarge(-kThickness, 0); @@ -2109,7 +2111,7 @@ TEST_F(ScrollViewTest, IgnoreOverlapWithDisabledVerticalScroll) { View* contents = InstallContents(); contents->SetBoundsRect(gfx::Rect(0, 0, 300, 300)); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); gfx::Size expected_size = scroll_view_->size(); expected_size.Enlarge(0, -kThickness); @@ -2135,7 +2137,7 @@ TEST_F(ScrollViewTest, IgnoreOverlapWithHiddenVerticalScroll) { View* contents = InstallContents(); contents->SetBoundsRect(gfx::Rect(0, 0, 300, 300)); - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); gfx::Size expected_size = scroll_view_->size(); expected_size.Enlarge(0, -kThickness); @@ -2147,7 +2149,7 @@ TEST_F(ScrollViewTest, TestSettingContentsToNull) { test::ObserveViewDeletion view_deletion{contents}; // Make sure the content is installed and working. - scroll_view_->Layout(); + RunScheduledLayout(scroll_view_.get()); EXPECT_EQ("0,0 100x100", contents->parent()->bounds().ToString()); // This should be legal and not DCHECK. diff --git a/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm b/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm index bab1c941dda..ac6c8ac1166 100644 --- a/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm +++ b/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/memory/raw_ptr.h" + #import "ui/views/controls/scrollbar/cocoa_scroll_bar.h" #include "base/bind.h" @@ -61,7 +63,7 @@ class CocoaScrollBarThumb : public BaseScrollBarThumb { private: // The CocoaScrollBar that owns us. - CocoaScrollBar* cocoa_scroll_bar_; // weak. + raw_ptr<CocoaScrollBar> cocoa_scroll_bar_; // weak. }; CocoaScrollBarThumb::CocoaScrollBarThumb(CocoaScrollBar* scroll_bar) diff --git a/chromium/ui/views/controls/separator.cc b/chromium/ui/views/controls/separator.cc index 4051d9ea810..c11ec64da75 100644 --- a/chromium/ui/views/controls/separator.cc +++ b/chromium/ui/views/controls/separator.cc @@ -20,16 +20,16 @@ Separator::Separator() = default; Separator::~Separator() = default; -SkColor Separator::GetColor() const { - return overridden_color_.value_or(0); +ui::ColorId Separator::GetColorId() const { + return color_id_; } -void Separator::SetColor(SkColor color) { - if (overridden_color_ == color) +void Separator::SetColorId(ui::ColorId color_id) { + if (color_id_ == color_id) return; - overridden_color_ = color; - OnPropertyChanged(&overridden_color_, kPropertyEffectsPaint); + color_id_ = color_id; + OnPropertyChanged(&color_id_, kPropertyEffectsPaint); } int Separator::GetPreferredLength() const { @@ -66,9 +66,7 @@ gfx::Size Separator::CalculatePreferredSize() const { } void Separator::OnPaint(gfx::Canvas* canvas) { - const SkColor color = overridden_color_ - ? *overridden_color_ - : GetColorProvider()->GetColor(ui::kColorSeparator); + const SkColor color = GetColorProvider()->GetColor(color_id_); // Paint background and border, if any. View::OnPaint(canvas); @@ -106,7 +104,7 @@ void Separator::OnPaint(gfx::Canvas* canvas) { } BEGIN_METADATA(Separator, View) -ADD_PROPERTY_METADATA(SkColor, Color, ui::metadata::SkColorConverter) +ADD_PROPERTY_METADATA(ui::ColorId, ColorId) ADD_PROPERTY_METADATA(int, PreferredLength) ADD_PROPERTY_METADATA(Separator::Orientation, Orientation) END_METADATA diff --git a/chromium/ui/views/controls/separator.h b/chromium/ui/views/controls/separator.h index 60f415ae5b5..bdc341d1884 100644 --- a/chromium/ui/views/controls/separator.h +++ b/chromium/ui/views/controls/separator.h @@ -6,6 +6,7 @@ #define UI_VIEWS_CONTROLS_SEPARATOR_H_ #include "third_party/abseil-cpp/absl/types/optional.h" +#include "ui/color/color_id.h" #include "ui/views/metadata/view_factory.h" #include "ui/views/view.h" @@ -30,8 +31,8 @@ class VIEWS_EXPORT Separator : public View { ~Separator() override; - SkColor GetColor() const; - void SetColor(SkColor color); + ui::ColorId GetColorId() const; + void SetColorId(ui::ColorId color_id); // Vertical or horizontal extension depending on the orientation. Set to // `kThickness` by default. @@ -47,12 +48,12 @@ class VIEWS_EXPORT Separator : public View { private: int preferred_length_ = kThickness; - absl::optional<SkColor> overridden_color_; + ui::ColorId color_id_ = ui::kColorSeparator; Orientation orientation_ = Orientation::kVertical; }; BEGIN_VIEW_BUILDER(VIEWS_EXPORT, Separator, View) -VIEW_BUILDER_PROPERTY(SkColor, Color) +VIEW_BUILDER_PROPERTY(ui::ColorId, ColorId) VIEW_BUILDER_PROPERTY(int, PreferredLength) VIEW_BUILDER_PROPERTY(Separator::Orientation, Orientation) END_VIEW_BUILDER diff --git a/chromium/ui/views/controls/separator_unittest.cc b/chromium/ui/views/controls/separator_unittest.cc index a9f608cfcf6..bf34bb52478 100644 --- a/chromium/ui/views/controls/separator_unittest.cc +++ b/chromium/ui/views/controls/separator_unittest.cc @@ -7,7 +7,9 @@ #include <memory> #include "base/memory/raw_ptr.h" +#include "ui/color/color_id.h" #include "ui/gfx/canvas.h" +#include "ui/gfx/color_palette.h" #include "ui/gfx/image/image_unittest_util.h" #include "ui/views/border.h" #include "ui/views/test/views_test_base.h" @@ -35,19 +37,23 @@ class SeparatorTest : public ViewsTestBase { std::unique_ptr<Widget> widget_; raw_ptr<Separator> separator_; + SkColor expected_foreground_color_ = gfx::kPlaceholderColor; + static const SkColor kBackgroundColor; - static const SkColor kForegroundColor; + static const ui::ColorId kForegroundColorId; static const gfx::Size kTestImageSize; }; const SkColor SeparatorTest::kBackgroundColor = SK_ColorRED; -const SkColor SeparatorTest::kForegroundColor = SK_ColorGRAY; +const ui::ColorId SeparatorTest::kForegroundColorId = ui::kColorSeparator; const gfx::Size SeparatorTest::kTestImageSize{24, 24}; void SeparatorTest::SetUp() { ViewsTestBase::SetUp(); widget_ = CreateTestWidget(); separator_ = widget_->SetContentsView(std::make_unique<Separator>()); + expected_foreground_color_ = + widget_->GetColorProvider()->GetColor(kForegroundColorId); } void SeparatorTest::TearDown() { @@ -102,201 +108,201 @@ TEST_F(SeparatorTest, ImageScaleBelowOne_HorizontalLine) { TEST_F(SeparatorTest, Paint_NoInsets_FillsCanvas_Scale100) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); SkBitmap painted = PaintToCanvas(1.0f); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 9)); - EXPECT_EQ(kForegroundColor, painted.getColor(9, 9)); - EXPECT_EQ(kForegroundColor, painted.getColor(9, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 9)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(9, 9)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(9, 0)); } TEST_F(SeparatorTest, Paint_NoInsets_FillsCanvas_Scale125) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); SkBitmap painted = PaintToCanvas(1.25f); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 12)); - EXPECT_EQ(kForegroundColor, painted.getColor(12, 12)); - EXPECT_EQ(kForegroundColor, painted.getColor(12, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 12)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(12, 12)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(12, 0)); } TEST_F(SeparatorTest, Paint_NoInsets_FillsCanvas_Scale150) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); SkBitmap painted = PaintToCanvas(1.5f); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 14)); - EXPECT_EQ(kForegroundColor, painted.getColor(14, 14)); - EXPECT_EQ(kForegroundColor, painted.getColor(14, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 14)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(14, 14)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(14, 0)); } TEST_F(SeparatorTest, Paint_TopInset_Scale100) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); separator_->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(1, 0, 0, 0))); SkBitmap painted = PaintToCanvas(1.0f); EXPECT_EQ(kBackgroundColor, painted.getColor(0, 0)); EXPECT_EQ(kBackgroundColor, painted.getColor(9, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 1)); - EXPECT_EQ(kForegroundColor, painted.getColor(9, 1)); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 9)); - EXPECT_EQ(kForegroundColor, painted.getColor(9, 9)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 1)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(9, 1)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 9)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(9, 9)); } TEST_F(SeparatorTest, Paint_TopInset_Scale125) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); separator_->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(1, 0, 0, 0))); SkBitmap painted = PaintToCanvas(1.25f); EXPECT_EQ(kBackgroundColor, painted.getColor(0, 1)); EXPECT_EQ(kBackgroundColor, painted.getColor(12, 1)); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 2)); - EXPECT_EQ(kForegroundColor, painted.getColor(12, 2)); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 12)); - EXPECT_EQ(kForegroundColor, painted.getColor(12, 12)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 2)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(12, 2)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 12)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(12, 12)); } TEST_F(SeparatorTest, Paint_LeftInset_Scale100) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); separator_->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(0, 1, 0, 0))); SkBitmap painted = PaintToCanvas(1.0f); EXPECT_EQ(kBackgroundColor, painted.getColor(0, 0)); EXPECT_EQ(kBackgroundColor, painted.getColor(0, 9)); - EXPECT_EQ(kForegroundColor, painted.getColor(1, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(1, 9)); - EXPECT_EQ(kForegroundColor, painted.getColor(9, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(9, 9)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(1, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(1, 9)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(9, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(9, 9)); } TEST_F(SeparatorTest, Paint_LeftInset_Scale125) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); separator_->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(0, 1, 0, 0))); SkBitmap painted = PaintToCanvas(1.25f); EXPECT_EQ(kBackgroundColor, painted.getColor(1, 0)); EXPECT_EQ(kBackgroundColor, painted.getColor(1, 12)); - EXPECT_EQ(kForegroundColor, painted.getColor(2, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(2, 12)); - EXPECT_EQ(kForegroundColor, painted.getColor(12, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(12, 12)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(2, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(2, 12)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(12, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(12, 12)); } TEST_F(SeparatorTest, Paint_BottomInset_Scale100) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); separator_->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(0, 0, 1, 0))); SkBitmap painted = PaintToCanvas(1.0f); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(9, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 8)); - EXPECT_EQ(kForegroundColor, painted.getColor(9, 8)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(9, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 8)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(9, 8)); EXPECT_EQ(kBackgroundColor, painted.getColor(0, 9)); EXPECT_EQ(kBackgroundColor, painted.getColor(9, 9)); } TEST_F(SeparatorTest, Paint_BottomInset_Scale125) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); separator_->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(0, 0, 1, 0))); SkBitmap painted = PaintToCanvas(1.25f); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(12, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 10)); - EXPECT_EQ(kForegroundColor, painted.getColor(12, 10)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(12, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 10)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(12, 10)); EXPECT_EQ(kBackgroundColor, painted.getColor(0, 11)); EXPECT_EQ(kBackgroundColor, painted.getColor(12, 11)); } TEST_F(SeparatorTest, Paint_RightInset_Scale100) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); separator_->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(0, 0, 0, 1))); SkBitmap painted = PaintToCanvas(1.0f); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 9)); - EXPECT_EQ(kForegroundColor, painted.getColor(8, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(8, 9)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 9)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(8, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(8, 9)); EXPECT_EQ(kBackgroundColor, painted.getColor(9, 0)); EXPECT_EQ(kBackgroundColor, painted.getColor(9, 9)); } TEST_F(SeparatorTest, Paint_RightInset_Scale125) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); separator_->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(0, 0, 0, 1))); SkBitmap painted = PaintToCanvas(1.25f); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 12)); - EXPECT_EQ(kForegroundColor, painted.getColor(10, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(10, 12)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 12)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(10, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(10, 12)); EXPECT_EQ(kBackgroundColor, painted.getColor(11, 0)); EXPECT_EQ(kBackgroundColor, painted.getColor(11, 12)); } TEST_F(SeparatorTest, Paint_Vertical_Scale100) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); separator_->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(0, 4, 0, 5))); SkBitmap painted = PaintToCanvas(1.0f); EXPECT_EQ(kBackgroundColor, painted.getColor(3, 0)); EXPECT_EQ(kBackgroundColor, painted.getColor(3, 9)); - EXPECT_EQ(kForegroundColor, painted.getColor(4, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(4, 9)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(4, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(4, 9)); EXPECT_EQ(kBackgroundColor, painted.getColor(5, 0)); EXPECT_EQ(kBackgroundColor, painted.getColor(5, 9)); } TEST_F(SeparatorTest, Paint_Vertical_Scale125) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); separator_->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(0, 4, 0, 5))); SkBitmap painted = PaintToCanvas(1.25f); EXPECT_EQ(kBackgroundColor, painted.getColor(4, 0)); EXPECT_EQ(kBackgroundColor, painted.getColor(4, 12)); - EXPECT_EQ(kForegroundColor, painted.getColor(5, 0)); - EXPECT_EQ(kForegroundColor, painted.getColor(5, 12)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(5, 0)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(5, 12)); EXPECT_EQ(kBackgroundColor, painted.getColor(6, 0)); EXPECT_EQ(kBackgroundColor, painted.getColor(6, 12)); } TEST_F(SeparatorTest, Paint_Horizontal_Scale100) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); separator_->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(4, 0, 5, 0))); SkBitmap painted = PaintToCanvas(1.0f); EXPECT_EQ(kBackgroundColor, painted.getColor(0, 3)); EXPECT_EQ(kBackgroundColor, painted.getColor(9, 3)); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 4)); - EXPECT_EQ(kForegroundColor, painted.getColor(9, 4)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 4)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(9, 4)); EXPECT_EQ(kBackgroundColor, painted.getColor(0, 5)); EXPECT_EQ(kBackgroundColor, painted.getColor(9, 5)); } TEST_F(SeparatorTest, Paint_Horizontal_Scale125) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); separator_->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(4, 0, 5, 0))); SkBitmap painted = PaintToCanvas(1.25f); EXPECT_EQ(kBackgroundColor, painted.getColor(0, 4)); EXPECT_EQ(kBackgroundColor, painted.getColor(12, 4)); - EXPECT_EQ(kForegroundColor, painted.getColor(0, 5)); - EXPECT_EQ(kForegroundColor, painted.getColor(12, 5)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(0, 5)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(12, 5)); EXPECT_EQ(kBackgroundColor, painted.getColor(0, 6)); EXPECT_EQ(kBackgroundColor, painted.getColor(12, 6)); } @@ -305,11 +311,11 @@ TEST_F(SeparatorTest, Paint_Horizontal_Scale125) { // it to zero. TEST_F(SeparatorTest, Paint_MinimumSize_Scale100) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); separator_->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(5, 5, 5, 5))); SkBitmap painted = PaintToCanvas(1.0f); - EXPECT_EQ(kForegroundColor, painted.getColor(5, 5)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(5, 5)); EXPECT_EQ(kBackgroundColor, painted.getColor(4, 5)); EXPECT_EQ(kBackgroundColor, painted.getColor(5, 4)); EXPECT_EQ(kBackgroundColor, painted.getColor(5, 6)); @@ -320,11 +326,11 @@ TEST_F(SeparatorTest, Paint_MinimumSize_Scale100) { // it to zero (with scale factor > 1). TEST_F(SeparatorTest, Paint_MinimumSize_Scale125) { separator_->SetSize({10, 10}); - separator_->SetColor(kForegroundColor); + separator_->SetColorId(kForegroundColorId); separator_->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(5, 5, 5, 5))); SkBitmap painted = PaintToCanvas(1.25f); - EXPECT_EQ(kForegroundColor, painted.getColor(7, 7)); + EXPECT_EQ(expected_foreground_color_, painted.getColor(7, 7)); EXPECT_EQ(kBackgroundColor, painted.getColor(6, 7)); EXPECT_EQ(kBackgroundColor, painted.getColor(7, 6)); EXPECT_EQ(kBackgroundColor, painted.getColor(7, 8)); diff --git a/chromium/ui/views/controls/styled_label.cc b/chromium/ui/views/controls/styled_label.cc index 8f25251d32f..283c51fae60 100644 --- a/chromium/ui/views/controls/styled_label.cc +++ b/chromium/ui/views/controls/styled_label.cc @@ -22,6 +22,8 @@ #include "ui/gfx/text_elider.h" #include "ui/gfx/text_utils.h" #include "ui/views/controls/label.h" +#include "ui/views/controls/link.h" +#include "ui/views/controls/link_fragment.h" #include "ui/views/view_class_properties.h" namespace views { @@ -326,14 +328,18 @@ void StyledLabel::ClearStyleRanges() { PreferredSizeChanged(); } -void StyledLabel::ClickLinkForTesting() { - const auto it = - base::ranges::find(children(), Link::kViewClassName, &View::GetClassName); - DCHECK(it != children().cend()); - (*it)->OnKeyPressed( +void StyledLabel::ClickFirstLinkForTesting() { + GetFirstLinkForTesting()->OnKeyPressed( // IN-TEST ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_SPACE, ui::EF_NONE)); } +views::Link* StyledLabel::GetFirstLinkForTesting() { + const auto it = base::ranges::find(children(), LinkFragment::kViewClassName, + &View::GetClassName); + DCHECK(it != children().cend()); + return static_cast<views::Link*>(*it); +} + int StyledLabel::StartX(int excess_space) const { int x = GetInsets().left(); if (horizontal_alignment_ == gfx::ALIGN_LEFT) @@ -361,6 +367,11 @@ void StyledLabel::CalculateLayout(int width) const { // Try to preserve leading whitespace on the first line. bool can_trim_leading_whitespace = false; StyleRanges::const_iterator current_range = style_ranges_.begin(); + + // A pointer to the previous link fragment if a logical link consists of + // multiple `LinkFragment` elements. + LinkFragment* previous_link_fragment = nullptr; + for (std::u16string remaining_string = text_; content_width > 0 && !remaining_string.empty();) { layout_size_info_.line_sizes.emplace_back(0, line_height); @@ -471,18 +482,26 @@ void StyledLabel::CalculateLayout(int width) const { if (chunk.size() > range.end() - position) chunk = chunk.substr(0, range.end() - position); - if (!custom_view) - label = CreateLabel(chunk, style_info, range); + if (!custom_view) { + label = + CreateLabel(chunk, style_info, range, &previous_link_fragment); + } else { + previous_link_fragment = nullptr; + } - if (position + chunk.size() >= range.end()) + if (position + chunk.size() >= range.end()) { ++current_range; + // Links do not connect across separate style ranges. + previous_link_fragment = nullptr; + } } else { chunk = substrings[0]; if (position + chunk.size() > range.start()) chunk = chunk.substr(0, range.start() - position); // This chunk is normal text. - label = CreateLabel(chunk, default_style, range); + label = + CreateLabel(chunk, default_style, range, &previous_link_fragment); } View* child_view = custom_view ? custom_view : label.get(); @@ -529,14 +548,17 @@ void StyledLabel::CalculateLayout(int width) const { std::unique_ptr<Label> StyledLabel::CreateLabel( const std::u16string& text, const RangeStyleInfo& style_info, - const gfx::Range& range) const { + const gfx::Range& range, + LinkFragment** previous_link_fragment) const { std::unique_ptr<Label> result; if (style_info.text_style == style::STYLE_LINK) { // Nothing should (and nothing does) use a custom font for links. DCHECK(!style_info.custom_font); - // Note this ignores |default_text_style_|, in favor of style::STYLE_LINK. - auto link = std::make_unique<Link>(text, text_context_); + // Note this ignores |default_text_style_|, in favor of `style::STYLE_LINK`. + auto link = std::make_unique<LinkFragment>( + text, text_context_, style::STYLE_LINK, *previous_link_fragment); + *previous_link_fragment = link.get(); link->SetCallback(style_info.callback); if (!style_info.accessible_name.empty()) link->SetAccessibleName(style_info.accessible_name); @@ -572,7 +594,7 @@ void StyledLabel::UpdateLabelBackgroundColor() { // TODO(kylixrd): Should updating the label background color even be // allowed if there are custom views? DCHECK((child->GetClassName() == Label::kViewClassName) || - (child->GetClassName() == Link::kViewClassName)); + (child->GetClassName() == LinkFragment::kViewClassName)); static_cast<Label*>(child)->SetBackgroundColor(new_color); } } diff --git a/chromium/ui/views/controls/styled_label.h b/chromium/ui/views/controls/styled_label.h index bff92d26c7e..3b2bd6670f7 100644 --- a/chromium/ui/views/controls/styled_label.h +++ b/chromium/ui/views/controls/styled_label.h @@ -30,6 +30,7 @@ namespace views { class Label; class Link; +class LinkFragment; // A class which can apply mixed styles to a block of text. Currently, text is // always multiline. Trailing whitespace in the styled label text is not @@ -181,7 +182,10 @@ class VIEWS_EXPORT StyledLabel : public View { // Sends a space keypress to the first child that is a link. Assumes at least // one such child exists. - void ClickLinkForTesting(); + void ClickFirstLinkForTesting(); + + // Get the first child that is a link. + views::Link* GetFirstLinkForTesting(); private: struct StyleRange { @@ -212,9 +216,11 @@ class VIEWS_EXPORT StyledLabel : public View { void CalculateLayout(int width) const; // Creates a Label for a given |text|, |style_info|, and |range|. - std::unique_ptr<Label> CreateLabel(const std::u16string& text, - const RangeStyleInfo& style_info, - const gfx::Range& range) const; + std::unique_ptr<Label> CreateLabel( + const std::u16string& text, + const RangeStyleInfo& style_info, + const gfx::Range& range, + LinkFragment** previous_link_component) const; // Update the label background color from the theme or // |displayed_on_background_color_| if set. diff --git a/chromium/ui/views/controls/styled_label_unittest.cc b/chromium/ui/views/controls/styled_label_unittest.cc index 2c4bdb0d3a5..dd67683cbcc 100644 --- a/chromium/ui/views/controls/styled_label_unittest.cc +++ b/chromium/ui/views/controls/styled_label_unittest.cc @@ -23,6 +23,7 @@ #include "ui/gfx/font_list.h" #include "ui/views/border.h" #include "ui/views/controls/link.h" +#include "ui/views/controls/link_fragment.h" #include "ui/views/style/typography.h" #include "ui/views/test/test_layout_provider.h" #include "ui/views/test/test_views.h" @@ -117,7 +118,7 @@ TEST_F(StyledLabelTest, TrailingWhitespaceiIgnored) { InitStyledLabel(text); styled()->SetBounds(0, 0, 1000, 1000); - styled()->Layout(); + RunScheduledLayout(styled()); ASSERT_EQ(1u, styled()->children().size()); EXPECT_EQ(u"This is a test block of text", LabelAt(styled(), 0)->GetText()); @@ -128,7 +129,7 @@ TEST_F(StyledLabelTest, RespectLeadingWhitespace) { InitStyledLabel(text); styled()->SetBounds(0, 0, 1000, 1000); - styled()->Layout(); + RunScheduledLayout(styled()); ASSERT_EQ(1u, styled()->children().size()); EXPECT_EQ(u" This is a test block of text", @@ -140,7 +141,7 @@ TEST_F(StyledLabelTest, RespectLeadingSpacesInNonFirstLine) { const std::string text(std::string("First line\n") + indented_line); InitStyledLabel(text); styled()->SetBounds(0, 0, 1000, 1000); - styled()->Layout(); + RunScheduledLayout(styled()); ASSERT_EQ(2u, styled()->children().size()); EXPECT_EQ(ASCIIToUTF16(indented_line), LabelAt(styled(), 1)->GetText()); } @@ -154,7 +155,7 @@ TEST_F(StyledLabelTest, CorrectWrapAtNewline) { gfx::Size label_preferred_size = label.GetPreferredSize(); // Correct handling of \n and label width limit encountered at the same place styled()->SetBounds(0, 0, label_preferred_size.width(), 1000); - styled()->Layout(); + RunScheduledLayout(styled()); ASSERT_EQ(2u, styled()->children().size()); EXPECT_EQ(ASCIIToUTF16(first_line), LabelAt(styled(), 0)->GetText()); const auto* label_1 = LabelAt(styled(), 1); @@ -170,7 +171,7 @@ TEST_F(StyledLabelTest, FirstLineNotEmptyWhenLeadingWhitespaceTooLong) { gfx::Size label_preferred_size = label.GetPreferredSize(); styled()->SetBounds(0, 0, label_preferred_size.width() / 2, 1000); - styled()->Layout(); + RunScheduledLayout(styled()); ASSERT_EQ(1u, styled()->children().size()); EXPECT_EQ(u"a", LabelAt(styled(), 0)->GetText()); @@ -192,7 +193,7 @@ TEST_F(StyledLabelTest, BasicWrapping) { styled()->SetBounds( 0, 0, styled()->GetInsets().width() + label_preferred_size.width(), styled()->GetInsets().height() + 2 * label_preferred_size.height()); - styled()->Layout(); + RunScheduledLayout(styled()); ASSERT_EQ(2u, styled()->children().size()); EXPECT_EQ(3, styled()->children()[0]->x()); EXPECT_EQ(3, styled()->children()[0]->y()); @@ -206,7 +207,7 @@ TEST_F(StyledLabelTest, AllowEmptyLines) { const std::string multiline_text("one\n\nthree"); InitStyledLabel(multiline_text); styled()->SetBounds(0, 0, 1000, 1000); - styled()->Layout(); + RunScheduledLayout(styled()); EXPECT_EQ(3 * default_height, styled()->GetHeightForWidth(1000)); ASSERT_EQ(2u, styled()->children().size()); EXPECT_EQ(styled()->GetHeightForWidth(1000), @@ -225,7 +226,7 @@ TEST_F(StyledLabelTest, WrapLongWords) { styled()->SetBounds( 0, 0, styled()->GetInsets().width() + label_preferred_size.width(), styled()->GetInsets().height() + 2 * label_preferred_size.height()); - styled()->Layout(); + RunScheduledLayout(styled()); ASSERT_EQ(2u, styled()->children().size()); ASSERT_EQ(gfx::Point(), styled()->origin()); @@ -266,7 +267,7 @@ TEST_F(StyledLabelTest, CreateLinks) { // Verify layout creates the right number of children. styled()->SetBounds(0, 0, 1000, 1000); - styled()->Layout(); + RunScheduledLayout(styled()); EXPECT_EQ(7u, styled()->children().size()); } @@ -283,7 +284,7 @@ TEST_F(StyledLabelTest, StyledRangeCustomFontUnderlined) { style_info); styled()->SetBounds(0, 0, 1000, 1000); - styled()->Layout(); + RunScheduledLayout(styled()); ASSERT_EQ(2u, styled()->children().size()); EXPECT_EQ(gfx::Font::UNDERLINE, @@ -328,11 +329,11 @@ TEST_F(StyledLabelTest, StyledRangeTextStyleBold) { StyledLabel unstyled; unstyled.SetText(ASCIIToUTF16(bold_text)); unstyled.SetBounds(0, 0, styled_width, pref_height); - unstyled.Layout(); + RunScheduledLayout(&unstyled); EXPECT_EQ(1u, unstyled.children().size()); styled()->SetBounds(0, 0, styled_width, pref_height); - styled()->Layout(); + RunScheduledLayout(styled()); ASSERT_EQ(3u, styled()->children().size()); @@ -367,10 +368,12 @@ TEST_F(StyledLabelInWidgetTest, Color) { style_info_link); styled()->SetBounds(0, 0, 1000, 1000); - styled()->Layout(); + RunScheduledLayout(styled()); // The code below is not prepared to deal with dark mode. - widget()->GetNativeTheme()->set_use_dark_colors(false); + auto* const native_theme = widget()->GetNativeTheme(); + native_theme->set_use_dark_colors(false); + native_theme->NotifyOnNativeThemeUpdated(); auto* container = widget()->GetContentsView(); // Obtain the default text color for a label. @@ -385,13 +388,14 @@ TEST_F(StyledLabelInWidgetTest, Color) { ASSERT_EQ(3u, styled()->children().size()); EXPECT_EQ(SK_ColorBLUE, LabelAt(styled(), 0)->GetEnabledColor()); - EXPECT_EQ(kDefaultLinkColor, - LabelAt(styled(), 1, Link::kViewClassName)->GetEnabledColor()); + EXPECT_EQ( + kDefaultLinkColor, + LabelAt(styled(), 1, LinkFragment::kViewClassName)->GetEnabledColor()); EXPECT_EQ(kDefaultTextColor, LabelAt(styled(), 2)->GetEnabledColor()); // Test adjusted color readability. styled()->SetDisplayedOnBackgroundColor(SK_ColorBLACK); - styled()->Layout(); + RunScheduledLayout(styled()); label->SetBackgroundColor(SK_ColorBLACK); const SkColor kAdjustedTextColor = label->GetEnabledColor(); @@ -428,7 +432,7 @@ TEST_F(StyledLabelTest, StyledRangeWithTooltip) { pref_height - styled()->GetInsets().height()); styled()->SetBounds(0, 0, label_preferred_size.width(), pref_height); - styled()->Layout(); + RunScheduledLayout(styled()); EXPECT_EQ(label_preferred_size.width(), styled()->width()); @@ -465,7 +469,7 @@ TEST_F(StyledLabelTest, SetTextContextAndDefaultStyle) { EXPECT_EQ(label.GetPreferredSize().height(), styled()->height()); EXPECT_EQ(label.GetPreferredSize().width(), styled()->width()); - styled()->Layout(); + RunScheduledLayout(styled()); ASSERT_EQ(1u, styled()->children().size()); Label* sublabel = LabelAt(styled(), 0); EXPECT_EQ(style::CONTEXT_DIALOG_TITLE, sublabel->GetTextContext()); @@ -509,7 +513,7 @@ TEST_F(StyledLabelTest, LineHeightWithLink) { TEST_F(StyledLabelTest, HandleEmptyLayout) { const std::string text("This is a test block of text."); InitStyledLabel(text); - styled()->Layout(); + RunScheduledLayout(styled()); EXPECT_EQ(0u, styled()->children().size()); } @@ -529,7 +533,7 @@ TEST_F(StyledLabelTest, CacheSize) { EXPECT_EQ(0u, styled()->children().size()); styled()->SetBounds(0, 0, preferred_width, preferred_height); - styled()->Layout(); + RunScheduledLayout(styled()); // controls should be created after layout // height should be the same as precalculated @@ -541,7 +545,7 @@ TEST_F(StyledLabelTest, CacheSize) { EXPECT_EQ(real_height, precalculated_height); // another call to Layout should not kill and recreate all controls - styled()->Layout(); + RunScheduledLayout(styled()); View* first_child_after_second_layout = styled()->children().empty() ? nullptr : styled()->children().front(); EXPECT_EQ(first_child_after_layout, first_child_after_second_layout); @@ -564,7 +568,7 @@ TEST_F(StyledLabelTest, Border) { gfx::Size label_preferred_size = label.GetPreferredSize(); styled()->SetBorder(CreateEmptyBorder(gfx::Insets::TLBR(5, 10, 6, 20))); styled()->SetBounds(0, 0, 1000, 0); - styled()->Layout(); + RunScheduledLayout(styled()); EXPECT_EQ( label_preferred_size.height() + 5 /*top border*/ + 6 /*bottom border*/, styled()->GetPreferredSize().height()); @@ -637,7 +641,7 @@ TEST_F(StyledLabelTest, AlignmentInLTR) { const std::string text("text"); InitStyledLabel(text); styled()->SetBounds(0, 0, 1000, 1000); - styled()->Layout(); + RunScheduledLayout(styled()); const auto& children = styled()->children(); ASSERT_EQ(1u, children.size()); @@ -645,15 +649,15 @@ TEST_F(StyledLabelTest, AlignmentInLTR) { EXPECT_EQ(0, children.front()->bounds().x()); styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT); - styled()->Layout(); + RunScheduledLayout(styled()); EXPECT_EQ(1000, children.front()->bounds().right()); styled()->SetHorizontalAlignment(gfx::ALIGN_LEFT); - styled()->Layout(); + RunScheduledLayout(styled()); EXPECT_EQ(0, children.front()->bounds().x()); styled()->SetHorizontalAlignment(gfx::ALIGN_CENTER); - styled()->Layout(); + RunScheduledLayout(styled()); Label label(ASCIIToUTF16(text)); EXPECT_EQ((1000 - label.GetPreferredSize().width()) / 2, children.front()->bounds().x()); @@ -670,7 +674,7 @@ TEST_F(StyledLabelTest, AlignmentInRTL) { const std::string text("text"); InitStyledLabel(text); styled()->SetBounds(0, 0, 1000, 1000); - styled()->Layout(); + RunScheduledLayout(styled()); const auto& children = styled()->children(); ASSERT_EQ(1u, children.size()); @@ -680,16 +684,16 @@ TEST_F(StyledLabelTest, AlignmentInRTL) { // Setting |ALIGN_LEFT| should be flipped to |ALIGN_RIGHT|. styled()->SetHorizontalAlignment(gfx::ALIGN_LEFT); - styled()->Layout(); + RunScheduledLayout(styled()); EXPECT_EQ(1000, children.front()->bounds().right()); // Setting |ALIGN_RIGHT| should be flipped to |ALIGN_LEFT|. styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT); - styled()->Layout(); + RunScheduledLayout(styled()); EXPECT_EQ(0, children.front()->bounds().x()); styled()->SetHorizontalAlignment(gfx::ALIGN_CENTER); - styled()->Layout(); + RunScheduledLayout(styled()); Label label(ASCIIToUTF16(text)); EXPECT_EQ((1000 - label.GetPreferredSize().width()) / 2, children.front()->bounds().x()); @@ -716,7 +720,7 @@ TEST_F(StyledLabelTest, ViewsCenteredWithLinkAndCustomView) { styled()->AddCustomView(std::move(custom_view)); styled()->SetBounds(0, 0, 1000, 500); - styled()->Layout(); + RunScheduledLayout(styled()); const int height = styled()->GetPreferredSize().height(); for (const auto* child : styled()->children()) EXPECT_EQ(height / 2, child->bounds().CenterPoint().y()); @@ -738,7 +742,7 @@ TEST_F(StyledLabelTest, ViewsCenteredForEvenAndOddSizes) { } styled()->SetBounds(0, 0, kViewWidth * 3, height); - styled()->Layout(); + RunScheduledLayout(styled()); for (const auto* child : styled()->children()) EXPECT_EQ(height / 2, child->bounds().CenterPoint().y()); @@ -750,13 +754,13 @@ TEST_F(StyledLabelTest, CacheSizeWithAlignment) { InitStyledLabel(text); styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT); styled()->SetBounds(0, 0, 1000, 1000); - styled()->Layout(); + RunScheduledLayout(styled()); ASSERT_EQ(1u, styled()->children().size()); const View* child = styled()->children().front(); EXPECT_EQ(1000, child->bounds().right()); styled()->SetSize({800, 1000}); - styled()->Layout(); + RunScheduledLayout(styled()); ASSERT_EQ(1u, styled()->children().size()); const View* new_child = styled()->children().front(); EXPECT_EQ(child, new_child); @@ -770,7 +774,7 @@ TEST_F(StyledLabelTest, SizeToFit) { InitStyledLabel(text); styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT); styled()->SizeToFit(1000); - styled()->Layout(); + RunScheduledLayout(styled()); ASSERT_EQ(1u, styled()->children().size()); EXPECT_EQ(1000, styled()->children().front()->bounds().right()); } @@ -790,7 +794,7 @@ TEST_F(StyledLabelTest, PreferredSizeRespectsWrapping) { size.set_width(size.width() / 2); size.set_height(styled()->GetHeightForWidth(size.width())); styled()->SetSize(size); - styled()->Layout(); + RunScheduledLayout(styled()); const gfx::Size new_size = styled()->GetPreferredSize(); EXPECT_LE(new_size.width(), size.width()); EXPECT_EQ(new_size.height(), size.height()); diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc index b7f190f87b2..6e91a7918d8 100644 --- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc +++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc @@ -331,15 +331,15 @@ void TabStrip::OnSelectedTabChanged(Tab* from_tab, Tab* to_tab, bool animate) { return; if (GetOrientation() == TabbedPane::Orientation::kHorizontal) { - animating_from_ = gfx::Range(from_tab->GetMirroredX(), - from_tab->GetMirroredX() + from_tab->width()); - animating_to_ = gfx::Range(to_tab->GetMirroredX(), - to_tab->GetMirroredX() + to_tab->width()); + animating_from_ = {from_tab->GetMirroredX(), + from_tab->GetMirroredX() + from_tab->width()}; + animating_to_ = {to_tab->GetMirroredX(), + to_tab->GetMirroredX() + to_tab->width()}; } else { - animating_from_ = gfx::Range(from_tab->bounds().y(), - from_tab->bounds().y() + from_tab->height()); - animating_to_ = gfx::Range(to_tab->bounds().y(), - to_tab->bounds().y() + to_tab->height()); + animating_from_ = {from_tab->bounds().y(), + from_tab->bounds().y() + from_tab->height()}; + animating_to_ = {to_tab->bounds().y(), + to_tab->bounds().y() + to_tab->height()}; } contract_animation_->Stop(); @@ -355,10 +355,11 @@ Tab* TabStrip::GetTabAtDeltaFromSelected(int delta) const { const size_t selected_tab_index = GetSelectedTabIndex(); DCHECK_NE(kNoSelectedTab, selected_tab_index); const size_t num_children = children().size(); - // Clamping |delta| here ensures that even a large negative |delta| will not - // cause the addition in the next statement to wrap below 0. - delta %= static_cast<int>(num_children); - return GetTabAtIndex((selected_tab_index + num_children + delta) % + // Clamping |delta| here ensures that even a large negative |delta| will be + // positive after the addition in the next statement. + delta %= base::checked_cast<int>(num_children); + delta += static_cast<int>(num_children); + return GetTabAtIndex((selected_tab_index + static_cast<size_t>(delta)) % num_children); } @@ -438,30 +439,30 @@ void TabStrip::OnPaintBorder(gfx::Canvas* canvas) { int min_main_axis = 0; int max_main_axis = 0; if (expand_animation_->is_animating()) { - bool animating_leading = animating_to_.start() < animating_from_.start(); + bool animating_leading = animating_to_.start < animating_from_.start; double anim_value = gfx::Tween::CalculateValue( gfx::Tween::FAST_OUT_LINEAR_IN, expand_animation_->GetCurrentValue()); if (animating_leading) { min_main_axis = gfx::Tween::IntValueBetween( - anim_value, animating_from_.start(), animating_to_.start()); - max_main_axis = animating_from_.end(); + anim_value, animating_from_.start, animating_to_.start); + max_main_axis = animating_from_.end; } else { - min_main_axis = animating_from_.start(); + min_main_axis = animating_from_.start; max_main_axis = gfx::Tween::IntValueBetween( - anim_value, animating_from_.end(), animating_to_.end()); + anim_value, animating_from_.end, animating_to_.end); } } else if (contract_animation_->is_animating()) { - bool animating_leading = animating_to_.start() < animating_from_.start(); + bool animating_leading = animating_to_.start < animating_from_.start; double anim_value = gfx::Tween::CalculateValue( gfx::Tween::LINEAR_OUT_SLOW_IN, contract_animation_->GetCurrentValue()); if (animating_leading) { - min_main_axis = animating_to_.start(); + min_main_axis = animating_to_.start; max_main_axis = gfx::Tween::IntValueBetween( - anim_value, animating_from_.end(), animating_to_.end()); + anim_value, animating_from_.end, animating_to_.end); } else { min_main_axis = gfx::Tween::IntValueBetween( - anim_value, animating_from_.start(), animating_to_.start()); - max_main_axis = animating_to_.end(); + anim_value, animating_from_.start, animating_to_.start); + max_main_axis = animating_to_.end; } } else if (is_horizontal) { min_main_axis = tab->GetMirroredX(); @@ -483,7 +484,7 @@ void TabStrip::OnPaintBorder(gfx::Canvas* canvas) { } BEGIN_METADATA(TabStrip, View) -ADD_READONLY_PROPERTY_METADATA(int, SelectedTabIndex) +ADD_READONLY_PROPERTY_METADATA(size_t, SelectedTabIndex) ADD_READONLY_PROPERTY_METADATA(TabbedPane::Orientation, Orientation) ADD_READONLY_PROPERTY_METADATA(TabbedPane::TabStripStyle, Style) END_METADATA @@ -525,12 +526,13 @@ void TabbedPane::AddTabInternal(size_t index, std::unique_ptr<View> contents) { DCHECK_LE(index, GetTabCount()); contents->SetVisible(false); - contents->GetViewAccessibility().OverrideName(title); contents->GetViewAccessibility().OverrideRole(ax::mojom::Role::kTabPanel); + if (!title.empty()) + contents->GetViewAccessibility().OverrideName(title); tab_strip_->AddChildViewAt(std::make_unique<Tab>(this, title, contents.get()), - static_cast<int>(index)); - contents_->AddChildViewAt(std::move(contents), static_cast<int>(index)); + index); + contents_->AddChildViewAt(std::move(contents), index); if (!GetSelectedTab()) SelectTabAt(index); @@ -564,8 +566,10 @@ void TabbedPane::SelectTab(Tab* new_selected_tab, bool animate) { focus_manager->SetFocusedView(new_selected_tab->contents()); } - if (listener()) - listener()->TabSelectedAt(tab_strip_->GetIndexOf(new_selected_tab)); + if (listener()) { + listener()->TabSelectedAt(base::checked_cast<int>( + tab_strip_->GetIndexOf(new_selected_tab).value())); + } } void TabbedPane::SelectTabAt(size_t index, bool animate) { diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.h b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.h index cc9622c0659..d8bfb3cdec3 100644 --- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.h +++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.h @@ -237,6 +237,10 @@ class TabStrip : public View, public gfx::AnimationDelegate { void OnPaintBorder(gfx::Canvas* canvas) override; private: + struct Coordinates { + int start, end; + }; + // The orientation of the tab alignment. const TabbedPane::Orientation orientation_; @@ -254,8 +258,8 @@ class TabStrip : public View, public gfx::AnimationDelegate { std::make_unique<gfx::LinearAnimation>(this); // The x-coordinate ranges of the old selection and the new selection. - gfx::Range animating_from_; - gfx::Range animating_to_; + Coordinates animating_from_; + Coordinates animating_to_; }; } // namespace views diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane_accessibility_mac_unittest.mm b/chromium/ui/views/controls/tabbed_pane/tabbed_pane_accessibility_mac_unittest.mm index 690b68cee63..24b3e1a320b 100644 --- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane_accessibility_mac_unittest.mm +++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane_accessibility_mac_unittest.mm @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/memory/raw_ptr.h" + #import <Cocoa/Cocoa.h> #import "base/mac/foundation_util.h" @@ -89,8 +91,8 @@ class TabbedPaneAccessibilityMacTest : public WidgetTest { } protected: - Widget* widget_ = nullptr; - TabbedPane* tabbed_pane_ = nullptr; + raw_ptr<Widget> widget_ = nullptr; + raw_ptr<TabbedPane> tabbed_pane_ = nullptr; }; // Test the Tab's a11y information compared to a Cocoa NSTabViewItem. diff --git a/chromium/ui/views/controls/table/table_grouper.h b/chromium/ui/views/controls/table/table_grouper.h index af2fc0f272f..73e233a95d3 100644 --- a/chromium/ui/views/controls/table/table_grouper.h +++ b/chromium/ui/views/controls/table/table_grouper.h @@ -10,8 +10,8 @@ namespace views { struct VIEWS_EXPORT GroupRange { - int start; - int length; + size_t start; + size_t length; }; // TableGrouper is used by TableView to group a set of rows and treat them @@ -19,7 +19,7 @@ struct VIEWS_EXPORT GroupRange { // together. class VIEWS_EXPORT TableGrouper { public: - virtual void GetGroupRange(int model_index, GroupRange* range) = 0; + virtual void GetGroupRange(size_t model_index, GroupRange* range) = 0; protected: virtual ~TableGrouper() = default; diff --git a/chromium/ui/views/controls/table/table_header.cc b/chromium/ui/views/controls/table/table_header.cc index d0e89665257..14183ea4f11 100644 --- a/chromium/ui/views/controls/table/table_header.cc +++ b/chromium/ui/views/controls/table/table_header.cc @@ -54,6 +54,7 @@ constexpr int kSortIndicatorSize = 8; // static const int TableHeader::kHorizontalPadding = 7; + // static const int TableHeader::kSortIndicatorWidth = kSortIndicatorSize + TableHeader::kHorizontalPadding * 2; @@ -139,16 +140,15 @@ void TableHeader::OnPaint(gfx::Canvas* canvas) { (column.column.id == sorted_column_id && title_width + kSortIndicatorWidth <= width); - if (paint_sort_indicator && - column.column.alignment == ui::TableColumn::RIGHT) { + if (paint_sort_indicator) width -= kSortIndicatorWidth; - } canvas->DrawStringRectWithFlags( column.column.title, font_list_, text_color, gfx::Rect(GetMirroredXWithWidthInView(x, width), kVerticalPadding, width, height() - kVerticalPadding * 2), - TableColumnAlignmentToCanvasAlignment(column.column.alignment)); + TableColumnAlignmentToCanvasAlignment( + GetMirroredTableColumnAlignment(column.column.alignment))); if (paint_sort_indicator) { cc::PaintFlags flags; @@ -157,19 +157,12 @@ void TableHeader::OnPaint(gfx::Canvas* canvas) { flags.setAntiAlias(true); int indicator_x = 0; - ui::TableColumn::Alignment alignment = column.column.alignment; - if (base::i18n::IsRTL()) { - if (alignment == ui::TableColumn::LEFT) - alignment = ui::TableColumn::RIGHT; - else if (alignment == ui::TableColumn::RIGHT) - alignment = ui::TableColumn::LEFT; - } - switch (alignment) { + switch (column.column.alignment) { case ui::TableColumn::LEFT: indicator_x = x + title_width; break; case ui::TableColumn::CENTER: - indicator_x = x + width / 2; + indicator_x = x + width / 2 + title_width / 2; break; case ui::TableColumn::RIGHT: indicator_x = x + width; @@ -227,7 +220,7 @@ void TableHeader::AddedToWidget() { } ui::Cursor TableHeader::GetCursor(const ui::MouseEvent& event) { - return GetResizeColumn(GetMirroredXInView(event.x())) != -1 + return GetResizeColumn(GetMirroredXInView(event.x())).has_value() ? ui::mojom::CursorType::kColumnResize : View::GetCursor(event); } @@ -290,9 +283,8 @@ void TableHeader::OnThemeChanged() { } void TableHeader::ResizeColumnViaKeyboard( - int index, + size_t index, TableView::AdvanceDirection direction) { - DCHECK_GE(index, 0); const TableView::VisibleColumn& column = table_->GetVisibleColumn(index); const int needed_for_title = gfx::GetStringWidth(column.column.title, font_list_) + @@ -317,30 +309,32 @@ bool TableHeader::GetHeaderRowHasFocus() const { } gfx::Rect TableHeader::GetActiveHeaderCellBounds() const { - const int active_index = table_->GetActiveVisibleColumnIndex(); - DCHECK_NE(ui::ListSelectionModel::kUnselectedIndex, active_index); + const absl::optional<size_t> active_index = + table_->GetActiveVisibleColumnIndex(); + DCHECK(active_index.has_value()); const TableView::VisibleColumn& column = - table_->GetVisibleColumn(active_index); + table_->GetVisibleColumn(active_index.value()); return gfx::Rect(column.x, 0, column.width, height()); } bool TableHeader::HasFocusIndicator() const { - return table_->GetActiveVisibleColumnIndex() != - ui::ListSelectionModel::kUnselectedIndex; + return table_->GetActiveVisibleColumnIndex().has_value(); } bool TableHeader::StartResize(const ui::LocatedEvent& event) { if (is_resizing()) return false; - const int index = GetResizeColumn(GetMirroredXInView(event.x())); - if (index == -1) + const absl::optional<size_t> index = + GetResizeColumn(GetMirroredXInView(event.x())); + if (!index.has_value()) return false; resize_details_ = std::make_unique<ColumnResizeDetails>(); - resize_details_->column_index = index; + resize_details_->column_index = index.value(); resize_details_->initial_x = event.root_location().x(); - resize_details_->initial_width = table_->GetVisibleColumn(index).width; + resize_details_->initial_width = + table_->GetVisibleColumn(index.value()).width; return true; } @@ -367,28 +361,34 @@ void TableHeader::ToggleSortOrder(const ui::LocatedEvent& event) { return; const int x = GetMirroredXInView(event.x()); - const int index = GetClosestVisibleColumnIndex(table_, x); - const TableView::VisibleColumn& column(table_->GetVisibleColumn(index)); + const absl::optional<size_t> index = GetClosestVisibleColumnIndex(table_, x); + if (!index.has_value()) + return; + const TableView::VisibleColumn& column( + table_->GetVisibleColumn(index.value())); if (x >= column.x && x < column.x + column.width && event.y() >= 0 && - event.y() < height()) - table_->ToggleSortOrder(index); + event.y() < height()) { + table_->ToggleSortOrder(index.value()); + } } -int TableHeader::GetResizeColumn(int x) const { +absl::optional<size_t> TableHeader::GetResizeColumn(int x) const { const Columns& columns(table_->visible_columns()); if (columns.empty()) - return -1; + return absl::nullopt; - const int index = GetClosestVisibleColumnIndex(table_, x); - DCHECK_NE(-1, index); - const TableView::VisibleColumn& column(table_->GetVisibleColumn(index)); - if (index > 0 && x >= column.x - kResizePadding && + const absl::optional<size_t> index = GetClosestVisibleColumnIndex(table_, x); + DCHECK(index.has_value()); + const TableView::VisibleColumn& column( + table_->GetVisibleColumn(index.value())); + if (index.value() > 0 && x >= column.x - kResizePadding && x <= column.x + kResizePadding) { - return index - 1; + return index.value() - 1; } const int max_x = column.x + column.width; - return (x >= max_x - kResizePadding && x <= max_x + kResizePadding) ? index - : -1; + return (x >= max_x - kResizePadding && x <= max_x + kResizePadding) + ? absl::make_optional(index.value()) + : absl::nullopt; } BEGIN_METADATA(TableHeader, View) END_METADATA diff --git a/chromium/ui/views/controls/table/table_header.h b/chromium/ui/views/controls/table/table_header.h index a435e5d3f3f..60693a4442d 100644 --- a/chromium/ui/views/controls/table/table_header.h +++ b/chromium/ui/views/controls/table/table_header.h @@ -34,7 +34,7 @@ class VIEWS_EXPORT TableHeader : public views::View { const gfx::FontList& font_list() const { return font_list_; } - void ResizeColumnViaKeyboard(int index, + void ResizeColumnViaKeyboard(size_t index, TableView::AdvanceDirection direction); // Call to update TableHeader objects that rely on the focus state of its @@ -63,7 +63,7 @@ class VIEWS_EXPORT TableHeader : public views::View { ColumnResizeDetails() = default; // Index into table_->visible_columns() that is being resized. - int column_index = 0; + size_t column_index = 0; // X-coordinate of the mouse at the time the resize started. int initial_x = 0; @@ -92,9 +92,9 @@ class VIEWS_EXPORT TableHeader : public views::View { // Toggles the sort order of the column at the location in |event|. void ToggleSortOrder(const ui::LocatedEvent& event); - // Returns the column to resize given the specified x-coordinate, or -1 if |x| - // is not in the resize range of any columns. - int GetResizeColumn(int x) const; + // Returns the column to resize given the specified x-coordinate, or nullopt + // if |x| is not in the resize range of any columns. + absl::optional<size_t> GetResizeColumn(int x) const; bool is_resizing() const { return resize_details_.get() != nullptr; } diff --git a/chromium/ui/views/controls/table/table_utils.cc b/chromium/ui/views/controls/table/table_utils.cc index d6ac523212e..7681fecf6ce 100644 --- a/chromium/ui/views/controls/table/table_utils.cc +++ b/chromium/ui/views/controls/table/table_utils.cc @@ -8,7 +8,9 @@ #include <algorithm> +#include "base/i18n/rtl.h" #include "base/notreached.h" +#include "ui/base/models/table_model.h" #include "ui/gfx/canvas.h" #include "ui/gfx/font_list.h" #include "ui/gfx/text_utils.h" @@ -29,7 +31,7 @@ int WidthForContent(const gfx::FontList& header_font_list, width = gfx::GetStringWidth(column.title, header_font_list) + header_padding; - for (int i = 0, row_count = model->RowCount(); i < row_count; ++i) { + for (size_t i = 0, row_count = model->RowCount(); i < row_count; ++i) { const int cell_width = gfx::GetStringWidth(model->GetText(i, column.id), content_font_list); width = std::max(width, cell_width); @@ -109,14 +111,32 @@ int TableColumnAlignmentToCanvasAlignment( return gfx::Canvas::TEXT_ALIGN_LEFT; } -int GetClosestVisibleColumnIndex(const TableView* table, int x) { +absl::optional<size_t> GetClosestVisibleColumnIndex(const TableView* table, + int x) { const std::vector<TableView::VisibleColumn>& columns( table->visible_columns()); + if (columns.empty()) + return absl::nullopt; for (size_t i = 0; i < columns.size(); ++i) { if (x <= columns[i].x + columns[i].width) - return static_cast<int>(i); + return i; + } + return columns.size() - 1; +} + +ui::TableColumn::Alignment GetMirroredTableColumnAlignment( + ui::TableColumn::Alignment alignment) { + if (!base::i18n::IsRTL()) + return alignment; + + switch (alignment) { + case ui::TableColumn::LEFT: + return ui::TableColumn::RIGHT; + case ui::TableColumn::RIGHT: + return ui::TableColumn::LEFT; + case ui::TableColumn::CENTER: + return ui::TableColumn::CENTER; } - return static_cast<int>(columns.size()) - 1; } } // namespace views diff --git a/chromium/ui/views/controls/table/table_utils.h b/chromium/ui/views/controls/table/table_utils.h index ec387102e69..08a342fd284 100644 --- a/chromium/ui/views/controls/table/table_utils.h +++ b/chromium/ui/views/controls/table/table_utils.h @@ -7,6 +7,7 @@ #include <vector> +#include "third_party/abseil-cpp/absl/types/optional.h" #include "ui/base/models/table_model.h" #include "ui/views/views_export.h" @@ -49,8 +50,16 @@ VIEWS_EXPORT std::vector<int> CalculateTableColumnSizes( int TableColumnAlignmentToCanvasAlignment(ui::TableColumn::Alignment alignment); // Returns the index of the closest visible column index to |x|. Return value is -// in terms of table->visible_columns(). -int GetClosestVisibleColumnIndex(const TableView* table, int x); +// in terms of table->visible_columns(). Returns nullopt if there are no visible +// columns. +absl::optional<size_t> GetClosestVisibleColumnIndex(const TableView* table, + int x); + +// Returns the mirror of the table column alignment if the layout is +// right-to-left. If the layout is left-to-right, the same alignment is +// returned. +ui::TableColumn::Alignment GetMirroredTableColumnAlignment( + ui::TableColumn::Alignment alignment); } // namespace views diff --git a/chromium/ui/views/controls/table/table_view.cc b/chromium/ui/views/controls/table/table_view.cc index 266c7a98fcd..b1da527d0b5 100644 --- a/chromium/ui/views/controls/table/table_view.cc +++ b/chromium/ui/views/controls/table/table_view.cc @@ -31,6 +31,7 @@ #include "ui/color/color_provider.h" #include "ui/events/event.h" #include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/rect_conversions.h" #include "ui/gfx/geometry/rect_f.h" #include "ui/gfx/geometry/skia_conversions.h" @@ -63,14 +64,15 @@ int SwapCompareResult(int result, bool ascending) { } // Populates |model_index_to_range_start| based on the |grouper|. -void GetModelIndexToRangeStart(TableGrouper* grouper, - int row_count, - std::map<int, int>* model_index_to_range_start) { - for (int model_index = 0; model_index < row_count;) { +void GetModelIndexToRangeStart( + TableGrouper* grouper, + size_t row_count, + std::map<size_t, size_t>* model_index_to_range_start) { + for (size_t model_index = 0; model_index < row_count;) { GroupRange range; grouper->GetGroupRange(model_index, &range); - DCHECK_GT(range.length, 0); - for (int i = model_index; i < model_index + range.length; ++i) + DCHECK_GT(range.length, 0u); + for (size_t i = model_index; i < model_index + range.length; ++i) (*model_index_to_range_start)[i] = model_index; model_index += range.length; } @@ -104,7 +106,7 @@ bool IsCmdOrCtrl(const ui::Event& event) { struct TableView::SortHelper { explicit SortHelper(TableView* table) : table(table) {} - bool operator()(int model_index1, int model_index2) { + bool operator()(size_t model_index1, size_t model_index2) { return table->CompareRows(model_index1, model_index2) < 0; } @@ -117,9 +119,9 @@ struct TableView::SortHelper { struct TableView::GroupSortHelper { explicit GroupSortHelper(TableView* table) : table(table) {} - bool operator()(int model_index1, int model_index2) { - const int range1 = model_index_to_range_start[model_index1]; - const int range2 = model_index_to_range_start[model_index2]; + bool operator()(size_t model_index1, size_t model_index2) { + const size_t range1 = model_index_to_range_start[model_index1]; + const size_t range2 = model_index_to_range_start[model_index2]; if (range1 == range2) { // The two rows are in the same group, sort so that items in the same // group always appear in the same order. @@ -129,7 +131,7 @@ struct TableView::GroupSortHelper { } raw_ptr<TableView> table; - std::map<int, int> model_index_to_range_start; + std::map<size_t, size_t> model_index_to_range_start; }; TableView::VisibleColumn::VisibleColumn() = default; @@ -287,15 +289,17 @@ void TableView::SetGrouper(TableGrouper* grouper) { SortItemsAndUpdateMapping(/*schedule_paint=*/true); } -int TableView::GetRowCount() const { +size_t TableView::GetRowCount() const { return model_ ? model_->RowCount() : 0; } -void TableView::Select(int model_row) { +void TableView::Select(absl::optional<size_t> model_row) { if (!model_) return; - SelectByViewIndex(model_row == -1 ? -1 : ModelToView(model_row)); + SelectByViewIndex(model_row.has_value() + ? absl::make_optional(ModelToView(model_row.value())) + : absl::nullopt); } void TableView::SetSelectionAll(bool select) { @@ -313,10 +317,11 @@ void TableView::SetSelectionAll(bool select) { SetSelectionModel(std::move(selection_model)); } -int TableView::GetFirstSelectedRow() const { +absl::optional<size_t> TableView::GetFirstSelectedRow() const { return selection_model_.empty() - ? -1 - : *selection_model_.selected_indices().begin(); + ? absl::nullopt + : absl::make_optional( + *selection_model_.selected_indices().begin()); } // TODO(dpenning) : Prevent the last column from being closed. See @@ -335,10 +340,12 @@ void TableView::SetColumnVisibility(int id, bool is_visible) { [id](const auto& column) { return column.column.id == id; }); if (i != visible_columns_.end()) { visible_columns_.erase(i); - if (active_visible_column_index_ >= - static_cast<int>(visible_columns_.size())) - SetActiveVisibleColumnIndex(static_cast<int>(visible_columns_.size()) - - 1); + if (active_visible_column_index_.has_value() && + active_visible_column_index_.value() >= visible_columns_.size()) + SetActiveVisibleColumnIndex( + visible_columns_.empty() + ? absl::nullopt + : absl::make_optional(visible_columns_.size() - 1)); } } @@ -354,9 +361,8 @@ void TableView::SetColumnVisibility(int id, bool is_visible) { RebuildVirtualAccessibilityChildren(); } -void TableView::ToggleSortOrder(int visible_column_index) { - DCHECK(visible_column_index >= 0 && - visible_column_index < static_cast<int>(visible_columns_.size())); +void TableView::ToggleSortOrder(size_t visible_column_index) { + DCHECK(visible_column_index < visible_columns_.size()); const ui::TableColumn& column = visible_columns_[visible_column_index].column; if (!column.sortable) return; @@ -411,10 +417,8 @@ bool TableView::HasColumn(int id) const { } bool TableView::GetHasFocusIndicator() const { - int active_row = selection_model_.active(); - return active_row != ui::ListSelectionModel::kUnselectedIndex && - active_visible_column_index_ != - ui::ListSelectionModel::kUnselectedIndex; + return selection_model_.active().has_value() && + active_visible_column_index_.has_value(); } void TableView::SetObserver(TableViewObserver* observer) { @@ -428,13 +432,13 @@ TableViewObserver* TableView::GetObserver() const { return observer_; } -const TableView::VisibleColumn& TableView::GetVisibleColumn(int index) { - DCHECK(index >= 0 && index < static_cast<int>(visible_columns_.size())); +const TableView::VisibleColumn& TableView::GetVisibleColumn(size_t index) { + DCHECK(index < visible_columns_.size()); return visible_columns_[index]; } -void TableView::SetVisibleColumnWidth(int index, int width) { - DCHECK(index >= 0 && index < static_cast<int>(visible_columns_.size())); +void TableView::SetVisibleColumnWidth(size_t index, int width) { + DCHECK(index < visible_columns_.size()); if (visible_columns_[index].width == width) return; base::AutoReset<bool> reseter(&in_set_visible_column_width_, true); @@ -448,21 +452,19 @@ void TableView::SetVisibleColumnWidth(int index, int width) { UpdateVirtualAccessibilityChildrenBounds(); } -int TableView::ModelToView(int model_index) const { - DCHECK_GE(model_index, 0) << " negative model_index " << model_index; +size_t TableView::ModelToView(size_t model_index) const { if (!GetIsSorted()) return model_index; - DCHECK_LT(model_index, static_cast<int>(model_to_view_.size())) + DCHECK_LT(model_index, model_to_view_.size()) << " out of bounds model_index " << model_index; return model_to_view_[model_index]; } -int TableView::ViewToModel(int view_index) const { - DCHECK_GE(view_index, 0) << " negative view_index " << view_index; +size_t TableView::ViewToModel(size_t view_index) const { DCHECK_LT(view_index, GetRowCount()); if (!GetIsSorted()) return view_index; - DCHECK_LT(view_index, static_cast<int>(view_to_model_.size())) + DCHECK_LT(view_index, view_to_model_.size()) << " out of bounds view_index " << view_index; return view_to_model_[view_index]; } @@ -535,7 +537,7 @@ gfx::Size TableView::CalculatePreferredSize() const { int width = 50; if (header_ && !visible_columns_.empty()) width = visible_columns_.back().x + visible_columns_.back().width; - return gfx::Size(width, GetRowCount() * row_height_); + return gfx::Size(width, static_cast<int>(GetRowCount()) * row_height_); } bool TableView::GetNeedsNotificationWhenVisibleBoundsChange() const { @@ -565,7 +567,7 @@ bool TableView::OnKeyPressed(const ui::KeyEvent& event) { if (header_row_is_active_) break; if (GetRowCount()) - SelectByViewIndex(0); + SelectByViewIndex(size_t{0}); return true; case ui::VKEY_END: @@ -579,7 +581,7 @@ bool TableView::OnKeyPressed(const ui::KeyEvent& event) { #if BUILDFLAG(IS_MAC) if (event.IsAltDown()) { if (GetRowCount()) - SelectByViewIndex(0); + SelectByViewIndex(size_t{0}); } else { AdvanceSelection(AdvanceDirection::kDecrement); } @@ -607,9 +609,9 @@ bool TableView::OnKeyPressed(const ui::KeyEvent& event) { ? AdvanceDirection::kIncrement : AdvanceDirection::kDecrement; if (IsCmdOrCtrl(event)) { - if (active_visible_column_index_ != -1 && header_) { - header_->ResizeColumnViaKeyboard(active_visible_column_index_, - direction); + if (active_visible_column_index_.has_value() && header_) { + header_->ResizeColumnViaKeyboard( + active_visible_column_index_.value(), direction); UpdateFocusRings(); } } else { @@ -628,9 +630,9 @@ bool TableView::OnKeyPressed(const ui::KeyEvent& event) { ? AdvanceDirection::kDecrement : AdvanceDirection::kIncrement; if (IsCmdOrCtrl(event)) { - if (active_visible_column_index_ != -1 && header_) { - header_->ResizeColumnViaKeyboard(active_visible_column_index_, - direction); + if (active_visible_column_index_.has_value() && header_) { + header_->ResizeColumnViaKeyboard( + active_visible_column_index_.value(), direction); UpdateFocusRings(); } } else { @@ -646,16 +648,16 @@ bool TableView::OnKeyPressed(const ui::KeyEvent& event) { // when the table header is active. case ui::VKEY_RETURN: if (PlatformStyle::kTableViewSupportsKeyboardNavigationByCell && - active_visible_column_index_ != -1 && header_row_is_active_) { - ToggleSortOrder(active_visible_column_index_); + active_visible_column_index_.has_value() && header_row_is_active_) { + ToggleSortOrder(active_visible_column_index_.value()); return true; } break; case ui::VKEY_SPACE: if (PlatformStyle::kTableViewSupportsKeyboardNavigationByCell && - active_visible_column_index_ != -1) { - ToggleSortOrder(active_visible_column_index_); + active_visible_column_index_.has_value()) { + ToggleSortOrder(active_visible_column_index_.value()); return true; } break; @@ -675,11 +677,11 @@ bool TableView::OnMousePressed(const ui::MouseEvent& event) { return true; const int row = event.y() / row_height_; - if (row < 0 || row >= GetRowCount()) + if (row < 0 || static_cast<size_t>(row) >= GetRowCount()) return true; if (event.GetClickCount() == 2) { - SelectByViewIndex(row); + SelectByViewIndex(static_cast<size_t>(row)); if (observer_) observer_->OnDoubleClick(); } else if (event.GetClickCount() == 1) { @@ -698,7 +700,7 @@ void TableView::OnGestureEvent(ui::GestureEvent* event) { RequestFocus(); const int row = event->y() / row_height_; - if (row < 0 || row >= GetRowCount()) + if (row < 0 || static_cast<size_t>(row) >= GetRowCount()) return; event->StopPropagation(); @@ -709,19 +711,23 @@ void TableView::OnGestureEvent(ui::GestureEvent* event) { std::u16string TableView::GetTooltipText(const gfx::Point& p) const { const int row = p.y() / row_height_; - if (row < 0 || row >= GetRowCount() || visible_columns_.empty()) + if (row < 0 || static_cast<size_t>(row) >= GetRowCount() || + visible_columns_.empty()) { return std::u16string(); + } const int x = GetMirroredXInView(p.x()); - const int column = GetClosestVisibleColumnIndex(this, x); - if (x < visible_columns_[column].x || - x > (visible_columns_[column].x + visible_columns_[column].width)) + const absl::optional<size_t> column = GetClosestVisibleColumnIndex(this, x); + if (!column.has_value() || x < visible_columns_[column.value()].x || + x > (visible_columns_[column.value()].x + + visible_columns_[column.value()].width)) { return std::u16string(); + } - const int model_row = ViewToModel(row); - if (column == 0 && !model_->GetTooltip(model_row).empty()) + const size_t model_row = ViewToModel(static_cast<size_t>(row)); + if (column.value() == 0 && !model_->GetTooltip(model_row).empty()) return model_->GetTooltip(model_row); - return model_->GetText(model_row, visible_columns_[column].column.id); + return model_->GetText(model_row, visible_columns_[column.value()].column.id); } void TableView::GetAccessibleNodeData(ui::AXNodeData* node_data) { @@ -740,7 +746,7 @@ void TableView::GetAccessibleNodeData(ui::AXNodeData* node_data) { } bool TableView::HandleAccessibleAction(const ui::AXActionData& action_data) { - const int row_count = GetRowCount(); + const size_t row_count = GetRowCount(); if (!row_count) return false; @@ -750,9 +756,7 @@ bool TableView::HandleAccessibleAction(const ui::AXActionData& action_data) { bool focus_on_row = ax_view ? ax_view->GetData().role == ax::mojom::Role::kRow : false; - int active_row = selection_model_.active(); - if (active_row == ui::ListSelectionModel::kUnselectedIndex) - active_row = ModelToView(0); + size_t active_row = selection_model_.active().value_or(ModelToView(0)); switch (action_data.action) { case ax::mojom::Action::kDoDefault: @@ -760,8 +764,9 @@ bool TableView::HandleAccessibleAction(const ui::AXActionData& action_data) { if (focus_on_row) { // If the ax focus is on a row, select this row. DCHECK(ax_view); - int row_index = ax_view->GetData().GetIntAttribute( - ax::mojom::IntAttribute::kTableRowIndex); + size_t row_index = + base::checked_cast<size_t>(ax_view->GetData().GetIntAttribute( + ax::mojom::IntAttribute::kTableRowIndex)); SelectByViewIndex(row_index); GetViewAccessibility().AnnounceText(l10n_util::GetStringFUTF16( IDS_TABLE_VIEW_AX_ANNOUNCE_ROW_SELECTED, @@ -780,7 +785,7 @@ bool TableView::HandleAccessibleAction(const ui::AXActionData& action_data) { RequestFocus(); // Setting focus should not affect the current selection. if (selection_model_.empty()) - SelectByViewIndex(0); + SelectByViewIndex(size_t{0}); break; case ax::mojom::Action::kScrollRight: { @@ -827,23 +832,21 @@ void TableView::OnModelChanged() { PreferredSizeChanged(); } -void TableView::OnItemsChanged(int start, int length) { +void TableView::OnItemsChanged(size_t start, size_t length) { SortItemsAndUpdateMapping(/*schedule_paint=*/true); } -void TableView::OnItemsAdded(int start, int length) { - DCHECK_GE(start, 0); - DCHECK_GE(length, 0); +void TableView::OnItemsAdded(size_t start, size_t length) { DCHECK_LE(start + length, GetRowCount()); - for (int i = 0; i < length; ++i) { + for (size_t i = 0; i < length; ++i) { // Increment selection model counter at start. selection_model_.IncrementFrom(start); // Append new virtual row to accessibility view. - const int virtual_children_count = + const size_t virtual_children_count = GetViewAccessibility().virtual_children().size(); - const int next_index = + const size_t next_index = header_ ? virtual_children_count - 1 : virtual_children_count; GetViewAccessibility().AddVirtualChildView( CreateRowAccessibilityView(next_index)); @@ -854,24 +857,25 @@ void TableView::OnItemsAdded(int start, int length) { NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged, true); } -void TableView::OnItemsMoved(int old_start, int length, int new_start) { +void TableView::OnItemsMoved(size_t old_start, + size_t length, + size_t new_start) { selection_model_.Move(old_start, new_start, length); SortItemsAndUpdateMapping(/*schedule_paint=*/true); } -void TableView::OnItemsRemoved(int start, int length) { - DCHECK_GE(start, 0); - DCHECK_GE(length, 0); - +void TableView::OnItemsRemoved(size_t start, size_t length) { // Determine the currently selected index in terms of the view. We inline the // implementation here since ViewToModel() has DCHECKs that fail since the // model has changed but |model_to_view_| has not been updated yet. - const int previously_selected_model_index = GetFirstSelectedRow(); - int previously_selected_view_index = previously_selected_model_index; - if (previously_selected_model_index != -1 && GetIsSorted()) + const absl::optional<size_t> previously_selected_model_index = + GetFirstSelectedRow(); + absl::optional<size_t> previously_selected_view_index = + previously_selected_model_index; + if (previously_selected_model_index.has_value() && GetIsSorted()) previously_selected_view_index = - model_to_view_[previously_selected_model_index]; - for (int i = 0; i < length; ++i) + model_to_view_[previously_selected_model_index.value()]; + for (size_t i = 0; i < length; ++i) selection_model_.DecrementFrom(start); // Update the `view_to_model_` and `model_to_view_` mappings prior to updating @@ -883,25 +887,29 @@ void TableView::OnItemsRemoved(int start, int length) { // See (https://crbug.com/1173373). SortItemsAndUpdateMapping(/*schedule_paint=*/true); if (GetIsSorted()) { - DCHECK_EQ(GetRowCount(), static_cast<int>(view_to_model_.size())); - DCHECK_EQ(GetRowCount(), static_cast<int>(model_to_view_.size())); + DCHECK_EQ(GetRowCount(), view_to_model_.size()); + DCHECK_EQ(GetRowCount(), model_to_view_.size()); } // If the selection was empty and is no longer empty select the same visual // index. - if (selection_model_.empty() && previously_selected_view_index != -1 && + if (selection_model_.empty() && previously_selected_view_index.has_value() && GetRowCount() && select_on_remove_) { selection_model_.SetSelectedIndex(ViewToModel( - std::min(GetRowCount() - 1, previously_selected_view_index))); + std::min(GetRowCount() - 1, previously_selected_view_index.value()))); + } + if (!selection_model_.empty()) { + const size_t selected_model_index = + *selection_model_.selected_indices().begin(); + if (!selection_model_.active().has_value()) + selection_model_.set_active(selected_model_index); + if (!selection_model_.anchor().has_value()) + selection_model_.set_anchor(selected_model_index); } - if (!selection_model_.empty() && selection_model_.active() == -1) - selection_model_.set_active(GetFirstSelectedRow()); - if (!selection_model_.empty() && selection_model_.anchor() == -1) - selection_model_.set_anchor(GetFirstSelectedRow()); // Remove the virtual views that are no longer needed. auto& virtual_children = GetViewAccessibility().virtual_children(); - for (int i = start; !virtual_children.empty() && i < start + length; i++) + for (size_t i = start; !virtual_children.empty() && i < start + length; i++) virtual_children[virtual_children.size() - 1]->RemoveFromParentView(); UpdateVirtualAccessibilityChildrenBounds(); @@ -912,11 +920,11 @@ void TableView::OnItemsRemoved(int start, int length) { } gfx::Point TableView::GetKeyboardContextMenuLocation() { - int first_selected = GetFirstSelectedRow(); + absl::optional<size_t> first_selected = GetFirstSelectedRow(); gfx::Rect vis_bounds(GetVisibleBounds()); int y = vis_bounds.height() / 2; - if (first_selected != -1) { - gfx::Rect cell_bounds(GetRowBounds(first_selected)); + if (first_selected.has_value()) { + gfx::Rect cell_bounds(GetRowBounds(first_selected.value())); if (cell_bounds.bottom() >= vis_bounds.y() && cell_bounds.bottom() < vis_bounds.bottom()) { y = cell_bounds.bottom(); @@ -960,7 +968,7 @@ void TableView::OnPaintImpl(gfx::Canvas* canvas) { return; const PaintRegion region(GetPaintRegion(GetPaintBounds(canvas))); - if (region.min_column == -1) + if (region.min_column == visible_columns_.size()) return; // No need to paint anything. const SkColor selected_bg_color = @@ -972,46 +980,50 @@ void TableView::OnPaintImpl(gfx::Canvas* canvas) { color_provider->GetColor(ui::kColorTableBackgroundAlternate); const int cell_margin = GetCellMargin(); const int cell_element_spacing = GetCellElementSpacing(); - for (int i = region.min_row; i < region.max_row; ++i) { - const int model_index = ViewToModel(i); + for (size_t i = region.min_row; i < region.max_row; ++i) { + const size_t model_index = ViewToModel(i); const bool is_selected = selection_model_.IsSelected(model_index); if (is_selected) canvas->FillRect(GetRowBounds(i), selected_bg_color); else if (alternate_bg_color != default_bg_color && (i % 2)) canvas->FillRect(GetRowBounds(i), alternate_bg_color); - for (int j = region.min_column; j < region.max_column; ++j) { - const gfx::Rect cell_bounds(GetCellBounds(i, j)); - int text_x = cell_margin + cell_bounds.x(); + for (size_t j = region.min_column; j < region.max_column; ++j) { + const gfx::Rect cell_bounds = GetCellBounds(i, j); + gfx::Rect text_bounds = cell_bounds; + text_bounds.Inset(gfx::Insets::VH(0, cell_margin)); // Provide space for the grouping indicator, but draw it separately. - if (j == 0 && grouper_) - text_x += kGroupingIndicatorSize + cell_element_spacing; + if (j == 0 && grouper_) { + text_bounds.Inset(gfx::Insets().set_left(kGroupingIndicatorSize + + cell_element_spacing)); + } // Always paint the icon in the first visible column. if (j == 0 && table_type_ == ICON_AND_TEXT) { gfx::ImageSkia image = model_->GetIcon(model_index).Rasterize(GetColorProvider()); if (!image.isNull()) { - int image_x = - GetMirroredXWithWidthInView(text_x, ui::TableModel::kIconSize); + int image_x = GetMirroredXWithWidthInView(text_bounds.x(), + ui::TableModel::kIconSize); canvas->DrawImageInt( image, 0, 0, image.width(), image.height(), image_x, cell_bounds.y() + (cell_bounds.height() - ui::TableModel::kIconSize) / 2, ui::TableModel::kIconSize, ui::TableModel::kIconSize, true); } - text_x += ui::TableModel::kIconSize + cell_element_spacing; + text_bounds.Inset(gfx::Insets().set_left(ui::TableModel::kIconSize + + cell_element_spacing)); } - if (text_x < cell_bounds.right() - cell_margin) { + + // Paint text if there is still room for it after all that insetting. + if (!text_bounds.IsEmpty()) { canvas->DrawStringRectWithFlags( model_->GetText(model_index, visible_columns_[j].column.id), font_list_, is_selected ? selected_fg_color : fg_color, - gfx::Rect(GetMirroredXWithWidthInView( - text_x, cell_bounds.right() - text_x - cell_margin), - cell_bounds.y(), cell_bounds.right() - text_x, - row_height_), + GetMirroredRect(text_bounds), TableColumnAlignmentToCanvasAlignment( - visible_columns_[j].column.alignment)); + GetMirroredTableColumnAlignment( + visible_columns_[j].column.alignment))); } } } @@ -1029,15 +1041,15 @@ void TableView::OnPaintImpl(gfx::Canvas* canvas) { grouping_flags.setAntiAlias(true); const int group_indicator_x = GetMirroredXInView( GetCellBounds(0, 0).x() + cell_margin + kGroupingIndicatorSize / 2); - for (int i = region.min_row; i < region.max_row;) { - const int model_index = ViewToModel(i); + for (size_t i = region.min_row; i < region.max_row;) { + const size_t model_index = ViewToModel(i); GroupRange range; grouper_->GetGroupRange(model_index, &range); - DCHECK_GT(range.length, 0); + DCHECK_GT(range.length, 0u); // The order of rows in a group is consistent regardless of sort, so it's ok // to do this calculation. - const int start = i - (model_index - range.start); - const int last = start + range.length - 1; + const size_t start = i - (model_index - range.start); + const size_t last = start + range.length - 1; const gfx::RectF start_cell_bounds(GetCellBounds(start, 0)); const gfx::RectF last_cell_bounds(GetCellBounds(last, 0)); canvas->DrawLine( @@ -1060,7 +1072,7 @@ int TableView::GetCellElementSpacing() const { void TableView::SortItemsAndUpdateMapping(bool schedule_paint) { - const int row_count = GetRowCount(); + const size_t row_count = GetRowCount(); if (!GetIsSorted()) { view_to_model_.clear(); @@ -1070,7 +1082,7 @@ void TableView::SortItemsAndUpdateMapping(bool schedule_paint) { model_to_view_.resize(row_count); // Resets the mapping so it can be sorted again. - for (int view_index = 0; view_index < row_count; ++view_index) + for (size_t view_index = 0; view_index < row_count; ++view_index) view_to_model_[view_index] = view_index; if (grouper_) { @@ -1084,7 +1096,7 @@ void TableView::SortItemsAndUpdateMapping(bool schedule_paint) { SortHelper(this)); } - for (int view_index = 0; view_index < row_count; ++view_index) + for (size_t view_index = 0; view_index < row_count; ++view_index) model_to_view_[view_to_model_[view_index]] = view_index; model_->ClearCollator(); @@ -1096,7 +1108,7 @@ void TableView::SortItemsAndUpdateMapping(bool schedule_paint) { SchedulePaint(); } -int TableView::CompareRows(int model_row1, int model_row2) { +int TableView::CompareRows(size_t model_row1, size_t model_row2) { const int sort_result = model_->CompareValues(model_row1, model_row2, sort_descriptors_[0].column_id); if (sort_result == 0 && sort_descriptors_.size() > 1) { @@ -1109,25 +1121,28 @@ int TableView::CompareRows(int model_row1, int model_row2) { return SwapCompareResult(sort_result, sort_descriptors_[0].ascending); } -gfx::Rect TableView::GetRowBounds(int row) const { - return gfx::Rect(0, row * row_height_, width(), row_height_); +gfx::Rect TableView::GetRowBounds(size_t row) const { + return gfx::Rect(0, static_cast<int>(row) * row_height_, width(), + row_height_); } -gfx::Rect TableView::GetCellBounds(int row, int visible_column_index) const { +gfx::Rect TableView::GetCellBounds(size_t row, + size_t visible_column_index) const { if (!header_) return GetRowBounds(row); const VisibleColumn& vis_col(visible_columns_[visible_column_index]); - return gfx::Rect(vis_col.x, row * row_height_, vis_col.width, row_height_); + return gfx::Rect(vis_col.x, static_cast<int>(row) * row_height_, + vis_col.width, row_height_); } gfx::Rect TableView::GetActiveCellBounds() const { - if (selection_model_.active() == ui::ListSelectionModel::kUnselectedIndex) + if (!selection_model_.active().has_value()) return gfx::Rect(); - return GetCellBounds(ModelToView(selection_model_.active()), - active_visible_column_index_); + return GetCellBounds(ModelToView(selection_model_.active().value()), + active_visible_column_index_.value()); } -void TableView::AdjustCellBoundsForText(int visible_column_index, +void TableView::AdjustCellBoundsForText(size_t visible_column_index, gfx::Rect* bounds) const { const int cell_margin = GetCellMargin(); const int cell_element_spacing = GetCellElementSpacing(); @@ -1191,8 +1206,10 @@ TableView::PaintRegion TableView::GetPaintRegion( DCHECK(GetRowCount()); PaintRegion region; - region.min_row = base::clamp(bounds.y() / row_height_, 0, GetRowCount() - 1); - region.max_row = bounds.bottom() / row_height_; + region.min_row = static_cast<size_t>( + base::clamp(bounds.y() / row_height_, 0, + base::saturated_cast<int>(GetRowCount() - 1))); + region.max_row = static_cast<size_t>(bounds.bottom() / row_height_); if (bounds.bottom() % row_height_ != 0) region.max_row++; region.max_row = std::min(region.max_row, GetRowCount()); @@ -1204,13 +1221,13 @@ TableView::PaintRegion TableView::GetPaintRegion( const int paint_x = GetMirroredXForRect(bounds); const int paint_max_x = paint_x + bounds.width(); - region.min_column = -1; - region.max_column = visible_columns_.size(); + region.min_column = region.max_column = visible_columns_.size(); for (size_t i = 0; i < visible_columns_.size(); ++i) { int max_x = visible_columns_[i].x + visible_columns_[i].width; - if (region.min_column == -1 && max_x >= paint_x) - region.min_column = static_cast<int>(i); - if (region.min_column != -1 && visible_columns_[i].x >= paint_max_x) { + if (region.min_column == visible_columns_.size() && max_x >= paint_x) + region.min_column = i; + if (region.min_column != visible_columns_.size() && + visible_columns_[i].x >= paint_max_x) { region.max_column = i; break; } @@ -1227,12 +1244,14 @@ gfx::Rect TableView::GetPaintBounds(gfx::Canvas* canvas) const { void TableView::SchedulePaintForSelection() { if (selection_model_.size() == 1) { - const int first_model_row = GetFirstSelectedRow(); - SchedulePaintInRect(GetRowBounds(ModelToView(first_model_row))); + const absl::optional<size_t> first_model_row = GetFirstSelectedRow(); + SchedulePaintInRect(GetRowBounds(ModelToView(first_model_row.value()))); - const int active_row = selection_model_.active(); - if (active_row >= 0 && first_model_row != active_row) - SchedulePaintInRect(GetRowBounds(ModelToView(active_row))); + if (selection_model_.active().has_value() && + first_model_row != selection_model_.active().value()) { + SchedulePaintInRect( + GetRowBounds(ModelToView(selection_model_.active().value()))); + } } else if (selection_model_.size() > 1) { SchedulePaint(); } @@ -1247,39 +1266,40 @@ ui::TableColumn TableView::FindColumnByID(int id) const { void TableView::AdvanceActiveVisibleColumn(AdvanceDirection direction) { if (visible_columns_.empty()) { - SetActiveVisibleColumnIndex(-1); + SetActiveVisibleColumnIndex(absl::nullopt); return; } - if (active_visible_column_index_ == -1) { - if (selection_model_.active() == -1 && !header_row_is_active_) - SelectByViewIndex(0); - SetActiveVisibleColumnIndex(0); + if (!active_visible_column_index_.has_value()) { + if (!selection_model_.active().has_value() && !header_row_is_active_) + SelectByViewIndex(size_t{0}); + SetActiveVisibleColumnIndex(size_t{0}); return; } if (direction == AdvanceDirection::kDecrement) { - SetActiveVisibleColumnIndex(std::max(0, active_visible_column_index_ - 1)); - } else { SetActiveVisibleColumnIndex( - std::min(static_cast<int>(visible_columns_.size()) - 1, - active_visible_column_index_ + 1)); + std::max(size_t{1}, active_visible_column_index_.value()) - 1); + } else { + SetActiveVisibleColumnIndex(std::min( + visible_columns_.size() - 1, active_visible_column_index_.value() + 1)); } } -int TableView::GetActiveVisibleColumnIndex() const { +absl::optional<size_t> TableView::GetActiveVisibleColumnIndex() const { return active_visible_column_index_; } -void TableView::SetActiveVisibleColumnIndex(int index) { +void TableView::SetActiveVisibleColumnIndex(absl::optional<size_t> index) { if (active_visible_column_index_ == index) return; active_visible_column_index_ = index; - if (selection_model_.active() != ui::ListSelectionModel::kUnselectedIndex && - active_visible_column_index_ != -1) { - ScrollRectToVisible(GetCellBounds(ModelToView(selection_model_.active()), - active_visible_column_index_)); + if (selection_model_.active().has_value() && + active_visible_column_index_.has_value()) { + ScrollRectToVisible( + GetCellBounds(ModelToView(selection_model_.active().value()), + active_visible_column_index_.value())); } UpdateFocusRings(); @@ -1287,12 +1307,12 @@ void TableView::SetActiveVisibleColumnIndex(int index) { OnPropertyChanged(&active_visible_column_index_, kPropertyEffectsNone); } -void TableView::SelectByViewIndex(int view_index) { +void TableView::SelectByViewIndex(absl::optional<size_t> view_index) { ui::ListSelectionModel new_selection; - if (view_index != -1) { - SelectRowsInRangeFrom(view_index, true, &new_selection); - new_selection.set_anchor(ViewToModel(view_index)); - new_selection.set_active(ViewToModel(view_index)); + if (view_index.has_value()) { + SelectRowsInRangeFrom(view_index.value(), true, &new_selection); + new_selection.set_anchor(ViewToModel(view_index.value())); + new_selection.set_active(ViewToModel(view_index.value())); } SetSelectionModel(std::move(new_selection)); @@ -1307,9 +1327,9 @@ void TableView::SetSelectionModel(ui::ListSelectionModel new_selection) { SchedulePaintForSelection(); // Scroll the group for the active item to visible. - if (selection_model_.active() != -1) { + if (selection_model_.active().has_value()) { gfx::Rect vis_rect(GetVisibleBounds()); - const GroupRange range(GetGroupRange(selection_model_.active())); + const GroupRange range(GetGroupRange(selection_model_.active().value())); const int start_y = GetRowBounds(ModelToView(range.start)).y(); const int end_y = GetRowBounds(ModelToView(range.start + range.length - 1)).bottom(); @@ -1317,10 +1337,10 @@ void TableView::SetSelectionModel(ui::ListSelectionModel new_selection) { vis_rect.set_height(end_y - start_y); ScrollRectToVisible(vis_rect); - if (active_visible_column_index_ == -1) - SetActiveVisibleColumnIndex(0); + if (!active_visible_column_index_.has_value()) + SetActiveVisibleColumnIndex(size_t{0}); } else if (!header_row_is_active_) { - SetActiveVisibleColumnIndex(-1); + SetActiveVisibleColumnIndex(absl::nullopt); } UpdateFocusRings(); @@ -1330,34 +1350,39 @@ void TableView::SetSelectionModel(ui::ListSelectionModel new_selection) { } void TableView::AdvanceSelection(AdvanceDirection direction) { - if (selection_model_.active() == -1) { + if (!selection_model_.active().has_value()) { bool make_header_active = header_ && direction == AdvanceDirection::kDecrement; header_row_is_active_ = make_header_active; - SelectByViewIndex(make_header_active ? -1 : 0); + SelectByViewIndex(make_header_active ? absl::nullopt + : absl::make_optional(size_t{0})); UpdateFocusRings(); ScheduleUpdateAccessibilityFocusIfNeeded(); return; } - int view_index = ModelToView(selection_model_.active()); + size_t view_index = ModelToView(selection_model_.active().value()); if (direction == AdvanceDirection::kDecrement) { bool make_header_active = header_ && view_index == 0; header_row_is_active_ = make_header_active; - view_index = make_header_active ? -1 : std::max(0, view_index - 1); + SelectByViewIndex( + make_header_active + ? absl::nullopt + : absl::make_optional(std::max(size_t{1}, view_index) - 1)); } else { header_row_is_active_ = false; - view_index = std::min(GetRowCount() - 1, view_index + 1); + SelectByViewIndex(std::min(GetRowCount() - 1, view_index + 1)); } - SelectByViewIndex(view_index); } void TableView::ConfigureSelectionModelForEvent( const ui::LocatedEvent& event, ui::ListSelectionModel* model) const { - const int view_index = event.y() / row_height_; - DCHECK(view_index >= 0 && view_index < GetRowCount()); + const int view_index_int = event.y() / row_height_; + DCHECK_GE(view_index_int, 0); + const size_t view_index = static_cast<size_t>(view_index_int); + DCHECK_LT(view_index, GetRowCount()); - if (selection_model_.anchor() == -1 || single_selection_ || + if (!selection_model_.anchor().has_value() || single_selection_ || (!IsCmdOrCtrl(event) && !event.IsShiftDown())) { SelectRowsInRangeFrom(view_index, true, model); model->set_anchor(ViewToModel(view_index)); @@ -1373,11 +1398,11 @@ void TableView::ConfigureSelectionModelForEvent( *model = selection_model_; else model->set_anchor(selection_model_.anchor()); - for (int i = std::min(view_index, ModelToView(model->anchor())), - end = std::max(view_index, ModelToView(model->anchor())); - i <= end; ++i) { + DCHECK(model->anchor().has_value()); + const size_t anchor_index = ModelToView(model->anchor().value()); + const auto [min, max] = std::minmax(view_index, anchor_index); + for (size_t i = min; i <= max; ++i) SelectRowsInRangeFrom(i, true, model); - } model->set_active(ViewToModel(view_index)); } else { DCHECK(IsCmdOrCtrl(event)); @@ -1391,11 +1416,11 @@ void TableView::ConfigureSelectionModelForEvent( } } -void TableView::SelectRowsInRangeFrom(int view_index, +void TableView::SelectRowsInRangeFrom(size_t view_index, bool select, ui::ListSelectionModel* model) const { const GroupRange range(GetGroupRange(ViewToModel(view_index))); - for (int i = 0; i < range.length; ++i) { + for (size_t i = 0; i < range.length; ++i) { if (select) model->AddIndexToSelection(range.start + i); else @@ -1403,7 +1428,7 @@ void TableView::SelectRowsInRangeFrom(int view_index, } } -GroupRange TableView::GetGroupRange(int model_index) const { +GroupRange TableView::GetGroupRange(size_t model_index) const { GroupRange range; if (grouper_) { grouper_->GetGroupRange(model_index, &range); @@ -1426,7 +1451,7 @@ void TableView::RebuildVirtualAccessibilityChildren() { // Create a virtual accessibility view for each row. At this point on, the // table has no sort behavior, hence the view index is the same as the model // index, the sorting will happen at the end. - for (int index = 0; index < GetRowCount(); ++index) + for (size_t index = 0; index < GetRowCount(); ++index) GetViewAccessibility().AddVirtualChildView( CreateRowAccessibilityView(index)); @@ -1439,7 +1464,7 @@ void TableView::ClearVirtualAccessibilityChildren() { } std::unique_ptr<AXVirtualView> TableView::CreateRowAccessibilityView( - int row_index) { + size_t row_index) { auto ax_row = std::make_unique<AXVirtualView>(); ui::AXNodeData& row_data = ax_row->GetCustomData(); @@ -1475,7 +1500,7 @@ std::unique_ptr<AXVirtualView> TableView::CreateRowAccessibilityView( } std::unique_ptr<AXVirtualView> TableView::CreateCellAccessibilityView( - int row_index, + size_t row_index, size_t column_index) { const VisibleColumn& visible_column = visible_columns_[column_index]; const ui::TableColumn column = visible_column.column; @@ -1530,8 +1555,7 @@ void TableView::PopulateAccessibilityRowData(AXVirtualView* ax_row, DCHECK(ax_index.has_value()); size_t row_index = ax_index.value() - (header_ ? 1 : 0); - int model_index = ViewToModel(static_cast<int>(row_index)); - DCHECK_GE(model_index, 0); + size_t model_index = ViewToModel(row_index); // When navigating using up / down cursor keys on the Mac, we read the // contents of the first cell. If the user needs to explore additional cell's, @@ -1560,8 +1584,7 @@ void TableView::PopulateAccessibilityCellData(AXVirtualView* ax_cell, auto column_index = ax_row->GetIndexOf(ax_cell); DCHECK(column_index.has_value()); - int model_index = ViewToModel(static_cast<int>(row_index)); - DCHECK_GE(model_index, 0); + size_t model_index = ViewToModel(row_index); gfx::Rect cell_bounds = GetCellBounds(row_index, column_index.value()); @@ -1569,7 +1592,7 @@ void TableView::PopulateAccessibilityCellData(AXVirtualView* ax_cell, data->AddState(ax::mojom::State::kInvisible); if (PlatformStyle::kTableViewSupportsKeyboardNavigationByCell && - static_cast<int>(column_index.value()) == GetActiveVisibleColumnIndex()) { + column_index.value() == GetActiveVisibleColumnIndex()) { if (selection_model().IsSelected(model_index)) data->AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true); } @@ -1695,7 +1718,7 @@ void TableView::UpdateVirtualAccessibilityChildrenBounds() { } // Update the bounds for the table's content rows. - for (int row_index = 0; row_index < GetRowCount(); row_index++) { + for (size_t row_index = 0; row_index < GetRowCount(); row_index++) { const size_t ax_row_index = header_ ? row_index + 1 : row_index; if (ax_row_index >= virtual_children.size()) break; @@ -1734,7 +1757,7 @@ gfx::Rect TableView::CalculateHeaderRowAccessibilityBounds() const { } gfx::Rect TableView::CalculateHeaderCellAccessibilityBounds( - const int visible_column_index) const { + const size_t visible_column_index) const { const gfx::Rect& header_bounds = CalculateHeaderRowAccessibilityBounds(); const VisibleColumn& visible_column = visible_columns_[visible_column_index]; gfx::Rect header_cell_bounds(visible_column.x, header_bounds.y(), @@ -1743,14 +1766,14 @@ gfx::Rect TableView::CalculateHeaderCellAccessibilityBounds( } gfx::Rect TableView::CalculateTableRowAccessibilityBounds( - const int row_index) const { + const size_t row_index) const { gfx::Rect row_bounds = GetRowBounds(row_index); return row_bounds; } gfx::Rect TableView::CalculateTableCellAccessibilityBounds( - const int row_index, - const int visible_column_index) const { + const size_t row_index, + const size_t visible_column_index) const { gfx::Rect cell_bounds = GetCellBounds(row_index, visible_column_index); return cell_bounds; } @@ -1777,14 +1800,14 @@ void TableView::UpdateAccessibilityFocus( if (header_ && header_row_is_active_) { AXVirtualView* ax_header_row = GetVirtualAccessibilityHeaderRow(); if (!PlatformStyle::kTableViewSupportsKeyboardNavigationByCell || - active_visible_column_index_ == -1) { + !active_visible_column_index_.has_value()) { if (ax_header_row) { ax_header_row->NotifyAccessibilityEvent(ax::mojom::Event::kSelection); GetViewAccessibility().OverrideFocus(ax_header_row); } } else { AXVirtualView* ax_header_cell = GetVirtualAccessibilityCellImpl( - ax_header_row, active_visible_column_index_); + ax_header_row, active_visible_column_index_.value()); if (ax_header_cell) { ax_header_cell->NotifyAccessibilityEvent(ax::mojom::Event::kSelection); GetViewAccessibility().OverrideFocus(ax_header_cell); @@ -1793,13 +1816,13 @@ void TableView::UpdateAccessibilityFocus( return; } - if (selection_model_.active() == ui::ListSelectionModel::kUnselectedIndex || - active_visible_column_index_ == -1) { + if (!selection_model_.active().has_value() || + !active_visible_column_index_.has_value()) { GetViewAccessibility().OverrideFocus(nullptr); return; } - int active_row = ModelToView(selection_model_.active()); + size_t active_row = ModelToView(selection_model_.active().value()); AXVirtualView* ax_row = GetVirtualAccessibilityBodyRow(active_row); if (!PlatformStyle::kTableViewSupportsKeyboardNavigationByCell) { if (ax_row) { @@ -1807,8 +1830,8 @@ void TableView::UpdateAccessibilityFocus( GetViewAccessibility().OverrideFocus(ax_row); } } else { - AXVirtualView* ax_cell = - GetVirtualAccessibilityCellImpl(ax_row, active_visible_column_index_); + AXVirtualView* ax_cell = GetVirtualAccessibilityCellImpl( + ax_row, active_visible_column_index_.value()); if (ax_cell) { ax_cell->NotifyAccessibilityEvent(ax::mojom::Event::kSelection); GetViewAccessibility().OverrideFocus(ax_cell); @@ -1816,13 +1839,11 @@ void TableView::UpdateAccessibilityFocus( } } -AXVirtualView* TableView::GetVirtualAccessibilityBodyRow(int row) { - DCHECK_GE(row, 0); +AXVirtualView* TableView::GetVirtualAccessibilityBodyRow(size_t row) { DCHECK_LT(row, GetRowCount()); if (header_) ++row; - if (static_cast<size_t>(row) < - GetViewAccessibility().virtual_children().size()) { + if (row < GetViewAccessibility().virtual_children().size()) { const auto& ax_row = GetViewAccessibility().virtual_children()[row]; DCHECK(ax_row); DCHECK_EQ(ax_row->GetData().role, ax::mojom::Role::kRow); @@ -1847,23 +1868,23 @@ AXVirtualView* TableView::GetVirtualAccessibilityHeaderRow() { } AXVirtualView* TableView::GetVirtualAccessibilityCell( - int row, - int visible_column_index) { + size_t row, + size_t visible_column_index) { return GetVirtualAccessibilityCellImpl(GetVirtualAccessibilityBodyRow(row), visible_column_index); } AXVirtualView* TableView::GetVirtualAccessibilityCellImpl( AXVirtualView* ax_row, - int visible_column_index) { + size_t visible_column_index) { DCHECK(ax_row) << "|row| not found. Did you forget to call " "RebuildVirtualAccessibilityChildren()?"; const auto matches_index = [visible_column_index](const auto& ax_cell) { DCHECK(ax_cell); DCHECK(ax_cell->GetData().role == ax::mojom::Role::kColumnHeader || ax_cell->GetData().role == ax::mojom::Role::kCell); - return ax_cell->GetData().GetIntAttribute( - ax::mojom::IntAttribute::kTableCellColumnIndex) == + return base::checked_cast<size_t>(ax_cell->GetData().GetIntAttribute( + ax::mojom::IntAttribute::kTableCellColumnIndex)) == visible_column_index; }; const auto i = std::find_if(ax_row->children().cbegin(), @@ -1875,10 +1896,10 @@ AXVirtualView* TableView::GetVirtualAccessibilityCellImpl( } BEGIN_METADATA(TableView, View) -ADD_READONLY_PROPERTY_METADATA(int, RowCount) -ADD_READONLY_PROPERTY_METADATA(int, FirstSelectedRow) +ADD_READONLY_PROPERTY_METADATA(size_t, RowCount) +ADD_READONLY_PROPERTY_METADATA(absl::optional<size_t>, FirstSelectedRow) ADD_READONLY_PROPERTY_METADATA(bool, HasFocusIndicator) -ADD_PROPERTY_METADATA(int, ActiveVisibleColumnIndex) +ADD_PROPERTY_METADATA(absl::optional<size_t>, ActiveVisibleColumnIndex) ADD_READONLY_PROPERTY_METADATA(bool, IsSorted) ADD_PROPERTY_METADATA(TableViewObserver*, Observer) ADD_READONLY_PROPERTY_METADATA(int, RowHeight) diff --git a/chromium/ui/views/controls/table/table_view.h b/chromium/ui/views/controls/table/table_view.h index 726353c5e4b..aafba0fd39e 100644 --- a/chromium/ui/views/controls/table/table_view.h +++ b/chromium/ui/views/controls/table/table_view.h @@ -151,16 +151,16 @@ class VIEWS_EXPORT TableView : public views::View, void SetGrouper(TableGrouper* grouper); // Returns the number of rows in the TableView. - int GetRowCount() const; + size_t GetRowCount() const; // Selects the specified item, making sure it's visible. - void Select(int model_row); + void Select(absl::optional<size_t> model_row); // Selects all items. void SetSelectionAll(bool select); // Returns the first selected row in terms of the model. - int GetFirstSelectedRow() const; + absl::optional<size_t> GetFirstSelectedRow() const; const ui::ListSelectionModel& selection_model() const { return selection_model_; @@ -187,18 +187,18 @@ class VIEWS_EXPORT TableView : public views::View, void SetObserver(TableViewObserver* observer); TableViewObserver* GetObserver() const; - int GetActiveVisibleColumnIndex() const; + absl::optional<size_t> GetActiveVisibleColumnIndex() const; - void SetActiveVisibleColumnIndex(int index); + void SetActiveVisibleColumnIndex(absl::optional<size_t> index); const std::vector<VisibleColumn>& visible_columns() const { return visible_columns_; } - const VisibleColumn& GetVisibleColumn(int index); + const VisibleColumn& GetVisibleColumn(size_t index); // Sets the width of the column. |index| is in terms of |visible_columns_|. - void SetVisibleColumnWidth(int index, int width); + void SetVisibleColumnWidth(size_t index, int width); // Modify the table sort order, depending on a clicked column and the previous // table sort order. Does nothing if this column is not sortable. @@ -207,17 +207,17 @@ class VIEWS_EXPORT TableView : public views::View, // cycle through three states in order: sorted -> reverse-sorted -> unsorted. // When switching from one sort column to another, the previous sort column // will be remembered and used as a secondary sort key. - void ToggleSortOrder(int visible_column_index); + void ToggleSortOrder(size_t visible_column_index); const SortDescriptors& sort_descriptors() const { return sort_descriptors_; } void SetSortDescriptors(const SortDescriptors& descriptors); bool GetIsSorted() const { return !sort_descriptors_.empty(); } // Maps from the index in terms of the model to that of the view. - int ModelToView(int model_index) const; + size_t ModelToView(size_t model_index) const; // Maps from the index in terms of the view to that of the model. - int ViewToModel(int view_index) const; + size_t ViewToModel(size_t view_index) const; int GetRowHeight() const { return row_height_; } @@ -247,7 +247,8 @@ class VIEWS_EXPORT TableView : public views::View, // Returns the virtual accessibility view corresponding to the specified cell. // |row| should be a view index, not a model index. // |visible_column_index| indexes into |visible_columns_|. - AXVirtualView* GetVirtualAccessibilityCell(int row, int visible_column_index); + AXVirtualView* GetVirtualAccessibilityCell(size_t row, + size_t visible_column_index); bool header_row_is_active() const { return header_row_is_active_; } @@ -265,10 +266,10 @@ class VIEWS_EXPORT TableView : public views::View, // ui::TableModelObserver overrides: void OnModelChanged() override; - void OnItemsChanged(int start, int length) override; - void OnItemsAdded(int start, int length) override; - void OnItemsRemoved(int start, int length) override; - void OnItemsMoved(int old_start, int length, int new_start) override; + void OnItemsChanged(size_t start, size_t length) override; + void OnItemsAdded(size_t start, size_t length) override; + void OnItemsRemoved(size_t start, size_t length) override; + void OnItemsMoved(size_t old_start, size_t length, size_t new_start) override; protected: // View overrides: @@ -292,10 +293,10 @@ class VIEWS_EXPORT TableView : public views::View, PaintRegion(); ~PaintRegion(); - int min_row = 0; - int max_row = 0; - int min_column = 0; - int max_column = 0; + size_t min_row = 0; + size_t max_row = 0; + size_t min_column = 0; + size_t max_column = 0; }; void OnPaintImpl(gfx::Canvas* canvas); @@ -317,14 +318,14 @@ class VIEWS_EXPORT TableView : public views::View, // Used to sort the two rows. Returns a value < 0, == 0 or > 0 indicating // whether the row2 comes before row1, row2 is the same as row1 or row1 comes // after row2. This invokes CompareValues on the model with the sorted column. - int CompareRows(int model_row1, int model_row2); + int CompareRows(size_t model_row1, size_t model_row2); // Returns the bounds of the specified row. - gfx::Rect GetRowBounds(int row) const; + gfx::Rect GetRowBounds(size_t row) const; // Returns the bounds of the specified cell. |visible_column_index| indexes // into |visible_columns_|. - gfx::Rect GetCellBounds(int row, int visible_column_index) const; + gfx::Rect GetCellBounds(size_t row, size_t visible_column_index) const; // Returns the bounds of the active cell. gfx::Rect GetActiveCellBounds() const; @@ -332,7 +333,7 @@ class VIEWS_EXPORT TableView : public views::View, // Adjusts |bounds| based on where the text should be painted. |bounds| comes // from GetCellBounds() and |visible_column_index| is the corresponding column // (in terms of |visible_columns_|). - void AdjustCellBoundsForText(int visible_column_index, + void AdjustCellBoundsForText(size_t visible_column_index, gfx::Rect* bounds) const; // Creates |header_| if necessary. @@ -360,7 +361,7 @@ class VIEWS_EXPORT TableView : public views::View, void AdvanceActiveVisibleColumn(AdvanceDirection direction); // Sets the selection to the specified index (in terms of the view). - void SelectByViewIndex(int view_index); + void SelectByViewIndex(absl::optional<size_t> view_index); // Sets the selection model to |new_selection|. void SetSelectionModel(ui::ListSelectionModel new_selection); @@ -375,14 +376,14 @@ class VIEWS_EXPORT TableView : public views::View, // Set the selection state of row at |view_index| to |select|, additionally // any other rows in the GroupRange containing |view_index| are updated as // well. This does not change the anchor or active index of |model|. - void SelectRowsInRangeFrom(int view_index, + void SelectRowsInRangeFrom(size_t view_index, bool select, ui::ListSelectionModel* model) const; // Returns the range of the specified model index. If a TableGrouper has not // been set this returns a group with a start of |model_index| and length of // 1. - GroupRange GetGroupRange(int model_index) const; + GroupRange GetGroupRange(size_t model_index) const; // Updates a set of accessibility views that expose the visible table contents // to assistive software. @@ -399,11 +400,11 @@ class VIEWS_EXPORT TableView : public views::View, // cell's. gfx::Rect CalculateHeaderRowAccessibilityBounds() const; gfx::Rect CalculateHeaderCellAccessibilityBounds( - const int visible_column_index) const; - gfx::Rect CalculateTableRowAccessibilityBounds(const int row_index) const; + const size_t visible_column_index) const; + gfx::Rect CalculateTableRowAccessibilityBounds(const size_t row_index) const; gfx::Rect CalculateTableCellAccessibilityBounds( - const int row_index, - const int visible_column_index) const; + const size_t row_index, + const size_t visible_column_index) const; // Schedule a future call UpdateAccessibilityFocus if not already pending. void ScheduleUpdateAccessibilityFocusIfNeeded(); @@ -431,7 +432,7 @@ class VIEWS_EXPORT TableView : public views::View, // Returns the virtual accessibility view corresponding to the specified row. // |row| should be a view index into the TableView's body elements, not a // model index. - AXVirtualView* GetVirtualAccessibilityBodyRow(int row); + AXVirtualView* GetVirtualAccessibilityBodyRow(size_t row); // Returns the virtual accessibility view corresponding to the header row, if // it exists. @@ -442,17 +443,17 @@ class VIEWS_EXPORT TableView : public views::View, // `ax_row` should be the virtual view of either a header or body row. // `visible_column_index` indexes into `visible_columns_`. AXVirtualView* GetVirtualAccessibilityCellImpl(AXVirtualView* ax_row, - int visible_column_index); + size_t visible_column_index); // Creates a virtual accessibility view that is used to expose information // about the row at |view_index| to assistive software. - std::unique_ptr<AXVirtualView> CreateRowAccessibilityView(int view_index); + std::unique_ptr<AXVirtualView> CreateRowAccessibilityView(size_t view_index); // Creates a virtual accessibility view that is used to expose information // about the cell at the provided coordinates |row_index| and |column_index| // to assistive software. std::unique_ptr<AXVirtualView> CreateCellAccessibilityView( - int row_index, + size_t row_index, size_t column_index); // Creates a virtual accessibility view that is used to expose information @@ -487,8 +488,8 @@ class VIEWS_EXPORT TableView : public views::View, std::vector<VisibleColumn> visible_columns_; // The active visible column. Used for keyboard access to functionality such - // as sorting and resizing. -1 if no visible column is active. - int active_visible_column_index_ = -1; + // as sorting and resizing. nullopt if no visible column is active. + absl::optional<size_t> active_visible_column_index_ = absl::nullopt; // The header. This is only created if more than one column is specified or // the first column has a non-empty title. @@ -532,8 +533,8 @@ class VIEWS_EXPORT TableView : public views::View, SortDescriptors sort_descriptors_; // Mappings used when sorted. - std::vector<int> view_to_model_; - std::vector<int> model_to_view_; + std::vector<size_t> view_to_model_; + std::vector<size_t> model_to_view_; raw_ptr<TableGrouper> grouper_ = nullptr; @@ -549,7 +550,7 @@ class VIEWS_EXPORT TableView : public views::View, }; BEGIN_VIEW_BUILDER(VIEWS_EXPORT, TableView, View) -VIEW_BUILDER_PROPERTY(int, ActiveVisibleColumnIndex) +VIEW_BUILDER_PROPERTY(absl::optional<size_t>, ActiveVisibleColumnIndex) VIEW_BUILDER_PROPERTY(const std::vector<ui::TableColumn>&, Columns, std::vector<ui::TableColumn>) diff --git a/chromium/ui/views/controls/table/table_view_unittest.cc b/chromium/ui/views/controls/table/table_view_unittest.cc index 348de27e81c..a2ddafb5f08 100644 --- a/chromium/ui/views/controls/table/table_view_unittest.cc +++ b/chromium/ui/views/controls/table/table_view_unittest.cc @@ -30,6 +30,7 @@ #include "ui/views/style/platform_style.h" #include "ui/views/test/focus_manager_test.h" #include "ui/views/test/views_test_base.h" +#include "ui/views/widget/unique_widget_ptr.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_delegate.h" #include "ui/views/widget/widget_utils.h" @@ -55,7 +56,7 @@ class TableViewTestHelper { size_t visible_col_count() { return table_->visible_columns().size(); } - int GetActiveVisibleColumnIndex() { + absl::optional<size_t> GetActiveVisibleColumnIndex() { return table_->GetActiveVisibleColumnIndex(); } @@ -67,7 +68,7 @@ class TableViewTestHelper { const gfx::FontList& font_list() { return table_->font_list_; } - AXVirtualView* GetVirtualAccessibilityBodyRow(int row) { + AXVirtualView* GetVirtualAccessibilityBodyRow(size_t row) { return table_->GetVirtualAccessibilityBodyRow(row); } @@ -75,17 +76,18 @@ class TableViewTestHelper { return table_->GetVirtualAccessibilityHeaderRow(); } - AXVirtualView* GetVirtualAccessibilityHeaderCell(int visible_column_index) { + AXVirtualView* GetVirtualAccessibilityHeaderCell( + size_t visible_column_index) { return table_->GetVirtualAccessibilityCellImpl( GetVirtualAccessibilityHeaderRow(), visible_column_index); } - AXVirtualView* GetVirtualAccessibilityCell(int row, - int visible_column_index) { + AXVirtualView* GetVirtualAccessibilityCell(size_t row, + size_t visible_column_index) { return table_->GetVirtualAccessibilityCell(row, visible_column_index); } - gfx::Rect GetCellBounds(int row, int visible_column_index) const { + gfx::Rect GetCellBounds(size_t row, size_t visible_column_index) const { return table_->GetCellBounds(row, visible_column_index); } @@ -116,7 +118,7 @@ class TableViewTestHelper { expected_bounds.push_back(header_row); // Generate the bounds for the table rows and cells. - for (int row_index = 0; row_index < table_->GetRowCount(); row_index++) { + for (size_t row_index = 0; row_index < table_->GetRowCount(); row_index++) { auto table_row = std::vector<gfx::Rect>(); gfx::Rect table_row_bounds = table_->CalculateTableRowAccessibilityBounds(row_index); @@ -137,7 +139,9 @@ class TableViewTestHelper { } private: - raw_ptr<TableView> table_; + // TODO(crbug.com/1298696): views_unittests breaks with MTECheckedPtr + // enabled. Triage. + raw_ptr<TableView, DegradeToNoOpWhenMTE> table_; }; namespace { @@ -167,34 +171,34 @@ class TestTableModel2 : public ui::TableModel { TestTableModel2& operator=(const TestTableModel2&) = delete; // Adds a new row at index |row| with values |c1_value| and |c2_value|. - void AddRow(int row, int c1_value, int c2_value); + void AddRow(size_t row, int c1_value, int c2_value); // Adds new rows starting from |row| to |row| + |length| with the value // of |row| times the |value_multiplier|. The |value_multiplier| can be used // to distinguish these rows from the rest. - void AddRows(int row, int length, int value_multiplier); + void AddRows(size_t row, size_t length, int value_multiplier); // Removes the row at index |row|. - void RemoveRow(int row); + void RemoveRow(size_t row); // Removes all the rows starting from |row| to |row| + |length|. - void RemoveRows(int row, int length); + void RemoveRows(size_t row, size_t length); // Changes the values of the row at |row|. - void ChangeRow(int row, int c1_value, int c2_value); + void ChangeRow(size_t row, int c1_value, int c2_value); // Reorders rows in the model. - void MoveRows(int row_from, int length, int row_to); + void MoveRows(size_t row_from, size_t length, size_t row_to); // Allows overriding the tooltip for testing. void SetTooltip(const std::u16string& tooltip); // ui::TableModel: - int RowCount() override; - std::u16string GetText(int row, int column_id) override; - std::u16string GetTooltip(int row) override; + size_t RowCount() override; + std::u16string GetText(size_t row, int column_id) override; + std::u16string GetTooltip(size_t row) override; void SetObserver(ui::TableModelObserver* observer) override; - int CompareValues(int row1, int row2, int column_id) override; + int CompareValues(size_t row1, size_t row2, int column_id) override; private: raw_ptr<ui::TableModelObserver> observer_ = nullptr; @@ -212,8 +216,8 @@ TestTableModel2::TestTableModel2() { AddRow(3, 3, 0); } -void TestTableModel2::AddRow(int row, int c1_value, int c2_value) { - DCHECK(row >= 0 && row <= static_cast<int>(rows_.size())); +void TestTableModel2::AddRow(size_t row, int c1_value, int c2_value) { + DCHECK(row <= rows_.size()); std::vector<int> new_row; new_row.push_back(c1_value); new_row.push_back(c2_value); @@ -222,14 +226,13 @@ void TestTableModel2::AddRow(int row, int c1_value, int c2_value) { observer_->OnItemsAdded(row, 1); } -void TestTableModel2::AddRows(int row, int length, int value_multiplier) { - DCHECK(row >= 0 && length >= 0); +void TestTableModel2::AddRows(size_t row, size_t length, int value_multiplier) { // Do not DCHECK here since we are testing the OnItemsAdded callback. - if (row >= 0 && row <= static_cast<int>(rows_.size())) { - for (int i = row; i < row + length; i++) { + if (row <= rows_.size()) { + for (size_t i = row; i < row + length; i++) { std::vector<int> new_row; - new_row.push_back(i + value_multiplier); - new_row.push_back(i + value_multiplier); + new_row.push_back(static_cast<int>(i) + value_multiplier); + new_row.push_back(static_cast<int>(i) + value_multiplier); rows_.insert(rows_.begin() + i, new_row); } } @@ -238,39 +241,36 @@ void TestTableModel2::AddRows(int row, int length, int value_multiplier) { observer_->OnItemsAdded(row, length); } -void TestTableModel2::RemoveRow(int row) { - DCHECK(row >= 0 && row < static_cast<int>(rows_.size())); +void TestTableModel2::RemoveRow(size_t row) { + DCHECK(row < rows_.size()); rows_.erase(rows_.begin() + row); if (observer_) observer_->OnItemsRemoved(row, 1); } -void TestTableModel2::RemoveRows(int row, int length) { - DCHECK(row >= 0 && length >= 0); - if (row >= 0 && row <= static_cast<int>(rows_.size())) { - rows_.erase(rows_.begin() + row, - rows_.begin() + base::clamp(row + length, 0, - static_cast<int>(rows_.size()))); +void TestTableModel2::RemoveRows(size_t row, size_t length) { + if (row <= rows_.size()) { + rows_.erase( + rows_.begin() + row, + rows_.begin() + base::clamp(row + length, size_t{0}, rows_.size())); } if (observer_ && length > 0) observer_->OnItemsRemoved(row, length); } -void TestTableModel2::ChangeRow(int row, int c1_value, int c2_value) { - DCHECK(row >= 0 && row < static_cast<int>(rows_.size())); +void TestTableModel2::ChangeRow(size_t row, int c1_value, int c2_value) { + DCHECK(row < rows_.size()); rows_[row][0] = c1_value; rows_[row][1] = c2_value; if (observer_) observer_->OnItemsChanged(row, 1); } -void TestTableModel2::MoveRows(int row_from, int length, int row_to) { - DCHECK_GT(length, 0); - DCHECK_GE(row_from, 0); - DCHECK_LE(row_from + length, static_cast<int>(rows_.size())); - DCHECK_GE(row_to, 0); - DCHECK_LE(row_to + length, static_cast<int>(rows_.size())); +void TestTableModel2::MoveRows(size_t row_from, size_t length, size_t row_to) { + DCHECK_GT(length, 0u); + DCHECK_LE(row_from + length, rows_.size()); + DCHECK_LE(row_to + length, rows_.size()); auto old_start = rows_.begin() + row_from; std::vector<std::vector<int>> temp(old_start, old_start + length); @@ -284,15 +284,15 @@ void TestTableModel2::SetTooltip(const std::u16string& tooltip) { tooltip_ = tooltip; } -int TestTableModel2::RowCount() { - return static_cast<int>(rows_.size()); +size_t TestTableModel2::RowCount() { + return rows_.size(); } -std::u16string TestTableModel2::GetText(int row, int column_id) { +std::u16string TestTableModel2::GetText(size_t row, int column_id) { return base::NumberToString16(rows_[row][column_id]); } -std::u16string TestTableModel2::GetTooltip(int row) { +std::u16string TestTableModel2::GetTooltip(size_t row) { return tooltip_ ? *tooltip_ : u"Tooltip" + base::NumberToString16(row); } @@ -300,14 +300,14 @@ void TestTableModel2::SetObserver(ui::TableModelObserver* observer) { observer_ = observer; } -int TestTableModel2::CompareValues(int row1, int row2, int column_id) { +int TestTableModel2::CompareValues(size_t row1, size_t row2, int column_id) { return rows_[row1][column_id] - rows_[row2][column_id]; } // Returns the view to model mapping as a string. std::string GetViewToModelAsString(TableView* table) { std::string result; - for (int i = 0; i < table->GetRowCount(); ++i) { + for (size_t i = 0; i < table->GetRowCount(); ++i) { if (i != 0) result += " "; result += base::NumberToString(table->ViewToModel(i)); @@ -318,7 +318,7 @@ std::string GetViewToModelAsString(TableView* table) { // Returns the model to view mapping as a string. std::string GetModelToViewAsString(TableView* table) { std::string result; - for (int i = 0; i < table->GetRowCount(); ++i) { + for (size_t i = 0; i < table->GetRowCount(); ++i) { if (i != 0) result += " "; result += base::NumberToString(table->ModelToView(i)); @@ -330,7 +330,7 @@ std::string GetModelToViewAsString(TableView* table) { // scrolled out of view are included; hidden columns are excluded. std::string GetRowsInViewOrderAsString(TableView* table) { std::string result; - for (int i = 0; i < table->GetRowCount(); ++i) { + for (size_t i = 0; i < table->GetRowCount(); ++i) { if (i != 0) result += ", "; // Comma between each row. @@ -454,17 +454,16 @@ class TableViewTest : public ViewsTestBase, table_ = table.get(); auto scroll_view = TableView::CreateScrollViewWithTable(std::move(table)); scroll_view->SetBounds(0, 0, 10000, 10000); - scroll_view->Layout(); helper_ = std::make_unique<TableViewTestHelper>(table_); widget_ = std::make_unique<Widget>(); Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS); - params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; params.bounds = gfx::Rect(0, 0, 650, 650); params.delegate = GetWidgetDelegate(widget_.get()); widget_->Init(std::move(params)); - widget_->GetRootView()->AddChildView(std::move(scroll_view)); + RunScheduledLayout( + widget_->GetRootView()->AddChildView(std::move(scroll_view))); widget_->Show(); } @@ -485,25 +484,8 @@ class TableViewTest : public ViewsTestBase, generator.GestureTapAt(GetPointForRow(row)); } - // Returns the state of the selection model as a string. The format is: - // 'active=X anchor=X selection=X X X...'. std::string SelectionStateAsString() const { - const ui::ListSelectionModel& model(table_->selection_model()); - std::string result = "active=" + base::NumberToString(model.active()) + - " anchor=" + base::NumberToString(model.anchor()) + - " selection="; - const ui::ListSelectionModel::SelectedIndices& selection( - model.selected_indices()); - bool first = true; - for (int index : selection) { - if (first) { - first = false; - } else { - result += " "; - } - result += base::NumberToString(index); - } - return result; + return table_->selection_model().ToString(); } void PressKey(ui::KeyboardCode code) { PressKey(code, ui::EF_NONE); } @@ -608,11 +590,14 @@ class TableViewTest : public ViewsTestBase, std::unique_ptr<TestTableModel2> model_; // Owned by |parent_|. - raw_ptr<TableView> table_ = nullptr; + // + // TODO(crbug.com/1298696): views_unittests breaks with MTECheckedPtr + // enabled. Triage. + raw_ptr<TableView, DegradeToNoOpWhenMTE> table_ = nullptr; std::unique_ptr<TableViewTestHelper> helper_; - std::unique_ptr<Widget> widget_; + UniqueWidgetPtr widget_; private: gfx::Point GetPointForRow(int row) { @@ -641,7 +626,7 @@ TEST_P(TableViewTest, RebuildVirtualAccessibilityChildren) { EXPECT_TRUE(data.HasState(ax::mojom::State::kFocusable)); EXPECT_EQ(ax::mojom::Restriction::kReadOnly, data.GetRestriction()); EXPECT_EQ(table_->GetRowCount(), - static_cast<int>( + static_cast<size_t>( data.GetIntAttribute(ax::mojom::IntAttribute::kTableRowCount))); EXPECT_EQ(helper_->visible_col_count(), static_cast<size_t>(data.GetIntAttribute( @@ -664,15 +649,15 @@ TEST_P(TableViewTest, RebuildVirtualAccessibilityChildren) { ax::mojom::IntAttribute::kTableCellColumnIndex)); } - int i = 0; + size_t i = 0; for (auto child_iter = view_accessibility.virtual_children().begin() + 1; i < table_->GetRowCount(); ++child_iter, ++i) { const auto& row = *child_iter; ASSERT_TRUE(row); const ui::AXNodeData& row_data = row->GetData(); EXPECT_EQ(ax::mojom::Role::kRow, row_data.role); - EXPECT_EQ( - i, row_data.GetIntAttribute(ax::mojom::IntAttribute::kTableRowIndex)); + EXPECT_EQ(i, static_cast<size_t>(row_data.GetIntAttribute( + ax::mojom::IntAttribute::kTableRowIndex))); ASSERT_FALSE(row_data.HasState(ax::mojom::State::kInvisible)); ASSERT_EQ(helper_->visible_col_count(), row->children().size()); @@ -681,8 +666,8 @@ TEST_P(TableViewTest, RebuildVirtualAccessibilityChildren) { ASSERT_TRUE(cell); const ui::AXNodeData& cell_data = cell->GetData(); EXPECT_EQ(ax::mojom::Role::kCell, cell_data.role); - EXPECT_EQ(i, cell_data.GetIntAttribute( - ax::mojom::IntAttribute::kTableCellRowIndex)); + EXPECT_EQ(i, static_cast<size_t>(cell_data.GetIntAttribute( + ax::mojom::IntAttribute::kTableCellRowIndex))); EXPECT_EQ(j++, cell_data.GetIntAttribute( ax::mojom::IntAttribute::kTableCellColumnIndex)); ASSERT_FALSE(cell_data.HasState(ax::mojom::State::kInvisible)); @@ -723,26 +708,26 @@ TEST_P(TableViewTest, UpdateVirtualAccessibilityChildrenBoundsHideColumn) { } TEST_P(TableViewTest, GetVirtualAccessibilityBodyRow) { - for (int i = 0; i < table_->GetRowCount(); ++i) { + for (size_t i = 0; i < table_->GetRowCount(); ++i) { const AXVirtualView* row = helper_->GetVirtualAccessibilityBodyRow(i); ASSERT_TRUE(row); const ui::AXNodeData& row_data = row->GetData(); EXPECT_EQ(ax::mojom::Role::kRow, row_data.role); - EXPECT_EQ(i, static_cast<int>(row_data.GetIntAttribute( + EXPECT_EQ(i, static_cast<size_t>(row_data.GetIntAttribute( ax::mojom::IntAttribute::kTableRowIndex))); } } TEST_P(TableViewTest, GetVirtualAccessibilityCell) { - for (int i = 0; i < table_->GetRowCount(); ++i) { - for (int j = 0; j < static_cast<int>(helper_->visible_col_count()); ++j) { + for (size_t i = 0; i < table_->GetRowCount(); ++i) { + for (size_t j = 0; j < helper_->visible_col_count(); ++j) { const AXVirtualView* cell = helper_->GetVirtualAccessibilityCell(i, j); ASSERT_TRUE(cell); const ui::AXNodeData& cell_data = cell->GetData(); EXPECT_EQ(ax::mojom::Role::kCell, cell_data.role); - EXPECT_EQ(i, static_cast<int>(cell_data.GetIntAttribute( + EXPECT_EQ(i, static_cast<size_t>(cell_data.GetIntAttribute( ax::mojom::IntAttribute::kTableCellRowIndex))); - EXPECT_EQ(j, static_cast<int>(cell_data.GetIntAttribute( + EXPECT_EQ(j, static_cast<size_t>(cell_data.GetIntAttribute( ax::mojom::IntAttribute::kTableCellColumnIndex))); } } @@ -860,12 +845,12 @@ TEST_P(TableViewTest, ResizeViaKeyboard) { EXPECT_NE(0, x); // Table starts off with no visible column being active. - ASSERT_EQ(-1, helper_->GetActiveVisibleColumnIndex()); + ASSERT_FALSE(helper_->GetActiveVisibleColumnIndex().has_value()); ui::ListSelectionModel new_selection; new_selection.SetSelectedIndex(1); helper_->SetSelectionModel(new_selection); - ASSERT_EQ(0, helper_->GetActiveVisibleColumnIndex()); + ASSERT_EQ(0u, helper_->GetActiveVisibleColumnIndex()); PressKey(ui::VKEY_LEFT, ui::EF_CONTROL_DOWN); // This should shrink the first column and pull the second column in. @@ -1062,12 +1047,12 @@ TEST_P(TableViewTest, SortOnSpaceBar) { table_->RequestFocus(); ASSERT_TRUE(table_->sort_descriptors().empty()); // Table starts off with no visible column being active. - ASSERT_EQ(-1, helper_->GetActiveVisibleColumnIndex()); + ASSERT_FALSE(helper_->GetActiveVisibleColumnIndex().has_value()); ui::ListSelectionModel new_selection; new_selection.SetSelectedIndex(1); helper_->SetSelectionModel(new_selection); - ASSERT_EQ(0, helper_->GetActiveVisibleColumnIndex()); + ASSERT_EQ(0u, helper_->GetActiveVisibleColumnIndex()); PressKey(ui::VKEY_SPACE); ASSERT_EQ(1u, table_->sort_descriptors().size()); @@ -1080,7 +1065,7 @@ TEST_P(TableViewTest, SortOnSpaceBar) { EXPECT_FALSE(table_->sort_descriptors()[0].ascending); PressKey(ui::VKEY_RIGHT); - ASSERT_EQ(1, helper_->GetActiveVisibleColumnIndex()); + ASSERT_EQ(1u, helper_->GetActiveVisibleColumnIndex()); PressKey(ui::VKEY_SPACE); ASSERT_EQ(2u, table_->sort_descriptors().size()); @@ -1102,15 +1087,15 @@ TEST_P(TableViewTest, ActiveCellBoundsFollowColumnSorting) { table_->ToggleSortOrder(0); ClickOnRow(0, 0); EXPECT_EQ(helper_->GetCellBounds(0, 0), helper_->GetActiveCellBounds()); - EXPECT_EQ(0, table_->ViewToModel(0)); + EXPECT_EQ(0u, table_->ViewToModel(0)); ClickOnRow(1, 0); EXPECT_EQ(helper_->GetCellBounds(1, 0), helper_->GetActiveCellBounds()); - EXPECT_EQ(1, table_->ViewToModel(1)); + EXPECT_EQ(1u, table_->ViewToModel(1)); ClickOnRow(2, 0); EXPECT_EQ(helper_->GetCellBounds(2, 0), helper_->GetActiveCellBounds()); - EXPECT_EQ(2, table_->ViewToModel(2)); + EXPECT_EQ(2u, table_->ViewToModel(2)); // Toggle the sort order of the second column. The active row will stay in // sync with the view index, meanwhile the model's change which shows that @@ -1118,15 +1103,15 @@ TEST_P(TableViewTest, ActiveCellBoundsFollowColumnSorting) { table_->ToggleSortOrder(1); ClickOnRow(0, 0); EXPECT_EQ(helper_->GetCellBounds(0, 0), helper_->GetActiveCellBounds()); - EXPECT_EQ(3, table_->ViewToModel(0)); + EXPECT_EQ(3u, table_->ViewToModel(0)); ClickOnRow(1, 0); EXPECT_EQ(helper_->GetCellBounds(1, 0), helper_->GetActiveCellBounds()); - EXPECT_EQ(0, table_->ViewToModel(1)); + EXPECT_EQ(0u, table_->ViewToModel(1)); ClickOnRow(2, 0); EXPECT_EQ(helper_->GetCellBounds(2, 0), helper_->GetActiveCellBounds()); - EXPECT_EQ(1, table_->ViewToModel(2)); + EXPECT_EQ(1u, table_->ViewToModel(2)); // Verifying invalid active indexes return an empty rect. new_selection.Clear(); @@ -1161,11 +1146,11 @@ class TableGrouperImpl : public TableGrouper { TableGrouperImpl(const TableGrouperImpl&) = delete; TableGrouperImpl& operator=(const TableGrouperImpl&) = delete; - void SetRanges(const std::vector<int>& ranges) { ranges_ = ranges; } + void SetRanges(const std::vector<size_t>& ranges) { ranges_ = ranges; } // TableGrouper overrides: - void GetGroupRange(int model_index, GroupRange* range) override { - int offset = 0; + void GetGroupRange(size_t model_index, GroupRange* range) override { + size_t offset = 0; size_t range_index = 0; for (; range_index < ranges_.size() && offset < model_index; ++range_index) offset += ranges_[range_index]; @@ -1180,7 +1165,7 @@ class TableGrouperImpl : public TableGrouper { } private: - std::vector<int> ranges_; + std::vector<size_t> ranges_; }; } // namespace @@ -1193,10 +1178,7 @@ TEST_P(TableViewTest, Grouping) { // B 2 // 3 TableGrouperImpl grouper; - std::vector<int> ranges; - ranges.push_back(2); - ranges.push_back(2); - grouper.SetRanges(ranges); + grouper.SetRanges({2, 2}); table_->SetGrouper(&grouper); // Toggle the sort order of the first column, shouldn't change anything. @@ -1289,7 +1271,7 @@ TEST_P(TableViewTest, Selection) { table_->set_observer(&observer); // Initially no selection. - EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); + EXPECT_EQ("active=<none> anchor=<none> selection=", SelectionStateAsString()); // Select the last row. table_->Select(3); @@ -1385,11 +1367,12 @@ TEST_P(TableViewTest, SelectAll) { table_->set_observer(&observer); // Initially no selection. - EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); + EXPECT_EQ("active=<none> anchor=<none> selection=", SelectionStateAsString()); table_->SetSelectionAll(/*select=*/true); EXPECT_EQ(1, observer.GetChangedCountAndClear()); - EXPECT_EQ("active=-1 anchor=-1 selection=0 1 2 3", SelectionStateAsString()); + EXPECT_EQ("active=<none> anchor=<none> selection=0 1 2 3", + SelectionStateAsString()); table_->Select(2); EXPECT_EQ(1, observer.GetChangedCountAndClear()); @@ -1452,7 +1435,7 @@ TEST_P(TableViewTest, SelectionNoSelectOnRemove) { table_->SetSelectOnRemove(false); // Initially no selection. - EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); + EXPECT_EQ("active=<none> anchor=<none> selection=", SelectionStateAsString()); // Select row 3. table_->Select(3); @@ -1464,7 +1447,7 @@ TEST_P(TableViewTest, SelectionNoSelectOnRemove) { // selected item, so no item is selected. model_->RemoveRow(3); EXPECT_EQ(1, observer.GetChangedCountAndClear()); - EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); + EXPECT_EQ("active=<none> anchor=<none> selection=", SelectionStateAsString()); // Select row 1. table_->Select(1); @@ -1474,7 +1457,7 @@ TEST_P(TableViewTest, SelectionNoSelectOnRemove) { // Remove the selected row. model_->RemoveRow(1); EXPECT_EQ(1, observer.GetChangedCountAndClear()); - EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); + EXPECT_EQ("active=<none> anchor=<none> selection=", SelectionStateAsString()); // Select row 0. table_->Select(0); @@ -1484,7 +1467,7 @@ TEST_P(TableViewTest, SelectionNoSelectOnRemove) { // Remove the selected row. model_->RemoveRow(0); EXPECT_EQ(1, observer.GetChangedCountAndClear()); - EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); + EXPECT_EQ("active=<none> anchor=<none> selection=", SelectionStateAsString()); table_->set_observer(nullptr); } @@ -1494,7 +1477,7 @@ TEST_P(TableViewTest, SelectionNoSelectOnRemove) { // Verifies selection works by way of a gesture. TEST_P(TableViewTest, SelectOnTap) { // Initially no selection. - EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); + EXPECT_EQ("active=<none> anchor=<none> selection=", SelectionStateAsString()); TableViewObserverImpl observer; table_->set_observer(&observer); @@ -1520,11 +1503,7 @@ TEST_P(TableViewTest, KeyUpDown) { // 3 model_->AddRow(2, 5, 0); TableGrouperImpl grouper; - std::vector<int> ranges; - ranges.push_back(2); - ranges.push_back(1); - ranges.push_back(2); - grouper.SetRanges(ranges); + grouper.SetRanges({2, 1, 2}); table_->SetGrouper(&grouper); TableViewObserverImpl observer; @@ -1532,7 +1511,7 @@ TEST_P(TableViewTest, KeyUpDown) { table_->RequestFocus(); // Initially no selection. - EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); + EXPECT_EQ("active=<none> anchor=<none> selection=", SelectionStateAsString()); PressKey(ui::VKEY_DOWN); EXPECT_EQ(1, observer.GetChangedCountAndClear()); @@ -1579,7 +1558,7 @@ TEST_P(TableViewTest, KeyUpDown) { PressKey(ui::VKEY_UP); EXPECT_TRUE(table_->header_row_is_active()); EXPECT_EQ(1, observer.GetChangedCountAndClear()); - EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); + EXPECT_EQ("active=<none> anchor=<none> selection=", SelectionStateAsString()); PressKey(ui::VKEY_DOWN); EXPECT_EQ(1, observer.GetChangedCountAndClear()); @@ -1596,8 +1575,8 @@ TEST_P(TableViewTest, KeyUpDown) { EXPECT_EQ("2 3 4 0 1", GetViewToModelAsString(table_)); - table_->Select(-1); - EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); + table_->Select(absl::nullopt); + EXPECT_EQ("active=<none> anchor=<none> selection=", SelectionStateAsString()); observer.GetChangedCountAndClear(); @@ -1606,7 +1585,7 @@ TEST_P(TableViewTest, KeyUpDown) { PressKey(ui::VKEY_UP); EXPECT_TRUE(table_->header_row_is_active()); EXPECT_EQ(0, observer.GetChangedCountAndClear()); - EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); + EXPECT_EQ("active=<none> anchor=<none> selection=", SelectionStateAsString()); PressKey(ui::VKEY_DOWN); EXPECT_EQ(1, observer.GetChangedCountAndClear()); @@ -1653,7 +1632,7 @@ TEST_P(TableViewTest, KeyUpDown) { PressKey(ui::VKEY_UP); EXPECT_TRUE(table_->header_row_is_active()); EXPECT_EQ(1, observer.GetChangedCountAndClear()); - EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); + EXPECT_EQ("active=<none> anchor=<none> selection=", SelectionStateAsString()); table_->set_observer(nullptr); } @@ -1668,89 +1647,89 @@ TEST_P(TableViewTest, KeyLeftRight) { table_->RequestFocus(); // Initially no active visible column. - EXPECT_EQ(-1, helper_->GetActiveVisibleColumnIndex()); + EXPECT_FALSE(helper_->GetActiveVisibleColumnIndex().has_value()); PressKey(ui::VKEY_RIGHT); - EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex()); + EXPECT_EQ(0u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(1, observer.GetChangedCountAndClear()); EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString()); helper_->SetSelectionModel(ui::ListSelectionModel()); - EXPECT_EQ(-1, helper_->GetActiveVisibleColumnIndex()); + EXPECT_FALSE(helper_->GetActiveVisibleColumnIndex().has_value()); EXPECT_EQ(1, observer.GetChangedCountAndClear()); PressKey(ui::VKEY_LEFT); - EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex()); + EXPECT_EQ(0u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(1, observer.GetChangedCountAndClear()); EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString()); PressKey(ui::VKEY_RIGHT); - EXPECT_EQ(1, helper_->GetActiveVisibleColumnIndex()); + EXPECT_EQ(1u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(0, observer.GetChangedCountAndClear()); EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString()); PressKey(ui::VKEY_RIGHT); - EXPECT_EQ(1, helper_->GetActiveVisibleColumnIndex()); + EXPECT_EQ(1u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(0, observer.GetChangedCountAndClear()); EXPECT_EQ("active=0 anchor=0 selection=0", SelectionStateAsString()); ui::ListSelectionModel new_selection; new_selection.SetSelectedIndex(1); helper_->SetSelectionModel(new_selection); - EXPECT_EQ(1, helper_->GetActiveVisibleColumnIndex()); + EXPECT_EQ(1u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(1, observer.GetChangedCountAndClear()); EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString()); PressKey(ui::VKEY_LEFT); - EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex()); + EXPECT_EQ(0u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(0, observer.GetChangedCountAndClear()); EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString()); PressKey(ui::VKEY_LEFT); - EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex()); + EXPECT_EQ(0u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(0, observer.GetChangedCountAndClear()); EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString()); table_->SetColumnVisibility(0, false); - EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex()); + EXPECT_EQ(0u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(0, observer.GetChangedCountAndClear()); EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString()); // Since the first column was hidden, the active visible column should not // advance. PressKey(ui::VKEY_RIGHT); - EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex()); + EXPECT_EQ(0u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(0, observer.GetChangedCountAndClear()); EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString()); // If visibility to the first column is restored, the active visible column // should be unchanged because columns are always added to the end. table_->SetColumnVisibility(0, true); - EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex()); + EXPECT_EQ(0u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(0, observer.GetChangedCountAndClear()); EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString()); PressKey(ui::VKEY_RIGHT); - EXPECT_EQ(1, helper_->GetActiveVisibleColumnIndex()); + EXPECT_EQ(1u, helper_->GetActiveVisibleColumnIndex()); // If visibility to the first column is removed, the active visible column // should be decreased by one. table_->SetColumnVisibility(0, false); - EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex()); + EXPECT_EQ(0u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(0, observer.GetChangedCountAndClear()); EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString()); PressKey(ui::VKEY_LEFT); - EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex()); + EXPECT_EQ(0u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(0, observer.GetChangedCountAndClear()); EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString()); table_->SetColumnVisibility(0, true); - EXPECT_EQ(0, helper_->GetActiveVisibleColumnIndex()); + EXPECT_EQ(0u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(0, observer.GetChangedCountAndClear()); EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString()); PressKey(ui::VKEY_RIGHT); - EXPECT_EQ(1, helper_->GetActiveVisibleColumnIndex()); + EXPECT_EQ(1u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(0, observer.GetChangedCountAndClear()); EXPECT_EQ("active=1 anchor=1 selection=1", SelectionStateAsString()); @@ -1767,8 +1746,7 @@ TEST_P(TableViewTest, HomeEnd) { // 3 model_->AddRow(2, 5, 0); TableGrouperImpl grouper; - std::vector<int> ranges{2, 1, 2}; - grouper.SetRanges(ranges); + grouper.SetRanges({2, 1, 2}); table_->SetGrouper(&grouper); TableViewObserverImpl observer; @@ -1776,7 +1754,7 @@ TEST_P(TableViewTest, HomeEnd) { table_->RequestFocus(); // Initially no selection. - EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); + EXPECT_EQ("active=<none> anchor=<none> selection=", SelectionStateAsString()); PressKey(ui::VKEY_HOME); EXPECT_EQ(1, observer.GetChangedCountAndClear()); @@ -1803,15 +1781,11 @@ TEST_P(TableViewTest, Multiselection) { // 3 model_->AddRow(2, 5, 0); TableGrouperImpl grouper; - std::vector<int> ranges; - ranges.push_back(2); - ranges.push_back(1); - ranges.push_back(2); - grouper.SetRanges(ranges); + grouper.SetRanges({2, 1, 2}); table_->SetGrouper(&grouper); // Initially no selection. - EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); + EXPECT_EQ("active=<none> anchor=<none> selection=", SelectionStateAsString()); TableViewObserverImpl observer; table_->set_observer(&observer); @@ -1859,11 +1833,7 @@ TEST_P(TableViewTest, MultiselectionWithSort) { // 3 model_->AddRow(2, 5, 0); TableGrouperImpl grouper; - std::vector<int> ranges; - ranges.push_back(2); - ranges.push_back(1); - ranges.push_back(2); - grouper.SetRanges(ranges); + grouper.SetRanges({2, 1, 2}); table_->SetGrouper(&grouper); // Sort the table descending by column 1, view now looks like: @@ -1876,7 +1846,7 @@ TEST_P(TableViewTest, MultiselectionWithSort) { table_->ToggleSortOrder(0); // Initially no selection. - EXPECT_EQ("active=-1 anchor=-1 selection=", SelectionStateAsString()); + EXPECT_EQ("active=<none> anchor=<none> selection=", SelectionStateAsString()); TableViewObserverImpl observer; table_->set_observer(&observer); @@ -2103,19 +2073,19 @@ TEST_P(TableViewTest, TableHeaderColumnAccessibleViewsFocusable) { // columns. PressKey(ui::VKEY_RIGHT); RunPendingMessages(); - ASSERT_EQ(0, helper_->GetActiveVisibleColumnIndex()); + ASSERT_EQ(0u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(helper_->GetVirtualAccessibilityHeaderCell(0), view_accessibility.FocusedVirtualChild()); PressKey(ui::VKEY_RIGHT); RunPendingMessages(); - ASSERT_EQ(1, helper_->GetActiveVisibleColumnIndex()); + ASSERT_EQ(1u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(helper_->GetVirtualAccessibilityHeaderCell(1), view_accessibility.FocusedVirtualChild()); PressKey(ui::VKEY_LEFT); RunPendingMessages(); - ASSERT_EQ(0, helper_->GetActiveVisibleColumnIndex()); + ASSERT_EQ(0u, helper_->GetActiveVisibleColumnIndex()); EXPECT_EQ(helper_->GetVirtualAccessibilityHeaderCell(0), view_accessibility.FocusedVirtualChild()); } @@ -2213,7 +2183,6 @@ class TableViewDefaultConstructabilityTest : public ViewsTestBase { ViewsTestBase::SetUp(); widget_ = std::make_unique<Widget>(); Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW); - params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; params.bounds = gfx::Rect(0, 0, 650, 650); widget_->Init(std::move(params)); widget_->Show(); @@ -2227,14 +2196,13 @@ class TableViewDefaultConstructabilityTest : public ViewsTestBase { Widget* widget() { return widget_.get(); } private: - std::unique_ptr<Widget> widget_; + UniqueWidgetPtr widget_; }; TEST_F(TableViewDefaultConstructabilityTest, TestFunctionalWithoutModel) { auto scroll_view = TableView::CreateScrollViewWithTable(std::make_unique<TableView>()); scroll_view->SetBounds(0, 0, 10000, 10000); - scroll_view->Layout(); - widget()->GetContentsView()->AddChildView(std::move(scroll_view)); + widget()->client_view()->AddChildView(std::move(scroll_view)); } } // namespace views diff --git a/chromium/ui/views/controls/table/test_table_model.cc b/chromium/ui/views/controls/table/test_table_model.cc index 99642e30e0a..d9b9e186c9b 100644 --- a/chromium/ui/views/controls/table/test_table_model.cc +++ b/chromium/ui/views/controls/table/test_table_model.cc @@ -11,21 +11,21 @@ #include "ui/base/models/table_model_observer.h" #include "ui/gfx/image/image_skia.h" -TestTableModel::TestTableModel(int row_count) +TestTableModel::TestTableModel(size_t row_count) : row_count_(row_count), observer_(nullptr) {} TestTableModel::~TestTableModel() = default; -int TestTableModel::RowCount() { +size_t TestTableModel::RowCount() { return row_count_; } -std::u16string TestTableModel::GetText(int row, int column_id) { +std::u16string TestTableModel::GetText(size_t row, int column_id) { return base::ASCIIToUTF16(base::NumberToString(row) + "x" + base::NumberToString(column_id)); } -ui::ImageModel TestTableModel::GetIcon(int row) { +ui::ImageModel TestTableModel::GetIcon(size_t row) { SkBitmap bitmap; bitmap.setInfo(SkImageInfo::MakeN32Premul(16, 16)); return ui::ImageModel::FromImageSkia( diff --git a/chromium/ui/views/controls/table/test_table_model.h b/chromium/ui/views/controls/table/test_table_model.h index 614489801ef..a3883aa3f79 100644 --- a/chromium/ui/views/controls/table/test_table_model.h +++ b/chromium/ui/views/controls/table/test_table_model.h @@ -10,7 +10,7 @@ class TestTableModel : public ui::TableModel { public: - explicit TestTableModel(int row_count); + explicit TestTableModel(size_t row_count); TestTableModel(const TestTableModel&) = delete; TestTableModel& operator=(const TestTableModel&) = delete; @@ -18,13 +18,13 @@ class TestTableModel : public ui::TableModel { ~TestTableModel() override; // ui::TableModel overrides: - int RowCount() override; - std::u16string GetText(int row, int column_id) override; - ui::ImageModel GetIcon(int row) override; + size_t RowCount() override; + std::u16string GetText(size_t row, int column_id) override; + ui::ImageModel GetIcon(size_t row) override; void SetObserver(ui::TableModelObserver* observer) override; private: - int row_count_; + size_t row_count_; raw_ptr<ui::TableModelObserver> observer_; }; diff --git a/chromium/ui/views/controls/textfield/textfield.cc b/chromium/ui/views/controls/textfield/textfield.cc index 4699148d8f8..a0a5dd7f469 100644 --- a/chromium/ui/views/controls/textfield/textfield.cc +++ b/chromium/ui/views/controls/textfield/textfield.cc @@ -70,11 +70,9 @@ #include "base/win/win_util.h" #endif -// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch -// of lacros-chrome is complete. -#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) +#if BUILDFLAG(IS_LINUX) #include "ui/base/ime/linux/text_edit_command_auralinux.h" -#include "ui/base/ime/linux/text_edit_key_bindings_delegate_auralinux.h" +#include "ui/linux/linux_ui.h" #endif #if BUILDFLAG(IS_CHROMEOS_ASH) @@ -270,15 +268,6 @@ Textfield::~Textfield() { void Textfield::SetAssociatedLabel(View* labelling_view) { DCHECK(labelling_view); GetViewAccessibility().OverrideLabelledBy(labelling_view); - ui::AXNodeData node_data; - labelling_view->GetAccessibleNodeData(&node_data); - // Labelled-by relations are not common practice in native UI, so we also - // set the accessible name for ATs which don't support that. - // TODO(aleventhal) automatically handle setting the name from the related - // label in ViewAccessibility and have it update the name if the text of the - // associated label changes. - SetAccessibleName( - node_data.GetString16Attribute(ax::mojom::StringAttribute::kName)); } void Textfield::SetController(TextfieldController* controller) { @@ -717,13 +706,11 @@ bool Textfield::OnKeyPressed(const ui::KeyEvent& event) { if (!textfield) return handled; -// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch -// of lacros-chrome is complete. -#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) - ui::TextEditKeyBindingsDelegateAuraLinux* delegate = - ui::GetTextEditKeyBindingsDelegate(); +#if BUILDFLAG(IS_LINUX) + auto* linux_ui = ui::LinuxUi::instance(); std::vector<ui::TextEditCommandAuraLinux> commands; - if (!handled && delegate && delegate->MatchEvent(event, &commands)) { + if (!handled && linux_ui && + linux_ui->GetTextEditCommandsForEvent(event, &commands)) { for (const auto& command : commands) { if (IsTextEditCommandEnabled(command.command())) { ExecuteTextEditCommand(command.command()); @@ -863,14 +850,11 @@ void Textfield::AboutToRequestFocusFromTabTraversal(bool reverse) { } bool Textfield::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) { -// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch -// of lacros-chrome is complete. -#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) +#if BUILDFLAG(IS_LINUX) // Skip any accelerator handling that conflicts with custom keybindings. - ui::TextEditKeyBindingsDelegateAuraLinux* delegate = - ui::GetTextEditKeyBindingsDelegate(); + auto* linux_ui = ui::LinuxUi::instance(); std::vector<ui::TextEditCommandAuraLinux> commands; - if (delegate && delegate->MatchEvent(event, &commands)) { + if (linux_ui && linux_ui->GetTextEditCommandsForEvent(event, &commands)) { const auto is_enabled = [this](const auto& command) { return IsTextEditCommandEnabled(command.command()); }; @@ -987,14 +971,16 @@ void Textfield::GetAccessibleNodeData(ui::AXNodeData* node_data) { const gfx::Range range = GetSelectedRange(); node_data->AddIntAttribute(ax::mojom::IntAttribute::kTextSelStart, - range.start()); - node_data->AddIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, range.end()); + base::checked_cast<int32_t>(range.start())); + node_data->AddIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, + base::checked_cast<int32_t>(range.end())); } bool Textfield::HandleAccessibleAction(const ui::AXActionData& action_data) { if (action_data.action == ax::mojom::Action::kSetSelection) { DCHECK_EQ(action_data.anchor_node_id, action_data.focus_node_id); - const gfx::Range range(action_data.anchor_offset, action_data.focus_offset); + const gfx::Range range(static_cast<size_t>(action_data.anchor_offset), + static_cast<size_t>(action_data.focus_offset)); return SetEditableSelectionRange(range); } @@ -1358,7 +1344,7 @@ void Textfield::SetCompositionText(const ui::CompositionText& composition) { OnAfterUserAction(); } -uint32_t Textfield::ConfirmCompositionText(bool keep_selection) { +size_t Textfield::ConfirmCompositionText(bool keep_selection) { // TODO(b/134473433) Modify this function so that when keep_selection is // true, the selection is not changed when text committed if (keep_selection) { @@ -1368,7 +1354,7 @@ uint32_t Textfield::ConfirmCompositionText(bool keep_selection) { return 0; OnBeforeUserAction(); skip_input_method_cancel_composition_ = true; - const uint32_t confirmed_text_length = model_->ConfirmCompositionText(); + const size_t confirmed_text_length = model_->ConfirmCompositionText(); skip_input_method_cancel_composition_ = false; UpdateAfterChange(TextChangeType::kUserTriggered, true); OnAfterUserAction(); @@ -1426,7 +1412,7 @@ void Textfield::InsertChar(const ui::KeyEvent& event) { DoInsertChar(ch); if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD) { - password_char_reveal_index_ = -1; + password_char_reveal_index_ = absl::nullopt; base::TimeDelta duration = GetPasswordRevealDuration(event); if (!duration.is_zero()) { const size_t change_offset = model_->GetCursorPosition(); @@ -1473,7 +1459,7 @@ gfx::Rect Textfield::GetSelectionBoundingBox() const { return gfx::Rect(); } -bool Textfield::GetCompositionCharacterBounds(uint32_t index, +bool Textfield::GetCompositionCharacterBounds(size_t index, gfx::Rect* rect) const { DCHECK(rect); if (!HasCompositionText()) @@ -1593,7 +1579,14 @@ bool Textfield::ChangeTextDirectionAndLayoutAlignment( void Textfield::ExtendSelectionAndDelete(size_t before, size_t after) { gfx::Range range = GetRenderText()->selection(); - DCHECK_GE(range.start(), before); + // Discard out-of-bound operations. + // TODO(crbug.com/1344096): this is a temporary fix to prevent the + // checked_cast failure in gfx::Range. There does not seem to be any + // observable bad behaviors before checked_cast was added. However, range + // clipping or dropping should be the last resort because a checkfail + // indicates that we run into bad states somewhere earlier on the stack. + if (range.start() < before) + return; range.set_start(range.start() - before); range.set_end(range.end() + after); @@ -2060,7 +2053,7 @@ Textfield::EditCommandResult Textfield::DoExecuteTextEditCommand( return {changed, cursor_changed}; } -void Textfield::OffsetDoubleClickWord(int offset) { +void Textfield::OffsetDoubleClickWord(size_t offset) { selection_controller_.OffsetDoubleClickWord(offset); } @@ -2562,17 +2555,18 @@ bool Textfield::ImeEditingAllowed() const { return (t != ui::TEXT_INPUT_TYPE_NONE && t != ui::TEXT_INPUT_TYPE_PASSWORD); } -void Textfield::RevealPasswordChar(int index, base::TimeDelta duration) { +void Textfield::RevealPasswordChar(absl::optional<size_t> index, + base::TimeDelta duration) { GetRenderText()->SetObscuredRevealIndex(index); SchedulePaint(); password_char_reveal_index_ = index; UpdateCursorViewPosition(); - if (index != -1) { - password_reveal_timer_.Start( - FROM_HERE, duration, - base::BindOnce(&Textfield::RevealPasswordChar, - weak_ptr_factory_.GetWeakPtr(), -1, duration)); + if (index.has_value()) { + password_reveal_timer_.Start(FROM_HERE, duration, + base::BindOnce(&Textfield::RevealPasswordChar, + weak_ptr_factory_.GetWeakPtr(), + absl::nullopt, duration)); } } diff --git a/chromium/ui/views/controls/textfield/textfield.h b/chromium/ui/views/controls/textfield/textfield.h index ea29dbdf334..9768a28f0e9 100644 --- a/chromium/ui/views/controls/textfield/textfield.h +++ b/chromium/ui/views/controls/textfield/textfield.h @@ -313,7 +313,9 @@ class VIEWS_EXPORT Textfield : public View, // Set extra spacing placed between glyphs; used for obscured text styling. void SetObscuredGlyphSpacing(int spacing); - int GetPasswordCharRevealIndex() const { return password_char_reveal_index_; } + absl::optional<size_t> GetPasswordCharRevealIndex() const { + return password_char_reveal_index_; + } void SetExtraInsets(const gfx::Insets& insets); @@ -407,7 +409,7 @@ class VIEWS_EXPORT Textfield : public View, // ui::TextInputClient overrides: void SetCompositionText(const ui::CompositionText& composition) override; - uint32_t ConfirmCompositionText(bool keep_selection) override; + size_t ConfirmCompositionText(bool keep_selection) override; void ClearCompositionText() override; void InsertText(const std::u16string& text, InsertTextCursorBehavior cursor_behavior) override; @@ -419,7 +421,7 @@ class VIEWS_EXPORT Textfield : public View, bool CanComposeInline() const override; gfx::Rect GetCaretBounds() const override; gfx::Rect GetSelectionBoundingBox() const override; - bool GetCompositionCharacterBounds(uint32_t index, + bool GetCompositionCharacterBounds(size_t index, gfx::Rect* rect) const override; bool HasCompositionText() const override; FocusReason GetFocusReason() const override; @@ -427,7 +429,11 @@ class VIEWS_EXPORT Textfield : public View, bool GetCompositionTextRange(gfx::Range* range) const override; bool GetEditableSelectionRange(gfx::Range* range) const override; bool SetEditableSelectionRange(const gfx::Range& range) override; +#if BUILDFLAG(IS_MAC) bool DeleteRange(const gfx::Range& range) override; +#else + bool DeleteRange(const gfx::Range& range); +#endif bool GetTextFromRange(const gfx::Range& range, std::u16string* text) const override; void OnInputMethodChanged() override; @@ -492,7 +498,7 @@ class VIEWS_EXPORT Textfield : public View, // Offsets the double-clicked word's range. This is only used in the unusual // case where the text changes on the second mousedown of a double-click. // This is harmless if there is not a currently double-clicked word. - void OffsetDoubleClickWord(int offset); + void OffsetDoubleClickWord(size_t offset); // Returns true if the drop cursor is for insertion at a target text location, // the standard behavior/style. Returns false when drop will do something @@ -615,9 +621,10 @@ class VIEWS_EXPORT Textfield : public View, bool ImeEditingAllowed() const; // Reveals the password character at |index| for a set duration. - // If |index| is -1, the existing revealed character will be reset. + // If |index| is nullopt, the existing revealed character will be reset. // |duration| is the time to remain the password char to be visible. - void RevealPasswordChar(int index, base::TimeDelta duration); + void RevealPasswordChar(absl::optional<size_t> index, + base::TimeDelta duration); void CreateTouchSelectionControllerAndNotifyIt(); @@ -772,7 +779,7 @@ class VIEWS_EXPORT Textfield : public View, ui::TextInputClient::FOCUS_REASON_NONE; // The password char reveal index, for testing only. - int password_char_reveal_index_ = -1; + absl::optional<size_t> password_char_reveal_index_; // Extra insets, useful to make room for a button for example. gfx::Insets extra_insets_ = gfx::Insets(); diff --git a/chromium/ui/views/controls/textfield/textfield_model.cc b/chromium/ui/views/controls/textfield/textfield_model.cc index b3202c3e087..8c67d53d205 100644 --- a/chromium/ui/views/controls/textfield/textfield_model.cc +++ b/chromium/ui/views/controls/textfield/textfield_model.cc @@ -364,8 +364,8 @@ void SelectRangeInCompositionText(gfx::RenderText* render_text, const gfx::Range& range) { DCHECK(render_text); DCHECK(range.IsValid()); - uint32_t start = range.GetMin(); - uint32_t end = range.GetMax(); + size_t start = range.GetMin(); + size_t end = range.GetMax(); #if BUILDFLAG(IS_CHROMEOS_ASH) // Swap |start| and |end| so that GetCaretBounds() can always return the same // value during conversion. @@ -783,11 +783,11 @@ void TextfieldModel::SetCompositionFromExistingText(const gfx::Range& range) { render_text_->SetCompositionRange(range); } -uint32_t TextfieldModel::ConfirmCompositionText() { +size_t TextfieldModel::ConfirmCompositionText() { DCHECK(HasCompositionText()); std::u16string composition = text().substr(composition_range_.start(), composition_range_.length()); - uint32_t composition_length = composition_range_.length(); + size_t composition_length = composition_range_.length(); // TODO(oshima): current behavior on ChromeOS is a bit weird and not // sure exactly how this should work. Find out and fix if necessary. AddOrMergeEditHistory(std::make_unique<internal::InsertEdit>( diff --git a/chromium/ui/views/controls/textfield/textfield_model.h b/chromium/ui/views/controls/textfield/textfield_model.h index 01452147fbb..614cb0b121c 100644 --- a/chromium/ui/views/controls/textfield/textfield_model.h +++ b/chromium/ui/views/controls/textfield/textfield_model.h @@ -257,7 +257,7 @@ class VIEWS_EXPORT TextfieldModel { // Converts current composition text into final content and returns the // length of the text committed. - uint32_t ConfirmCompositionText(); + size_t ConfirmCompositionText(); // Removes current composition text. void CancelCompositionText(); diff --git a/chromium/ui/views/controls/textfield/textfield_model_unittest.cc b/chromium/ui/views/controls/textfield/textfield_model_unittest.cc index b87b93f5146..809201cd843 100644 --- a/chromium/ui/views/controls/textfield/textfield_model_unittest.cc +++ b/chromium/ui/views/controls/textfield/textfield_model_unittest.cc @@ -1947,8 +1947,8 @@ TEST_F(TextfieldModelTest, UndoRedo_CompositionText) { EXPECT_EQ(u"ABCDEabc", model.text()); // Confirm the composition. - uint32_t composition_text_length = model.ConfirmCompositionText(); - EXPECT_EQ(composition_text_length, static_cast<uint32_t>(3)); + size_t composition_text_length = model.ConfirmCompositionText(); + EXPECT_EQ(composition_text_length, 3u); EXPECT_EQ(u"ABCDEabc", model.text()); EXPECT_TRUE(model.Undo()); EXPECT_EQ(u"ABCDE", model.text()); diff --git a/chromium/ui/views/controls/textfield/textfield_unittest.cc b/chromium/ui/views/controls/textfield/textfield_unittest.cc index 81d16dcdfd8..3f77efc92df 100644 --- a/chromium/ui/views/controls/textfield/textfield_unittest.cc +++ b/chromium/ui/views/controls/textfield/textfield_unittest.cc @@ -28,9 +28,9 @@ #include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h" #include "ui/base/emoji/emoji_panel_helper.h" #include "ui/base/ime/constants.h" +#include "ui/base/ime/ime_key_event_dispatcher.h" #include "ui/base/ime/init/input_method_factory.h" #include "ui/base/ime/input_method_base.h" -#include "ui/base/ime/input_method_delegate.h" #include "ui/base/ime/text_edit_commands.h" #include "ui/base/ime/text_input_client.h" #include "ui/base/l10n/l10n_util.h" @@ -62,10 +62,9 @@ #include "base/win/windows_version.h" #endif -// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch -// of lacros-chrome is complete. -#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) -#include "ui/base/ime/linux/text_edit_key_bindings_delegate_auralinux.h" +#if BUILDFLAG(IS_LINUX) +#include "ui/linux/fake_linux_ui.h" +#include "ui/linux/linux_ui.h" #endif #if BUILDFLAG(IS_CHROMEOS_ASH) @@ -88,14 +87,10 @@ namespace test { const char16_t kHebrewLetterSamekh = 0x05E1; // Convenience to make constructing a GestureEvent simpler. -class GestureEventForTest : public ui::GestureEvent { - public: - GestureEventForTest(int x, int y, ui::GestureEventDetails details) - : GestureEvent(x, y, ui::EF_NONE, base::TimeTicks(), details) {} - - GestureEventForTest(const GestureEventForTest&) = delete; - GestureEventForTest& operator=(const GestureEventForTest&) = delete; -}; +ui::GestureEvent +CreateTestGestureEvent(int x, int y, const ui::GestureEventDetails& details) { + return ui::GestureEvent(x, y, ui::EF_NONE, base::TimeTicks(), details); +} // This controller will happily destroy the target field passed on // construction when a key event is triggered. @@ -167,6 +162,16 @@ class MockInputMethod : public ui::InputMethodBase { count_show_virtual_keyboard_++; } +#if BUILDFLAG(IS_WIN) + bool OnUntranslatedIMEMessage( + const CHROME_MSG event, + InputMethod::NativeEventResult* result) override { + return false; + } + void OnInputLocaleChanged() override {} + bool IsInputLocaleCJK() const override { return false; } +#endif + bool untranslated_ime_message_called() const { return untranslated_ime_message_called_; } @@ -473,8 +478,8 @@ void TextfieldTest::PrepareTextfieldsInternal(int count, Textfield* textfield, View* container, gfx::Rect bounds) { - input_method_->SetDelegate( - test::WidgetTest::GetInputMethodDelegateForWidget(widget_.get())); + input_method_->SetImeKeyEventDispatcher( + test::WidgetTest::GetImeKeyEventDispatcherForWidget(widget_.get())); textfield->set_controller(this); textfield->SetBoundsRect(bounds); @@ -806,12 +811,14 @@ void TextfieldTest::MoveMouseTo(const gfx::Point& where) { void TextfieldTest::TapAtCursor(ui::EventPointerType pointer_type) { ui::GestureEventDetails tap_down_details(ui::ET_GESTURE_TAP_DOWN); tap_down_details.set_primary_pointer_type(pointer_type); - GestureEventForTest tap_down(GetCursorPositionX(0), 0, tap_down_details); + ui::GestureEvent tap_down = + CreateTestGestureEvent(GetCursorPositionX(0), 0, tap_down_details); textfield_->OnGestureEvent(&tap_down); ui::GestureEventDetails tap_up_details(ui::ET_GESTURE_TAP); tap_up_details.set_primary_pointer_type(pointer_type); - GestureEventForTest tap_up(GetCursorPositionX(0), 0, tap_up_details); + ui::GestureEvent tap_up = + CreateTestGestureEvent(GetCursorPositionX(0), 0, tap_up_details); textfield_->OnGestureEvent(&tap_up); } @@ -1473,15 +1480,15 @@ TEST_F(TextfieldTest, TextInputType_InsertionTest) { EXPECT_EQ(ui::TEXT_INPUT_TYPE_PASSWORD, textfield_->GetTextInputType()); SendKeyEvent(ui::VKEY_A); - EXPECT_EQ(-1, textfield_->GetPasswordCharRevealIndex()); + EXPECT_FALSE(textfield_->GetPasswordCharRevealIndex().has_value()); SendKeyEvent(kHebrewLetterSamekh, ui::EF_NONE, true /* from_vk */); #if !BUILDFLAG(IS_MAC) // Don't verifies the password character reveal on MacOS, because on MacOS, // the text insertion is not done through TextInputClient::InsertChar(). - EXPECT_EQ(1, textfield_->GetPasswordCharRevealIndex()); + EXPECT_EQ(1u, textfield_->GetPasswordCharRevealIndex()); #endif SendKeyEvent(ui::VKEY_B); - EXPECT_EQ(-1, textfield_->GetPasswordCharRevealIndex()); + EXPECT_FALSE(textfield_->GetPasswordCharRevealIndex().has_value()); EXPECT_EQ( u"a\x05E1" @@ -1579,11 +1586,9 @@ TEST_F(TextfieldTest, OnKeyPress) { TEST_F(TextfieldTest, OnKeyPressBinding) { InitTextfield(); -// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch -// of lacros-chrome is complete. -#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) +#if BUILDFLAG(IS_LINUX) // Install a TextEditKeyBindingsDelegateAuraLinux that does nothing. - class TestDelegate : public ui::TextEditKeyBindingsDelegateAuraLinux { + class TestDelegate : public ui::FakeLinuxUi { public: TestDelegate() = default; @@ -1592,15 +1597,14 @@ TEST_F(TextfieldTest, OnKeyPressBinding) { ~TestDelegate() override = default; - bool MatchEvent( + bool GetTextEditCommandsForEvent( const ui::Event& event, std::vector<ui::TextEditCommandAuraLinux>* commands) override { return false; } }; - TestDelegate delegate; - ui::SetTextEditKeyBindingsDelegate(&delegate); + ui::LinuxUi::SetInstance(std::make_unique<TestDelegate>()); #endif SendKeyEvent(ui::VKEY_A, false, false); @@ -1620,10 +1624,8 @@ TEST_F(TextfieldTest, OnKeyPressBinding) { EXPECT_EQ(u"a", textfield_->GetText()); textfield_->clear(); -// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch -// of lacros-chrome is complete. -#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) - ui::SetTextEditKeyBindingsDelegate(nullptr); +#if BUILDFLAG(IS_LINUX) + ui::LinuxUi::SetInstance(nullptr); #endif } @@ -2321,8 +2323,10 @@ TEST_F(TextfieldTest, TextInputClientTest) { EXPECT_TRUE(client->GetTextFromRange(range, &substring)); EXPECT_EQ(u"123", substring); +#if BUILDFLAG(IS_MAC) EXPECT_TRUE(client->DeleteRange(range)); EXPECT_EQ(u"0456789", textfield_->GetText()); +#endif ui::CompositionText composition; composition.text = u"321"; @@ -3062,10 +3066,10 @@ TEST_F(TextfieldTest, CommitComposingTextTest) { ui::CompositionText composition; composition.text = u"abc123"; textfield_->SetCompositionText(composition); - uint32_t composed_text_length = + size_t composed_text_length = textfield_->ConfirmCompositionText(/* keep_selection */ false); - EXPECT_EQ(composed_text_length, static_cast<uint32_t>(6)); + EXPECT_EQ(composed_text_length, 6u); } TEST_F(TextfieldTest, CommitEmptyComposingTextTest) { @@ -3073,10 +3077,10 @@ TEST_F(TextfieldTest, CommitEmptyComposingTextTest) { ui::CompositionText composition; composition.text = u""; textfield_->SetCompositionText(composition); - uint32_t composed_text_length = + size_t composed_text_length = textfield_->ConfirmCompositionText(/* keep_selection */ false); - EXPECT_EQ(composed_text_length, static_cast<uint32_t>(0)); + EXPECT_EQ(composed_text_length, 0u); } #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) @@ -3206,8 +3210,7 @@ TEST_F(TextfieldTest, GetAutocorrectCharacterBoundsTest) { gfx::Rect rect_for_long_text = textfield_->GetAutocorrectCharacterBounds(); - // Clear the text - textfield_->DeleteRange(gfx::Range(0, 99)); + textfield_->clear(); textfield_->InsertText( u"hello placeholder text", @@ -3457,7 +3460,7 @@ TEST_F(TextfieldTest, TestLongPressInitiatesDragDrop) { switches::kEnableTouchDragDrop); // Create a long press event in the selected region should start a drag. - GestureEventForTest long_press( + ui::GestureEvent long_press = CreateTestGestureEvent( kStringPoint.x(), kStringPoint.y(), ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS)); textfield_->OnGestureEvent(&long_press); @@ -3541,7 +3544,7 @@ TEST_F(TextfieldTest, VirtualKeyboardFocusEnsureCaretNotInRect) { EXPECT_EQ(widget_->GetNativeView()->bounds(), orig_widget_bounds); // Simulate virtual keyboard. - input_method_->SetOnScreenKeyboardBounds(keyboard_view_bounds); + input_method_->SetVirtualKeyboardBounds(keyboard_view_bounds); // Window should be shifted. EXPECT_EQ(widget_->GetNativeView()->bounds(), shifted_widget_bounds); @@ -3560,26 +3563,27 @@ class TextfieldTouchSelectionTest : public TextfieldTest { protected: // Simulates a complete tap. void Tap(const gfx::Point& point) { - GestureEventForTest begin(point.x(), point.y(), - ui::GestureEventDetails(ui::ET_GESTURE_BEGIN)); + ui::GestureEvent begin = CreateTestGestureEvent( + point.x(), point.y(), ui::GestureEventDetails(ui::ET_GESTURE_BEGIN)); textfield_->OnGestureEvent(&begin); - GestureEventForTest tap_down( + ui::GestureEvent tap_down = CreateTestGestureEvent( point.x(), point.y(), ui::GestureEventDetails(ui::ET_GESTURE_TAP_DOWN)); textfield_->OnGestureEvent(&tap_down); - GestureEventForTest show_press( + ui::GestureEvent show_press = CreateTestGestureEvent( point.x(), point.y(), ui::GestureEventDetails(ui::ET_GESTURE_SHOW_PRESS)); textfield_->OnGestureEvent(&show_press); ui::GestureEventDetails tap_details(ui::ET_GESTURE_TAP); tap_details.set_tap_count(1); - GestureEventForTest tap(point.x(), point.y(), tap_details); + ui::GestureEvent tap = + CreateTestGestureEvent(point.x(), point.y(), tap_details); textfield_->OnGestureEvent(&tap); - GestureEventForTest end(point.x(), point.y(), - ui::GestureEventDetails(ui::ET_GESTURE_END)); + ui::GestureEvent end = CreateTestGestureEvent( + point.x(), point.y(), ui::GestureEventDetails(ui::ET_GESTURE_END)); textfield_->OnGestureEvent(&end); } }; @@ -3595,7 +3599,7 @@ TEST_F(TextfieldTouchSelectionTest, TouchSelectionAndDraggingTest) { // Tapping on the textfield should turn on the TouchSelectionController. ui::GestureEventDetails tap_details(ui::ET_GESTURE_TAP); tap_details.set_tap_count(1); - GestureEventForTest tap(x, 0, tap_details); + ui::GestureEvent tap = CreateTestGestureEvent(x, 0, tap_details); textfield_->OnGestureEvent(&tap); EXPECT_TRUE(test_api_->touch_selection_controller()); @@ -3606,7 +3610,7 @@ TEST_F(TextfieldTouchSelectionTest, TouchSelectionAndDraggingTest) { // With touch editing enabled, long press should not show context menu. // Instead, select word and invoke TouchSelectionController. - GestureEventForTest long_press_1( + ui::GestureEvent long_press_1 = CreateTestGestureEvent( x, 0, ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS)); textfield_->OnGestureEvent(&long_press_1); EXPECT_EQ(u"hello", textfield_->GetSelectedText()); @@ -3616,7 +3620,7 @@ TEST_F(TextfieldTouchSelectionTest, TouchSelectionAndDraggingTest) { // With touch drag drop enabled, long pressing in the selected region should // start a drag and remove TouchSelectionController. ASSERT_TRUE(switches::IsTouchDragDropEnabled()); - GestureEventForTest long_press_2( + ui::GestureEvent long_press_2 = CreateTestGestureEvent( x, 0, ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS)); textfield_->OnGestureEvent(&long_press_2); EXPECT_EQ(u"hello", textfield_->GetSelectedText()); @@ -3628,7 +3632,7 @@ TEST_F(TextfieldTouchSelectionTest, TouchSelectionAndDraggingTest) { base::CommandLine::ForCurrentProcess()->AppendSwitch( switches::kDisableTouchDragDrop); ASSERT_FALSE(switches::IsTouchDragDropEnabled()); - GestureEventForTest long_press_3( + ui::GestureEvent long_press_3 = CreateTestGestureEvent( x, 0, ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS)); textfield_->OnGestureEvent(&long_press_3); EXPECT_EQ(u"hello", textfield_->GetSelectedText()); @@ -3878,7 +3882,7 @@ TEST_F(TextfieldTest, EmojiItem_EmptyField) { // A normal empty field may show the Emoji option (if supported). ui::MenuModel* context_menu = GetContextMenuModel(); EXPECT_TRUE(context_menu); - EXPECT_GT(context_menu->GetItemCount(), 0); + EXPECT_GT(context_menu->GetItemCount(), 0u); // Not all OS/versions support the emoji menu. EXPECT_EQ(ui::IsEmojiPanelSupported(), context_menu->GetLabelAt(0) == @@ -3893,7 +3897,7 @@ TEST_F(TextfieldTest, EmojiItem_ReadonlyField) { // In no case is the emoji option showing on a read-only field. ui::MenuModel* context_menu = GetContextMenuModel(); EXPECT_TRUE(context_menu); - EXPECT_GT(context_menu->GetItemCount(), 0); + EXPECT_GT(context_menu->GetItemCount(), 0u); EXPECT_NE(context_menu->GetLabelAt(0), l10n_util::GetStringUTF16(IDS_CONTENT_CONTEXT_EMOJI)); } @@ -3915,7 +3919,7 @@ TEST_F(TextfieldTest, EmojiItem_FieldWithText) { textfield_->SelectAll(false); ui::MenuModel* context_menu = GetContextMenuModel(); EXPECT_TRUE(context_menu); - EXPECT_GT(context_menu->GetItemCount(), 0); + EXPECT_GT(context_menu->GetItemCount(), 0u); // Not all OS/versions support the emoji menu. EXPECT_EQ(ui::IsEmojiPanelSupported(), context_menu->GetLabelAt(kExpectedEmojiIndex) == @@ -3966,7 +3970,7 @@ TEST_F(TextfieldTest, LookUpPassword) { ui::MenuModel* context_menu = GetContextMenuModel(); EXPECT_TRUE(context_menu); - EXPECT_GT(context_menu->GetItemCount(), 0); + EXPECT_GT(context_menu->GetItemCount(), 0u); EXPECT_NE(context_menu->GetCommandIdAt(0), IDS_CONTENT_CONTEXT_LOOK_UP); EXPECT_NE(context_menu->GetLabelAt(0), l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP, kText)); diff --git a/chromium/ui/views/controls/tree/tree_view.cc b/chromium/ui/views/controls/tree/tree_view.cc index 27011fc5fdd..e9bbeed02f4 100644 --- a/chromium/ui/views/controls/tree/tree_view.cc +++ b/chromium/ui/views/controls/tree/tree_view.cc @@ -204,6 +204,10 @@ void TreeView::StartEditing(TreeModelNode* node) { editor_->set_controller(this); } editor_->SetText(selected_node_->model_node()->GetTitle()); + // TODO(crbug.com/1345828): Investigate whether accessible name should stay in + // sync during editing. + editor_->SetAccessibleName( + selected_node_->model_node()->GetAccessibleTitle()); LayoutEditor(); editor_->SetVisible(true); SchedulePaintForNode(selected_node_); @@ -245,6 +249,7 @@ void TreeView::CommitEdit() { DCHECK(selected_node_); const bool editor_has_focus = editor_->HasFocus(); model_->SetTitle(GetSelectedNode(), editor_->GetText()); + editor_->SetAccessibleName(GetSelectedNode()->GetAccessibleTitle()); CancelEdit(); if (editor_has_focus) RequestFocus(); @@ -646,30 +651,35 @@ void TreeView::OnDidChangeFocus(View* focused_before, View* focused_now) { CommitEdit(); } -int TreeView::GetRowCount() { - int row_count = root_.NumExpandedNodes(); +size_t TreeView::GetRowCount() { + size_t row_count = root_.NumExpandedNodes(); if (!root_shown_) row_count--; return row_count; } -int TreeView::GetSelectedRow() { +absl::optional<size_t> TreeView::GetSelectedRow() { // Type-ahead searches should be relative to the active node, so return the // row of the active node for |PrefixSelector|. ui::TreeModelNode* model_node = GetActiveNode(); - return model_node ? GetRowForNode(model_node) : -1; + if (!model_node) + return absl::nullopt; + const int row = GetRowForNode(model_node); + return (row == -1) ? absl::nullopt + : absl::make_optional(static_cast<size_t>(row)); } -void TreeView::SetSelectedRow(int row) { +void TreeView::SetSelectedRow(absl::optional<size_t> row) { // Type-ahead manipulates selection because active node is synced to selected // node, so call SetSelectedNode() instead of SetActiveNode(). // TODO(crbug.com/1080944): Decouple active node from selected node by adding // new keyboard affordances. - SetSelectedNode(GetNodeForRow(row)); + SetSelectedNode( + GetNodeForRow(row.has_value() ? static_cast<int>(row.value()) : -1)); } -std::u16string TreeView::GetTextForRow(int row) { - return GetNodeForRow(row)->GetTitle(); +std::u16string TreeView::GetTextForRow(size_t row) { + return GetNodeForRow(static_cast<int>(row))->GetTitle(); } gfx::Point TreeView::GetKeyboardContextMenuLocation() { @@ -966,12 +976,12 @@ void TreeView::PopulateAccessibilityData(InternalNode* node, // Per the ARIA Spec, aria-posinset and aria-setsize are 1-based // not 0-based. - int pos_in_parent = node->parent()->GetIndexOf(node) + 1; - int sibling_size = static_cast<int>(node->parent()->children().size()); + size_t pos_in_parent = node->parent()->GetIndexOf(node).value() + 1; + size_t sibling_size = node->parent()->children().size(); data->AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, - int32_t{pos_in_parent}); + static_cast<int32_t>(pos_in_parent)); data->AddIntAttribute(ax::mojom::IntAttribute::kSetSize, - int32_t{sibling_size}); + static_cast<int32_t>(sibling_size)); } int ignored_depth; @@ -1003,7 +1013,7 @@ void TreeView::UpdatePreferredSize() { preferred_size_.SetSize( root_.GetMaxWidth(this, text_offset_, root_shown_ ? 1 : 0) + kTextHorizontalPadding * 2, - row_height_ * GetRowCount()); + row_height_ * base::checked_cast<int>(GetRowCount())); // When the editor is visible, more space is needed beyond the regular row, // such as for drawing the focus ring. @@ -1173,9 +1183,9 @@ void TreeView::PaintExpandControl(gfx::Canvas* canvas, void TreeView::PaintNodeIcon(gfx::Canvas* canvas, InternalNode* node, const gfx::Rect& bounds) { - int icon_index = model_->GetIconIndex(node->model_node()); + absl::optional<size_t> icon_index = model_->GetIconIndex(node->model_node()); int icon_x = kArrowRegionSize + kImagePadding; - if (icon_index == -1) { + if (!icon_index.has_value()) { // Flip just the |bounds| region of |canvas|. gfx::ScopedCanvas scoped_canvas(canvas); canvas->Translate(gfx::Vector2d(bounds.x(), 0)); @@ -1188,7 +1198,7 @@ void TreeView::PaintNodeIcon(gfx::Canvas* canvas, gfx::Rect(0, bounds.y(), bounds.width(), bounds.height())); } else { const gfx::ImageSkia& icon = - icons_[icon_index].Rasterize(GetColorProvider()); + icons_[icon_index.value()].Rasterize(GetColorProvider()); icon_x += (open_icon_.Size().width() - icon.width()) / 2; if (base::i18n::IsRTL()) icon_x = bounds.width() - icon_x - icon.width(); @@ -1211,7 +1221,8 @@ TreeView::InternalNode* TreeView::GetInternalNodeForModelNode( LoadChildren(parent_internal_node); } size_t index = - model_->GetIndexOf(parent_internal_node->model_node(), model_node); + model_->GetIndexOf(parent_internal_node->model_node(), model_node) + .value(); return parent_internal_node->children()[index].get(); } @@ -1297,11 +1308,13 @@ int TreeView::GetRowForInternalNode(InternalNode* node, int* depth) { int row = -1; const InternalNode* tmp_node = node; while (tmp_node->parent()) { - size_t index_in_parent = tmp_node->parent()->GetIndexOf(tmp_node); + size_t index_in_parent = tmp_node->parent()->GetIndexOf(tmp_node).value(); (*depth)++; row++; // For node. - for (size_t i = 0; i < index_in_parent; ++i) - row += tmp_node->parent()->children()[i]->NumExpandedNodes(); + for (size_t i = 0; i < index_in_parent; ++i) { + row += static_cast<int>( + tmp_node->parent()->children()[i]->NumExpandedNodes()); + } tmp_node = tmp_node->parent(); } if (root_shown_) { @@ -1363,10 +1376,11 @@ void TreeView::IncrementSelection(IncrementType type) { if (root_.children().empty()) return; if (type == IncrementType::kPrevious) { - int row_count = GetRowCount(); + size_t row_count = GetRowCount(); int depth = 0; DCHECK(row_count); - InternalNode* node = GetNodeByRow(row_count - 1, &depth); + InternalNode* node = + GetNodeByRow(static_cast<int>(row_count - 1), &depth); SetSelectedNode(node->model_node()); } else if (root_shown_) { SetSelectedNode(root_.model_node()); @@ -1379,7 +1393,8 @@ void TreeView::IncrementSelection(IncrementType type) { int depth = 0; int delta = type == IncrementType::kPrevious ? -1 : 1; int row = GetRowForInternalNode(active_node_, &depth); - int new_row = base::clamp(row + delta, 0, GetRowCount() - 1); + int new_row = + base::clamp(row + delta, 0, base::checked_cast<int>(GetRowCount()) - 1); if (new_row == row) return; // At the end/beginning. SetSelectedNode(GetNodeByRow(new_row, &depth)->model_node()); @@ -1472,8 +1487,8 @@ void TreeView::InternalNode::Reset(ui::TreeModelNode* node) { accessibility_view_ = nullptr; } -int TreeView::InternalNode::NumExpandedNodes() const { - int result = 1; // For this. +size_t TreeView::InternalNode::NumExpandedNodes() const { + size_t result = 1; // For this. if (!is_expanded_) return result; for (const auto& child : children()) diff --git a/chromium/ui/views/controls/tree/tree_view.h b/chromium/ui/views/controls/tree/tree_view.h index f7ab0945b19..03678965087 100644 --- a/chromium/ui/views/controls/tree/tree_view.h +++ b/chromium/ui/views/controls/tree/tree_view.h @@ -202,10 +202,10 @@ class VIEWS_EXPORT TreeView : public View, void OnDidChangeFocus(View* focused_before, View* focused_now) override; // PrefixDelegate overrides: - int GetRowCount() override; - int GetSelectedRow() override; - void SetSelectedRow(int row) override; - std::u16string GetTextForRow(int row) override; + size_t GetRowCount() override; + absl::optional<size_t> GetSelectedRow() override; + void SetSelectedRow(absl::optional<size_t> row) override; + std::u16string GetTextForRow(size_t row) override; protected: // View overrides: @@ -276,7 +276,7 @@ class VIEWS_EXPORT TreeView : public View, int text_width() const { return text_width_; } // Returns the total number of descendants (including this node). - int NumExpandedNodes() const; + size_t NumExpandedNodes() const; // Returns the max width of all descendants (including this node). |indent| // is how many pixels each child is indented and |depth| is the depth of diff --git a/chromium/ui/views/controls/tree/tree_view_unittest.cc b/chromium/ui/views/controls/tree/tree_view_unittest.cc index 20ff974e567..2582f594198 100644 --- a/chromium/ui/views/controls/tree/tree_view_unittest.cc +++ b/chromium/ui/views/controls/tree/tree_view_unittest.cc @@ -145,7 +145,7 @@ class TreeViewTest : public ViewsTestBase { void IncrementSelection(bool next); void CollapseOrSelectParent(); void ExpandOrSelectChild(); - int GetRowCount(); + size_t GetRowCount(); PrefixSelector* selector() { return tree_->GetPrefixSelector(); } ui::TreeNodeModel<TestNode> model_; @@ -343,7 +343,7 @@ void TreeViewTest::ExpandOrSelectChild() { tree_->ExpandOrSelectChild(); } -int TreeViewTest::GetRowCount() { +size_t TreeViewTest::GetRowCount() { return tree_->GetRowCount(); } @@ -396,7 +396,7 @@ TEST_F(TreeViewTest, SetModel) { EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("root", GetSelectedNodeTitle()); EXPECT_EQ("root", GetSelectedAccessibilityViewName()); - EXPECT_EQ(4, GetRowCount()); + EXPECT_EQ(4u, GetRowCount()); EXPECT_EQ( (AccessibilityEventsVector{ @@ -468,7 +468,7 @@ TEST_F(TreeViewTest, HideRoot) { EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("a", GetSelectedNodeTitle()); EXPECT_EQ("a", GetSelectedAccessibilityViewName()); - EXPECT_EQ(3, GetRowCount()); + EXPECT_EQ(3u, GetRowCount()); EXPECT_EQ((AccessibilityEventsVector{ std::make_pair(GetAccessibilityViewByName("a"), @@ -489,7 +489,7 @@ TEST_F(TreeViewTest, Expand) { EXPECT_EQ("root [a b [b1] c]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("root", GetSelectedNodeTitle()); EXPECT_EQ("root", GetSelectedAccessibilityViewName()); - EXPECT_EQ(5, GetRowCount()); + EXPECT_EQ(5u, GetRowCount()); EXPECT_EQ((AccessibilityEventsVector{ std::make_pair(GetTreeAccessibilityView(), @@ -509,7 +509,7 @@ TEST_F(TreeViewTest, Collapse) { tree_->Expand(GetNodeByTitle("b1")); EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString()); EXPECT_EQ("root [a b [b1] c]", TreeViewAccessibilityContentsAsString()); - EXPECT_EQ(5, GetRowCount()); + EXPECT_EQ(5u, GetRowCount()); tree_->SetSelectedNode(GetNodeByTitle("b1")); EXPECT_EQ("b1", GetSelectedNodeTitle()); EXPECT_EQ("b1", GetSelectedAccessibilityViewName()); @@ -521,7 +521,7 @@ TEST_F(TreeViewTest, Collapse) { // Selected node should have moved to 'b' EXPECT_EQ("b", GetSelectedNodeTitle()); EXPECT_EQ("b", GetSelectedAccessibilityViewName()); - EXPECT_EQ(4, GetRowCount()); + EXPECT_EQ(4u, GetRowCount()); EXPECT_EQ((AccessibilityEventsVector{ std::make_pair(GetAccessibilityViewByName("b"), @@ -550,7 +550,7 @@ TEST_F(TreeViewTest, TreeNodesAdded) { EXPECT_EQ("root [a b B c]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("root", GetSelectedNodeTitle()); EXPECT_EQ("root", GetSelectedAccessibilityViewName()); - EXPECT_EQ(5, GetRowCount()); + EXPECT_EQ(5u, GetRowCount()); EXPECT_EQ((AccessibilityEventsVector{ std::make_pair(GetTreeAccessibilityView(), ax::mojom::Event::kChildrenChanged), @@ -565,7 +565,7 @@ TEST_F(TreeViewTest, TreeNodesAdded) { EXPECT_EQ("root [a b B c]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("root", GetSelectedNodeTitle()); EXPECT_EQ("root", GetSelectedAccessibilityViewName()); - EXPECT_EQ(5, GetRowCount()); + EXPECT_EQ(5u, GetRowCount()); // Added node is not visible, hence no accessibility event needed. EXPECT_EQ(AccessibilityEventsVector(), accessibility_events()); @@ -577,7 +577,7 @@ TEST_F(TreeViewTest, TreeNodesAdded) { EXPECT_EQ("root [a b B c]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("root", GetSelectedNodeTitle()); EXPECT_EQ("root", GetSelectedAccessibilityViewName()); - EXPECT_EQ(5, GetRowCount()); + EXPECT_EQ(5u, GetRowCount()); // Added node is not visible, hence no accessibility event needed. EXPECT_EQ(AccessibilityEventsVector(), accessibility_events()); @@ -588,7 +588,7 @@ TEST_F(TreeViewTest, TreeNodesAdded) { EXPECT_EQ("root [a b [b1 b2] B c]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("root", GetSelectedNodeTitle()); EXPECT_EQ("root", GetSelectedAccessibilityViewName()); - EXPECT_EQ(7, GetRowCount()); + EXPECT_EQ(7u, GetRowCount()); // Since the added node was not visible when it was added, no extra events // other than the ones for expanding a node are needed. EXPECT_EQ((AccessibilityEventsVector{ @@ -616,7 +616,7 @@ TEST_F(TreeViewTest, TreeNodesRemoved) { EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("root", GetSelectedNodeTitle()); EXPECT_EQ("root", GetSelectedAccessibilityViewName()); - EXPECT_EQ(4, GetRowCount()); + EXPECT_EQ(4u, GetRowCount()); // Expand b1, then collapse it and remove its only child, b1. This shouldn't // effect the tree. @@ -628,7 +628,7 @@ TEST_F(TreeViewTest, TreeNodesRemoved) { EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("root", GetSelectedNodeTitle()); EXPECT_EQ("root", GetSelectedAccessibilityViewName()); - EXPECT_EQ(4, GetRowCount()); + EXPECT_EQ(4u, GetRowCount()); EXPECT_EQ( (AccessibilityEventsVector{std::make_pair( GetTreeAccessibilityView(), ax::mojom::Event::kChildrenChanged)}), @@ -641,7 +641,7 @@ TEST_F(TreeViewTest, TreeNodesRemoved) { EXPECT_EQ("root [a c]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("root", GetSelectedNodeTitle()); EXPECT_EQ("root", GetSelectedAccessibilityViewName()); - EXPECT_EQ(3, GetRowCount()); + EXPECT_EQ(3u, GetRowCount()); EXPECT_EQ( (AccessibilityEventsVector{ std::make_pair(GetTreeAccessibilityView(), ax::mojom::Event::kFocus), @@ -658,7 +658,7 @@ TEST_F(TreeViewTest, TreeNodesRemoved) { EXPECT_EQ("root [a c]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("root", GetSelectedNodeTitle()); EXPECT_EQ("root", GetSelectedAccessibilityViewName()); - EXPECT_EQ(3, GetRowCount()); + EXPECT_EQ(3u, GetRowCount()); // Node "c11" is not visible, hence no accessibility event needed. EXPECT_EQ(AccessibilityEventsVector(), accessibility_events()); @@ -672,7 +672,7 @@ TEST_F(TreeViewTest, TreeNodesRemoved) { EXPECT_EQ("root [a]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("a", GetSelectedNodeTitle()); EXPECT_EQ("a", GetSelectedAccessibilityViewName()); - EXPECT_EQ(2, GetRowCount()); + EXPECT_EQ(2u, GetRowCount()); EXPECT_EQ( (AccessibilityEventsVector{ std::make_pair(GetTreeAccessibilityView(), ax::mojom::Event::kFocus), @@ -696,7 +696,7 @@ TEST_F(TreeViewTest, TreeNodesRemoved) { EXPECT_EQ("root [a [c1 c3]]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("c3", GetSelectedNodeTitle()); EXPECT_EQ("c3", GetSelectedAccessibilityViewName()); - EXPECT_EQ(4, GetRowCount()); + EXPECT_EQ(4u, GetRowCount()); // Now delete 'c3' and then 'c1' should be selected. ClearAccessibilityEvents(); @@ -705,7 +705,7 @@ TEST_F(TreeViewTest, TreeNodesRemoved) { EXPECT_EQ("root [a [c1]]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("c1", GetSelectedNodeTitle()); EXPECT_EQ("c1", GetSelectedAccessibilityViewName()); - EXPECT_EQ(3, GetRowCount()); + EXPECT_EQ(3u, GetRowCount()); EXPECT_EQ( (AccessibilityEventsVector{ std::make_pair(GetTreeAccessibilityView(), ax::mojom::Event::kFocus), @@ -726,7 +726,7 @@ TEST_F(TreeViewTest, TreeNodesRemoved) { EXPECT_EQ("root [a]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("a", GetSelectedNodeTitle()); EXPECT_EQ("a", GetSelectedAccessibilityViewName()); - EXPECT_EQ(2, GetRowCount()); + EXPECT_EQ(2u, GetRowCount()); EXPECT_EQ( (AccessibilityEventsVector{ std::make_pair(GetTreeAccessibilityView(), ax::mojom::Event::kFocus), @@ -750,7 +750,7 @@ TEST_F(TreeViewTest, TreeNodesRemoved) { EXPECT_EQ("root [a c]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("c", GetSelectedNodeTitle()); EXPECT_EQ("c", GetSelectedAccessibilityViewName()); - EXPECT_EQ(2, GetRowCount()); + EXPECT_EQ(2u, GetRowCount()); } class TestController : public TreeViewController { @@ -802,7 +802,7 @@ TEST_F(TreeViewTest, TreeNodeChanged) { EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("root", GetSelectedNodeTitle()); EXPECT_EQ("root", GetSelectedAccessibilityViewName()); - EXPECT_EQ(4, GetRowCount()); + EXPECT_EQ(4u, GetRowCount()); EXPECT_EQ(AccessibilityEventsVector(), accessibility_events()); // Change 'b1', shouldn't do anything. @@ -812,7 +812,7 @@ TEST_F(TreeViewTest, TreeNodeChanged) { EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("root", GetSelectedNodeTitle()); EXPECT_EQ("root", GetSelectedAccessibilityViewName()); - EXPECT_EQ(4, GetRowCount()); + EXPECT_EQ(4u, GetRowCount()); EXPECT_EQ(AccessibilityEventsVector(), accessibility_events()); // Change 'b'. @@ -822,7 +822,7 @@ TEST_F(TreeViewTest, TreeNodeChanged) { EXPECT_EQ("root [a b.new c]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("root", GetSelectedNodeTitle()); EXPECT_EQ("root", GetSelectedAccessibilityViewName()); - EXPECT_EQ(4, GetRowCount()); + EXPECT_EQ(4u, GetRowCount()); EXPECT_EQ((AccessibilityEventsVector{ std::make_pair(GetAccessibilityViewByName("b.new"), ax::mojom::Event::kLocationChanged)}), @@ -997,7 +997,7 @@ TEST_F(TreeViewTest, VirtualAccessibleAction) { tree_->Expand(GetNodeByTitle("b1")); EXPECT_EQ("root [a b [b1] c]", TreeViewContentsAsString()); EXPECT_EQ("root [a b [b1] c]", TreeViewAccessibilityContentsAsString()); - EXPECT_EQ(5, GetRowCount()); + EXPECT_EQ(5u, GetRowCount()); // Set to nullptr should clear the selection. tree_->SetSelectedNode(nullptr); @@ -1062,7 +1062,7 @@ TEST_F(TreeViewTest, OnFocusAccessibilityEvents) { EXPECT_EQ("root [a b c]", TreeViewAccessibilityContentsAsString()); EXPECT_EQ("root", GetSelectedNodeTitle()); EXPECT_EQ("root", GetSelectedAccessibilityViewName()); - EXPECT_EQ(4, GetRowCount()); + EXPECT_EQ(4u, GetRowCount()); EXPECT_EQ((AccessibilityEventsVector{ std::make_pair(GetTreeAccessibilityView(), ax::mojom::Event::kChildrenChanged), diff --git a/chromium/ui/views/controls/webview/webview_unittest.cc b/chromium/ui/views/controls/webview/webview_unittest.cc index edf636df419..ea25270ff23 100644 --- a/chromium/ui/views/controls/webview/webview_unittest.cc +++ b/chromium/ui/views/controls/webview/webview_unittest.cc @@ -173,9 +173,9 @@ class WebViewUnitTest : public views::test::WidgetTest { top_level_widget_->SetBounds(gfx::Rect(0, 10, 100, 100)); View* const contents_view = top_level_widget_->SetContentsView(std::make_unique<View>()); - web_view_ = new WebView(browser_context_.get()); - web_view_->SetBoundsRect(gfx::Rect(contents_view->size())); - contents_view->AddChildView(web_view_.get()); + auto web_view = std::make_unique<WebView>(browser_context_.get()); + web_view->SetBoundsRect(gfx::Rect(contents_view->size())); + web_view_ = contents_view->AddChildView(std::move(web_view)); top_level_widget_->Show(); ASSERT_EQ(gfx::Rect(0, 0, 100, 100), web_view_->bounds()); } @@ -231,8 +231,8 @@ TEST_F(WebViewUnitTest, TestWebViewAttachDetachWebContents) { EXPECT_FALSE(observer1.was_shown()); web_view()->SetWebContents(web_contents1.get()); - // Layout() is normally async, call it now to ensure visibility is updated. - web_view()->Layout(); + // Layout is normally async, ensure it runs now so visibility is updated. + RunScheduledLayout(web_view()); EXPECT_TRUE(observer1.was_shown()); #if defined(USE_AURA) EXPECT_TRUE(web_contents1->GetNativeView()->IsVisible()); @@ -251,8 +251,8 @@ TEST_F(WebViewUnitTest, TestWebViewAttachDetachWebContents) { // Setting the new WebContents should hide the existing one. web_view()->SetWebContents(web_contents2.get()); - // Layout() is normally async, call it now to ensure visibility is updated. - web_view()->Layout(); + // Layout is normally async, ensure it runs now so visibility is updated. + RunScheduledLayout(web_view()); EXPECT_FALSE(observer1.was_shown()); EXPECT_TRUE(observer2.was_shown()); EXPECT_TRUE(observer2.valid_root_while_shown()); @@ -270,8 +270,8 @@ TEST_F(WebViewUnitTest, TestWebViewAttachDetachWebContents) { EXPECT_EQ(1, observer1.shown_count()); web_view()->SetWebContents(web_contents1.get()); - // Layout() is normally async, call it now to ensure visibility is updated. - web_view()->Layout(); + // Layout is normally async, ensure it runs now so visibility is updated. + RunScheduledLayout(web_view()); EXPECT_EQ(1, observer1.shown_count()); // Nothing else should change. |