Drive: Add BatchableRequest subclass.
[chromium-blink-merge.git] / ui / views / controls / combobox / combobox.cc
blobe1c7586f2de4cbf49502916bd876eecbb9099ab5
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/message_loop/message_loop_proxy.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "ui/accessibility/ax_view_state.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/ime/input_method.h"
37 #include "ui/views/mouse_constants.h"
38 #include "ui/views/painter.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);
278 // static
279 const gfx::FontList& Combobox::GetFontList() {
280 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
281 return rb.GetFontList(ui::ResourceBundle::BaseFont);
284 void Combobox::SetStyle(Style style) {
285 if (style_ == style)
286 return;
288 style_ = style;
289 if (style_ == STYLE_ACTION)
290 selected_index_ = 0;
292 UpdateBorder();
293 UpdateFromModel();
294 PreferredSizeChanged();
297 void Combobox::ModelChanged() {
298 selected_index_ = std::min(0, model_->GetItemCount());
299 UpdateFromModel();
300 PreferredSizeChanged();
303 void Combobox::SetSelectedIndex(int index) {
304 if (style_ == STYLE_ACTION)
305 return;
307 selected_index_ = index;
308 SchedulePaint();
311 bool Combobox::SelectValue(const base::string16& value) {
312 if (style_ == STYLE_ACTION)
313 return false;
315 for (int i = 0; i < model()->GetItemCount(); ++i) {
316 if (value == model()->GetItemAt(i)) {
317 SetSelectedIndex(i);
318 return true;
321 return false;
324 void Combobox::SetAccessibleName(const base::string16& name) {
325 accessible_name_ = name;
328 void Combobox::SetInvalid(bool invalid) {
329 if (invalid == invalid_)
330 return;
332 invalid_ = invalid;
334 UpdateBorder();
335 SchedulePaint();
338 ui::TextInputClient* Combobox::GetTextInputClient() {
339 if (!selector_)
340 selector_.reset(new PrefixSelector(this));
341 return selector_.get();
344 void Combobox::Layout() {
345 PrefixDelegate::Layout();
347 gfx::Insets insets = GetInsets();
348 int text_button_width = 0;
349 int arrow_button_width = 0;
351 switch (style_) {
352 case STYLE_NORMAL: {
353 arrow_button_width = width();
354 break;
356 case STYLE_ACTION: {
357 arrow_button_width = GetDisclosureArrowLeftPadding() +
358 ArrowSize().width() +
359 GetDisclosureArrowRightPadding();
360 text_button_width = width() - arrow_button_width;
361 break;
365 int arrow_button_x = std::max(0, text_button_width);
366 text_button_->SetBounds(0, 0, std::max(0, text_button_width), height());
367 arrow_button_->SetBounds(arrow_button_x, 0, arrow_button_width, height());
370 bool Combobox::IsItemChecked(int id) const {
371 return false;
374 bool Combobox::IsCommandEnabled(int id) const {
375 return model()->IsItemEnabledAt(MenuCommandToIndex(id));
378 void Combobox::ExecuteCommand(int id) {
379 selected_index_ = MenuCommandToIndex(id);
380 OnPerformAction();
383 bool Combobox::GetAccelerator(int id, ui::Accelerator* accel) const {
384 return false;
387 int Combobox::GetRowCount() {
388 return model()->GetItemCount();
391 int Combobox::GetSelectedRow() {
392 return selected_index_;
395 void Combobox::SetSelectedRow(int row) {
396 int prev_index = selected_index_;
397 SetSelectedIndex(row);
398 if (selected_index_ != prev_index)
399 OnPerformAction();
402 base::string16 Combobox::GetTextForRow(int row) {
403 return model()->IsItemSeparatorAt(row) ? base::string16() :
404 model()->GetItemAt(row);
407 ////////////////////////////////////////////////////////////////////////////////
408 // Combobox, View overrides:
410 gfx::Size Combobox::GetPreferredSize() const {
411 // The preferred size will drive the local bounds which in turn is used to set
412 // the minimum width for the dropdown list.
413 gfx::Insets insets = GetInsets();
414 insets += gfx::Insets(Textfield::kTextPadding,
415 Textfield::kTextPadding,
416 Textfield::kTextPadding,
417 Textfield::kTextPadding);
418 int total_width = std::max(kMinComboboxWidth, content_size_.width()) +
419 insets.width() + GetDisclosureArrowLeftPadding() +
420 ArrowSize().width() + GetDisclosureArrowRightPadding();
421 return gfx::Size(total_width, content_size_.height() + insets.height());
424 const char* Combobox::GetClassName() const {
425 return kViewClassName;
428 bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) {
429 // Escape should close the drop down list when it is active, not host UI.
430 if (e.key_code() != ui::VKEY_ESCAPE ||
431 e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) {
432 return false;
434 return dropdown_open_;
437 bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
438 // TODO(oshima): handle IME.
439 DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED);
441 DCHECK_GE(selected_index_, 0);
442 DCHECK_LT(selected_index_, model()->GetItemCount());
443 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
444 selected_index_ = 0;
446 bool show_menu = false;
447 int new_index = kNoSelection;
448 switch (e.key_code()) {
449 // Show the menu on F4 without modifiers.
450 case ui::VKEY_F4:
451 if (e.IsAltDown() || e.IsAltGrDown() || e.IsControlDown())
452 return false;
453 show_menu = true;
454 break;
456 // Move to the next item if any, or show the menu on Alt+Down like Windows.
457 case ui::VKEY_DOWN:
458 if (e.IsAltDown())
459 show_menu = true;
460 else
461 new_index = GetAdjacentIndex(model(), 1, selected_index_);
462 break;
464 // Move to the end of the list.
465 case ui::VKEY_END:
466 case ui::VKEY_NEXT: // Page down.
467 new_index = GetAdjacentIndex(model(), -1, model()->GetItemCount());
468 break;
470 // Move to the beginning of the list.
471 case ui::VKEY_HOME:
472 case ui::VKEY_PRIOR: // Page up.
473 new_index = GetAdjacentIndex(model(), 1, -1);
474 break;
476 // Move to the previous item if any.
477 case ui::VKEY_UP:
478 new_index = GetAdjacentIndex(model(), -1, selected_index_);
479 break;
481 // Click the button only when the button style mode.
482 case ui::VKEY_SPACE:
483 if (style_ == STYLE_ACTION) {
484 // When pressing space, the click event will be raised after the key is
485 // released.
486 text_button_->SetState(Button::STATE_PRESSED);
487 } else {
488 return false;
490 break;
492 // Click the button only when the button style mode.
493 case ui::VKEY_RETURN:
494 if (style_ != STYLE_ACTION)
495 return false;
496 OnPerformAction();
497 break;
499 default:
500 return false;
503 if (show_menu) {
504 UpdateFromModel();
505 ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD);
506 } else if (new_index != selected_index_ && new_index != kNoSelection &&
507 style_ != STYLE_ACTION) {
508 DCHECK(!model()->IsItemSeparatorAt(new_index));
509 selected_index_ = new_index;
510 OnPerformAction();
513 return true;
516 bool Combobox::OnKeyReleased(const ui::KeyEvent& e) {
517 if (style_ != STYLE_ACTION)
518 return false; // crbug.com/127520
520 if (e.key_code() == ui::VKEY_SPACE && style_ == STYLE_ACTION)
521 OnPerformAction();
523 return false;
526 void Combobox::OnPaint(gfx::Canvas* canvas) {
527 switch (style_) {
528 case STYLE_NORMAL: {
529 OnPaintBackground(canvas);
530 PaintText(canvas);
531 OnPaintBorder(canvas);
532 break;
534 case STYLE_ACTION: {
535 PaintButtons(canvas);
536 PaintText(canvas);
537 break;
542 void Combobox::OnFocus() {
543 GetInputMethod()->OnFocus();
544 View::OnFocus();
545 // Border renders differently when focused.
546 SchedulePaint();
549 void Combobox::OnBlur() {
550 GetInputMethod()->OnBlur();
551 if (selector_)
552 selector_->OnViewBlur();
553 // Border renders differently when focused.
554 SchedulePaint();
557 void Combobox::GetAccessibleState(ui::AXViewState* state) {
558 state->role = ui::AX_ROLE_COMBO_BOX;
559 state->name = accessible_name_;
560 state->value = model_->GetItemAt(selected_index_);
561 state->index = selected_index_;
562 state->count = model_->GetItemCount();
565 void Combobox::OnComboboxModelChanged(ui::ComboboxModel* model) {
566 DCHECK_EQ(model, model_);
567 ModelChanged();
570 void Combobox::ButtonPressed(Button* sender, const ui::Event& event) {
571 if (!enabled())
572 return;
574 RequestFocus();
576 if (sender == text_button_) {
577 OnPerformAction();
578 } else {
579 DCHECK_EQ(arrow_button_, sender);
580 // TODO(hajimehoshi): Fix the problem that the arrow button blinks when
581 // cliking this while the dropdown menu is opened.
582 const base::TimeDelta delta = base::Time::Now() - closed_time_;
583 if (delta.InMilliseconds() <= kMinimumMsBetweenButtonClicks)
584 return;
586 ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE;
587 if (event.IsKeyEvent())
588 source_type = ui::MENU_SOURCE_KEYBOARD;
589 else if (event.IsGestureEvent() || event.IsTouchEvent())
590 source_type = ui::MENU_SOURCE_TOUCH;
591 ShowDropDownMenu(source_type);
595 void Combobox::UpdateFromModel() {
596 const gfx::FontList& font_list = Combobox::GetFontList();
598 menu_ = new MenuItemView(this);
599 // MenuRunner owns |menu_|.
600 dropdown_list_menu_runner_.reset(new MenuRunner(menu_, MenuRunner::COMBOBOX));
602 int num_items = model()->GetItemCount();
603 int width = 0;
604 bool text_item_appended = false;
605 for (int i = 0; i < num_items; ++i) {
606 // When STYLE_ACTION is used, the first item and the following separators
607 // are not added to the dropdown menu. It is assumed that the first item is
608 // always selected and rendered on the top of the action button.
609 if (model()->IsItemSeparatorAt(i)) {
610 if (text_item_appended || style_ != STYLE_ACTION)
611 menu_->AppendSeparator();
612 continue;
615 base::string16 text = model()->GetItemAt(i);
617 // Inserting the Unicode formatting characters if necessary so that the
618 // text is displayed correctly in right-to-left UIs.
619 base::i18n::AdjustStringForLocaleDirection(&text);
621 if (style_ != STYLE_ACTION || i > 0) {
622 menu_->AppendMenuItem(i + kFirstMenuItemId, text, MenuItemView::NORMAL);
623 text_item_appended = true;
626 if (style_ != STYLE_ACTION || i == selected_index_)
627 width = std::max(width, gfx::GetStringWidth(text, font_list));
630 content_size_.SetSize(width, font_list.GetHeight());
633 void Combobox::UpdateBorder() {
634 scoped_ptr<FocusableBorder> border(new FocusableBorder());
635 if (style_ == STYLE_ACTION)
636 border->SetInsets(5, 10, 5, 10);
637 if (invalid_)
638 border->SetColor(kWarningColor);
639 SetBorder(border.Pass());
642 void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
643 rect->set_x(GetMirroredXForRect(*rect));
646 void Combobox::PaintText(gfx::Canvas* canvas) {
647 gfx::Insets insets = GetInsets();
648 insets += gfx::Insets(0, Textfield::kTextPadding, 0, Textfield::kTextPadding);
650 gfx::ScopedCanvas scoped_canvas(canvas);
651 canvas->ClipRect(GetContentsBounds());
653 int x = insets.left();
654 int y = insets.top();
655 int text_height = height() - insets.height();
656 SkColor text_color = GetNativeTheme()->GetSystemColor(
657 ui::NativeTheme::kColorId_LabelEnabledColor);
659 DCHECK_GE(selected_index_, 0);
660 DCHECK_LT(selected_index_, model()->GetItemCount());
661 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
662 selected_index_ = 0;
663 base::string16 text = model()->GetItemAt(selected_index_);
665 gfx::Size arrow_size = ArrowSize();
666 int disclosure_arrow_offset = width() - arrow_size.width() -
667 GetDisclosureArrowLeftPadding() - GetDisclosureArrowRightPadding();
669 const gfx::FontList& font_list = Combobox::GetFontList();
670 int text_width = gfx::GetStringWidth(text, font_list);
671 if ((text_width + insets.width()) > disclosure_arrow_offset)
672 text_width = disclosure_arrow_offset - insets.width();
674 gfx::Rect text_bounds(x, y, text_width, text_height);
675 AdjustBoundsForRTLUI(&text_bounds);
676 canvas->DrawStringRect(text, font_list, text_color, text_bounds);
678 int arrow_x = disclosure_arrow_offset + GetDisclosureArrowLeftPadding();
679 gfx::Rect arrow_bounds(arrow_x,
680 height() / 2 - arrow_size.height() / 2,
681 arrow_size.width(),
682 arrow_size.height());
683 AdjustBoundsForRTLUI(&arrow_bounds);
685 // TODO(estade): hack alert! Remove this direct call into CommonTheme. For now
686 // STYLE_ACTION isn't properly themed so we have to override the NativeTheme
687 // behavior. See crbug.com/384071
688 if (style_ == STYLE_ACTION) {
689 ui::CommonThemePaintComboboxArrow(canvas->sk_canvas(), arrow_bounds);
690 } else {
691 ui::NativeTheme::ExtraParams ignored;
692 GetNativeTheme()->Paint(canvas->sk_canvas(),
693 ui::NativeTheme::kComboboxArrow,
694 ui::NativeTheme::kNormal,
695 arrow_bounds,
696 ignored);
700 void Combobox::PaintButtons(gfx::Canvas* canvas) {
701 DCHECK(style_ == STYLE_ACTION);
703 gfx::ScopedCanvas scoped_canvas(canvas);
704 if (base::i18n::IsRTL()) {
705 canvas->Translate(gfx::Vector2d(width(), 0));
706 canvas->Scale(-1, 1);
709 bool focused = HasFocus();
710 const std::vector<const gfx::ImageSkia*>& arrow_button_images =
711 menu_button_images_[focused][
712 arrow_button_->state() == Button::STATE_HOVERED ?
713 Button::STATE_NORMAL : arrow_button_->state()];
715 int text_button_hover_alpha =
716 text_button_->state() == Button::STATE_PRESSED ? 0 :
717 static_cast<int>(static_cast<TransparentButton*>(text_button_)->
718 GetAnimationValue() * 255);
719 if (text_button_hover_alpha < 255) {
720 canvas->SaveLayerAlpha(255 - text_button_hover_alpha);
721 Painter* text_button_painter =
722 body_button_painters_[focused][
723 text_button_->state() == Button::STATE_HOVERED ?
724 Button::STATE_NORMAL : text_button_->state()].get();
725 Painter::PaintPainterAt(canvas, text_button_painter,
726 gfx::Rect(0, 0, text_button_->width(), height()));
727 canvas->Restore();
729 if (0 < text_button_hover_alpha) {
730 canvas->SaveLayerAlpha(text_button_hover_alpha);
731 Painter* text_button_hovered_painter =
732 body_button_painters_[focused][Button::STATE_HOVERED].get();
733 Painter::PaintPainterAt(canvas, text_button_hovered_painter,
734 gfx::Rect(0, 0, text_button_->width(), height()));
735 canvas->Restore();
738 int arrow_button_hover_alpha =
739 arrow_button_->state() == Button::STATE_PRESSED ? 0 :
740 static_cast<int>(static_cast<TransparentButton*>(arrow_button_)->
741 GetAnimationValue() * 255);
742 if (arrow_button_hover_alpha < 255) {
743 canvas->SaveLayerAlpha(255 - arrow_button_hover_alpha);
744 PaintArrowButton(canvas, arrow_button_images, arrow_button_->x(), height());
745 canvas->Restore();
747 if (0 < arrow_button_hover_alpha) {
748 canvas->SaveLayerAlpha(arrow_button_hover_alpha);
749 const std::vector<const gfx::ImageSkia*>& arrow_button_hovered_images =
750 menu_button_images_[focused][Button::STATE_HOVERED];
751 PaintArrowButton(canvas, arrow_button_hovered_images,
752 arrow_button_->x(), height());
753 canvas->Restore();
757 void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
758 if (!dropdown_list_menu_runner_.get())
759 UpdateFromModel();
761 // Extend the menu to the width of the combobox.
762 SubmenuView* submenu = menu_->CreateSubmenu();
763 submenu->set_minimum_preferred_width(
764 size().width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
766 gfx::Rect lb = GetLocalBounds();
767 gfx::Point menu_position(lb.origin());
769 if (style_ == STYLE_NORMAL) {
770 // Inset the menu's requested position so the border of the menu lines up
771 // with the border of the combobox.
772 menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft);
773 menu_position.set_y(menu_position.y() + kMenuBorderWidthTop);
775 lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
777 View::ConvertPointToScreen(this, &menu_position);
778 if (menu_position.x() < 0)
779 menu_position.set_x(0);
781 gfx::Rect bounds(menu_position, lb.size());
783 Button::ButtonState original_state = Button::STATE_NORMAL;
784 if (arrow_button_) {
785 original_state = arrow_button_->state();
786 arrow_button_->SetState(Button::STATE_PRESSED);
788 dropdown_open_ = true;
789 MenuAnchorPosition anchor_position =
790 style_ == STYLE_ACTION ? MENU_ANCHOR_TOPRIGHT : MENU_ANCHOR_TOPLEFT;
791 if (dropdown_list_menu_runner_->RunMenuAt(
792 GetWidget(), NULL, bounds, anchor_position, source_type) ==
793 MenuRunner::MENU_DELETED) {
794 return;
796 dropdown_open_ = false;
797 if (arrow_button_)
798 arrow_button_->SetState(original_state);
799 closed_time_ = base::Time::Now();
801 // Need to explicitly clear mouse handler so that events get sent
802 // properly after the menu finishes running. If we don't do this, then
803 // the first click to other parts of the UI is eaten.
804 SetMouseHandler(NULL);
807 void Combobox::OnPerformAction() {
808 NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, false);
809 SchedulePaint();
811 // This combobox may be deleted by the listener.
812 base::WeakPtr<Combobox> weak_ptr = weak_ptr_factory_.GetWeakPtr();
813 if (listener_)
814 listener_->OnPerformAction(this);
816 if (weak_ptr && style_ == STYLE_ACTION)
817 selected_index_ = 0;
820 int Combobox::MenuCommandToIndex(int menu_command_id) const {
821 // (note that the id received is offset by kFirstMenuItemId)
822 // Revert menu ID offset to map back to combobox model.
823 int index = menu_command_id - kFirstMenuItemId;
824 DCHECK_LT(index, model()->GetItemCount());
825 return index;
828 int Combobox::GetDisclosureArrowLeftPadding() const {
829 switch (style_) {
830 case STYLE_NORMAL:
831 return kDisclosureArrowLeftPadding;
832 case STYLE_ACTION:
833 return kDisclosureArrowButtonLeftPadding;
835 NOTREACHED();
836 return 0;
839 int Combobox::GetDisclosureArrowRightPadding() const {
840 switch (style_) {
841 case STYLE_NORMAL:
842 return kDisclosureArrowRightPadding;
843 case STYLE_ACTION:
844 return kDisclosureArrowButtonRightPadding;
846 NOTREACHED();
847 return 0;
850 gfx::Size Combobox::ArrowSize() const {
851 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
852 // TODO(estade): hack alert! This should always use GetNativeTheme(). For now
853 // STYLE_ACTION isn't properly themed so we have to override the NativeTheme
854 // behavior. See crbug.com/384071
855 const ui::NativeTheme* native_theme_for_arrow = style_ == STYLE_ACTION ?
856 ui::NativeTheme::instance() :
857 GetNativeTheme();
858 #else
859 const ui::NativeTheme* native_theme_for_arrow = GetNativeTheme();
860 #endif
862 ui::NativeTheme::ExtraParams ignored;
863 return native_theme_for_arrow->GetPartSize(ui::NativeTheme::kComboboxArrow,
864 ui::NativeTheme::kNormal,
865 ignored);
868 } // namespace views