Chromium Code Reviews| 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 || model_->IsItemSeparatorAt(selected_index_)) | |
| 409 selected_index_ = model_->GetDefaultIndex(); | |
|
sky
2015/09/08 16:23:12
nit: {}
tapted
2015/09/09 00:30:57
Done.
| |
| 410 | |
| 411 content_size_ = GetContentSize(); | |
| 305 PreferredSizeChanged(); | 412 PreferredSizeChanged(); |
| 306 } | 413 } |
| 307 | 414 |
| 308 void Combobox::SetSelectedIndex(int index) { | 415 void Combobox::SetSelectedIndex(int index) { |
| 309 if (style_ == STYLE_ACTION) | 416 if (style_ == STYLE_ACTION) |
| 310 return; | 417 return; |
| 311 | 418 |
| 312 selected_index_ = index; | 419 selected_index_ = index; |
| 313 SchedulePaint(); | 420 SchedulePaint(); |
| 314 } | 421 } |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 359 text_button_width = width() - arrow_button_width; | 466 text_button_width = width() - arrow_button_width; |
| 360 break; | 467 break; |
| 361 } | 468 } |
| 362 } | 469 } |
| 363 | 470 |
| 364 int arrow_button_x = std::max(0, text_button_width); | 471 int arrow_button_x = std::max(0, text_button_width); |
| 365 text_button_->SetBounds(0, 0, std::max(0, text_button_width), height()); | 472 text_button_->SetBounds(0, 0, std::max(0, text_button_width), height()); |
| 366 arrow_button_->SetBounds(arrow_button_x, 0, arrow_button_width, height()); | 473 arrow_button_->SetBounds(arrow_button_x, 0, arrow_button_width, height()); |
| 367 } | 474 } |
| 368 | 475 |
| 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() { | 476 int Combobox::GetRowCount() { |
| 387 return model()->GetItemCount(); | 477 return model()->GetItemCount(); |
| 388 } | 478 } |
| 389 | 479 |
| 390 int Combobox::GetSelectedRow() { | 480 int Combobox::GetSelectedRow() { |
| 391 return selected_index_; | 481 return selected_index_; |
| 392 } | 482 } |
| 393 | 483 |
| 394 void Combobox::SetSelectedRow(int row) { | 484 void Combobox::SetSelectedRow(int row) { |
| 395 int prev_index = selected_index_; | 485 int prev_index = selected_index_; |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 408 | 498 |
| 409 gfx::Size Combobox::GetPreferredSize() const { | 499 gfx::Size Combobox::GetPreferredSize() const { |
| 410 // The preferred size will drive the local bounds which in turn is used to set | 500 // The preferred size will drive the local bounds which in turn is used to set |
| 411 // the minimum width for the dropdown list. | 501 // the minimum width for the dropdown list. |
| 412 gfx::Insets insets = GetInsets(); | 502 gfx::Insets insets = GetInsets(); |
| 413 insets += gfx::Insets(Textfield::kTextPadding, | 503 insets += gfx::Insets(Textfield::kTextPadding, |
| 414 Textfield::kTextPadding, | 504 Textfield::kTextPadding, |
| 415 Textfield::kTextPadding, | 505 Textfield::kTextPadding, |
| 416 Textfield::kTextPadding); | 506 Textfield::kTextPadding); |
| 417 int total_width = std::max(kMinComboboxWidth, content_size_.width()) + | 507 int total_width = std::max(kMinComboboxWidth, content_size_.width()) + |
| 418 insets.width() + GetDisclosureArrowLeftPadding() + | 508 insets.width() + GetDisclosureArrowLeftPadding() + |
| 419 ArrowSize().width() + GetDisclosureArrowRightPadding(); | 509 ArrowSize().width() + GetDisclosureArrowRightPadding(); |
| 420 return gfx::Size(total_width, content_size_.height() + insets.height()); | 510 return gfx::Size(total_width, content_size_.height() + insets.height()); |
| 421 } | 511 } |
| 422 | 512 |
| 423 const char* Combobox::GetClassName() const { | 513 const char* Combobox::GetClassName() const { |
| 424 return kViewClassName; | 514 return kViewClassName; |
| 425 } | 515 } |
| 426 | 516 |
| 427 bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) { | 517 bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) { |
| 428 // Escape should close the drop down list when it is active, not host UI. | 518 // Escape should close the drop down list when it is active, not host UI. |
| 429 if (e.key_code() != ui::VKEY_ESCAPE || | 519 if (e.key_code() != ui::VKEY_ESCAPE || |
| 430 e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) { | 520 e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) { |
| 431 return false; | 521 return false; |
| 432 } | 522 } |
| 433 return dropdown_open_; | 523 return menu_runner_; |
| 434 } | 524 } |
| 435 | 525 |
| 436 bool Combobox::OnKeyPressed(const ui::KeyEvent& e) { | 526 bool Combobox::OnKeyPressed(const ui::KeyEvent& e) { |
| 437 // TODO(oshima): handle IME. | 527 // TODO(oshima): handle IME. |
| 438 DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED); | 528 DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED); |
| 439 | 529 |
| 440 DCHECK_GE(selected_index_, 0); | 530 DCHECK_GE(selected_index_, 0); |
| 441 DCHECK_LT(selected_index_, model()->GetItemCount()); | 531 DCHECK_LT(selected_index_, model()->GetItemCount()); |
| 442 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount()) | 532 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount()) |
| 443 selected_index_ = 0; | 533 selected_index_ = 0; |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 493 if (style_ != STYLE_ACTION) | 583 if (style_ != STYLE_ACTION) |
| 494 return false; | 584 return false; |
| 495 OnPerformAction(); | 585 OnPerformAction(); |
| 496 break; | 586 break; |
| 497 | 587 |
| 498 default: | 588 default: |
| 499 return false; | 589 return false; |
| 500 } | 590 } |
| 501 | 591 |
| 502 if (show_menu) { | 592 if (show_menu) { |
| 503 UpdateFromModel(); | |
| 504 ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD); | 593 ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD); |
| 505 } else if (new_index != selected_index_ && new_index != kNoSelection && | 594 } else if (new_index != selected_index_ && new_index != kNoSelection && |
| 506 style_ != STYLE_ACTION) { | 595 style_ != STYLE_ACTION) { |
| 507 DCHECK(!model()->IsItemSeparatorAt(new_index)); | 596 DCHECK(!model()->IsItemSeparatorAt(new_index)); |
| 508 selected_index_ = new_index; | 597 selected_index_ = new_index; |
| 509 OnPerformAction(); | 598 OnPerformAction(); |
| 510 } | 599 } |
| 511 | 600 |
| 512 return true; | 601 return true; |
| 513 } | 602 } |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 558 } | 647 } |
| 559 | 648 |
| 560 void Combobox::GetAccessibleState(ui::AXViewState* state) { | 649 void Combobox::GetAccessibleState(ui::AXViewState* state) { |
| 561 state->role = ui::AX_ROLE_COMBO_BOX; | 650 state->role = ui::AX_ROLE_COMBO_BOX; |
| 562 state->name = accessible_name_; | 651 state->name = accessible_name_; |
| 563 state->value = model_->GetItemAt(selected_index_); | 652 state->value = model_->GetItemAt(selected_index_); |
| 564 state->index = selected_index_; | 653 state->index = selected_index_; |
| 565 state->count = model_->GetItemCount(); | 654 state->count = model_->GetItemCount(); |
| 566 } | 655 } |
| 567 | 656 |
| 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) { | 657 void Combobox::ButtonPressed(Button* sender, const ui::Event& event) { |
| 574 if (!enabled()) | 658 if (!enabled()) |
| 575 return; | 659 return; |
| 576 | 660 |
| 577 RequestFocus(); | 661 RequestFocus(); |
| 578 | 662 |
| 579 if (sender == text_button_) { | 663 if (sender == text_button_) { |
| 580 OnPerformAction(); | 664 OnPerformAction(); |
| 581 } else { | 665 } else { |
| 582 DCHECK_EQ(arrow_button_, sender); | 666 DCHECK_EQ(arrow_button_, sender); |
| 583 // TODO(hajimehoshi): Fix the problem that the arrow button blinks when | 667 // TODO(hajimehoshi): Fix the problem that the arrow button blinks when |
| 584 // cliking this while the dropdown menu is opened. | 668 // cliking this while the dropdown menu is opened. |
| 585 const base::TimeDelta delta = base::Time::Now() - closed_time_; | 669 const base::TimeDelta delta = base::Time::Now() - closed_time_; |
| 586 if (delta.InMilliseconds() <= kMinimumMsBetweenButtonClicks) | 670 if (delta.InMilliseconds() <= kMinimumMsBetweenButtonClicks) |
| 587 return; | 671 return; |
| 588 | 672 |
| 589 ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE; | 673 ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE; |
| 590 if (event.IsKeyEvent()) | 674 if (event.IsKeyEvent()) |
| 591 source_type = ui::MENU_SOURCE_KEYBOARD; | 675 source_type = ui::MENU_SOURCE_KEYBOARD; |
| 592 else if (event.IsGestureEvent() || event.IsTouchEvent()) | 676 else if (event.IsGestureEvent() || event.IsTouchEvent()) |
| 593 source_type = ui::MENU_SOURCE_TOUCH; | 677 source_type = ui::MENU_SOURCE_TOUCH; |
| 594 ShowDropDownMenu(source_type); | 678 ShowDropDownMenu(source_type); |
| 595 } | 679 } |
| 596 } | 680 } |
| 597 | 681 |
| 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() { | 682 void Combobox::UpdateBorder() { |
| 637 scoped_ptr<FocusableBorder> border(new FocusableBorder()); | 683 scoped_ptr<FocusableBorder> border(new FocusableBorder()); |
| 638 if (style_ == STYLE_ACTION) | 684 if (style_ == STYLE_ACTION) |
| 639 border->SetInsets(5, 10, 5, 10); | 685 border->SetInsets(5, 10, 5, 10); |
| 640 if (invalid_) | 686 if (invalid_) |
| 641 border->SetColor(kWarningColor); | 687 border->SetColor(kWarningColor); |
| 642 SetBorder(border.Pass()); | 688 SetBorder(border.Pass()); |
| 643 } | 689 } |
| 644 | 690 |
| 645 void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const { | 691 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); | 797 canvas->SaveLayerAlpha(arrow_button_hover_alpha); |
| 752 const std::vector<const gfx::ImageSkia*>& arrow_button_hovered_images = | 798 const std::vector<const gfx::ImageSkia*>& arrow_button_hovered_images = |
| 753 menu_button_images_[focused][Button::STATE_HOVERED]; | 799 menu_button_images_[focused][Button::STATE_HOVERED]; |
| 754 PaintArrowButton(canvas, arrow_button_hovered_images, | 800 PaintArrowButton(canvas, arrow_button_hovered_images, |
| 755 arrow_button_->x(), height()); | 801 arrow_button_->x(), height()); |
| 756 canvas->Restore(); | 802 canvas->Restore(); |
| 757 } | 803 } |
| 758 } | 804 } |
| 759 | 805 |
| 760 void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) { | 806 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(); | 807 gfx::Rect lb = GetLocalBounds(); |
| 770 gfx::Point menu_position(lb.origin()); | 808 gfx::Point menu_position(lb.origin()); |
| 771 | 809 |
| 772 if (style_ == STYLE_NORMAL) { | 810 if (style_ == STYLE_NORMAL) { |
| 773 // Inset the menu's requested position so the border of the menu lines up | 811 // Inset the menu's requested position so the border of the menu lines up |
| 774 // with the border of the combobox. | 812 // with the border of the combobox. |
| 775 menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft); | 813 menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft); |
| 776 menu_position.set_y(menu_position.y() + kMenuBorderWidthTop); | 814 menu_position.set_y(menu_position.y() + kMenuBorderWidthTop); |
| 777 } | 815 } |
| 778 lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight)); | 816 lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight)); |
| 779 | 817 |
| 780 View::ConvertPointToScreen(this, &menu_position); | 818 View::ConvertPointToScreen(this, &menu_position); |
| 781 if (menu_position.x() < 0) | 819 if (menu_position.x() < 0) |
| 782 menu_position.set_x(0); | 820 menu_position.set_x(0); |
| 783 | 821 |
| 784 gfx::Rect bounds(menu_position, lb.size()); | 822 gfx::Rect bounds(menu_position, lb.size()); |
| 785 | 823 |
| 786 Button::ButtonState original_state = Button::STATE_NORMAL; | 824 Button::ButtonState original_state = Button::STATE_NORMAL; |
| 787 if (arrow_button_) { | 825 if (arrow_button_) { |
| 788 original_state = arrow_button_->state(); | 826 original_state = arrow_button_->state(); |
| 789 arrow_button_->SetState(Button::STATE_PRESSED); | 827 arrow_button_->SetState(Button::STATE_PRESSED); |
| 790 } | 828 } |
| 791 dropdown_open_ = true; | |
| 792 MenuAnchorPosition anchor_position = | 829 MenuAnchorPosition anchor_position = |
| 793 style_ == STYLE_ACTION ? MENU_ANCHOR_TOPRIGHT : MENU_ANCHOR_TOPLEFT; | 830 style_ == STYLE_ACTION ? MENU_ANCHOR_TOPRIGHT : MENU_ANCHOR_TOPLEFT; |
| 794 if (dropdown_list_menu_runner_->RunMenuAt( | 831 |
| 795 GetWidget(), NULL, bounds, anchor_position, source_type) == | 832 // Allow |menu_runner_| to be set by the testing API, but if this method is |
| 796 MenuRunner::MENU_DELETED) { | 833 // ever invoked recursively, ensure the old menu is closed. |
| 834 if (!menu_runner_ || menu_runner_->IsRunning()) { | |
| 835 menu_runner_.reset( | |
| 836 new MenuRunner(menu_model_adapter_.get(), MenuRunner::COMBOBOX)); | |
| 837 } | |
| 838 if (menu_runner_->RunMenuAt(GetWidget(), nullptr, bounds, anchor_position, | |
| 839 source_type) == MenuRunner::MENU_DELETED) { | |
| 797 return; | 840 return; |
| 798 } | 841 } |
| 799 dropdown_open_ = false; | 842 menu_runner_.reset(); |
| 800 if (arrow_button_) | 843 if (arrow_button_) |
| 801 arrow_button_->SetState(original_state); | 844 arrow_button_->SetState(original_state); |
| 802 closed_time_ = base::Time::Now(); | 845 closed_time_ = base::Time::Now(); |
| 803 | 846 |
| 804 // Need to explicitly clear mouse handler so that events get sent | 847 // 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 | 848 // 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. | 849 // the first click to other parts of the UI is eaten. |
| 807 SetMouseHandler(NULL); | 850 SetMouseHandler(NULL); |
| 808 } | 851 } |
| 809 | 852 |
| 810 void Combobox::OnPerformAction() { | 853 void Combobox::OnPerformAction() { |
| 811 NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, false); | 854 NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, false); |
| 812 SchedulePaint(); | 855 SchedulePaint(); |
| 813 | 856 |
| 814 // This combobox may be deleted by the listener. | 857 // This combobox may be deleted by the listener. |
| 815 base::WeakPtr<Combobox> weak_ptr = weak_ptr_factory_.GetWeakPtr(); | 858 base::WeakPtr<Combobox> weak_ptr = weak_ptr_factory_.GetWeakPtr(); |
| 816 if (listener_) | 859 if (listener_) |
| 817 listener_->OnPerformAction(this); | 860 listener_->OnPerformAction(this); |
| 818 | 861 |
| 819 if (weak_ptr && style_ == STYLE_ACTION) | 862 if (weak_ptr && style_ == STYLE_ACTION) |
| 820 selected_index_ = 0; | 863 selected_index_ = 0; |
| 821 } | 864 } |
| 822 | 865 |
| 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 { | 866 int Combobox::GetDisclosureArrowLeftPadding() const { |
| 832 switch (style_) { | 867 switch (style_) { |
| 833 case STYLE_NORMAL: | 868 case STYLE_NORMAL: |
| 834 return kDisclosureArrowLeftPadding; | 869 return kDisclosureArrowLeftPadding; |
| 835 case STYLE_ACTION: | 870 case STYLE_ACTION: |
| 836 return kDisclosureArrowButtonLeftPadding; | 871 return kDisclosureArrowButtonLeftPadding; |
| 837 } | 872 } |
| 838 NOTREACHED(); | 873 NOTREACHED(); |
| 839 return 0; | 874 return 0; |
| 840 } | 875 } |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 861 #else | 896 #else |
| 862 const ui::NativeTheme* native_theme_for_arrow = GetNativeTheme(); | 897 const ui::NativeTheme* native_theme_for_arrow = GetNativeTheme(); |
| 863 #endif | 898 #endif |
| 864 | 899 |
| 865 ui::NativeTheme::ExtraParams ignored; | 900 ui::NativeTheme::ExtraParams ignored; |
| 866 return native_theme_for_arrow->GetPartSize(ui::NativeTheme::kComboboxArrow, | 901 return native_theme_for_arrow->GetPartSize(ui::NativeTheme::kComboboxArrow, |
| 867 ui::NativeTheme::kNormal, | 902 ui::NativeTheme::kNormal, |
| 868 ignored); | 903 ignored); |
| 869 } | 904 } |
| 870 | 905 |
| 906 gfx::Size Combobox::GetContentSize() const { | |
| 907 const gfx::FontList& font_list = GetFontList(); | |
| 908 | |
| 909 int width = 0; | |
| 910 for (int i = 0; i < model()->GetItemCount(); ++i) { | |
| 911 if (model_->IsItemSeparatorAt(i)) | |
| 912 continue; | |
| 913 | |
| 914 if (style_ != STYLE_ACTION || i == selected_index_) { | |
| 915 width = std::max( | |
| 916 width, | |
| 917 gfx::GetStringWidth(menu_model_adapter_->GetLabelAt(i), font_list)); | |
| 918 } | |
| 919 } | |
| 920 return gfx::Size(width, font_list.GetHeight()); | |
| 921 } | |
| 922 | |
| 871 PrefixSelector* Combobox::GetPrefixSelector() { | 923 PrefixSelector* Combobox::GetPrefixSelector() { |
| 872 if (!selector_) | 924 if (!selector_) |
| 873 selector_.reset(new PrefixSelector(this)); | 925 selector_.reset(new PrefixSelector(this)); |
| 874 return selector_.get(); | 926 return selector_.get(); |
| 875 } | 927 } |
| 876 | 928 |
| 877 } // namespace views | 929 } // namespace views |
| OLD | NEW |