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