| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ui/views/controls/combobox/combobox.h" | 5 #include "ui/views/controls/combobox/combobox.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | |
| 8 #include "base/logging.h" | 7 #include "base/logging.h" |
| 9 #include "base/strings/utf_string_conversions.h" | |
| 10 #include "ui/accessibility/ax_view_state.h" | 8 #include "ui/accessibility/ax_view_state.h" |
| 11 #include "ui/base/ime/input_method.h" | 9 #include "ui/base/ime/input_method.h" |
| 12 #include "ui/base/models/combobox_model.h" | 10 #include "ui/base/models/combobox_model.h" |
| 11 #include "ui/base/models/combobox_model_observer.h" |
| 13 #include "ui/base/resource/resource_bundle.h" | 12 #include "ui/base/resource/resource_bundle.h" |
| 14 #include "ui/events/event.h" | 13 #include "ui/events/event.h" |
| 15 #include "ui/events/keycodes/keyboard_codes.h" | |
| 16 #include "ui/gfx/animation/throb_animation.h" | 14 #include "ui/gfx/animation/throb_animation.h" |
| 17 #include "ui/gfx/canvas.h" | 15 #include "ui/gfx/canvas.h" |
| 18 #include "ui/gfx/image/image.h" | |
| 19 #include "ui/gfx/scoped_canvas.h" | 16 #include "ui/gfx/scoped_canvas.h" |
| 20 #include "ui/gfx/text_utils.h" | 17 #include "ui/gfx/text_utils.h" |
| 21 #include "ui/native_theme/common_theme.h" | 18 #include "ui/native_theme/common_theme.h" |
| 22 #include "ui/native_theme/native_theme.h" | 19 #include "ui/native_theme/native_theme.h" |
| 23 #include "ui/resources/grit/ui_resources.h" | 20 #include "ui/resources/grit/ui_resources.h" |
| 24 #include "ui/views/background.h" | |
| 25 #include "ui/views/color_constants.h" | 21 #include "ui/views/color_constants.h" |
| 26 #include "ui/views/controls/button/custom_button.h" | 22 #include "ui/views/controls/button/custom_button.h" |
| 27 #include "ui/views/controls/button/label_button.h" | 23 #include "ui/views/controls/button/label_button.h" |
| 28 #include "ui/views/controls/combobox/combobox_listener.h" | 24 #include "ui/views/controls/combobox/combobox_listener.h" |
| 29 #include "ui/views/controls/focusable_border.h" | 25 #include "ui/views/controls/focusable_border.h" |
| 30 #include "ui/views/controls/menu/menu_item_view.h" | 26 #include "ui/views/controls/menu/menu_config.h" |
| 31 #include "ui/views/controls/menu/menu_runner.h" | 27 #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" | 28 #include "ui/views/controls/prefix_selector.h" |
| 35 #include "ui/views/controls/textfield/textfield.h" | 29 #include "ui/views/controls/textfield/textfield.h" |
| 36 #include "ui/views/mouse_constants.h" | 30 #include "ui/views/mouse_constants.h" |
| 37 #include "ui/views/painter.h" | 31 #include "ui/views/painter.h" |
| 38 #include "ui/views/resources/grit/views_resources.h" | 32 #include "ui/views/resources/grit/views_resources.h" |
| 39 #include "ui/views/widget/widget.h" | 33 #include "ui/views/widget/widget.h" |
| 40 | 34 |
| 41 namespace views { | 35 namespace views { |
| 42 | 36 |
| 43 namespace { | 37 namespace { |
| (...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 217 *arrow_button_images[1], | 211 *arrow_button_images[1], |
| 218 *arrow_button_images[2], | 212 *arrow_button_images[2], |
| 219 x, 0, arrow_button_images[0]->width(), height); | 213 x, 0, arrow_button_images[0]->width(), height); |
| 220 } | 214 } |
| 221 | 215 |
| 222 } // namespace | 216 } // namespace |
| 223 | 217 |
| 224 // static | 218 // static |
| 225 const char Combobox::kViewClassName[] = "views/Combobox"; | 219 const char Combobox::kViewClassName[] = "views/Combobox"; |
| 226 | 220 |
| 221 // Adapts a ui::ComboboxModel to a ui::MenuModel. |
| 222 class Combobox::ComboboxMenuModelAdapter : public ui::MenuModel, |
| 223 public ui::ComboboxModelObserver { |
| 224 public: |
| 225 ComboboxMenuModelAdapter(Combobox* owner, ui::ComboboxModel* model) |
| 226 : owner_(owner), model_(model) { |
| 227 model_->AddObserver(this); |
| 228 } |
| 229 |
| 230 ~ComboboxMenuModelAdapter() override { model_->RemoveObserver(this); } |
| 231 |
| 232 private: |
| 233 bool UseCheckmarks() const { |
| 234 return owner_->style_ != STYLE_ACTION && |
| 235 MenuConfig::instance(owner_->GetNativeTheme()) |
| 236 .check_selected_combobox_item; |
| 237 } |
| 238 |
| 239 // Overridden from MenuModel: |
| 240 bool HasIcons() const override { return false; } |
| 241 |
| 242 int GetItemCount() const override { return model_->GetItemCount(); } |
| 243 |
| 244 ItemType GetTypeAt(int index) const override { |
| 245 if (model_->IsItemSeparatorAt(index)) { |
| 246 // In action menus, disallow <item>, <separator>, ... since that would put |
| 247 // a separator at the top of the menu. |
| 248 DCHECK(index != 1 || owner_->style_ != STYLE_ACTION); |
| 249 return TYPE_SEPARATOR; |
| 250 } |
| 251 return UseCheckmarks() ? TYPE_CHECK : TYPE_COMMAND; |
| 252 } |
| 253 |
| 254 ui::MenuSeparatorType GetSeparatorTypeAt(int index) const override { |
| 255 return ui::NORMAL_SEPARATOR; |
| 256 } |
| 257 |
| 258 int GetCommandIdAt(int index) const override { |
| 259 return index + kFirstMenuItemId; |
| 260 } |
| 261 |
| 262 base::string16 GetLabelAt(int index) const override { |
| 263 // Inserting the Unicode formatting characters if necessary so that the |
| 264 // text is displayed correctly in right-to-left UIs. |
| 265 base::string16 text = model_->GetItemAt(index); |
| 266 base::i18n::AdjustStringForLocaleDirection(&text); |
| 267 return text; |
| 268 } |
| 269 |
| 270 bool IsItemDynamicAt(int index) const override { return true; } |
| 271 |
| 272 const gfx::FontList* GetLabelFontListAt(int index) const override { |
| 273 return &GetFontList(); |
| 274 } |
| 275 |
| 276 bool GetAcceleratorAt(int index, |
| 277 ui::Accelerator* accelerator) const override { |
| 278 return false; |
| 279 } |
| 280 |
| 281 bool IsItemCheckedAt(int index) const override { |
| 282 return UseCheckmarks() && index == owner_->selected_index_; |
| 283 } |
| 284 |
| 285 int GetGroupIdAt(int index) const override { return -1; } |
| 286 |
| 287 bool GetIconAt(int index, gfx::Image* icon) override { return false; } |
| 288 |
| 289 ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override { |
| 290 return nullptr; |
| 291 } |
| 292 |
| 293 bool IsEnabledAt(int index) const override { |
| 294 return model_->IsItemEnabledAt(index); |
| 295 } |
| 296 |
| 297 bool IsVisibleAt(int index) const override { |
| 298 // When STYLE_ACTION is used, the first item is not added to the menu. It is |
| 299 // assumed that the first item is always selected and rendered on the top of |
| 300 // the action button. |
| 301 return index > 0 || owner_->style_ != STYLE_ACTION; |
| 302 } |
| 303 |
| 304 void HighlightChangedTo(int index) override {} |
| 305 |
| 306 void ActivatedAt(int index) override { |
| 307 owner_->selected_index_ = index; |
| 308 owner_->OnPerformAction(); |
| 309 } |
| 310 |
| 311 void ActivatedAt(int index, int event_flags) override { ActivatedAt(index); } |
| 312 |
| 313 MenuModel* GetSubmenuModelAt(int index) const override { return nullptr; } |
| 314 |
| 315 void SetMenuModelDelegate( |
| 316 ui::MenuModelDelegate* menu_model_delegate) override {} |
| 317 |
| 318 ui::MenuModelDelegate* GetMenuModelDelegate() const override { |
| 319 return nullptr; |
| 320 } |
| 321 |
| 322 // Overridden from ComboboxModelObserver: |
| 323 void OnComboboxModelChanged(ui::ComboboxModel* model) override { |
| 324 owner_->ModelChanged(); |
| 325 } |
| 326 |
| 327 Combobox* owner_; // Weak. Owns this. |
| 328 ui::ComboboxModel* model_; // Weak. |
| 329 |
| 330 DISALLOW_COPY_AND_ASSIGN(ComboboxMenuModelAdapter); |
| 331 }; |
| 332 |
| 227 //////////////////////////////////////////////////////////////////////////////// | 333 //////////////////////////////////////////////////////////////////////////////// |
| 228 // Combobox, public: | 334 // Combobox, public: |
| 229 | 335 |
| 230 Combobox::Combobox(ui::ComboboxModel* model) | 336 Combobox::Combobox(ui::ComboboxModel* model) |
| 231 : model_(model), | 337 : model_(model), |
| 232 style_(STYLE_NORMAL), | 338 style_(STYLE_NORMAL), |
| 233 listener_(NULL), | 339 listener_(NULL), |
| 234 selected_index_(model_->GetDefaultIndex()), | 340 selected_index_(model_->GetDefaultIndex()), |
| 235 invalid_(false), | 341 invalid_(false), |
| 236 menu_(NULL), | 342 menu_model_adapter_(new ComboboxMenuModelAdapter(this, model)), |
| 237 dropdown_open_(false), | |
| 238 text_button_(new TransparentButton(this)), | 343 text_button_(new TransparentButton(this)), |
| 239 arrow_button_(new TransparentButton(this)), | 344 arrow_button_(new TransparentButton(this)), |
| 240 weak_ptr_factory_(this) { | 345 weak_ptr_factory_(this) { |
| 241 model_->AddObserver(this); | 346 ModelChanged(); |
| 242 UpdateFromModel(); | |
| 243 SetFocusable(true); | 347 SetFocusable(true); |
| 244 UpdateBorder(); | 348 UpdateBorder(); |
| 245 | 349 |
| 246 // Initialize the button images. | 350 // Initialize the button images. |
| 247 Button::ButtonState button_states[] = { | 351 Button::ButtonState button_states[] = { |
| 248 Button::STATE_DISABLED, | 352 Button::STATE_DISABLED, |
| 249 Button::STATE_NORMAL, | 353 Button::STATE_NORMAL, |
| 250 Button::STATE_HOVERED, | 354 Button::STATE_HOVERED, |
| 251 Button::STATE_PRESSED, | 355 Button::STATE_PRESSED, |
| 252 }; | 356 }; |
| (...skipping 12 matching lines...) Expand all Loading... |
| 265 | 369 |
| 266 text_button_->SetVisible(true); | 370 text_button_->SetVisible(true); |
| 267 arrow_button_->SetVisible(true); | 371 arrow_button_->SetVisible(true); |
| 268 text_button_->SetFocusable(false); | 372 text_button_->SetFocusable(false); |
| 269 arrow_button_->SetFocusable(false); | 373 arrow_button_->SetFocusable(false); |
| 270 AddChildView(text_button_); | 374 AddChildView(text_button_); |
| 271 AddChildView(arrow_button_); | 375 AddChildView(arrow_button_); |
| 272 } | 376 } |
| 273 | 377 |
| 274 Combobox::~Combobox() { | 378 Combobox::~Combobox() { |
| 275 model_->RemoveObserver(this); | |
| 276 | |
| 277 if (GetInputMethod() && selector_.get()) { | 379 if (GetInputMethod() && selector_.get()) { |
| 278 // Combobox should have been blurred before destroy. | 380 // Combobox should have been blurred before destroy. |
| 279 DCHECK(selector_.get() != GetInputMethod()->GetTextInputClient()); | 381 DCHECK(selector_.get() != GetInputMethod()->GetTextInputClient()); |
| 280 } | 382 } |
| 281 } | 383 } |
| 282 | 384 |
| 283 // static | 385 // static |
| 284 const gfx::FontList& Combobox::GetFontList() { | 386 const gfx::FontList& Combobox::GetFontList() { |
| 285 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | 387 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| 286 return rb.GetFontList(ui::ResourceBundle::BaseFont); | 388 return rb.GetFontList(ui::ResourceBundle::BaseFont); |
| 287 } | 389 } |
| 288 | 390 |
| 289 void Combobox::SetStyle(Style style) { | 391 void Combobox::SetStyle(Style style) { |
| 290 if (style_ == style) | 392 if (style_ == style) |
| 291 return; | 393 return; |
| 292 | 394 |
| 293 style_ = style; | 395 style_ = style; |
| 294 if (style_ == STYLE_ACTION) | 396 if (style_ == STYLE_ACTION) |
| 295 selected_index_ = 0; | 397 selected_index_ = 0; |
| 296 | 398 |
| 297 UpdateBorder(); | 399 UpdateBorder(); |
| 298 UpdateFromModel(); | 400 content_size_ = GetContentSize(); |
| 299 PreferredSizeChanged(); | 401 PreferredSizeChanged(); |
| 300 } | 402 } |
| 301 | 403 |
| 302 void Combobox::ModelChanged() { | 404 void Combobox::ModelChanged() { |
| 303 selected_index_ = std::min(0, model_->GetItemCount()); | 405 // If the selection is no longer valid (or the model is empty), restore the |
| 304 UpdateFromModel(); | 406 // default index. |
| 407 if (selected_index_ >= model_->GetItemCount() || |
| 408 model_->GetItemCount() == 0 || |
| 409 model_->IsItemSeparatorAt(selected_index_)) { |
| 410 selected_index_ = model_->GetDefaultIndex(); |
| 411 } |
| 412 |
| 413 content_size_ = GetContentSize(); |
| 305 PreferredSizeChanged(); | 414 PreferredSizeChanged(); |
| 306 } | 415 } |
| 307 | 416 |
| 308 void Combobox::SetSelectedIndex(int index) { | 417 void Combobox::SetSelectedIndex(int index) { |
| 309 if (style_ == STYLE_ACTION) | 418 if (style_ == STYLE_ACTION) |
| 310 return; | 419 return; |
| 311 | 420 |
| 312 selected_index_ = index; | 421 selected_index_ = index; |
| 313 SchedulePaint(); | 422 SchedulePaint(); |
| 314 } | 423 } |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 359 text_button_width = width() - arrow_button_width; | 468 text_button_width = width() - arrow_button_width; |
| 360 break; | 469 break; |
| 361 } | 470 } |
| 362 } | 471 } |
| 363 | 472 |
| 364 int arrow_button_x = std::max(0, text_button_width); | 473 int arrow_button_x = std::max(0, text_button_width); |
| 365 text_button_->SetBounds(0, 0, std::max(0, text_button_width), height()); | 474 text_button_->SetBounds(0, 0, std::max(0, text_button_width), height()); |
| 366 arrow_button_->SetBounds(arrow_button_x, 0, arrow_button_width, height()); | 475 arrow_button_->SetBounds(arrow_button_x, 0, arrow_button_width, height()); |
| 367 } | 476 } |
| 368 | 477 |
| 369 bool Combobox::IsItemChecked(int id) const { | |
| 370 return false; | |
| 371 } | |
| 372 | |
| 373 bool Combobox::IsCommandEnabled(int id) const { | |
| 374 return model()->IsItemEnabledAt(MenuCommandToIndex(id)); | |
| 375 } | |
| 376 | |
| 377 void Combobox::ExecuteCommand(int id) { | |
| 378 selected_index_ = MenuCommandToIndex(id); | |
| 379 OnPerformAction(); | |
| 380 } | |
| 381 | |
| 382 bool Combobox::GetAccelerator(int id, ui::Accelerator* accel) const { | |
| 383 return false; | |
| 384 } | |
| 385 | |
| 386 int Combobox::GetRowCount() { | 478 int Combobox::GetRowCount() { |
| 387 return model()->GetItemCount(); | 479 return model()->GetItemCount(); |
| 388 } | 480 } |
| 389 | 481 |
| 390 int Combobox::GetSelectedRow() { | 482 int Combobox::GetSelectedRow() { |
| 391 return selected_index_; | 483 return selected_index_; |
| 392 } | 484 } |
| 393 | 485 |
| 394 void Combobox::SetSelectedRow(int row) { | 486 void Combobox::SetSelectedRow(int row) { |
| 395 int prev_index = selected_index_; | 487 int prev_index = selected_index_; |
| (...skipping 12 matching lines...) Expand all Loading... |
| 408 | 500 |
| 409 gfx::Size Combobox::GetPreferredSize() const { | 501 gfx::Size Combobox::GetPreferredSize() const { |
| 410 // The preferred size will drive the local bounds which in turn is used to set | 502 // The preferred size will drive the local bounds which in turn is used to set |
| 411 // the minimum width for the dropdown list. | 503 // the minimum width for the dropdown list. |
| 412 gfx::Insets insets = GetInsets(); | 504 gfx::Insets insets = GetInsets(); |
| 413 insets += gfx::Insets(Textfield::kTextPadding, | 505 insets += gfx::Insets(Textfield::kTextPadding, |
| 414 Textfield::kTextPadding, | 506 Textfield::kTextPadding, |
| 415 Textfield::kTextPadding, | 507 Textfield::kTextPadding, |
| 416 Textfield::kTextPadding); | 508 Textfield::kTextPadding); |
| 417 int total_width = std::max(kMinComboboxWidth, content_size_.width()) + | 509 int total_width = std::max(kMinComboboxWidth, content_size_.width()) + |
| 418 insets.width() + GetDisclosureArrowLeftPadding() + | 510 insets.width() + GetDisclosureArrowLeftPadding() + |
| 419 ArrowSize().width() + GetDisclosureArrowRightPadding(); | 511 ArrowSize().width() + GetDisclosureArrowRightPadding(); |
| 420 return gfx::Size(total_width, content_size_.height() + insets.height()); | 512 return gfx::Size(total_width, content_size_.height() + insets.height()); |
| 421 } | 513 } |
| 422 | 514 |
| 423 const char* Combobox::GetClassName() const { | 515 const char* Combobox::GetClassName() const { |
| 424 return kViewClassName; | 516 return kViewClassName; |
| 425 } | 517 } |
| 426 | 518 |
| 427 bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) { | 519 bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) { |
| 428 // Escape should close the drop down list when it is active, not host UI. | 520 // Escape should close the drop down list when it is active, not host UI. |
| 429 if (e.key_code() != ui::VKEY_ESCAPE || | 521 if (e.key_code() != ui::VKEY_ESCAPE || |
| 430 e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) { | 522 e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) { |
| 431 return false; | 523 return false; |
| 432 } | 524 } |
| 433 return dropdown_open_; | 525 return menu_runner_; |
| 434 } | 526 } |
| 435 | 527 |
| 436 bool Combobox::OnKeyPressed(const ui::KeyEvent& e) { | 528 bool Combobox::OnKeyPressed(const ui::KeyEvent& e) { |
| 437 // TODO(oshima): handle IME. | 529 // TODO(oshima): handle IME. |
| 438 DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED); | 530 DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED); |
| 439 | 531 |
| 440 DCHECK_GE(selected_index_, 0); | 532 DCHECK_GE(selected_index_, 0); |
| 441 DCHECK_LT(selected_index_, model()->GetItemCount()); | 533 DCHECK_LT(selected_index_, model()->GetItemCount()); |
| 442 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount()) | 534 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount()) |
| 443 selected_index_ = 0; | 535 selected_index_ = 0; |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 493 if (style_ != STYLE_ACTION) | 585 if (style_ != STYLE_ACTION) |
| 494 return false; | 586 return false; |
| 495 OnPerformAction(); | 587 OnPerformAction(); |
| 496 break; | 588 break; |
| 497 | 589 |
| 498 default: | 590 default: |
| 499 return false; | 591 return false; |
| 500 } | 592 } |
| 501 | 593 |
| 502 if (show_menu) { | 594 if (show_menu) { |
| 503 UpdateFromModel(); | |
| 504 ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD); | 595 ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD); |
| 505 } else if (new_index != selected_index_ && new_index != kNoSelection && | 596 } else if (new_index != selected_index_ && new_index != kNoSelection && |
| 506 style_ != STYLE_ACTION) { | 597 style_ != STYLE_ACTION) { |
| 507 DCHECK(!model()->IsItemSeparatorAt(new_index)); | 598 DCHECK(!model()->IsItemSeparatorAt(new_index)); |
| 508 selected_index_ = new_index; | 599 selected_index_ = new_index; |
| 509 OnPerformAction(); | 600 OnPerformAction(); |
| 510 } | 601 } |
| 511 | 602 |
| 512 return true; | 603 return true; |
| 513 } | 604 } |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 558 } | 649 } |
| 559 | 650 |
| 560 void Combobox::GetAccessibleState(ui::AXViewState* state) { | 651 void Combobox::GetAccessibleState(ui::AXViewState* state) { |
| 561 state->role = ui::AX_ROLE_COMBO_BOX; | 652 state->role = ui::AX_ROLE_COMBO_BOX; |
| 562 state->name = accessible_name_; | 653 state->name = accessible_name_; |
| 563 state->value = model_->GetItemAt(selected_index_); | 654 state->value = model_->GetItemAt(selected_index_); |
| 564 state->index = selected_index_; | 655 state->index = selected_index_; |
| 565 state->count = model_->GetItemCount(); | 656 state->count = model_->GetItemCount(); |
| 566 } | 657 } |
| 567 | 658 |
| 568 void Combobox::OnComboboxModelChanged(ui::ComboboxModel* model) { | |
| 569 DCHECK_EQ(model, model_); | |
| 570 ModelChanged(); | |
| 571 } | |
| 572 | |
| 573 void Combobox::ButtonPressed(Button* sender, const ui::Event& event) { | 659 void Combobox::ButtonPressed(Button* sender, const ui::Event& event) { |
| 574 if (!enabled()) | 660 if (!enabled()) |
| 575 return; | 661 return; |
| 576 | 662 |
| 577 RequestFocus(); | 663 RequestFocus(); |
| 578 | 664 |
| 579 if (sender == text_button_) { | 665 if (sender == text_button_) { |
| 580 OnPerformAction(); | 666 OnPerformAction(); |
| 581 } else { | 667 } else { |
| 582 DCHECK_EQ(arrow_button_, sender); | 668 DCHECK_EQ(arrow_button_, sender); |
| 583 // TODO(hajimehoshi): Fix the problem that the arrow button blinks when | 669 // TODO(hajimehoshi): Fix the problem that the arrow button blinks when |
| 584 // cliking this while the dropdown menu is opened. | 670 // cliking this while the dropdown menu is opened. |
| 585 const base::TimeDelta delta = base::Time::Now() - closed_time_; | 671 const base::TimeDelta delta = base::Time::Now() - closed_time_; |
| 586 if (delta.InMilliseconds() <= kMinimumMsBetweenButtonClicks) | 672 if (delta.InMilliseconds() <= kMinimumMsBetweenButtonClicks) |
| 587 return; | 673 return; |
| 588 | 674 |
| 589 ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE; | 675 ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE; |
| 590 if (event.IsKeyEvent()) | 676 if (event.IsKeyEvent()) |
| 591 source_type = ui::MENU_SOURCE_KEYBOARD; | 677 source_type = ui::MENU_SOURCE_KEYBOARD; |
| 592 else if (event.IsGestureEvent() || event.IsTouchEvent()) | 678 else if (event.IsGestureEvent() || event.IsTouchEvent()) |
| 593 source_type = ui::MENU_SOURCE_TOUCH; | 679 source_type = ui::MENU_SOURCE_TOUCH; |
| 594 ShowDropDownMenu(source_type); | 680 ShowDropDownMenu(source_type); |
| 595 } | 681 } |
| 596 } | 682 } |
| 597 | 683 |
| 598 void Combobox::UpdateFromModel() { | |
| 599 const gfx::FontList& font_list = Combobox::GetFontList(); | |
| 600 | |
| 601 menu_ = new MenuItemView(this); | |
| 602 // MenuRunner owns |menu_|. | |
| 603 dropdown_list_menu_runner_.reset(new MenuRunner(menu_, MenuRunner::COMBOBOX)); | |
| 604 | |
| 605 int num_items = model()->GetItemCount(); | |
| 606 int width = 0; | |
| 607 bool text_item_appended = false; | |
| 608 for (int i = 0; i < num_items; ++i) { | |
| 609 // When STYLE_ACTION is used, the first item and the following separators | |
| 610 // are not added to the dropdown menu. It is assumed that the first item is | |
| 611 // always selected and rendered on the top of the action button. | |
| 612 if (model()->IsItemSeparatorAt(i)) { | |
| 613 if (text_item_appended || style_ != STYLE_ACTION) | |
| 614 menu_->AppendSeparator(); | |
| 615 continue; | |
| 616 } | |
| 617 | |
| 618 base::string16 text = model()->GetItemAt(i); | |
| 619 | |
| 620 // Inserting the Unicode formatting characters if necessary so that the | |
| 621 // text is displayed correctly in right-to-left UIs. | |
| 622 base::i18n::AdjustStringForLocaleDirection(&text); | |
| 623 | |
| 624 if (style_ != STYLE_ACTION || i > 0) { | |
| 625 menu_->AppendMenuItem(i + kFirstMenuItemId, text, MenuItemView::NORMAL); | |
| 626 text_item_appended = true; | |
| 627 } | |
| 628 | |
| 629 if (style_ != STYLE_ACTION || i == selected_index_) | |
| 630 width = std::max(width, gfx::GetStringWidth(text, font_list)); | |
| 631 } | |
| 632 | |
| 633 content_size_.SetSize(width, font_list.GetHeight()); | |
| 634 } | |
| 635 | |
| 636 void Combobox::UpdateBorder() { | 684 void Combobox::UpdateBorder() { |
| 637 scoped_ptr<FocusableBorder> border(new FocusableBorder()); | 685 scoped_ptr<FocusableBorder> border(new FocusableBorder()); |
| 638 if (style_ == STYLE_ACTION) | 686 if (style_ == STYLE_ACTION) |
| 639 border->SetInsets(5, 10, 5, 10); | 687 border->SetInsets(5, 10, 5, 10); |
| 640 if (invalid_) | 688 if (invalid_) |
| 641 border->SetColor(kWarningColor); | 689 border->SetColor(kWarningColor); |
| 642 SetBorder(border.Pass()); | 690 SetBorder(border.Pass()); |
| 643 } | 691 } |
| 644 | 692 |
| 645 void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const { | 693 void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const { |
| (...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 751 canvas->SaveLayerAlpha(arrow_button_hover_alpha); | 799 canvas->SaveLayerAlpha(arrow_button_hover_alpha); |
| 752 const std::vector<const gfx::ImageSkia*>& arrow_button_hovered_images = | 800 const std::vector<const gfx::ImageSkia*>& arrow_button_hovered_images = |
| 753 menu_button_images_[focused][Button::STATE_HOVERED]; | 801 menu_button_images_[focused][Button::STATE_HOVERED]; |
| 754 PaintArrowButton(canvas, arrow_button_hovered_images, | 802 PaintArrowButton(canvas, arrow_button_hovered_images, |
| 755 arrow_button_->x(), height()); | 803 arrow_button_->x(), height()); |
| 756 canvas->Restore(); | 804 canvas->Restore(); |
| 757 } | 805 } |
| 758 } | 806 } |
| 759 | 807 |
| 760 void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) { | 808 void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) { |
| 761 if (!dropdown_list_menu_runner_.get()) | |
| 762 UpdateFromModel(); | |
| 763 | |
| 764 // Extend the menu to the width of the combobox. | |
| 765 SubmenuView* submenu = menu_->CreateSubmenu(); | |
| 766 submenu->set_minimum_preferred_width( | |
| 767 size().width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight)); | |
| 768 | |
| 769 gfx::Rect lb = GetLocalBounds(); | 809 gfx::Rect lb = GetLocalBounds(); |
| 770 gfx::Point menu_position(lb.origin()); | 810 gfx::Point menu_position(lb.origin()); |
| 771 | 811 |
| 772 if (style_ == STYLE_NORMAL) { | 812 if (style_ == STYLE_NORMAL) { |
| 773 // Inset the menu's requested position so the border of the menu lines up | 813 // Inset the menu's requested position so the border of the menu lines up |
| 774 // with the border of the combobox. | 814 // with the border of the combobox. |
| 775 menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft); | 815 menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft); |
| 776 menu_position.set_y(menu_position.y() + kMenuBorderWidthTop); | 816 menu_position.set_y(menu_position.y() + kMenuBorderWidthTop); |
| 777 } | 817 } |
| 778 lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight)); | 818 lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight)); |
| 779 | 819 |
| 780 View::ConvertPointToScreen(this, &menu_position); | 820 View::ConvertPointToScreen(this, &menu_position); |
| 781 if (menu_position.x() < 0) | 821 if (menu_position.x() < 0) |
| 782 menu_position.set_x(0); | 822 menu_position.set_x(0); |
| 783 | 823 |
| 784 gfx::Rect bounds(menu_position, lb.size()); | 824 gfx::Rect bounds(menu_position, lb.size()); |
| 785 | 825 |
| 786 Button::ButtonState original_state = Button::STATE_NORMAL; | 826 Button::ButtonState original_state = Button::STATE_NORMAL; |
| 787 if (arrow_button_) { | 827 if (arrow_button_) { |
| 788 original_state = arrow_button_->state(); | 828 original_state = arrow_button_->state(); |
| 789 arrow_button_->SetState(Button::STATE_PRESSED); | 829 arrow_button_->SetState(Button::STATE_PRESSED); |
| 790 } | 830 } |
| 791 dropdown_open_ = true; | |
| 792 MenuAnchorPosition anchor_position = | 831 MenuAnchorPosition anchor_position = |
| 793 style_ == STYLE_ACTION ? MENU_ANCHOR_TOPRIGHT : MENU_ANCHOR_TOPLEFT; | 832 style_ == STYLE_ACTION ? MENU_ANCHOR_TOPRIGHT : MENU_ANCHOR_TOPLEFT; |
| 794 if (dropdown_list_menu_runner_->RunMenuAt( | 833 |
| 795 GetWidget(), NULL, bounds, anchor_position, source_type) == | 834 // Allow |menu_runner_| to be set by the testing API, but if this method is |
| 796 MenuRunner::MENU_DELETED) { | 835 // ever invoked recursively, ensure the old menu is closed. |
| 836 if (!menu_runner_ || menu_runner_->IsRunning()) { |
| 837 menu_runner_.reset( |
| 838 new MenuRunner(menu_model_adapter_.get(), MenuRunner::COMBOBOX)); |
| 839 } |
| 840 if (menu_runner_->RunMenuAt(GetWidget(), nullptr, bounds, anchor_position, |
| 841 source_type) == MenuRunner::MENU_DELETED) { |
| 797 return; | 842 return; |
| 798 } | 843 } |
| 799 dropdown_open_ = false; | 844 menu_runner_.reset(); |
| 800 if (arrow_button_) | 845 if (arrow_button_) |
| 801 arrow_button_->SetState(original_state); | 846 arrow_button_->SetState(original_state); |
| 802 closed_time_ = base::Time::Now(); | 847 closed_time_ = base::Time::Now(); |
| 803 | 848 |
| 804 // Need to explicitly clear mouse handler so that events get sent | 849 // Need to explicitly clear mouse handler so that events get sent |
| 805 // properly after the menu finishes running. If we don't do this, then | 850 // properly after the menu finishes running. If we don't do this, then |
| 806 // the first click to other parts of the UI is eaten. | 851 // the first click to other parts of the UI is eaten. |
| 807 SetMouseHandler(NULL); | 852 SetMouseHandler(NULL); |
| 808 } | 853 } |
| 809 | 854 |
| 810 void Combobox::OnPerformAction() { | 855 void Combobox::OnPerformAction() { |
| 811 NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, false); | 856 NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, false); |
| 812 SchedulePaint(); | 857 SchedulePaint(); |
| 813 | 858 |
| 814 // This combobox may be deleted by the listener. | 859 // This combobox may be deleted by the listener. |
| 815 base::WeakPtr<Combobox> weak_ptr = weak_ptr_factory_.GetWeakPtr(); | 860 base::WeakPtr<Combobox> weak_ptr = weak_ptr_factory_.GetWeakPtr(); |
| 816 if (listener_) | 861 if (listener_) |
| 817 listener_->OnPerformAction(this); | 862 listener_->OnPerformAction(this); |
| 818 | 863 |
| 819 if (weak_ptr && style_ == STYLE_ACTION) | 864 if (weak_ptr && style_ == STYLE_ACTION) |
| 820 selected_index_ = 0; | 865 selected_index_ = 0; |
| 821 } | 866 } |
| 822 | 867 |
| 823 int Combobox::MenuCommandToIndex(int menu_command_id) const { | |
| 824 // (note that the id received is offset by kFirstMenuItemId) | |
| 825 // Revert menu ID offset to map back to combobox model. | |
| 826 int index = menu_command_id - kFirstMenuItemId; | |
| 827 DCHECK_LT(index, model()->GetItemCount()); | |
| 828 return index; | |
| 829 } | |
| 830 | |
| 831 int Combobox::GetDisclosureArrowLeftPadding() const { | 868 int Combobox::GetDisclosureArrowLeftPadding() const { |
| 832 switch (style_) { | 869 switch (style_) { |
| 833 case STYLE_NORMAL: | 870 case STYLE_NORMAL: |
| 834 return kDisclosureArrowLeftPadding; | 871 return kDisclosureArrowLeftPadding; |
| 835 case STYLE_ACTION: | 872 case STYLE_ACTION: |
| 836 return kDisclosureArrowButtonLeftPadding; | 873 return kDisclosureArrowButtonLeftPadding; |
| 837 } | 874 } |
| 838 NOTREACHED(); | 875 NOTREACHED(); |
| 839 return 0; | 876 return 0; |
| 840 } | 877 } |
| (...skipping 20 matching lines...) Expand all Loading... |
| 861 #else | 898 #else |
| 862 const ui::NativeTheme* native_theme_for_arrow = GetNativeTheme(); | 899 const ui::NativeTheme* native_theme_for_arrow = GetNativeTheme(); |
| 863 #endif | 900 #endif |
| 864 | 901 |
| 865 ui::NativeTheme::ExtraParams ignored; | 902 ui::NativeTheme::ExtraParams ignored; |
| 866 return native_theme_for_arrow->GetPartSize(ui::NativeTheme::kComboboxArrow, | 903 return native_theme_for_arrow->GetPartSize(ui::NativeTheme::kComboboxArrow, |
| 867 ui::NativeTheme::kNormal, | 904 ui::NativeTheme::kNormal, |
| 868 ignored); | 905 ignored); |
| 869 } | 906 } |
| 870 | 907 |
| 908 gfx::Size Combobox::GetContentSize() const { |
| 909 const gfx::FontList& font_list = GetFontList(); |
| 910 |
| 911 int width = 0; |
| 912 for (int i = 0; i < model()->GetItemCount(); ++i) { |
| 913 if (model_->IsItemSeparatorAt(i)) |
| 914 continue; |
| 915 |
| 916 if (style_ != STYLE_ACTION || i == selected_index_) { |
| 917 width = std::max( |
| 918 width, |
| 919 gfx::GetStringWidth(menu_model_adapter_->GetLabelAt(i), font_list)); |
| 920 } |
| 921 } |
| 922 return gfx::Size(width, font_list.GetHeight()); |
| 923 } |
| 924 |
| 871 PrefixSelector* Combobox::GetPrefixSelector() { | 925 PrefixSelector* Combobox::GetPrefixSelector() { |
| 872 if (!selector_) | 926 if (!selector_) |
| 873 selector_.reset(new PrefixSelector(this)); | 927 selector_.reset(new PrefixSelector(this)); |
| 874 return selector_.get(); | 928 return selector_.get(); |
| 875 } | 929 } |
| 876 | 930 |
| 877 } // namespace views | 931 } // namespace views |
| OLD | NEW |