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