Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(66)

Side by Side Diff: ui/views/controls/label.cc

Issue 2422993002: views::Label: Implement context menu, keyboard shortcuts for copy/select all. (Closed)
Patch Set: Address comments. Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « ui/views/controls/label.h ('k') | ui/views/controls/label_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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
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
OLDNEW
« no previous file with comments | « ui/views/controls/label.h ('k') | ui/views/controls/label_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698