OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 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 "views/touchui/touch_selection_controller_impl.h" | 5 #include "views/touchui/touch_selection_controller_impl.h" |
6 | 6 |
7 #include "base/time.h" | |
8 #include "grit/ui_strings.h" | |
9 #include "ui/base/l10n/l10n_util.h" | |
10 #include "ui/base/resource/resource_bundle.h" | |
7 #include "ui/gfx/canvas.h" | 11 #include "ui/gfx/canvas.h" |
8 #include "ui/gfx/canvas_skia.h" | 12 #include "ui/gfx/canvas_skia.h" |
9 #include "ui/gfx/path.h" | 13 #include "ui/gfx/path.h" |
10 #include "ui/gfx/rect.h" | 14 #include "ui/gfx/rect.h" |
15 #include "ui/gfx/screen.h" | |
11 #include "ui/gfx/size.h" | 16 #include "ui/gfx/size.h" |
12 #include "ui/gfx/transform.h" | 17 #include "ui/gfx/transform.h" |
18 #include "views/background.h" | |
19 #include "views/controls/button/button.h" | |
20 #include "views/controls/button/text_button.h" | |
21 #include "views/controls/label.h" | |
22 #include "views/layout/box_layout.h" | |
13 #include "views/widget/widget.h" | 23 #include "views/widget/widget.h" |
14 #include "views/controls/label.h" | |
15 #include "views/background.h" | |
16 | 24 |
17 namespace { | 25 namespace { |
18 | 26 |
19 // Constants defining the visual attributes of selection handles | 27 // Constants defining the visual attributes of selection handles |
20 const int kSelectionHandleRadius = 10; | 28 const int kSelectionHandleRadius = 10; |
21 const int kSelectionHandleCursorHeight = 10; | 29 const int kSelectionHandleCursorHeight = 10; |
22 const int kSelectionHandleAlpha = 0x7F; | 30 const int kSelectionHandleAlpha = 0x7F; |
23 const SkColor kSelectionHandleColor = | 31 const SkColor kSelectionHandleColor = |
24 SkColorSetA(SK_ColorBLUE, kSelectionHandleAlpha); | 32 SkColorSetA(SK_ColorBLUE, kSelectionHandleAlpha); |
25 | 33 |
34 const int kContextMenuCommands[] = {IDS_APP_CUT, | |
35 IDS_APP_COPY, | |
36 // TODO(varunjain): PASTE is acting funny due to some gtk clipboard issue. | |
37 // Uncomment the following when that is fixed. | |
38 // IDS_APP_PASTE, | |
39 IDS_APP_DELETE, | |
40 IDS_APP_SELECT_ALL}; | |
41 const int kContextMenuPadding = 2; | |
42 const int kContextMenuTimoutMs = 1000; | |
43 const int kContextMenuVerticalOffset = 10; | |
44 | |
26 // Convenience struct to represent a circle shape. | 45 // Convenience struct to represent a circle shape. |
27 struct Circle { | 46 struct Circle { |
28 int radius; | 47 int radius; |
29 gfx::Point center; | 48 gfx::Point center; |
30 SkColor color; | 49 SkColor color; |
31 }; | 50 }; |
32 | 51 |
33 // Creates a widget to host SelectionHandleView. | 52 // Creates a widget to host SelectionHandleView. |
34 views::Widget* CreateSelectionHandleWidget() { | 53 views::Widget* CreateTouchSelectionPopupWidget() { |
35 views::Widget* widget = new views::Widget; | 54 views::Widget* widget = new views::Widget; |
36 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); | 55 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); |
37 params.can_activate = false; | 56 params.can_activate = false; |
38 params.transparent = true; | 57 params.transparent = true; |
39 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; | 58 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
40 widget->Init(params); | 59 widget->Init(params); |
41 return widget; | 60 return widget; |
42 } | 61 } |
43 | 62 |
44 void PaintCircle(const Circle& circle, gfx::Canvas* canvas) { | 63 void PaintCircle(const Circle& circle, gfx::Canvas* canvas) { |
(...skipping 16 matching lines...) Expand all Loading... | |
61 | 80 |
62 } // namespace | 81 } // namespace |
63 | 82 |
64 namespace views { | 83 namespace views { |
65 | 84 |
66 // A View that displays the text selection handle. | 85 // A View that displays the text selection handle. |
67 class TouchSelectionControllerImpl::SelectionHandleView : public View { | 86 class TouchSelectionControllerImpl::SelectionHandleView : public View { |
68 public: | 87 public: |
69 SelectionHandleView(TouchSelectionControllerImpl* controller) | 88 SelectionHandleView(TouchSelectionControllerImpl* controller) |
70 : controller_(controller) { | 89 : controller_(controller) { |
71 widget_.reset(CreateSelectionHandleWidget()); | 90 widget_.reset(CreateTouchSelectionPopupWidget()); |
72 widget_->SetContentsView(this); | 91 widget_->SetContentsView(this); |
73 widget_->SetAlwaysOnTop(true); | 92 widget_->SetAlwaysOnTop(true); |
74 | 93 |
75 // We are owned by the TouchSelectionController. | 94 // We are owned by the TouchSelectionController. |
76 set_parent_owned(false); | 95 set_parent_owned(false); |
77 } | 96 } |
78 | 97 |
79 virtual ~SelectionHandleView() { | 98 virtual ~SelectionHandleView() { |
80 widget_->Close(); | 99 widget_->Close(); |
81 } | 100 } |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
134 return widget_->GetClientAreaScreenBounds().origin(); | 153 return widget_->GetClientAreaScreenBounds().origin(); |
135 } | 154 } |
136 | 155 |
137 private: | 156 private: |
138 scoped_ptr<Widget> widget_; | 157 scoped_ptr<Widget> widget_; |
139 TouchSelectionControllerImpl* controller_; | 158 TouchSelectionControllerImpl* controller_; |
140 | 159 |
141 DISALLOW_COPY_AND_ASSIGN(SelectionHandleView); | 160 DISALLOW_COPY_AND_ASSIGN(SelectionHandleView); |
142 }; | 161 }; |
143 | 162 |
163 // A View that displays the touch context menu. | |
164 class TouchSelectionControllerImpl::TouchContextMenuView | |
165 : public ButtonListener, | |
166 public View { | |
167 public: | |
168 TouchContextMenuView(TouchSelectionControllerImpl* controller) | |
169 : controller_(controller) { | |
170 widget_.reset(CreateTouchSelectionPopupWidget()); | |
171 widget_->SetContentsView(this); | |
172 widget_->SetAlwaysOnTop(true); | |
173 | |
174 // We are owned by the TouchSelectionController. | |
175 set_parent_owned(false); | |
176 SetLayoutManager(new BoxLayout(BoxLayout::kHorizontal, kContextMenuPadding, | |
177 kContextMenuPadding, kContextMenuPadding)); | |
178 } | |
179 | |
180 virtual ~TouchContextMenuView() { | |
181 widget_->Close(); | |
182 } | |
183 | |
184 virtual void SetVisible(bool visible) OVERRIDE { | |
185 // We simply show/hide the container widget. | |
186 if (visible != widget_->IsVisible()) { | |
187 if (visible) | |
188 widget_->Show(); | |
189 else | |
190 widget_->Hide(); | |
191 } | |
192 View::SetVisible(visible); | |
193 } | |
194 | |
195 void SetScreenPosition(const gfx::Point& position) { | |
196 RefreshButtonsAndLayout(); | |
197 gfx::Rect widget_bounds(position.x() - width() / 2, position.y() - height(), | |
198 width(), height()); | |
199 gfx::Rect monitor_bounds = | |
200 gfx::Screen::GetMonitorAreaNearestPoint(position); | |
201 widget_->SetBounds(widget_bounds.AdjustToFit(monitor_bounds)); | |
sky
2011/08/26 15:45:33
Might you end up showing the context menu over the
varunjain
2011/08/26 16:31:15
This is possible in theory. But in practice, selec
| |
202 } | |
203 | |
204 gfx::Point GetScreenPosition() { | |
205 return widget_->GetClientAreaScreenBounds().origin(); | |
206 } | |
207 | |
208 // ButtonListener | |
209 virtual void ButtonPressed(Button* sender, const views::Event& event) { | |
210 controller_->ExecuteCommand(sender->tag()); | |
211 } | |
212 | |
213 private: | |
214 // Queries the client view for what elements to show in the menu and sizes | |
215 // the menu appropriately. | |
216 void RefreshButtonsAndLayout() { | |
sky
2011/08/26 15:45:33
nit: this doesn't layout, so maybe just RefreshBut
varunjain
2011/08/26 16:31:15
Done.
| |
217 RemoveAllChildViews(true); | |
218 int total_width = 0; | |
219 int height = 0; | |
220 for (size_t i = 0; i < arraysize(kContextMenuCommands); i++) { | |
221 int command_id = kContextMenuCommands[i]; | |
222 if (controller_->IsCommandIdEnabled(command_id)) { | |
223 TextButton* button = new TextButton(this, | |
224 UTF16ToWide(l10n_util::GetStringUTF16(command_id))); | |
225 button->set_focusable(true); | |
sky
2011/08/26 15:45:33
Is there a reason why we aren't using a standard m
| |
226 button->set_request_focus_on_press(false); | |
227 button->set_prefix_type(TextButton::PREFIX_HIDE); | |
228 button->SetEnabledColor(SK_ColorWHITE); | |
229 button->SetHoverColor(SK_ColorWHITE); | |
230 button->set_background( | |
231 Background::CreateSolidBackground(SK_ColorBLACK)); | |
232 button->set_alignment(TextButton::ALIGN_CENTER); | |
233 button->SetFont(ui::ResourceBundle::GetSharedInstance().GetFont( | |
234 ui::ResourceBundle::LargeFont)); | |
235 button->set_tag(command_id); | |
236 AddChildView(button); | |
237 gfx::Size button_size = button->GetPreferredSize(); | |
238 total_width += button_size.width() + kContextMenuPadding; | |
239 if (height < button_size.height()) | |
240 height = button_size.height(); | |
241 } | |
242 } | |
243 SetBounds(0, 0, total_width, height); | |
sky
2011/08/26 15:45:33
I don't think this is necessary. What matters is t
varunjain
2011/08/26 16:31:15
Done.
varunjain
2011/08/26 16:41:39
This is not really working. What I am seeing is th
| |
244 } | |
245 | |
246 scoped_ptr<Widget> widget_; | |
247 TouchSelectionControllerImpl* controller_; | |
248 | |
249 DISALLOW_COPY_AND_ASSIGN(TouchContextMenuView); | |
250 }; | |
251 | |
144 TouchSelectionControllerImpl::TouchSelectionControllerImpl( | 252 TouchSelectionControllerImpl::TouchSelectionControllerImpl( |
145 TouchSelectionClientView* client_view) | 253 TouchSelectionClientView* client_view) |
146 : client_view_(client_view), | 254 : client_view_(client_view), |
147 selection_handle_1_(new SelectionHandleView(this)), | 255 selection_handle_1_(new SelectionHandleView(this)), |
148 selection_handle_2_(new SelectionHandleView(this)), | 256 selection_handle_2_(new SelectionHandleView(this)), |
257 context_menu_(new TouchContextMenuView(this)), | |
149 dragging_handle_(NULL) { | 258 dragging_handle_(NULL) { |
150 } | 259 } |
151 | 260 |
152 TouchSelectionControllerImpl::~TouchSelectionControllerImpl() { | 261 TouchSelectionControllerImpl::~TouchSelectionControllerImpl() { |
153 } | 262 } |
154 | 263 |
155 void TouchSelectionControllerImpl::SelectionChanged(const gfx::Point& p1, | 264 void TouchSelectionControllerImpl::SelectionChanged(const gfx::Point& p1, |
156 const gfx::Point& p2) { | 265 const gfx::Point& p2) { |
157 gfx::Point screen_pos_1(p1); | 266 gfx::Point screen_pos_1(p1); |
158 View::ConvertPointToScreen(client_view_, &screen_pos_1); | 267 View::ConvertPointToScreen(client_view_, &screen_pos_1); |
159 gfx::Point screen_pos_2(p2); | 268 gfx::Point screen_pos_2(p2); |
160 View::ConvertPointToScreen(client_view_, &screen_pos_2); | 269 View::ConvertPointToScreen(client_view_, &screen_pos_2); |
161 | 270 |
162 if (dragging_handle_) { | 271 if (dragging_handle_) { |
163 // We need to reposition only the selection handle that is being dragged. | 272 // We need to reposition only the selection handle that is being dragged. |
164 // The other handle stays the same. Also, the selection handle being dragged | 273 // The other handle stays the same. Also, the selection handle being dragged |
165 // will always be at the end of selection, while the other handle will be at | 274 // will always be at the end of selection, while the other handle will be at |
166 // the start. | 275 // the start. |
167 dragging_handle_->SetScreenPosition(screen_pos_2); | 276 dragging_handle_->SetScreenPosition(screen_pos_2); |
168 } else { | 277 } else { |
278 UpdateContextMenu(p1, p2); | |
279 | |
169 // Check if there is any selection at all. | 280 // Check if there is any selection at all. |
170 if (screen_pos_1 == screen_pos_2) { | 281 if (screen_pos_1 == screen_pos_2) { |
171 selection_handle_1_->SetVisible(false); | 282 selection_handle_1_->SetVisible(false); |
172 selection_handle_2_->SetVisible(false); | 283 selection_handle_2_->SetVisible(false); |
173 return; | 284 return; |
174 } | 285 } |
175 | 286 |
176 if (client_view_->bounds().Contains(p1)) { | 287 if (client_view_->bounds().Contains(p1)) { |
177 selection_handle_1_->SetScreenPosition(screen_pos_1); | 288 selection_handle_1_->SetScreenPosition(screen_pos_1); |
178 selection_handle_1_->SetVisible(true); | 289 selection_handle_1_->SetVisible(true); |
179 } else { | 290 } else { |
180 selection_handle_1_->SetVisible(false); | 291 selection_handle_1_->SetVisible(false); |
181 } | 292 } |
182 | 293 |
183 if (client_view_->bounds().Contains(p2)) { | 294 if (client_view_->bounds().Contains(p2)) { |
184 selection_handle_2_->SetScreenPosition(screen_pos_2); | 295 selection_handle_2_->SetScreenPosition(screen_pos_2); |
185 selection_handle_2_->SetVisible(true); | 296 selection_handle_2_->SetVisible(true); |
186 } else { | 297 } else { |
187 selection_handle_2_->SetVisible(false); | 298 selection_handle_2_->SetVisible(false); |
188 } | 299 } |
189 } | 300 } |
190 } | 301 } |
191 | 302 |
192 void TouchSelectionControllerImpl::ClientViewLostFocus() { | 303 void TouchSelectionControllerImpl::ClientViewLostFocus() { |
193 selection_handle_1_->SetVisible(false); | 304 selection_handle_1_->SetVisible(false); |
194 selection_handle_2_->SetVisible(false); | 305 selection_handle_2_->SetVisible(false); |
306 HideContextMenu(); | |
195 } | 307 } |
196 | 308 |
197 void TouchSelectionControllerImpl::SelectionHandleDragged( | 309 void TouchSelectionControllerImpl::SelectionHandleDragged( |
198 const gfx::Point& drag_pos) { | 310 const gfx::Point& drag_pos) { |
311 // We do not want to show the context menu while dragging. | |
312 HideContextMenu(); | |
313 context_menu_timer_.Start( | |
314 base::TimeDelta::FromMilliseconds(kContextMenuTimoutMs), | |
315 this, | |
316 &TouchSelectionControllerImpl::ContextMenuTimerFired); | |
317 | |
199 if (client_view_->GetWidget()) { | 318 if (client_view_->GetWidget()) { |
200 DCHECK(dragging_handle_); | 319 DCHECK(dragging_handle_); |
201 // Find the stationary selection handle. | 320 // Find the stationary selection handle. |
202 SelectionHandleView* fixed_handle = selection_handle_1_.get(); | 321 SelectionHandleView* fixed_handle = selection_handle_1_.get(); |
203 if (fixed_handle == dragging_handle_) | 322 if (fixed_handle == dragging_handle_) |
204 fixed_handle = selection_handle_2_.get(); | 323 fixed_handle = selection_handle_2_.get(); |
205 | 324 |
206 // Find selection end points in client_view's coordinate system. | 325 // Find selection end points in client_view's coordinate system. |
207 gfx::Point p1(drag_pos.x() + kSelectionHandleRadius, drag_pos.y()); | 326 gfx::Point p1(drag_pos.x() + kSelectionHandleRadius, drag_pos.y()); |
208 ConvertPointToClientView(dragging_handle_, &p1); | 327 ConvertPointToClientView(dragging_handle_, &p1); |
209 | 328 |
210 gfx::Point p2(kSelectionHandleRadius, 0); | 329 gfx::Point p2(kSelectionHandleRadius, 0); |
211 ConvertPointToClientView(fixed_handle, &p2); | 330 ConvertPointToClientView(fixed_handle, &p2); |
212 | 331 |
213 // Instruct client_view to select the region between p1 and p2. The position | 332 // Instruct client_view to select the region between p1 and p2. The position |
214 // of |fixed_handle| is the start and that of |dragging_handle| is the end | 333 // of |fixed_handle| is the start and that of |dragging_handle| is the end |
215 // of selection. | 334 // of selection. |
216 client_view_->SelectRect(p2, p1); | 335 client_view_->SelectRect(p2, p1); |
217 } | 336 } |
218 } | 337 } |
219 | 338 |
220 void TouchSelectionControllerImpl::ConvertPointToClientView( | 339 void TouchSelectionControllerImpl::ConvertPointToClientView( |
221 SelectionHandleView* source, gfx::Point* point) { | 340 SelectionHandleView* source, gfx::Point* point) { |
222 View::ConvertPointToScreen(source, point); | 341 View::ConvertPointToScreen(source, point); |
223 gfx::Rect r = client_view_->GetWidget()->GetClientAreaScreenBounds(); | 342 gfx::Rect r = client_view_->GetWidget()->GetClientAreaScreenBounds(); |
224 point->SetPoint(point->x() - r.x(), point->y() - r.y()); | 343 point->SetPoint(point->x() - r.x(), point->y() - r.y()); |
225 View::ConvertPointFromWidget(client_view_, point); | 344 View::ConvertPointFromWidget(client_view_, point); |
226 } | 345 } |
227 | 346 |
347 bool TouchSelectionControllerImpl::IsCommandIdEnabled(int command_id) const { | |
348 return client_view_->IsCommandIdEnabled(command_id); | |
349 } | |
350 | |
351 void TouchSelectionControllerImpl::ExecuteCommand(int command_id) { | |
352 HideContextMenu(); | |
353 client_view_->ExecuteCommand(command_id); | |
354 } | |
355 | |
356 void TouchSelectionControllerImpl::ContextMenuTimerFired() { | |
357 context_menu_->SetScreenPosition(context_menu_screen_pos_); | |
358 context_menu_->SetVisible(true); | |
359 } | |
360 | |
361 void TouchSelectionControllerImpl::UpdateContextMenu(const gfx::Point& p1, | |
362 const gfx::Point& p2) { | |
363 | |
364 // No selection means no context menu. | |
365 if (p1 == p2) { | |
366 HideContextMenu(); | |
367 return; | |
368 } | |
369 | |
370 // if selection is completely inside the view, we display the context menu | |
371 // in the middle of the end points on the top. Else, we show the menu on the | |
372 // top border of the view in the center. | |
373 if (client_view_->bounds().Contains(p1) && | |
374 client_view_->bounds().Contains(p2)) { | |
375 context_menu_screen_pos_.set_x((p1.x() + p2.x()) / 2); | |
sky
2011/08/26 15:45:33
Why does this need to be cached here instead of ca
varunjain
2011/08/26 16:31:15
It doesnt. Changed.
| |
376 context_menu_screen_pos_.set_y( | |
377 std::min(p1.y(), p2.y()) - kContextMenuVerticalOffset); | |
378 } else { | |
379 context_menu_screen_pos_.set_x( | |
380 client_view_->x() + client_view_->width() / 2); | |
381 context_menu_screen_pos_.set_y(client_view_->y()); | |
382 } | |
383 | |
384 View::ConvertPointToScreen(client_view_, &context_menu_screen_pos_); | |
385 | |
386 // If menu is already visible we hide it to be display after the timer fires. | |
387 if (context_menu_->IsVisible()) | |
388 context_menu_->SetVisible(false); | |
389 if (context_menu_timer_.IsRunning()) { | |
390 context_menu_timer_.Reset(); | |
391 } else { | |
392 context_menu_timer_.Start( | |
393 base::TimeDelta::FromMilliseconds(kContextMenuTimoutMs), | |
394 this, | |
395 &TouchSelectionControllerImpl::ContextMenuTimerFired); | |
396 } | |
397 } | |
398 | |
399 void TouchSelectionControllerImpl::HideContextMenu() { | |
400 context_menu_->SetVisible(false); | |
401 if (context_menu_timer_.IsRunning()) | |
sky
2011/08/26 15:45:33
No need to check if running.
varunjain
2011/08/26 16:31:15
Done.
| |
402 context_menu_timer_.Stop(); | |
403 } | |
404 | |
228 gfx::Point TouchSelectionControllerImpl::GetSelectionHandle1Position() { | 405 gfx::Point TouchSelectionControllerImpl::GetSelectionHandle1Position() { |
229 return selection_handle_1_->GetScreenPosition(); | 406 return selection_handle_1_->GetScreenPosition(); |
230 } | 407 } |
231 | 408 |
232 gfx::Point TouchSelectionControllerImpl::GetSelectionHandle2Position() { | 409 gfx::Point TouchSelectionControllerImpl::GetSelectionHandle2Position() { |
233 return selection_handle_2_->GetScreenPosition(); | 410 return selection_handle_2_->GetScreenPosition(); |
234 } | 411 } |
235 | 412 |
236 bool TouchSelectionControllerImpl::IsSelectionHandle1Visible() { | 413 bool TouchSelectionControllerImpl::IsSelectionHandle1Visible() { |
237 return selection_handle_1_->IsVisible(); | 414 return selection_handle_1_->IsVisible(); |
238 } | 415 } |
239 | 416 |
240 bool TouchSelectionControllerImpl::IsSelectionHandle2Visible() { | 417 bool TouchSelectionControllerImpl::IsSelectionHandle2Visible() { |
241 return selection_handle_2_->IsVisible(); | 418 return selection_handle_2_->IsVisible(); |
242 } | 419 } |
243 | 420 |
244 TouchSelectionController* TouchSelectionController::create( | 421 TouchSelectionController* TouchSelectionController::create( |
245 TouchSelectionClientView* client_view) { | 422 TouchSelectionClientView* client_view) { |
246 return new TouchSelectionControllerImpl(client_view); | 423 return new TouchSelectionControllerImpl(client_view); |
247 } | 424 } |
248 | 425 |
249 } // namespace views | 426 } // namespace views |
OLD | NEW |