| 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/label.h" | 5 #include "ui/views/controls/label.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 | 8 |
| 9 #include <algorithm> | 9 #include <algorithm> |
| 10 #include <cmath> | 10 #include <cmath> |
| (...skipping 11 matching lines...) Expand all Loading... |
| 22 #include "ui/base/clipboard/scoped_clipboard_writer.h" | 22 #include "ui/base/clipboard/scoped_clipboard_writer.h" |
| 23 #include "ui/base/cursor/cursor.h" | 23 #include "ui/base/cursor/cursor.h" |
| 24 #include "ui/base/default_style.h" | 24 #include "ui/base/default_style.h" |
| 25 #include "ui/base/material_design/material_design_controller.h" | 25 #include "ui/base/material_design/material_design_controller.h" |
| 26 #include "ui/base/resource/resource_bundle.h" | 26 #include "ui/base/resource/resource_bundle.h" |
| 27 #include "ui/gfx/canvas.h" | 27 #include "ui/gfx/canvas.h" |
| 28 #include "ui/gfx/color_utils.h" | 28 #include "ui/gfx/color_utils.h" |
| 29 #include "ui/gfx/geometry/insets.h" | 29 #include "ui/gfx/geometry/insets.h" |
| 30 #include "ui/gfx/text_elider.h" | 30 #include "ui/gfx/text_elider.h" |
| 31 #include "ui/native_theme/native_theme.h" | 31 #include "ui/native_theme/native_theme.h" |
| 32 #include "ui/strings/grit/ui_strings.h" |
| 33 #include "ui/views/controls/menu/menu_runner.h" |
| 32 #include "ui/views/focus/focus_manager.h" | 34 #include "ui/views/focus/focus_manager.h" |
| 33 #include "ui/views/native_cursor.h" | 35 #include "ui/views/native_cursor.h" |
| 34 #include "ui/views/selection_controller.h" | 36 #include "ui/views/selection_controller.h" |
| 35 | 37 |
| 36 namespace views { | 38 namespace views { |
| 37 // static | 39 // static |
| 38 const char Label::kViewClassName[] = "Label"; | 40 const char Label::kViewClassName[] = "Label"; |
| 39 const int Label::kFocusBorderPadding = 1; | 41 const int Label::kFocusBorderPadding = 1; |
| 40 | 42 |
| 41 Label::Label() : Label(base::string16()) { | 43 Label::Label() : Label(base::string16()) { |
| 42 } | 44 } |
| 43 | 45 |
| 44 Label::Label(const base::string16& text) : Label(text, GetDefaultFontList()) { | 46 Label::Label(const base::string16& text) : Label(text, GetDefaultFontList()) { |
| 45 } | 47 } |
| 46 | 48 |
| 47 Label::Label(const base::string16& text, const gfx::FontList& font_list) { | 49 Label::Label(const base::string16& text, const gfx::FontList& font_list) |
| 50 : context_menu_contents_(this) { |
| 48 Init(text, font_list); | 51 Init(text, font_list); |
| 49 } | 52 } |
| 50 | 53 |
| 51 Label::~Label() { | 54 Label::~Label() { |
| 52 } | 55 } |
| 53 | 56 |
| 54 // static | 57 // static |
| 55 const gfx::FontList& Label::GetDefaultFontList() { | 58 const gfx::FontList& Label::GetDefaultFontList() { |
| 56 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | 59 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| 57 return rb.GetFontListWithDelta(ui::kLabelFontSizeDelta); | 60 return rb.GetFontListWithDelta(ui::kLabelFontSizeDelta); |
| 58 } | 61 } |
| 59 | 62 |
| 60 void Label::SetFontList(const gfx::FontList& font_list) { | 63 void Label::SetFontList(const gfx::FontList& font_list) { |
| 61 is_first_paint_text_ = true; | 64 is_first_paint_text_ = true; |
| 62 render_text_->SetFontList(font_list); | 65 render_text_->SetFontList(font_list); |
| 63 ResetLayout(); | 66 ResetLayout(); |
| 64 } | 67 } |
| 65 | 68 |
| 66 void Label::SetText(const base::string16& new_text) { | 69 void Label::SetText(const base::string16& new_text) { |
| 67 if (new_text == text()) | 70 if (new_text == text()) |
| 68 return; | 71 return; |
| 69 is_first_paint_text_ = true; | 72 is_first_paint_text_ = true; |
| 70 render_text_->SetText(new_text); | 73 render_text_->SetText(new_text); |
| 71 ResetLayout(); | 74 ResetLayout(); |
| 75 stored_selection_range_ = gfx::Range::InvalidRange(); |
| 72 } | 76 } |
| 73 | 77 |
| 74 void Label::SetAutoColorReadabilityEnabled(bool enabled) { | 78 void Label::SetAutoColorReadabilityEnabled(bool enabled) { |
| 75 if (auto_color_readability_ == enabled) | 79 if (auto_color_readability_ == enabled) |
| 76 return; | 80 return; |
| 77 is_first_paint_text_ = true; | 81 is_first_paint_text_ = true; |
| 78 auto_color_readability_ = enabled; | 82 auto_color_readability_ = enabled; |
| 79 RecalculateColors(); | 83 RecalculateColors(); |
| 80 } | 84 } |
| 81 | 85 |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 174 if (multi_line) | 178 if (multi_line) |
| 175 SetSelectable(false); | 179 SetSelectable(false); |
| 176 ResetLayout(); | 180 ResetLayout(); |
| 177 } | 181 } |
| 178 | 182 |
| 179 void Label::SetObscured(bool obscured) { | 183 void Label::SetObscured(bool obscured) { |
| 180 if (this->obscured() == obscured) | 184 if (this->obscured() == obscured) |
| 181 return; | 185 return; |
| 182 is_first_paint_text_ = true; | 186 is_first_paint_text_ = true; |
| 183 render_text_->SetObscured(obscured); | 187 render_text_->SetObscured(obscured); |
| 188 if (obscured) |
| 189 SetSelectable(false); |
| 184 ResetLayout(); | 190 ResetLayout(); |
| 185 } | 191 } |
| 186 | 192 |
| 187 void Label::SetAllowCharacterBreak(bool allow_character_break) { | 193 void Label::SetAllowCharacterBreak(bool allow_character_break) { |
| 188 const gfx::WordWrapBehavior behavior = | 194 const gfx::WordWrapBehavior behavior = |
| 189 allow_character_break ? gfx::WRAP_LONG_WORDS : gfx::TRUNCATE_LONG_WORDS; | 195 allow_character_break ? gfx::WRAP_LONG_WORDS : gfx::TRUNCATE_LONG_WORDS; |
| 190 if (render_text_->word_wrap_behavior() == behavior) | 196 if (render_text_->word_wrap_behavior() == behavior) |
| 191 return; | 197 return; |
| 192 render_text_->SetWordWrapBehavior(behavior); | 198 render_text_->SetWordWrapBehavior(behavior); |
| 193 if (multi_line()) { | 199 if (multi_line()) { |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 237 return result; | 243 return result; |
| 238 result.append(lines_[0]->GetDisplayText()); | 244 result.append(lines_[0]->GetDisplayText()); |
| 239 for (size_t i = 1; i < lines_.size(); ++i) { | 245 for (size_t i = 1; i < lines_.size(); ++i) { |
| 240 result.append(1, '\n'); | 246 result.append(1, '\n'); |
| 241 result.append(lines_[i]->GetDisplayText()); | 247 result.append(lines_[i]->GetDisplayText()); |
| 242 } | 248 } |
| 243 return result; | 249 return result; |
| 244 } | 250 } |
| 245 | 251 |
| 246 bool Label::IsSelectionSupported() const { | 252 bool Label::IsSelectionSupported() const { |
| 247 return !multi_line() && render_text_->IsSelectionSupported(); | 253 return !multi_line() && !obscured() && render_text_->IsSelectionSupported(); |
| 248 } | 254 } |
| 249 | 255 |
| 250 bool Label::SetSelectable(bool value) { | 256 bool Label::SetSelectable(bool value) { |
| 251 if (value == selectable()) | 257 if (value == selectable()) |
| 252 return true; | 258 return true; |
| 253 | 259 |
| 254 if (!value) { | 260 if (!value) { |
| 255 ClearSelection(); | 261 ClearSelection(); |
| 262 stored_selection_range_ = gfx::Range::InvalidRange(); |
| 256 selection_controller_.reset(); | 263 selection_controller_.reset(); |
| 257 return true; | 264 return true; |
| 258 } | 265 } |
| 259 | 266 |
| 267 DCHECK(!stored_selection_range_.IsValid()); |
| 260 if (!IsSelectionSupported()) | 268 if (!IsSelectionSupported()) |
| 261 return false; | 269 return false; |
| 262 | 270 |
| 263 selection_controller_ = base::MakeUnique<SelectionController>(this); | 271 selection_controller_ = base::MakeUnique<SelectionController>(this); |
| 264 return true; | 272 return true; |
| 265 } | 273 } |
| 266 | 274 |
| 267 bool Label::HasSelection() const { | 275 bool Label::HasSelection() const { |
| 268 const gfx::RenderText* render_text = GetRenderTextForSelectionController(); | 276 const gfx::RenderText* render_text = GetRenderTextForSelectionController(); |
| 269 return render_text ? !render_text->selection().is_empty() : false; | 277 return render_text ? !render_text->selection().is_empty() : false; |
| (...skipping 267 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 537 selection_controller_->OnMouseReleased(event); | 545 selection_controller_->OnMouseReleased(event); |
| 538 } | 546 } |
| 539 | 547 |
| 540 void Label::OnMouseCaptureLost() { | 548 void Label::OnMouseCaptureLost() { |
| 541 if (!GetRenderTextForSelectionController()) | 549 if (!GetRenderTextForSelectionController()) |
| 542 return; | 550 return; |
| 543 | 551 |
| 544 selection_controller_->OnMouseCaptureLost(); | 552 selection_controller_->OnMouseCaptureLost(); |
| 545 } | 553 } |
| 546 | 554 |
| 555 bool Label::OnKeyPressed(const ui::KeyEvent& event) { |
| 556 if (!GetRenderTextForSelectionController()) |
| 557 return false; |
| 558 |
| 559 const bool shift = event.IsShiftDown(); |
| 560 const bool control = event.IsControlDown(); |
| 561 const bool alt = event.IsAltDown() || event.IsAltGrDown(); |
| 562 |
| 563 switch (event.key_code()) { |
| 564 case ui::VKEY_C: |
| 565 if (control && !alt && HasSelection()) { |
| 566 CopyToClipboard(); |
| 567 return true; |
| 568 } |
| 569 break; |
| 570 case ui::VKEY_INSERT: |
| 571 if (control && !shift && HasSelection()) { |
| 572 CopyToClipboard(); |
| 573 return true; |
| 574 } |
| 575 break; |
| 576 case ui::VKEY_A: |
| 577 if (control && !alt && !text().empty()) { |
| 578 SelectAll(); |
| 579 DCHECK(HasSelection()); |
| 580 UpdateSelectionClipboard(); |
| 581 return true; |
| 582 } |
| 583 break; |
| 584 default: |
| 585 break; |
| 586 } |
| 587 |
| 588 return false; |
| 589 } |
| 590 |
| 591 bool Label::AcceleratorPressed(const ui::Accelerator& accelerator) { |
| 592 // Allow the "Copy" action from the Chrome menu to be invoked. E.g., if a user |
| 593 // selects a Label on a web modal dialog. "Select All" doesn't appear in the |
| 594 // Chrome menu so isn't handled here. |
| 595 if (accelerator.key_code() == ui::VKEY_C && accelerator.IsCtrlDown()) { |
| 596 CopyToClipboard(); |
| 597 return true; |
| 598 } |
| 599 return false; |
| 600 } |
| 601 |
| 602 bool Label::CanHandleAccelerators() const { |
| 603 // Focus needs to be checked since the accelerator for the Copy command from |
| 604 // the Chrome menu should only be handled when the current view has focus. See |
| 605 // related comment in BrowserView::CutCopyPaste. |
| 606 return HasFocus() && GetRenderTextForSelectionController() && |
| 607 View::CanHandleAccelerators(); |
| 608 } |
| 609 |
| 547 void Label::OnDeviceScaleFactorChanged(float device_scale_factor) { | 610 void Label::OnDeviceScaleFactorChanged(float device_scale_factor) { |
| 548 View::OnDeviceScaleFactorChanged(device_scale_factor); | 611 View::OnDeviceScaleFactorChanged(device_scale_factor); |
| 549 // When the device scale factor is changed, some font rendering parameters is | 612 // When the device scale factor is changed, some font rendering parameters is |
| 550 // changed (especially, hinting). The bounding box of the text has to be | 613 // changed (especially, hinting). The bounding box of the text has to be |
| 551 // re-computed based on the new parameters. See crbug.com/441439 | 614 // re-computed based on the new parameters. See crbug.com/441439 |
| 552 ResetLayout(); | 615 ResetLayout(); |
| 553 } | 616 } |
| 554 | 617 |
| 555 void Label::VisibilityChanged(View* starting_from, bool is_visible) { | 618 void Label::VisibilityChanged(View* starting_from, bool is_visible) { |
| 556 if (!is_visible) | 619 if (!is_visible) |
| 557 ClearRenderTextLines(); | 620 ClearRenderTextLines(); |
| 558 } | 621 } |
| 559 | 622 |
| 623 void Label::ShowContextMenuForView(View* source, |
| 624 const gfx::Point& point, |
| 625 ui::MenuSourceType source_type) { |
| 626 if (!GetRenderTextForSelectionController()) |
| 627 return; |
| 628 |
| 629 context_menu_runner_.reset( |
| 630 new MenuRunner(&context_menu_contents_, MenuRunner::HAS_MNEMONICS | |
| 631 MenuRunner::CONTEXT_MENU | |
| 632 MenuRunner::ASYNC)); |
| 633 ignore_result(context_menu_runner_->RunMenuAt( |
| 634 GetWidget(), nullptr, gfx::Rect(point, gfx::Size()), MENU_ANCHOR_TOPLEFT, |
| 635 source_type)); |
| 636 } |
| 637 |
| 560 gfx::RenderText* Label::GetRenderTextForSelectionController() { | 638 gfx::RenderText* Label::GetRenderTextForSelectionController() { |
| 561 return const_cast<gfx::RenderText*>( | 639 return const_cast<gfx::RenderText*>( |
| 562 static_cast<const Label*>(this)->GetRenderTextForSelectionController()); | 640 static_cast<const Label*>(this)->GetRenderTextForSelectionController()); |
| 563 } | 641 } |
| 564 | 642 |
| 565 bool Label::IsReadOnly() const { | 643 bool Label::IsReadOnly() const { |
| 566 return true; | 644 return true; |
| 567 } | 645 } |
| 568 | 646 |
| 569 bool Label::SupportsDrag() const { | 647 bool Label::SupportsDrag() const { |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 604 } | 682 } |
| 605 | 683 |
| 606 bool Label::PasteSelectionClipboard() { | 684 bool Label::PasteSelectionClipboard() { |
| 607 NOTREACHED(); | 685 NOTREACHED(); |
| 608 return false; | 686 return false; |
| 609 } | 687 } |
| 610 | 688 |
| 611 void Label::UpdateSelectionClipboard() { | 689 void Label::UpdateSelectionClipboard() { |
| 612 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) | 690 #if defined(OS_LINUX) && !defined(OS_CHROMEOS) |
| 613 if (!obscured()) { | 691 if (!obscured()) { |
| 614 const gfx::RenderText* render_text = GetRenderTextForSelectionController(); | |
| 615 DCHECK(render_text); | |
| 616 const base::string16 selected_text = | |
| 617 render_text->GetTextFromRange(render_text->selection()); | |
| 618 ui::ScopedClipboardWriter(ui::CLIPBOARD_TYPE_SELECTION) | 692 ui::ScopedClipboardWriter(ui::CLIPBOARD_TYPE_SELECTION) |
| 619 .WriteText(selected_text); | 693 .WriteText(GetSelectedText()); |
| 620 } | 694 } |
| 621 #endif | 695 #endif |
| 622 } | 696 } |
| 623 | 697 |
| 698 bool Label::IsCommandIdChecked(int command_id) const { |
| 699 return true; |
| 700 } |
| 701 |
| 702 bool Label::IsCommandIdEnabled(int command_id) const { |
| 703 switch (command_id) { |
| 704 case IDS_APP_COPY: |
| 705 return HasSelection() && !obscured(); |
| 706 case IDS_APP_SELECT_ALL: |
| 707 return GetRenderTextForSelectionController() && !text().empty(); |
| 708 } |
| 709 return false; |
| 710 } |
| 711 |
| 712 void Label::ExecuteCommand(int command_id, int event_flags) { |
| 713 switch (command_id) { |
| 714 case IDS_APP_COPY: |
| 715 CopyToClipboard(); |
| 716 break; |
| 717 case IDS_APP_SELECT_ALL: |
| 718 SelectAll(); |
| 719 DCHECK(HasSelection()); |
| 720 UpdateSelectionClipboard(); |
| 721 break; |
| 722 default: |
| 723 NOTREACHED(); |
| 724 } |
| 725 } |
| 726 |
| 727 bool Label::GetAcceleratorForCommandId(int command_id, |
| 728 ui::Accelerator* accelerator) const { |
| 729 switch (command_id) { |
| 730 case IDS_APP_COPY: |
| 731 *accelerator = ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN); |
| 732 return true; |
| 733 |
| 734 case IDS_APP_SELECT_ALL: |
| 735 *accelerator = ui::Accelerator(ui::VKEY_A, ui::EF_CONTROL_DOWN); |
| 736 return true; |
| 737 |
| 738 default: |
| 739 return false; |
| 740 } |
| 741 } |
| 742 |
| 624 const gfx::RenderText* Label::GetRenderTextForSelectionController() const { | 743 const gfx::RenderText* Label::GetRenderTextForSelectionController() const { |
| 625 if (!selectable()) | 744 if (!selectable()) |
| 626 return nullptr; | 745 return nullptr; |
| 627 MaybeBuildRenderTextLines(); | 746 MaybeBuildRenderTextLines(); |
| 628 | 747 |
| 629 // This may happen when the content bounds of the view are empty. | 748 // This may happen when the content bounds of the view are empty. |
| 630 if (lines_.empty()) | 749 if (lines_.empty()) |
| 631 return nullptr; | 750 return nullptr; |
| 632 | 751 |
| 633 DCHECK_EQ(1u, lines_.size()); | 752 DCHECK_EQ(1u, lines_.size()); |
| (...skipping 18 matching lines...) Expand all Loading... |
| 652 subpixel_rendering_enabled_ = true; | 771 subpixel_rendering_enabled_ = true; |
| 653 auto_color_readability_ = true; | 772 auto_color_readability_ = true; |
| 654 multi_line_ = false; | 773 multi_line_ = false; |
| 655 UpdateColorsFromTheme(GetNativeTheme()); | 774 UpdateColorsFromTheme(GetNativeTheme()); |
| 656 handles_tooltips_ = true; | 775 handles_tooltips_ = true; |
| 657 collapse_when_hidden_ = false; | 776 collapse_when_hidden_ = false; |
| 658 fixed_width_ = 0; | 777 fixed_width_ = 0; |
| 659 max_width_ = 0; | 778 max_width_ = 0; |
| 660 is_first_paint_text_ = true; | 779 is_first_paint_text_ = true; |
| 661 SetText(text); | 780 SetText(text); |
| 781 |
| 782 // Only selectable labels will get requests to show the context menu, due to |
| 783 // CanProcessEventsWithinSubtree(). |
| 784 BuildContextMenuContents(); |
| 785 set_context_menu_controller(this); |
| 786 |
| 787 // This allows the BrowserView to pass the copy command from the Chrome menu |
| 788 // to the Label. |
| 789 AddAccelerator(ui::Accelerator(ui::VKEY_C, ui::EF_CONTROL_DOWN)); |
| 662 } | 790 } |
| 663 | 791 |
| 664 void Label::ResetLayout() { | 792 void Label::ResetLayout() { |
| 665 InvalidateLayout(); | 793 InvalidateLayout(); |
| 666 PreferredSizeChanged(); | 794 PreferredSizeChanged(); |
| 667 SchedulePaint(); | 795 SchedulePaint(); |
| 668 ClearRenderTextLines(); | 796 ClearRenderTextLines(); |
| 669 } | 797 } |
| 670 | 798 |
| 671 void Label::MaybeBuildRenderTextLines() const { | 799 void Label::MaybeBuildRenderTextLines() const { |
| (...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 855 } | 983 } |
| 856 | 984 |
| 857 bool Label::ShouldShowDefaultTooltip() const { | 985 bool Label::ShouldShowDefaultTooltip() const { |
| 858 const gfx::Size text_size = GetTextSize(); | 986 const gfx::Size text_size = GetTextSize(); |
| 859 const gfx::Size size = GetContentsBounds().size(); | 987 const gfx::Size size = GetContentsBounds().size(); |
| 860 return !obscured() && (text_size.width() > size.width() || | 988 return !obscured() && (text_size.width() > size.width() || |
| 861 (multi_line() && text_size.height() > size.height())); | 989 (multi_line() && text_size.height() > size.height())); |
| 862 } | 990 } |
| 863 | 991 |
| 864 void Label::ClearRenderTextLines() const { | 992 void Label::ClearRenderTextLines() const { |
| 993 // The HasSelection() call below will build |lines_| in case it is empty. |
| 994 // Return early to avoid this. |
| 995 if (lines_.empty()) |
| 996 return; |
| 997 |
| 865 // Persist the selection range if there is an active selection. | 998 // Persist the selection range if there is an active selection. |
| 866 if (HasSelection()) { | 999 if (HasSelection()) { |
| 867 stored_selection_range_ = | 1000 stored_selection_range_ = |
| 868 GetRenderTextForSelectionController()->selection(); | 1001 GetRenderTextForSelectionController()->selection(); |
| 869 } | 1002 } |
| 870 lines_.clear(); | 1003 lines_.clear(); |
| 871 } | 1004 } |
| 872 | 1005 |
| 1006 base::string16 Label::GetSelectedText() const { |
| 1007 const gfx::RenderText* render_text = GetRenderTextForSelectionController(); |
| 1008 return render_text ? render_text->GetTextFromRange(render_text->selection()) |
| 1009 : base::string16(); |
| 1010 } |
| 1011 |
| 1012 void Label::CopyToClipboard() { |
| 1013 if (!HasSelection() || obscured()) |
| 1014 return; |
| 1015 ui::ScopedClipboardWriter(ui::CLIPBOARD_TYPE_COPY_PASTE) |
| 1016 .WriteText(GetSelectedText()); |
| 1017 } |
| 1018 |
| 1019 void Label::BuildContextMenuContents() { |
| 1020 context_menu_contents_.AddItemWithStringId(IDS_APP_COPY, IDS_APP_COPY); |
| 1021 context_menu_contents_.AddItemWithStringId(IDS_APP_SELECT_ALL, |
| 1022 IDS_APP_SELECT_ALL); |
| 1023 } |
| 1024 |
| 873 } // namespace views | 1025 } // namespace views |
| OLD | NEW |