Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / ui / views / controls / combobox / combobox.cc
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.
4
5 #include "ui/views/controls/combobox/combobox.h"
6
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"
39
40 namespace views {
41
42 namespace {
43
44 // Menu border widths
45 const int kMenuBorderWidthLeft = 1;
46 const int kMenuBorderWidthTop = 1;
47 const int kMenuBorderWidthRight = 1;
48
49 // Limit how small a combobox can be.
50 const int kMinComboboxWidth = 25;
51
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;
57
58 // Define the id of the first item in the menu (since it needs to be > 0)
59 const int kFirstMenuItemId = 1000;
60
61 // Used to indicate that no item is currently selected by the user.
62 const int kNoSelection = -1;
63
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);
72
73 #define MENU_IMAGE_GRID(x) { \
74     x ## _MENU_TOP, x ## _MENU_CENTER, x ## _MENU_BOTTOM, }
75
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);
84
85 #undef MENU_IMAGE_GRID
86
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);
93   }
94   virtual ~TransparentButton() {}
95
96   virtual bool OnMousePressed(const ui::MouseEvent& mouse_event) OVERRIDE {
97     parent()->RequestFocus();
98     return true;
99   }
100
101   double GetAnimationValue() const {
102     return hover_animation_->GetCurrentValue();
103   }
104
105  private:
106   DISALLOW_COPY_AND_ASSIGN(TransparentButton);
107 };
108
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);
114
115   index += increment;
116   while (index >= 0 && index < model->GetItemCount()) {
117     if (!model->IsItemSeparatorAt(index) || !model->IsItemEnabledAt(index))
118       return index;
119     index += increment;
120   }
121   return kNoSelection;
122 }
123
124 // Returns the image resource ids of an array for the body button.
125 //
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();
146   }
147   return NULL;
148 }
149
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();
169   }
170   return NULL;
171 }
172
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;
186 }
187
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);
207 }
208
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);
219 }
220
221 }  // namespace
222
223 // static
224 const char Combobox::kViewClassName[] = "views/Combobox";
225
226 ////////////////////////////////////////////////////////////////////////////////
227 // Combobox, public:
228
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();
244
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,
251   };
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);
262     }
263   }
264
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_);
271 }
272
273 Combobox::~Combobox() {
274   model_->RemoveObserver(this);
275 }
276
277 // static
278 const gfx::FontList& Combobox::GetFontList() {
279   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
280   return rb.GetFontList(ui::ResourceBundle::BaseFont);
281 }
282
283 void Combobox::SetStyle(Style style) {
284   if (style_ == style)
285     return;
286
287   style_ = style;
288   if (style_ == STYLE_ACTION)
289     selected_index_ = 0;
290
291   UpdateBorder();
292   UpdateFromModel();
293   PreferredSizeChanged();
294 }
295
296 void Combobox::ModelChanged() {
297   selected_index_ = std::min(0, model_->GetItemCount());
298   UpdateFromModel();
299   PreferredSizeChanged();
300 }
301
302 void Combobox::SetSelectedIndex(int index) {
303   if (style_ == STYLE_ACTION)
304     return;
305
306   selected_index_ = index;
307   SchedulePaint();
308 }
309
310 bool Combobox::SelectValue(const base::string16& value) {
311   if (style_ == STYLE_ACTION)
312     return false;
313
314   for (int i = 0; i < model()->GetItemCount(); ++i) {
315     if (value == model()->GetItemAt(i)) {
316       SetSelectedIndex(i);
317       return true;
318     }
319   }
320   return false;
321 }
322
323 void Combobox::SetAccessibleName(const base::string16& name) {
324   accessible_name_ = name;
325 }
326
327 void Combobox::SetInvalid(bool invalid) {
328   if (invalid == invalid_)
329     return;
330
331   invalid_ = invalid;
332
333   UpdateBorder();
334   SchedulePaint();
335 }
336
337 ui::TextInputClient* Combobox::GetTextInputClient() {
338   if (!selector_)
339     selector_.reset(new PrefixSelector(this));
340   return selector_.get();
341 }
342
343 void Combobox::Layout() {
344   PrefixDelegate::Layout();
345
346   gfx::Insets insets = GetInsets();
347   int text_button_width = 0;
348   int arrow_button_width = 0;
349
350   switch (style_) {
351     case STYLE_NORMAL: {
352       arrow_button_width = width();
353       break;
354     }
355     case STYLE_ACTION: {
356       arrow_button_width = GetDisclosureArrowLeftPadding() +
357           ArrowSize().width() +
358           GetDisclosureArrowRightPadding();
359       text_button_width = width() - arrow_button_width;
360       break;
361     }
362   }
363
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());
367 }
368
369 bool Combobox::IsItemChecked(int id) const {
370   return false;
371 }
372
373 bool Combobox::IsCommandEnabled(int id) const {
374   return model()->IsItemEnabledAt(MenuCommandToIndex(id));
375 }
376
377 void Combobox::ExecuteCommand(int id) {
378   selected_index_ = MenuCommandToIndex(id);
379   OnPerformAction();
380 }
381
382 bool Combobox::GetAccelerator(int id, ui::Accelerator* accel) const {
383   return false;
384 }
385
386 int Combobox::GetRowCount() {
387   return model()->GetItemCount();
388 }
389
390 int Combobox::GetSelectedRow() {
391   return selected_index_;
392 }
393
394 void Combobox::SetSelectedRow(int row) {
395   int prev_index = selected_index_;
396   SetSelectedIndex(row);
397   if (selected_index_ != prev_index)
398     OnPerformAction();
399 }
400
401 base::string16 Combobox::GetTextForRow(int row) {
402   return model()->IsItemSeparatorAt(row) ? base::string16() :
403                                            model()->GetItemAt(row);
404 }
405
406 ////////////////////////////////////////////////////////////////////////////////
407 // Combobox, View overrides:
408
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());
417 }
418
419 const char* Combobox::GetClassName() const {
420   return kViewClassName;
421 }
422
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;
428   }
429   return dropdown_open_;
430 }
431
432 bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
433   // TODO(oshima): handle IME.
434   DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED);
435
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;
440
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;
450
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;
458
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;
464
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;
470
471     // Move to the previous item if any.
472     case ui::VKEY_UP:
473       new_index = GetAdjacentIndex(model(), -1, selected_index_);
474       break;
475
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;
484       }
485       break;
486
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;
493
494     default:
495       return false;
496   }
497
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();
506   }
507
508   return true;
509 }
510
511 bool Combobox::OnKeyReleased(const ui::KeyEvent& e) {
512   if (style_ != STYLE_ACTION)
513     return false;  // crbug.com/127520
514
515   if (e.key_code() == ui::VKEY_SPACE && style_ == STYLE_ACTION)
516     OnPerformAction();
517
518   return false;
519 }
520
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;
528     }
529     case STYLE_ACTION: {
530       PaintButtons(canvas);
531       PaintText(canvas);
532       break;
533     }
534   }
535 }
536
537 void Combobox::OnFocus() {
538   GetInputMethod()->OnFocus();
539   View::OnFocus();
540   // Border renders differently when focused.
541   SchedulePaint();
542 }
543
544 void Combobox::OnBlur() {
545   GetInputMethod()->OnBlur();
546   if (selector_)
547     selector_->OnViewBlur();
548   // Border renders differently when focused.
549   SchedulePaint();
550 }
551
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();
558 }
559
560 void Combobox::OnComboboxModelChanged(ui::ComboboxModel* model) {
561   DCHECK_EQ(model, model_);
562   ModelChanged();
563 }
564
565 void Combobox::ButtonPressed(Button* sender, const ui::Event& event) {
566   if (!enabled())
567     return;
568
569   RequestFocus();
570
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;
580
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);
587   }
588 }
589
590 void Combobox::UpdateFromModel() {
591   const gfx::FontList& font_list = Combobox::GetFontList();
592
593   menu_ = new MenuItemView(this);
594   // MenuRunner owns |menu_|.
595   dropdown_list_menu_runner_.reset(new MenuRunner(menu_, MenuRunner::COMBOBOX));
596
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;
608     }
609
610     base::string16 text = model()->GetItemAt(i);
611
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);
615
616     if (style_ != STYLE_ACTION || i > 0) {
617       menu_->AppendMenuItem(i + kFirstMenuItemId, text, MenuItemView::NORMAL);
618       text_item_appended = true;
619     }
620
621     if (style_ != STYLE_ACTION || i == selected_index_)
622       width = std::max(width, gfx::GetStringWidth(text, font_list));
623   }
624
625   content_size_.SetSize(width, font_list.GetHeight());
626 }
627
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>());
635 }
636
637 void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
638   rect->set_x(GetMirroredXForRect(*rect));
639 }
640
641 void Combobox::PaintText(gfx::Canvas* canvas) {
642   gfx::Insets insets = GetInsets();
643
644   gfx::ScopedCanvas scoped_canvas(canvas);
645   canvas->ClipRect(GetContentsBounds());
646
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);
652
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_);
658
659   gfx::Size arrow_size = ArrowSize();
660   int disclosure_arrow_offset = width() - arrow_size.width() -
661       GetDisclosureArrowLeftPadding() - GetDisclosureArrowRightPadding();
662
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();
667
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);
671
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);
678
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);
691   }
692 }
693
694 void Combobox::PaintButtons(gfx::Canvas* canvas) {
695   DCHECK(style_ == STYLE_ACTION);
696
697   gfx::ScopedCanvas scoped_canvas(canvas);
698   if (base::i18n::IsRTL()) {
699     canvas->Translate(gfx::Vector2d(width(), 0));
700     canvas->Scale(-1, 1);
701   }
702
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()];
708
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();
722   }
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();
730   }
731
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();
740   }
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();
748   }
749 }
750
751 void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
752   if (!dropdown_list_menu_runner_.get())
753     UpdateFromModel();
754
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));
759
760   gfx::Rect lb = GetLocalBounds();
761   gfx::Point menu_position(lb.origin());
762
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);
768   }
769   lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
770
771   View::ConvertPointToScreen(this, &menu_position);
772   if (menu_position.x() < 0)
773     menu_position.set_x(0);
774
775   gfx::Rect bounds(menu_position, lb.size());
776
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);
781   }
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;
789   }
790   dropdown_open_ = false;
791   if (arrow_button_)
792     arrow_button_->SetState(original_state);
793   closed_time_ = base::Time::Now();
794
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);
799 }
800
801 void Combobox::OnPerformAction() {
802   NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, false);
803   SchedulePaint();
804
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);
809
810   if (weak_ptr && style_ == STYLE_ACTION)
811     selected_index_ = 0;
812 }
813
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;
820 }
821
822 int Combobox::GetDisclosureArrowLeftPadding() const {
823   switch (style_) {
824     case STYLE_NORMAL:
825       return kDisclosureArrowLeftPadding;
826     case STYLE_ACTION:
827       return kDisclosureArrowButtonLeftPadding;
828   }
829   NOTREACHED();
830   return 0;
831 }
832
833 int Combobox::GetDisclosureArrowRightPadding() const {
834   switch (style_) {
835     case STYLE_NORMAL:
836       return kDisclosureArrowRightPadding;
837     case STYLE_ACTION:
838       return kDisclosureArrowButtonRightPadding;
839   }
840   NOTREACHED();
841   return 0;
842 }
843
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
855
856   ui::NativeTheme::ExtraParams ignored;
857   return native_theme_for_arrow->GetPartSize(ui::NativeTheme::kComboboxArrow,
858                                              ui::NativeTheme::kNormal,
859                                              ignored);
860 }
861
862 }  // namespace views