Update To 11.40.268.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 "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"
40
41 namespace views {
42
43 namespace {
44
45 // Menu border widths
46 const int kMenuBorderWidthLeft = 1;
47 const int kMenuBorderWidthTop = 1;
48 const int kMenuBorderWidthRight = 1;
49
50 // Limit how small a combobox can be.
51 const int kMinComboboxWidth = 25;
52
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;
58
59 // Define the id of the first item in the menu (since it needs to be > 0)
60 const int kFirstMenuItemId = 1000;
61
62 // Used to indicate that no item is currently selected by the user.
63 const int kNoSelection = -1;
64
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);
73
74 #define MENU_IMAGE_GRID(x) { \
75     x ## _MENU_TOP, x ## _MENU_CENTER, x ## _MENU_BOTTOM, }
76
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);
85
86 #undef MENU_IMAGE_GRID
87
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);
94   }
95   ~TransparentButton() override {}
96
97   bool OnMousePressed(const ui::MouseEvent& mouse_event) override {
98     parent()->RequestFocus();
99     return true;
100   }
101
102   double GetAnimationValue() const {
103     return hover_animation_->GetCurrentValue();
104   }
105
106  private:
107   DISALLOW_COPY_AND_ASSIGN(TransparentButton);
108 };
109
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);
115
116   index += increment;
117   while (index >= 0 && index < model->GetItemCount()) {
118     if (!model->IsItemSeparatorAt(index) || !model->IsItemEnabledAt(index))
119       return index;
120     index += increment;
121   }
122   return kNoSelection;
123 }
124
125 // Returns the image resource ids of an array for the body button.
126 //
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();
147   }
148   return NULL;
149 }
150
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();
170   }
171   return NULL;
172 }
173
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;
187 }
188
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);
208 }
209
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);
220 }
221
222 }  // namespace
223
224 // static
225 const char Combobox::kViewClassName[] = "views/Combobox";
226
227 ////////////////////////////////////////////////////////////////////////////////
228 // Combobox, public:
229
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();
245
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,
252   };
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);
263     }
264   }
265
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_);
272 }
273
274 Combobox::~Combobox() {
275   model_->RemoveObserver(this);
276 }
277
278 // static
279 const gfx::FontList& Combobox::GetFontList() {
280   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
281   return rb.GetFontList(ui::ResourceBundle::BaseFont);
282 }
283
284 void Combobox::SetStyle(Style style) {
285   if (style_ == style)
286     return;
287
288   style_ = style;
289   if (style_ == STYLE_ACTION)
290     selected_index_ = 0;
291
292   UpdateBorder();
293   UpdateFromModel();
294   PreferredSizeChanged();
295 }
296
297 void Combobox::ModelChanged() {
298   selected_index_ = std::min(0, model_->GetItemCount());
299   UpdateFromModel();
300   PreferredSizeChanged();
301 }
302
303 void Combobox::SetSelectedIndex(int index) {
304   if (style_ == STYLE_ACTION)
305     return;
306
307   selected_index_ = index;
308   SchedulePaint();
309 }
310
311 bool Combobox::SelectValue(const base::string16& value) {
312   if (style_ == STYLE_ACTION)
313     return false;
314
315   for (int i = 0; i < model()->GetItemCount(); ++i) {
316     if (value == model()->GetItemAt(i)) {
317       SetSelectedIndex(i);
318       return true;
319     }
320   }
321   return false;
322 }
323
324 void Combobox::SetAccessibleName(const base::string16& name) {
325   accessible_name_ = name;
326 }
327
328 void Combobox::SetInvalid(bool invalid) {
329   if (invalid == invalid_)
330     return;
331
332   invalid_ = invalid;
333
334   UpdateBorder();
335   SchedulePaint();
336 }
337
338 ui::TextInputClient* Combobox::GetTextInputClient() {
339   if (!selector_)
340     selector_.reset(new PrefixSelector(this));
341   return selector_.get();
342 }
343
344 void Combobox::Layout() {
345   PrefixDelegate::Layout();
346
347   gfx::Insets insets = GetInsets();
348   int text_button_width = 0;
349   int arrow_button_width = 0;
350
351   switch (style_) {
352     case STYLE_NORMAL: {
353       arrow_button_width = width();
354       break;
355     }
356     case STYLE_ACTION: {
357       arrow_button_width = GetDisclosureArrowLeftPadding() +
358           ArrowSize().width() +
359           GetDisclosureArrowRightPadding();
360       text_button_width = width() - arrow_button_width;
361       break;
362     }
363   }
364
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());
368 }
369
370 bool Combobox::IsItemChecked(int id) const {
371   return false;
372 }
373
374 bool Combobox::IsCommandEnabled(int id) const {
375   return model()->IsItemEnabledAt(MenuCommandToIndex(id));
376 }
377
378 void Combobox::ExecuteCommand(int id) {
379   selected_index_ = MenuCommandToIndex(id);
380   OnPerformAction();
381 }
382
383 bool Combobox::GetAccelerator(int id, ui::Accelerator* accel) const {
384   return false;
385 }
386
387 int Combobox::GetRowCount() {
388   return model()->GetItemCount();
389 }
390
391 int Combobox::GetSelectedRow() {
392   return selected_index_;
393 }
394
395 void Combobox::SetSelectedRow(int row) {
396   int prev_index = selected_index_;
397   SetSelectedIndex(row);
398   if (selected_index_ != prev_index)
399     OnPerformAction();
400 }
401
402 base::string16 Combobox::GetTextForRow(int row) {
403   return model()->IsItemSeparatorAt(row) ? base::string16() :
404                                            model()->GetItemAt(row);
405 }
406
407 ////////////////////////////////////////////////////////////////////////////////
408 // Combobox, View overrides:
409
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());
422 }
423
424 const char* Combobox::GetClassName() const {
425   return kViewClassName;
426 }
427
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;
433   }
434   return dropdown_open_;
435 }
436
437 bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
438   // TODO(oshima): handle IME.
439   DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED);
440
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;
445
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;
455
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;
463
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;
469
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;
475
476     // Move to the previous item if any.
477     case ui::VKEY_UP:
478       new_index = GetAdjacentIndex(model(), -1, selected_index_);
479       break;
480
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;
489       }
490       break;
491
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;
498
499     default:
500       return false;
501   }
502
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();
511   }
512
513   return true;
514 }
515
516 bool Combobox::OnKeyReleased(const ui::KeyEvent& e) {
517   if (style_ != STYLE_ACTION)
518     return false;  // crbug.com/127520
519
520   if (e.key_code() == ui::VKEY_SPACE && style_ == STYLE_ACTION)
521     OnPerformAction();
522
523   return false;
524 }
525
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;
533     }
534     case STYLE_ACTION: {
535       PaintButtons(canvas);
536       PaintText(canvas);
537       break;
538     }
539   }
540 }
541
542 void Combobox::OnFocus() {
543   GetInputMethod()->OnFocus();
544   View::OnFocus();
545   // Border renders differently when focused.
546   SchedulePaint();
547 }
548
549 void Combobox::OnBlur() {
550   GetInputMethod()->OnBlur();
551   if (selector_)
552     selector_->OnViewBlur();
553   // Border renders differently when focused.
554   SchedulePaint();
555 }
556
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();
563 }
564
565 void Combobox::OnComboboxModelChanged(ui::ComboboxModel* model) {
566   DCHECK_EQ(model, model_);
567   ModelChanged();
568 }
569
570 void Combobox::ButtonPressed(Button* sender, const ui::Event& event) {
571   if (!enabled())
572     return;
573
574   RequestFocus();
575
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;
585
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);
592   }
593 }
594
595 void Combobox::UpdateFromModel() {
596   const gfx::FontList& font_list = Combobox::GetFontList();
597
598   menu_ = new MenuItemView(this);
599   // MenuRunner owns |menu_|.
600   dropdown_list_menu_runner_.reset(new MenuRunner(menu_, MenuRunner::COMBOBOX));
601
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;
613     }
614
615     base::string16 text = model()->GetItemAt(i);
616
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);
620
621     if (style_ != STYLE_ACTION || i > 0) {
622       menu_->AppendMenuItem(i + kFirstMenuItemId, text, MenuItemView::NORMAL);
623       text_item_appended = true;
624     }
625
626     if (style_ != STYLE_ACTION || i == selected_index_)
627       width = std::max(width, gfx::GetStringWidth(text, font_list));
628   }
629
630   content_size_.SetSize(width, font_list.GetHeight());
631 }
632
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());
640 }
641
642 void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
643   rect->set_x(GetMirroredXForRect(*rect));
644 }
645
646 void Combobox::PaintText(gfx::Canvas* canvas) {
647   gfx::Insets insets = GetInsets();
648   insets += gfx::Insets(0, Textfield::kTextPadding, 0, Textfield::kTextPadding);
649
650   gfx::ScopedCanvas scoped_canvas(canvas);
651   canvas->ClipRect(GetContentsBounds());
652
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);
658
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_);
664
665   gfx::Size arrow_size = ArrowSize();
666   int disclosure_arrow_offset = width() - arrow_size.width() -
667       GetDisclosureArrowLeftPadding() - GetDisclosureArrowRightPadding();
668
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();
673
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);
677
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);
684
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);
697   }
698 }
699
700 void Combobox::PaintButtons(gfx::Canvas* canvas) {
701   DCHECK(style_ == STYLE_ACTION);
702
703   gfx::ScopedCanvas scoped_canvas(canvas);
704   if (base::i18n::IsRTL()) {
705     canvas->Translate(gfx::Vector2d(width(), 0));
706     canvas->Scale(-1, 1);
707   }
708
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()];
714
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();
728   }
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();
736   }
737
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();
746   }
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();
754   }
755 }
756
757 void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
758   if (!dropdown_list_menu_runner_.get())
759     UpdateFromModel();
760
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));
765
766   gfx::Rect lb = GetLocalBounds();
767   gfx::Point menu_position(lb.origin());
768
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);
774   }
775   lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
776
777   View::ConvertPointToScreen(this, &menu_position);
778   if (menu_position.x() < 0)
779     menu_position.set_x(0);
780
781   gfx::Rect bounds(menu_position, lb.size());
782
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);
787   }
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;
795   }
796   dropdown_open_ = false;
797   if (arrow_button_)
798     arrow_button_->SetState(original_state);
799   closed_time_ = base::Time::Now();
800
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);
805 }
806
807 void Combobox::OnPerformAction() {
808   NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, false);
809   SchedulePaint();
810
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);
815
816   if (weak_ptr && style_ == STYLE_ACTION)
817     selected_index_ = 0;
818 }
819
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;
826 }
827
828 int Combobox::GetDisclosureArrowLeftPadding() const {
829   switch (style_) {
830     case STYLE_NORMAL:
831       return kDisclosureArrowLeftPadding;
832     case STYLE_ACTION:
833       return kDisclosureArrowButtonLeftPadding;
834   }
835   NOTREACHED();
836   return 0;
837 }
838
839 int Combobox::GetDisclosureArrowRightPadding() const {
840   switch (style_) {
841     case STYLE_NORMAL:
842       return kDisclosureArrowRightPadding;
843     case STYLE_ACTION:
844       return kDisclosureArrowButtonRightPadding;
845   }
846   NOTREACHED();
847   return 0;
848 }
849
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
861
862   ui::NativeTheme::ExtraParams ignored;
863   return native_theme_for_arrow->GetPartSize(ui::NativeTheme::kComboboxArrow,
864                                              ui::NativeTheme::kNormal,
865                                              ignored);
866 }
867
868 }  // namespace views