1 /*
2 * Copyright (c) 2008-2009 Christian Hammond
3 * Copyright (c) 2008-2009 David Trowbridge
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 * THE SOFTWARE.
22 */
23
24 #include "config.h"
25 #include <glib/gi18n-lib.h>
26
27 #include "window.h"
28 #include "object-tree.h"
29
30 #include "gtkstack.h"
31 #include "gtkmain.h"
32 #include "gtkinvisible.h"
33
34 typedef struct
35 {
36 gint x;
37 gint y;
38 gboolean found;
39 gboolean first;
40 GtkWidget *res_widget;
41 } FindWidgetData;
42
43 static void
find_widget(GtkWidget * widget,FindWidgetData * data)44 find_widget (GtkWidget *widget,
45 FindWidgetData *data)
46 {
47 GtkAllocation new_allocation;
48 gint x_offset = 0;
49 gint y_offset = 0;
50
51 gtk_widget_get_allocation (widget, &new_allocation);
52
53 if (data->found || !gtk_widget_get_mapped (widget))
54 return;
55
56 /* Note that in the following code, we only count the
57 * position as being inside a WINDOW widget if it is inside
58 * widget->window; points that are outside of widget->window
59 * but within the allocation are not counted. This is consistent
60 * with the way we highlight drag targets.
61 */
62 if (gtk_widget_get_has_window (widget))
63 {
64 new_allocation.x = 0;
65 new_allocation.y = 0;
66 }
67
68 if (gtk_widget_get_parent (widget) && !data->first)
69 {
70 GdkWindow *window;
71
72 window = gtk_widget_get_window (widget);
73 while (window != gtk_widget_get_window (gtk_widget_get_parent (widget)))
74 {
75 gint tx, ty, twidth, theight;
76
77 if (window == NULL)
78 return;
79
80 twidth = gdk_window_get_width (window);
81 theight = gdk_window_get_height (window);
82
83 if (new_allocation.x < 0)
84 {
85 new_allocation.width += new_allocation.x;
86 new_allocation.x = 0;
87 }
88 if (new_allocation.y < 0)
89 {
90 new_allocation.height += new_allocation.y;
91 new_allocation.y = 0;
92 }
93 if (new_allocation.x + new_allocation.width > twidth)
94 new_allocation.width = twidth - new_allocation.x;
95 if (new_allocation.y + new_allocation.height > theight)
96 new_allocation.height = theight - new_allocation.y;
97
98 gdk_window_get_position (window, &tx, &ty);
99 new_allocation.x += tx;
100 x_offset += tx;
101 new_allocation.y += ty;
102 y_offset += ty;
103
104 window = gdk_window_get_parent (window);
105 }
106 }
107
108 if ((data->x >= new_allocation.x) && (data->y >= new_allocation.y) &&
109 (data->x < new_allocation.x + new_allocation.width) &&
110 (data->y < new_allocation.y + new_allocation.height))
111 {
112 /* First, check if the drag is in a valid drop site in
113 * one of our children
114 */
115 if (GTK_IS_CONTAINER (widget))
116 {
117 FindWidgetData new_data = *data;
118
119 new_data.x -= x_offset;
120 new_data.y -= y_offset;
121 new_data.found = FALSE;
122 new_data.first = FALSE;
123
124 gtk_container_forall (GTK_CONTAINER (widget),
125 (GtkCallback)find_widget,
126 &new_data);
127
128 data->found = new_data.found;
129 if (data->found)
130 data->res_widget = new_data.res_widget;
131 }
132
133 /* If not, and this widget is registered as a drop site, check to
134 * emit "drag_motion" to check if we are actually in
135 * a drop site.
136 */
137 if (!data->found)
138 {
139 data->found = TRUE;
140 data->res_widget = widget;
141 }
142 }
143 }
144
145 static GtkWidget *
find_widget_at_pointer(GdkDevice * device)146 find_widget_at_pointer (GdkDevice *device)
147 {
148 GtkWidget *widget = NULL;
149 GdkWindow *pointer_window;
150 gint x, y;
151 FindWidgetData data;
152
153 pointer_window = gdk_device_get_window_at_position (device, NULL, NULL);
154
155 if (pointer_window)
156 {
157 gpointer widget_ptr;
158
159 gdk_window_get_user_data (pointer_window, &widget_ptr);
160 widget = widget_ptr;
161 }
162
163 if (widget)
164 {
165 gdk_window_get_device_position (gtk_widget_get_window (widget),
166 device, &x, &y, NULL);
167
168 data.x = x;
169 data.y = y;
170 data.found = FALSE;
171 data.first = TRUE;
172
173 find_widget (widget, &data);
174 if (data.found)
175 return data.res_widget;
176
177 return widget;
178 }
179
180 return NULL;
181 }
182
183 static gboolean draw_flash (GtkWidget *widget,
184 cairo_t *cr,
185 GtkInspectorWindow *iw);
186
187 static void
clear_flash(GtkInspectorWindow * iw)188 clear_flash (GtkInspectorWindow *iw)
189 {
190 if (iw->flash_widget)
191 {
192 gtk_widget_queue_draw (iw->flash_widget);
193 g_signal_handlers_disconnect_by_func (iw->flash_widget, draw_flash, iw);
194 g_signal_handlers_disconnect_by_func (iw->flash_widget, clear_flash, iw);
195 iw->flash_widget = NULL;
196 }
197 }
198
199 static void
start_flash(GtkInspectorWindow * iw,GtkWidget * widget)200 start_flash (GtkInspectorWindow *iw,
201 GtkWidget *widget)
202 {
203 clear_flash (iw);
204
205 iw->flash_count = 1;
206 iw->flash_widget = widget;
207 g_signal_connect_after (widget, "draw", G_CALLBACK (draw_flash), iw);
208 g_signal_connect_swapped (widget, "unmap", G_CALLBACK (clear_flash), iw);
209 gtk_widget_queue_draw (widget);
210 }
211
212 static void
select_widget(GtkInspectorWindow * iw,GtkWidget * widget)213 select_widget (GtkInspectorWindow *iw,
214 GtkWidget *widget)
215 {
216 GtkInspectorObjectTree *wt = GTK_INSPECTOR_OBJECT_TREE (iw->object_tree);
217
218 iw->selected_widget = widget;
219
220 if (!gtk_inspector_object_tree_select_object (wt, G_OBJECT (widget)))
221 {
222 gtk_inspector_object_tree_scan (wt, gtk_widget_get_toplevel (widget));
223 gtk_inspector_object_tree_select_object (wt, G_OBJECT (widget));
224 }
225 }
226
227 static void
on_inspect_widget(GtkWidget * button,GdkEvent * event,GtkInspectorWindow * iw)228 on_inspect_widget (GtkWidget *button,
229 GdkEvent *event,
230 GtkInspectorWindow *iw)
231 {
232 GtkWidget *widget;
233
234 gdk_window_raise (gtk_widget_get_window (GTK_WIDGET (iw)));
235
236 clear_flash (iw);
237
238 widget = find_widget_at_pointer (gdk_event_get_device (event));
239
240 if (widget)
241 select_widget (iw, widget);
242 }
243
244 static void
on_highlight_widget(GtkWidget * button,GdkEvent * event,GtkInspectorWindow * iw)245 on_highlight_widget (GtkWidget *button,
246 GdkEvent *event,
247 GtkInspectorWindow *iw)
248 {
249 GtkWidget *widget;
250
251 widget = find_widget_at_pointer (gdk_event_get_device (event));
252
253 if (widget == NULL)
254 {
255 /* This window isn't in-process. Ignore it. */
256 return;
257 }
258
259 if (gtk_widget_get_toplevel (widget) == GTK_WIDGET (iw))
260 {
261 /* Don't hilight things in the inspector window */
262 return;
263 }
264
265 if (iw->flash_widget == widget)
266 {
267 /* Already selected */
268 return;
269 }
270
271 clear_flash (iw);
272 start_flash (iw, widget);
273 }
274
275 static void
deemphasize_window(GtkWidget * window)276 deemphasize_window (GtkWidget *window)
277 {
278 GdkScreen *screen;
279
280 screen = gtk_widget_get_screen (window);
281 if (gdk_screen_is_composited (screen) &&
282 gtk_widget_get_visual (window) == gdk_screen_get_rgba_visual (screen))
283 {
284 cairo_rectangle_int_t rect;
285 cairo_region_t *region;
286
287 gtk_widget_set_opacity (window, 0.3);
288 rect.x = rect.y = rect.width = rect.height = 0;
289 region = cairo_region_create_rectangle (&rect);
290 gtk_widget_input_shape_combine_region (window, region);
291 cairo_region_destroy (region);
292 }
293 else
294 gdk_window_lower (gtk_widget_get_window (window));
295 }
296
297 static void
reemphasize_window(GtkWidget * window)298 reemphasize_window (GtkWidget *window)
299 {
300 GdkScreen *screen;
301
302 screen = gtk_widget_get_screen (window);
303 if (gdk_screen_is_composited (screen) &&
304 gtk_widget_get_visual (window) == gdk_screen_get_rgba_visual (screen))
305 {
306 gtk_widget_set_opacity (window, 1.0);
307 gtk_widget_input_shape_combine_region (window, NULL);
308 }
309 else
310 gdk_window_raise (gtk_widget_get_window (window));
311 }
312
313 static gboolean
property_query_event(GtkWidget * widget,GdkEvent * event,gpointer data)314 property_query_event (GtkWidget *widget,
315 GdkEvent *event,
316 gpointer data)
317 {
318 GtkInspectorWindow *iw = (GtkInspectorWindow *)data;
319
320 if (event->type == GDK_BUTTON_RELEASE)
321 {
322 g_signal_handlers_disconnect_by_func (widget, property_query_event, data);
323 gtk_grab_remove (widget);
324 if (iw->grabbed)
325 gdk_seat_ungrab (gdk_event_get_seat (event));
326 reemphasize_window (GTK_WIDGET (iw));
327
328 on_inspect_widget (widget, event, data);
329 }
330 else if (event->type == GDK_MOTION_NOTIFY)
331 {
332 on_highlight_widget (widget, event, data);
333 }
334 else if (event->type == GDK_KEY_PRESS)
335 {
336 GdkEventKey *ke = (GdkEventKey*)event;
337
338 if (ke->keyval == GDK_KEY_Escape)
339 {
340 g_signal_handlers_disconnect_by_func (widget, property_query_event, data);
341 gtk_grab_remove (widget);
342 if (iw->grabbed)
343 gdk_seat_ungrab (gdk_event_get_seat (event));
344 reemphasize_window (GTK_WIDGET (iw));
345
346 clear_flash (iw);
347 }
348 }
349
350 return TRUE;
351 }
352
353 void
gtk_inspector_on_inspect(GtkWidget * button,GtkInspectorWindow * iw)354 gtk_inspector_on_inspect (GtkWidget *button,
355 GtkInspectorWindow *iw)
356 {
357 GdkDisplay *display;
358 GdkCursor *cursor;
359 GdkGrabStatus status;
360
361 if (!iw->invisible)
362 {
363 iw->invisible = gtk_invisible_new_for_screen (gdk_screen_get_default ());
364 gtk_widget_add_events (iw->invisible,
365 GDK_POINTER_MOTION_MASK |
366 GDK_BUTTON_PRESS_MASK |
367 GDK_BUTTON_RELEASE_MASK |
368 GDK_KEY_PRESS_MASK |
369 GDK_KEY_RELEASE_MASK);
370 gtk_widget_realize (iw->invisible);
371 gtk_widget_show (iw->invisible);
372 }
373
374 display = gdk_display_get_default ();
375 cursor = gdk_cursor_new_from_name (display, "crosshair");
376 status = gdk_seat_grab (gdk_display_get_default_seat (display),
377 gtk_widget_get_window (iw->invisible),
378 GDK_SEAT_CAPABILITY_ALL_POINTING, TRUE,
379 cursor, NULL, NULL, NULL);
380 g_object_unref (cursor);
381 iw->grabbed = status == GDK_GRAB_SUCCESS;
382
383 g_signal_connect (iw->invisible, "event", G_CALLBACK (property_query_event), iw);
384
385 gtk_grab_add (GTK_WIDGET (iw->invisible));
386 deemphasize_window (GTK_WIDGET (iw));
387 }
388
389 static gboolean
draw_flash(GtkWidget * widget,cairo_t * cr,GtkInspectorWindow * iw)390 draw_flash (GtkWidget *widget,
391 cairo_t *cr,
392 GtkInspectorWindow *iw)
393 {
394 GtkAllocation alloc;
395
396 if (iw && iw->flash_count % 2 == 0)
397 return FALSE;
398
399 if (GTK_IS_WINDOW (widget))
400 {
401 GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget));
402 /* We don't want to draw the drag highlight around the
403 * CSD window decorations
404 */
405 if (child == NULL)
406 return FALSE;
407
408 gtk_widget_get_allocation (child, &alloc);
409 }
410 else
411 {
412 alloc.x = 0;
413 alloc.y = 0;
414 alloc.width = gtk_widget_get_allocated_width (widget);
415 alloc.height = gtk_widget_get_allocated_height (widget);
416 }
417
418 cairo_set_source_rgba (cr, 0.0, 0.0, 1.0, 0.2);
419 cairo_rectangle (cr,
420 alloc.x + 0.5, alloc.y + 0.5,
421 alloc.width - 1, alloc.height - 1);
422 cairo_fill (cr);
423
424 return FALSE;
425 }
426
427 static gboolean
on_flash_timeout(GtkInspectorWindow * iw)428 on_flash_timeout (GtkInspectorWindow *iw)
429 {
430 gtk_widget_queue_draw (iw->flash_widget);
431
432 iw->flash_count++;
433
434 if (iw->flash_count == 6)
435 {
436 g_signal_handlers_disconnect_by_func (iw->flash_widget, draw_flash, iw);
437 g_signal_handlers_disconnect_by_func (iw->flash_widget, clear_flash, iw);
438 iw->flash_widget = NULL;
439 iw->flash_cnx = 0;
440
441 return G_SOURCE_REMOVE;
442 }
443
444 return G_SOURCE_CONTINUE;
445 }
446
447 void
gtk_inspector_flash_widget(GtkInspectorWindow * iw,GtkWidget * widget)448 gtk_inspector_flash_widget (GtkInspectorWindow *iw,
449 GtkWidget *widget)
450 {
451 if (!gtk_widget_get_visible (widget) || !gtk_widget_get_mapped (widget))
452 return;
453
454 if (iw->flash_cnx != 0)
455 {
456 g_source_remove (iw->flash_cnx);
457 iw->flash_cnx = 0;
458 }
459
460 start_flash (iw, widget);
461 iw->flash_cnx = g_timeout_add (150, (GSourceFunc) on_flash_timeout, iw);
462 }
463
464 void
gtk_inspector_start_highlight(GtkWidget * widget)465 gtk_inspector_start_highlight (GtkWidget *widget)
466 {
467 g_signal_connect_after (widget, "draw", G_CALLBACK (draw_flash), NULL);
468 gtk_widget_queue_draw (widget);
469 }
470
471 void
gtk_inspector_stop_highlight(GtkWidget * widget)472 gtk_inspector_stop_highlight (GtkWidget *widget)
473 {
474 g_signal_handlers_disconnect_by_func (widget, draw_flash, NULL);
475 g_signal_handlers_disconnect_by_func (widget, clear_flash, NULL);
476 gtk_widget_queue_draw (widget);
477 }
478
479 void
gtk_inspector_window_select_widget_under_pointer(GtkInspectorWindow * iw)480 gtk_inspector_window_select_widget_under_pointer (GtkInspectorWindow *iw)
481 {
482 GdkDisplay *display;
483 GdkDevice *device;
484 GtkWidget *widget;
485
486 display = gdk_display_get_default ();
487 device = gdk_seat_get_pointer (gdk_display_get_default_seat (display));
488
489 widget = find_widget_at_pointer (device);
490
491 if (widget)
492 select_widget (iw, widget);
493 }
494
495 /* vim: set et sw=2 ts=2: */
496