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

Side by Side Diff: views/touchui/touch_selection_controller_impl.cc

Issue 8568022: views: Move desktop and touchui directories to ui/views/. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 9 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 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.
4
5 #include "views/touchui/touch_selection_controller_impl.h"
6
7 #include "base/time.h"
8 #include "base/utf_string_conversions.h"
9 #include "grit/ui_strings.h"
10 #include "third_party/skia/include/effects/SkGradientShader.h"
11 #include "ui/base/l10n/l10n_util.h"
12 #include "ui/base/resource/resource_bundle.h"
13 #include "ui/gfx/canvas.h"
14 #include "ui/gfx/canvas_skia.h"
15 #include "ui/gfx/path.h"
16 #include "ui/gfx/rect.h"
17 #include "ui/gfx/screen.h"
18 #include "ui/gfx/size.h"
19 #include "ui/gfx/transform.h"
20 #include "views/background.h"
21 #include "views/controls/button/button.h"
22 #include "views/controls/button/custom_button.h"
23 #include "views/controls/button/text_button.h"
24 #include "views/controls/label.h"
25 #include "views/controls/menu/menu_config.h"
26 #include "views/layout/box_layout.h"
27 #include "views/widget/widget.h"
28
29 namespace {
30
31 // Constants defining the visual attributes of selection handles
32 const int kSelectionHandleRadius = 10;
33 const int kSelectionHandleCursorHeight = 10;
34 const int kSelectionHandleAlpha = 0x7F;
35 const SkColor kSelectionHandleColor =
36 SkColorSetA(SK_ColorBLUE, kSelectionHandleAlpha);
37
38 // The minimum selection size to trigger selection controller.
39 const int kMinSelectionSize = 4;
40
41 const int kContextMenuCommands[] = {IDS_APP_CUT,
42 IDS_APP_COPY,
43 // TODO(varunjain): PASTE is acting funny due to some gtk clipboard issue.
44 // Uncomment the following when that is fixed.
45 // IDS_APP_PASTE,
46 IDS_APP_DELETE,
47 IDS_APP_SELECT_ALL};
48 const int kContextMenuPadding = 2;
49 const int kContextMenuTimoutMs = 1000;
50 const int kContextMenuVerticalOffset = 25;
51
52 // Convenience struct to represent a circle shape.
53 struct Circle {
54 int radius;
55 gfx::Point center;
56 SkColor color;
57 };
58
59 // Creates a widget to host SelectionHandleView.
60 views::Widget* CreateTouchSelectionPopupWidget() {
61 views::Widget* widget = new views::Widget;
62 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
63 params.can_activate = false;
64 params.transparent = true;
65 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
66 widget->Init(params);
67 return widget;
68 }
69
70 void PaintCircle(const Circle& circle, gfx::Canvas* canvas) {
71 SkPaint paint;
72 paint.setAntiAlias(true);
73 paint.setStyle(SkPaint::kFill_Style);
74 paint.setColor(circle.color);
75 gfx::Path path;
76 gfx::Rect bounds(circle.center.x() - circle.radius,
77 circle.center.y() - circle.radius,
78 circle.radius * 2,
79 circle.radius * 2);
80 SkRect rect;
81 rect.set(SkIntToScalar(bounds.x()), SkIntToScalar(bounds.y()),
82 SkIntToScalar(bounds.right()), SkIntToScalar(bounds.bottom()));
83 SkScalar radius = SkIntToScalar(circle.radius);
84 path.addRoundRect(rect, radius, radius);
85 canvas->GetSkCanvas()->drawPath(path, paint);
86 }
87
88 // The points may not match exactly, since the selection range computation may
89 // introduce some floating point errors. So check for a minimum size to decide
90 // whether or not there is any selection.
91 bool IsEmptySelection(const gfx::Point& p1, const gfx::Point& p2) {
92 int delta_x = p2.x() - p1.x();
93 int delta_y = p2.y() - p1.y();
94 return (abs(delta_x) < kMinSelectionSize && abs(delta_y) < kMinSelectionSize);
95 }
96
97 } // namespace
98
99 namespace views {
100
101 // A View that displays the text selection handle.
102 class TouchSelectionControllerImpl::SelectionHandleView : public View {
103 public:
104 SelectionHandleView(TouchSelectionControllerImpl* controller)
105 : controller_(controller) {
106 widget_.reset(CreateTouchSelectionPopupWidget());
107 widget_->SetContentsView(this);
108 widget_->SetAlwaysOnTop(true);
109
110 // We are owned by the TouchSelectionController.
111 set_parent_owned(false);
112 }
113
114 virtual ~SelectionHandleView() {
115 }
116
117 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
118 Circle circle = {kSelectionHandleRadius, gfx::Point(kSelectionHandleRadius,
119 kSelectionHandleRadius + kSelectionHandleCursorHeight),
120 kSelectionHandleColor};
121 PaintCircle(circle, canvas);
122 canvas->DrawLineInt(kSelectionHandleColor, kSelectionHandleRadius, 0,
123 kSelectionHandleRadius, kSelectionHandleCursorHeight);
124 }
125
126 virtual bool OnMousePressed(const MouseEvent& event) OVERRIDE {
127 controller_->dragging_handle_ = this;
128 return true;
129 }
130
131 virtual bool OnMouseDragged(const MouseEvent& event) OVERRIDE {
132 controller_->SelectionHandleDragged(event.location());
133 return true;
134 }
135
136 virtual void OnMouseReleased(const MouseEvent& event) OVERRIDE {
137 controller_->dragging_handle_ = NULL;
138 }
139
140 virtual void OnMouseCaptureLost() OVERRIDE {
141 controller_->dragging_handle_ = NULL;
142 }
143
144 virtual void SetVisible(bool visible) OVERRIDE {
145 // We simply show/hide the container widget.
146 if (visible != widget_->IsVisible()) {
147 if (visible)
148 widget_->Show();
149 else
150 widget_->Hide();
151 }
152 View::SetVisible(visible);
153 }
154
155 virtual gfx::Size GetPreferredSize() OVERRIDE {
156 return gfx::Size(2 * kSelectionHandleRadius,
157 2 * kSelectionHandleRadius + kSelectionHandleCursorHeight);
158 }
159
160 void SetScreenPosition(const gfx::Point& position) {
161 gfx::Rect widget_bounds(position.x() - kSelectionHandleRadius, position.y(),
162 2 * kSelectionHandleRadius,
163 2 * kSelectionHandleRadius + kSelectionHandleCursorHeight);
164 widget_->SetBounds(widget_bounds);
165 }
166
167 gfx::Point GetScreenPosition() {
168 return widget_->GetClientAreaScreenBounds().origin();
169 }
170
171 private:
172 scoped_ptr<Widget> widget_;
173 TouchSelectionControllerImpl* controller_;
174
175 DISALLOW_COPY_AND_ASSIGN(SelectionHandleView);
176 };
177
178 class ContextMenuButtonBackground : public Background {
179 public:
180 ContextMenuButtonBackground() {}
181
182 virtual void Paint(gfx::Canvas* canvas, View* view) const OVERRIDE {
183 CustomButton::ButtonState state = static_cast<CustomButton*>(view)->state();
184 SkColor background_color, border_color;
185 if (state == CustomButton::BS_NORMAL) {
186 background_color = SkColorSetARGB(102, 255, 255, 255);
187 border_color = SkColorSetARGB(36, 0, 0, 0);
188 } else {
189 background_color = SkColorSetARGB(13, 0, 0, 0);
190 border_color = SkColorSetARGB(72, 0, 0, 0);
191 }
192 int w = view->width();
193 int h = view->height();
194 canvas->FillRect(background_color, gfx::Rect(1, 1, w - 2, h - 2));
195 canvas->FillRect(border_color, gfx::Rect(2, 0, w - 4, 1));
196 canvas->FillRect(border_color, gfx::Rect(1, 1, 1, 1));
197 canvas->FillRect(border_color, gfx::Rect(0, 2, 1, h - 4));
198 canvas->FillRect(border_color, gfx::Rect(1, h - 2, 1, 1));
199 canvas->FillRect(border_color, gfx::Rect(2, h - 1, w - 4, 1));
200 canvas->FillRect(border_color, gfx::Rect(w - 2, 1, 1, 1));
201 canvas->FillRect(border_color, gfx::Rect(w - 1, 2, 1, h - 4));
202 canvas->FillRect(border_color, gfx::Rect(w - 2, h - 2, 1, 1));
203 }
204
205 private:
206 DISALLOW_COPY_AND_ASSIGN(ContextMenuButtonBackground);
207 };
208
209 // A View that displays the touch context menu.
210 class TouchSelectionControllerImpl::TouchContextMenuView
211 : public ButtonListener,
212 public View {
213 public:
214 TouchContextMenuView(TouchSelectionControllerImpl* controller)
215 : controller_(controller) {
216 widget_.reset(CreateTouchSelectionPopupWidget());
217 widget_->SetContentsView(this);
218 widget_->SetAlwaysOnTop(true);
219
220 // We are owned by the TouchSelectionController.
221 set_parent_owned(false);
222 SetLayoutManager(new BoxLayout(BoxLayout::kHorizontal, kContextMenuPadding,
223 kContextMenuPadding, kContextMenuPadding));
224 }
225
226 virtual ~TouchContextMenuView() {
227 }
228
229 virtual void SetVisible(bool visible) OVERRIDE {
230 // We simply show/hide the container widget.
231 if (visible != widget_->IsVisible()) {
232 if (visible)
233 widget_->Show();
234 else
235 widget_->Hide();
236 }
237 View::SetVisible(visible);
238 }
239
240 void SetScreenPosition(const gfx::Point& position) {
241 RefreshButtonsAndSetWidgetPosition(position);
242 }
243
244 gfx::Point GetScreenPosition() {
245 return widget_->GetClientAreaScreenBounds().origin();
246 }
247
248 void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE {
249 // TODO(varunjain): the following color scheme is copied from
250 // menu_scroll_view_container.cc. Figure out how to consolidate the two
251 // pieces of code.
252 #if defined(OS_CHROMEOS)
253 static const SkColor kGradientColors[2] = {
254 SK_ColorWHITE,
255 SkColorSetRGB(0xF0, 0xF0, 0xF0)
256 };
257
258 static const SkScalar kGradientPoints[2] = {
259 SkIntToScalar(0),
260 SkIntToScalar(1)
261 };
262
263 SkPoint points[2];
264 points[0].set(SkIntToScalar(0), SkIntToScalar(0));
265 points[1].set(SkIntToScalar(0), SkIntToScalar(height()));
266
267 SkShader* shader = SkGradientShader::CreateLinear(points,
268 kGradientColors, kGradientPoints, arraysize(kGradientPoints),
269 SkShader::kRepeat_TileMode);
270 DCHECK(shader);
271
272 SkPaint paint;
273 paint.setShader(shader);
274 shader->unref();
275
276 paint.setStyle(SkPaint::kFill_Style);
277 paint.setXfermodeMode(SkXfermode::kSrc_Mode);
278
279 canvas->DrawRectInt(0, 0, width(), height(), paint);
280 #else
281 // This is the same as COLOR_TOOLBAR.
282 canvas->GetSkCanvas()->drawColor(SkColorSetRGB(210, 225, 246),
283 SkXfermode::kSrc_Mode);
284 #endif
285 }
286
287 // ButtonListener
288 virtual void ButtonPressed(Button* sender, const views::Event& event) {
289 controller_->ExecuteCommand(sender->tag());
290 }
291
292 private:
293 // Queries the client view for what elements to show in the menu and sizes
294 // the menu appropriately.
295 void RefreshButtonsAndSetWidgetPosition(const gfx::Point& position) {
296 RemoveAllChildViews(true);
297 int total_width = 0;
298 int height = 0;
299 for (size_t i = 0; i < arraysize(kContextMenuCommands); i++) {
300 int command_id = kContextMenuCommands[i];
301 if (controller_->IsCommandIdEnabled(command_id)) {
302 TextButton* button = new TextButton(
303 this, l10n_util::GetStringUTF16(command_id));
304 button->set_focusable(true);
305 button->set_request_focus_on_press(false);
306 button->set_prefix_type(TextButton::PREFIX_HIDE);
307 button->SetEnabledColor(MenuConfig::instance().text_color);
308 button->set_background(new ContextMenuButtonBackground());
309 button->set_alignment(TextButton::ALIGN_CENTER);
310 button->SetFont(ui::ResourceBundle::GetSharedInstance().GetFont(
311 ui::ResourceBundle::LargeFont));
312 button->set_tag(command_id);
313 AddChildView(button);
314 gfx::Size button_size = button->GetPreferredSize();
315 total_width += button_size.width() + kContextMenuPadding;
316 if (height < button_size.height())
317 height = button_size.height();
318 }
319 }
320 gfx::Rect widget_bounds(position.x() - total_width / 2,
321 position.y() - height,
322 total_width,
323 height);
324 gfx::Rect monitor_bounds =
325 gfx::Screen::GetMonitorAreaNearestPoint(position);
326 widget_->SetBounds(widget_bounds.AdjustToFit(monitor_bounds));
327 Layout();
328 }
329
330 scoped_ptr<Widget> widget_;
331 TouchSelectionControllerImpl* controller_;
332
333 DISALLOW_COPY_AND_ASSIGN(TouchContextMenuView);
334 };
335
336 TouchSelectionControllerImpl::TouchSelectionControllerImpl(
337 TouchSelectionClientView* client_view)
338 : client_view_(client_view),
339 selection_handle_1_(new SelectionHandleView(this)),
340 selection_handle_2_(new SelectionHandleView(this)),
341 context_menu_(new TouchContextMenuView(this)),
342 dragging_handle_(NULL) {
343 }
344
345 TouchSelectionControllerImpl::~TouchSelectionControllerImpl() {
346 }
347
348 void TouchSelectionControllerImpl::SelectionChanged(const gfx::Point& p1,
349 const gfx::Point& p2) {
350 gfx::Point screen_pos_1(p1);
351 View::ConvertPointToScreen(client_view_, &screen_pos_1);
352 gfx::Point screen_pos_2(p2);
353 View::ConvertPointToScreen(client_view_, &screen_pos_2);
354
355 if (dragging_handle_) {
356 // We need to reposition only the selection handle that is being dragged.
357 // The other handle stays the same. Also, the selection handle being dragged
358 // will always be at the end of selection, while the other handle will be at
359 // the start.
360 dragging_handle_->SetScreenPosition(screen_pos_2);
361 } else {
362 UpdateContextMenu(p1, p2);
363
364 // Check if there is any selection at all.
365 if (IsEmptySelection(screen_pos_2, screen_pos_1)) {
366 selection_handle_1_->SetVisible(false);
367 selection_handle_2_->SetVisible(false);
368 return;
369 }
370
371 if (client_view_->bounds().Contains(p1)) {
372 selection_handle_1_->SetScreenPosition(screen_pos_1);
373 selection_handle_1_->SetVisible(true);
374 } else {
375 selection_handle_1_->SetVisible(false);
376 }
377
378 if (client_view_->bounds().Contains(p2)) {
379 selection_handle_2_->SetScreenPosition(screen_pos_2);
380 selection_handle_2_->SetVisible(true);
381 } else {
382 selection_handle_2_->SetVisible(false);
383 }
384 }
385 }
386
387 void TouchSelectionControllerImpl::ClientViewLostFocus() {
388 selection_handle_1_->SetVisible(false);
389 selection_handle_2_->SetVisible(false);
390 HideContextMenu();
391 }
392
393 void TouchSelectionControllerImpl::SelectionHandleDragged(
394 const gfx::Point& drag_pos) {
395 // We do not want to show the context menu while dragging.
396 HideContextMenu();
397 context_menu_timer_.Start(
398 FROM_HERE,
399 base::TimeDelta::FromMilliseconds(kContextMenuTimoutMs),
400 this,
401 &TouchSelectionControllerImpl::ContextMenuTimerFired);
402
403 if (client_view_->GetWidget()) {
404 DCHECK(dragging_handle_);
405 // Find the stationary selection handle.
406 SelectionHandleView* fixed_handle = selection_handle_1_.get();
407 if (fixed_handle == dragging_handle_)
408 fixed_handle = selection_handle_2_.get();
409
410 // Find selection end points in client_view's coordinate system.
411 gfx::Point p1(drag_pos.x() + kSelectionHandleRadius, drag_pos.y());
412 ConvertPointToClientView(dragging_handle_, &p1);
413
414 gfx::Point p2(kSelectionHandleRadius, 0);
415 ConvertPointToClientView(fixed_handle, &p2);
416
417 // Instruct client_view to select the region between p1 and p2. The position
418 // of |fixed_handle| is the start and that of |dragging_handle| is the end
419 // of selection.
420 client_view_->SelectRect(p2, p1);
421 }
422 }
423
424 void TouchSelectionControllerImpl::ConvertPointToClientView(
425 SelectionHandleView* source, gfx::Point* point) {
426 View::ConvertPointToScreen(source, point);
427 gfx::Rect r = client_view_->GetWidget()->GetClientAreaScreenBounds();
428 point->SetPoint(point->x() - r.x(), point->y() - r.y());
429 View::ConvertPointFromWidget(client_view_, point);
430 }
431
432 bool TouchSelectionControllerImpl::IsCommandIdEnabled(int command_id) const {
433 return client_view_->IsCommandIdEnabled(command_id);
434 }
435
436 void TouchSelectionControllerImpl::ExecuteCommand(int command_id) {
437 HideContextMenu();
438 client_view_->ExecuteCommand(command_id);
439 }
440
441 void TouchSelectionControllerImpl::ContextMenuTimerFired() {
442 // Get selection end points in client_view's space.
443 gfx::Point p1(kSelectionHandleRadius, 0);
444 ConvertPointToClientView(selection_handle_1_.get(), &p1);
445 gfx::Point p2(kSelectionHandleRadius, 0);
446 ConvertPointToClientView(selection_handle_2_.get(), &p2);
447
448 // if selection is completely inside the view, we display the context menu
449 // in the middle of the end points on the top. Else, we show the menu on the
450 // top border of the view in the center.
451 gfx::Point menu_pos;
452 if (client_view_->bounds().Contains(p1) &&
453 client_view_->bounds().Contains(p2)) {
454 menu_pos.set_x((p1.x() + p2.x()) / 2);
455 menu_pos.set_y(std::min(p1.y(), p2.y()) - kContextMenuVerticalOffset);
456 } else {
457 menu_pos.set_x(client_view_->x() + client_view_->width() / 2);
458 menu_pos.set_y(client_view_->y());
459 }
460
461 View::ConvertPointToScreen(client_view_, &menu_pos);
462
463 context_menu_->SetScreenPosition(menu_pos);
464 context_menu_->SetVisible(true);
465 }
466
467 void TouchSelectionControllerImpl::UpdateContextMenu(const gfx::Point& p1,
468 const gfx::Point& p2) {
469 // Hide context menu to be shown when the timer fires.
470 HideContextMenu();
471
472 // If there is selection, we restart the context menu timer.
473 if (!IsEmptySelection(p1, p2)) {
474 context_menu_timer_.Start(
475 FROM_HERE,
476 base::TimeDelta::FromMilliseconds(kContextMenuTimoutMs),
477 this,
478 &TouchSelectionControllerImpl::ContextMenuTimerFired);
479 }
480 }
481
482 void TouchSelectionControllerImpl::HideContextMenu() {
483 context_menu_->SetVisible(false);
484 context_menu_timer_.Stop();
485 }
486
487 gfx::Point TouchSelectionControllerImpl::GetSelectionHandle1Position() {
488 return selection_handle_1_->GetScreenPosition();
489 }
490
491 gfx::Point TouchSelectionControllerImpl::GetSelectionHandle2Position() {
492 return selection_handle_2_->GetScreenPosition();
493 }
494
495 bool TouchSelectionControllerImpl::IsSelectionHandle1Visible() {
496 return selection_handle_1_->IsVisible();
497 }
498
499 bool TouchSelectionControllerImpl::IsSelectionHandle2Visible() {
500 return selection_handle_2_->IsVisible();
501 }
502
503 TouchSelectionController* TouchSelectionController::create(
504 TouchSelectionClientView* client_view) {
505 return new TouchSelectionControllerImpl(client_view);
506 }
507
508 } // namespace views
OLDNEW
« no previous file with comments | « views/touchui/touch_selection_controller_impl.h ('k') | views/touchui/touch_selection_controller_impl_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698