]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - libs/gtkutil/cursor.cpp
macos: workaround the laggy XY/Camera window mouse pointer recentering
[xonotic/netradiant.git] / libs / gtkutil / cursor.cpp
1 /*
2    Copyright (C) 2001-2006, William Joseph.
3    All Rights Reserved.
4
5    This file is part of GtkRadiant.
6
7    GtkRadiant is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    GtkRadiant is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with GtkRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 #include "cursor.h"
23
24 #include "stream/textstream.h"
25
26 #include <string.h>
27 #include <gdk/gdk.h>
28 #include <gtk/gtk.h>
29
30 GdkCursor* create_blank_cursor(){
31         return gdk_cursor_new( GDK_BLANK_CURSOR );
32 }
33
34 void set_cursor( ui::Widget widget, GdkCursorType cursor_type ){
35         GdkCursor* cursor = gdk_cursor_new( cursor_type );
36         gdk_window_set_cursor( gtk_widget_get_window( widget ), cursor );
37         gdk_cursor_unref( cursor );
38 }
39
40 void blank_cursor( ui::Widget widget ){
41         set_cursor( widget, GDK_BLANK_CURSOR );
42 }
43
44 void default_cursor( ui::Widget widget ){
45         gdk_window_set_cursor( gtk_widget_get_window( widget ), NULL );
46 }
47
48 void Sys_GetCursorPos( ui::Widget widget, int *x, int *y ){
49         GdkDisplay *display = gtk_widget_get_display( GTK_WIDGET( widget ) );
50         // No need to store the screen, it will be recovered from widget again.
51         gdk_display_get_pointer( display, NULL, x, y, NULL );
52 }
53
54 void Sys_SetCursorPos( ui::Widget widget, int x, int y ){
55         GdkDisplay *display = gtk_widget_get_display( GTK_WIDGET( widget ) );
56         GdkScreen *screen = gtk_widget_get_screen( GTK_WIDGET( widget ) );
57         gdk_display_warp_pointer( display, screen, x, y );
58 }
59
60 gboolean DeferredMotion::gtk_motion(ui::Widget widget, GdkEventMotion *event, DeferredMotion *self)
61 {
62     self->motion( event->x, event->y, event->state );
63     return FALSE;
64 }
65
66 gboolean FreezePointer::motion_delta(ui::Widget widget, GdkEventMotion *event, FreezePointer *self)
67 {
68         /* FIXME: The pointer can be lost outside of the XY Window
69         or the Camera Window, see the comment in freeze_pointer function */
70         int current_x, current_y;
71         Sys_GetCursorPos( widget, &current_x, &current_y );
72         int dx = current_x - self->last_x;
73         int dy = current_y - self->last_y;
74         self->last_x = current_x;
75         self->last_y = current_y;
76         if ( dx != 0 || dy != 0 ) {
77                 //globalOutputStream() << "motion x: " << dx << ", y: " << dy << "\n";
78 #if defined(WORKAROUND_MACOS_GTK2_LAGGYPOINTER)
79                 ui::Dimensions dimensions = widget.dimensions();
80                 int window_x, window_y;
81                 int translated_x, translated_y;
82
83                 gdk_window_get_origin( gtk_widget_get_window( widget ), &window_x, &window_y);
84
85
86                 translated_x = current_x - ( window_x );
87                 translated_y = current_y - ( window_y );
88
89 #if 0
90                 int widget_x, widget_y;
91                 gtk_widget_translate_coordinates( GTK_WIDGET( widget ), gtk_widget_get_toplevel( GTK_WIDGET( widget ) ), 0, 0, &widget_x, &widget_y);
92
93                 globalOutputStream()
94                         << "window_x: " << window_x
95                         << ", window_y: " << window_y
96                         << ", widget_x: " << widget_x
97                         << ", widget_y: " << widget_y
98                         << ", current x: " << current_x
99                         << ", current_y: " << current_y
100                         << ", translated x: " << translated_x
101                         << ", translated_y: " << translated_y
102                         << ", width: " << dimensions.width
103                         << ", height: " << dimensions.height
104                         << "\n";
105 #endif
106
107                 if ( translated_x < 32 || translated_x > dimensions.width - 32
108                         || translated_y < 32 || translated_y > dimensions.height - 32 ) {
109 #if 0
110                         // Reposition the pointer to the widget center.
111                         int reposition_x = window_x + dimensions.width / 2;
112                         int reposition_y = window_y + dimensions.height / 2;
113 #else
114                         // Move the pointer to the opposite side of the XY Window
115                         // to maximize the distance that can be moved again.
116                         int reposition_x = current_x;
117                         int reposition_y = current_y;
118
119                         if ( translated_x < 32 ) {
120                                 reposition_x = window_x + dimensions.width - 32;
121                         }
122                         else if ( translated_x > dimensions.width - 32 ) {
123                                 reposition_x = window_x + 32;
124                         }
125
126                         if ( translated_y < 32 ) {
127                                 reposition_y = window_y + dimensions.height - 32;
128                         }
129                         else if ( translated_y > dimensions.height - 32 ) {
130                                 reposition_y = window_y + 32;
131                         }
132 #endif
133
134                         Sys_SetCursorPos( widget, reposition_x, reposition_y );
135                         self->last_x = reposition_x;
136                         self->last_y = reposition_y;
137                 }
138 #else
139                 int ddx = current_x - self->recorded_x;
140                 int ddy = current_y - self->recorded_y;
141                 if (ddx < -32 || ddx > 32 || ddy < -32 || ddy > 32) {
142                         Sys_SetCursorPos( widget, self->recorded_x, self->recorded_y );
143                         self->last_x = self->recorded_x;
144                         self->last_y = self->recorded_y;
145                 }
146 #endif
147                 self->m_function( dx, dy, event->state, self->m_data );
148         }
149         return FALSE;
150 }
151
152 void FreezePointer::freeze_pointer(ui::Widget widget, FreezePointer::MotionDeltaFunction function, void *data)
153 {
154         /* FIXME: This bug can happen if the pointer goes outside of the
155         XY Window while the right mouse button is not released,
156         the XY Window loses focus and can't read the right mouse button
157         release event and then cannot unfreeze the pointer, meaning the
158         user can attempt to freeze the pointer in another XY window.
159
160         This can happen with touch screen, especially those used to drive
161         virtual machine pointers, the cursor can be teleported outside of
162         the XY Window while maintaining pressure on the right mouse button.
163         This can also happen when the render is slow.
164
165         The bug also occurs with the Camera Window.
166
167         FIXME: It's would be possible to tell the user to save the map
168         at assert time before crashing because this bug does not corrupt
169         map saving. */
170         ASSERT_MESSAGE( m_function == 0, "can't freeze pointer" );
171
172         const GdkEventMask mask = static_cast<GdkEventMask>( GDK_POINTER_MOTION_MASK
173                                                                                                                  | GDK_POINTER_MOTION_HINT_MASK
174                                                                                                                  | GDK_BUTTON_MOTION_MASK
175                                                                                                                  | GDK_BUTTON1_MOTION_MASK
176                                                                                                                  | GDK_BUTTON2_MOTION_MASK
177                                                                                                                  | GDK_BUTTON3_MOTION_MASK
178                                                                                                                  | GDK_BUTTON_PRESS_MASK
179                                                                                                                  | GDK_BUTTON_RELEASE_MASK
180                                                                                                                  | GDK_VISIBILITY_NOTIFY_MASK );
181
182 #if defined(WORKAROUND_MACOS_GTK2_LAGGYPOINTER)
183         /* Keep the pointer visible during the move operation.
184         Because of a bug, it remains visible even if we give
185         the order to hide it anyway.
186         Other parts of the code assume the pointer is visible,
187         so make sure it is consistently visible accross
188         third-party updates that may fix the mouse pointer
189         visibility issue. */
190 #else
191         GdkCursor* cursor = create_blank_cursor();
192         //GdkGrabStatus status =
193         gdk_pointer_grab( gtk_widget_get_window( widget ), TRUE, mask, 0, cursor, GDK_CURRENT_TIME );
194         gdk_cursor_unref( cursor );
195 #endif
196
197         Sys_GetCursorPos( widget, &recorded_x, &recorded_y );
198
199         Sys_SetCursorPos( widget, recorded_x, recorded_y );
200
201         last_x = recorded_x;
202         last_y = recorded_y;
203
204         m_function = function;
205         m_data = data;
206
207         handle_motion = widget.connect( "motion_notify_event", G_CALLBACK( motion_delta ), this );
208 }
209
210 void FreezePointer::unfreeze_pointer(ui::Widget widget)
211 {
212         g_signal_handler_disconnect( G_OBJECT( widget ), handle_motion );
213
214         m_function = 0;
215         m_data = 0;
216
217 #if defined(WORKAROUND_MACOS_GTK2_LAGGYPOINTER)
218         /* The pointer was visible during all the move operation,
219         so, keep the current position. */
220 #else
221         Sys_SetCursorPos( widget, recorded_x, recorded_y );
222 #endif
223
224         gdk_pointer_ungrab( GDK_CURRENT_TIME );
225 }