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