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