1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/views/touchui/touch_selection_menu_runner_views.h"
7 #include "base/strings/utf_string_conversions.h"
8 #include "ui/aura/window.h"
9 #include "ui/base/l10n/l10n_util.h"
10 #include "ui/base/resource/resource_bundle.h"
11 #include "ui/gfx/canvas.h"
12 #include "ui/gfx/geometry/rect.h"
13 #include "ui/gfx/geometry/size.h"
14 #include "ui/gfx/text_utils.h"
15 #include "ui/strings/grit/ui_strings.h"
16 #include "ui/views/bubble/bubble_delegate.h"
17 #include "ui/views/controls/button/button.h"
18 #include "ui/views/controls/button/label_button.h"
19 #include "ui/views/layout/box_layout.h"
24 const int kMenuCommands
[] = {IDS_APP_CUT
, IDS_APP_COPY
, IDS_APP_PASTE
};
25 const int kSpacingBetweenButtons
= 2;
26 const int kButtonSeparatorColor
= SkColorSetARGB(13, 0, 0, 0);
27 const int kMenuButtonMinHeight
= 38;
28 const int kMenuButtonMinWidth
= 63;
29 const int kMenuMargin
= 1;
31 const char kEllipsesButtonText
[] = "...";
32 const int kEllipsesButtonTag
= -1;
36 // A bubble that contains actions available for the selected text. An object of
37 // this type, as a BubbleDelegateView, manages its own lifetime.
38 class TouchSelectionMenuRunnerViews::Menu
: public BubbleDelegateView
,
39 public ButtonListener
{
41 // Closes the menu. This will eventually self-destroy the object.
44 // Returns a new instance of Menu if there is any command available;
45 // otherwise, returns |nullptr|.
46 static Menu
* Create(TouchSelectionMenuRunnerViews
* owner
,
47 ui::TouchSelectionMenuClient
* client
,
48 const gfx::Rect
& anchor_rect
,
49 const gfx::Size
& handle_image_size
,
50 aura::Window
* context
);
53 Menu(TouchSelectionMenuRunnerViews
* owner
,
54 ui::TouchSelectionMenuClient
* client
,
55 const gfx::Rect
& anchor_rect
,
56 const gfx::Size
& handle_image_size
,
57 aura::Window
* context
);
61 // Queries the |client_| for what commands to show in the menu and sizes the
62 // menu appropriately.
65 // Helper method to create a single button.
66 Button
* CreateButton(const base::string16
& title
, int tag
);
68 // BubbleDelegateView:
69 void OnPaint(gfx::Canvas
* canvas
) override
;
70 void WindowClosing() override
;
73 void ButtonPressed(Button
* sender
, const ui::Event
& event
) override
;
75 TouchSelectionMenuRunnerViews
* owner_
;
76 ui::TouchSelectionMenuClient
* const client_
;
78 DISALLOW_COPY_AND_ASSIGN(Menu
);
81 TouchSelectionMenuRunnerViews::Menu
*
82 TouchSelectionMenuRunnerViews::Menu::Create(
83 TouchSelectionMenuRunnerViews
* owner
,
84 ui::TouchSelectionMenuClient
* client
,
85 const gfx::Rect
& anchor_rect
,
86 const gfx::Size
& handle_image_size
,
87 aura::Window
* context
) {
90 for (size_t i
= 0; i
< arraysize(kMenuCommands
); i
++) {
91 if (client
->IsCommandIdEnabled(kMenuCommands
[i
]))
92 return new Menu(owner
, client
, anchor_rect
, handle_image_size
, context
);
95 // No command is available, so return |nullptr|.
99 TouchSelectionMenuRunnerViews::Menu::Menu(TouchSelectionMenuRunnerViews
* owner
,
100 ui::TouchSelectionMenuClient
* client
,
101 const gfx::Rect
& anchor_rect
,
102 const gfx::Size
& handle_image_size
,
103 aura::Window
* context
)
104 : BubbleDelegateView(nullptr, BubbleBorder::BOTTOM_CENTER
),
110 set_shadow(BubbleBorder::SMALL_SHADOW
);
111 set_parent_window(context
);
112 set_margins(gfx::Insets(kMenuMargin
, kMenuMargin
, kMenuMargin
, kMenuMargin
));
113 set_can_activate(false);
114 set_adjust_if_offscreen(true);
115 EnableCanvasFlippingForRTLUI(true);
118 new BoxLayout(BoxLayout::kHorizontal
, 0, 0, kSpacingBetweenButtons
));
121 // After buttons are created, check if there is enough room between handles to
122 // show the menu and adjust anchor rect properly if needed, just in case the
123 // menu is needed to be shown under the selection.
124 gfx::Rect
adjusted_anchor_rect(anchor_rect
);
125 int menu_width
= GetPreferredSize().width();
126 // TODO(mfomitchev): This assumes that the handles are center-aligned to the
127 // |achor_rect| edges, which is not true. We should fix this, perhaps by
128 // passing down the cumulative width occupied by the handles within
129 // |anchor_rect| plus the handle image height instead of |handle_image_size|.
130 // Perhaps we should also allow for some minimum padding.
131 if (menu_width
> anchor_rect
.width() - handle_image_size
.width())
132 adjusted_anchor_rect
.Inset(0, 0, 0, -handle_image_size
.height());
133 SetAnchorRect(adjusted_anchor_rect
);
135 BubbleDelegateView::CreateBubble(this);
139 TouchSelectionMenuRunnerViews::Menu::~Menu() {
142 void TouchSelectionMenuRunnerViews::Menu::CreateButtons() {
143 for (size_t i
= 0; i
< arraysize(kMenuCommands
); i
++) {
144 int command_id
= kMenuCommands
[i
];
145 if (!client_
->IsCommandIdEnabled(command_id
))
149 CreateButton(l10n_util::GetStringUTF16(command_id
), command_id
);
150 AddChildView(button
);
153 // Finally, add ellipses button.
155 CreateButton(base::UTF8ToUTF16(kEllipsesButtonText
), kEllipsesButtonTag
));
159 Button
* TouchSelectionMenuRunnerViews::Menu::CreateButton(
160 const base::string16
& title
,
162 base::string16 label
=
163 gfx::RemoveAcceleratorChar(title
, '&', nullptr, nullptr);
164 LabelButton
* button
= new LabelButton(this, label
);
165 button
->SetMinSize(gfx::Size(kMenuButtonMinWidth
, kMenuButtonMinHeight
));
166 button
->SetFocusable(true);
167 button
->set_request_focus_on_press(false);
168 const gfx::FontList
& font_list
=
169 ui::ResourceBundle::GetSharedInstance().GetFontList(
170 ui::ResourceBundle::SmallFont
);
171 button
->SetFontList(font_list
);
172 button
->SetHorizontalAlignment(gfx::ALIGN_CENTER
);
173 button
->set_tag(tag
);
177 void TouchSelectionMenuRunnerViews::Menu::Close() {
178 // Closing the widget will self-destroy this object.
179 Widget
* widget
= GetWidget();
180 if (widget
&& !widget
->IsClosed())
185 void TouchSelectionMenuRunnerViews::Menu::OnPaint(gfx::Canvas
* canvas
) {
186 BubbleDelegateView::OnPaint(canvas
);
188 // Draw separator bars.
189 for (int i
= 0; i
< child_count() - 1; ++i
) {
190 View
* child
= child_at(i
);
191 int x
= child
->bounds().right() + kSpacingBetweenButtons
/ 2;
192 canvas
->FillRect(gfx::Rect(x
, 0, 1, child
->height()),
193 kButtonSeparatorColor
);
197 void TouchSelectionMenuRunnerViews::Menu::WindowClosing() {
198 DCHECK_IMPLIES(owner_
, owner_
->menu_
== this);
199 BubbleDelegateView::WindowClosing();
201 owner_
->menu_
= nullptr;
204 void TouchSelectionMenuRunnerViews::Menu::ButtonPressed(
206 const ui::Event
& event
) {
208 if (sender
->tag() != kEllipsesButtonTag
)
209 client_
->ExecuteCommand(sender
->tag(), event
.flags());
211 client_
->RunContextMenu();
214 TouchSelectionMenuRunnerViews::TouchSelectionMenuRunnerViews()
218 TouchSelectionMenuRunnerViews::~TouchSelectionMenuRunnerViews() {
222 gfx::Rect
TouchSelectionMenuRunnerViews::GetAnchorRectForTest() const {
223 return menu_
? menu_
->GetAnchorRect() : gfx::Rect();
226 void TouchSelectionMenuRunnerViews::OpenMenu(
227 ui::TouchSelectionMenuClient
* client
,
228 const gfx::Rect
& anchor_rect
,
229 const gfx::Size
& handle_image_size
,
230 aura::Window
* context
) {
233 menu_
= Menu::Create(this, client
, anchor_rect
, handle_image_size
, context
);
236 void TouchSelectionMenuRunnerViews::CloseMenu() {
240 // Closing the menu will eventually delete the object.
245 bool TouchSelectionMenuRunnerViews::IsRunning() const {
246 return menu_
!= nullptr;