[SyncFS] Build indexes from FileTracker entries on disk.
[chromium-blink-merge.git] / ui / views / controls / combobox / combobox.cc
blobab8138715af544157f3028f478ea122fca495427
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 menu_(NULL),
236 dropdown_open_(false),
237 text_button_(new TransparentButton(this)),
238 arrow_button_(new TransparentButton(this)),
239 weak_ptr_factory_(this) {
240 model_->AddObserver(this);
241 UpdateFromModel();
242 SetFocusable(true);
243 UpdateBorder();
245 // Initialize the button images.
246 Button::ButtonState button_states[] = {
247 Button::STATE_DISABLED,
248 Button::STATE_NORMAL,
249 Button::STATE_HOVERED,
250 Button::STATE_PRESSED,
252 for (int i = 0; i < 2; i++) {
253 for (size_t state_index = 0; state_index < arraysize(button_states);
254 state_index++) {
255 Button::ButtonState state = button_states[state_index];
256 size_t num;
257 bool focused = !!i;
258 const int* ids = GetBodyButtonImageIds(focused, state, &num);
259 body_button_painters_[focused][state].reset(
260 Painter::CreateImageGridPainter(ids));
261 menu_button_images_[focused][state] = GetMenuButtonImages(focused, state);
265 text_button_->SetVisible(true);
266 arrow_button_->SetVisible(true);
267 text_button_->SetFocusable(false);
268 arrow_button_->SetFocusable(false);
269 AddChildView(text_button_);
270 AddChildView(arrow_button_);
273 Combobox::~Combobox() {
274 model_->RemoveObserver(this);
277 // static
278 const gfx::FontList& Combobox::GetFontList() {
279 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
280 return rb.GetFontList(ui::ResourceBundle::BaseFont);
283 void Combobox::SetStyle(Style style) {
284 if (style_ == style)
285 return;
287 style_ = style;
288 if (style_ == STYLE_ACTION)
289 selected_index_ = 0;
291 UpdateBorder();
292 UpdateFromModel();
293 PreferredSizeChanged();
296 void Combobox::ModelChanged() {
297 selected_index_ = std::min(0, model_->GetItemCount());
298 UpdateFromModel();
299 PreferredSizeChanged();
302 void Combobox::SetSelectedIndex(int index) {
303 if (style_ == STYLE_ACTION)
304 return;
306 selected_index_ = index;
307 SchedulePaint();
310 bool Combobox::SelectValue(const base::string16& value) {
311 if (style_ == STYLE_ACTION)
312 return false;
314 for (int i = 0; i < model()->GetItemCount(); ++i) {
315 if (value == model()->GetItemAt(i)) {
316 SetSelectedIndex(i);
317 return true;
320 return false;
323 void Combobox::SetAccessibleName(const base::string16& name) {
324 accessible_name_ = name;
327 void Combobox::SetInvalid(bool invalid) {
328 if (invalid == invalid_)
329 return;
331 invalid_ = invalid;
333 UpdateBorder();
334 SchedulePaint();
337 ui::TextInputClient* Combobox::GetTextInputClient() {
338 if (!selector_)
339 selector_.reset(new PrefixSelector(this));
340 return selector_.get();
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 int total_width = std::max(kMinComboboxWidth, content_size_.width()) +
414 insets.width() + GetDisclosureArrowLeftPadding() +
415 ArrowSize().width() + GetDisclosureArrowRightPadding();
416 return gfx::Size(total_width, content_size_.height() + insets.height());
419 const char* Combobox::GetClassName() const {
420 return kViewClassName;
423 bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) {
424 // Escape should close the drop down list when it is active, not host UI.
425 if (e.key_code() != ui::VKEY_ESCAPE ||
426 e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) {
427 return false;
429 return dropdown_open_;
432 bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
433 // TODO(oshima): handle IME.
434 DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED);
436 DCHECK_GE(selected_index_, 0);
437 DCHECK_LT(selected_index_, model()->GetItemCount());
438 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
439 selected_index_ = 0;
441 bool show_menu = false;
442 int new_index = kNoSelection;
443 switch (e.key_code()) {
444 // Show the menu on F4 without modifiers.
445 case ui::VKEY_F4:
446 if (e.IsAltDown() || e.IsAltGrDown() || e.IsControlDown())
447 return false;
448 show_menu = true;
449 break;
451 // Move to the next item if any, or show the menu on Alt+Down like Windows.
452 case ui::VKEY_DOWN:
453 if (e.IsAltDown())
454 show_menu = true;
455 else
456 new_index = GetAdjacentIndex(model(), 1, selected_index_);
457 break;
459 // Move to the end of the list.
460 case ui::VKEY_END:
461 case ui::VKEY_NEXT: // Page down.
462 new_index = GetAdjacentIndex(model(), -1, model()->GetItemCount());
463 break;
465 // Move to the beginning of the list.
466 case ui::VKEY_HOME:
467 case ui::VKEY_PRIOR: // Page up.
468 new_index = GetAdjacentIndex(model(), 1, -1);
469 break;
471 // Move to the previous item if any.
472 case ui::VKEY_UP:
473 new_index = GetAdjacentIndex(model(), -1, selected_index_);
474 break;
476 // Click the button only when the button style mode.
477 case ui::VKEY_SPACE:
478 if (style_ == STYLE_ACTION) {
479 // When pressing space, the click event will be raised after the key is
480 // released.
481 text_button_->SetState(Button::STATE_PRESSED);
482 } else {
483 return false;
485 break;
487 // Click the button only when the button style mode.
488 case ui::VKEY_RETURN:
489 if (style_ != STYLE_ACTION)
490 return false;
491 OnPerformAction();
492 break;
494 default:
495 return false;
498 if (show_menu) {
499 UpdateFromModel();
500 ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD);
501 } else if (new_index != selected_index_ && new_index != kNoSelection &&
502 style_ != STYLE_ACTION) {
503 DCHECK(!model()->IsItemSeparatorAt(new_index));
504 selected_index_ = new_index;
505 OnPerformAction();
508 return true;
511 bool Combobox::OnKeyReleased(const ui::KeyEvent& e) {
512 if (style_ != STYLE_ACTION)
513 return false; // crbug.com/127520
515 if (e.key_code() == ui::VKEY_SPACE && style_ == STYLE_ACTION)
516 OnPerformAction();
518 return false;
521 void Combobox::OnPaint(gfx::Canvas* canvas) {
522 switch (style_) {
523 case STYLE_NORMAL: {
524 OnPaintBackground(canvas);
525 PaintText(canvas);
526 OnPaintBorder(canvas);
527 break;
529 case STYLE_ACTION: {
530 PaintButtons(canvas);
531 PaintText(canvas);
532 break;
537 void Combobox::OnFocus() {
538 GetInputMethod()->OnFocus();
539 View::OnFocus();
540 // Border renders differently when focused.
541 SchedulePaint();
544 void Combobox::OnBlur() {
545 GetInputMethod()->OnBlur();
546 if (selector_)
547 selector_->OnViewBlur();
548 // Border renders differently when focused.
549 SchedulePaint();
552 void Combobox::GetAccessibleState(ui::AXViewState* state) {
553 state->role = ui::AX_ROLE_COMBO_BOX;
554 state->name = accessible_name_;
555 state->value = model_->GetItemAt(selected_index_);
556 state->index = selected_index_;
557 state->count = model_->GetItemCount();
560 void Combobox::OnComboboxModelChanged(ui::ComboboxModel* model) {
561 DCHECK_EQ(model, model_);
562 ModelChanged();
565 void Combobox::ButtonPressed(Button* sender, const ui::Event& event) {
566 if (!enabled())
567 return;
569 RequestFocus();
571 if (sender == text_button_) {
572 OnPerformAction();
573 } else {
574 DCHECK_EQ(arrow_button_, sender);
575 // TODO(hajimehoshi): Fix the problem that the arrow button blinks when
576 // cliking this while the dropdown menu is opened.
577 const base::TimeDelta delta = base::Time::Now() - closed_time_;
578 if (delta.InMilliseconds() <= kMinimumMsBetweenButtonClicks)
579 return;
581 ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE;
582 if (event.IsKeyEvent())
583 source_type = ui::MENU_SOURCE_KEYBOARD;
584 else if (event.IsGestureEvent() || event.IsTouchEvent())
585 source_type = ui::MENU_SOURCE_TOUCH;
586 ShowDropDownMenu(source_type);
590 void Combobox::UpdateFromModel() {
591 const gfx::FontList& font_list = Combobox::GetFontList();
593 menu_ = new MenuItemView(this);
594 // MenuRunner owns |menu_|.
595 dropdown_list_menu_runner_.reset(new MenuRunner(menu_, MenuRunner::COMBOBOX));
597 int num_items = model()->GetItemCount();
598 int width = 0;
599 bool text_item_appended = false;
600 for (int i = 0; i < num_items; ++i) {
601 // When STYLE_ACTION is used, the first item and the following separators
602 // are not added to the dropdown menu. It is assumed that the first item is
603 // always selected and rendered on the top of the action button.
604 if (model()->IsItemSeparatorAt(i)) {
605 if (text_item_appended || style_ != STYLE_ACTION)
606 menu_->AppendSeparator();
607 continue;
610 base::string16 text = model()->GetItemAt(i);
612 // Inserting the Unicode formatting characters if necessary so that the
613 // text is displayed correctly in right-to-left UIs.
614 base::i18n::AdjustStringForLocaleDirection(&text);
616 if (style_ != STYLE_ACTION || i > 0) {
617 menu_->AppendMenuItem(i + kFirstMenuItemId, text, MenuItemView::NORMAL);
618 text_item_appended = true;
621 if (style_ != STYLE_ACTION || i == selected_index_)
622 width = std::max(width, gfx::GetStringWidth(text, font_list));
625 content_size_.SetSize(width, font_list.GetHeight());
628 void Combobox::UpdateBorder() {
629 scoped_ptr<FocusableBorder> border(new FocusableBorder());
630 if (style_ == STYLE_ACTION)
631 border->SetInsets(8, 13, 8, 13);
632 if (invalid_)
633 border->SetColor(kWarningColor);
634 SetBorder(border.PassAs<Border>());
637 void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
638 rect->set_x(GetMirroredXForRect(*rect));
641 void Combobox::PaintText(gfx::Canvas* canvas) {
642 gfx::Insets insets = GetInsets();
644 gfx::ScopedCanvas scoped_canvas(canvas);
645 canvas->ClipRect(GetContentsBounds());
647 int x = insets.left();
648 int y = insets.top();
649 int text_height = height() - insets.height();
650 SkColor text_color = GetNativeTheme()->GetSystemColor(
651 ui::NativeTheme::kColorId_LabelEnabledColor);
653 DCHECK_GE(selected_index_, 0);
654 DCHECK_LT(selected_index_, model()->GetItemCount());
655 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
656 selected_index_ = 0;
657 base::string16 text = model()->GetItemAt(selected_index_);
659 gfx::Size arrow_size = ArrowSize();
660 int disclosure_arrow_offset = width() - arrow_size.width() -
661 GetDisclosureArrowLeftPadding() - GetDisclosureArrowRightPadding();
663 const gfx::FontList& font_list = Combobox::GetFontList();
664 int text_width = gfx::GetStringWidth(text, font_list);
665 if ((text_width + insets.width()) > disclosure_arrow_offset)
666 text_width = disclosure_arrow_offset - insets.width();
668 gfx::Rect text_bounds(x, y, text_width, text_height);
669 AdjustBoundsForRTLUI(&text_bounds);
670 canvas->DrawStringRect(text, font_list, text_color, text_bounds);
672 int arrow_x = disclosure_arrow_offset + GetDisclosureArrowLeftPadding();
673 gfx::Rect arrow_bounds(arrow_x,
674 height() / 2 - arrow_size.height() / 2,
675 arrow_size.width(),
676 arrow_size.height());
677 AdjustBoundsForRTLUI(&arrow_bounds);
679 // TODO(estade): hack alert! Remove this direct call into CommonTheme. For now
680 // STYLE_ACTION isn't properly themed so we have to override the NativeTheme
681 // behavior. See crbug.com/384071
682 if (style_ == STYLE_ACTION) {
683 ui::CommonThemePaintComboboxArrow(canvas->sk_canvas(), arrow_bounds);
684 } else {
685 ui::NativeTheme::ExtraParams ignored;
686 GetNativeTheme()->Paint(canvas->sk_canvas(),
687 ui::NativeTheme::kComboboxArrow,
688 ui::NativeTheme::kNormal,
689 arrow_bounds,
690 ignored);
694 void Combobox::PaintButtons(gfx::Canvas* canvas) {
695 DCHECK(style_ == STYLE_ACTION);
697 gfx::ScopedCanvas scoped_canvas(canvas);
698 if (base::i18n::IsRTL()) {
699 canvas->Translate(gfx::Vector2d(width(), 0));
700 canvas->Scale(-1, 1);
703 bool focused = HasFocus();
704 const std::vector<const gfx::ImageSkia*>& arrow_button_images =
705 menu_button_images_[focused][
706 arrow_button_->state() == Button::STATE_HOVERED ?
707 Button::STATE_NORMAL : arrow_button_->state()];
709 int text_button_hover_alpha =
710 text_button_->state() == Button::STATE_PRESSED ? 0 :
711 static_cast<int>(static_cast<TransparentButton*>(text_button_)->
712 GetAnimationValue() * 255);
713 if (text_button_hover_alpha < 255) {
714 canvas->SaveLayerAlpha(255 - text_button_hover_alpha);
715 Painter* text_button_painter =
716 body_button_painters_[focused][
717 text_button_->state() == Button::STATE_HOVERED ?
718 Button::STATE_NORMAL : text_button_->state()].get();
719 Painter::PaintPainterAt(canvas, text_button_painter,
720 gfx::Rect(0, 0, text_button_->width(), height()));
721 canvas->Restore();
723 if (0 < text_button_hover_alpha) {
724 canvas->SaveLayerAlpha(text_button_hover_alpha);
725 Painter* text_button_hovered_painter =
726 body_button_painters_[focused][Button::STATE_HOVERED].get();
727 Painter::PaintPainterAt(canvas, text_button_hovered_painter,
728 gfx::Rect(0, 0, text_button_->width(), height()));
729 canvas->Restore();
732 int arrow_button_hover_alpha =
733 arrow_button_->state() == Button::STATE_PRESSED ? 0 :
734 static_cast<int>(static_cast<TransparentButton*>(arrow_button_)->
735 GetAnimationValue() * 255);
736 if (arrow_button_hover_alpha < 255) {
737 canvas->SaveLayerAlpha(255 - arrow_button_hover_alpha);
738 PaintArrowButton(canvas, arrow_button_images, arrow_button_->x(), height());
739 canvas->Restore();
741 if (0 < arrow_button_hover_alpha) {
742 canvas->SaveLayerAlpha(arrow_button_hover_alpha);
743 const std::vector<const gfx::ImageSkia*>& arrow_button_hovered_images =
744 menu_button_images_[focused][Button::STATE_HOVERED];
745 PaintArrowButton(canvas, arrow_button_hovered_images,
746 arrow_button_->x(), height());
747 canvas->Restore();
751 void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
752 if (!dropdown_list_menu_runner_.get())
753 UpdateFromModel();
755 // Extend the menu to the width of the combobox.
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(
786 GetWidget(), NULL, bounds, anchor_position, source_type) ==
787 MenuRunner::MENU_DELETED) {
788 return;
790 dropdown_open_ = false;
791 if (arrow_button_)
792 arrow_button_->SetState(original_state);
793 closed_time_ = base::Time::Now();
795 // Need to explicitly clear mouse handler so that events get sent
796 // properly after the menu finishes running. If we don't do this, then
797 // the first click to other parts of the UI is eaten.
798 SetMouseHandler(NULL);
801 void Combobox::OnPerformAction() {
802 NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, false);
803 SchedulePaint();
805 // This combobox may be deleted by the listener.
806 base::WeakPtr<Combobox> weak_ptr = weak_ptr_factory_.GetWeakPtr();
807 if (listener_)
808 listener_->OnPerformAction(this);
810 if (weak_ptr && style_ == STYLE_ACTION)
811 selected_index_ = 0;
814 int Combobox::MenuCommandToIndex(int menu_command_id) const {
815 // (note that the id received is offset by kFirstMenuItemId)
816 // Revert menu ID offset to map back to combobox model.
817 int index = menu_command_id - kFirstMenuItemId;
818 DCHECK_LT(index, model()->GetItemCount());
819 return index;
822 int Combobox::GetDisclosureArrowLeftPadding() const {
823 switch (style_) {
824 case STYLE_NORMAL:
825 return kDisclosureArrowLeftPadding;
826 case STYLE_ACTION:
827 return kDisclosureArrowButtonLeftPadding;
829 NOTREACHED();
830 return 0;
833 int Combobox::GetDisclosureArrowRightPadding() const {
834 switch (style_) {
835 case STYLE_NORMAL:
836 return kDisclosureArrowRightPadding;
837 case STYLE_ACTION:
838 return kDisclosureArrowButtonRightPadding;
840 NOTREACHED();
841 return 0;
844 gfx::Size Combobox::ArrowSize() const {
845 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
846 // TODO(estade): hack alert! This should always use GetNativeTheme(). For now
847 // STYLE_ACTION isn't properly themed so we have to override the NativeTheme
848 // behavior. See crbug.com/384071
849 const ui::NativeTheme* native_theme_for_arrow = style_ == STYLE_ACTION ?
850 ui::NativeTheme::instance() :
851 GetNativeTheme();
852 #else
853 const ui::NativeTheme* native_theme_for_arrow = GetNativeTheme();
854 #endif
856 ui::NativeTheme::ExtraParams ignored;
857 return native_theme_for_arrow->GetPartSize(ui::NativeTheme::kComboboxArrow,
858 ui::NativeTheme::kNormal,
859 ignored);
862 } // namespace views