| 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 RefreshButtonsAndSetWidgetPosition(position); |
| 197 } |
| 198 |
| 199 gfx::Point GetScreenPosition() { |
| 200 return widget_->GetClientAreaScreenBounds().origin(); |
| 201 } |
| 202 |
| 203 // ButtonListener |
| 204 virtual void ButtonPressed(Button* sender, const views::Event& event) { |
| 205 controller_->ExecuteCommand(sender->tag()); |
| 206 } |
| 207 |
| 208 private: |
| 209 // Queries the client view for what elements to show in the menu and sizes |
| 210 // the menu appropriately. |
| 211 void RefreshButtonsAndSetWidgetPosition(const gfx::Point& position) { |
| 212 RemoveAllChildViews(true); |
| 213 int total_width = 0; |
| 214 int height = 0; |
| 215 for (size_t i = 0; i < arraysize(kContextMenuCommands); i++) { |
| 216 int command_id = kContextMenuCommands[i]; |
| 217 if (controller_->IsCommandIdEnabled(command_id)) { |
| 218 TextButton* button = new TextButton(this, |
| 219 UTF16ToWide(l10n_util::GetStringUTF16(command_id))); |
| 220 button->set_focusable(true); |
| 221 button->set_request_focus_on_press(false); |
| 222 button->set_prefix_type(TextButton::PREFIX_HIDE); |
| 223 button->SetEnabledColor(SK_ColorWHITE); |
| 224 button->SetHoverColor(SK_ColorWHITE); |
| 225 button->set_background( |
| 226 Background::CreateSolidBackground(SK_ColorBLACK)); |
| 227 button->set_alignment(TextButton::ALIGN_CENTER); |
| 228 button->SetFont(ui::ResourceBundle::GetSharedInstance().GetFont( |
| 229 ui::ResourceBundle::LargeFont)); |
| 230 button->set_tag(command_id); |
| 231 AddChildView(button); |
| 232 gfx::Size button_size = button->GetPreferredSize(); |
| 233 total_width += button_size.width() + kContextMenuPadding; |
| 234 if (height < button_size.height()) |
| 235 height = button_size.height(); |
| 236 } |
| 237 } |
| 238 gfx::Rect widget_bounds(position.x() - total_width / 2, |
| 239 position.y() - height, |
| 240 total_width, |
| 241 height); |
| 242 gfx::Rect monitor_bounds = |
| 243 gfx::Screen::GetMonitorAreaNearestPoint(position); |
| 244 widget_->SetBounds(widget_bounds.AdjustToFit(monitor_bounds)); |
| 245 Layout(); |
| 246 } |
| 247 |
| 248 scoped_ptr<Widget> widget_; |
| 249 TouchSelectionControllerImpl* controller_; |
| 250 |
| 251 DISALLOW_COPY_AND_ASSIGN(TouchContextMenuView); |
| 252 }; |
| 253 |
| 144 TouchSelectionControllerImpl::TouchSelectionControllerImpl( | 254 TouchSelectionControllerImpl::TouchSelectionControllerImpl( |
| 145 TouchSelectionClientView* client_view) | 255 TouchSelectionClientView* client_view) |
| 146 : client_view_(client_view), | 256 : client_view_(client_view), |
| 147 selection_handle_1_(new SelectionHandleView(this)), | 257 selection_handle_1_(new SelectionHandleView(this)), |
| 148 selection_handle_2_(new SelectionHandleView(this)), | 258 selection_handle_2_(new SelectionHandleView(this)), |
| 259 context_menu_(new TouchContextMenuView(this)), |
| 149 dragging_handle_(NULL) { | 260 dragging_handle_(NULL) { |
| 150 } | 261 } |
| 151 | 262 |
| 152 TouchSelectionControllerImpl::~TouchSelectionControllerImpl() { | 263 TouchSelectionControllerImpl::~TouchSelectionControllerImpl() { |
| 153 } | 264 } |
| 154 | 265 |
| 155 void TouchSelectionControllerImpl::SelectionChanged(const gfx::Point& p1, | 266 void TouchSelectionControllerImpl::SelectionChanged(const gfx::Point& p1, |
| 156 const gfx::Point& p2) { | 267 const gfx::Point& p2) { |
| 157 gfx::Point screen_pos_1(p1); | 268 gfx::Point screen_pos_1(p1); |
| 158 View::ConvertPointToScreen(client_view_, &screen_pos_1); | 269 View::ConvertPointToScreen(client_view_, &screen_pos_1); |
| 159 gfx::Point screen_pos_2(p2); | 270 gfx::Point screen_pos_2(p2); |
| 160 View::ConvertPointToScreen(client_view_, &screen_pos_2); | 271 View::ConvertPointToScreen(client_view_, &screen_pos_2); |
| 161 | 272 |
| 162 if (dragging_handle_) { | 273 if (dragging_handle_) { |
| 163 // We need to reposition only the selection handle that is being dragged. | 274 // 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 | 275 // 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 | 276 // will always be at the end of selection, while the other handle will be at |
| 166 // the start. | 277 // the start. |
| 167 dragging_handle_->SetScreenPosition(screen_pos_2); | 278 dragging_handle_->SetScreenPosition(screen_pos_2); |
| 168 } else { | 279 } else { |
| 280 UpdateContextMenu(p1, p2); |
| 281 |
| 169 // Check if there is any selection at all. | 282 // Check if there is any selection at all. |
| 170 if (screen_pos_1 == screen_pos_2) { | 283 if (screen_pos_1 == screen_pos_2) { |
| 171 selection_handle_1_->SetVisible(false); | 284 selection_handle_1_->SetVisible(false); |
| 172 selection_handle_2_->SetVisible(false); | 285 selection_handle_2_->SetVisible(false); |
| 173 return; | 286 return; |
| 174 } | 287 } |
| 175 | 288 |
| 176 if (client_view_->bounds().Contains(p1)) { | 289 if (client_view_->bounds().Contains(p1)) { |
| 177 selection_handle_1_->SetScreenPosition(screen_pos_1); | 290 selection_handle_1_->SetScreenPosition(screen_pos_1); |
| 178 selection_handle_1_->SetVisible(true); | 291 selection_handle_1_->SetVisible(true); |
| 179 } else { | 292 } else { |
| 180 selection_handle_1_->SetVisible(false); | 293 selection_handle_1_->SetVisible(false); |
| 181 } | 294 } |
| 182 | 295 |
| 183 if (client_view_->bounds().Contains(p2)) { | 296 if (client_view_->bounds().Contains(p2)) { |
| 184 selection_handle_2_->SetScreenPosition(screen_pos_2); | 297 selection_handle_2_->SetScreenPosition(screen_pos_2); |
| 185 selection_handle_2_->SetVisible(true); | 298 selection_handle_2_->SetVisible(true); |
| 186 } else { | 299 } else { |
| 187 selection_handle_2_->SetVisible(false); | 300 selection_handle_2_->SetVisible(false); |
| 188 } | 301 } |
| 189 } | 302 } |
| 190 } | 303 } |
| 191 | 304 |
| 192 void TouchSelectionControllerImpl::ClientViewLostFocus() { | 305 void TouchSelectionControllerImpl::ClientViewLostFocus() { |
| 193 selection_handle_1_->SetVisible(false); | 306 selection_handle_1_->SetVisible(false); |
| 194 selection_handle_2_->SetVisible(false); | 307 selection_handle_2_->SetVisible(false); |
| 308 HideContextMenu(); |
| 195 } | 309 } |
| 196 | 310 |
| 197 void TouchSelectionControllerImpl::SelectionHandleDragged( | 311 void TouchSelectionControllerImpl::SelectionHandleDragged( |
| 198 const gfx::Point& drag_pos) { | 312 const gfx::Point& drag_pos) { |
| 313 // We do not want to show the context menu while dragging. |
| 314 HideContextMenu(); |
| 315 context_menu_timer_.Start( |
| 316 base::TimeDelta::FromMilliseconds(kContextMenuTimoutMs), |
| 317 this, |
| 318 &TouchSelectionControllerImpl::ContextMenuTimerFired); |
| 319 |
| 199 if (client_view_->GetWidget()) { | 320 if (client_view_->GetWidget()) { |
| 200 DCHECK(dragging_handle_); | 321 DCHECK(dragging_handle_); |
| 201 // Find the stationary selection handle. | 322 // Find the stationary selection handle. |
| 202 SelectionHandleView* fixed_handle = selection_handle_1_.get(); | 323 SelectionHandleView* fixed_handle = selection_handle_1_.get(); |
| 203 if (fixed_handle == dragging_handle_) | 324 if (fixed_handle == dragging_handle_) |
| 204 fixed_handle = selection_handle_2_.get(); | 325 fixed_handle = selection_handle_2_.get(); |
| 205 | 326 |
| 206 // Find selection end points in client_view's coordinate system. | 327 // Find selection end points in client_view's coordinate system. |
| 207 gfx::Point p1(drag_pos.x() + kSelectionHandleRadius, drag_pos.y()); | 328 gfx::Point p1(drag_pos.x() + kSelectionHandleRadius, drag_pos.y()); |
| 208 ConvertPointToClientView(dragging_handle_, &p1); | 329 ConvertPointToClientView(dragging_handle_, &p1); |
| 209 | 330 |
| 210 gfx::Point p2(kSelectionHandleRadius, 0); | 331 gfx::Point p2(kSelectionHandleRadius, 0); |
| 211 ConvertPointToClientView(fixed_handle, &p2); | 332 ConvertPointToClientView(fixed_handle, &p2); |
| 212 | 333 |
| 213 // Instruct client_view to select the region between p1 and p2. The position | 334 // 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 | 335 // of |fixed_handle| is the start and that of |dragging_handle| is the end |
| 215 // of selection. | 336 // of selection. |
| 216 client_view_->SelectRect(p2, p1); | 337 client_view_->SelectRect(p2, p1); |
| 217 } | 338 } |
| 218 } | 339 } |
| 219 | 340 |
| 220 void TouchSelectionControllerImpl::ConvertPointToClientView( | 341 void TouchSelectionControllerImpl::ConvertPointToClientView( |
| 221 SelectionHandleView* source, gfx::Point* point) { | 342 SelectionHandleView* source, gfx::Point* point) { |
| 222 View::ConvertPointToScreen(source, point); | 343 View::ConvertPointToScreen(source, point); |
| 223 gfx::Rect r = client_view_->GetWidget()->GetClientAreaScreenBounds(); | 344 gfx::Rect r = client_view_->GetWidget()->GetClientAreaScreenBounds(); |
| 224 point->SetPoint(point->x() - r.x(), point->y() - r.y()); | 345 point->SetPoint(point->x() - r.x(), point->y() - r.y()); |
| 225 View::ConvertPointFromWidget(client_view_, point); | 346 View::ConvertPointFromWidget(client_view_, point); |
| 226 } | 347 } |
| 227 | 348 |
| 349 bool TouchSelectionControllerImpl::IsCommandIdEnabled(int command_id) const { |
| 350 return client_view_->IsCommandIdEnabled(command_id); |
| 351 } |
| 352 |
| 353 void TouchSelectionControllerImpl::ExecuteCommand(int command_id) { |
| 354 HideContextMenu(); |
| 355 client_view_->ExecuteCommand(command_id); |
| 356 } |
| 357 |
| 358 void TouchSelectionControllerImpl::ContextMenuTimerFired() { |
| 359 // Get selection end points in client_view's space. |
| 360 gfx::Point p1(kSelectionHandleRadius, 0); |
| 361 ConvertPointToClientView(selection_handle_1_.get(), &p1); |
| 362 gfx::Point p2(kSelectionHandleRadius, 0); |
| 363 ConvertPointToClientView(selection_handle_2_.get(), &p2); |
| 364 |
| 365 // if selection is completely inside the view, we display the context menu |
| 366 // in the middle of the end points on the top. Else, we show the menu on the |
| 367 // top border of the view in the center. |
| 368 gfx::Point menu_pos; |
| 369 if (client_view_->bounds().Contains(p1) && |
| 370 client_view_->bounds().Contains(p2)) { |
| 371 menu_pos.set_x((p1.x() + p2.x()) / 2); |
| 372 menu_pos.set_y(std::min(p1.y(), p2.y()) - kContextMenuVerticalOffset); |
| 373 } else { |
| 374 menu_pos.set_x(client_view_->x() + client_view_->width() / 2); |
| 375 menu_pos.set_y(client_view_->y()); |
| 376 } |
| 377 |
| 378 View::ConvertPointToScreen(client_view_, &menu_pos); |
| 379 |
| 380 context_menu_->SetScreenPosition(menu_pos); |
| 381 context_menu_->SetVisible(true); |
| 382 } |
| 383 |
| 384 void TouchSelectionControllerImpl::UpdateContextMenu(const gfx::Point& p1, |
| 385 const gfx::Point& p2) { |
| 386 // Hide context menu to be shown when the timer fires. |
| 387 HideContextMenu(); |
| 388 |
| 389 // If there is selection, we restart the context menu timer. |
| 390 if (p1 != p2) { |
| 391 context_menu_timer_.Start( |
| 392 base::TimeDelta::FromMilliseconds(kContextMenuTimoutMs), |
| 393 this, |
| 394 &TouchSelectionControllerImpl::ContextMenuTimerFired); |
| 395 } |
| 396 } |
| 397 |
| 398 void TouchSelectionControllerImpl::HideContextMenu() { |
| 399 context_menu_->SetVisible(false); |
| 400 context_menu_timer_.Stop(); |
| 401 } |
| 402 |
| 228 gfx::Point TouchSelectionControllerImpl::GetSelectionHandle1Position() { | 403 gfx::Point TouchSelectionControllerImpl::GetSelectionHandle1Position() { |
| 229 return selection_handle_1_->GetScreenPosition(); | 404 return selection_handle_1_->GetScreenPosition(); |
| 230 } | 405 } |
| 231 | 406 |
| 232 gfx::Point TouchSelectionControllerImpl::GetSelectionHandle2Position() { | 407 gfx::Point TouchSelectionControllerImpl::GetSelectionHandle2Position() { |
| 233 return selection_handle_2_->GetScreenPosition(); | 408 return selection_handle_2_->GetScreenPosition(); |
| 234 } | 409 } |
| 235 | 410 |
| 236 bool TouchSelectionControllerImpl::IsSelectionHandle1Visible() { | 411 bool TouchSelectionControllerImpl::IsSelectionHandle1Visible() { |
| 237 return selection_handle_1_->IsVisible(); | 412 return selection_handle_1_->IsVisible(); |
| 238 } | 413 } |
| 239 | 414 |
| 240 bool TouchSelectionControllerImpl::IsSelectionHandle2Visible() { | 415 bool TouchSelectionControllerImpl::IsSelectionHandle2Visible() { |
| 241 return selection_handle_2_->IsVisible(); | 416 return selection_handle_2_->IsVisible(); |
| 242 } | 417 } |
| 243 | 418 |
| 244 TouchSelectionController* TouchSelectionController::create( | 419 TouchSelectionController* TouchSelectionController::create( |
| 245 TouchSelectionClientView* client_view) { | 420 TouchSelectionClientView* client_view) { |
| 246 return new TouchSelectionControllerImpl(client_view); | 421 return new TouchSelectionControllerImpl(client_view); |
| 247 } | 422 } |
| 248 | 423 |
| 249 } // namespace views | 424 } // namespace views |
| OLD | NEW |