// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/controls/combobox/combobox.h"

#include "base/bind.h"
#include "base/logging.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/strings/utf_string_conversions.h"
#include "grit/ui_resources.h"
#include "ui/accessibility/ax_view_state.h"
#include "ui/base/models/combobox_model.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/animation/throb_animation.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/gfx/text_utils.h"
#include "ui/native_theme/common_theme.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/background.h"
#include "ui/views/color_constants.h"
#include "ui/views/controls/button/custom_button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/combobox/combobox_listener.h"
#include "ui/views/controls/focusable_border.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/controls/menu/menu_runner.h"
#include "ui/views/controls/menu/menu_runner_handler.h"
#include "ui/views/controls/menu/submenu_view.h"
#include "ui/views/controls/prefix_selector.h"
#include "ui/views/ime/input_method.h"
#include "ui/views/mouse_constants.h"
#include "ui/views/painter.h"
#include "ui/views/widget/widget.h"

namespace views {

namespace {

// Menu border widths
const int kMenuBorderWidthLeft = 1;
const int kMenuBorderWidthTop = 1;
const int kMenuBorderWidthRight = 1;

// Limit how small a combobox can be.
const int kMinComboboxWidth = 25;

// Size of the combobox arrow margins
const int kDisclosureArrowLeftPadding = 7;
const int kDisclosureArrowRightPadding = 7;
const int kDisclosureArrowButtonLeftPadding = 11;
const int kDisclosureArrowButtonRightPadding = 12;

// Define the id of the first item in the menu (since it needs to be > 0)
const int kFirstMenuItemId = 1000;

// Used to indicate that no item is currently selected by the user.
const int kNoSelection = -1;

const int kBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON);
const int kHoveredBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_H);
const int kPressedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_P);
const int kFocusedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_F);
const int kFocusedHoveredBodyButtonImages[] =
    IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_H);
const int kFocusedPressedBodyButtonImages[] =
    IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_P);

#define MENU_IMAGE_GRID(x) { \
    x ## _MENU_TOP, x ## _MENU_CENTER, x ## _MENU_BOTTOM, }

const int kMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON);
const int kHoveredMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_H);
const int kPressedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_P);
const int kFocusedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F);
const int kFocusedHoveredMenuButtonImages[] =
    MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_H);
const int kFocusedPressedMenuButtonImages[] =
    MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_P);

#undef MENU_IMAGE_GRID

// The transparent button which holds a button state but is not rendered.
class TransparentButton : public CustomButton {
 public:
  TransparentButton(ButtonListener* listener)
      : CustomButton(listener) {
    SetAnimationDuration(LabelButton::kHoverAnimationDurationMs);
  }
  virtual ~TransparentButton() {}

  virtual bool OnMousePressed(const ui::MouseEvent& mouse_event) OVERRIDE {
    parent()->RequestFocus();
    return true;
  }

  double GetAnimationValue() const {
    return hover_animation_->GetCurrentValue();
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(TransparentButton);
};

// Returns the next or previous valid index (depending on |increment|'s value).
// Skips separator or disabled indices. Returns -1 if there is no valid adjacent
// index.
int GetAdjacentIndex(ui::ComboboxModel* model, int increment, int index) {
  DCHECK(increment == -1 || increment == 1);

  index += increment;
  while (index >= 0 && index < model->GetItemCount()) {
    if (!model->IsItemSeparatorAt(index) || !model->IsItemEnabledAt(index))
      return index;
    index += increment;
  }
  return kNoSelection;
}

// Returns the image resource ids of an array for the body button.
//
// TODO(hajimehoshi): This function should return the images for the 'disabled'
// status. (crbug/270052)
const int* GetBodyButtonImageIds(bool focused,
                                 Button::ButtonState state,
                                 size_t* num) {
  DCHECK(num);
  *num = 9;
  switch (state) {
    case Button::STATE_DISABLED:
      return focused ? kFocusedBodyButtonImages : kBodyButtonImages;
    case Button::STATE_NORMAL:
      return focused ? kFocusedBodyButtonImages : kBodyButtonImages;
    case Button::STATE_HOVERED:
      return focused ?
          kFocusedHoveredBodyButtonImages : kHoveredBodyButtonImages;
    case Button::STATE_PRESSED:
      return focused ?
          kFocusedPressedBodyButtonImages : kPressedBodyButtonImages;
    default:
      NOTREACHED();
  }
  return NULL;
}

// Returns the image resource ids of an array for the menu button.
const int* GetMenuButtonImageIds(bool focused,
                                 Button::ButtonState state,
                                 size_t* num) {
  DCHECK(num);
  *num = 3;
  switch (state) {
    case Button::STATE_DISABLED:
      return focused ? kFocusedMenuButtonImages : kMenuButtonImages;
    case Button::STATE_NORMAL:
      return focused ? kFocusedMenuButtonImages : kMenuButtonImages;
    case Button::STATE_HOVERED:
      return focused ?
          kFocusedHoveredMenuButtonImages : kHoveredMenuButtonImages;
    case Button::STATE_PRESSED:
      return focused ?
          kFocusedPressedMenuButtonImages : kPressedMenuButtonImages;
    default:
      NOTREACHED();
  }
  return NULL;
}

// Returns the images for the menu buttons.
std::vector<const gfx::ImageSkia*> GetMenuButtonImages(
    bool focused,
    Button::ButtonState state) {
  const int* ids;
  size_t num_ids;
  ids = GetMenuButtonImageIds(focused, state, &num_ids);
  std::vector<const gfx::ImageSkia*> images;
  images.reserve(num_ids);
  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
  for (size_t i = 0; i < num_ids; i++)
    images.push_back(rb.GetImageSkiaNamed(ids[i]));
  return images;
}

// Paints three images in a column at the given location. The center image is
// stretched so as to fit the given height.
void PaintImagesVertically(gfx::Canvas* canvas,
                           const gfx::ImageSkia& top_image,
                           const gfx::ImageSkia& center_image,
                           const gfx::ImageSkia& bottom_image,
                           int x, int y, int width, int height) {
  canvas->DrawImageInt(top_image,
                       0, 0, top_image.width(), top_image.height(),
                       x, y, width, top_image.height(), false);
  y += top_image.height();
  int center_height = height - top_image.height() - bottom_image.height();
  canvas->DrawImageInt(center_image,
                       0, 0, center_image.width(), center_image.height(),
                       x, y, width, center_height, false);
  y += center_height;
  canvas->DrawImageInt(bottom_image,
                       0, 0, bottom_image.width(), bottom_image.height(),
                       x, y, width, bottom_image.height(), false);
}

// Paints the arrow button.
void PaintArrowButton(
    gfx::Canvas* canvas,
    const std::vector<const gfx::ImageSkia*>& arrow_button_images,
    int x, int height) {
  PaintImagesVertically(canvas,
                        *arrow_button_images[0],
                        *arrow_button_images[1],
                        *arrow_button_images[2],
                        x, 0, arrow_button_images[0]->width(), height);
}

}  // namespace

// static
const char Combobox::kViewClassName[] = "views/Combobox";

////////////////////////////////////////////////////////////////////////////////
// Combobox, public:

Combobox::Combobox(ui::ComboboxModel* model)
    : model_(model),
      style_(STYLE_NORMAL),
      listener_(NULL),
      selected_index_(model_->GetDefaultIndex()),
      invalid_(false),
      dropdown_open_(false),
      text_button_(new TransparentButton(this)),
      arrow_button_(new TransparentButton(this)),
      weak_ptr_factory_(this) {
  model_->AddObserver(this);
  UpdateFromModel();
  SetFocusable(true);
  UpdateBorder();

  // Initialize the button images.
  Button::ButtonState button_states[] = {
    Button::STATE_DISABLED,
    Button::STATE_NORMAL,
    Button::STATE_HOVERED,
    Button::STATE_PRESSED,
  };
  for (int i = 0; i < 2; i++) {
    for (size_t state_index = 0; state_index < arraysize(button_states);
         state_index++) {
      Button::ButtonState state = button_states[state_index];
      size_t num;
      bool focused = !!i;
      const int* ids = GetBodyButtonImageIds(focused, state, &num);
      body_button_painters_[focused][state].reset(
          Painter::CreateImageGridPainter(ids));
      menu_button_images_[focused][state] = GetMenuButtonImages(focused, state);
    }
  }

  text_button_->SetVisible(true);
  arrow_button_->SetVisible(true);
  text_button_->SetFocusable(false);
  arrow_button_->SetFocusable(false);
  AddChildView(text_button_);
  AddChildView(arrow_button_);
}

Combobox::~Combobox() {
  model_->RemoveObserver(this);
}

// static
const gfx::FontList& Combobox::GetFontList() {
  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
  return rb.GetFontList(ui::ResourceBundle::BaseFont);
}

void Combobox::SetStyle(Style style) {
  if (style_ == style)
    return;

  style_ = style;
  if (style_ == STYLE_ACTION)
    selected_index_ = 0;

  UpdateBorder();
  UpdateFromModel();
  PreferredSizeChanged();
}

void Combobox::ModelChanged() {
  selected_index_ = std::min(0, model_->GetItemCount());
  UpdateFromModel();
  PreferredSizeChanged();
}

void Combobox::SetSelectedIndex(int index) {
  if (style_ == STYLE_ACTION)
    return;

  selected_index_ = index;
  SchedulePaint();
}

bool Combobox::SelectValue(const base::string16& value) {
  if (style_ == STYLE_ACTION)
    return false;

  for (int i = 0; i < model()->GetItemCount(); ++i) {
    if (value == model()->GetItemAt(i)) {
      SetSelectedIndex(i);
      return true;
    }
  }
  return false;
}

void Combobox::SetAccessibleName(const base::string16& name) {
  accessible_name_ = name;
}

void Combobox::SetInvalid(bool invalid) {
  if (invalid == invalid_)
    return;

  invalid_ = invalid;

  UpdateBorder();
  SchedulePaint();
}

ui::TextInputClient* Combobox::GetTextInputClient() {
  if (!selector_)
    selector_.reset(new PrefixSelector(this));
  return selector_.get();
}

void Combobox::Layout() {
  PrefixDelegate::Layout();

  gfx::Insets insets = GetInsets();
  int text_button_width = 0;
  int arrow_button_width = 0;

  switch (style_) {
    case STYLE_NORMAL: {
      arrow_button_width = width();
      break;
    }
    case STYLE_ACTION: {
      arrow_button_width = GetDisclosureArrowLeftPadding() +
          ArrowSize().width() +
          GetDisclosureArrowRightPadding();
      text_button_width = width() - arrow_button_width;
      break;
    }
  }

  int arrow_button_x = std::max(0, text_button_width);
  text_button_->SetBounds(0, 0, std::max(0, text_button_width), height());
  arrow_button_->SetBounds(arrow_button_x, 0, arrow_button_width, height());
}

bool Combobox::IsItemChecked(int id) const {
  return false;
}

bool Combobox::IsCommandEnabled(int id) const {
  return model()->IsItemEnabledAt(MenuCommandToIndex(id));
}

void Combobox::ExecuteCommand(int id) {
  selected_index_ = MenuCommandToIndex(id);
  OnPerformAction();
}

bool Combobox::GetAccelerator(int id, ui::Accelerator* accel) const {
  return false;
}

int Combobox::GetRowCount() {
  return model()->GetItemCount();
}

int Combobox::GetSelectedRow() {
  return selected_index_;
}

void Combobox::SetSelectedRow(int row) {
  int prev_index = selected_index_;
  SetSelectedIndex(row);
  if (selected_index_ != prev_index)
    OnPerformAction();
}

base::string16 Combobox::GetTextForRow(int row) {
  return model()->IsItemSeparatorAt(row) ? base::string16() :
                                           model()->GetItemAt(row);
}

////////////////////////////////////////////////////////////////////////////////
// Combobox, View overrides:

gfx::Size Combobox::GetPreferredSize() const {
  // The preferred size will drive the local bounds which in turn is used to set
  // the minimum width for the dropdown list.
  gfx::Insets insets = GetInsets();
  int total_width = std::max(kMinComboboxWidth, content_size_.width()) +
      insets.width() + GetDisclosureArrowLeftPadding() +
      ArrowSize().width() + GetDisclosureArrowRightPadding();
  return gfx::Size(total_width, content_size_.height() + insets.height());
}

const char* Combobox::GetClassName() const {
  return kViewClassName;
}

bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) {
  // Escape should close the drop down list when it is active, not host UI.
  if (e.key_code() != ui::VKEY_ESCAPE ||
      e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) {
    return false;
  }
  return dropdown_open_;
}

bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
  // TODO(oshima): handle IME.
  DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED);

  DCHECK_GE(selected_index_, 0);
  DCHECK_LT(selected_index_, model()->GetItemCount());
  if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
    selected_index_ = 0;

  bool show_menu = false;
  int new_index = kNoSelection;
  switch (e.key_code()) {
    // Show the menu on F4 without modifiers.
    case ui::VKEY_F4:
      if (e.IsAltDown() || e.IsAltGrDown() || e.IsControlDown())
        return false;
      show_menu = true;
      break;

    // Move to the next item if any, or show the menu on Alt+Down like Windows.
    case ui::VKEY_DOWN:
      if (e.IsAltDown())
        show_menu = true;
      else
        new_index = GetAdjacentIndex(model(), 1, selected_index_);
      break;

    // Move to the end of the list.
    case ui::VKEY_END:
    case ui::VKEY_NEXT:  // Page down.
      new_index = GetAdjacentIndex(model(), -1, model()->GetItemCount());
      break;

    // Move to the beginning of the list.
    case ui::VKEY_HOME:
    case ui::VKEY_PRIOR:  // Page up.
      new_index = GetAdjacentIndex(model(), 1, -1);
      break;

    // Move to the previous item if any.
    case ui::VKEY_UP:
      new_index = GetAdjacentIndex(model(), -1, selected_index_);
      break;

    // Click the button only when the button style mode.
    case ui::VKEY_SPACE:
      if (style_ == STYLE_ACTION) {
        // When pressing space, the click event will be raised after the key is
        // released.
        text_button_->SetState(Button::STATE_PRESSED);
      } else {
        return false;
      }
      break;

    // Click the button only when the button style mode.
    case ui::VKEY_RETURN:
      if (style_ != STYLE_ACTION)
        return false;
      OnPerformAction();
      break;

    default:
      return false;
  }

  if (show_menu) {
    UpdateFromModel();
    ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD);
  } else if (new_index != selected_index_ && new_index != kNoSelection &&
             style_ != STYLE_ACTION) {
    DCHECK(!model()->IsItemSeparatorAt(new_index));
    selected_index_ = new_index;
    OnPerformAction();
  }

  return true;
}

bool Combobox::OnKeyReleased(const ui::KeyEvent& e) {
  if (style_ != STYLE_ACTION)
    return false;  // crbug.com/127520

  if (e.key_code() == ui::VKEY_SPACE && style_ == STYLE_ACTION)
    OnPerformAction();

  return false;
}

void Combobox::OnPaint(gfx::Canvas* canvas) {
  switch (style_) {
    case STYLE_NORMAL: {
      OnPaintBackground(canvas);
      PaintText(canvas);
      OnPaintBorder(canvas);
      break;
    }
    case STYLE_ACTION: {
      PaintButtons(canvas);
      PaintText(canvas);
      break;
    }
  }
}

void Combobox::OnFocus() {
  GetInputMethod()->OnFocus();
  View::OnFocus();
  // Border renders differently when focused.
  SchedulePaint();
}

void Combobox::OnBlur() {
  GetInputMethod()->OnBlur();
  if (selector_)
    selector_->OnViewBlur();
  // Border renders differently when focused.
  SchedulePaint();
}

void Combobox::GetAccessibleState(ui::AXViewState* state) {
  state->role = ui::AX_ROLE_COMBO_BOX;
  state->name = accessible_name_;
  state->value = model_->GetItemAt(selected_index_);
  state->index = selected_index_;
  state->count = model_->GetItemCount();
}

void Combobox::OnComboboxModelChanged(ui::ComboboxModel* model) {
  DCHECK_EQ(model, model_);
  ModelChanged();
}

void Combobox::ButtonPressed(Button* sender, const ui::Event& event) {
  if (!enabled())
    return;

  RequestFocus();

  if (sender == text_button_) {
    OnPerformAction();
  } else {
    DCHECK_EQ(arrow_button_, sender);
    // TODO(hajimehoshi): Fix the problem that the arrow button blinks when
    // cliking this while the dropdown menu is opened.
    const base::TimeDelta delta = base::Time::Now() - closed_time_;
    if (delta.InMilliseconds() <= kMinimumMsBetweenButtonClicks)
      return;

    ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE;
    if (event.IsKeyEvent())
      source_type = ui::MENU_SOURCE_KEYBOARD;
    else if (event.IsGestureEvent() || event.IsTouchEvent())
      source_type = ui::MENU_SOURCE_TOUCH;
    ShowDropDownMenu(source_type);
  }
}

void Combobox::UpdateFromModel() {
  const gfx::FontList& font_list = Combobox::GetFontList();

  MenuItemView* menu = new MenuItemView(this);
  // MenuRunner owns |menu|.
  dropdown_list_menu_runner_.reset(new MenuRunner(menu));

  int num_items = model()->GetItemCount();
  int width = 0;
  bool text_item_appended = false;
  for (int i = 0; i < num_items; ++i) {
    // When STYLE_ACTION is used, the first item and the following separators
    // are not added to the dropdown menu. It is assumed that the first item is
    // always selected and rendered on the top of the action button.
    if (model()->IsItemSeparatorAt(i)) {
      if (text_item_appended || style_ != STYLE_ACTION)
        menu->AppendSeparator();
      continue;
    }

    base::string16 text = model()->GetItemAt(i);

    // Inserting the Unicode formatting characters if necessary so that the
    // text is displayed correctly in right-to-left UIs.
    base::i18n::AdjustStringForLocaleDirection(&text);

    if (style_ != STYLE_ACTION || i > 0) {
      menu->AppendMenuItem(i + kFirstMenuItemId, text, MenuItemView::NORMAL);
      text_item_appended = true;
    }

    if (style_ != STYLE_ACTION || i == selected_index_)
      width = std::max(width, gfx::GetStringWidth(text, font_list));
  }

  content_size_.SetSize(width, font_list.GetHeight());
}

void Combobox::UpdateBorder() {
  scoped_ptr<FocusableBorder> border(new FocusableBorder());
  if (style_ == STYLE_ACTION)
    border->SetInsets(8, 13, 8, 13);
  if (invalid_)
    border->SetColor(kWarningColor);
  SetBorder(border.PassAs<Border>());
}

void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
  rect->set_x(GetMirroredXForRect(*rect));
}

void Combobox::PaintText(gfx::Canvas* canvas) {
  gfx::Insets insets = GetInsets();

  gfx::ScopedCanvas scoped_canvas(canvas);
  canvas->ClipRect(GetContentsBounds());

  int x = insets.left();
  int y = insets.top();
  int text_height = height() - insets.height();
  SkColor text_color = GetNativeTheme()->GetSystemColor(
      ui::NativeTheme::kColorId_LabelEnabledColor);

  DCHECK_GE(selected_index_, 0);
  DCHECK_LT(selected_index_, model()->GetItemCount());
  if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
    selected_index_ = 0;
  base::string16 text = model()->GetItemAt(selected_index_);

  gfx::Size arrow_size = ArrowSize();
  int disclosure_arrow_offset = width() - arrow_size.width() -
      GetDisclosureArrowLeftPadding() - GetDisclosureArrowRightPadding();

  const gfx::FontList& font_list = Combobox::GetFontList();
  int text_width = gfx::GetStringWidth(text, font_list);
  if ((text_width + insets.width()) > disclosure_arrow_offset)
    text_width = disclosure_arrow_offset - insets.width();

  gfx::Rect text_bounds(x, y, text_width, text_height);
  AdjustBoundsForRTLUI(&text_bounds);
  canvas->DrawStringRect(text, font_list, text_color, text_bounds);

  int arrow_x = disclosure_arrow_offset + GetDisclosureArrowLeftPadding();
  gfx::Rect arrow_bounds(arrow_x,
                         height() / 2 - arrow_size.height() / 2,
                         arrow_size.width(),
                         arrow_size.height());
  AdjustBoundsForRTLUI(&arrow_bounds);

  // TODO(estade): hack alert! Remove this direct call into CommonTheme. For now
  // STYLE_ACTION isn't properly themed so we have to override the NativeTheme
  // behavior. See crbug.com/384071
  if (style_ == STYLE_ACTION) {
    ui::CommonThemePaintComboboxArrow(canvas->sk_canvas(), arrow_bounds);
  } else {
    ui::NativeTheme::ExtraParams ignored;
    GetNativeTheme()->Paint(canvas->sk_canvas(),
                            ui::NativeTheme::kComboboxArrow,
                            ui::NativeTheme::kNormal,
                            arrow_bounds,
                            ignored);
  }
}

void Combobox::PaintButtons(gfx::Canvas* canvas) {
  DCHECK(style_ == STYLE_ACTION);

  gfx::ScopedCanvas scoped_canvas(canvas);
  if (base::i18n::IsRTL()) {
    canvas->Translate(gfx::Vector2d(width(), 0));
    canvas->Scale(-1, 1);
  }

  bool focused = HasFocus();
  const std::vector<const gfx::ImageSkia*>& arrow_button_images =
      menu_button_images_[focused][
          arrow_button_->state() == Button::STATE_HOVERED ?
          Button::STATE_NORMAL : arrow_button_->state()];

  int text_button_hover_alpha =
      text_button_->state() == Button::STATE_PRESSED ? 0 :
      static_cast<int>(static_cast<TransparentButton*>(text_button_)->
                       GetAnimationValue() * 255);
  if (text_button_hover_alpha < 255) {
    canvas->SaveLayerAlpha(255 - text_button_hover_alpha);
    Painter* text_button_painter =
        body_button_painters_[focused][
            text_button_->state() == Button::STATE_HOVERED ?
            Button::STATE_NORMAL : text_button_->state()].get();
    Painter::PaintPainterAt(canvas, text_button_painter,
                            gfx::Rect(0, 0, text_button_->width(), height()));
    canvas->Restore();
  }
  if (0 < text_button_hover_alpha) {
    canvas->SaveLayerAlpha(text_button_hover_alpha);
    Painter* text_button_hovered_painter =
        body_button_painters_[focused][Button::STATE_HOVERED].get();
    Painter::PaintPainterAt(canvas, text_button_hovered_painter,
                            gfx::Rect(0, 0, text_button_->width(), height()));
    canvas->Restore();
  }

  int arrow_button_hover_alpha =
      arrow_button_->state() == Button::STATE_PRESSED ? 0 :
      static_cast<int>(static_cast<TransparentButton*>(arrow_button_)->
                       GetAnimationValue() * 255);
  if (arrow_button_hover_alpha < 255) {
    canvas->SaveLayerAlpha(255 - arrow_button_hover_alpha);
    PaintArrowButton(canvas, arrow_button_images, arrow_button_->x(), height());
    canvas->Restore();
  }
  if (0 < arrow_button_hover_alpha) {
    canvas->SaveLayerAlpha(arrow_button_hover_alpha);
    const std::vector<const gfx::ImageSkia*>& arrow_button_hovered_images =
        menu_button_images_[focused][Button::STATE_HOVERED];
    PaintArrowButton(canvas, arrow_button_hovered_images,
                     arrow_button_->x(), height());
    canvas->Restore();
  }
}

void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
  if (!dropdown_list_menu_runner_.get())
    UpdateFromModel();

  // Extend the menu to the width of the combobox.
  MenuItemView* menu = dropdown_list_menu_runner_->GetMenu();
  SubmenuView* submenu = menu->CreateSubmenu();
  submenu->set_minimum_preferred_width(
      size().width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));

  gfx::Rect lb = GetLocalBounds();
  gfx::Point menu_position(lb.origin());

  if (style_ == STYLE_NORMAL) {
    // Inset the menu's requested position so the border of the menu lines up
    // with the border of the combobox.
    menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft);
    menu_position.set_y(menu_position.y() + kMenuBorderWidthTop);
  }
  lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));

  View::ConvertPointToScreen(this, &menu_position);
  if (menu_position.x() < 0)
    menu_position.set_x(0);

  gfx::Rect bounds(menu_position, lb.size());

  Button::ButtonState original_state = Button::STATE_NORMAL;
  if (arrow_button_) {
    original_state = arrow_button_->state();
    arrow_button_->SetState(Button::STATE_PRESSED);
  }
  dropdown_open_ = true;
  MenuAnchorPosition anchor_position =
      style_ == STYLE_ACTION ? MENU_ANCHOR_TOPRIGHT : MENU_ANCHOR_TOPLEFT;
  if (dropdown_list_menu_runner_->RunMenuAt(GetWidget(), NULL, bounds,
                                            anchor_position, source_type,
                                            MenuRunner::COMBOBOX) ==
      MenuRunner::MENU_DELETED) {
    return;
  }
  dropdown_open_ = false;
  if (arrow_button_)
    arrow_button_->SetState(original_state);
  closed_time_ = base::Time::Now();

  // Need to explicitly clear mouse handler so that events get sent
  // properly after the menu finishes running. If we don't do this, then
  // the first click to other parts of the UI is eaten.
  SetMouseHandler(NULL);
}

void Combobox::OnPerformAction() {
  NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, false);
  SchedulePaint();

  // This combobox may be deleted by the listener.
  base::WeakPtr<Combobox> weak_ptr = weak_ptr_factory_.GetWeakPtr();
  if (listener_)
    listener_->OnPerformAction(this);

  if (weak_ptr && style_ == STYLE_ACTION)
    selected_index_ = 0;
}

int Combobox::MenuCommandToIndex(int menu_command_id) const {
  // (note that the id received is offset by kFirstMenuItemId)
  // Revert menu ID offset to map back to combobox model.
  int index = menu_command_id - kFirstMenuItemId;
  DCHECK_LT(index, model()->GetItemCount());
  return index;
}

int Combobox::GetDisclosureArrowLeftPadding() const {
  switch (style_) {
    case STYLE_NORMAL:
      return kDisclosureArrowLeftPadding;
    case STYLE_ACTION:
      return kDisclosureArrowButtonLeftPadding;
  }
  NOTREACHED();
  return 0;
}

int Combobox::GetDisclosureArrowRightPadding() const {
  switch (style_) {
    case STYLE_NORMAL:
      return kDisclosureArrowRightPadding;
    case STYLE_ACTION:
      return kDisclosureArrowButtonRightPadding;
  }
  NOTREACHED();
  return 0;
}

gfx::Size Combobox::ArrowSize() const {
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
  // TODO(estade): hack alert! This should always use GetNativeTheme(). For now
  // STYLE_ACTION isn't properly themed so we have to override the NativeTheme
  // behavior. See crbug.com/384071
  const ui::NativeTheme* native_theme_for_arrow = style_ == STYLE_ACTION ?
      ui::NativeTheme::instance() :
      GetNativeTheme();
#else
  const ui::NativeTheme* native_theme_for_arrow = GetNativeTheme();
#endif

  ui::NativeTheme::ExtraParams ignored;
  return native_theme_for_arrow->GetPartSize(ui::NativeTheme::kComboboxArrow,
                                             ui::NativeTheme::kNormal,
                                             ignored);
}

}  // namespace views