Home | History | Annotate | Download | only in combobox
      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   virtual ~TransparentButton() {}
     96 
     97   virtual 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.PassAs<Border>());
    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
    869