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 "grit/ui_resources.h"
     12 #include "ui/accessibility/ax_view_state.h"
     13 #include "ui/base/models/combobox_model.h"
     14 #include "ui/base/resource/resource_bundle.h"
     15 #include "ui/events/event.h"
     16 #include "ui/events/keycodes/keyboard_codes.h"
     17 #include "ui/gfx/animation/throb_animation.h"
     18 #include "ui/gfx/canvas.h"
     19 #include "ui/gfx/image/image.h"
     20 #include "ui/gfx/scoped_canvas.h"
     21 #include "ui/gfx/text_utils.h"
     22 #include "ui/native_theme/common_theme.h"
     23 #include "ui/native_theme/native_theme.h"
     24 #include "ui/views/background.h"
     25 #include "ui/views/color_constants.h"
     26 #include "ui/views/controls/button/custom_button.h"
     27 #include "ui/views/controls/button/label_button.h"
     28 #include "ui/views/controls/combobox/combobox_listener.h"
     29 #include "ui/views/controls/focusable_border.h"
     30 #include "ui/views/controls/menu/menu_item_view.h"
     31 #include "ui/views/controls/menu/menu_runner.h"
     32 #include "ui/views/controls/menu/menu_runner_handler.h"
     33 #include "ui/views/controls/menu/submenu_view.h"
     34 #include "ui/views/controls/prefix_selector.h"
     35 #include "ui/views/ime/input_method.h"
     36 #include "ui/views/mouse_constants.h"
     37 #include "ui/views/painter.h"
     38 #include "ui/views/widget/widget.h"
     39 
     40 namespace views {
     41 
     42 namespace {
     43 
     44 // Menu border widths
     45 const int kMenuBorderWidthLeft = 1;
     46 const int kMenuBorderWidthTop = 1;
     47 const int kMenuBorderWidthRight = 1;
     48 
     49 // Limit how small a combobox can be.
     50 const int kMinComboboxWidth = 25;
     51 
     52 // Size of the combobox arrow margins
     53 const int kDisclosureArrowLeftPadding = 7;
     54 const int kDisclosureArrowRightPadding = 7;
     55 const int kDisclosureArrowButtonLeftPadding = 11;
     56 const int kDisclosureArrowButtonRightPadding = 12;
     57 
     58 // Define the id of the first item in the menu (since it needs to be > 0)
     59 const int kFirstMenuItemId = 1000;
     60 
     61 // Used to indicate that no item is currently selected by the user.
     62 const int kNoSelection = -1;
     63 
     64 const int kBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON);
     65 const int kHoveredBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_H);
     66 const int kPressedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_P);
     67 const int kFocusedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_F);
     68 const int kFocusedHoveredBodyButtonImages[] =
     69     IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_H);
     70 const int kFocusedPressedBodyButtonImages[] =
     71     IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_P);
     72 
     73 #define MENU_IMAGE_GRID(x) { \
     74     x ## _MENU_TOP, x ## _MENU_CENTER, x ## _MENU_BOTTOM, }
     75 
     76 const int kMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON);
     77 const int kHoveredMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_H);
     78 const int kPressedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_P);
     79 const int kFocusedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F);
     80 const int kFocusedHoveredMenuButtonImages[] =
     81     MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_H);
     82 const int kFocusedPressedMenuButtonImages[] =
     83     MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_P);
     84 
     85 #undef MENU_IMAGE_GRID
     86 
     87 // The transparent button which holds a button state but is not rendered.
     88 class TransparentButton : public CustomButton {
     89  public:
     90   TransparentButton(ButtonListener* listener)
     91       : CustomButton(listener) {
     92     SetAnimationDuration(LabelButton::kHoverAnimationDurationMs);
     93   }
     94   virtual ~TransparentButton() {}
     95 
     96   virtual bool OnMousePressed(const ui::MouseEvent& mouse_event) OVERRIDE {
     97     parent()->RequestFocus();
     98     return true;
     99   }
    100 
    101   double GetAnimationValue() const {
    102     return hover_animation_->GetCurrentValue();
    103   }
    104 
    105  private:
    106   DISALLOW_COPY_AND_ASSIGN(TransparentButton);
    107 };
    108 
    109 // Returns the next or previous valid index (depending on |increment|'s value).
    110 // Skips separator or disabled indices. Returns -1 if there is no valid adjacent
    111 // index.
    112 int GetAdjacentIndex(ui::ComboboxModel* model, int increment, int index) {
    113   DCHECK(increment == -1 || increment == 1);
    114 
    115   index += increment;
    116   while (index >= 0 && index < model->GetItemCount()) {
    117     if (!model->IsItemSeparatorAt(index) || !model->IsItemEnabledAt(index))
    118       return index;
    119     index += increment;
    120   }
    121   return kNoSelection;
    122 }
    123 
    124 // Returns the image resource ids of an array for the body button.
    125 //
    126 // TODO(hajimehoshi): This function should return the images for the 'disabled'
    127 // status. (crbug/270052)
    128 const int* GetBodyButtonImageIds(bool focused,
    129                                  Button::ButtonState state,
    130                                  size_t* num) {
    131   DCHECK(num);
    132   *num = 9;
    133   switch (state) {
    134     case Button::STATE_DISABLED:
    135       return focused ? kFocusedBodyButtonImages : kBodyButtonImages;
    136     case Button::STATE_NORMAL:
    137       return focused ? kFocusedBodyButtonImages : kBodyButtonImages;
    138     case Button::STATE_HOVERED:
    139       return focused ?
    140           kFocusedHoveredBodyButtonImages : kHoveredBodyButtonImages;
    141     case Button::STATE_PRESSED:
    142       return focused ?
    143           kFocusedPressedBodyButtonImages : kPressedBodyButtonImages;
    144     default:
    145       NOTREACHED();
    146   }
    147   return NULL;
    148 }
    149 
    150 // Returns the image resource ids of an array for the menu button.
    151 const int* GetMenuButtonImageIds(bool focused,
    152                                  Button::ButtonState state,
    153                                  size_t* num) {
    154   DCHECK(num);
    155   *num = 3;
    156   switch (state) {
    157     case Button::STATE_DISABLED:
    158       return focused ? kFocusedMenuButtonImages : kMenuButtonImages;
    159     case Button::STATE_NORMAL:
    160       return focused ? kFocusedMenuButtonImages : kMenuButtonImages;
    161     case Button::STATE_HOVERED:
    162       return focused ?
    163           kFocusedHoveredMenuButtonImages : kHoveredMenuButtonImages;
    164     case Button::STATE_PRESSED:
    165       return focused ?
    166           kFocusedPressedMenuButtonImages : kPressedMenuButtonImages;
    167     default:
    168       NOTREACHED();
    169   }
    170   return NULL;
    171 }
    172 
    173 // Returns the images for the menu buttons.
    174 std::vector<const gfx::ImageSkia*> GetMenuButtonImages(
    175     bool focused,
    176     Button::ButtonState state) {
    177   const int* ids;
    178   size_t num_ids;
    179   ids = GetMenuButtonImageIds(focused, state, &num_ids);
    180   std::vector<const gfx::ImageSkia*> images;
    181   images.reserve(num_ids);
    182   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    183   for (size_t i = 0; i < num_ids; i++)
    184     images.push_back(rb.GetImageSkiaNamed(ids[i]));
    185   return images;
    186 }
    187 
    188 // Paints three images in a column at the given location. The center image is
    189 // stretched so as to fit the given height.
    190 void PaintImagesVertically(gfx::Canvas* canvas,
    191                            const gfx::ImageSkia& top_image,
    192                            const gfx::ImageSkia& center_image,
    193                            const gfx::ImageSkia& bottom_image,
    194                            int x, int y, int width, int height) {
    195   canvas->DrawImageInt(top_image,
    196                        0, 0, top_image.width(), top_image.height(),
    197                        x, y, width, top_image.height(), false);
    198   y += top_image.height();
    199   int center_height = height - top_image.height() - bottom_image.height();
    200   canvas->DrawImageInt(center_image,
    201                        0, 0, center_image.width(), center_image.height(),
    202                        x, y, width, center_height, false);
    203   y += center_height;
    204   canvas->DrawImageInt(bottom_image,
    205                        0, 0, bottom_image.width(), bottom_image.height(),
    206                        x, y, width, bottom_image.height(), false);
    207 }
    208 
    209 // Paints the arrow button.
    210 void PaintArrowButton(
    211     gfx::Canvas* canvas,
    212     const std::vector<const gfx::ImageSkia*>& arrow_button_images,
    213     int x, int height) {
    214   PaintImagesVertically(canvas,
    215                         *arrow_button_images[0],
    216                         *arrow_button_images[1],
    217                         *arrow_button_images[2],
    218                         x, 0, arrow_button_images[0]->width(), height);
    219 }
    220 
    221 }  // namespace
    222 
    223 // static
    224 const char Combobox::kViewClassName[] = "views/Combobox";
    225 
    226 ////////////////////////////////////////////////////////////////////////////////
    227 // Combobox, public:
    228 
    229 Combobox::Combobox(ui::ComboboxModel* model)
    230     : model_(model),
    231       style_(STYLE_NORMAL),
    232       listener_(NULL),
    233       selected_index_(model_->GetDefaultIndex()),
    234       invalid_(false),
    235       dropdown_open_(false),
    236       text_button_(new TransparentButton(this)),
    237       arrow_button_(new TransparentButton(this)),
    238       weak_ptr_factory_(this) {
    239   model_->AddObserver(this);
    240   UpdateFromModel();
    241   SetFocusable(true);
    242   UpdateBorder();
    243 
    244   // Initialize the button images.
    245   Button::ButtonState button_states[] = {
    246     Button::STATE_DISABLED,
    247     Button::STATE_NORMAL,
    248     Button::STATE_HOVERED,
    249     Button::STATE_PRESSED,
    250   };
    251   for (int i = 0; i < 2; i++) {
    252     for (size_t state_index = 0; state_index < arraysize(button_states);
    253          state_index++) {
    254       Button::ButtonState state = button_states[state_index];
    255       size_t num;
    256       bool focused = !!i;
    257       const int* ids = GetBodyButtonImageIds(focused, state, &num);
    258       body_button_painters_[focused][state].reset(
    259           Painter::CreateImageGridPainter(ids));
    260       menu_button_images_[focused][state] = GetMenuButtonImages(focused, state);
    261     }
    262   }
    263 
    264   text_button_->SetVisible(true);
    265   arrow_button_->SetVisible(true);
    266   text_button_->SetFocusable(false);
    267   arrow_button_->SetFocusable(false);
    268   AddChildView(text_button_);
    269   AddChildView(arrow_button_);
    270 }
    271 
    272 Combobox::~Combobox() {
    273   model_->RemoveObserver(this);
    274 }
    275 
    276 // static
    277 const gfx::FontList& Combobox::GetFontList() {
    278   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    279   return rb.GetFontList(ui::ResourceBundle::BaseFont);
    280 }
    281 
    282 void Combobox::SetStyle(Style style) {
    283   if (style_ == style)
    284     return;
    285 
    286   style_ = style;
    287   if (style_ == STYLE_ACTION)
    288     selected_index_ = 0;
    289 
    290   UpdateBorder();
    291   UpdateFromModel();
    292   PreferredSizeChanged();
    293 }
    294 
    295 void Combobox::ModelChanged() {
    296   selected_index_ = std::min(0, model_->GetItemCount());
    297   UpdateFromModel();
    298   PreferredSizeChanged();
    299 }
    300 
    301 void Combobox::SetSelectedIndex(int index) {
    302   if (style_ == STYLE_ACTION)
    303     return;
    304 
    305   selected_index_ = index;
    306   SchedulePaint();
    307 }
    308 
    309 bool Combobox::SelectValue(const base::string16& value) {
    310   if (style_ == STYLE_ACTION)
    311     return false;
    312 
    313   for (int i = 0; i < model()->GetItemCount(); ++i) {
    314     if (value == model()->GetItemAt(i)) {
    315       SetSelectedIndex(i);
    316       return true;
    317     }
    318   }
    319   return false;
    320 }
    321 
    322 void Combobox::SetAccessibleName(const base::string16& name) {
    323   accessible_name_ = name;
    324 }
    325 
    326 void Combobox::SetInvalid(bool invalid) {
    327   if (invalid == invalid_)
    328     return;
    329 
    330   invalid_ = invalid;
    331 
    332   UpdateBorder();
    333   SchedulePaint();
    334 }
    335 
    336 ui::TextInputClient* Combobox::GetTextInputClient() {
    337   if (!selector_)
    338     selector_.reset(new PrefixSelector(this));
    339   return selector_.get();
    340 }
    341 
    342 void Combobox::Layout() {
    343   PrefixDelegate::Layout();
    344 
    345   gfx::Insets insets = GetInsets();
    346   int text_button_width = 0;
    347   int arrow_button_width = 0;
    348 
    349   switch (style_) {
    350     case STYLE_NORMAL: {
    351       arrow_button_width = width();
    352       break;
    353     }
    354     case STYLE_ACTION: {
    355       arrow_button_width = GetDisclosureArrowLeftPadding() +
    356           ArrowSize().width() +
    357           GetDisclosureArrowRightPadding();
    358       text_button_width = width() - arrow_button_width;
    359       break;
    360     }
    361   }
    362 
    363   int arrow_button_x = std::max(0, text_button_width);
    364   text_button_->SetBounds(0, 0, std::max(0, text_button_width), height());
    365   arrow_button_->SetBounds(arrow_button_x, 0, arrow_button_width, height());
    366 }
    367 
    368 bool Combobox::IsItemChecked(int id) const {
    369   return false;
    370 }
    371 
    372 bool Combobox::IsCommandEnabled(int id) const {
    373   return model()->IsItemEnabledAt(MenuCommandToIndex(id));
    374 }
    375 
    376 void Combobox::ExecuteCommand(int id) {
    377   selected_index_ = MenuCommandToIndex(id);
    378   OnPerformAction();
    379 }
    380 
    381 bool Combobox::GetAccelerator(int id, ui::Accelerator* accel) const {
    382   return false;
    383 }
    384 
    385 int Combobox::GetRowCount() {
    386   return model()->GetItemCount();
    387 }
    388 
    389 int Combobox::GetSelectedRow() {
    390   return selected_index_;
    391 }
    392 
    393 void Combobox::SetSelectedRow(int row) {
    394   int prev_index = selected_index_;
    395   SetSelectedIndex(row);
    396   if (selected_index_ != prev_index)
    397     OnPerformAction();
    398 }
    399 
    400 base::string16 Combobox::GetTextForRow(int row) {
    401   return model()->IsItemSeparatorAt(row) ? base::string16() :
    402                                            model()->GetItemAt(row);
    403 }
    404 
    405 ////////////////////////////////////////////////////////////////////////////////
    406 // Combobox, View overrides:
    407 
    408 gfx::Size Combobox::GetPreferredSize() const {
    409   // The preferred size will drive the local bounds which in turn is used to set
    410   // the minimum width for the dropdown list.
    411   gfx::Insets insets = GetInsets();
    412   int total_width = std::max(kMinComboboxWidth, content_size_.width()) +
    413       insets.width() + GetDisclosureArrowLeftPadding() +
    414       ArrowSize().width() + GetDisclosureArrowRightPadding();
    415   return gfx::Size(total_width, content_size_.height() + insets.height());
    416 }
    417 
    418 const char* Combobox::GetClassName() const {
    419   return kViewClassName;
    420 }
    421 
    422 bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) {
    423   // Escape should close the drop down list when it is active, not host UI.
    424   if (e.key_code() != ui::VKEY_ESCAPE ||
    425       e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) {
    426     return false;
    427   }
    428   return dropdown_open_;
    429 }
    430 
    431 bool Combobox::OnKeyPressed(const ui::KeyEvent& e) {
    432   // TODO(oshima): handle IME.
    433   DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED);
    434 
    435   DCHECK_GE(selected_index_, 0);
    436   DCHECK_LT(selected_index_, model()->GetItemCount());
    437   if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
    438     selected_index_ = 0;
    439 
    440   bool show_menu = false;
    441   int new_index = kNoSelection;
    442   switch (e.key_code()) {
    443     // Show the menu on F4 without modifiers.
    444     case ui::VKEY_F4:
    445       if (e.IsAltDown() || e.IsAltGrDown() || e.IsControlDown())
    446         return false;
    447       show_menu = true;
    448       break;
    449 
    450     // Move to the next item if any, or show the menu on Alt+Down like Windows.
    451     case ui::VKEY_DOWN:
    452       if (e.IsAltDown())
    453         show_menu = true;
    454       else
    455         new_index = GetAdjacentIndex(model(), 1, selected_index_);
    456       break;
    457 
    458     // Move to the end of the list.
    459     case ui::VKEY_END:
    460     case ui::VKEY_NEXT:  // Page down.
    461       new_index = GetAdjacentIndex(model(), -1, model()->GetItemCount());
    462       break;
    463 
    464     // Move to the beginning of the list.
    465     case ui::VKEY_HOME:
    466     case ui::VKEY_PRIOR:  // Page up.
    467       new_index = GetAdjacentIndex(model(), 1, -1);
    468       break;
    469 
    470     // Move to the previous item if any.
    471     case ui::VKEY_UP:
    472       new_index = GetAdjacentIndex(model(), -1, selected_index_);
    473       break;
    474 
    475     // Click the button only when the button style mode.
    476     case ui::VKEY_SPACE:
    477       if (style_ == STYLE_ACTION) {
    478         // When pressing space, the click event will be raised after the key is
    479         // released.
    480         text_button_->SetState(Button::STATE_PRESSED);
    481       } else {
    482         return false;
    483       }
    484       break;
    485 
    486     // Click the button only when the button style mode.
    487     case ui::VKEY_RETURN:
    488       if (style_ != STYLE_ACTION)
    489         return false;
    490       OnPerformAction();
    491       break;
    492 
    493     default:
    494       return false;
    495   }
    496 
    497   if (show_menu) {
    498     UpdateFromModel();
    499     ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD);
    500   } else if (new_index != selected_index_ && new_index != kNoSelection &&
    501              style_ != STYLE_ACTION) {
    502     DCHECK(!model()->IsItemSeparatorAt(new_index));
    503     selected_index_ = new_index;
    504     OnPerformAction();
    505   }
    506 
    507   return true;
    508 }
    509 
    510 bool Combobox::OnKeyReleased(const ui::KeyEvent& e) {
    511   if (style_ != STYLE_ACTION)
    512     return false;  // crbug.com/127520
    513 
    514   if (e.key_code() == ui::VKEY_SPACE && style_ == STYLE_ACTION)
    515     OnPerformAction();
    516 
    517   return false;
    518 }
    519 
    520 void Combobox::OnPaint(gfx::Canvas* canvas) {
    521   switch (style_) {
    522     case STYLE_NORMAL: {
    523       OnPaintBackground(canvas);
    524       PaintText(canvas);
    525       OnPaintBorder(canvas);
    526       break;
    527     }
    528     case STYLE_ACTION: {
    529       PaintButtons(canvas);
    530       PaintText(canvas);
    531       break;
    532     }
    533   }
    534 }
    535 
    536 void Combobox::OnFocus() {
    537   GetInputMethod()->OnFocus();
    538   View::OnFocus();
    539   // Border renders differently when focused.
    540   SchedulePaint();
    541 }
    542 
    543 void Combobox::OnBlur() {
    544   GetInputMethod()->OnBlur();
    545   if (selector_)
    546     selector_->OnViewBlur();
    547   // Border renders differently when focused.
    548   SchedulePaint();
    549 }
    550 
    551 void Combobox::GetAccessibleState(ui::AXViewState* state) {
    552   state->role = ui::AX_ROLE_COMBO_BOX;
    553   state->name = accessible_name_;
    554   state->value = model_->GetItemAt(selected_index_);
    555   state->index = selected_index_;
    556   state->count = model_->GetItemCount();
    557 }
    558 
    559 void Combobox::OnComboboxModelChanged(ui::ComboboxModel* model) {
    560   DCHECK_EQ(model, model_);
    561   ModelChanged();
    562 }
    563 
    564 void Combobox::ButtonPressed(Button* sender, const ui::Event& event) {
    565   if (!enabled())
    566     return;
    567 
    568   RequestFocus();
    569 
    570   if (sender == text_button_) {
    571     OnPerformAction();
    572   } else {
    573     DCHECK_EQ(arrow_button_, sender);
    574     // TODO(hajimehoshi): Fix the problem that the arrow button blinks when
    575     // cliking this while the dropdown menu is opened.
    576     const base::TimeDelta delta = base::Time::Now() - closed_time_;
    577     if (delta.InMilliseconds() <= kMinimumMsBetweenButtonClicks)
    578       return;
    579 
    580     ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE;
    581     if (event.IsKeyEvent())
    582       source_type = ui::MENU_SOURCE_KEYBOARD;
    583     else if (event.IsGestureEvent() || event.IsTouchEvent())
    584       source_type = ui::MENU_SOURCE_TOUCH;
    585     ShowDropDownMenu(source_type);
    586   }
    587 }
    588 
    589 void Combobox::UpdateFromModel() {
    590   const gfx::FontList& font_list = Combobox::GetFontList();
    591 
    592   MenuItemView* menu = new MenuItemView(this);
    593   // MenuRunner owns |menu|.
    594   dropdown_list_menu_runner_.reset(new MenuRunner(menu));
    595 
    596   int num_items = model()->GetItemCount();
    597   int width = 0;
    598   bool text_item_appended = false;
    599   for (int i = 0; i < num_items; ++i) {
    600     // When STYLE_ACTION is used, the first item and the following separators
    601     // are not added to the dropdown menu. It is assumed that the first item is
    602     // always selected and rendered on the top of the action button.
    603     if (model()->IsItemSeparatorAt(i)) {
    604       if (text_item_appended || style_ != STYLE_ACTION)
    605         menu->AppendSeparator();
    606       continue;
    607     }
    608 
    609     base::string16 text = model()->GetItemAt(i);
    610 
    611     // Inserting the Unicode formatting characters if necessary so that the
    612     // text is displayed correctly in right-to-left UIs.
    613     base::i18n::AdjustStringForLocaleDirection(&text);
    614 
    615     if (style_ != STYLE_ACTION || i > 0) {
    616       menu->AppendMenuItem(i + kFirstMenuItemId, text, MenuItemView::NORMAL);
    617       text_item_appended = true;
    618     }
    619 
    620     if (style_ != STYLE_ACTION || i == selected_index_)
    621       width = std::max(width, gfx::GetStringWidth(text, font_list));
    622   }
    623 
    624   content_size_.SetSize(width, font_list.GetHeight());
    625 }
    626 
    627 void Combobox::UpdateBorder() {
    628   scoped_ptr<FocusableBorder> border(new FocusableBorder());
    629   if (style_ == STYLE_ACTION)
    630     border->SetInsets(8, 13, 8, 13);
    631   if (invalid_)
    632     border->SetColor(kWarningColor);
    633   SetBorder(border.PassAs<Border>());
    634 }
    635 
    636 void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const {
    637   rect->set_x(GetMirroredXForRect(*rect));
    638 }
    639 
    640 void Combobox::PaintText(gfx::Canvas* canvas) {
    641   gfx::Insets insets = GetInsets();
    642 
    643   gfx::ScopedCanvas scoped_canvas(canvas);
    644   canvas->ClipRect(GetContentsBounds());
    645 
    646   int x = insets.left();
    647   int y = insets.top();
    648   int text_height = height() - insets.height();
    649   SkColor text_color = GetNativeTheme()->GetSystemColor(
    650       ui::NativeTheme::kColorId_LabelEnabledColor);
    651 
    652   DCHECK_GE(selected_index_, 0);
    653   DCHECK_LT(selected_index_, model()->GetItemCount());
    654   if (selected_index_ < 0 || selected_index_ > model()->GetItemCount())
    655     selected_index_ = 0;
    656   base::string16 text = model()->GetItemAt(selected_index_);
    657 
    658   gfx::Size arrow_size = ArrowSize();
    659   int disclosure_arrow_offset = width() - arrow_size.width() -
    660       GetDisclosureArrowLeftPadding() - GetDisclosureArrowRightPadding();
    661 
    662   const gfx::FontList& font_list = Combobox::GetFontList();
    663   int text_width = gfx::GetStringWidth(text, font_list);
    664   if ((text_width + insets.width()) > disclosure_arrow_offset)
    665     text_width = disclosure_arrow_offset - insets.width();
    666 
    667   gfx::Rect text_bounds(x, y, text_width, text_height);
    668   AdjustBoundsForRTLUI(&text_bounds);
    669   canvas->DrawStringRect(text, font_list, text_color, text_bounds);
    670 
    671   int arrow_x = disclosure_arrow_offset + GetDisclosureArrowLeftPadding();
    672   gfx::Rect arrow_bounds(arrow_x,
    673                          height() / 2 - arrow_size.height() / 2,
    674                          arrow_size.width(),
    675                          arrow_size.height());
    676   AdjustBoundsForRTLUI(&arrow_bounds);
    677 
    678   // TODO(estade): hack alert! Remove this direct call into CommonTheme. For now
    679   // STYLE_ACTION isn't properly themed so we have to override the NativeTheme
    680   // behavior. See crbug.com/384071
    681   if (style_ == STYLE_ACTION) {
    682     ui::CommonThemePaintComboboxArrow(canvas->sk_canvas(), arrow_bounds);
    683   } else {
    684     ui::NativeTheme::ExtraParams ignored;
    685     GetNativeTheme()->Paint(canvas->sk_canvas(),
    686                             ui::NativeTheme::kComboboxArrow,
    687                             ui::NativeTheme::kNormal,
    688                             arrow_bounds,
    689                             ignored);
    690   }
    691 }
    692 
    693 void Combobox::PaintButtons(gfx::Canvas* canvas) {
    694   DCHECK(style_ == STYLE_ACTION);
    695 
    696   gfx::ScopedCanvas scoped_canvas(canvas);
    697   if (base::i18n::IsRTL()) {
    698     canvas->Translate(gfx::Vector2d(width(), 0));
    699     canvas->Scale(-1, 1);
    700   }
    701 
    702   bool focused = HasFocus();
    703   const std::vector<const gfx::ImageSkia*>& arrow_button_images =
    704       menu_button_images_[focused][
    705           arrow_button_->state() == Button::STATE_HOVERED ?
    706           Button::STATE_NORMAL : arrow_button_->state()];
    707 
    708   int text_button_hover_alpha =
    709       text_button_->state() == Button::STATE_PRESSED ? 0 :
    710       static_cast<int>(static_cast<TransparentButton*>(text_button_)->
    711                        GetAnimationValue() * 255);
    712   if (text_button_hover_alpha < 255) {
    713     canvas->SaveLayerAlpha(255 - text_button_hover_alpha);
    714     Painter* text_button_painter =
    715         body_button_painters_[focused][
    716             text_button_->state() == Button::STATE_HOVERED ?
    717             Button::STATE_NORMAL : text_button_->state()].get();
    718     Painter::PaintPainterAt(canvas, text_button_painter,
    719                             gfx::Rect(0, 0, text_button_->width(), height()));
    720     canvas->Restore();
    721   }
    722   if (0 < text_button_hover_alpha) {
    723     canvas->SaveLayerAlpha(text_button_hover_alpha);
    724     Painter* text_button_hovered_painter =
    725         body_button_painters_[focused][Button::STATE_HOVERED].get();
    726     Painter::PaintPainterAt(canvas, text_button_hovered_painter,
    727                             gfx::Rect(0, 0, text_button_->width(), height()));
    728     canvas->Restore();
    729   }
    730 
    731   int arrow_button_hover_alpha =
    732       arrow_button_->state() == Button::STATE_PRESSED ? 0 :
    733       static_cast<int>(static_cast<TransparentButton*>(arrow_button_)->
    734                        GetAnimationValue() * 255);
    735   if (arrow_button_hover_alpha < 255) {
    736     canvas->SaveLayerAlpha(255 - arrow_button_hover_alpha);
    737     PaintArrowButton(canvas, arrow_button_images, arrow_button_->x(), height());
    738     canvas->Restore();
    739   }
    740   if (0 < arrow_button_hover_alpha) {
    741     canvas->SaveLayerAlpha(arrow_button_hover_alpha);
    742     const std::vector<const gfx::ImageSkia*>& arrow_button_hovered_images =
    743         menu_button_images_[focused][Button::STATE_HOVERED];
    744     PaintArrowButton(canvas, arrow_button_hovered_images,
    745                      arrow_button_->x(), height());
    746     canvas->Restore();
    747   }
    748 }
    749 
    750 void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) {
    751   if (!dropdown_list_menu_runner_.get())
    752     UpdateFromModel();
    753 
    754   // Extend the menu to the width of the combobox.
    755   MenuItemView* menu = dropdown_list_menu_runner_->GetMenu();
    756   SubmenuView* submenu = menu->CreateSubmenu();
    757   submenu->set_minimum_preferred_width(
    758       size().width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
    759 
    760   gfx::Rect lb = GetLocalBounds();
    761   gfx::Point menu_position(lb.origin());
    762 
    763   if (style_ == STYLE_NORMAL) {
    764     // Inset the menu's requested position so the border of the menu lines up
    765     // with the border of the combobox.
    766     menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft);
    767     menu_position.set_y(menu_position.y() + kMenuBorderWidthTop);
    768   }
    769   lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight));
    770 
    771   View::ConvertPointToScreen(this, &menu_position);
    772   if (menu_position.x() < 0)
    773     menu_position.set_x(0);
    774 
    775   gfx::Rect bounds(menu_position, lb.size());
    776 
    777   Button::ButtonState original_state = Button::STATE_NORMAL;
    778   if (arrow_button_) {
    779     original_state = arrow_button_->state();
    780     arrow_button_->SetState(Button::STATE_PRESSED);
    781   }
    782   dropdown_open_ = true;
    783   MenuAnchorPosition anchor_position =
    784       style_ == STYLE_ACTION ? MENU_ANCHOR_TOPRIGHT : MENU_ANCHOR_TOPLEFT;
    785   if (dropdown_list_menu_runner_->RunMenuAt(GetWidget(), NULL, bounds,
    786                                             anchor_position, source_type,
    787                                             MenuRunner::COMBOBOX) ==
    788       MenuRunner::MENU_DELETED) {
    789     return;
    790   }
    791   dropdown_open_ = false;
    792   if (arrow_button_)
    793     arrow_button_->SetState(original_state);
    794   closed_time_ = base::Time::Now();
    795 
    796   // Need to explicitly clear mouse handler so that events get sent
    797   // properly after the menu finishes running. If we don't do this, then
    798   // the first click to other parts of the UI is eaten.
    799   SetMouseHandler(NULL);
    800 }
    801 
    802 void Combobox::OnPerformAction() {
    803   NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, false);
    804   SchedulePaint();
    805 
    806   // This combobox may be deleted by the listener.
    807   base::WeakPtr<Combobox> weak_ptr = weak_ptr_factory_.GetWeakPtr();
    808   if (listener_)
    809     listener_->OnPerformAction(this);
    810 
    811   if (weak_ptr && style_ == STYLE_ACTION)
    812     selected_index_ = 0;
    813 }
    814 
    815 int Combobox::MenuCommandToIndex(int menu_command_id) const {
    816   // (note that the id received is offset by kFirstMenuItemId)
    817   // Revert menu ID offset to map back to combobox model.
    818   int index = menu_command_id - kFirstMenuItemId;
    819   DCHECK_LT(index, model()->GetItemCount());
    820   return index;
    821 }
    822 
    823 int Combobox::GetDisclosureArrowLeftPadding() const {
    824   switch (style_) {
    825     case STYLE_NORMAL:
    826       return kDisclosureArrowLeftPadding;
    827     case STYLE_ACTION:
    828       return kDisclosureArrowButtonLeftPadding;
    829   }
    830   NOTREACHED();
    831   return 0;
    832 }
    833 
    834 int Combobox::GetDisclosureArrowRightPadding() const {
    835   switch (style_) {
    836     case STYLE_NORMAL:
    837       return kDisclosureArrowRightPadding;
    838     case STYLE_ACTION:
    839       return kDisclosureArrowButtonRightPadding;
    840   }
    841   NOTREACHED();
    842   return 0;
    843 }
    844 
    845 gfx::Size Combobox::ArrowSize() const {
    846 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
    847   // TODO(estade): hack alert! This should always use GetNativeTheme(). For now
    848   // STYLE_ACTION isn't properly themed so we have to override the NativeTheme
    849   // behavior. See crbug.com/384071
    850   const ui::NativeTheme* native_theme_for_arrow = style_ == STYLE_ACTION ?
    851       ui::NativeTheme::instance() :
    852       GetNativeTheme();
    853 #else
    854   const ui::NativeTheme* native_theme_for_arrow = GetNativeTheme();
    855 #endif
    856 
    857   ui::NativeTheme::ExtraParams ignored;
    858   return native_theme_for_arrow->GetPartSize(ui::NativeTheme::kComboboxArrow,
    859                                              ui::NativeTheme::kNormal,
    860                                              ignored);
    861 }
    862 
    863 }  // namespace views
    864