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)); |