Add ICU message format support
[chromium-blink-merge.git] / ui / views / controls / combobox / combobox.cc
blob7d527a9b0108e80457e2fd0b838195017d596376
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/bind.h"
8 #include "base/logging.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "ui/accessibility/ax_view_state.h"
11 #include "ui/base/ime/input_method.h"
12 #include "ui/base/models/combobox_model.h"
13 #include "ui/base/resource/resource_bundle.h"
14 #include "ui/events/event.h"
15 #include "ui/events/keycodes/keyboard_codes.h"
16 #include "ui/gfx/animation/throb_animation.h"
17 #include "ui/gfx/canvas.h"
18 #include "ui/gfx/image/image.h"
19 #include "ui/gfx/scoped_canvas.h"
20 #include "ui/gfx/text_utils.h"
21 #include "ui/native_theme/common_theme.h"
22 #include "ui/native_theme/native_theme.h"
23 #include "ui/resources/grit/ui_resources.h"
24 #include "ui/views/background.h"
25 #include "ui/views/color_constants.h"
26 #include "ui/views/controls/button/custom_button.h"
27 #include "ui/views/controls/button/label_button.h"
28 #include "ui/views/controls/combobox/combobox_listener.h"
29 #include "ui/views/controls/focusable_border.h"
30 #include "ui/views/controls/menu/menu_item_view.h"
31 #include "ui/views/controls/menu/menu_runner.h"
32 #include "ui/views/controls/menu/menu_runner_handler.h"
33 #include "ui/views/controls/menu/submenu_view.h"
34 #include "ui/views/controls/prefix_selector.h"
35 #include "ui/views/controls/textfield/textfield.h"
36 #include "ui/views/mouse_constants.h"
37 #include "ui/views/painter.h"
38 #include "ui/views/resources/grit/views_resources.h"
39 #include "ui/views/widget/widget.h"
41 namespace views {
43 namespace {
45 // Menu border widths
46 const int kMenuBorderWidthLeft = 1;
47 const int kMenuBorderWidthTop = 1;
48 const int kMenuBorderWidthRight = 1;
50 // Limit how small a combobox can be.
51 const int kMinComboboxWidth = 25;
53 // Size of the combobox arrow margins
54 const int kDisclosureArrowLeftPadding = 7;
55 const int kDisclosureArrowRightPadding = 7;
56 const int kDisclosureArrowButtonLeftPadding = 11;
57 const int kDisclosureArrowButtonRightPadding = 12;
59 // Define the id of the first item in the menu (since it needs to be > 0)
60 const int kFirstMenuItemId = 1000;
62 // Used to indicate that no item is currently selected by the user.
63 const int kNoSelection = -1;
65 const int kBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON);
66 const int kHoveredBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_H);
67 const int kPressedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_P);
68 const int kFocusedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_F);
69 const int kFocusedHoveredBodyButtonImages[] =
70 IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_H);
71 const int kFocusedPressedBodyButtonImages[] =
72 IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_P);
74 #define MENU_IMAGE_GRID(x) { \
75 x ## _MENU_TOP, x ## _MENU_CENTER, x ## _MENU_BOTTOM, }
77 const int kMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON);
78 const int kHoveredMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_H);
79 const int kPressedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_P);
80 const int kFocusedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F);
81 const int kFocusedHoveredMenuButtonImages[] =
82 MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_H);
83 const int kFocusedPressedMenuButtonImages[] =
84 MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_P);
86 #undef MENU_IMAGE_GRID
88 // The transparent button which holds a button state but is not rendered.
89 class TransparentButton : public CustomButton {
90 public:
91 TransparentButton(ButtonListener* listener)
92 : CustomButton(listener) {
93 SetAnimationDuration(LabelButton::kHoverAnimationDurationMs);
95 ~TransparentButton() override {}
97 bool OnMousePressed(const ui::MouseEvent& mouse_event) override {
98 parent()->RequestFocus();
99 return true;
102 double GetAnimationValue() const {
103 return hover_animation_->GetCurrentValue();
106 private:
107 DISALLOW_COPY_AND_ASSIGN(TransparentButton);
110 // Returns the next or previous valid index (depending on |increment|'s value).
111 // Skips separator or disabled indices. Returns -1 if there is no valid adjacent
112 // index.
113 int GetAdjacentIndex(ui::ComboboxModel* model, int increment, int index) {
114 DCHECK(increment == -1 || increment == 1);
116 index += increment;
117 while (index >= 0 && index < model->GetItemCount()) {
118 if (!model->IsItemSeparatorAt(index) || !model->IsItemEnabledAt(index))
119 return index;
120 index += increment;
122 return kNoSelection;
125 // Returns the image resource ids of an array for the body button.
127 // TODO(hajimehoshi): This function should return the images for the 'disabled'
128 // status. (crbug/270052)
129 const int* GetBodyButtonImageIds(bool focused,
130 Button::ButtonState state,
131 size_t* num) {
132 DCHECK(num);
133 *num = 9;
134 switch (state) {
135 case Button::STATE_DISABLED:
136 return focused ? kFocusedBodyButtonImages : kBodyButtonImages;
137 case Button::STATE_NORMAL:
138 return focused ? kFocusedBodyButtonImages : kBodyButtonImages;
139 case Button::STATE_HOVERED:
140 return focused ?
141 kFocusedHoveredBodyButtonImages : kHoveredBodyButtonImages;
142 case Button::STATE_PRESSED:
143 return focused ?
144 kFocusedPressedBodyButtonImages : kPressedBodyButtonImages;
145 default:
146 NOTREACHED();
148 return NULL;
151 // Returns the image resource ids of an array for the menu button.
152 const int* GetMenuButtonImageIds(bool focused,
153 Button::ButtonState state,
154 size_t* num) {
155 DCHECK(num);
156 *num = 3;
157 switch (state) {
158 case Button::STATE_DISABLED:
159 return focused ? kFocusedMenuButtonImages : kMenuButtonImages;
160 case Button::STATE_NORMAL:
161 return focused ? kFocusedMenuButtonImages : kMenuButtonImages;
162 case Button::STATE_HOVERED:
163 return focused ?
164 kFocusedHoveredMenuButtonImages : kHoveredMenuButtonImages;
165 case Button::STATE_PRESSED:
166 return focused ?
167 kFocusedPressedMenuButtonImages : kPressedMenuButtonImages;
168 default:
169 NOTREACHED();
171 return NULL;
174 // Returns the images for the menu buttons.
175 std::vector<const gfx::ImageSkia*> GetMenuButtonImages(
176 bool focused,
177 Button::ButtonState state) {
178 const int* ids;
179 size_t num_ids;
180 ids = GetMenuButtonImageIds(focused, state, &num_ids);
181 std::vector<const gfx::ImageSkia*> images;
182 images.reserve(num_ids);
183 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
184 for (size_t i = 0; i < num_ids; i++)
185 images.push_back(rb.GetImageSkiaNamed(ids[i]));
186 return images;
189 // Paints three images in a column at the given location. The center image is
190 // stretched so as to fit the given height.
191 void PaintImagesVertically(gfx::Canvas* canvas,
192 const gfx::ImageSkia& top_image,
193 const gfx::ImageSkia& center_image,
194 const gfx::ImageSkia& bottom_image,
195 int x, int y, int width, int height) {
196 canvas->DrawImageInt(top_image,
197 0, 0, top_image.width(), top_image.height(),
198 x, y, width, top_image.height(), false);
199 y += top_image.height();
200 int center_height = height - top_image.height() - bottom_image.height();
201 canvas->DrawImageInt(center_image,
202 0, 0, center_image.width(), center_image.height(),
203 x, y, width, center_height, false);
204 y += center_height;
205 canvas->DrawImageInt(bottom_image,
206 0, 0, bottom_image.width(), bottom_image.height(),
207 x, y, width, bottom_image.height(), false);
210 // Paints the arrow button.
211 void PaintArrowButton(
212 gfx::Canvas* canvas,
213 const std::vector<const gfx::ImageSkia*>& arrow_button_images,
214 int x, int height) {
215 PaintImagesVertically(canvas,
216 *arrow_button_images[0],
217 *arrow_button_images[1],
218 *arrow_button_images[2],
219 x, 0, arrow_button_images[0]->width(), height);
222 } // namespace
224 // static
225 const char Combobox::kViewClassName[] = "views/Combobox";
227 ////////////////////////////////////////////////////////////////////////////////
228 // Combobox, public:
230 Combobox::Combobox(ui::ComboboxModel* model)
231 : model_(model),
232 style_(STYLE_NORMAL),
233 listener_(NULL),
234 selected_index_(model_->GetDefaultIndex()),
235 invalid_(false),
236 menu_(NULL),
237 dropdown_open_(false),
238 text_button_(new TransparentButton(this)),
239 arrow_button_(new TransparentButton(this)),
240 weak_ptr_factory_(this) {
241 model_->AddObserver(this);
242 UpdateFromModel();
243 SetFocusable(true);
244 UpdateBorder();
246 // Initialize the button images.
247 Button::ButtonState button_states[] = {
248 Button::STATE_DISABLED,
249 Button::STATE_NORMAL,
250 Button::STATE_HOVERED,
251 Button::STATE_PRESSED,
253 for (int i = 0; i < 2; i++) {
254 for (size_t state_index = 0; state_index < arraysize(button_states);
255 state_index++) {
256 Button::ButtonState state = button_states[state_index];
257 size_t num;
258 bool focused = !!i;
259 const int* ids = GetBodyButtonImageIds(focused, state, &num);
260 body_button_painters_[focused][state].reset(
261 Painter::CreateImageGridPainter(ids));
262 menu_button_images_[focused][state] = GetMenuButtonImages(focused, state);
266 text_button_->SetVisible(true);
267 arrow_button_->SetVisible(true);
268 text_button_->SetFocusable(false);
269 arrow_button_->SetFocusable(false);
270 AddChildView(text_button_);
271 AddChildView(arrow_button_);
274 Combobox::~Combobox() {
275 model_->RemoveObserver(this);
277 if (GetInputMethod() && selector_.get()) {
278 // Combobox should have been blurred before destroy.
279 DCHECK(selector_.get() != GetInputMethod()->GetTextInputClient());
283 // static
284 const gfx::FontList& Combobox::GetFontList() {
285 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
286 return rb.GetFontList(ui::ResourceBundle::BaseFont);
289 void Combobox::SetStyle(Style style) {
290 if (style_ == style)
291 return;
293 style_ = style;
294 if (style_ == STYLE_ACTION)
295 selected_index_ = 0;
297 UpdateBorder();
298 UpdateFromModel();
299 PreferredSizeChanged();
302 void Combobox::ModelChanged() {
303 selected_index_ = std::min(0, model_->GetItemCount());
304 UpdateFromModel();
305 PreferredSizeChanged();
308 void Combobox::SetSelectedIndex(int index) {
309 if (style_ == STYLE_ACTION)
310 return;
312 selected_index_ = index;
313 SchedulePaint();
316 bool Combobox::SelectValue(const base::string16& value) {
317 if (style_ == STYLE_ACTION)
318 return false;
320 for (int i = 0; i < model()->GetItemCount(); ++i) {
321 if (value == model()->GetItemAt(i)) {
322 SetSelectedIndex(i);
323 return true;
326 return false;
329 void Combobox::SetAccessibleName(const base::string16& name) {
330 accessible_name_ = name;
333 void Combobox::SetInvalid(bool invalid) {
334 if (invalid == invalid_)
335 return;
337 invalid_ = invalid;
339 UpdateBorder();
340 SchedulePaint();
343 void Combobox::Layout() {
344 PrefixDelegate::Layout();
346 gfx::Insets insets = GetInsets();
347 int text_button_width = 0;
348 int arrow_button_width = 0;
350 switch (style_) {
351 case STYLE_NORMAL: {
352 arrow_button_width = width();
353 break;
355 case STYLE_ACTION: {
356 arrow_button_width = GetDisclosureArrowLeftPadding() +
357 ArrowSize().width() +
358 GetDisclosureArrowRightPadding();
359 text_button_width = width() - arrow_button_width;
360 break;
364 int arrow_button_x = std::max(0, text_button_width);
365 text_button_->SetBounds(0, 0, std::max(0, text_button_width), height());
366 arrow_button_->SetBounds(arrow_button_x, 0, arrow_button_width, height());
369 bool Combobox::IsItemChecked(int id) const {
370 return false;
373 bool Combobox::IsCommandEnabled(int id) const {
374 return model()->IsItemEnabledAt(MenuCommandToIndex(id));
377 void Combobox::ExecuteCommand(int id) {
378 selected_index_ = MenuCommandToIndex(id);
379 OnPerformAction();
382 bool Combobox::GetAccelerator(int id, ui::Accelerator* accel) const {
383 return false;
386 int Combobox::GetRowCount() {
387 return model()->GetItemCount();
390 int Combobox::GetSelectedRow() {
391 return selected_index_;
394 void Combobox::SetSelectedRow(int row) {
395 int prev_index = selected_index_;
396 SetSelectedIndex(row);
397 if (selected_index_ != prev_index)
398 OnPerformAction();
401 base::string16 Combobox::GetTextForRow(int row) {
402 return model()->IsItemSeparatorAt(row) ? base::string16() :
403 model()->GetItemAt(row);
406 ////////////////////////////////////////////////////////////////////////////////
407 // Combobox, View overrides:
409 gfx::Size Combobox::GetPreferredSize() const {
410 // The preferred size will drive the local bounds which in turn is used to set
411 // the minimum width for the dropdown list.
412 gfx::Insets insets = GetInsets();
413 insets += gfx::Insets(Textfield::kTextPadding,
414 Textfield::kTextPadding,
415 Textfield::kTextPadding,
416 Textfield::kTextPadding);
417 int total_width = std::max(kMinComboboxWidth, content_size_.width()) +
418 insets.width() + GetDisclosureArrowLeftPadding() +
419 ArrowSize().width() + GetDisclosureArrowRightPadding();
420 return gfx::Size(total_width, content_size_.height() + insets.height());
423 const char* Combobox::GetClassName() const {
424 return kViewClassName;
427 bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) {
428 // Escape should close the drop down list when it is active, not host UI.
429 if (e.key_code() != ui::VKEY_ESCAPE ||
430 e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) {
431 return false;
433 return dropdown_open_;
436 bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
437 // TODO(oshima): handle IME.
438 DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED);
440 DCHECK_GE(selected_index_, 0);
441 DCHECK_LT(selected_index_, model()->GetItemCount());
442 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
443 selected_index_ = 0;
445 bool show_menu = false;
446 int new_index = kNoSelection;
447 switch (e.key_code()) {
448 // Show the menu on F4 without modifiers.
449 case ui::VKEY_F4:
450 if (e.IsAltDown() || e.IsAltGrDown() || e.IsControlDown())
451 return false;
452 show_menu = true;
453 break;
455 // Move to the next item if any, or show the menu on Alt+Down like Windows.
456 case ui::VKEY_DOWN:
457 if (e.IsAltDown())
458 show_menu = true;
459 else
460 new_index = GetAdjacentIndex(model(), 1, selected_index_);
461 break;
463 // Move to the end of the list.
464 case ui::VKEY_END:
465 case ui::VKEY_NEXT: // Page down.
466 new_index = GetAdjacentIndex(model(), -1, model()->GetItemCount());
467 break;
469 // Move to the beginning of the list.
470 case ui::VKEY_HOME:
471 case ui::VKEY_PRIOR: // Page up.
472 new_index = GetAdjacentIndex(model(), 1, -1);
473 break;
475 // Move to the previous item if any.
476 case ui::VKEY_UP:
477 new_index = GetAdjacentIndex(model(), -1, selected_index_);
478 break;
480 // Click the button only when the button style mode.
481 case ui::VKEY_SPACE:
482 if (style_ == STYLE_ACTION) {
483 // When pressing space, the click event will be raised after the key is
484 // released.
485 text_button_->SetState(Button::STATE_PRESSED);
486 } else {
487 return false;
489 break;
491 // Click the button only when the button style mode.
492 case ui::VKEY_RETURN:
493 if (style_ != STYLE_ACTION)
494 return false;
495 OnPerformAction();
496 break;
498 default:
499 return false;
502 if (show_menu) {
503 UpdateFromModel();
504 ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD);
505 } else if (new_index != selected_index_ && new_index != kNoSelection &&
506 style_ != STYLE_ACTION) {
507 DCHECK(!model()->IsItemSeparatorAt(new_index));
508 selected_index_ = new_index;
509 OnPerformAction();
512 return true;
515 bool Combobox::OnKeyReleased(const ui::KeyEvent& e) {
516 if (style_ != STYLE_ACTION)
517 return false; // crbug.com/127520
519 if (e.key_code() == ui::VKEY_SPACE && style_ == STYLE_ACTION)
520 OnPerformAction();
522 return false;
525 void Combobox::OnPaint(gfx::Canvas* canvas) {
526 switch (style_) {
527 case STYLE_NORMAL: {
528 OnPaintBackground(canvas);
529 PaintText(canvas);
530 OnPaintBorder(canvas);
531 break;
533 case STYLE_ACTION: {
534 PaintButtons(canvas);
535 PaintText(canvas);
536 break;
541 void Combobox::OnFocus() {
542 if (GetInputMethod())
543 GetInputMethod()->SetFocusedTextInputClient(GetPrefixSelector());
545 View::OnFocus();
546 // Border renders differently when focused.
547 SchedulePaint();
550 void Combobox::OnBlur() {
551 if (GetInputMethod())
552 GetInputMethod()->DetachTextInputClient(GetPrefixSelector());
554 if (selector_)
555 selector_->OnViewBlur();
556 // Border renders differently when focused.
557 SchedulePaint();
560 void Combobox::GetAccessibleState(ui::AXViewState* state) {
561 state->role = ui::AX_ROLE_COMBO_BOX;
562 state->name = accessible_name_;
563 state->value = model_->GetItemAt(selected_index_);
564 state->index = selected_index_;
565 state->count = model_->GetItemCount();
568 void Combobox::OnComboboxModelChanged(ui::ComboboxModel* model) {
569 DCHECK_EQ(model, model_);
570 ModelChanged();
573 void Combobox::ButtonPressed(Button* sender, const ui::Event& event) {
574 if (!enabled())
575 return;
577 RequestFocus();
579 if (sender == text_button_) {
580 OnPerformAction();
581 } else {
582 DCHECK_EQ(arrow_button_, sender);
583 // TODO(hajimehoshi): Fix the problem that the arrow button blinks when
584 // cliking this while the dropdown menu is opened.
585 const base::TimeDelta delta = base::Time::Now() - closed_time_;
586 if (delta.InMilliseconds() <= kMinimumMsBetweenButtonClicks)
587 return;
589 ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE;
590 if (event.IsKeyEvent())
591 source_type = ui::MENU_SOURCE_KEYBOARD;
592 else if (event.IsGestureEvent() || event.IsTouchEvent())
593 source_type = ui::MENU_SOURCE_TOUCH;
594 ShowDropDownMenu(source_type);
598 void Combobox::UpdateFromModel() {
599 const gfx::FontList& font_list = Combobox::GetFontList();
601 menu_ = new MenuItemView(this);
602 // MenuRunner owns |menu_|.
603 dropdown_list_menu_runner_.reset(new MenuRunner(menu_, MenuRunner::COMBOBOX));
605 int num_items = model()->GetItemCount();
606 int width = 0;
607 bool text_item_appended = false;
608 for (int i = 0; i < num_items; ++i) {
609 // When STYLE_ACTION is used, the first item and the following separators
610 // are not added to the dropdown menu. It is assumed that the first item is
611 // always selected and rendered on the top of the action button.
612 if (model()->IsItemSeparatorAt(i)) {
613 if (text_item_appended || style_ != STYLE_ACTION)
614 menu_->AppendSeparator();
615 continue;
618 base::string16 text = model()->GetItemAt(i);
620 // Inserting the Unicode formatting characters if necessary so that the
621 // text is displayed correctly in right-to-left UIs.
622 base::i18n::AdjustStringForLocaleDirection(&text);
624 if (style_ != STYLE_ACTION || i > 0) {
625 menu_->AppendMenuItem(i + kFirstMenuItemId, text, MenuItemView::NORMAL);
626 text_item_appended = true;
629 if (style_ != STYLE_ACTION || i == selected_index_)
630 width = std::max(width, gfx::GetStringWidth(text, font_list));
633 content_size_.SetSize(width, font_list.GetHeight());
636 void Combobox::UpdateBorder() {
637 scoped_ptr<FocusableBorder> border(new FocusableBorder());
638 if (style_ == STYLE_ACTION)
639 border->SetInsets(5, 10, 5, 10);
640 if (invalid_)
641 border->SetColor(kWarningColor);
642 SetBorder(border.Pass());
645 void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
646 rect->set_x(GetMirroredXForRect(*rect));
649 void Combobox::PaintText(gfx::Canvas* canvas) {
650 gfx::Insets insets = GetInsets();
651 insets += gfx::Insets(0, Textfield::kTextPadding, 0, Textfield::kTextPadding);
653 gfx::ScopedCanvas scoped_canvas(canvas);
654 canvas->ClipRect(GetContentsBounds());
656 int x = insets.left();
657 int y = insets.top();
658 int text_height = height() - insets.height();
659 SkColor text_color = GetNativeTheme()->GetSystemColor(
660 ui::NativeTheme::kColorId_LabelEnabledColor);
662 DCHECK_GE(selected_index_, 0);
663 DCHECK_LT(selected_index_, model()->GetItemCount());
664 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
665 selected_index_ = 0;
666 base::string16 text = model()->GetItemAt(selected_index_);
668 gfx::Size arrow_size = ArrowSize();
669 int disclosure_arrow_offset = width() - arrow_size.width() -
670 GetDisclosureArrowLeftPadding() - GetDisclosureArrowRightPadding();
672 const gfx::FontList& font_list = Combobox::GetFontList();
673 int text_width = gfx::GetStringWidth(text, font_list);
674 if ((text_width + insets.width()) > disclosure_arrow_offset)
675 text_width = disclosure_arrow_offset - insets.width();
677 gfx::Rect text_bounds(x, y, text_width, text_height);
678 AdjustBoundsForRTLUI(&text_bounds);
679 canvas->DrawStringRect(text, font_list, text_color, text_bounds);
681 int arrow_x = disclosure_arrow_offset + GetDisclosureArrowLeftPadding();
682 gfx::Rect arrow_bounds(arrow_x,
683 height() / 2 - arrow_size.height() / 2,
684 arrow_size.width(),
685 arrow_size.height());
686 AdjustBoundsForRTLUI(&arrow_bounds);
688 // TODO(estade): hack alert! Remove this direct call into CommonTheme. For now
689 // STYLE_ACTION isn't properly themed so we have to override the NativeTheme
690 // behavior. See crbug.com/384071
691 if (style_ == STYLE_ACTION) {
692 ui::CommonThemePaintComboboxArrow(canvas->sk_canvas(), arrow_bounds);
693 } else {
694 ui::NativeTheme::ExtraParams ignored;
695 GetNativeTheme()->Paint(canvas->sk_canvas(),
696 ui::NativeTheme::kComboboxArrow,
697 ui::NativeTheme::kNormal,
698 arrow_bounds,
699 ignored);
703 void Combobox::PaintButtons(gfx::Canvas* canvas) {
704 DCHECK(style_ == STYLE_ACTION);
706 gfx::ScopedCanvas scoped_canvas(canvas);
707 if (base::i18n::IsRTL()) {
708 canvas->Translate(gfx::Vector2d(width(), 0));
709 canvas->Scale(-1, 1);
712 bool focused = HasFocus();
713 const std::vector<const gfx::ImageSkia*>& arrow_button_images =
714 menu_button_images_[focused][
715 arrow_button_->state() == Button::STATE_HOVERED ?
716 Button::STATE_NORMAL : arrow_button_->state()];
718 int text_button_hover_alpha =
719 text_button_->state() == Button::STATE_PRESSED ? 0 :
720 static_cast<int>(static_cast<TransparentButton*>(text_button_)->
721 GetAnimationValue() * 255);
722 if (text_button_hover_alpha < 255) {
723 canvas->SaveLayerAlpha(255 - text_button_hover_alpha);
724 Painter* text_button_painter =
725 body_button_painters_[focused][
726 text_button_->state() == Button::STATE_HOVERED ?
727 Button::STATE_NORMAL : text_button_->state()].get();
728 Painter::PaintPainterAt(canvas, text_button_painter,
729 gfx::Rect(0, 0, text_button_->width(), height()));
730 canvas->Restore();
732 if (0 < text_button_hover_alpha) {
733 canvas->SaveLayerAlpha(text_button_hover_alpha);
734 Painter* text_button_hovered_painter =
735 body_button_painters_[focused][Button::STATE_HOVERED].get();
736 Painter::PaintPainterAt(canvas, text_button_hovered_painter,
737 gfx::Rect(0, 0, text_button_->width(), height()));
738 canvas->Restore();
741 int arrow_button_hover_alpha =
742 arrow_button_->state() == Button::STATE_PRESSED ? 0 :
743 static_cast<int>(static_cast<TransparentButton*>(arrow_button_)->
744 GetAnimationValue() * 255);
745 if (arrow_button_hover_alpha < 255) {
746 canvas->SaveLayerAlpha(255 - arrow_button_hover_alpha);
747 PaintArrowButton(canvas, arrow_button_images, arrow_button_->x(), height());
748 canvas->Restore();
750 if (0 < arrow_button_hover_alpha) {
751 canvas->SaveLayerAlpha(arrow_button_hover_alpha);
752 const std::vector<const gfx::ImageSkia*>& arrow_button_hovered_images =
753 menu_button_images_[focused][Button::STATE_HOVERED];
754 PaintArrowButton(canvas, arrow_button_hovered_images,
755 arrow_button_->x(), height());
756 canvas->Restore();
760 void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
761 if (!dropdown_list_menu_runner_.get())
762 UpdateFromModel();
764 // Extend the menu to the width of the combobox.
765 SubmenuView* submenu = menu_->CreateSubmenu();
766 submenu->set_minimum_preferred_width(
767 size().width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
769 gfx::Rect lb = GetLocalBounds();
770 gfx::Point menu_position(lb.origin());
772 if (style_ == STYLE_NORMAL) {
773 // Inset the menu's requested position so the border of the menu lines up
774 // with the border of the combobox.
775 menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft);
776 menu_position.set_y(menu_position.y() + kMenuBorderWidthTop);
778 lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
780 View::ConvertPointToScreen(this, &menu_position);
781 if (menu_position.x() < 0)
782 menu_position.set_x(0);
784 gfx::Rect bounds(menu_position, lb.size());
786 Button::ButtonState original_state = Button::STATE_NORMAL;
787 if (arrow_button_) {
788 original_state = arrow_button_->state();
789 arrow_button_->SetState(Button::STATE_PRESSED);
791 dropdown_open_ = true;
792 MenuAnchorPosition anchor_position =
793 style_ == STYLE_ACTION ? MENU_ANCHOR_TOPRIGHT : MENU_ANCHOR_TOPLEFT;
794 if (dropdown_list_menu_runner_->RunMenuAt(
795 GetWidget(), NULL, bounds, anchor_position, source_type) ==
796 MenuRunner::MENU_DELETED) {
797 return;
799 dropdown_open_ = false;
800 if (arrow_button_)
801 arrow_button_->SetState(original_state);
802 closed_time_ = base::Time::Now();
804 // Need to explicitly clear mouse handler so that events get sent
805 // properly after the menu finishes running. If we don't do this, then
806 // the first click to other parts of the UI is eaten.
807 SetMouseHandler(NULL);
810 void Combobox::OnPerformAction() {
811 NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, false);
812 SchedulePaint();
814 // This combobox may be deleted by the listener.
815 base::WeakPtr<Combobox> weak_ptr = weak_ptr_factory_.GetWeakPtr();
816 if (listener_)
817 listener_->OnPerformAction(this);
819 if (weak_ptr && style_ == STYLE_ACTION)
820 selected_index_ = 0;
823 int Combobox::MenuCommandToIndex(int menu_command_id) const {
824 // (note that the id received is offset by kFirstMenuItemId)
825 // Revert menu ID offset to map back to combobox model.
826 int index = menu_command_id - kFirstMenuItemId;
827 DCHECK_LT(index, model()->GetItemCount());
828 return index;
831 int Combobox::GetDisclosureArrowLeftPadding() const {
832 switch (style_) {
833 case STYLE_NORMAL:
834 return kDisclosureArrowLeftPadding;
835 case STYLE_ACTION:
836 return kDisclosureArrowButtonLeftPadding;
838 NOTREACHED();
839 return 0;
842 int Combobox::GetDisclosureArrowRightPadding() const {
843 switch (style_) {
844 case STYLE_NORMAL:
845 return kDisclosureArrowRightPadding;
846 case STYLE_ACTION:
847 return kDisclosureArrowButtonRightPadding;
849 NOTREACHED();
850 return 0;
853 gfx::Size Combobox::ArrowSize() const {
854 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
855 // TODO(estade): hack alert! This should always use GetNativeTheme(). For now
856 // STYLE_ACTION isn't properly themed so we have to override the NativeTheme
857 // behavior. See crbug.com/384071
858 const ui::NativeTheme* native_theme_for_arrow = style_ == STYLE_ACTION ?
859 ui::NativeTheme::instance() :
860 GetNativeTheme();
861 #else
862 const ui::NativeTheme* native_theme_for_arrow = GetNativeTheme();
863 #endif
865 ui::NativeTheme::ExtraParams ignored;
866 return native_theme_for_arrow->GetPartSize(ui::NativeTheme::kComboboxArrow,
867 ui::NativeTheme::kNormal,
868 ignored);
871 PrefixSelector* Combobox::GetPrefixSelector() {
872 if (!selector_)
873 selector_.reset(new PrefixSelector(this));
874 return selector_.get();
877 } // namespace views