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"
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
{
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();
96 double GetAnimationValue() const {
97 return hover_animation_
->GetCurrentValue();
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
107 int GetAdjacentIndex(ui::ComboboxModel
* model
, int increment
, int index
) {
108 DCHECK(increment
== -1 || increment
== 1);
111 while (index
>= 0 && index
< model
->GetItemCount()) {
112 if (!model
->IsItemSeparatorAt(index
) || !model
->IsItemEnabledAt(index
))
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
,
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
:
135 kFocusedHoveredBodyButtonImages
: kHoveredBodyButtonImages
;
136 case Button::STATE_PRESSED
:
138 kFocusedPressedBodyButtonImages
: kPressedBodyButtonImages
;
145 // Returns the image resource ids of an array for the menu button.
146 const int* GetMenuButtonImageIds(bool focused
,
147 Button::ButtonState 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
:
158 kFocusedHoveredMenuButtonImages
: kHoveredMenuButtonImages
;
159 case Button::STATE_PRESSED
:
161 kFocusedPressedMenuButtonImages
: kPressedMenuButtonImages
;
168 // Returns the images for the menu buttons.
169 std::vector
<const gfx::ImageSkia
*> GetMenuButtonImages(
171 Button::ButtonState state
) {
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
]));
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);
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(
207 const std::vector
<const gfx::ImageSkia
*>& arrow_button_images
,
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
);
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
{
225 ComboboxMenuModelAdapter(Combobox
* owner
, ui::ComboboxModel
* model
)
226 : owner_(owner
), model_(model
) {
227 model_
->AddObserver(this);
230 ~ComboboxMenuModelAdapter() override
{ model_
->RemoveObserver(this); }
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
);
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
{
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
{
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
{
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 ////////////////////////////////////////////////////////////////////////////////
336 Combobox::Combobox(ui::ComboboxModel
* model
)
338 style_(STYLE_NORMAL
),
340 selected_index_(model_
->GetDefaultIndex()),
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) {
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
);
360 Button::ButtonState state
= button_states
[state_index
];
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());
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
) {
396 if (style_
== STYLE_ACTION
)
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
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
)
421 selected_index_
= index
;
425 bool Combobox::SelectValue(const base::string16
& value
) {
426 if (style_
== STYLE_ACTION
)
429 for (int i
= 0; i
< model()->GetItemCount(); ++i
) {
430 if (value
== model()->GetItemAt(i
)) {
438 void Combobox::SetAccessibleName(const base::string16
& name
) {
439 accessible_name_
= name
;
442 void Combobox::SetInvalid(bool invalid
) {
443 if (invalid
== invalid_
)
452 void Combobox::Layout() {
453 PrefixDelegate::Layout();
455 gfx::Insets insets
= GetInsets();
456 int text_button_width
= 0;
457 int arrow_button_width
= 0;
461 arrow_button_width
= width();
465 arrow_button_width
= GetDisclosureArrowLeftPadding() +
466 ArrowSize().width() +
467 GetDisclosureArrowRightPadding();
468 text_button_width
= width() - arrow_button_width
;
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
)
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()) {
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())
537 bool show_menu
= false;
538 int new_index
= kNoSelection
;
539 switch (e
.key_code()) {
540 // Show the menu on F4 without modifiers.
542 if (e
.IsAltDown() || e
.IsAltGrDown() || e
.IsControlDown())
547 // Move to the next item if any, or show the menu on Alt+Down like Windows.
552 new_index
= GetAdjacentIndex(model(), 1, selected_index_
);
555 // Move to the end of the list.
557 case ui::VKEY_NEXT
: // Page down.
558 new_index
= GetAdjacentIndex(model(), -1, model()->GetItemCount());
561 // Move to the beginning of the list.
563 case ui::VKEY_PRIOR
: // Page up.
564 new_index
= GetAdjacentIndex(model(), 1, -1);
567 // Move to the previous item if any.
569 new_index
= GetAdjacentIndex(model(), -1, selected_index_
);
572 // Click the button only when the button style mode.
574 if (style_
== STYLE_ACTION
) {
575 // When pressing space, the click event will be raised after the key is
577 text_button_
->SetState(Button::STATE_PRESSED
);
583 // Click the button only when the button style mode.
584 case ui::VKEY_RETURN
:
585 if (style_
!= STYLE_ACTION
)
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
;
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
)
616 void Combobox::OnPaint(gfx::Canvas
* canvas
) {
619 OnPaintBackground(canvas
);
621 OnPaintBorder(canvas
);
625 PaintButtons(canvas
);
632 void Combobox::OnFocus() {
633 if (GetInputMethod())
634 GetInputMethod()->SetFocusedTextInputClient(GetPrefixSelector());
637 // Border renders differently when focused.
641 void Combobox::OnBlur() {
642 if (GetInputMethod())
643 GetInputMethod()->DetachTextInputClient(GetPrefixSelector());
646 selector_
->OnViewBlur();
647 // Border renders differently when focused.
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
) {
665 if (sender
== text_button_
) {
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
)
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);
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())
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,
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
);
742 ui::NativeTheme::ExtraParams ignored
;
743 GetNativeTheme()->Paint(canvas
->sk_canvas(),
744 ui::NativeTheme::kComboboxArrow
,
745 ui::NativeTheme::kNormal
,
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()));
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()));
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());
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());
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
;
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()) {
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
) {
842 menu_runner_
.reset();
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);
857 // This combobox may be deleted by the listener.
858 base::WeakPtr
<Combobox
> weak_ptr
= weak_ptr_factory_
.GetWeakPtr();
860 listener_
->OnPerformAction(this);
862 if (weak_ptr
&& style_
== STYLE_ACTION
)
866 int Combobox::GetDisclosureArrowLeftPadding() const {
869 return kDisclosureArrowLeftPadding
;
871 return kDisclosureArrowButtonLeftPadding
;
877 int Combobox::GetDisclosureArrowRightPadding() const {
880 return kDisclosureArrowRightPadding
;
882 return kDisclosureArrowButtonRightPadding
;
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() :
897 const ui::NativeTheme
* native_theme_for_arrow
= GetNativeTheme();
900 ui::NativeTheme::ExtraParams ignored
;
901 return native_theme_for_arrow
->GetPartSize(ui::NativeTheme::kComboboxArrow
,
902 ui::NativeTheme::kNormal
,
906 gfx::Size
Combobox::GetContentSize() const {
907 const gfx::FontList
& font_list
= GetFontList();
910 for (int i
= 0; i
< model()->GetItemCount(); ++i
) {
911 if (model_
->IsItemSeparatorAt(i
))
914 if (style_
!= STYLE_ACTION
|| i
== selected_index_
) {
917 gfx::GetStringWidth(menu_model_adapter_
->GetLabelAt(i
), font_list
));
920 return gfx::Size(width
, font_list
.GetHeight());
923 PrefixSelector
* Combobox::GetPrefixSelector() {
925 selector_
.reset(new PrefixSelector(this));
926 return selector_
.get();