Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / views / controls / combobox / combobox.cc
blobddf5e4083653f78a39890b0ab5c01abd7da87b04
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/views/controls/combobox/combobox.h"
7 #include "base/logging.h"
8 #include "ui/accessibility/ax_view_state.h"
9 #include "ui/base/ime/input_method.h"
10 #include "ui/base/models/combobox_model.h"
11 #include "ui/base/models/combobox_model_observer.h"
12 #include "ui/base/resource/resource_bundle.h"
13 #include "ui/events/event.h"
14 #include "ui/gfx/animation/throb_animation.h"
15 #include "ui/gfx/canvas.h"
16 #include "ui/gfx/scoped_canvas.h"
17 #include "ui/gfx/text_utils.h"
18 #include "ui/native_theme/common_theme.h"
19 #include "ui/native_theme/native_theme.h"
20 #include "ui/resources/grit/ui_resources.h"
21 #include "ui/views/color_constants.h"
22 #include "ui/views/controls/button/custom_button.h"
23 #include "ui/views/controls/button/label_button.h"
24 #include "ui/views/controls/combobox/combobox_listener.h"
25 #include "ui/views/controls/focusable_border.h"
26 #include "ui/views/controls/menu/menu_config.h"
27 #include "ui/views/controls/menu/menu_runner.h"
28 #include "ui/views/controls/prefix_selector.h"
29 #include "ui/views/controls/textfield/textfield.h"
30 #include "ui/views/mouse_constants.h"
31 #include "ui/views/painter.h"
32 #include "ui/views/resources/grit/views_resources.h"
33 #include "ui/views/widget/widget.h"
35 namespace views {
37 namespace {
39 // Menu border widths
40 const int kMenuBorderWidthLeft = 1;
41 const int kMenuBorderWidthTop = 1;
42 const int kMenuBorderWidthRight = 1;
44 // Limit how small a combobox can be.
45 const int kMinComboboxWidth = 25;
47 // Size of the combobox arrow margins
48 const int kDisclosureArrowLeftPadding = 7;
49 const int kDisclosureArrowRightPadding = 7;
50 const int kDisclosureArrowButtonLeftPadding = 11;
51 const int kDisclosureArrowButtonRightPadding = 12;
53 // Define the id of the first item in the menu (since it needs to be > 0)
54 const int kFirstMenuItemId = 1000;
56 // Used to indicate that no item is currently selected by the user.
57 const int kNoSelection = -1;
59 const int kBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON);
60 const int kHoveredBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_H);
61 const int kPressedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_P);
62 const int kFocusedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_F);
63 const int kFocusedHoveredBodyButtonImages[] =
64 IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_H);
65 const int kFocusedPressedBodyButtonImages[] =
66 IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_P);
68 #define MENU_IMAGE_GRID(x) { \
69 x ## _MENU_TOP, x ## _MENU_CENTER, x ## _MENU_BOTTOM, }
71 const int kMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON);
72 const int kHoveredMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_H);
73 const int kPressedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_P);
74 const int kFocusedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F);
75 const int kFocusedHoveredMenuButtonImages[] =
76 MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_H);
77 const int kFocusedPressedMenuButtonImages[] =
78 MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_P);
80 #undef MENU_IMAGE_GRID
82 // The transparent button which holds a button state but is not rendered.
83 class TransparentButton : public CustomButton {
84 public:
85 TransparentButton(ButtonListener* listener)
86 : CustomButton(listener) {
87 SetAnimationDuration(LabelButton::kHoverAnimationDurationMs);
89 ~TransparentButton() override {}
91 bool OnMousePressed(const ui::MouseEvent& mouse_event) override {
92 parent()->RequestFocus();
93 return true;
96 double GetAnimationValue() const {
97 return hover_animation_->GetCurrentValue();
100 private:
101 DISALLOW_COPY_AND_ASSIGN(TransparentButton);
104 // Returns the next or previous valid index (depending on |increment|'s value).
105 // Skips separator or disabled indices. Returns -1 if there is no valid adjacent
106 // index.
107 int GetAdjacentIndex(ui::ComboboxModel* model, int increment, int index) {
108 DCHECK(increment == -1 || increment == 1);
110 index += increment;
111 while (index >= 0 && index < model->GetItemCount()) {
112 if (!model->IsItemSeparatorAt(index) || !model->IsItemEnabledAt(index))
113 return index;
114 index += increment;
116 return kNoSelection;
119 // Returns the image resource ids of an array for the body button.
121 // TODO(hajimehoshi): This function should return the images for the 'disabled'
122 // status. (crbug/270052)
123 const int* GetBodyButtonImageIds(bool focused,
124 Button::ButtonState state,
125 size_t* num) {
126 DCHECK(num);
127 *num = 9;
128 switch (state) {
129 case Button::STATE_DISABLED:
130 return focused ? kFocusedBodyButtonImages : kBodyButtonImages;
131 case Button::STATE_NORMAL:
132 return focused ? kFocusedBodyButtonImages : kBodyButtonImages;
133 case Button::STATE_HOVERED:
134 return focused ?
135 kFocusedHoveredBodyButtonImages : kHoveredBodyButtonImages;
136 case Button::STATE_PRESSED:
137 return focused ?
138 kFocusedPressedBodyButtonImages : kPressedBodyButtonImages;
139 default:
140 NOTREACHED();
142 return NULL;
145 // Returns the image resource ids of an array for the menu button.
146 const int* GetMenuButtonImageIds(bool focused,
147 Button::ButtonState state,
148 size_t* num) {
149 DCHECK(num);
150 *num = 3;
151 switch (state) {
152 case Button::STATE_DISABLED:
153 return focused ? kFocusedMenuButtonImages : kMenuButtonImages;
154 case Button::STATE_NORMAL:
155 return focused ? kFocusedMenuButtonImages : kMenuButtonImages;
156 case Button::STATE_HOVERED:
157 return focused ?
158 kFocusedHoveredMenuButtonImages : kHoveredMenuButtonImages;
159 case Button::STATE_PRESSED:
160 return focused ?
161 kFocusedPressedMenuButtonImages : kPressedMenuButtonImages;
162 default:
163 NOTREACHED();
165 return NULL;
168 // Returns the images for the menu buttons.
169 std::vector<const gfx::ImageSkia*> GetMenuButtonImages(
170 bool focused,
171 Button::ButtonState state) {
172 const int* ids;
173 size_t num_ids;
174 ids = GetMenuButtonImageIds(focused, state, &num_ids);
175 std::vector<const gfx::ImageSkia*> images;
176 images.reserve(num_ids);
177 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
178 for (size_t i = 0; i < num_ids; i++)
179 images.push_back(rb.GetImageSkiaNamed(ids[i]));
180 return images;
183 // Paints three images in a column at the given location. The center image is
184 // stretched so as to fit the given height.
185 void PaintImagesVertically(gfx::Canvas* canvas,
186 const gfx::ImageSkia& top_image,
187 const gfx::ImageSkia& center_image,
188 const gfx::ImageSkia& bottom_image,
189 int x, int y, int width, int height) {
190 canvas->DrawImageInt(top_image,
191 0, 0, top_image.width(), top_image.height(),
192 x, y, width, top_image.height(), false);
193 y += top_image.height();
194 int center_height = height - top_image.height() - bottom_image.height();
195 canvas->DrawImageInt(center_image,
196 0, 0, center_image.width(), center_image.height(),
197 x, y, width, center_height, false);
198 y += center_height;
199 canvas->DrawImageInt(bottom_image,
200 0, 0, bottom_image.width(), bottom_image.height(),
201 x, y, width, bottom_image.height(), false);
204 // Paints the arrow button.
205 void PaintArrowButton(
206 gfx::Canvas* canvas,
207 const std::vector<const gfx::ImageSkia*>& arrow_button_images,
208 int x, int height) {
209 PaintImagesVertically(canvas,
210 *arrow_button_images[0],
211 *arrow_button_images[1],
212 *arrow_button_images[2],
213 x, 0, arrow_button_images[0]->width(), height);
216 } // namespace
218 // static
219 const char Combobox::kViewClassName[] = "views/Combobox";
221 // Adapts a ui::ComboboxModel to a ui::MenuModel.
222 class Combobox::ComboboxMenuModelAdapter : public ui::MenuModel,
223 public ui::ComboboxModelObserver {
224 public:
225 ComboboxMenuModelAdapter(Combobox* owner, ui::ComboboxModel* model)
226 : owner_(owner), model_(model) {
227 model_->AddObserver(this);
230 ~ComboboxMenuModelAdapter() override { model_->RemoveObserver(this); }
232 private:
233 bool UseCheckmarks() const {
234 return owner_->style_ != STYLE_ACTION &&
235 MenuConfig::instance(owner_->GetNativeTheme())
236 .check_selected_combobox_item;
239 // Overridden from MenuModel:
240 bool HasIcons() const override { return false; }
242 int GetItemCount() const override { return model_->GetItemCount(); }
244 ItemType GetTypeAt(int index) const override {
245 if (model_->IsItemSeparatorAt(index)) {
246 // In action menus, disallow <item>, <separator>, ... since that would put
247 // a separator at the top of the menu.
248 DCHECK(index != 1 || owner_->style_ != STYLE_ACTION);
249 return TYPE_SEPARATOR;
251 return UseCheckmarks() ? TYPE_CHECK : TYPE_COMMAND;
254 ui::MenuSeparatorType GetSeparatorTypeAt(int index) const override {
255 return ui::NORMAL_SEPARATOR;
258 int GetCommandIdAt(int index) const override {
259 return index + kFirstMenuItemId;
262 base::string16 GetLabelAt(int index) const override {
263 // Inserting the Unicode formatting characters if necessary so that the
264 // text is displayed correctly in right-to-left UIs.
265 base::string16 text = model_->GetItemAt(index);
266 base::i18n::AdjustStringForLocaleDirection(&text);
267 return text;
270 bool IsItemDynamicAt(int index) const override { return true; }
272 const gfx::FontList* GetLabelFontListAt(int index) const override {
273 return &GetFontList();
276 bool GetAcceleratorAt(int index,
277 ui::Accelerator* accelerator) const override {
278 return false;
281 bool IsItemCheckedAt(int index) const override {
282 return UseCheckmarks() && index == owner_->selected_index_;
285 int GetGroupIdAt(int index) const override { return -1; }
287 bool GetIconAt(int index, gfx::Image* icon) override { return false; }
289 ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override {
290 return nullptr;
293 bool IsEnabledAt(int index) const override {
294 return model_->IsItemEnabledAt(index);
297 bool IsVisibleAt(int index) const override {
298 // When STYLE_ACTION is used, the first item is not added to the menu. It is
299 // assumed that the first item is always selected and rendered on the top of
300 // the action button.
301 return index > 0 || owner_->style_ != STYLE_ACTION;
304 void HighlightChangedTo(int index) override {}
306 void ActivatedAt(int index) override {
307 owner_->selected_index_ = index;
308 owner_->OnPerformAction();
311 void ActivatedAt(int index, int event_flags) override { ActivatedAt(index); }
313 MenuModel* GetSubmenuModelAt(int index) const override { return nullptr; }
315 void SetMenuModelDelegate(
316 ui::MenuModelDelegate* menu_model_delegate) override {}
318 ui::MenuModelDelegate* GetMenuModelDelegate() const override {
319 return nullptr;
322 // Overridden from ComboboxModelObserver:
323 void OnComboboxModelChanged(ui::ComboboxModel* model) override {
324 owner_->ModelChanged();
327 Combobox* owner_; // Weak. Owns this.
328 ui::ComboboxModel* model_; // Weak.
330 DISALLOW_COPY_AND_ASSIGN(ComboboxMenuModelAdapter);
333 ////////////////////////////////////////////////////////////////////////////////
334 // Combobox, public:
336 Combobox::Combobox(ui::ComboboxModel* model)
337 : model_(model),
338 style_(STYLE_NORMAL),
339 listener_(NULL),
340 selected_index_(model_->GetDefaultIndex()),
341 invalid_(false),
342 menu_model_adapter_(new ComboboxMenuModelAdapter(this, model)),
343 text_button_(new TransparentButton(this)),
344 arrow_button_(new TransparentButton(this)),
345 weak_ptr_factory_(this) {
346 ModelChanged();
347 SetFocusable(true);
348 UpdateBorder();
350 // Initialize the button images.
351 Button::ButtonState button_states[] = {
352 Button::STATE_DISABLED,
353 Button::STATE_NORMAL,
354 Button::STATE_HOVERED,
355 Button::STATE_PRESSED,
357 for (int i = 0; i < 2; i++) {
358 for (size_t state_index = 0; state_index < arraysize(button_states);
359 state_index++) {
360 Button::ButtonState state = button_states[state_index];
361 size_t num;
362 bool focused = !!i;
363 const int* ids = GetBodyButtonImageIds(focused, state, &num);
364 body_button_painters_[focused][state].reset(
365 Painter::CreateImageGridPainter(ids));
366 menu_button_images_[focused][state] = GetMenuButtonImages(focused, state);
370 text_button_->SetVisible(true);
371 arrow_button_->SetVisible(true);
372 text_button_->SetFocusable(false);
373 arrow_button_->SetFocusable(false);
374 AddChildView(text_button_);
375 AddChildView(arrow_button_);
378 Combobox::~Combobox() {
379 if (GetInputMethod() && selector_.get()) {
380 // Combobox should have been blurred before destroy.
381 DCHECK(selector_.get() != GetInputMethod()->GetTextInputClient());
385 // static
386 const gfx::FontList& Combobox::GetFontList() {
387 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
388 return rb.GetFontList(ui::ResourceBundle::BaseFont);
391 void Combobox::SetStyle(Style style) {
392 if (style_ == style)
393 return;
395 style_ = style;
396 if (style_ == STYLE_ACTION)
397 selected_index_ = 0;
399 UpdateBorder();
400 content_size_ = GetContentSize();
401 PreferredSizeChanged();
404 void Combobox::ModelChanged() {
405 // If the selection is no longer valid (or the model is empty), restore the
406 // default index.
407 if (selected_index_ >= model_->GetItemCount() ||
408 model_->GetItemCount() == 0 ||
409 model_->IsItemSeparatorAt(selected_index_)) {
410 selected_index_ = model_->GetDefaultIndex();
413 content_size_ = GetContentSize();
414 PreferredSizeChanged();
417 void Combobox::SetSelectedIndex(int index) {
418 if (style_ == STYLE_ACTION)
419 return;
421 selected_index_ = index;
422 SchedulePaint();
425 bool Combobox::SelectValue(const base::string16& value) {
426 if (style_ == STYLE_ACTION)
427 return false;
429 for (int i = 0; i < model()->GetItemCount(); ++i) {
430 if (value == model()->GetItemAt(i)) {
431 SetSelectedIndex(i);
432 return true;
435 return false;
438 void Combobox::SetAccessibleName(const base::string16& name) {
439 accessible_name_ = name;
442 void Combobox::SetInvalid(bool invalid) {
443 if (invalid == invalid_)
444 return;
446 invalid_ = invalid;
448 UpdateBorder();
449 SchedulePaint();
452 void Combobox::Layout() {
453 PrefixDelegate::Layout();
455 gfx::Insets insets = GetInsets();
456 int text_button_width = 0;
457 int arrow_button_width = 0;
459 switch (style_) {
460 case STYLE_NORMAL: {
461 arrow_button_width = width();
462 break;
464 case STYLE_ACTION: {
465 arrow_button_width = GetDisclosureArrowLeftPadding() +
466 ArrowSize().width() +
467 GetDisclosureArrowRightPadding();
468 text_button_width = width() - arrow_button_width;
469 break;
473 int arrow_button_x = std::max(0, text_button_width);
474 text_button_->SetBounds(0, 0, std::max(0, text_button_width), height());
475 arrow_button_->SetBounds(arrow_button_x, 0, arrow_button_width, height());
478 int Combobox::GetRowCount() {
479 return model()->GetItemCount();
482 int Combobox::GetSelectedRow() {
483 return selected_index_;
486 void Combobox::SetSelectedRow(int row) {
487 int prev_index = selected_index_;
488 SetSelectedIndex(row);
489 if (selected_index_ != prev_index)
490 OnPerformAction();
493 base::string16 Combobox::GetTextForRow(int row) {
494 return model()->IsItemSeparatorAt(row) ? base::string16() :
495 model()->GetItemAt(row);
498 ////////////////////////////////////////////////////////////////////////////////
499 // Combobox, View overrides:
501 gfx::Size Combobox::GetPreferredSize() const {
502 // The preferred size will drive the local bounds which in turn is used to set
503 // the minimum width for the dropdown list.
504 gfx::Insets insets = GetInsets();
505 insets += gfx::Insets(Textfield::kTextPadding,
506 Textfield::kTextPadding,
507 Textfield::kTextPadding,
508 Textfield::kTextPadding);
509 int total_width = std::max(kMinComboboxWidth, content_size_.width()) +
510 insets.width() + GetDisclosureArrowLeftPadding() +
511 ArrowSize().width() + GetDisclosureArrowRightPadding();
512 return gfx::Size(total_width, content_size_.height() + insets.height());
515 const char* Combobox::GetClassName() const {
516 return kViewClassName;
519 bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) {
520 // Escape should close the drop down list when it is active, not host UI.
521 if (e.key_code() != ui::VKEY_ESCAPE ||
522 e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) {
523 return false;
525 return menu_runner_;
528 bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
529 // TODO(oshima): handle IME.
530 DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED);
532 DCHECK_GE(selected_index_, 0);
533 DCHECK_LT(selected_index_, model()->GetItemCount());
534 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
535 selected_index_ = 0;
537 bool show_menu = false;
538 int new_index = kNoSelection;
539 switch (e.key_code()) {
540 // Show the menu on F4 without modifiers.
541 case ui::VKEY_F4:
542 if (e.IsAltDown() || e.IsAltGrDown() || e.IsControlDown())
543 return false;
544 show_menu = true;
545 break;
547 // Move to the next item if any, or show the menu on Alt+Down like Windows.
548 case ui::VKEY_DOWN:
549 if (e.IsAltDown())
550 show_menu = true;
551 else
552 new_index = GetAdjacentIndex(model(), 1, selected_index_);
553 break;
555 // Move to the end of the list.
556 case ui::VKEY_END:
557 case ui::VKEY_NEXT: // Page down.
558 new_index = GetAdjacentIndex(model(), -1, model()->GetItemCount());
559 break;
561 // Move to the beginning of the list.
562 case ui::VKEY_HOME:
563 case ui::VKEY_PRIOR: // Page up.
564 new_index = GetAdjacentIndex(model(), 1, -1);
565 break;
567 // Move to the previous item if any.
568 case ui::VKEY_UP:
569 new_index = GetAdjacentIndex(model(), -1, selected_index_);
570 break;
572 // Click the button only when the button style mode.
573 case ui::VKEY_SPACE:
574 if (style_ == STYLE_ACTION) {
575 // When pressing space, the click event will be raised after the key is
576 // released.
577 text_button_->SetState(Button::STATE_PRESSED);
578 } else {
579 return false;
581 break;
583 // Click the button only when the button style mode.
584 case ui::VKEY_RETURN:
585 if (style_ != STYLE_ACTION)
586 return false;
587 OnPerformAction();
588 break;
590 default:
591 return false;
594 if (show_menu) {
595 ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD);
596 } else if (new_index != selected_index_ && new_index != kNoSelection &&
597 style_ != STYLE_ACTION) {
598 DCHECK(!model()->IsItemSeparatorAt(new_index));
599 selected_index_ = new_index;
600 OnPerformAction();
603 return true;
606 bool Combobox::OnKeyReleased(const ui::KeyEvent& e) {
607 if (style_ != STYLE_ACTION)
608 return false; // crbug.com/127520
610 if (e.key_code() == ui::VKEY_SPACE && style_ == STYLE_ACTION)
611 OnPerformAction();
613 return false;
616 void Combobox::OnPaint(gfx::Canvas* canvas) {
617 switch (style_) {
618 case STYLE_NORMAL: {
619 OnPaintBackground(canvas);
620 PaintText(canvas);
621 OnPaintBorder(canvas);
622 break;
624 case STYLE_ACTION: {
625 PaintButtons(canvas);
626 PaintText(canvas);
627 break;
632 void Combobox::OnFocus() {
633 if (GetInputMethod())
634 GetInputMethod()->SetFocusedTextInputClient(GetPrefixSelector());
636 View::OnFocus();
637 // Border renders differently when focused.
638 SchedulePaint();
641 void Combobox::OnBlur() {
642 if (GetInputMethod())
643 GetInputMethod()->DetachTextInputClient(GetPrefixSelector());
645 if (selector_)
646 selector_->OnViewBlur();
647 // Border renders differently when focused.
648 SchedulePaint();
651 void Combobox::GetAccessibleState(ui::AXViewState* state) {
652 state->role = ui::AX_ROLE_COMBO_BOX;
653 state->name = accessible_name_;
654 state->value = model_->GetItemAt(selected_index_);
655 state->index = selected_index_;
656 state->count = model_->GetItemCount();
659 void Combobox::ButtonPressed(Button* sender, const ui::Event& event) {
660 if (!enabled())
661 return;
663 RequestFocus();
665 if (sender == text_button_) {
666 OnPerformAction();
667 } else {
668 DCHECK_EQ(arrow_button_, sender);
669 // TODO(hajimehoshi): Fix the problem that the arrow button blinks when
670 // cliking this while the dropdown menu is opened.
671 const base::TimeDelta delta = base::Time::Now() - closed_time_;
672 if (delta.InMilliseconds() <= kMinimumMsBetweenButtonClicks)
673 return;
675 ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE;
676 if (event.IsKeyEvent())
677 source_type = ui::MENU_SOURCE_KEYBOARD;
678 else if (event.IsGestureEvent() || event.IsTouchEvent())
679 source_type = ui::MENU_SOURCE_TOUCH;
680 ShowDropDownMenu(source_type);
684 void Combobox::UpdateBorder() {
685 scoped_ptr<FocusableBorder> border(new FocusableBorder());
686 if (style_ == STYLE_ACTION)
687 border->SetInsets(5, 10, 5, 10);
688 if (invalid_)
689 border->SetColor(kWarningColor);
690 SetBorder(border.Pass());
693 void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
694 rect->set_x(GetMirroredXForRect(*rect));
697 void Combobox::PaintText(gfx::Canvas* canvas) {
698 gfx::Insets insets = GetInsets();
699 insets += gfx::Insets(0, Textfield::kTextPadding, 0, Textfield::kTextPadding);
701 gfx::ScopedCanvas scoped_canvas(canvas);
702 canvas->ClipRect(GetContentsBounds());
704 int x = insets.left();
705 int y = insets.top();
706 int text_height = height() - insets.height();
707 SkColor text_color = GetNativeTheme()->GetSystemColor(
708 ui::NativeTheme::kColorId_LabelEnabledColor);
710 DCHECK_GE(selected_index_, 0);
711 DCHECK_LT(selected_index_, model()->GetItemCount());
712 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
713 selected_index_ = 0;
714 base::string16 text = model()->GetItemAt(selected_index_);
716 gfx::Size arrow_size = ArrowSize();
717 int disclosure_arrow_offset = width() - arrow_size.width() -
718 GetDisclosureArrowLeftPadding() - GetDisclosureArrowRightPadding();
720 const gfx::FontList& font_list = Combobox::GetFontList();
721 int text_width = gfx::GetStringWidth(text, font_list);
722 if ((text_width + insets.width()) > disclosure_arrow_offset)
723 text_width = disclosure_arrow_offset - insets.width();
725 gfx::Rect text_bounds(x, y, text_width, text_height);
726 AdjustBoundsForRTLUI(&text_bounds);
727 canvas->DrawStringRect(text, font_list, text_color, text_bounds);
729 int arrow_x = disclosure_arrow_offset + GetDisclosureArrowLeftPadding();
730 gfx::Rect arrow_bounds(arrow_x,
731 height() / 2 - arrow_size.height() / 2,
732 arrow_size.width(),
733 arrow_size.height());
734 AdjustBoundsForRTLUI(&arrow_bounds);
736 // TODO(estade): hack alert! Remove this direct call into CommonTheme. For now
737 // STYLE_ACTION isn't properly themed so we have to override the NativeTheme
738 // behavior. See crbug.com/384071
739 if (style_ == STYLE_ACTION) {
740 ui::CommonThemePaintComboboxArrow(canvas->sk_canvas(), arrow_bounds);
741 } else {
742 ui::NativeTheme::ExtraParams ignored;
743 GetNativeTheme()->Paint(canvas->sk_canvas(),
744 ui::NativeTheme::kComboboxArrow,
745 ui::NativeTheme::kNormal,
746 arrow_bounds,
747 ignored);
751 void Combobox::PaintButtons(gfx::Canvas* canvas) {
752 DCHECK(style_ == STYLE_ACTION);
754 gfx::ScopedCanvas scoped_canvas(canvas);
755 if (base::i18n::IsRTL()) {
756 canvas->Translate(gfx::Vector2d(width(), 0));
757 canvas->Scale(-1, 1);
760 bool focused = HasFocus();
761 const std::vector<const gfx::ImageSkia*>& arrow_button_images =
762 menu_button_images_[focused][
763 arrow_button_->state() == Button::STATE_HOVERED ?
764 Button::STATE_NORMAL : arrow_button_->state()];
766 int text_button_hover_alpha =
767 text_button_->state() == Button::STATE_PRESSED ? 0 :
768 static_cast<int>(static_cast<TransparentButton*>(text_button_)->
769 GetAnimationValue() * 255);
770 if (text_button_hover_alpha < 255) {
771 canvas->SaveLayerAlpha(255 - text_button_hover_alpha);
772 Painter* text_button_painter =
773 body_button_painters_[focused][
774 text_button_->state() == Button::STATE_HOVERED ?
775 Button::STATE_NORMAL : text_button_->state()].get();
776 Painter::PaintPainterAt(canvas, text_button_painter,
777 gfx::Rect(0, 0, text_button_->width(), height()));
778 canvas->Restore();
780 if (0 < text_button_hover_alpha) {
781 canvas->SaveLayerAlpha(text_button_hover_alpha);
782 Painter* text_button_hovered_painter =
783 body_button_painters_[focused][Button::STATE_HOVERED].get();
784 Painter::PaintPainterAt(canvas, text_button_hovered_painter,
785 gfx::Rect(0, 0, text_button_->width(), height()));
786 canvas->Restore();
789 int arrow_button_hover_alpha =
790 arrow_button_->state() == Button::STATE_PRESSED ? 0 :
791 static_cast<int>(static_cast<TransparentButton*>(arrow_button_)->
792 GetAnimationValue() * 255);
793 if (arrow_button_hover_alpha < 255) {
794 canvas->SaveLayerAlpha(255 - arrow_button_hover_alpha);
795 PaintArrowButton(canvas, arrow_button_images, arrow_button_->x(), height());
796 canvas->Restore();
798 if (0 < arrow_button_hover_alpha) {
799 canvas->SaveLayerAlpha(arrow_button_hover_alpha);
800 const std::vector<const gfx::ImageSkia*>& arrow_button_hovered_images =
801 menu_button_images_[focused][Button::STATE_HOVERED];
802 PaintArrowButton(canvas, arrow_button_hovered_images,
803 arrow_button_->x(), height());
804 canvas->Restore();
808 void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
809 gfx::Rect lb = GetLocalBounds();
810 gfx::Point menu_position(lb.origin());
812 if (style_ == STYLE_NORMAL) {
813 // Inset the menu's requested position so the border of the menu lines up
814 // with the border of the combobox.
815 menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft);
816 menu_position.set_y(menu_position.y() + kMenuBorderWidthTop);
818 lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
820 View::ConvertPointToScreen(this, &menu_position);
822 gfx::Rect bounds(menu_position, lb.size());
824 Button::ButtonState original_state = Button::STATE_NORMAL;
825 if (arrow_button_) {
826 original_state = arrow_button_->state();
827 arrow_button_->SetState(Button::STATE_PRESSED);
829 MenuAnchorPosition anchor_position =
830 style_ == STYLE_ACTION ? MENU_ANCHOR_TOPRIGHT : MENU_ANCHOR_TOPLEFT;
832 // Allow |menu_runner_| to be set by the testing API, but if this method is
833 // ever invoked recursively, ensure the old menu is closed.
834 if (!menu_runner_ || menu_runner_->IsRunning()) {
835 menu_runner_.reset(
836 new MenuRunner(menu_model_adapter_.get(), MenuRunner::COMBOBOX));
838 if (menu_runner_->RunMenuAt(GetWidget(), nullptr, bounds, anchor_position,
839 source_type) == MenuRunner::MENU_DELETED) {
840 return;
842 menu_runner_.reset();
843 if (arrow_button_)
844 arrow_button_->SetState(original_state);
845 closed_time_ = base::Time::Now();
847 // Need to explicitly clear mouse handler so that events get sent
848 // properly after the menu finishes running. If we don't do this, then
849 // the first click to other parts of the UI is eaten.
850 SetMouseHandler(NULL);
853 void Combobox::OnPerformAction() {
854 NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, false);
855 SchedulePaint();
857 // This combobox may be deleted by the listener.
858 base::WeakPtr<Combobox> weak_ptr = weak_ptr_factory_.GetWeakPtr();
859 if (listener_)
860 listener_->OnPerformAction(this);
862 if (weak_ptr && style_ == STYLE_ACTION)
863 selected_index_ = 0;
866 int Combobox::GetDisclosureArrowLeftPadding() const {
867 switch (style_) {
868 case STYLE_NORMAL:
869 return kDisclosureArrowLeftPadding;
870 case STYLE_ACTION:
871 return kDisclosureArrowButtonLeftPadding;
873 NOTREACHED();
874 return 0;
877 int Combobox::GetDisclosureArrowRightPadding() const {
878 switch (style_) {
879 case STYLE_NORMAL:
880 return kDisclosureArrowRightPadding;
881 case STYLE_ACTION:
882 return kDisclosureArrowButtonRightPadding;
884 NOTREACHED();
885 return 0;
888 gfx::Size Combobox::ArrowSize() const {
889 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
890 // TODO(estade): hack alert! This should always use GetNativeTheme(). For now
891 // STYLE_ACTION isn't properly themed so we have to override the NativeTheme
892 // behavior. See crbug.com/384071
893 const ui::NativeTheme* native_theme_for_arrow = style_ == STYLE_ACTION ?
894 ui::NativeTheme::instance() :
895 GetNativeTheme();
896 #else
897 const ui::NativeTheme* native_theme_for_arrow = GetNativeTheme();
898 #endif
900 ui::NativeTheme::ExtraParams ignored;
901 return native_theme_for_arrow->GetPartSize(ui::NativeTheme::kComboboxArrow,
902 ui::NativeTheme::kNormal,
903 ignored);
906 gfx::Size Combobox::GetContentSize() const {
907 const gfx::FontList& font_list = GetFontList();
909 int width = 0;
910 for (int i = 0; i < model()->GetItemCount(); ++i) {
911 if (model_->IsItemSeparatorAt(i))
912 continue;
914 if (style_ != STYLE_ACTION || i == selected_index_) {
915 width = std::max(
916 width,
917 gfx::GetStringWidth(menu_model_adapter_->GetLabelAt(i), font_list));
920 return gfx::Size(width, font_list.GetHeight());
923 PrefixSelector* Combobox::GetPrefixSelector() {
924 if (!selector_)
925 selector_.reset(new PrefixSelector(this));
926 return selector_.get();
929 } // namespace views