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 // Finds the size of the largest menu label or, for STYLE_ACTION, the size of | |
233 // the selected label. | |
234 gfx::Size GetContentSize() const { | |
235 const gfx::FontList& font_list = GetFontList(); | |
236 const int num_items = GetItemCount(); | |
sky
2015/09/03 17:52:27
move into for loop.
tapted
2015/09/04 00:46:54
Done.
| |
237 | |
238 int width = 0; | |
239 for (int i = 0; i < num_items; ++i) { | |
240 if (model_->IsItemSeparatorAt(i)) | |
241 continue; | |
242 | |
243 if (owner_->style_ != STYLE_ACTION || i == owner_->selected_index_) | |
244 width = std::max(width, gfx::GetStringWidth(GetLabelAt(i), font_list)); | |
245 } | |
246 return gfx::Size(width, font_list.GetHeight()); | |
247 } | |
248 | |
249 private: | |
250 bool UseCheckmarks() const { | |
251 return owner_->style_ != STYLE_ACTION && | |
252 MenuConfig::instance(owner_->GetNativeTheme()) | |
253 .check_selected_combobox_item; | |
254 } | |
255 | |
256 // Overridden from MenuModel: | |
257 bool HasIcons() const override { return false; } | |
258 | |
259 int GetItemCount() const override { return model_->GetItemCount(); } | |
260 | |
261 ItemType GetTypeAt(int index) const override { | |
262 if (model_->IsItemSeparatorAt(index)) { | |
263 // In action menus, disallow <item>, <separator>, ... since that would put | |
264 // a separator at the top of the menu. | |
265 DCHECK(index != 1 || owner_->style_ != STYLE_ACTION); | |
266 return TYPE_SEPARATOR; | |
267 } | |
268 return UseCheckmarks() ? TYPE_CHECK : TYPE_COMMAND; | |
269 } | |
270 | |
271 ui::MenuSeparatorType GetSeparatorTypeAt(int index) const override { | |
272 return ui::NORMAL_SEPARATOR; | |
273 } | |
274 | |
275 int GetCommandIdAt(int index) const override { | |
276 return index + kFirstMenuItemId; | |
277 } | |
278 | |
279 base::string16 GetLabelAt(int index) const override { | |
280 // Inserting the Unicode formatting characters if necessary so that the | |
281 // text is displayed correctly in right-to-left UIs. | |
282 base::string16 text = model_->GetItemAt(index); | |
283 base::i18n::AdjustStringForLocaleDirection(&text); | |
284 return text; | |
285 } | |
286 | |
287 bool IsItemDynamicAt(int index) const override { return true; } | |
288 | |
289 const gfx::FontList* GetLabelFontListAt(int index) const override { | |
290 return &GetFontList(); | |
291 } | |
292 | |
293 bool GetAcceleratorAt(int index, | |
294 ui::Accelerator* accelerator) const override { | |
295 return false; | |
296 } | |
297 | |
298 bool IsItemCheckedAt(int index) const override { | |
299 return UseCheckmarks() && index == owner_->selected_index_; | |
300 } | |
301 | |
302 int GetGroupIdAt(int index) const override { return -1; } | |
303 | |
304 bool GetIconAt(int index, gfx::Image* icon) override { return false; } | |
305 | |
306 ui::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override { | |
307 return nullptr; | |
308 } | |
309 | |
310 bool IsEnabledAt(int index) const override { | |
311 return model_->IsItemEnabledAt(index); | |
312 } | |
313 | |
314 bool IsVisibleAt(int index) const override { | |
315 // When STYLE_ACTION is used, the first item is not added to the menu. It is | |
316 // assumed that the first item is always selected and rendered on the top of | |
317 // the action button. | |
318 return index > 0 || owner_->style_ != STYLE_ACTION; | |
319 } | |
320 | |
321 void HighlightChangedTo(int index) override {} | |
322 | |
323 void ActivatedAt(int index) override { | |
324 owner_->selected_index_ = index; | |
325 owner_->OnPerformAction(); | |
326 } | |
327 | |
328 void ActivatedAt(int index, int event_flags) override { ActivatedAt(index); } | |
329 | |
330 MenuModel* GetSubmenuModelAt(int index) const override { return nullptr; } | |
331 | |
332 void SetMenuModelDelegate( | |
333 ui::MenuModelDelegate* menu_model_delegate) override {} | |
334 | |
335 ui::MenuModelDelegate* GetMenuModelDelegate() const override { | |
336 return nullptr; | |
337 } | |
338 | |
339 // Overridden from ComboboxModelObserver: | |
340 void OnComboboxModelChanged(ui::ComboboxModel* model) override { | |
341 owner_->ModelChanged(); | |
342 } | |
343 | |
344 Combobox* owner_; // Weak. Owns this. | |
345 ui::ComboboxModel* model_; // Weak. | |
346 | |
347 DISALLOW_COPY_AND_ASSIGN(ComboboxMenuModelAdapter); | |
348 }; | |
349 | |
227 //////////////////////////////////////////////////////////////////////////////// | 350 //////////////////////////////////////////////////////////////////////////////// |
228 // Combobox, public: | 351 // Combobox, public: |
229 | 352 |
230 Combobox::Combobox(ui::ComboboxModel* model) | 353 Combobox::Combobox(ui::ComboboxModel* model) |
231 : model_(model), | 354 : model_(model), |
232 style_(STYLE_NORMAL), | 355 style_(STYLE_NORMAL), |
233 listener_(NULL), | 356 listener_(NULL), |
234 selected_index_(model_->GetDefaultIndex()), | 357 selected_index_(model_->GetDefaultIndex()), |
235 invalid_(false), | 358 invalid_(false), |
236 menu_(NULL), | 359 menu_model_adapter_(new ComboboxMenuModelAdapter(this, model)), |
237 dropdown_open_(false), | |
238 text_button_(new TransparentButton(this)), | 360 text_button_(new TransparentButton(this)), |
239 arrow_button_(new TransparentButton(this)), | 361 arrow_button_(new TransparentButton(this)), |
240 weak_ptr_factory_(this) { | 362 weak_ptr_factory_(this) { |
241 model_->AddObserver(this); | 363 ModelChanged(); |
242 UpdateFromModel(); | |
243 SetFocusable(true); | 364 SetFocusable(true); |
244 UpdateBorder(); | 365 UpdateBorder(); |
245 | 366 |
246 // Initialize the button images. | 367 // Initialize the button images. |
247 Button::ButtonState button_states[] = { | 368 Button::ButtonState button_states[] = { |
248 Button::STATE_DISABLED, | 369 Button::STATE_DISABLED, |
249 Button::STATE_NORMAL, | 370 Button::STATE_NORMAL, |
250 Button::STATE_HOVERED, | 371 Button::STATE_HOVERED, |
251 Button::STATE_PRESSED, | 372 Button::STATE_PRESSED, |
252 }; | 373 }; |
(...skipping 12 matching lines...) Expand all Loading... | |
265 | 386 |
266 text_button_->SetVisible(true); | 387 text_button_->SetVisible(true); |
267 arrow_button_->SetVisible(true); | 388 arrow_button_->SetVisible(true); |
268 text_button_->SetFocusable(false); | 389 text_button_->SetFocusable(false); |
269 arrow_button_->SetFocusable(false); | 390 arrow_button_->SetFocusable(false); |
270 AddChildView(text_button_); | 391 AddChildView(text_button_); |
271 AddChildView(arrow_button_); | 392 AddChildView(arrow_button_); |
272 } | 393 } |
273 | 394 |
274 Combobox::~Combobox() { | 395 Combobox::~Combobox() { |
275 model_->RemoveObserver(this); | |
276 | |
277 if (GetInputMethod() && selector_.get()) { | 396 if (GetInputMethod() && selector_.get()) { |
278 // Combobox should have been blurred before destroy. | 397 // Combobox should have been blurred before destroy. |
279 DCHECK(selector_.get() != GetInputMethod()->GetTextInputClient()); | 398 DCHECK(selector_.get() != GetInputMethod()->GetTextInputClient()); |
280 } | 399 } |
281 } | 400 } |
282 | 401 |
283 // static | 402 // static |
284 const gfx::FontList& Combobox::GetFontList() { | 403 const gfx::FontList& Combobox::GetFontList() { |
285 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | 404 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
286 return rb.GetFontList(ui::ResourceBundle::BaseFont); | 405 return rb.GetFontList(ui::ResourceBundle::BaseFont); |
287 } | 406 } |
288 | 407 |
289 void Combobox::SetStyle(Style style) { | 408 void Combobox::SetStyle(Style style) { |
290 if (style_ == style) | 409 if (style_ == style) |
291 return; | 410 return; |
292 | 411 |
293 style_ = style; | 412 style_ = style; |
294 if (style_ == STYLE_ACTION) | 413 if (style_ == STYLE_ACTION) |
295 selected_index_ = 0; | 414 selected_index_ = 0; |
296 | 415 |
297 UpdateBorder(); | 416 UpdateBorder(); |
298 UpdateFromModel(); | 417 content_size_ = menu_model_adapter_->GetContentSize(); |
299 PreferredSizeChanged(); | 418 PreferredSizeChanged(); |
300 } | 419 } |
301 | 420 |
302 void Combobox::ModelChanged() { | 421 void Combobox::ModelChanged() { |
303 selected_index_ = std::min(0, model_->GetItemCount()); | 422 selected_index_ = std::min(model_->GetDefaultIndex(), model_->GetItemCount()); |
tapted
2015/09/03 12:52:26
The old code did `index_ = std::min(0, itemcount)`
sky
2015/09/03 17:52:27
I suspect the old code really wanted something lik
tapted
2015/09/04 00:46:54
Ah, yeah using GetItemCount() would only make sens
sky
2015/09/04 14:34:17
I think GetDefaultIndex() is used during construct
tapted
2015/09/07 00:37:27
Sorry - misunderstood your comment. Changed this t
| |
304 UpdateFromModel(); | 423 content_size_ = menu_model_adapter_->GetContentSize(); |
305 PreferredSizeChanged(); | 424 PreferredSizeChanged(); |
306 } | 425 } |
307 | 426 |
308 void Combobox::SetSelectedIndex(int index) { | 427 void Combobox::SetSelectedIndex(int index) { |
309 if (style_ == STYLE_ACTION) | 428 if (style_ == STYLE_ACTION) |
310 return; | 429 return; |
311 | 430 |
312 selected_index_ = index; | 431 selected_index_ = index; |
313 SchedulePaint(); | 432 SchedulePaint(); |
314 } | 433 } |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
359 text_button_width = width() - arrow_button_width; | 478 text_button_width = width() - arrow_button_width; |
360 break; | 479 break; |
361 } | 480 } |
362 } | 481 } |
363 | 482 |
364 int arrow_button_x = std::max(0, text_button_width); | 483 int arrow_button_x = std::max(0, text_button_width); |
365 text_button_->SetBounds(0, 0, std::max(0, text_button_width), height()); | 484 text_button_->SetBounds(0, 0, std::max(0, text_button_width), height()); |
366 arrow_button_->SetBounds(arrow_button_x, 0, arrow_button_width, height()); | 485 arrow_button_->SetBounds(arrow_button_x, 0, arrow_button_width, height()); |
367 } | 486 } |
368 | 487 |
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() { | 488 int Combobox::GetRowCount() { |
387 return model()->GetItemCount(); | 489 return model()->GetItemCount(); |
388 } | 490 } |
389 | 491 |
390 int Combobox::GetSelectedRow() { | 492 int Combobox::GetSelectedRow() { |
391 return selected_index_; | 493 return selected_index_; |
392 } | 494 } |
393 | 495 |
394 void Combobox::SetSelectedRow(int row) { | 496 void Combobox::SetSelectedRow(int row) { |
395 int prev_index = selected_index_; | 497 int prev_index = selected_index_; |
(...skipping 12 matching lines...) Expand all Loading... | |
408 | 510 |
409 gfx::Size Combobox::GetPreferredSize() const { | 511 gfx::Size Combobox::GetPreferredSize() const { |
410 // The preferred size will drive the local bounds which in turn is used to set | 512 // The preferred size will drive the local bounds which in turn is used to set |
411 // the minimum width for the dropdown list. | 513 // the minimum width for the dropdown list. |
412 gfx::Insets insets = GetInsets(); | 514 gfx::Insets insets = GetInsets(); |
413 insets += gfx::Insets(Textfield::kTextPadding, | 515 insets += gfx::Insets(Textfield::kTextPadding, |
414 Textfield::kTextPadding, | 516 Textfield::kTextPadding, |
415 Textfield::kTextPadding, | 517 Textfield::kTextPadding, |
416 Textfield::kTextPadding); | 518 Textfield::kTextPadding); |
417 int total_width = std::max(kMinComboboxWidth, content_size_.width()) + | 519 int total_width = std::max(kMinComboboxWidth, content_size_.width()) + |
418 insets.width() + GetDisclosureArrowLeftPadding() + | 520 insets.width() + GetDisclosureArrowLeftPadding() + |
419 ArrowSize().width() + GetDisclosureArrowRightPadding(); | 521 ArrowSize().width() + GetDisclosureArrowRightPadding(); |
420 return gfx::Size(total_width, content_size_.height() + insets.height()); | 522 return gfx::Size(total_width, content_size_.height() + insets.height()); |
421 } | 523 } |
422 | 524 |
423 const char* Combobox::GetClassName() const { | 525 const char* Combobox::GetClassName() const { |
424 return kViewClassName; | 526 return kViewClassName; |
425 } | 527 } |
426 | 528 |
427 bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) { | 529 bool Combobox::SkipDefaultKeyEventProcessing(const ui::KeyEvent& e) { |
428 // Escape should close the drop down list when it is active, not host UI. | 530 // Escape should close the drop down list when it is active, not host UI. |
429 if (e.key_code() != ui::VKEY_ESCAPE || | 531 if (e.key_code() != ui::VKEY_ESCAPE || |
430 e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) { | 532 e.IsShiftDown() || e.IsControlDown() || e.IsAltDown()) { |
431 return false; | 533 return false; |
432 } | 534 } |
433 return dropdown_open_; | 535 return menu_runner_; |
434 } | 536 } |
435 | 537 |
436 bool Combobox::OnKeyPressed(const ui::KeyEvent& e) { | 538 bool Combobox::OnKeyPressed(const ui::KeyEvent& e) { |
437 // TODO(oshima): handle IME. | 539 // TODO(oshima): handle IME. |
438 DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED); | 540 DCHECK_EQ(e.type(), ui::ET_KEY_PRESSED); |
439 | 541 |
440 DCHECK_GE(selected_index_, 0); | 542 DCHECK_GE(selected_index_, 0); |
441 DCHECK_LT(selected_index_, model()->GetItemCount()); | 543 DCHECK_LT(selected_index_, model()->GetItemCount()); |
442 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount()) | 544 if (selected_index_ < 0 || selected_index_ > model()->GetItemCount()) |
443 selected_index_ = 0; | 545 selected_index_ = 0; |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
493 if (style_ != STYLE_ACTION) | 595 if (style_ != STYLE_ACTION) |
494 return false; | 596 return false; |
495 OnPerformAction(); | 597 OnPerformAction(); |
496 break; | 598 break; |
497 | 599 |
498 default: | 600 default: |
499 return false; | 601 return false; |
500 } | 602 } |
501 | 603 |
502 if (show_menu) { | 604 if (show_menu) { |
503 UpdateFromModel(); | |
504 ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD); | 605 ShowDropDownMenu(ui::MENU_SOURCE_KEYBOARD); |
505 } else if (new_index != selected_index_ && new_index != kNoSelection && | 606 } else if (new_index != selected_index_ && new_index != kNoSelection && |
506 style_ != STYLE_ACTION) { | 607 style_ != STYLE_ACTION) { |
507 DCHECK(!model()->IsItemSeparatorAt(new_index)); | 608 DCHECK(!model()->IsItemSeparatorAt(new_index)); |
508 selected_index_ = new_index; | 609 selected_index_ = new_index; |
509 OnPerformAction(); | 610 OnPerformAction(); |
510 } | 611 } |
511 | 612 |
512 return true; | 613 return true; |
513 } | 614 } |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
558 } | 659 } |
559 | 660 |
560 void Combobox::GetAccessibleState(ui::AXViewState* state) { | 661 void Combobox::GetAccessibleState(ui::AXViewState* state) { |
561 state->role = ui::AX_ROLE_COMBO_BOX; | 662 state->role = ui::AX_ROLE_COMBO_BOX; |
562 state->name = accessible_name_; | 663 state->name = accessible_name_; |
563 state->value = model_->GetItemAt(selected_index_); | 664 state->value = model_->GetItemAt(selected_index_); |
564 state->index = selected_index_; | 665 state->index = selected_index_; |
565 state->count = model_->GetItemCount(); | 666 state->count = model_->GetItemCount(); |
566 } | 667 } |
567 | 668 |
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) { | 669 void Combobox::ButtonPressed(Button* sender, const ui::Event& event) { |
574 if (!enabled()) | 670 if (!enabled()) |
575 return; | 671 return; |
576 | 672 |
577 RequestFocus(); | 673 RequestFocus(); |
578 | 674 |
579 if (sender == text_button_) { | 675 if (sender == text_button_) { |
580 OnPerformAction(); | 676 OnPerformAction(); |
581 } else { | 677 } else { |
582 DCHECK_EQ(arrow_button_, sender); | 678 DCHECK_EQ(arrow_button_, sender); |
583 // TODO(hajimehoshi): Fix the problem that the arrow button blinks when | 679 // TODO(hajimehoshi): Fix the problem that the arrow button blinks when |
584 // cliking this while the dropdown menu is opened. | 680 // cliking this while the dropdown menu is opened. |
585 const base::TimeDelta delta = base::Time::Now() - closed_time_; | 681 const base::TimeDelta delta = base::Time::Now() - closed_time_; |
586 if (delta.InMilliseconds() <= kMinimumMsBetweenButtonClicks) | 682 if (delta.InMilliseconds() <= kMinimumMsBetweenButtonClicks) |
587 return; | 683 return; |
588 | 684 |
589 ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE; | 685 ui::MenuSourceType source_type = ui::MENU_SOURCE_MOUSE; |
590 if (event.IsKeyEvent()) | 686 if (event.IsKeyEvent()) |
591 source_type = ui::MENU_SOURCE_KEYBOARD; | 687 source_type = ui::MENU_SOURCE_KEYBOARD; |
592 else if (event.IsGestureEvent() || event.IsTouchEvent()) | 688 else if (event.IsGestureEvent() || event.IsTouchEvent()) |
593 source_type = ui::MENU_SOURCE_TOUCH; | 689 source_type = ui::MENU_SOURCE_TOUCH; |
594 ShowDropDownMenu(source_type); | 690 ShowDropDownMenu(source_type); |
595 } | 691 } |
596 } | 692 } |
597 | 693 |
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() { | 694 void Combobox::UpdateBorder() { |
637 scoped_ptr<FocusableBorder> border(new FocusableBorder()); | 695 scoped_ptr<FocusableBorder> border(new FocusableBorder()); |
638 if (style_ == STYLE_ACTION) | 696 if (style_ == STYLE_ACTION) |
639 border->SetInsets(5, 10, 5, 10); | 697 border->SetInsets(5, 10, 5, 10); |
640 if (invalid_) | 698 if (invalid_) |
641 border->SetColor(kWarningColor); | 699 border->SetColor(kWarningColor); |
642 SetBorder(border.Pass()); | 700 SetBorder(border.Pass()); |
643 } | 701 } |
644 | 702 |
645 void Combobox::AdjustBoundsForRTLUI(gfx::Rect* rect) const { | 703 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); | 809 canvas->SaveLayerAlpha(arrow_button_hover_alpha); |
752 const std::vector<const gfx::ImageSkia*>& arrow_button_hovered_images = | 810 const std::vector<const gfx::ImageSkia*>& arrow_button_hovered_images = |
753 menu_button_images_[focused][Button::STATE_HOVERED]; | 811 menu_button_images_[focused][Button::STATE_HOVERED]; |
754 PaintArrowButton(canvas, arrow_button_hovered_images, | 812 PaintArrowButton(canvas, arrow_button_hovered_images, |
755 arrow_button_->x(), height()); | 813 arrow_button_->x(), height()); |
756 canvas->Restore(); | 814 canvas->Restore(); |
757 } | 815 } |
758 } | 816 } |
759 | 817 |
760 void Combobox::ShowDropDownMenu(ui::MenuSourceType source_type) { | 818 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(); | 819 gfx::Rect lb = GetLocalBounds(); |
770 gfx::Point menu_position(lb.origin()); | 820 gfx::Point menu_position(lb.origin()); |
771 | 821 |
772 if (style_ == STYLE_NORMAL) { | 822 if (style_ == STYLE_NORMAL) { |
773 // Inset the menu's requested position so the border of the menu lines up | 823 // Inset the menu's requested position so the border of the menu lines up |
774 // with the border of the combobox. | 824 // with the border of the combobox. |
775 menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft); | 825 menu_position.set_x(menu_position.x() + kMenuBorderWidthLeft); |
776 menu_position.set_y(menu_position.y() + kMenuBorderWidthTop); | 826 menu_position.set_y(menu_position.y() + kMenuBorderWidthTop); |
777 } | 827 } |
778 lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight)); | 828 lb.set_width(lb.width() - (kMenuBorderWidthLeft + kMenuBorderWidthRight)); |
779 | 829 |
780 View::ConvertPointToScreen(this, &menu_position); | 830 View::ConvertPointToScreen(this, &menu_position); |
781 if (menu_position.x() < 0) | 831 if (menu_position.x() < 0) |
782 menu_position.set_x(0); | 832 menu_position.set_x(0); |
783 | 833 |
784 gfx::Rect bounds(menu_position, lb.size()); | 834 gfx::Rect bounds(menu_position, lb.size()); |
785 | 835 |
786 Button::ButtonState original_state = Button::STATE_NORMAL; | 836 Button::ButtonState original_state = Button::STATE_NORMAL; |
787 if (arrow_button_) { | 837 if (arrow_button_) { |
788 original_state = arrow_button_->state(); | 838 original_state = arrow_button_->state(); |
789 arrow_button_->SetState(Button::STATE_PRESSED); | 839 arrow_button_->SetState(Button::STATE_PRESSED); |
790 } | 840 } |
791 dropdown_open_ = true; | |
792 MenuAnchorPosition anchor_position = | 841 MenuAnchorPosition anchor_position = |
793 style_ == STYLE_ACTION ? MENU_ANCHOR_TOPRIGHT : MENU_ANCHOR_TOPLEFT; | 842 style_ == STYLE_ACTION ? MENU_ANCHOR_TOPRIGHT : MENU_ANCHOR_TOPLEFT; |
794 if (dropdown_list_menu_runner_->RunMenuAt( | 843 |
795 GetWidget(), NULL, bounds, anchor_position, source_type) == | 844 // Allow |menu_runner_| to be set by the testing API. |
796 MenuRunner::MENU_DELETED) { | 845 if (!menu_runner_) { |
sky
2015/09/03 17:52:27
This makes me nervous, in particular what happens
tapted
2015/09/04 00:46:54
Done.
| |
846 menu_runner_.reset( | |
847 new MenuRunner(menu_model_adapter_.get(), MenuRunner::COMBOBOX)); | |
848 } | |
849 if (menu_runner_->RunMenuAt(GetWidget(), nullptr, bounds, anchor_position, | |
850 source_type) == MenuRunner::MENU_DELETED) { | |
797 return; | 851 return; |
798 } | 852 } |
799 dropdown_open_ = false; | 853 menu_runner_.reset(); |
800 if (arrow_button_) | 854 if (arrow_button_) |
801 arrow_button_->SetState(original_state); | 855 arrow_button_->SetState(original_state); |
802 closed_time_ = base::Time::Now(); | 856 closed_time_ = base::Time::Now(); |
803 | 857 |
804 // Need to explicitly clear mouse handler so that events get sent | 858 // 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 | 859 // 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. | 860 // the first click to other parts of the UI is eaten. |
807 SetMouseHandler(NULL); | 861 SetMouseHandler(NULL); |
808 } | 862 } |
809 | 863 |
810 void Combobox::OnPerformAction() { | 864 void Combobox::OnPerformAction() { |
811 NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, false); | 865 NotifyAccessibilityEvent(ui::AX_EVENT_VALUE_CHANGED, false); |
812 SchedulePaint(); | 866 SchedulePaint(); |
813 | 867 |
814 // This combobox may be deleted by the listener. | 868 // This combobox may be deleted by the listener. |
815 base::WeakPtr<Combobox> weak_ptr = weak_ptr_factory_.GetWeakPtr(); | 869 base::WeakPtr<Combobox> weak_ptr = weak_ptr_factory_.GetWeakPtr(); |
816 if (listener_) | 870 if (listener_) |
817 listener_->OnPerformAction(this); | 871 listener_->OnPerformAction(this); |
818 | 872 |
819 if (weak_ptr && style_ == STYLE_ACTION) | 873 if (weak_ptr && style_ == STYLE_ACTION) |
820 selected_index_ = 0; | 874 selected_index_ = 0; |
821 } | 875 } |
822 | 876 |
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 { | 877 int Combobox::GetDisclosureArrowLeftPadding() const { |
832 switch (style_) { | 878 switch (style_) { |
833 case STYLE_NORMAL: | 879 case STYLE_NORMAL: |
834 return kDisclosureArrowLeftPadding; | 880 return kDisclosureArrowLeftPadding; |
835 case STYLE_ACTION: | 881 case STYLE_ACTION: |
836 return kDisclosureArrowButtonLeftPadding; | 882 return kDisclosureArrowButtonLeftPadding; |
837 } | 883 } |
838 NOTREACHED(); | 884 NOTREACHED(); |
839 return 0; | 885 return 0; |
840 } | 886 } |
(...skipping 26 matching lines...) Expand all Loading... | |
867 ui::NativeTheme::kNormal, | 913 ui::NativeTheme::kNormal, |
868 ignored); | 914 ignored); |
869 } | 915 } |
870 | 916 |
871 PrefixSelector* Combobox::GetPrefixSelector() { | 917 PrefixSelector* Combobox::GetPrefixSelector() { |
872 if (!selector_) | 918 if (!selector_) |
873 selector_.reset(new PrefixSelector(this)); | 919 selector_.reset(new PrefixSelector(this)); |
874 return selector_.get(); | 920 return selector_.get(); |
875 } | 921 } |
876 | 922 |
923 ui::MenuModel* Combobox::GetMenuModelForTest() { | |
924 return menu_model_adapter_.get(); | |
925 } | |
926 | |
877 } // namespace views | 927 } // namespace views |
OLD | NEW |