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.
5 #include "chrome/browser/ui/gtk/panels/panel_drag_gtk.h"
7 #include <gdk/gdkkeysyms.h>
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"
16 panel::ResizingSides
GdkWindowEdgeToResizingSide(GdkWindowEdge 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
;
35 return panel::RESIZE_NONE
;
41 // Virtual base class to abstract move vs resize drag logic.
42 class PanelDragDelegate
{
44 explicit PanelDragDelegate(Panel
* panel
) : panel_(panel
) {}
45 virtual ~PanelDragDelegate() {}
47 Panel
* panel() const { return panel_
; }
48 PanelManager
* panel_manager() const { return panel_
->manager(); }
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;
54 // |canceled| is true to abort the drag.
55 virtual void DragEnded(bool canceled
) = 0;
58 Panel
* panel_
; // Weak pointer to the panel being dragged.
60 DISALLOW_COPY_AND_ASSIGN(PanelDragDelegate
);
63 // Delegate for moving a panel by dragging the mouse.
64 class MoveDragDelegate
: public PanelDragDelegate
{
66 explicit MoveDragDelegate(Panel
* panel
)
67 : PanelDragDelegate(panel
) {}
68 virtual ~MoveDragDelegate() {}
70 virtual void DragStarted(gfx::Point point
) OVERRIDE
{
71 panel_manager()->StartDragging(panel(), point
);
73 virtual void Dragged(gfx::Point point
) OVERRIDE
{
74 panel_manager()->Drag(point
);
76 virtual void DragEnded(bool canceled
) OVERRIDE
{
77 panel_manager()->EndDragging(canceled
);
80 DISALLOW_COPY_AND_ASSIGN(MoveDragDelegate
);
83 // Delegate for resizing a panel by dragging the mouse.
84 class ResizeDragDelegate
: public PanelDragDelegate
{
86 ResizeDragDelegate(Panel
* panel
, GdkWindowEdge edge
)
87 : PanelDragDelegate(panel
),
88 resizing_side_(GdkWindowEdgeToResizingSide(edge
)) {}
89 virtual ~ResizeDragDelegate() {}
91 virtual void DragStarted(gfx::Point point
) OVERRIDE
{
92 panel_manager()->StartResizingByMouse(panel(), point
, resizing_side_
);
94 virtual void Dragged(gfx::Point point
) OVERRIDE
{
95 panel_manager()->ResizeByMouse(point
);
97 virtual void DragEnded(bool canceled
) OVERRIDE
{
98 panel_manager()->EndResizingByMouse(canceled
);
101 // The edge from which the panel is being resized.
102 panel::ResizingSides resizing_side_
;
104 DISALLOW_COPY_AND_ASSIGN(ResizeDragDelegate
);
107 // Panel drag helper for processing mouse and keyboard events while
108 // the left mouse button is pressed.
109 PanelDragGtk::PanelDragGtk(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
);
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);
134 PanelDragGtk::~PanelDragGtk() {
135 EndDrag(true); // Clean up drag state.
136 ReleasePointerAndKeyboardGrab();
139 void PanelDragGtk::AssertCleanState() {
140 DCHECK_EQ(NOT_DRAGGING
, drag_state_
);
141 DCHECK(!drag_delegate_
);
142 DCHECK(!initial_mouse_down_
);
143 DCHECK(!click_handler_
);
146 void PanelDragGtk::InitialWindowEdgeMousePress(GdkEventButton
* event
,
148 GdkWindowEdge
& edge
) {
150 drag_delegate_
= new ResizeDragDelegate(panel_
, edge
);
151 drag_state_
= DRAG_CAN_START
;
152 GrabPointerAndKeyboard(event
, cursor
);
155 void PanelDragGtk::InitialTitlebarMousePress(GdkEventButton
* event
,
156 GtkWidget
* titlebar_widget
) {
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.
164 void PanelDragGtk::GrabPointerAndKeyboard(GdkEventButton
* event
,
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
));
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_
);
174 GdkGrabStatus pointer_grab_status
=
175 gdk_pointer_grab(gdk_window
,
177 GdkEventMask(GDK_BUTTON_PRESS_MASK
|
178 GDK_BUTTON_RELEASE_MASK
|
179 GDK_POINTER_MOTION_MASK
),
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
<< ")";
193 ReleasePointerAndKeyboardGrab();
197 gtk_grab_add(drag_widget_
);
200 void PanelDragGtk::ReleasePointerAndKeyboardGrab() {
201 DCHECK(!drag_delegate_
);
202 if (drag_state_
== NOT_DRAGGING
)
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.
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_
);
219 DCHECK(drag_delegate_
);
221 if (initial_mouse_down_
) {
222 gdk_event_free(initial_mouse_down_
);
223 initial_mouse_down_
= NULL
;
226 if (drag_state_
== DRAG_IN_PROGRESS
) {
227 drag_delegate_
->DragEnded(canceled
);
229 drag_state_
= DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE
;
231 delete drag_delegate_
;
232 drag_delegate_
= NULL
;
234 click_handler_
= NULL
;
237 gboolean
PanelDragGtk::OnMouseMoveEvent(GtkWidget
* widget
,
238 GdkEventMotion
* event
) {
239 DCHECK(drag_state_
!= NOT_DRAGGING
);
241 if (drag_state_
== DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE
) {
242 DCHECK(!drag_delegate_
);
246 DCHECK(drag_delegate_
);
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
);
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
);
265 if (gtk_drag_check_threshold(drag_widget_
, old_x
, old_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
;
274 if (drag_state_
== DRAG_IN_PROGRESS
)
275 drag_delegate_
->Dragged(gfx::Point(new_x
, new_y
));
280 gboolean
PanelDragGtk::OnButtonPressEvent(GtkWidget
* widget
,
281 GdkEventButton
* event
) {
282 DCHECK(drag_state_
!= NOT_DRAGGING
);
286 gboolean
PanelDragGtk::OnButtonReleaseEvent(GtkWidget
* widget
,
287 GdkEventButton
* event
) {
288 DCHECK(drag_state_
!= NOT_DRAGGING
);
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
));
296 // Cleanup state regardless.
298 ReleasePointerAndKeyboardGrab();
304 gboolean
PanelDragGtk::OnKeyPressEvent(GtkWidget
* widget
,
305 GdkEventKey
* event
) {
306 DCHECK(drag_state_
!= NOT_DRAGGING
);
310 gboolean
PanelDragGtk::OnKeyReleaseEvent(GtkWidget
* widget
,
311 GdkEventKey
* event
) {
312 DCHECK(drag_state_
!= NOT_DRAGGING
);
314 if (drag_state_
== DRAG_ENDED_WAITING_FOR_MOUSE_RELEASE
) {
315 DCHECK(!drag_delegate_
);
319 DCHECK(drag_delegate_
);
321 switch (event
->keyval
) {
323 EndDrag(true); // Cancel drag.
329 EndDrag(false); // Normal end.
335 gboolean
PanelDragGtk::OnGrabBrokenEvent(GtkWidget
* widget
,
336 GdkEventGrabBroken
* event
) {
337 DCHECK(drag_state_
!= NOT_DRAGGING
);
338 EndDrag(true); // Cancel drag.
339 ReleasePointerAndKeyboardGrab();