]> git.xonotic.org Git - xonotic/netradiant.git/blob - libs/gtkutil/cursor.cpp
Merge commit 'c5a6237a2b002c9811719172931b0c9cc5a725f4' into master-merge
[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 // Note: NetRadiantCustom disables them but we still make use of them.
31 #if 1
32 /* Note: here is an alternative implementation,
33 it may be useful to try it on platforms were
34         gdk_cursor_new(GDK_BLANK_CURSOR)
35 does not work:
36
37 GdkCursor* create_blank_cursor(){
38         GdkPixmap *pixmap;
39         GdkBitmap *mask;
40         char buffer [( 32 * 32 ) / 8];
41         memset( buffer, 0, ( 32 * 32 ) / 8 );
42         GdkColor white = {0, 0xffff, 0xffff, 0xffff};
43         GdkColor black = {0, 0x0000, 0x0000, 0x0000};
44         pixmap = gdk_bitmap_create_from_data( 0, buffer, 32, 32 );
45         mask   = gdk_bitmap_create_from_data( 0, buffer, 32, 32 );
46         GdkCursor *cursor = gdk_cursor_new_from_pixmap( pixmap, mask, &white, &black, 1, 1 );
47         gdk_drawable_unref( pixmap );
48         gdk_drawable_unref( mask );
49
50         return cursor;
51 }
52 */
53 GdkCursor* create_blank_cursor(){
54         return gdk_cursor_new(GDK_BLANK_CURSOR);
55 }
56
57 void set_cursor( ui::Widget widget, GdkCursorType cursor_type ){
58         GdkCursor* cursor = gdk_cursor_new( cursor_type );
59         gdk_window_set_cursor( gtk_widget_get_window(widget), cursor );
60         gdk_cursor_unref( cursor );
61 }
62
63 void blank_cursor( ui::Widget widget ){
64         set_cursor( widget, GDK_BLANK_CURSOR );
65 }
66
67 void default_cursor( ui::Widget widget ){
68         gdk_window_set_cursor( gtk_widget_get_window( widget ), NULL );
69 }
70 #endif
71
72 void Sys_GetCursorPos( ui::Widget widget, int *x, int *y ){
73         GdkDisplay *display = gtk_widget_get_display( GTK_WIDGET( widget ) );
74         // No need to store the screen, it will be recovered from widget again.
75         gdk_display_get_pointer( display, NULL, x, y, NULL );
76 }
77
78 void Sys_SetCursorPos( ui::Widget widget, int x, int y ){
79         GdkDisplay *display = gtk_widget_get_display( GTK_WIDGET( widget ) );
80         GdkScreen *screen = gtk_widget_get_screen( GTK_WIDGET( widget ) );
81         gdk_display_warp_pointer( display, screen, x, y );
82 }
83
84 gboolean DeferredMotion::gtk_motion(ui::Widget widget, GdkEventMotion *event, DeferredMotion *self)
85 {
86     self->motion( event->x, event->y, event->state );
87     return FALSE;
88 }
89
90 gboolean FreezePointer::motion_delta(ui::Widget widget, GdkEventMotion *event, FreezePointer *self)
91 {
92         /* FIXME: The pointer can be lost outside of the XY Window
93         or the Camera Window, see the comment in freeze_pointer function */
94         int current_x, current_y;
95         Sys_GetCursorPos( widget, &current_x, &current_y );
96         int dx = current_x - self->last_x;
97         int dy = current_y - self->last_y;
98 #if 0 // NetRadiantCustom
99         int ddx = current_x - self->center_x;
100         int ddy = current_y - self->center_y;
101 #else
102         int ddx = current_x - self->recorded_x;
103         int ddy = current_y - self->recorded_y;
104 #endif
105         self->last_x = current_x;
106         self->last_y = current_y;
107         if ( dx != 0 || dy != 0 ) {
108                 //globalOutputStream() << "motion x: " << dx << ", y: " << dy << "\n";
109 #if defined(WORKAROUND_MACOS_GTK2_LAGGYPOINTER)
110                 ui::Dimensions dimensions = widget.dimensions();
111                 int window_x, window_y;
112                 int translated_x, translated_y;
113
114                 gdk_window_get_origin( gtk_widget_get_window( widget ), &window_x, &window_y);
115
116                 translated_x = current_x - ( window_x );
117                 translated_y = current_y - ( window_y );
118
119 #if 0
120                 int widget_x, widget_y;
121                 gtk_widget_translate_coordinates( GTK_WIDGET( widget ), gtk_widget_get_toplevel( GTK_WIDGET( widget ) ), 0, 0, &widget_x, &widget_y);
122
123                 globalOutputStream()
124                         << "window_x: " << window_x
125                         << ", window_y: " << window_y
126                         << ", widget_x: " << widget_x
127                         << ", widget_y: " << widget_y
128                         << ", current x: " << current_x
129                         << ", current_y: " << current_y
130                         << ", translated x: " << translated_x
131                         << ", translated_y: " << translated_y
132                         << ", width: " << dimensions.width
133                         << ", height: " << dimensions.height
134                         << "\n";
135 #endif
136
137                 if ( translated_x < 32 || translated_x > dimensions.width - 32
138                         || translated_y < 32 || translated_y > dimensions.height - 32 ) {
139 #if 0
140                         // Reposition the pointer to the widget center.
141                         int reposition_x = window_x + dimensions.width / 2;
142                         int reposition_y = window_y + dimensions.height / 2;
143 #else
144                         // Move the pointer to the opposite side of the XY Window
145                         // to maximize the distance that can be moved again.
146                         int reposition_x = current_x;
147                         int reposition_y = current_y;
148
149                         if ( translated_x < 32 ) {
150                                 reposition_x = window_x + dimensions.width - 32;
151                         }
152                         else if ( translated_x > dimensions.width - 32 ) {
153                                 reposition_x = window_x + 32;
154                         }
155
156                         if ( translated_y < 32 ) {
157                                 reposition_y = window_y + dimensions.height - 32;
158                         }
159                         else if ( translated_y > dimensions.height - 32 ) {
160                                 reposition_y = window_y + 32;
161                         }
162 #endif
163
164                         Sys_SetCursorPos( widget, reposition_x, reposition_y );
165                         self->last_x = reposition_x;
166                         self->last_y = reposition_y;
167                 }
168 #else
169                 if (ddx < -32 || ddx > 32 || ddy < -32 || ddy > 32) {
170 #if 0 // NetRadiantCustom
171                         Sys_SetCursorPos( widget, self->center_x, self->center_y );
172                         self->last_x = self->center_x;
173                         self->last_y = self->center_y;
174 #else
175                         Sys_SetCursorPos( widget, self->recorded_x, self->recorded_y );
176                         self->last_x = self->recorded_x;
177                         self->last_y = self->recorded_y;
178 #endif
179                 }
180 #endif
181                 self->m_function( dx, dy, event->state, self->m_data );
182         }
183         return FALSE;
184 }
185
186 /* NetRadiantCustom did this instead:
187 void FreezePointer::freeze_pointer(ui::Window window, ui::Widget widget, FreezePointer::MotionDeltaFunction function, void *data) */
188 void FreezePointer::freeze_pointer(ui::Widget widget, FreezePointer::MotionDeltaFunction function, void *data)
189 {
190         /* FIXME: This bug can happen if the pointer goes outside of the
191         XY Window while the right mouse button is not released,
192         the XY Window loses focus and can't read the right mouse button
193         release event and then cannot unfreeze the pointer, meaning the
194         user can attempt to freeze the pointer in another XY window.
195
196         This can happen with touch screen, especially those used to drive
197         virtual machine pointers, the cursor can be teleported outside of
198         the XY Window while maintaining pressure on the right mouse button.
199         This can also happen when the render is slow.
200
201         The bug also occurs with the Camera Window.
202
203         FIXME: It's would be possible to tell the user to save the map
204         at assert time before crashing because this bug does not corrupt
205         map saving. */
206         ASSERT_MESSAGE( m_function == 0, "can't freeze pointer" );
207
208         const GdkEventMask mask = static_cast<GdkEventMask>( GDK_POINTER_MOTION_MASK
209                                                                                                                  | GDK_POINTER_MOTION_HINT_MASK
210                                                                                                                  | GDK_BUTTON_MOTION_MASK
211                                                                                                                  | GDK_BUTTON1_MOTION_MASK
212                                                                                                                  | GDK_BUTTON2_MOTION_MASK
213                                                                                                                  | GDK_BUTTON3_MOTION_MASK
214                                                                                                                  | GDK_BUTTON_PRESS_MASK
215                                                                                                                  | GDK_BUTTON_RELEASE_MASK
216                                                                                                                  | GDK_VISIBILITY_NOTIFY_MASK );
217
218 #if defined(WORKAROUND_MACOS_GTK2_LAGGYPOINTER)
219         /* Keep the pointer visible during the move operation.
220         Because of a bug, it remains visible even if we give
221         the order to hide it anyway.
222         Other parts of the code assume the pointer is visible,
223         so make sure it is consistently visible accross
224         third-party updates that may fix the mouse pointer
225         visibility issue. */
226 #else
227 #if 0 // NetRadiantCustom
228         //GdkCursor* cursor = create_blank_cursor();
229         GdkCursor* cursor = gdk_cursor_new( GDK_BLANK_CURSOR );
230         //GdkGrabStatus status =
231         /*      fixes cursor runaways during srsly quick drags in camera
232         drags with pressed buttons have no problem at all w/o this      */
233         gdk_pointer_grab( gtk_widget_get_window( widget ), TRUE, mask, 0, cursor, GDK_CURRENT_TIME );
234         //gdk_window_set_cursor ( GTK_WIDGET( window )->window, cursor );
235         /*      is needed to fix activating neighbour widgets, that happens, if using upper one */
236         gtk_grab_add( widget );
237         weedjet = widget;
238 #else
239         GdkCursor* cursor = create_blank_cursor();
240         //GdkGrabStatus status =
241         gdk_pointer_grab( gtk_widget_get_window( widget ), TRUE, mask, 0, cursor, GDK_CURRENT_TIME );
242         gdk_cursor_unref( cursor );
243 #endif
244 #endif
245
246         Sys_GetCursorPos( widget, &recorded_x, &recorded_y );
247
248 #if 0 // NetRadiantCustom
249         /*      using center for tracking for max safety        */
250         gdk_window_get_origin( GTK_WIDGET( widget )->window, &center_x, &center_y );
251         auto allocation = widget.dimensions();
252         center_y += allocation.height / 2;
253         center_x += allocation.width / 2;
254
255         Sys_SetCursorPos( widget, center_x, center_y );
256
257         last_x = center_x;
258         last_y = center_y;
259 #else
260         last_x = recorded_x;
261         last_y = recorded_y;
262 #endif
263
264         m_function = function;
265         m_data = data;
266
267         handle_motion = widget.connect( "motion_notify_event", G_CALLBACK( motion_delta ), this );
268 }
269
270 void FreezePointer::unfreeze_pointer(ui::Widget widget)
271 {
272         g_signal_handler_disconnect( G_OBJECT( widget ), handle_motion );
273
274         m_function = 0;
275         m_data = 0;
276
277 #if defined(WORKAROUND_MACOS_GTK2_LAGGYPOINTER)
278         /* The pointer was visible during all the move operation,
279         so, keep the current position. */
280 #else
281         // NetRadiantCustom still uses window instead of widget.
282 #if 0 // NetRadiantCustom
283         Sys_SetCursorPos( window, recorded_x, recorded_y );
284 #else
285         Sys_SetCursorPos( widget, recorded_x, recorded_y );
286 #endif
287 #endif
288
289 //      gdk_window_set_cursor( GTK_WIDGET( window )->window, 0 );
290         gdk_pointer_ungrab( GDK_CURRENT_TIME );
291 #if 0 // NetRadiantCustom
292         gtk_grab_remove( weedjet );
293 #endif
294 }