Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / views / touchui / touch_selection_menu_runner_views.cc
blob6f44e526957c84f4f8ac1b2e7d82f29eb5755926
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"
21 namespace views {
22 namespace {
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;
34 } // namespace
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 {
40 public:
41 // Closes the menu. This will eventually self-destroy the object.
42 void Close();
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);
52 private:
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);
59 ~Menu() override;
61 // Queries the |client_| for what commands to show in the menu and sizes the
62 // menu appropriately.
63 void CreateButtons();
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;
72 // ButtonListener:
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) {
88 DCHECK(client);
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|.
96 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),
105 owner_(owner),
106 client_(client) {
107 DCHECK(owner_);
108 DCHECK(client_);
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);
117 SetLayoutManager(
118 new BoxLayout(BoxLayout::kHorizontal, 0, 0, kSpacingBetweenButtons));
119 CreateButtons();
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);
136 GetWidget()->Show();
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))
146 continue;
148 Button* button =
149 CreateButton(l10n_util::GetStringUTF16(command_id), command_id);
150 AddChildView(button);
153 // Finally, add ellipses button.
154 AddChildView(
155 CreateButton(base::UTF8ToUTF16(kEllipsesButtonText), kEllipsesButtonTag));
156 Layout();
159 Button* TouchSelectionMenuRunnerViews::Menu::CreateButton(
160 const base::string16& title,
161 int tag) {
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);
174 return button;
177 void TouchSelectionMenuRunnerViews::Menu::Close() {
178 // Closing the widget will self-destroy this object.
179 Widget* widget = GetWidget();
180 if (widget && !widget->IsClosed())
181 widget->Close();
182 owner_ = nullptr;
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();
200 if (owner_)
201 owner_->menu_ = nullptr;
204 void TouchSelectionMenuRunnerViews::Menu::ButtonPressed(
205 Button* sender,
206 const ui::Event& event) {
207 Close();
208 if (sender->tag() != kEllipsesButtonTag)
209 client_->ExecuteCommand(sender->tag(), event.flags());
210 else
211 client_->RunContextMenu();
214 TouchSelectionMenuRunnerViews::TouchSelectionMenuRunnerViews()
215 : menu_(nullptr) {
218 TouchSelectionMenuRunnerViews::~TouchSelectionMenuRunnerViews() {
219 CloseMenu();
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) {
231 CloseMenu();
233 menu_ = Menu::Create(this, client, anchor_rect, handle_image_size, context);
236 void TouchSelectionMenuRunnerViews::CloseMenu() {
237 if (!menu_)
238 return;
240 // Closing the menu will eventually delete the object.
241 menu_->Close();
242 menu_ = nullptr;
245 bool TouchSelectionMenuRunnerViews::IsRunning() const {
246 return menu_ != nullptr;
249 } // namespace views