Demonstrate the basic functionality of the File System
[chromium-blink-merge.git] / ui / views / controls / combobox / combobox.cc
blobb2a456d49063ad04e826d677da03590e80add0ba
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 "grit/ui_resources.h"
12 #include "ui/accessibility/ax_view_state.h"
13 #include "ui/base/models/combobox_model.h"
14 #include "ui/base/resource/resource_bundle.h"
15 #include "ui/events/event.h"
16 #include "ui/events/keycodes/keyboard_codes.h"
17 #include "ui/gfx/animation/throb_animation.h"
18 #include "ui/gfx/canvas.h"
19 #include "ui/gfx/image/image.h"
20 #include "ui/gfx/scoped_canvas.h"
21 #include "ui/gfx/text_utils.h"
22 #include "ui/native_theme/common_theme.h"
23 #include "ui/native_theme/native_theme.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/ime/input_method.h"
36 #include "ui/views/mouse_constants.h"
37 #include "ui/views/painter.h"
38 #include "ui/views/widget/widget.h"
40 namespace views {
42 namespace {
44 // Menu border widths
45 const int kMenuBorderWidthLeft = 1;
46 const int kMenuBorderWidthTop = 1;
47 const int kMenuBorderWidthRight = 1;
49 // Limit how small a combobox can be.
50 const int kMinComboboxWidth = 25;
52 // Size of the combobox arrow margins
53 const int kDisclosureArrowLeftPadding = 7;
54 const int kDisclosureArrowRightPadding = 7;
55 const int kDisclosureArrowButtonLeftPadding = 11;
56 const int kDisclosureArrowButtonRightPadding = 12;
58 // Define the id of the first item in the menu (since it needs to be > 0)
59 const int kFirstMenuItemId = 1000;
61 // Used to indicate that no item is currently selected by the user.
62 const int kNoSelection = -1;
64 const int kBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON);
65 const int kHoveredBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_H);
66 const int kPressedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_P);
67 const int kFocusedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_F);
68 const int kFocusedHoveredBodyButtonImages[] =
69 IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_H);
70 const int kFocusedPressedBodyButtonImages[] =
71 IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_P);
73 #define MENU_IMAGE_GRID(x) { \
74 x ## _MENU_TOP, x ## _MENU_CENTER, x ## _MENU_BOTTOM, }
76 const int kMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON);
77 const int kHoveredMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_H);
78 const int kPressedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_P);
79 const int kFocusedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F);
80 const int kFocusedHoveredMenuButtonImages[] =
81 MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_H);
82 const int kFocusedPressedMenuButtonImages[] =
83 MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_P);
85 #undef MENU_IMAGE_GRID
87 // The transparent button which holds a button state but is not rendered.
88 class TransparentButton : public CustomButton {
89 public:
90 TransparentButton(ButtonListener* listener)
91 : CustomButton(listener) {
92 SetAnimationDuration(LabelButton::kHoverAnimationDurationMs);
94 virtual ~TransparentButton() {}
96 virtual bool OnMousePressed(const ui::MouseEvent& mouse_event) OVERRIDE {
97 parent()->RequestFocus();
98 return true;
101 double GetAnimationValue() const {
102 return hover_animation_->GetCurrentValue();
105 private:
106 DISALLOW_COPY_AND_ASSIGN(TransparentButton);
109 // Returns the next or previous valid index (depending on |increment|'s value).
110 // Skips separator or disabled indices. Returns -1 if there is no valid adjacent
111 // index.
112 int GetAdjacentIndex(ui::ComboboxModel* model, int increment, int index) {
113 DCHECK(increment == -1 || increment == 1);
115 index += increment;
116 while (index >= 0 && index < model->GetItemCount()) {
117 if (!model->IsItemSeparatorAt(index) || !model->IsItemEnabledAt(index))
118 return index;
119 index += increment;
121 return kNoSelection;
124 // Returns the image resource ids of an array for the body button.
126 // TODO(hajimehoshi): This function should return the images for the 'disabled'
127 // status. (crbug/270052)
128 const int* GetBodyButtonImageIds(bool focused,
129 Button::ButtonState state,
130 size_t* num) {
131 DCHECK(num);
132 *num = 9;
133 switch (state) {
134 case Button::STATE_DISABLED:
135 return focused ? kFocusedBodyButtonImages : kBodyButtonImages;
136 case Button::STATE_NORMAL:
137 return focused ? kFocusedBodyButtonImages : kBodyButtonImages;
138 case Button::STATE_HOVERED:
139 return focused ?
140 kFocusedHoveredBodyButtonImages : kHoveredBodyButtonImages;
141 case Button::STATE_PRESSED:
142 return focused ?
143 kFocusedPressedBodyButtonImages : kPressedBodyButtonImages;
144 default:
145 NOTREACHED();
147 return NULL;
150 // Returns the image resource ids of an array for the menu button.
151 const int* GetMenuButtonImageIds(bool focused,
152 Button::ButtonState state,
153 size_t* num) {
154 DCHECK(num);
155 *num = 3;
156 switch (state) {
157 case Button::STATE_DISABLED:
158 return focused ? kFocusedMenuButtonImages : kMenuButtonImages;
159 case Button::STATE_NORMAL:
160 return focused ? kFocusedMenuButtonImages : kMenuButtonImages;
161 case Button::STATE_HOVERED:
162 return focused ?
163 kFocusedHoveredMenuButtonImages : kHoveredMenuButtonImages;
164 case Button::STATE_PRESSED:
165 return focused ?
166 kFocusedPressedMenuButtonImages : kPressedMenuButtonImages;
167 default:
168 NOTREACHED();
170 return NULL;
173 // Returns the images for the menu buttons.
174 std::vector<const gfx::ImageSkia*> GetMenuButtonImages(
175 bool focused,
176 Button::ButtonState state) {
177 const int* ids;
178 size_t num_ids;
179 ids = GetMenuButtonImageIds(focused, state, &num_ids);
180 std::vector<const gfx::ImageSkia*> images;
181 images.reserve(num_ids);
182 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
183 for (size_t i = 0; i < num_ids; i++)
184 images.push_back(rb.GetImageSkiaNamed(ids[i]));
185 return images;
188 // Paints three images in a column at the given location. The center image is
189 // stretched so as to fit the given height.
190 void PaintImagesVertically(gfx::Canvas* canvas,
191 const gfx::ImageSkia& top_image,
192 const gfx::ImageSkia& center_image,
193 const gfx::ImageSkia& bottom_image,
194 int x, int y, int width, int height) {
195 canvas->DrawImageInt(top_image,
196 0, 0, top_image.width(), top_image.height(),
197 x, y, width, top_image.height(), false);
198 y += top_image.height();
199 int center_height = height - top_image.height() - bottom_image.height();
200 canvas->DrawImageInt(center_image,
201 0, 0, center_image.width(), center_image.height(),
202 x, y, width, center_height, false);
203 y += center_height;
204 canvas->DrawImageInt(bottom_image,
205 0, 0, bottom_image.width(), bottom_image.height(),
206 x, y, width, bottom_image.height(), false);
209 // Paints the arrow button.
210 void PaintArrowButton(
211 gfx::Canvas* canvas,
212 const std::vector<const gfx::ImageSkia*>& arrow_button_images,
213 int x, int height) {
214 PaintImagesVertically(canvas,
215 *arrow_button_images[0],
216 *arrow_button_images[1],
217 *arrow_button_images[2],
218 x, 0, arrow_button_images[0]->width(), height);
221 } // namespace
223 // static
224 const char Combobox::kViewClassName[] = "views/Combobox";
226 ////////////////////////////////////////////////////////////////////////////////
227 // Combobox, public:
229 Combobox::Combobox(ui::ComboboxModel* model)
230 : model_(model),
231 style_(STYLE_NORMAL),
232 listener_(NULL),
233 selected_index_(model_->GetDefaultIndex()),
234 invalid_(false),
235 dropdown_open_(false),
236 text_button_(new TransparentButton(this)),
237 arrow_button_(new TransparentButton(this)),
238 weak_ptr_factory_(this) {
239 model_->AddObserver(this);
240 UpdateFromModel();
241 SetFocusable(true);
242 UpdateBorder();
244 // Initialize the button images.
245 Button::ButtonState button_states[] = {
246 Button::STATE_DISABLED,
247 Button::STATE_NORMAL,
248 Button::STATE_HOVERED,
249 Button::STATE_PRESSED,
251 for (int i = 0; i < 2; i++) {
252 for (size_t state_index = 0; state_index < arraysize(button_states);
253 state_index++) {
254 Button::ButtonState state = button_states[state_index];
255 size_t num;
256 bool focused = !!i;
257 const int* ids = GetBodyButtonImageIds(focused, state, &num);
258 body_button_painters_[focused][state].reset(
259 Painter::CreateImageGridPainter(ids));
260 menu_button_images_[focused][state] = GetMenuButtonImages(focused, state);
264 text_button_->SetVisible(true);
265 arrow_button_->SetVisible(true);
266 text_button_->SetFocusable(false);
267 arrow_button_->SetFocusable(false);
268 AddChildView(text_button_);
269 AddChildView(arrow_button_);
272 Combobox::~Combobox() {
273 model_->RemoveObserver(this);
276 // static
277 const gfx::FontList& Combobox::GetFontList() {
278 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
279 return rb.GetFontList(ui::ResourceBundle::BaseFont);
282 void Combobox::SetStyle(Style style) {
283 if (style_ == style)
284 return;
286 style_ = style;
287 if (style_ == STYLE_ACTION)
288 selected_index_ = 0;
290 UpdateBorder();
291 UpdateFromModel();
292 PreferredSizeChanged();
295 void Combobox::ModelChanged() {
296 selected_index_ = std::min(0, model_->GetItemCount());
297 UpdateFromModel();
298 PreferredSizeChanged();
301 void Combobox::SetSelectedIndex(int index) {
302 if (style_ == STYLE_ACTION)
303 return;
305 selected_index_ = index;
306 SchedulePaint();
309 bool Combobox::SelectValue(const base::string16& value) {
310 if (style_ == STYLE_ACTION)
311 return false;
313 for (int i = 0; i < model()->GetItemCount(); ++i) {
314 if (value == model()->GetItemAt(i)) {
315 SetSelectedIndex(i);
316 return true;
319 return false;
322 void Combobox::SetAccessibleName(const base::string16& name) {
323 accessible_name_ = name;
326 void Combobox::SetInvalid(bool invalid) {
327 if (invalid == invalid_)
328 return;
330 invalid_ = invalid;
332 UpdateBorder();
333 SchedulePaint();
336 ui::TextInputClient* Combobox::GetTextInputClient() {
337 if (!selector_)
338 selector_.reset(new PrefixSelector(this));
339 return selector_.get();
342 void Combobox::Layout() {
343 PrefixDelegate::Layout();
345 gfx::Insets insets = GetInsets();
346 int text_button_width = 0;
347 int arrow_button_width = 0;
349 switch (style_) {
350 case STYLE_NORMAL: {
351 arrow_button_width = width();
352 break;
354 case STYLE_ACTION: {
355 arrow_button_width = GetDisclosureArrowLeftPadding() +
356 ArrowSize().width() +
357 GetDisclosureArrowRightPadding();
358 text_button_width = width() - arrow_button_width;
359 break;
363 int arrow_button_x = std::max(0, text_button_width);
364 text_button_->SetBounds(0, 0, std::max(0, text_button_width), height());
365 arrow_button_->SetBounds(arrow_button_x, 0, arrow_button_width, height());
368 bool Combobox::IsItemChecked(int id) const {
369 return false;
372 bool Combobox::IsCommandEnabled(int id) const {
373 return model()->IsItemEnabledAt(MenuCommandToIndex(id));
376 void Combobox::ExecuteCommand(int id) {
377 selected_index_ = MenuCommandToIndex(id);
378 OnPerformAction();
381 bool Combobox::GetAccelerator(int id, ui::Accelerator* accel) const {
382 return false;
385 int Combobox::GetRowCount() {
386 return model()->GetItemCount();
389 int Combobox::GetSelectedRow() {
390 return selected_index_;
393 void Combobox::SetSelectedRow(int row) {
394 int prev_index = selected_index_;
395 SetSelectedIndex(row);
396 if (selected_index_ != prev_index)
397 OnPerformAction();
400 base::string16 Combobox::GetTextForRow(int row) {
401 return model()->IsItemSeparatorAt(row) ? base::string16() :
402 model()->GetItemAt(row);
405 ////////////////////////////////////////////////////////////////////////////////
406 // Combobox, View overrides:
408 gfx::Size Combobox::GetPreferredSize() const {
409 // The preferred size will drive the local bounds which in turn is used to set
410 // the minimum width for the dropdown list.
411 gfx::Insets insets = GetInsets();
412 int total_width = std::max(kMinComboboxWidth, content_size_.width()) +
413 insets.width() + GetDisclosureArrowLeftPadding() +
414 ArrowSize().width() + GetDisclosureArrowRightPadding();
415 return gfx::Size(total_width, content_size_.height() + insets.height());
418 const char* Combobox::GetClassName() const {
419 return kViewClassName;
422 bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) {
423 // Escape should close the drop down list when it is active, not host UI.
424 if (e.key_code() != ui::VKEY_ESCAPE ||
425 e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) {
426 return false;
428 return dropdown_open_;
431 bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
432 // TODO(oshima): handle IME.
433 DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED);
435 DCHECK_GE(selected_index_, 0);
436 DCHECK_LT(selected_index_, model()->GetItemCount());
437 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
438 selected_index_ = 0;
440 bool show_menu = false;
441 int new_index = kNoSelection;
442 switch (e.key_code()) {
443 // Show the menu on F4 without modifiers.
444 case ui::VKEY_F4:
445 if (e.IsAltDown() || e.IsAltGrDown() || e.IsControlDown())
446 return false;
447 show_menu = true;
448 break;
450 // Move to the next item if any, or show the menu on Alt+Down like Windows.
451 case ui::VKEY_DOWN:
452 if (e.IsAltDown())
453 show_menu = true;
454 else
455 new_index = GetAdjacentIndex(model(), 1, selected_index_);
456 break;
458 // Move to the end of the list.
459 case ui::VKEY_END:
460 case ui::VKEY_NEXT: // Page down.
461 new_index = GetAdjacentIndex(model(), -1, model()->GetItemCount());
462 break;
464 // Move to the beginning of the list.
465 case ui::VKEY_HOME:
466 case ui::VKEY_PRIOR: // Page up.
467 new_index = GetAdjacentIndex(model(), 1, -1);
468 break;
470 // Move to the previous item if any.
471 case ui::VKEY_UP:
472 new_index = GetAdjacentIndex(model(), -1, selected_index_);
473 break;
475 // Click the button only when the button style mode.
476 case ui::VKEY_SPACE:
477 if (style_ == STYLE_ACTION) {
478 // When pressing space, the click event will be raised after the key is
479 // released.
480 text_button_->SetState(Button::STATE_PRESSED);
481 } else {
482 return false;
484 break;
486 // Click the button only when the button style mode.
487 case ui::VKEY_RETURN:
488 if (style_ != STYLE_ACTION)
489 return false;
490 OnPerformAction();
491 break;
493 default:
494 return false;
497 if (show_menu) {
498 UpdateFromModel();
499 ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD);
500 } else if (new_index != selected_index_ && new_index != kNoSelection &&
501 style_ != STYLE_ACTION) {
502 DCHECK(!model()->IsItemSeparatorAt(new_index));
503 selected_index_ = new_index;
504 OnPerformAction();
507 return true;
510 bool Combobox::OnKeyReleased(const ui::KeyEvent& e) {
511 if (style_ != STYLE_ACTION)
512 return false; // crbug.com/127520
514 if (e.key_code() == ui::VKEY_SPACE && style_ == STYLE_ACTION)
515 OnPerformAction();
517 return false;
520 void Combobox::OnPaint(gfx::Canvas* canvas) {
521 switch (style_) {
522 case STYLE_NORMAL: {
523 OnPaintBackground(canvas);
524 PaintText(canvas);
525 OnPaintBorder(canvas);
526 break;
528 case STYLE_ACTION: {
529 PaintButtons(canvas);
530 PaintText(canvas);
531 break;
536 void Combobox::OnFocus() {
537 GetInputMethod()->OnFocus();
538 View::OnFocus();
539 // Border renders differently when focused.
540 SchedulePaint();
543 void Combobox::OnBlur() {
544 GetInputMethod()->OnBlur();
545 if (selector_)
546 selector_->OnViewBlur();
547 // Border renders differently when focused.
548 SchedulePaint();
551 void Combobox::GetAccessibleState(ui::AXViewState* state) {
552 state->role = ui::AX_ROLE_COMBO_BOX;
553 state->name = accessible_name_;
554 state->value = model_->GetItemAt(selected_index_);
555 state->index = selected_index_;
556 state->count = model_->GetItemCount();
559 void Combobox::OnComboboxModelChanged(ui::ComboboxModel* model) {
560 DCHECK_EQ(model, model_);
561 ModelChanged();
564 void Combobox::ButtonPressed(Button* sender, const ui::Event& event) {
565 if (!enabled())
566 return;
568 RequestFocus();
570 if (sender == text_button_) {
571 OnPerformAction();
572 } else {
573 DCHECK_EQ(arrow_button_, sender);
574 // TODO(hajimehoshi): Fix the problem that the arrow button blinks when
575 // cliking this while the dropdown menu is opened.
576 const base::TimeDelta delta = base::Time::Now() - closed_time_;
577 if (delta.InMilliseconds() <= kMinimumMsBetweenButtonClicks)
578 return;
580 ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE;
581 if (event.IsKeyEvent())
582 source_type = ui::MENU_SOURCE_KEYBOARD;
583 else if (event.IsGestureEvent() || event.IsTouchEvent())
584 source_type = ui::MENU_SOURCE_TOUCH;
585 ShowDropDownMenu(source_type);
589 void Combobox::UpdateFromModel() {
590 const gfx::FontList& font_list = Combobox::GetFontList();
592 MenuItemView* menu = new MenuItemView(this);
593 // MenuRunner owns |menu|.
594 dropdown_list_menu_runner_.reset(new MenuRunner(menu));
596 int num_items = model()->GetItemCount();
597 int width = 0;
598 bool text_item_appended = false;
599 for (int i = 0; i < num_items; ++i) {
600 // When STYLE_ACTION is used, the first item and the following separators
601 // are not added to the dropdown menu. It is assumed that the first item is
602 // always selected and rendered on the top of the action button.
603 if (model()->IsItemSeparatorAt(i)) {
604 if (text_item_appended || style_ != STYLE_ACTION)
605 menu->AppendSeparator();
606 continue;
609 base::string16 text = model()->GetItemAt(i);
611 // Inserting the Unicode formatting characters if necessary so that the
612 // text is displayed correctly in right-to-left UIs.
613 base::i18n::AdjustStringForLocaleDirection(&text);
615 if (style_ != STYLE_ACTION || i > 0) {
616 menu->AppendMenuItem(i + kFirstMenuItemId, text, MenuItemView::NORMAL);
617 text_item_appended = true;
620 if (style_ != STYLE_ACTION || i == selected_index_)
621 width = std::max(width, gfx::GetStringWidth(text, font_list));
624 content_size_.SetSize(width, font_list.GetHeight());
627 void Combobox::UpdateBorder() {
628 scoped_ptr<FocusableBorder> border(new FocusableBorder());
629 if (style_ == STYLE_ACTION)
630 border->SetInsets(8, 13, 8, 13);
631 if (invalid_)
632 border->SetColor(kWarningColor);
633 SetBorder(border.PassAs<Border>());
636 void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
637 rect->set_x(GetMirroredXForRect(*rect));
640 void Combobox::PaintText(gfx::Canvas* canvas) {
641 gfx::Insets insets = GetInsets();
643 gfx::ScopedCanvas scoped_canvas(canvas);
644 canvas->ClipRect(GetContentsBounds());
646 int x = insets.left();
647 int y = insets.top();
648 int text_height = height() - insets.height();
649 SkColor text_color = GetNativeTheme()->GetSystemColor(
650 ui::NativeTheme::kColorId_LabelEnabledColor);
652 DCHECK_GE(selected_index_, 0);
653 DCHECK_LT(selected_index_, model()->GetItemCount());
654 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
655 selected_index_ = 0;
656 base::string16 text = model()->GetItemAt(selected_index_);
658 gfx::Size arrow_size = ArrowSize();
659 int disclosure_arrow_offset = width() - arrow_size.width() -
660 GetDisclosureArrowLeftPadding() - GetDisclosureArrowRightPadding();
662 const gfx::FontList& font_list = Combobox::GetFontList();
663 int text_width = gfx::GetStringWidth(text, font_list);
664 if ((text_width + insets.width()) > disclosure_arrow_offset)
665 text_width = disclosure_arrow_offset - insets.width();
667 gfx::Rect text_bounds(x, y, text_width, text_height);
668 AdjustBoundsForRTLUI(&text_bounds);
669 canvas->DrawStringRect(text, font_list, text_color, text_bounds);
671 int arrow_x = disclosure_arrow_offset + GetDisclosureArrowLeftPadding();
672 gfx::Rect arrow_bounds(arrow_x,
673 height() / 2 - arrow_size.height() / 2,
674 arrow_size.width(),
675 arrow_size.height());
676 AdjustBoundsForRTLUI(&arrow_bounds);
678 // TODO(estade): hack alert! Remove this direct call into CommonTheme. For now
679 // STYLE_ACTION isn't properly themed so we have to override the NativeTheme
680 // behavior. See crbug.com/384071
681 if (style_ == STYLE_ACTION) {
682 ui::CommonThemePaintComboboxArrow(canvas->sk_canvas(), arrow_bounds);
683 } else {
684 ui::NativeTheme::ExtraParams ignored;
685 GetNativeTheme()->Paint(canvas->sk_canvas(),
686 ui::NativeTheme::kComboboxArrow,
687 ui::NativeTheme::kNormal,
688 arrow_bounds,
689 ignored);
693 void Combobox::PaintButtons(gfx::Canvas* canvas) {
694 DCHECK(style_ == STYLE_ACTION);
696 gfx::ScopedCanvas scoped_canvas(canvas);
697 if (base::i18n::IsRTL()) {
698 canvas->Translate(gfx::Vector2d(width(), 0));
699 canvas->Scale(-1, 1);
702 bool focused = HasFocus();
703 const std::vector<const gfx::ImageSkia*>& arrow_button_images =
704 menu_button_images_[focused][
705 arrow_button_->state() == Button::STATE_HOVERED ?
706 Button::STATE_NORMAL : arrow_button_->state()];
708 int text_button_hover_alpha =
709 text_button_->state() == Button::STATE_PRESSED ? 0 :
710 static_cast<int>(static_cast<TransparentButton*>(text_button_)->
711 GetAnimationValue() * 255);
712 if (text_button_hover_alpha < 255) {
713 canvas->SaveLayerAlpha(255 - text_button_hover_alpha);
714 Painter* text_button_painter =
715 body_button_painters_[focused][
716 text_button_->state() == Button::STATE_HOVERED ?
717 Button::STATE_NORMAL : text_button_->state()].get();
718 Painter::PaintPainterAt(canvas, text_button_painter,
719 gfx::Rect(0, 0, text_button_->width(), height()));
720 canvas->Restore();
722 if (0 < text_button_hover_alpha) {
723 canvas->SaveLayerAlpha(text_button_hover_alpha);
724 Painter* text_button_hovered_painter =
725 body_button_painters_[focused][Button::STATE_HOVERED].get();
726 Painter::PaintPainterAt(canvas, text_button_hovered_painter,
727 gfx::Rect(0, 0, text_button_->width(), height()));
728 canvas->Restore();
731 int arrow_button_hover_alpha =
732 arrow_button_->state() == Button::STATE_PRESSED ? 0 :
733 static_cast<int>(static_cast<TransparentButton*>(arrow_button_)->
734 GetAnimationValue() * 255);
735 if (arrow_button_hover_alpha < 255) {
736 canvas->SaveLayerAlpha(255 - arrow_button_hover_alpha);
737 PaintArrowButton(canvas, arrow_button_images, arrow_button_->x(), height());
738 canvas->Restore();
740 if (0 < arrow_button_hover_alpha) {
741 canvas->SaveLayerAlpha(arrow_button_hover_alpha);
742 const std::vector<const gfx::ImageSkia*>& arrow_button_hovered_images =
743 menu_button_images_[focused][Button::STATE_HOVERED];
744 PaintArrowButton(canvas, arrow_button_hovered_images,
745 arrow_button_->x(), height());
746 canvas->Restore();
750 void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
751 if (!dropdown_list_menu_runner_.get())
752 UpdateFromModel();
754 // Extend the menu to the width of the combobox.
755 MenuItemView* menu = dropdown_list_menu_runner_->GetMenu();
756 SubmenuView* submenu = menu->CreateSubmenu();
757 submenu->set_minimum_preferred_width(
758 size().width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
760 gfx::Rect lb = GetLocalBounds();
761 gfx::Point menu_position(lb.origin());
763 if (style_ == STYLE_NORMAL) {
764 // Inset the menu's requested position so the border of the menu lines up
765 // with the border of the combobox.
766 menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft);
767 menu_position.set_y(menu_position.y() + kMenuBorderWidthTop);
769 lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
771 View::ConvertPointToScreen(this, &menu_position);
772 if (menu_position.x() < 0)
773 menu_position.set_x(0);
775 gfx::Rect bounds(menu_position, lb.size());
777 Button::ButtonState original_state = Button::STATE_NORMAL;
778 if (arrow_button_) {
779 original_state = arrow_button_->state();
780 arrow_button_->SetState(Button::STATE_PRESSED);
782 dropdown_open_ = true;
783 MenuAnchorPosition anchor_position =
784 style_ == STYLE_ACTION ? MENU_ANCHOR_TOPRIGHT : MENU_ANCHOR_TOPLEFT;
785 if (dropdown_list_menu_runner_->RunMenuAt(GetWidget(), NULL, bounds,
786 anchor_position, source_type,
787 MenuRunner::COMBOBOX) ==
788 MenuRunner::MENU_DELETED) {
789 return;
791 dropdown_open_ = false;
792 if (arrow_button_)
793 arrow_button_->SetState(original_state);
794 closed_time_ = base::Time::Now();
796 // Need to explicitly clear mouse handler so that events get sent
797 // properly after the menu finishes running. If we don't do this, then
798 // the first click to other parts of the UI is eaten.
799 SetMouseHandler(NULL);
802 void Combobox::OnPerformAction() {
803 NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, false);
804 SchedulePaint();
806 // This combobox may be deleted by the listener.
807 base::WeakPtr<Combobox> weak_ptr = weak_ptr_factory_.GetWeakPtr();
808 if (listener_)
809 listener_->OnPerformAction(this);
811 if (weak_ptr && style_ == STYLE_ACTION)
812 selected_index_ = 0;
815 int Combobox::MenuCommandToIndex(int menu_command_id) const {
816 // (note that the id received is offset by kFirstMenuItemId)
817 // Revert menu ID offset to map back to combobox model.
818 int index = menu_command_id - kFirstMenuItemId;
819 DCHECK_LT(index, model()->GetItemCount());
820 return index;
823 int Combobox::GetDisclosureArrowLeftPadding() const {
824 switch (style_) {
825 case STYLE_NORMAL:
826 return kDisclosureArrowLeftPadding;
827 case STYLE_ACTION:
828 return kDisclosureArrowButtonLeftPadding;
830 NOTREACHED();
831 return 0;
834 int Combobox::GetDisclosureArrowRightPadding() const {
835 switch (style_) {
836 case STYLE_NORMAL:
837 return kDisclosureArrowRightPadding;
838 case STYLE_ACTION:
839 return kDisclosureArrowButtonRightPadding;
841 NOTREACHED();
842 return 0;
845 gfx::Size Combobox::ArrowSize() const {
846 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
847 // TODO(estade): hack alert! This should always use GetNativeTheme(). For now
848 // STYLE_ACTION isn't properly themed so we have to override the NativeTheme
849 // behavior. See crbug.com/384071
850 const ui::NativeTheme* native_theme_for_arrow = style_ == STYLE_ACTION ?
851 ui::NativeTheme::instance() :
852 GetNativeTheme();
853 #else
854 const ui::NativeTheme* native_theme_for_arrow = GetNativeTheme();
855 #endif
857 ui::NativeTheme::ExtraParams ignored;
858 return native_theme_for_arrow->GetPartSize(ui::NativeTheme::kComboboxArrow,
859 ui::NativeTheme::kNormal,
860 ignored);
863 } // namespace views