1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165

ash / style / combobox.h [blame]

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef ASH_STYLE_COMBOBOX_H_
#define ASH_STYLE_COMBOBOX_H_

#include <memory>

#include "ash/ash_export.h"
#include "base/scoped_observation.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/models/combobox_model_observer.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/widget/unique_widget_ptr.h"

namespace ui {
class ComboboxModel;
}

namespace views {
class ImageView;
class Label;
}  // namespace views

namespace ash {

// A stylized non-editable combobox driven by `ui::ComboboxModel`.
class ASH_EXPORT Combobox : public views::Button,
                            public ui::ComboboxModelObserver,
                            public views::WidgetObserver {
  METADATA_HEADER(Combobox, views::Button)

 public:
  static constexpr gfx::Insets kComboboxBorderInsets =
      gfx::Insets::TLBR(4, 10, 4, 4);

  // `model` is owned by the combobox when using this constructor.
  explicit Combobox(std::unique_ptr<ui::ComboboxModel> model);
  // `model` is not owned by the combobox when using this constructor.
  explicit Combobox(ui::ComboboxModel* model);
  Combobox(const Combobox&) = delete;
  Combobox& operator=(const Combobox&) = delete;
  ~Combobox() override;

  // Sets the callback that is invoked when the selected item changes. Note that
  // this works same as `views::Combobox::SetCallback`.
  void SetSelectionChangedCallback(base::RepeatingClosure callback);

  // Gets/Sets the selected index.
  std::optional<size_t> GetSelectedIndex() const { return selected_index_; }
  void SetSelectedIndex(std::optional<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);

  // Returns whether or not the menu is currently running.
  bool IsMenuRunning() const;
  gfx::Size GetMenuViewSize() const;

  views::View* MenuItemAtIndex(int index) const;
  views::View* MenuView() const;

  // views::Button:
  void SetCallback(PressedCallback callback) override;
  void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
  void OnBlur() override;
  void AddedToWidget() override;
  void RemovedFromWidget() override;
  void Layout(PassKey) override;

  // WidgetObserver:
  void OnWidgetBoundsChanged(views::Widget* widget,
                             const gfx::Rect& bounds) override;

  std::u16string GetTextForRow(size_t row) const;

  // Test method exposing MenuSelectionAt.
  void SelectMenuItemForTest(size_t index);

 private:
  friend class ComboboxTest;
  class ComboboxMenuView;
  class ComboboxEventHandler;

  // Gets expected menu bounds according to combox location.
  gfx::Rect GetExpectedMenuBounds() const;

  // Called when there has been a selection from the menu.
  void MenuSelectionAt(size_t index);

  // Called when the combobox is pressed.
  void OnComboboxPressed();

  // Shows/Closes the drop down menu.
  void ShowDropDownMenu();
  void CloseDropDownMenu();

  // Called when a selection is made.
  void OnPerformAction();

  // Overridden from ComboboxModelObserver:
  void OnComboboxModelChanged(ui::ComboboxModel* model) override;
  void OnComboboxModelDestroying(ui::ComboboxModel* model) override;

  // views::Button:
  bool SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) override;
  bool OnKeyPressed(const ui::KeyEvent& e) override;
  void OnEnabledChanged() override;

  void UpdateExpandedCollapsedAccessibleState() const;
  void UpdateAccessibleAccessibleActiveDescendantId();
  void UpdateAccessibleDefaultAction();

  // Optionally used to tie the lifetime of the model to this combobox. See
  // constructor.
  std::unique_ptr<ui::ComboboxModel> owned_model_;

  // Reference to our model, which may be owned or not.
  raw_ptr<ui::ComboboxModel> model_;

  const raw_ptr<views::Label> title_ = nullptr;
  const raw_ptr<views::ImageView> drop_down_arrow_ = nullptr;

  // Callback notified when the selected index changes.
  base::RepeatingClosure callback_;

  // The current selected index; nullopt means no selection.
  std::optional<size_t> selected_index_ = std::nullopt;

  // The selection that committed by performing selection changed action.
  std::optional<size_t> last_commit_selection_ = std::nullopt;

  // A handler handles mouse and touch event happening outside combobox and drop
  // down menu. This is mainly used to decide if we should close the drop down
  // menu.
  std::unique_ptr<ComboboxEventHandler> event_handler_;

  // Drop down menu view owned by menu widget.
  raw_ptr<ComboboxMenuView> menu_view_ = nullptr;

  // Drop down menu widget.
  views::UniqueWidgetPtr menu_;

  // Like MenuButton, we use a time object in order to keep track of when the
  // combobox was closed. The time is used for simulating menu behavior; that
  // is, if the menu is shown and the button is pressed, we need to close the
  // menu. There is no clean way to get the second click event because the
  // menu is displayed using a modal loop and, unlike regular menus in Windows,
  // the button is not part of the displayed menu.
  base::TimeTicks closed_time_;

  base::ScopedObservation<ui::ComboboxModel, ui::ComboboxModelObserver>
      observation_{this};

  base::ScopedObservation<views::Widget, views::WidgetObserver>
      widget_observer_{this};

  base::WeakPtrFactory<Combobox> weak_ptr_factory_{this};
};

}  // namespace ash

#endif  // ASH_STYLE_COMBOBOX_H_