1 // Copyright (c) 2012 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 "chrome/browser/ui/gtk/panels/panel_drag_gtk.h" 6 7 #include <gdk/gdkkeysyms.h> 8 9 #include "chrome/browser/ui/panels/panel.h" 10 #include "chrome/browser/ui/panels/panel_constants.h" 11 #include "chrome/browser/ui/panels/panel_manager.h" 12 #include "ui/gfx/gtk_util.h" 13 14 namespace { 15 16 panel::ResizingSides GdkWindowEdgeToResizingSide(GdkWindowEdge edge) { 17 switch (edge) { 18 case GDK_WINDOW_EDGE_NORTH_WEST: 19 return panel::RESIZE_TOP_LEFT; 20 case GDK_WINDOW_EDGE_NORTH: 21 return panel::RESIZE_TOP; 22 case GDK_WINDOW_EDGE_NORTH_EAST: 23 return panel::RESIZE_TOP_RIGHT; 24 case GDK_WINDOW_EDGE_WEST: 25 return panel::RESIZE_LEFT; 26 case GDK_WINDOW_EDGE_EAST: 27 return panel::RESIZE_RIGHT; 28 case GDK_WINDOW_EDGE_SOUTH_WEST: 29 return panel::RESIZE_BOTTOM_LEFT; 30 case GDK_WINDOW_EDGE_SOUTH: 31 return panel::RESIZE_BOTTOM; 32 case GDK_WINDOW_EDGE_SOUTH_EAST: 33 return panel::RESIZE_BOTTOM_RIGHT; 34 default: 35 return panel::RESIZE_NONE; 36 } 37 } 38 39 } // namespace 40 41 // Virtual base class to abstract move vs resize drag logic. 42 class PanelDragDelegate { 43 public: 44 explicit PanelDragDelegate(Panel* panel) : panel_(panel) {} 45 virtual ~PanelDragDelegate() {} 46 47 Panel* panel() const { return panel_; } 48 PanelManager* panel_manager() const { return panel_->manager(); } 49 50 // |point| is the mouse location in screen coordinates. 51 virtual void DragStarted(gfx::Point point) = 0; 52 virtual void Dragged(gfx::Point point) = 0; 53 54 // |canceled| is true to abort the drag. 55 virtual void DragEnded(bool canceled) = 0; 56 57 private: 58 Panel* panel_; // Weak pointer to the panel being dragged. 59 60 DISALLOW_COPY_AND_ASSIGN(PanelDragDelegate); 61 }; 62 63 // Delegate for moving a panel by dragging the mouse. 64 class MoveDragDelegate : public PanelDragDelegate { 65 public: 66 explicit MoveDragDelegate(Panel* panel) 67 : PanelDragDelegate(panel) {} 68 virtual ~MoveDragDelegate() {} 69 70 virtual void DragStarted(gfx::Point point) OVERRIDE { 71 panel_manager()->StartDragging(panel(), point); 72 } 73 virtual void Dragged(gfx::Point point) OVERRIDE { 74 panel_manager()->Drag(point); 75 } 76 virtual void DragEnded(bool canceled) OVERRIDE { 77 panel_manager()->EndDragging(canceled); 78 } 79 80 DISALLOW_COPY_AND_ASSIGN(MoveDragDelegate); 81 }; 82 83 // Delegate for resizing a panel by dragging the mouse. 84 class ResizeDragDelegate : public PanelDragDelegate { 85 public: 86 ResizeDragDelegate(Panel* panel, GdkWindowEdge edge) 87 : PanelDragDelegate(panel), 88 resizing_side_(GdkWindowEdgeToResizingSide(edge)) {} 89 virtual ~ResizeDragDelegate() {} 90 91 virtual void DragStarted(gfx::Point point) OVERRIDE { 92 panel_manager()->StartResizingByMouse(panel(), point, resizing_side_); 93 } 94 virtual void Dragged(gfx::Point point) OVERRIDE { 95 panel_manager()->ResizeByMouse(point); 96 } 97 virtual void DragEnded(bool canceled) OVERRIDE { 98 panel_manager()->EndResizingByMouse(canceled); 99 } 100 private: 101 // The edge from which the panel is being resized. 102 panel::ResizingSides resizing_side_; 103 104 DISALLOW_COPY_AND_ASSIGN(ResizeDragDelegate); 105 }; 106 107 // Panel drag helper for processing mouse and keyboard events while 108 // the left mouse button is pressed. 109 PanelDragGtk::PanelDragGtk(Panel* panel) 110 : panel_(panel), 111 drag_state_(NOT_DRAGGING), 112 initial_mouse_down_(NULL), 113 click_handler_(NULL), 114 drag_delegate_(NULL) { 115 // Create an invisible event box to receive mouse and key events. 116 drag_widget_ = gtk_event_box_new(); 117 gtk_event_box_set_visible_window(GTK_EVENT_BOX(drag_widget_), FALSE); 118 119 // Connect signals for events during a drag. 120 g_signal_connect(drag_widget_, "motion-notify-event", 121 G_CALLBACK(OnMouseMoveEventThunk), this); 122 g_signal_connect(drag_widget_, "key-press-event", 123 G_CALLBACK(OnKeyPressEventThunk), this); 124 g_signal_connect(drag_widget_, "key-release-event", 125 G_CALLBACK(OnKeyReleaseEventThunk), this); 126 g_signal_connect(drag_widget_, "button-press-event", 127 G_CALLBACK(OnButtonPressEventThunk), this); 128 g_signal_connect(drag_widget_, "button-release-event", 129 G_CALLBACK(OnButtonReleaseEventThunk), this); 130 g_signal_connect(drag_widget_, "grab-broken-event", 131 G_CALLBACK(OnGrabBrokenEventThunk), this); 132 } 133 134 PanelDragGtk::~PanelDragGtk() { 135 EndDrag(true); // Clean up drag state. 136 ReleasePointerAndKeyboardGrab(); 137 } 138 139 void PanelDragGtk::AssertCleanState() { 140 DCHECK_EQ(NOT_DRAGGING, drag_state_); 141 DCHECK(!drag_delegate_); 142 DCHECK(!initial_mouse_down_); 143 DCHECK(!click_handler_); 144 } 145 146 void PanelDragGtk::InitialWindowEdgeMousePress(GdkEventButton* event, 147 GdkCursor* cursor, 148 GdkWindowEdge& edge) { 149 AssertCleanState(); 150 drag_delegate_ = new ResizeDragDelegate(panel_, edge); 151 drag_state_ = DRAG_CAN_START; 152 GrabPointerAndKeyboard(event, cursor); 153 } 154 155 void PanelDragGtk::InitialTitlebarMousePress(GdkEventButton* event, 156 GtkWidget* titlebar_widget) { 157 AssertCleanState(); 158 click_handler_ = titlebar_widget; 159 drag_delegate_ = new MoveDragDelegate(panel_); 160 drag_state_ = DRAG_CAN_START; 161 GrabPointerAndKeyboard(event, gfx::GetCursor(GDK_FLEUR)); // Drag cursor. 162 } 163 164 void PanelDragGtk::GrabPointerAndKeyboard(GdkEventButton* event, 165 GdkCursor* cursor) { 166 // Remember initial mouse event for use in determining when drag 167 // threshold has been exceeded. 168 initial_mouse_down_ = gdk_event_copy(reinterpret_cast<GdkEvent*>(event)); 169 170 // Grab pointer and keyboard to make sure we have the focus and get 171 // all mouse and keyboard events during the drag. 172 GdkWindow* gdk_window = gtk_widget_get_window(drag_widget_); 173 DCHECK(gdk_window); 174 GdkGrabStatus pointer_grab_status = 175 gdk_pointer_grab(gdk_window, 176 TRUE, 177 GdkEventMask(GDK_BUTTON_PRESS_MASK | 178 GDK_BUTTON_RELEASE_MASK | 179 GDK_POINTER_MOTION_MASK), 180 NULL, 181 cursor, 182 event->time); 183 GdkGrabStatus keyboard_grab_status = 184 gdk_keyboard_grab(gdk_window, TRUE, event->time); 185 if (pointer_grab_status != GDK_GRAB_SUCCESS || 186 keyboard_grab_status != GDK_GRAB_SUCCESS) { 187 // Grab could fail if someone else already has the pointer/keyboard 188 // grabbed. Cancel the drag. 189 DLOG(ERROR) << "Unable to grab pointer or keyboard (pointer_status=" 190 << pointer_grab_status << ", keyboard_status=" 191 << keyboard_grab_status << ")"; 192 EndDrag(true); 193 ReleasePointerAndKeyboardGrab(); 194 return; 195 } 196 197 gtk_grab_add(drag_widget_); 198 } 199 200 void PanelDragGtk::ReleasePointerAndKeyboardGrab() { 201 DCHECK(!drag_delegate_); 202 if (drag_state_ == NOT_DRAGGING) 203 return; 204 205 DCHECK_EQ(DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE, drag_state_); 206 gdk_pointer_ungrab(GDK_CURRENT_TIME); 207 gdk_keyboard_ungrab(GDK_CURRENT_TIME); 208 gtk_grab_remove(drag_widget_); 209 drag_state_ = NOT_DRAGGING; // Drag is truly over now. 210 } 211 212 void PanelDragGtk::EndDrag(bool canceled) { 213 if (drag_state_ == NOT_DRAGGING || 214 drag_state_ == DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE) { 215 DCHECK(!drag_delegate_); 216 return; 217 } 218 219 DCHECK(drag_delegate_); 220 221 if (initial_mouse_down_) { 222 gdk_event_free(initial_mouse_down_); 223 initial_mouse_down_ = NULL; 224 } 225 226 if (drag_state_ == DRAG_IN_PROGRESS) { 227 drag_delegate_->DragEnded(canceled); 228 } 229 drag_state_ = DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE; 230 231 delete drag_delegate_; 232 drag_delegate_ = NULL; 233 234 click_handler_ = NULL; 235 } 236 237 gboolean PanelDragGtk::OnMouseMoveEvent(GtkWidget* widget, 238 GdkEventMotion* event) { 239 DCHECK(drag_state_ != NOT_DRAGGING); 240 241 if (drag_state_ == DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE) { 242 DCHECK(!drag_delegate_); 243 return TRUE; 244 } 245 246 DCHECK(drag_delegate_); 247 248 gdouble new_x_double; 249 gdouble new_y_double; 250 gdk_event_get_root_coords(reinterpret_cast<GdkEvent*>(event), 251 &new_x_double, &new_y_double); 252 gint new_x = static_cast<gint>(new_x_double); 253 gint new_y = static_cast<gint>(new_y_double); 254 255 // Begin dragging only after mouse has moved beyond the drag threshold. 256 if (drag_state_ == DRAG_CAN_START) { 257 DCHECK(initial_mouse_down_); 258 gdouble old_x_double; 259 gdouble old_y_double; 260 gdk_event_get_root_coords(initial_mouse_down_, 261 &old_x_double, &old_y_double); 262 gint old_x = static_cast<gint>(old_x_double); 263 gint old_y = static_cast<gint>(old_y_double); 264 265 if (gtk_drag_check_threshold(drag_widget_, old_x, old_y, 266 new_x, new_y)) { 267 drag_state_ = DRAG_IN_PROGRESS; 268 drag_delegate_->DragStarted(gfx::Point(old_x, old_y)); 269 gdk_event_free(initial_mouse_down_); 270 initial_mouse_down_ = NULL; 271 } 272 } 273 274 if (drag_state_ == DRAG_IN_PROGRESS) 275 drag_delegate_->Dragged(gfx::Point(new_x, new_y)); 276 277 return TRUE; 278 } 279 280 gboolean PanelDragGtk::OnButtonPressEvent(GtkWidget* widget, 281 GdkEventButton* event) { 282 DCHECK(drag_state_ != NOT_DRAGGING); 283 return TRUE; 284 } 285 286 gboolean PanelDragGtk::OnButtonReleaseEvent(GtkWidget* widget, 287 GdkEventButton* event) { 288 DCHECK(drag_state_ != NOT_DRAGGING); 289 290 if (event->button == 1) { 291 // Treat release as a mouse click if drag was never started. 292 if (drag_state_ == DRAG_CAN_START && click_handler_) { 293 gtk_propagate_event(click_handler_, 294 reinterpret_cast<GdkEvent*>(event)); 295 } 296 // Cleanup state regardless. 297 EndDrag(false); 298 ReleasePointerAndKeyboardGrab(); 299 } 300 301 return TRUE; 302 } 303 304 gboolean PanelDragGtk::OnKeyPressEvent(GtkWidget* widget, 305 GdkEventKey* event) { 306 DCHECK(drag_state_ != NOT_DRAGGING); 307 return TRUE; 308 } 309 310 gboolean PanelDragGtk::OnKeyReleaseEvent(GtkWidget* widget, 311 GdkEventKey* event) { 312 DCHECK(drag_state_ != NOT_DRAGGING); 313 314 if (drag_state_ == DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE) { 315 DCHECK(!drag_delegate_); 316 return TRUE; 317 } 318 319 DCHECK(drag_delegate_); 320 321 switch (event->keyval) { 322 case GDK_Escape: 323 EndDrag(true); // Cancel drag. 324 break; 325 case GDK_Return: 326 case GDK_KP_Enter: 327 case GDK_ISO_Enter: 328 case GDK_space: 329 EndDrag(false); // Normal end. 330 break; 331 } 332 return TRUE; 333 } 334 335 gboolean PanelDragGtk::OnGrabBrokenEvent(GtkWidget* widget, 336 GdkEventGrabBroken* event) { 337 DCHECK(drag_state_ != NOT_DRAGGING); 338 EndDrag(true); // Cancel drag. 339 ReleasePointerAndKeyboardGrab(); 340 return TRUE; 341 } 342