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/logging.h"
8#include "base/strings/utf_string_conversions.h"
9#include "grit/ui_resources.h"
10#include "ui/base/accessibility/accessible_view_state.h"
11#include "ui/base/models/combobox_model.h"
12#include "ui/base/resource/resource_bundle.h"
13#include "ui/events/event.h"
14#include "ui/events/keycodes/keyboard_codes.h"
15#include "ui/gfx/animation/throb_animation.h"
16#include "ui/gfx/canvas.h"
17#include "ui/gfx/image/image.h"
18#include "ui/gfx/scoped_canvas.h"
19#include "ui/native_theme/native_theme.h"
20#include "ui/views/background.h"
21#include "ui/views/color_constants.h"
22#include "ui/views/controls/button/custom_button.h"
23#include "ui/views/controls/button/label_button.h"
24#include "ui/views/controls/combobox/combobox_listener.h"
25#include "ui/views/controls/focusable_border.h"
26#include "ui/views/controls/menu/menu_runner.h"
27#include "ui/views/controls/menu/menu_runner_handler.h"
28#include "ui/views/controls/menu/submenu_view.h"
29#include "ui/views/controls/prefix_selector.h"
30#include "ui/views/ime/input_method.h"
31#include "ui/views/mouse_constants.h"
32#include "ui/views/painter.h"
33#include "ui/views/widget/widget.h"
34
35namespace views {
36
37namespace {
38
39// Menu border widths
40const int kMenuBorderWidthLeft = 1;
41const int kMenuBorderWidthTop = 1;
42const int kMenuBorderWidthRight = 1;
43
44// Limit how small a combobox can be.
45const int kMinComboboxWidth = 25;
46
47// Size of the combobox arrow margins
48const int kDisclosureArrowLeftPadding = 7;
49const int kDisclosureArrowRightPadding = 7;
50const int kDisclosureArrowButtonLeftPadding = 11;
51const int kDisclosureArrowButtonRightPadding = 12;
52
53// Define the id of the first item in the menu (since it needs to be > 0)
54const int kFirstMenuItemId = 1000;
55
56const SkColor kInvalidTextColor = SK_ColorWHITE;
57
58// Used to indicate that no item is currently selected by the user.
59const int kNoSelection = -1;
60
61const int kBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON);
62const int kHoveredBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_H);
63const int kPressedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_P);
64const int kFocusedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_F);
65const int kFocusedHoveredBodyButtonImages[] =
66    IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_H);
67const int kFocusedPressedBodyButtonImages[] =
68    IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_P);
69
70#define MENU_IMAGE_GRID(x) { \
71    x ## _MENU_TOP, x ## _MENU_CENTER, x ## _MENU_BOTTOM, }
72
73const int kMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON);
74const int kHoveredMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_H);
75const int kPressedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_P);
76const int kFocusedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F);
77const int kFocusedHoveredMenuButtonImages[] =
78    MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_H);
79const int kFocusedPressedMenuButtonImages[] =
80    MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_P);
81
82#undef MENU_IMAGE_GRID
83
84// The background to use for invalid comboboxes.
85class InvalidBackground : public Background {
86 public:
87  InvalidBackground() {}
88  virtual ~InvalidBackground() {}
89
90  // Overridden from Background:
91  virtual void Paint(gfx::Canvas* canvas, View* view) const OVERRIDE {
92    gfx::Rect bounds(view->GetLocalBounds());
93    // Inset by 2 to leave 1 empty pixel between background and border.
94    bounds.Inset(2, 2, 2, 2);
95    canvas->FillRect(bounds, kWarningColor);
96  }
97
98 private:
99  DISALLOW_COPY_AND_ASSIGN(InvalidBackground);
100};
101
102// The transparent button which holds a button state but is not rendered.
103class TransparentButton : public CustomButton {
104 public:
105  TransparentButton(ButtonListener* listener)
106      : CustomButton(listener) {
107    SetAnimationDuration(LabelButton::kHoverAnimationDurationMs);
108  }
109  virtual ~TransparentButton() {}
110
111  double GetAnimationValue() const {
112    return hover_animation_->GetCurrentValue();
113  }
114
115 private:
116  DISALLOW_COPY_AND_ASSIGN(TransparentButton);
117};
118
119// Returns the next or previous valid index (depending on |increment|'s value).
120// Skips separator or disabled indices. Returns -1 if there is no valid adjacent
121// index.
122int GetAdjacentIndex(ui::ComboboxModel* model, int increment, int index) {
123  DCHECK(increment == -1 || increment == 1);
124
125  index += increment;
126  while (index >= 0 && index < model->GetItemCount()) {
127    if (!model->IsItemSeparatorAt(index) || !model->IsItemEnabledAt(index))
128      return index;
129    index += increment;
130  }
131  return kNoSelection;
132}
133
134// Returns the image resource ids of an array for the body button.
135//
136// TODO(hajimehoshi): This function should return the images for the 'disabled'
137// status. (crbug/270052)
138const int* GetBodyButtonImageIds(bool focused,
139                                 Button::ButtonState state,
140                                 size_t* num) {
141  DCHECK(num);
142  *num = 9;
143  switch (state) {
144    case Button::STATE_DISABLED:
145      return focused ? kFocusedBodyButtonImages : kBodyButtonImages;
146    case Button::STATE_NORMAL:
147      return focused ? kFocusedBodyButtonImages : kBodyButtonImages;
148    case Button::STATE_HOVERED:
149      return focused ?
150          kFocusedHoveredBodyButtonImages : kHoveredBodyButtonImages;
151    case Button::STATE_PRESSED:
152      return focused ?
153          kFocusedPressedBodyButtonImages : kPressedBodyButtonImages;
154    default:
155      NOTREACHED();
156  }
157  return NULL;
158}
159
160// Returns the image resource ids of an array for the menu button.
161const int* GetMenuButtonImageIds(bool focused,
162                                 Button::ButtonState state,
163                                 size_t* num) {
164  DCHECK(num);
165  *num = 3;
166  switch (state) {
167    case Button::STATE_DISABLED:
168      return focused ? kFocusedMenuButtonImages : kMenuButtonImages;
169    case Button::STATE_NORMAL:
170      return focused ? kFocusedMenuButtonImages : kMenuButtonImages;
171    case Button::STATE_HOVERED:
172      return focused ?
173          kFocusedHoveredMenuButtonImages : kHoveredMenuButtonImages;
174    case Button::STATE_PRESSED:
175      return focused ?
176          kFocusedPressedMenuButtonImages : kPressedMenuButtonImages;
177    default:
178      NOTREACHED();
179  }
180  return NULL;
181}
182
183// Returns the images for the menu buttons.
184std::vector<const gfx::ImageSkia*> GetMenuButtonImages(
185    bool focused,
186    Button::ButtonState state) {
187  const int* ids;
188  size_t num_ids;
189  ids = GetMenuButtonImageIds(focused, state, &num_ids);
190  std::vector<const gfx::ImageSkia*> images;
191  images.reserve(num_ids);
192  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
193  for (size_t i = 0; i < num_ids; i++)
194    images.push_back(rb.GetImageSkiaNamed(ids[i]));
195  return images;
196}
197
198// Paints three images in a column at the given location. The center image is
199// stretched so as to fit the given height.
200void PaintImagesVertically(gfx::Canvas* canvas,
201                           const gfx::ImageSkia& top_image,
202                           const gfx::ImageSkia& center_image,
203                           const gfx::ImageSkia& bottom_image,
204                           int x, int y, int width, int height) {
205  canvas->DrawImageInt(top_image,
206                       0, 0, top_image.width(), top_image.height(),
207                       x, y, width, top_image.height(), false);
208  y += top_image.height();
209  int center_height = height - top_image.height() - bottom_image.height();
210  canvas->DrawImageInt(center_image,
211                       0, 0, center_image.width(), center_image.height(),
212                       x, y, width, center_height, false);
213  y += center_height;
214  canvas->DrawImageInt(bottom_image,
215                       0, 0, bottom_image.width(), bottom_image.height(),
216                       x, y, width, bottom_image.height(), false);
217}
218
219// Paints the arrow button.
220void PaintArrowButton(
221    gfx::Canvas* canvas,
222    const std::vector<const gfx::ImageSkia*>& arrow_button_images,
223    int x, int height) {
224  PaintImagesVertically(canvas,
225                        *arrow_button_images[0],
226                        *arrow_button_images[1],
227                        *arrow_button_images[2],
228                        x, 0, arrow_button_images[0]->width(), height);
229}
230
231}  // namespace
232
233// static
234const char Combobox::kViewClassName[] = "views/Combobox";
235
236////////////////////////////////////////////////////////////////////////////////
237// Combobox, public:
238
239Combobox::Combobox(ui::ComboboxModel* model)
240    : model_(model),
241      style_(STYLE_SHOW_DROP_DOWN_ON_CLICK),
242      listener_(NULL),
243      selected_index_(model_->GetDefaultIndex()),
244      invalid_(false),
245      disclosure_arrow_(ui::ResourceBundle::GetSharedInstance().GetImageNamed(
246          IDR_MENU_DROPARROW).ToImageSkia()),
247      dropdown_open_(false),
248      text_button_(new TransparentButton(this)),
249      arrow_button_(new TransparentButton(this)) {
250  model_->AddObserver(this);
251  UpdateFromModel();
252  SetFocusable(true);
253  UpdateBorder();
254
255  // Initialize the button images.
256  Button::ButtonState button_states[] = {
257    Button::STATE_DISABLED,
258    Button::STATE_NORMAL,
259    Button::STATE_HOVERED,
260    Button::STATE_PRESSED,
261  };
262  for (int i = 0; i < 2; i++) {
263    for (size_t state_index = 0; state_index < arraysize(button_states);
264         state_index++) {
265      Button::ButtonState state = button_states[state_index];
266      size_t num;
267      bool focused = !!i;
268      const int* ids = GetBodyButtonImageIds(focused, state, &num);
269      body_button_painters_[focused][state].reset(
270          Painter::CreateImageGridPainter(ids));
271      menu_button_images_[focused][state] = GetMenuButtonImages(focused, state);
272    }
273  }
274
275  text_button_->SetVisible(true);
276  arrow_button_->SetVisible(true);
277  text_button_->SetFocusable(false);
278  arrow_button_->SetFocusable(false);
279  AddChildView(text_button_);
280  AddChildView(arrow_button_);
281}
282
283Combobox::~Combobox() {
284  model_->RemoveObserver(this);
285}
286
287// static
288const gfx::Font& Combobox::GetFont() {
289  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
290  return rb.GetFont(ui::ResourceBundle::BaseFont);
291}
292
293void Combobox::SetStyle(Style style) {
294  if (style_ == style)
295    return;
296
297  style_ = style;
298
299  UpdateBorder();
300  PreferredSizeChanged();
301}
302
303void Combobox::ModelChanged() {
304  selected_index_ = std::min(0, model_->GetItemCount());
305  UpdateFromModel();
306  PreferredSizeChanged();
307}
308
309void Combobox::SetSelectedIndex(int index) {
310  selected_index_ = index;
311  SchedulePaint();
312}
313
314bool Combobox::SelectValue(const base::string16& value) {
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
324void Combobox::SetAccessibleName(const string16& name) {
325  accessible_name_ = name;
326}
327
328void Combobox::SetInvalid(bool invalid) {
329  if (invalid == invalid_)
330    return;
331
332  invalid_ = invalid;
333
334  set_background(invalid_ ? new InvalidBackground() : NULL);
335  UpdateBorder();
336  SchedulePaint();
337}
338
339ui::TextInputClient* Combobox::GetTextInputClient() {
340  if (!selector_)
341    selector_.reset(new PrefixSelector(this));
342  return selector_.get();
343}
344
345void Combobox::Layout() {
346  PrefixDelegate::Layout();
347
348  gfx::Insets insets = GetInsets();
349  int text_button_width = 0;
350  int arrow_button_width = 0;
351
352  switch (style_) {
353    case STYLE_SHOW_DROP_DOWN_ON_CLICK: {
354      arrow_button_width = width();
355      break;
356    }
357    case STYLE_NOTIFY_ON_CLICK: {
358      arrow_button_width = GetDisclosureArrowLeftPadding() +
359          disclosure_arrow_->width() + 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
370bool Combobox::IsItemChecked(int id) const {
371  return false;
372}
373
374bool Combobox::IsCommandEnabled(int id) const {
375  return model()->IsItemEnabledAt(MenuCommandToIndex(id));
376}
377
378void Combobox::ExecuteCommand(int id) {
379  selected_index_ = MenuCommandToIndex(id);
380  OnSelectionChanged();
381}
382
383bool Combobox::GetAccelerator(int id, ui::Accelerator* accel) {
384  return false;
385}
386
387int Combobox::GetRowCount() {
388  return model()->GetItemCount();
389}
390
391int Combobox::GetSelectedRow() {
392  return selected_index_;
393}
394
395void Combobox::SetSelectedRow(int row) {
396  SetSelectedIndex(row);
397}
398
399string16 Combobox::GetTextForRow(int row) {
400  return model()->IsItemSeparatorAt(row) ? string16() : model()->GetItemAt(row);
401}
402
403////////////////////////////////////////////////////////////////////////////////
404// Combobox, View overrides:
405
406gfx::Size Combobox::GetPreferredSize() {
407  if (content_size_.IsEmpty())
408    UpdateFromModel();
409
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      disclosure_arrow_->width() + GetDisclosureArrowRightPadding();
416  return gfx::Size(total_width, content_size_.height() + insets.height());
417}
418
419const char* Combobox::GetClassName() const {
420  return kViewClassName;
421}
422
423bool 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
432bool 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_NOTIFY_ON_CLICK) {
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_NOTIFY_ON_CLICK)
490        return false;
491      HandleClickEvent();
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    DCHECK(!model()->IsItemSeparatorAt(new_index));
503    selected_index_ = new_index;
504    OnSelectionChanged();
505  }
506
507  return true;
508}
509
510bool Combobox::OnKeyReleased(const ui::KeyEvent& e) {
511  if (style_ != STYLE_NOTIFY_ON_CLICK)
512    return false;  // crbug.com/127520
513
514  if (e.key_code() == ui::VKEY_SPACE)
515    HandleClickEvent();
516
517  return false;
518}
519
520void Combobox::OnPaint(gfx::Canvas* canvas) {
521  switch (style_) {
522    case STYLE_SHOW_DROP_DOWN_ON_CLICK: {
523      OnPaintBackground(canvas);
524      PaintText(canvas);
525      OnPaintBorder(canvas);
526      break;
527    }
528    case STYLE_NOTIFY_ON_CLICK: {
529      PaintButtons(canvas);
530      PaintText(canvas);
531      break;
532    }
533  }
534}
535
536void Combobox::OnFocus() {
537  GetInputMethod()->OnFocus();
538  View::OnFocus();
539  // Border renders differently when focused.
540  SchedulePaint();
541}
542
543void Combobox::OnBlur() {
544  GetInputMethod()->OnBlur();
545  if (selector_)
546    selector_->OnViewBlur();
547  // Border renders differently when focused.
548  SchedulePaint();
549}
550
551void Combobox::GetAccessibleState(ui::AccessibleViewState* state) {
552  state->role = ui::AccessibilityTypes::ROLE_COMBOBOX;
553  state->name = accessible_name_;
554  state->value = model_->GetItemAt(selected_index_);
555  state->index = selected_index_;
556  state->count = model_->GetItemCount();
557}
558
559void Combobox::OnModelChanged() {
560  ModelChanged();
561}
562
563void Combobox::ButtonPressed(Button* sender, const ui::Event& event) {
564  RequestFocus();
565
566  if (sender == text_button_) {
567    HandleClickEvent();
568  } else {
569    DCHECK_EQ(arrow_button_, sender);
570    // TODO(hajimehoshi): Fix the problem that the arrow button blinks when
571    // cliking this while the dropdown menu is opened.
572    const base::TimeDelta delta = base::Time::Now() - closed_time_;
573    if (delta.InMilliseconds() <= kMinimumMsBetweenButtonClicks)
574      return;
575
576    ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE;
577    if (event.IsKeyEvent())
578      source_type = ui::MENU_SOURCE_KEYBOARD;
579    else if (event.IsGestureEvent() || event.IsTouchEvent())
580      source_type = ui::MENU_SOURCE_TOUCH;
581    ShowDropDownMenu(source_type);
582  }
583}
584
585void Combobox::UpdateFromModel() {
586  int max_width = 0;
587  const gfx::Font& font = Combobox::GetFont();
588
589  MenuItemView* menu = new MenuItemView(this);
590  // MenuRunner owns |menu|.
591  dropdown_list_menu_runner_.reset(new MenuRunner(menu));
592
593  int num_items = model()->GetItemCount();
594  for (int i = 0; i < num_items; ++i) {
595    if (model()->IsItemSeparatorAt(i)) {
596      menu->AppendSeparator();
597      continue;
598    }
599
600    string16 text = model()->GetItemAt(i);
601
602    // Inserting the Unicode formatting characters if necessary so that the
603    // text is displayed correctly in right-to-left UIs.
604    base::i18n::AdjustStringForLocaleDirection(&text);
605
606    menu->AppendMenuItem(i + kFirstMenuItemId, text, MenuItemView::NORMAL);
607    max_width = std::max(max_width, font.GetStringWidth(text));
608  }
609
610  content_size_.SetSize(max_width, font.GetHeight());
611}
612
613void Combobox::UpdateBorder() {
614  FocusableBorder* border = new FocusableBorder();
615  if (style_ == STYLE_NOTIFY_ON_CLICK)
616    border->SetInsets(8, 13, 8, 13);
617  if (invalid_)
618    border->SetColor(kWarningColor);
619  set_border(border);
620}
621
622void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
623  rect->set_x(GetMirroredXForRect(*rect));
624}
625
626void Combobox::PaintText(gfx::Canvas* canvas) {
627  gfx::Insets insets = GetInsets();
628
629  gfx::ScopedCanvas scoped_canvas(canvas);
630  canvas->ClipRect(GetContentsBounds());
631
632  int x = insets.left();
633  int y = insets.top();
634  int text_height = height() - insets.height();
635  SkColor text_color = invalid() ? kInvalidTextColor :
636      GetNativeTheme()->GetSystemColor(
637          ui::NativeTheme::kColorId_LabelEnabledColor);
638
639  DCHECK_GE(selected_index_, 0);
640  DCHECK_LT(selected_index_, model()->GetItemCount());
641  if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
642    selected_index_ = 0;
643  string16 text = model()->GetItemAt(selected_index_);
644
645  int disclosure_arrow_offset = width() - disclosure_arrow_->width() -
646      GetDisclosureArrowLeftPadding() - GetDisclosureArrowRightPadding();
647
648  const gfx::Font& font = Combobox::GetFont();
649  int text_width = font.GetStringWidth(text);
650  if ((text_width + insets.width()) > disclosure_arrow_offset)
651    text_width = disclosure_arrow_offset - insets.width();
652
653  gfx::Rect text_bounds(x, y, text_width, text_height);
654  AdjustBoundsForRTLUI(&text_bounds);
655  canvas->DrawStringInt(text, font, text_color, text_bounds);
656
657  int arrow_x = disclosure_arrow_offset + GetDisclosureArrowLeftPadding();
658  gfx::Rect arrow_bounds(arrow_x,
659                         height() / 2 - disclosure_arrow_->height() / 2,
660                         disclosure_arrow_->width(),
661                         disclosure_arrow_->height());
662  AdjustBoundsForRTLUI(&arrow_bounds);
663
664  SkPaint paint;
665  // This makes the arrow subtractive.
666  if (invalid())
667    paint.setXfermodeMode(SkXfermode::kDstOut_Mode);
668  canvas->DrawImageInt(*disclosure_arrow_, arrow_bounds.x(), arrow_bounds.y(),
669                       paint);
670}
671
672void Combobox::PaintButtons(gfx::Canvas* canvas) {
673  DCHECK(style_ == STYLE_NOTIFY_ON_CLICK);
674
675  gfx::ScopedCanvas scoped_canvas(canvas);
676  if (base::i18n::IsRTL()) {
677    canvas->Translate(gfx::Vector2d(width(), 0));
678    canvas->Scale(-1, 1);
679  }
680
681  bool focused = HasFocus();
682  const std::vector<const gfx::ImageSkia*>& arrow_button_images =
683      menu_button_images_[focused][
684          arrow_button_->state() == Button::STATE_HOVERED ?
685          Button::STATE_NORMAL : arrow_button_->state()];
686
687  int text_button_hover_alpha =
688      text_button_->state() == Button::STATE_PRESSED ? 0 :
689      static_cast<int>(static_cast<TransparentButton*>(text_button_)->
690                       GetAnimationValue() * 255);
691  if (text_button_hover_alpha < 255) {
692    canvas->SaveLayerAlpha(255 - text_button_hover_alpha);
693    Painter* text_button_painter =
694        body_button_painters_[focused][
695            text_button_->state() == Button::STATE_HOVERED ?
696            Button::STATE_NORMAL : text_button_->state()].get();
697    Painter::PaintPainterAt(canvas, text_button_painter,
698                            gfx::Rect(0, 0, text_button_->width(), height()));
699    canvas->Restore();
700  }
701  if (0 < text_button_hover_alpha) {
702    canvas->SaveLayerAlpha(text_button_hover_alpha);
703    Painter* text_button_hovered_painter =
704        body_button_painters_[focused][Button::STATE_HOVERED].get();
705    Painter::PaintPainterAt(canvas, text_button_hovered_painter,
706                            gfx::Rect(0, 0, text_button_->width(), height()));
707    canvas->Restore();
708  }
709
710  int arrow_button_hover_alpha =
711      arrow_button_->state() == Button::STATE_PRESSED ? 0 :
712      static_cast<int>(static_cast<TransparentButton*>(arrow_button_)->
713                       GetAnimationValue() * 255);
714  if (arrow_button_hover_alpha < 255) {
715    canvas->SaveLayerAlpha(255 - arrow_button_hover_alpha);
716    PaintArrowButton(canvas, arrow_button_images, arrow_button_->x(), height());
717    canvas->Restore();
718  }
719  if (0 < arrow_button_hover_alpha) {
720    canvas->SaveLayerAlpha(arrow_button_hover_alpha);
721    const std::vector<const gfx::ImageSkia*>& arrow_button_hovered_images =
722        menu_button_images_[focused][Button::STATE_HOVERED];
723    PaintArrowButton(canvas, arrow_button_hovered_images,
724                     arrow_button_->x(), height());
725    canvas->Restore();
726  }
727}
728
729void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
730  if (!dropdown_list_menu_runner_.get())
731    UpdateFromModel();
732
733  // Extend the menu to the width of the combobox.
734  MenuItemView* menu = dropdown_list_menu_runner_->GetMenu();
735  SubmenuView* submenu = menu->CreateSubmenu();
736  submenu->set_minimum_preferred_width(
737      size().width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
738
739  gfx::Rect lb = GetLocalBounds();
740  gfx::Point menu_position(lb.origin());
741
742  // Inset the menu's requested position so the border of the menu lines up
743  // with the border of the combobox.
744  menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft);
745  menu_position.set_y(menu_position.y() + kMenuBorderWidthTop);
746  lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
747
748  View::ConvertPointToScreen(this, &menu_position);
749  if (menu_position.x() < 0)
750    menu_position.set_x(0);
751
752  gfx::Rect bounds(menu_position, lb.size());
753
754  Button::ButtonState original_state = Button::STATE_NORMAL;
755  if (arrow_button_) {
756    original_state = arrow_button_->state();
757    arrow_button_->SetState(Button::STATE_PRESSED);
758  }
759  dropdown_open_ = true;
760  if (dropdown_list_menu_runner_->RunMenuAt(GetWidget(), NULL, bounds,
761                                            MenuItemView::TOPLEFT, source_type,
762                                            MenuRunner::COMBOBOX) ==
763      MenuRunner::MENU_DELETED) {
764    return;
765  }
766  dropdown_open_ = false;
767  if (arrow_button_)
768    arrow_button_->SetState(original_state);
769  closed_time_ = base::Time::Now();
770
771  // Need to explicitly clear mouse handler so that events get sent
772  // properly after the menu finishes running. If we don't do this, then
773  // the first click to other parts of the UI is eaten.
774  SetMouseHandler(NULL);
775}
776
777void Combobox::OnSelectionChanged() {
778  NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_VALUE_CHANGED, false);
779  SchedulePaint();
780  if (listener_)
781    listener_->OnSelectedIndexChanged(this);
782  // |this| may now be deleted.
783}
784
785int Combobox::MenuCommandToIndex(int menu_command_id) const {
786  // (note that the id received is offset by kFirstMenuItemId)
787  // Revert menu ID offset to map back to combobox model.
788  int index = menu_command_id - kFirstMenuItemId;
789  DCHECK_LT(index, model()->GetItemCount());
790  return index;
791}
792
793int Combobox::GetDisclosureArrowLeftPadding() const {
794  switch (style_) {
795    case STYLE_SHOW_DROP_DOWN_ON_CLICK:
796      return kDisclosureArrowLeftPadding;
797    case STYLE_NOTIFY_ON_CLICK:
798      return kDisclosureArrowButtonLeftPadding;
799  }
800  NOTREACHED();
801  return 0;
802}
803
804int Combobox::GetDisclosureArrowRightPadding() const {
805  switch (style_) {
806    case STYLE_SHOW_DROP_DOWN_ON_CLICK:
807      return kDisclosureArrowRightPadding;
808    case STYLE_NOTIFY_ON_CLICK:
809      return kDisclosureArrowButtonRightPadding;
810  }
811  NOTREACHED();
812  return 0;
813}
814
815void Combobox::HandleClickEvent() {
816  if (style_ != STYLE_NOTIFY_ON_CLICK)
817    return;
818
819  if (listener_)
820    listener_->OnComboboxTextButtonClicked(this);
821}
822
823}  // namespace views
824