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 41namespace views { 42 43namespace { 44 45// Menu border widths 46const int kMenuBorderWidthLeft = 1; 47const int kMenuBorderWidthTop = 1; 48const int kMenuBorderWidthRight = 1; 49 50// Limit how small a combobox can be. 51const int kMinComboboxWidth = 25; 52 53// Size of the combobox arrow margins 54const int kDisclosureArrowLeftPadding = 7; 55const int kDisclosureArrowRightPadding = 7; 56const int kDisclosureArrowButtonLeftPadding = 11; 57const int kDisclosureArrowButtonRightPadding = 12; 58 59// Define the id of the first item in the menu (since it needs to be > 0) 60const int kFirstMenuItemId = 1000; 61 62// Used to indicate that no item is currently selected by the user. 63const int kNoSelection = -1; 64 65const int kBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON); 66const int kHoveredBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_H); 67const int kPressedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_P); 68const int kFocusedBodyButtonImages[] = IMAGE_GRID(IDR_COMBOBOX_BUTTON_F); 69const int kFocusedHoveredBodyButtonImages[] = 70 IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_H); 71const 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 77const int kMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON); 78const int kHoveredMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_H); 79const int kPressedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_P); 80const int kFocusedMenuButtonImages[] = MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F); 81const int kFocusedHoveredMenuButtonImages[] = 82 MENU_IMAGE_GRID(IDR_COMBOBOX_BUTTON_F_H); 83const 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. 89class 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. 113int 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) 129const 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. 152const 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. 175std::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. 191void 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. 211void 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 225const char Combobox::kViewClassName[] = "views/Combobox"; 226 227//////////////////////////////////////////////////////////////////////////////// 228// Combobox, public: 229 230Combobox::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 274Combobox::~Combobox() { 275 model_->RemoveObserver(this); 276} 277 278// static 279const gfx::FontList& Combobox::GetFontList() { 280 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 281 return rb.GetFontList(ui::ResourceBundle::BaseFont); 282} 283 284void 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 297void Combobox::ModelChanged() { 298 selected_index_ = std::min(0, model_->GetItemCount()); 299 UpdateFromModel(); 300 PreferredSizeChanged(); 301} 302 303void Combobox::SetSelectedIndex(int index) { 304 if (style_ == STYLE_ACTION) 305 return; 306 307 selected_index_ = index; 308 SchedulePaint(); 309} 310 311bool 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 324void Combobox::SetAccessibleName(const base::string16& name) { 325 accessible_name_ = name; 326} 327 328void Combobox::SetInvalid(bool invalid) { 329 if (invalid == invalid_) 330 return; 331 332 invalid_ = invalid; 333 334 UpdateBorder(); 335 SchedulePaint(); 336} 337 338ui::TextInputClient* Combobox::GetTextInputClient() { 339 if (!selector_) 340 selector_.reset(new PrefixSelector(this)); 341 return selector_.get(); 342} 343 344void 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 370bool Combobox::IsItemChecked(int id) const { 371 return false; 372} 373 374bool Combobox::IsCommandEnabled(int id) const { 375 return model()->IsItemEnabledAt(MenuCommandToIndex(id)); 376} 377 378void Combobox::ExecuteCommand(int id) { 379 selected_index_ = MenuCommandToIndex(id); 380 OnPerformAction(); 381} 382 383bool Combobox::GetAccelerator(int id, ui::Accelerator* accel) const { 384 return false; 385} 386 387int Combobox::GetRowCount() { 388 return model()->GetItemCount(); 389} 390 391int Combobox::GetSelectedRow() { 392 return selected_index_; 393} 394 395void Combobox::SetSelectedRow(int row) { 396 int prev_index = selected_index_; 397 SetSelectedIndex(row); 398 if (selected_index_ != prev_index) 399 OnPerformAction(); 400} 401 402base::string16 Combobox::GetTextForRow(int row) { 403 return model()->IsItemSeparatorAt(row) ? base::string16() : 404 model()->GetItemAt(row); 405} 406 407//////////////////////////////////////////////////////////////////////////////// 408// Combobox, View overrides: 409 410gfx::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 424const char* Combobox::GetClassName() const { 425 return kViewClassName; 426} 427 428bool 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 437bool 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 516bool 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 526void 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 542void Combobox::OnFocus() { 543 GetInputMethod()->OnFocus(); 544 View::OnFocus(); 545 // Border renders differently when focused. 546 SchedulePaint(); 547} 548 549void Combobox::OnBlur() { 550 GetInputMethod()->OnBlur(); 551 if (selector_) 552 selector_->OnViewBlur(); 553 // Border renders differently when focused. 554 SchedulePaint(); 555} 556 557void 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 565void Combobox::OnComboboxModelChanged(ui::ComboboxModel* model) { 566 DCHECK_EQ(model, model_); 567 ModelChanged(); 568} 569 570void 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 595void 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 633void 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 642void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const { 643 rect->set_x(GetMirroredXForRect(*rect)); 644} 645 646void 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 700void 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 757void 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 807void 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 820int 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 828int 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 839int 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 850gfx::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