]> git.xonotic.org Git - xonotic/netradiant.git/blob - libs/gtkutil/cursor.cpp
Merge commit 'a764616a9c1b2f9148e3381f808b600c512592c9' 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         self->last_x = current_x;
99         self->last_y = current_y;
100         if ( dx != 0 || dy != 0 ) {
101                 //globalOutputStream() << "motion x: " << dx << ", y: " << dy << "\n";
102 #if defined(WORKAROUND_MACOS_GTK2_LAGGYPOINTER)
103                 ui::Dimensions dimensions = widget.dimensions();
104                 int window_x, window_y;
105                 int translated_x, translated_y;
106
107                 gdk_window_get_origin( gtk_widget_get_window( widget ), &window_x, &window_y);
108
109                 translated_x = current_x - ( window_x );
110                 translated_y = current_y - ( window_y );
111
112 #if 0
113                 int widget_x, widget_y;
114                 gtk_widget_translate_coordinates( GTK_WIDGET( widget ), gtk_widget_get_toplevel( GTK_WIDGET( widget ) ), 0, 0, &widget_x, &widget_y);
115
116                 globalOutputStream()
117                         << "window_x: " << window_x
118                         << ", window_y: " << window_y
119                         << ", widget_x: " << widget_x
120                         << ", widget_y: " << widget_y
121                         << ", current x: " << current_x
122                         << ", current_y: " << current_y
123                         << ", translated x: " << translated_x
124                         << ", translated_y: " << translated_y
125                         << ", width: " << dimensions.width
126                         << ", height: " << dimensions.height
127                         << "\n";
128 #endif
129
130                 if ( translated_x < 32 || translated_x > dimensions.width - 32
131                         || translated_y < 32 || translated_y > dimensions.height - 32 ) {
132 #if 0
133                         // Reposition the pointer to the widget center.
134                         int reposition_x = window_x + dimensions.width / 2;
135                         int reposition_y = window_y + dimensions.height / 2;
136 #else
137                         // Move the pointer to the opposite side of the XY Window
138                         // to maximize the distance that can be moved again.
139                         int reposition_x = current_x;
140                         int reposition_y = current_y;
141
142                         if ( translated_x < 32 ) {
143                                 reposition_x = window_x + dimensions.width - 32;
144                         }
145                         else if ( translated_x > dimensions.width - 32 ) {
146                                 reposition_x = window_x + 32;
147                         }
148
149                         if ( translated_y < 32 ) {
150                                 reposition_y = window_y + dimensions.height - 32;
151                         }
152                         else if ( translated_y > dimensions.height - 32 ) {
153                                 reposition_y = window_y + 32;
154                         }
155 #endif
156
157                         Sys_SetCursorPos( widget, reposition_x, reposition_y );
158                         self->last_x = reposition_x;
159                         self->last_y = reposition_y;
160                 }
161 #else
162 #if 0 // NetRadiantCustom
163                 int ddx = current_x - self->center_x;
164                 int ddy = current_y - self->center_y;
165 #else
166                 int ddx = current_x - self->recorded_x;
167                 int ddy = current_y - self->recorded_y;
168 #endif
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 #else
173                         Sys_SetCursorPos( widget, self->recorded_x, self->recorded_y );
174 #endif
175                         self->last_x = self->recorded_x;
176                         self->last_y = self->recorded_y;
177                 }
178 #endif
179                 self->m_function( dx, dy, event->state, self->m_data );
180         }
181         return FALSE;
182 }
183
184 /* NetRadiantCustom did this instead:
185 void FreezePointer::freeze_pointer(ui::Window window, ui::Widget widget, FreezePointer::MotionDeltaFunction function, void *data) */
186 void FreezePointer::freeze_pointer(ui::Widget widget, FreezePointer::MotionDeltaFunction function, void *data)
187 {
188         /* FIXME: This bug can happen if the pointer goes outside of the
189         XY Window while the right mouse button is not released,
190         the XY Window loses focus and can't read the right mouse button
191         release event and then cannot unfreeze the pointer, meaning the
192         user can attempt to freeze the pointer in another XY window.
193
194         This can happen with touch screen, especially those used to drive
195         virtual machine pointers, the cursor can be teleported outside of
196         the XY Window while maintaining pressure on the right mouse button.
197         This can also happen when the render is slow.
198
199         The bug also occurs with the Camera Window.
200
201         FIXME: It's would be possible to tell the user to save the map
202         at assert time before crashing because this bug does not corrupt
203         map saving. */
204         ASSERT_MESSAGE( m_function == 0, "can't freeze pointer" );
205
206         const GdkEventMask mask = static_cast<GdkEventMask>( GDK_POINTER_MOTION_MASK
207                                                                                                                  | GDK_POINTER_MOTION_HINT_MASK
208                                                                                                                  | GDK_BUTTON_MOTION_MASK
209                                                                                                                  | GDK_BUTTON1_MOTION_MASK
210                                                                                                                  | GDK_BUTTON2_MOTION_MASK
211                                                                                                                  | GDK_BUTTON3_MOTION_MASK
212                                                                                                                  | GDK_BUTTON_PRESS_MASK
213                                                                                                                  | GDK_BUTTON_RELEASE_MASK
214                                                                                                                  | GDK_VISIBILITY_NOTIFY_MASK );
215
216 #if defined(WORKAROUND_MACOS_GTK2_LAGGYPOINTER)
217         /* Keep the pointer visible during the move operation.
218         Because of a bug, it remains visible even if we give
219         the order to hide it anyway.
220         Other parts of the code assume the pointer is visible,
221         so make sure it is consistently visible accross
222         third-party updates that may fix the mouse pointer
223         visibility issue. */
224 #else
225 #if 0 // NetRadiantCustom
226         GdkCursor* cursor = create_blank_cursor();
227         //GdkGrabStatus status =
228         /*      fixes cursor runaways during srsly quick drags in camera
229         drags with pressed buttons have no problem at all w/o this      */
230         gdk_pointer_grab( gtk_widget_get_window( widget ), TRUE, mask, 0, cursor, GDK_CURRENT_TIME );
231         //gdk_window_set_cursor ( GTK_WIDGET( window )->window, cursor );
232         /*      is needed to fix activating neighbour widgets, that happens, if using upper one */
233         gtk_grab_add( widget );
234         weedjet = widget;
235 #else
236         GdkCursor* cursor = create_blank_cursor();
237         //GdkGrabStatus status =
238         gdk_pointer_grab( gtk_widget_get_window( widget ), TRUE, mask, 0, cursor, GDK_CURRENT_TIME );
239         gdk_cursor_unref( cursor );
240 #endif
241 #endif
242
243         Sys_GetCursorPos( widget, &recorded_x, &recorded_y );
244
245 #if 0 // NetRadiantCustom
246         /*      using center for tracking for max safety        */
247         gdk_window_get_origin( GTK_WIDGET( widget )->window, &center_x, &center_y );
248         auto allocation = widget.dimensions();
249         center_y += allocation.height / 2;
250         center_x += allocation.width / 2;
251
252         Sys_SetCursorPos( widget, center_x, center_y );
253
254         last_x = center_x;
255         last_y = center_y;
256 #else
257         last_x = recorded_x;
258         last_y = recorded_y;
259 #endif
260
261         m_function = function;
262         m_data = data;
263
264         handle_motion = widget.connect( "motion_notify_event", G_CALLBACK( motion_delta ), this );
265 }
266
267 void FreezePointer::unfreeze_pointer(ui::Widget widget)
268 {
269         g_signal_handler_disconnect( G_OBJECT( widget ), handle_motion );
270
271         m_function = 0;
272         m_data = 0;
273
274 #if defined(WORKAROUND_MACOS_GTK2_LAGGYPOINTER)
275         /* The pointer was visible during all the move operation,
276         so, keep the current position. */
277 #else
278         // NetRadiantCustom still uses window instead of widget.
279 #if 0 // NetRadiantCustom
280 //      Sys_SetCursorPos( widget, center_x, center_y );
281 #else
282         Sys_SetCursorPos( widget, recorded_x, recorded_y );
283 #endif
284 #endif
285
286         gdk_pointer_ungrab( GDK_CURRENT_TIME );
287
288 #if 0 // NetRadiantCustom
289         gtk_grab_remove( weedjet );
290 #endif
291 }