Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(20)

Unified Diff: ui/views/controls/combobox/combobox.cc

Issue 1310903004: Use a ui::MenuModel in views::Combobox (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: +curlies Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « ui/views/controls/combobox/combobox.h ('k') | ui/views/controls/combobox/combobox_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: ui/views/controls/combobox/combobox.cc
diff --git a/ui/views/controls/combobox/combobox.cc b/ui/views/controls/combobox/combobox.cc
index 7d527a9b0108e80457e2fd0b838195017d596376..6f302a92a3562f564976c8bb55d80a645aa61981 100644
--- a/ui/views/controls/combobox/combobox.cc
+++ b/ui/views/controls/combobox/combobox.cc
@@ -4,33 +4,27 @@
#include "ui/views/controls/combobox/combobox.h"
-#include "base/bind.h"
#include "base/logging.h"
-#include "base/strings/utf_string_conversions.h"
#include "ui/accessibility/ax_view_state.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/models/combobox_model.h"
+#include "ui/base/models/combobox_model_observer.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/events/event.h"
-#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/animation/throb_animation.h"
#include "ui/gfx/canvas.h"
-#include "ui/gfx/image/image.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/gfx/text_utils.h"
#include "ui/native_theme/common_theme.h"
#include "ui/native_theme/native_theme.h"
#include "ui/resources/grit/ui_resources.h"
-#include "ui/views/background.h"
#include "ui/views/color_constants.h"
#include "ui/views/controls/button/custom_button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/combobox/combobox_listener.h"
#include "ui/views/controls/focusable_border.h"
-#include "ui/views/controls/menu/menu_item_view.h"
+#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/menu/menu_runner.h"
-#include "ui/views/controls/menu/menu_runner_handler.h"
-#include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/controls/prefix_selector.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/mouse_constants.h"
@@ -224,6 +218,118 @@ void PaintArrowButton(
// static
const char Combobox::kViewClassName[] = "views/Combobox";
+// Adapts a ui::ComboboxModel to a ui::MenuModel.
+class Combobox::ComboboxMenuModelAdapter : public ui::MenuModel,
+ public ui::ComboboxModelObserver {
+ public:
+ ComboboxMenuModelAdapter(Combobox* owner, ui::ComboboxModel* model)
+ : owner_(owner), model_(model) {
+ model_->AddObserver(this);
+ }
+
+ ~ComboboxMenuModelAdapter() override { model_->RemoveObserver(this); }
+
+ private:
+ bool UseCheckmarks() const {
+ return owner_->style_ != STYLE_ACTION &&
+ MenuConfig::instance(owner_->GetNativeTheme())
+ .check_selected_combobox_item;
+ }
+
+ // Overridden from MenuModel:
+ bool HasIcons() const override { return false; }
+
+ int GetItemCount() const override { return model_->GetItemCount(); }
+
+ ItemType GetTypeAt(int index) const override {
+ if (model_->IsItemSeparatorAt(index)) {
+ // In action menus, disallow <item>, <separator>, ... since that would put
+ // a separator at the top of the menu.
+ DCHECK(index != 1 || owner_->style_ != STYLE_ACTION);
+ 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 {
+ return index + kFirstMenuItemId;
+ }
+
+ base::string16 GetLabelAt(int index) const override {
+ // Inserting the Unicode formatting characters if necessary so that the
+ // text is displayed correctly in right-to-left UIs.
+ base::string16 text = model_->GetItemAt(index);
+ base::i18n::AdjustStringForLocaleDirection(&text);
+ return text;
+ }
+
+ bool IsItemDynamicAt(int index) const override { return true; }
+
+ const gfx::FontList* GetLabelFontListAt(int index) const override {
+ return &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; }
+
+ bool GetIconAt(int index, gfx::Image* icon) override { return false; }
+
+ ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override {
+ return nullptr;
+ }
+
+ bool IsEnabledAt(int index) const override {
+ return model_->IsItemEnabledAt(index);
+ }
+
+ bool IsVisibleAt(int index) const override {
+ // When STYLE_ACTION is used, the first item is not added to the menu. It is
+ // assumed that the first item is always selected and rendered on the top of
+ // the action button.
+ return index > 0 || owner_->style_ != STYLE_ACTION;
+ }
+
+ void HighlightChangedTo(int index) override {}
+
+ void ActivatedAt(int index) override {
+ owner_->selected_index_ = index;
+ owner_->OnPerformAction();
+ }
+
+ void ActivatedAt(int index, int event_flags) override { ActivatedAt(index); }
+
+ MenuModel* GetSubmenuModelAt(int index) const override { return nullptr; }
+
+ void SetMenuModelDelegate(
+ ui::MenuModelDelegate* menu_model_delegate) override {}
+
+ ui::MenuModelDelegate* GetMenuModelDelegate() const override {
+ return nullptr;
+ }
+
+ // Overridden from ComboboxModelObserver:
+ void OnComboboxModelChanged(ui::ComboboxModel* model) override {
+ owner_->ModelChanged();
+ }
+
+ Combobox* owner_; // Weak. Owns this.
+ ui::ComboboxModel* model_; // Weak.
+
+ DISALLOW_COPY_AND_ASSIGN(ComboboxMenuModelAdapter);
+};
+
////////////////////////////////////////////////////////////////////////////////
// Combobox, public:
@@ -233,13 +339,11 @@ Combobox::Combobox(ui::ComboboxModel* model)
listener_(NULL),
selected_index_(model_->GetDefaultIndex()),
invalid_(false),
- menu_(NULL),
- dropdown_open_(false),
+ menu_model_adapter_(new ComboboxMenuModelAdapter(this, model)),
text_button_(new TransparentButton(this)),
arrow_button_(new TransparentButton(this)),
weak_ptr_factory_(this) {
- model_->AddObserver(this);
- UpdateFromModel();
+ ModelChanged();
SetFocusable(true);
UpdateBorder();
@@ -272,8 +376,6 @@ Combobox::Combobox(ui::ComboboxModel* model)
}
Combobox::~Combobox() {
- model_->RemoveObserver(this);
-
if (GetInputMethod() && selector_.get()) {
// Combobox should have been blurred before destroy.
DCHECK(selector_.get() != GetInputMethod()->GetTextInputClient());
@@ -295,13 +397,20 @@ void Combobox::SetStyle(Style style) {
selected_index_ = 0;
UpdateBorder();
- UpdateFromModel();
+ content_size_ = GetContentSize();
PreferredSizeChanged();
}
void Combobox::ModelChanged() {
- selected_index_ = std::min(0, model_->GetItemCount());
- UpdateFromModel();
+ // If the selection is no longer valid (or the model is empty), restore the
+ // default index.
+ if (selected_index_ >= model_->GetItemCount() ||
+ model_->GetItemCount() == 0 ||
+ model_->IsItemSeparatorAt(selected_index_)) {
+ selected_index_ = model_->GetDefaultIndex();
+ }
+
+ content_size_ = GetContentSize();
PreferredSizeChanged();
}
@@ -366,23 +475,6 @@ void Combobox::Layout() {
arrow_button_->SetBounds(arrow_button_x, 0, arrow_button_width, height());
}
-bool Combobox::IsItemChecked(int id) const {
- return false;
-}
-
-bool Combobox::IsCommandEnabled(int id) const {
- return model()->IsItemEnabledAt(MenuCommandToIndex(id));
-}
-
-void Combobox::ExecuteCommand(int id) {
- selected_index_ = MenuCommandToIndex(id);
- OnPerformAction();
-}
-
-bool Combobox::GetAccelerator(int id, ui::Accelerator* accel) const {
- return false;
-}
-
int Combobox::GetRowCount() {
return model()->GetItemCount();
}
@@ -415,8 +507,8 @@ gfx::Size Combobox::GetPreferredSize() const {
Textfield::kTextPadding,
Textfield::kTextPadding);
int total_width = std::max(kMinComboboxWidth, content_size_.width()) +
- insets.width() + GetDisclosureArrowLeftPadding() +
- ArrowSize().width() + GetDisclosureArrowRightPadding();
+ insets.width() + GetDisclosureArrowLeftPadding() +
+ ArrowSize().width() + GetDisclosureArrowRightPadding();
return gfx::Size(total_width, content_size_.height() + insets.height());
}
@@ -430,7 +522,7 @@ bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) {
e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) {
return false;
}
- return dropdown_open_;
+ return menu_runner_;
}
bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
@@ -500,7 +592,6 @@ bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
}
if (show_menu) {
- UpdateFromModel();
ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD);
} else if (new_index != selected_index_ && new_index != kNoSelection &&
style_ != STYLE_ACTION) {
@@ -565,11 +656,6 @@ void Combobox::GetAccessibleState(ui::AXViewState* state) {
state->count = model_->GetItemCount();
}
-void Combobox::OnComboboxModelChanged(ui::ComboboxModel* model) {
- DCHECK_EQ(model, model_);
- ModelChanged();
-}
-
void Combobox::ButtonPressed(Button* sender, const ui::Event& event) {
if (!enabled())
return;
@@ -595,44 +681,6 @@ void Combobox::ButtonPressed(Button* sender, const ui::Event& event) {
}
}
-void Combobox::UpdateFromModel() {
- const gfx::FontList& font_list = Combobox::GetFontList();
-
- menu_ = new MenuItemView(this);
- // MenuRunner owns |menu_|.
- dropdown_list_menu_runner_.reset(new MenuRunner(menu_, MenuRunner::COMBOBOX));
-
- int num_items = model()->GetItemCount();
- int width = 0;
- bool text_item_appended = false;
- for (int i = 0; i < num_items; ++i) {
- // When STYLE_ACTION is used, the first item and the following separators
- // are not added to the dropdown menu. It is assumed that the first item is
- // always selected and rendered on the top of the action button.
- if (model()->IsItemSeparatorAt(i)) {
- if (text_item_appended || style_ != STYLE_ACTION)
- menu_->AppendSeparator();
- continue;
- }
-
- base::string16 text = model()->GetItemAt(i);
-
- // Inserting the Unicode formatting characters if necessary so that the
- // text is displayed correctly in right-to-left UIs.
- base::i18n::AdjustStringForLocaleDirection(&text);
-
- if (style_ != STYLE_ACTION || i > 0) {
- menu_->AppendMenuItem(i + kFirstMenuItemId, text, MenuItemView::NORMAL);
- text_item_appended = true;
- }
-
- if (style_ != STYLE_ACTION || i == selected_index_)
- width = std::max(width, gfx::GetStringWidth(text, font_list));
- }
-
- content_size_.SetSize(width, font_list.GetHeight());
-}
-
void Combobox::UpdateBorder() {
scoped_ptr<FocusableBorder> border(new FocusableBorder());
if (style_ == STYLE_ACTION)
@@ -758,14 +806,6 @@ void Combobox::PaintButtons(gfx::Canvas* canvas) {
}
void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
- if (!dropdown_list_menu_runner_.get())
- UpdateFromModel();
-
- // Extend the menu to the width of the combobox.
- SubmenuView* submenu = menu_->CreateSubmenu();
- submenu->set_minimum_preferred_width(
- size().width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
-
gfx::Rect lb = GetLocalBounds();
gfx::Point menu_position(lb.origin());
@@ -788,15 +828,20 @@ void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
original_state = arrow_button_->state();
arrow_button_->SetState(Button::STATE_PRESSED);
}
- dropdown_open_ = true;
MenuAnchorPosition anchor_position =
style_ == STYLE_ACTION ? MENU_ANCHOR_TOPRIGHT : MENU_ANCHOR_TOPLEFT;
- if (dropdown_list_menu_runner_->RunMenuAt(
- GetWidget(), NULL, bounds, anchor_position, source_type) ==
- MenuRunner::MENU_DELETED) {
+
+ // Allow |menu_runner_| to be set by the testing API, but if this method is
+ // ever invoked recursively, ensure the old menu is closed.
+ if (!menu_runner_ || menu_runner_->IsRunning()) {
+ menu_runner_.reset(
+ new MenuRunner(menu_model_adapter_.get(), MenuRunner::COMBOBOX));
+ }
+ if (menu_runner_->RunMenuAt(GetWidget(), nullptr, bounds, anchor_position,
+ source_type) == MenuRunner::MENU_DELETED) {
return;
}
- dropdown_open_ = false;
+ menu_runner_.reset();
if (arrow_button_)
arrow_button_->SetState(original_state);
closed_time_ = base::Time::Now();
@@ -820,14 +865,6 @@ void Combobox::OnPerformAction() {
selected_index_ = 0;
}
-int Combobox::MenuCommandToIndex(int menu_command_id) const {
- // (note that the id received is offset by kFirstMenuItemId)
- // Revert menu ID offset to map back to combobox model.
- int index = menu_command_id - kFirstMenuItemId;
- DCHECK_LT(index, model()->GetItemCount());
- return index;
-}
-
int Combobox::GetDisclosureArrowLeftPadding() const {
switch (style_) {
case STYLE_NORMAL:
@@ -868,6 +905,23 @@ gfx::Size Combobox::ArrowSize() const {
ignored);
}
+gfx::Size Combobox::GetContentSize() const {
+ const gfx::FontList& font_list = GetFontList();
+
+ int width = 0;
+ for (int i = 0; i < model()->GetItemCount(); ++i) {
+ if (model_->IsItemSeparatorAt(i))
+ continue;
+
+ if (style_ != STYLE_ACTION || i == selected_index_) {
+ width = std::max(
+ width,
+ gfx::GetStringWidth(menu_model_adapter_->GetLabelAt(i), font_list));
+ }
+ }
+ return gfx::Size(width, font_list.GetHeight());
+}
+
PrefixSelector* Combobox::GetPrefixSelector() {
if (!selector_)
selector_.reset(new PrefixSelector(this));
« no previous file with comments | « ui/views/controls/combobox/combobox.h ('k') | ui/views/controls/combobox/combobox_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698