1 /*****************************************************************************
2  *   Copyright 2003 - 2010 Craig Drummond <craig.p.drummond@gmail.com>       *
3  *   Copyright 2013 - 2015 Yichao Yu <yyc1992@gmail.com>                     *
4  *                                                                           *
5  *   This program is free software; you can redistribute it and/or modify    *
6  *   it under the terms of the GNU Lesser General Public License as          *
7  *   published by the Free Software Foundation; either version 2.1 of the    *
8  *   License, or (at your option) version 3, or any later version accepted   *
9  *   by the membership of KDE e.V. (or its successor approved by the         *
10  *   membership of KDE e.V.), which shall act as a proxy defined in          *
11  *   Section 6 of version 3 of the license.                                  *
12  *                                                                           *
13  *   This program is distributed in the hope that it will be useful,         *
14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       *
16  *   Lesser General Public License for more details.                         *
17  *                                                                           *
18  *   You should have received a copy of the GNU Lesser General Public        *
19  *   License along with this library. If not,                                *
20  *   see <http://www.gnu.org/licenses/>.                                     *
21  *****************************************************************************/
22 
23 #include "wmmove.h"
24 
25 #include <qtcurve-utils/gtkprops.h>
26 #include <qtcurve-utils/x11wmmove.h>
27 
28 #include <gdk/gdkx.h>
29 #include "helpers.h"
30 #include "qt_settings.h"
31 #include "tab.h"
32 
33 namespace QtCurve {
34 namespace WMMove {
35 
36 static int lastX = -1;
37 static int lastY = -1;
38 static int timer = 0;
39 static GtkWidget *dragWidget = nullptr;
40 //! keep track of the last rejected button event to reject it again if passed to some parent widget
41 /*! this spares some time (by not processing the same event twice), and prevents some bugs */
42 GdkEventButton *lastRejectedEvent = nullptr;
43 
44 static int btnReleaseSignalId = 0;
45 static int btnReleaseHookId = 0;
46 
47 static bool dragEnd();
48 
49 static gboolean
btnReleaseHook(GSignalInvocationHint *,unsigned,const GValue *,void *)50 btnReleaseHook(GSignalInvocationHint*, unsigned, const GValue*, void*)
51 {
52     if (dragWidget)
53         dragEnd();
54     return true;
55 }
56 
57 static void
registerBtnReleaseHook()58 registerBtnReleaseHook()
59 {
60     if (btnReleaseSignalId == 0 && btnReleaseHookId == 0) {
61         btnReleaseSignalId =
62             g_signal_lookup("button-release-event", GTK_TYPE_WIDGET);
63         if (btnReleaseSignalId) {
64             btnReleaseHookId =
65                 g_signal_add_emission_hook(btnReleaseSignalId,
66                                            (GQuark)0, btnReleaseHook,
67                                            nullptr, nullptr);
68         }
69     }
70 }
71 
72 static void
stopTimer()73 stopTimer()
74 {
75     if (timer)
76         g_source_remove(timer);
77     timer = 0;
78 }
79 
80 static void
reset()81 reset()
82 {
83     lastX = -1;
84     lastY = -1;
85     dragWidget = nullptr;
86     lastRejectedEvent = nullptr;
87     stopTimer();
88 }
89 
90 static void
store(GtkWidget * widget,GdkEventButton * event)91 store(GtkWidget *widget, GdkEventButton *event)
92 {
93     lastX = event ? event->x_root : -1;
94     lastY = event ? event->y_root : -1;
95     dragWidget = widget;
96 }
97 
98 static void
trigger(GtkWidget * w,int x,int y)99 trigger(GtkWidget *w, int x, int y)
100 {
101     GtkWindow *topLevel = GTK_WINDOW(gtk_widget_get_toplevel(w));
102     xcb_window_t wid =
103         GDK_WINDOW_XID(gtk_widget_get_window(GTK_WIDGET(topLevel)));
104     qtcX11MoveTrigger(wid, x, y);
105     dragEnd();
106 }
107 
108 static bool
withinWidget(GtkWidget * widget,GdkEventButton * event)109 withinWidget(GtkWidget *widget, GdkEventButton *event)
110 {
111     // get top level widget
112     GtkWidget *topLevel=gtk_widget_get_toplevel(widget);;
113     GdkWindow *window = topLevel ? gtk_widget_get_window(topLevel) : nullptr;
114 
115     if (window) {
116         QtcRect allocation;
117         int           wx=0, wy=0, nx=0, ny=0;
118 
119         // translate widget position to topLevel
120         gtk_widget_translate_coordinates(widget, topLevel, wx, wy, &wx, &wy);
121 
122         // translate to absolute coordinates
123         gdk_window_get_origin(window, &nx, &ny);
124         wx += nx;
125         wy += ny;
126 
127         // get widget size.
128         // for notebooks, only consider the tabbar rect
129         if (GTK_IS_NOTEBOOK(widget)) {
130             QtcRect widgetAlloc = Widget::getAllocation(widget);
131             allocation = Tab::getTabbarRect(GTK_NOTEBOOK(widget));
132             allocation.x += wx - widgetAlloc.x;
133             allocation.y += wy - widgetAlloc.y;
134         } else {
135             allocation = Widget::getAllocation(widget);
136             allocation.x = wx;
137             allocation.y = wy;
138         }
139 
140         return allocation.x<=event->x_root && allocation.y<=event->y_root &&
141             (allocation.x+allocation.width)>event->x_root && (allocation.y+allocation.height)>event->y_root;
142     }
143     return true;
144 }
145 
146 static bool
isBlackListed(GObject * object)147 isBlackListed(GObject *object)
148 {
149     static const char *widgets[] = {
150         "GtkPizza", "GladeDesignLayout", "MetaFrames", "SPHRuler",
151         "SPVRuler", nullptr
152     };
153 
154     for (int i = 0;widgets[i];i++) {
155         if (objectIsA(object, widgets[i])) {
156             return true;
157         }
158     }
159     return false;
160 }
161 
162 static bool
childrenUseEvent(GtkWidget * widget,GdkEventButton * event,bool inNoteBook)163 childrenUseEvent(GtkWidget *widget, GdkEventButton *event, bool inNoteBook)
164 {
165     // accept, by default
166     bool usable = true;
167 
168     // get children and check
169     GList *children = gtk_container_get_children(GTK_CONTAINER(widget));
170     for (GList *child = children;child && usable;child = g_list_next(child)) {
171         // cast child to GtkWidget
172         if (GTK_IS_WIDGET(child->data)) {
173             GtkWidget *childWidget = GTK_WIDGET(child->data);
174             GdkWindow *window = nullptr;
175 
176             // check widget state and type
177             if (gtk_widget_get_state(childWidget) == GTK_STATE_PRELIGHT) {
178                 // if widget is prelight, we don't need to check where event
179                 // happen, any prelight widget indicate we can't do a move
180                 usable = false;
181                 continue;
182             }
183 
184             window = gtk_widget_get_window(childWidget);
185             if (!(window && gdk_window_is_visible(window)))
186                 continue;
187 
188             if (GTK_IS_NOTEBOOK(childWidget))
189                 inNoteBook = true;
190 
191             if(!(event && withinWidget(childWidget, event)))
192                 continue;
193 
194             // check special cases for which grab should not be enabled
195             if((isBlackListed(G_OBJECT(childWidget))) ||
196                (GTK_IS_NOTEBOOK(widget) && Tab::isLabel(GTK_NOTEBOOK(widget),
197                                                          childWidget)) ||
198                (GTK_IS_BUTTON(childWidget) &&
199                 gtk_widget_get_state(childWidget) != GTK_STATE_INSENSITIVE) ||
200                (gtk_widget_get_events(childWidget) &
201                 (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK)) ||
202                (GTK_IS_MENU_ITEM(childWidget)) ||
203                (GTK_IS_SCROLLED_WINDOW(childWidget) &&
204                 (!inNoteBook || gtk_widget_is_focus(childWidget)))) {
205                 usable = false;
206             }
207 
208             // if child is a container and event has been accepted so far,
209             // also check it, recursively
210             if (usable && GTK_IS_CONTAINER(childWidget)) {
211                 usable = childrenUseEvent(childWidget, event, inNoteBook);
212             }
213         }
214     }
215     if (children) {
216         g_list_free(children);
217     }
218     return usable;
219 }
220 
221 static bool
useEvent(GtkWidget * widget,GdkEventButton * event)222 useEvent(GtkWidget *widget, GdkEventButton *event)
223 {
224     if(lastRejectedEvent && lastRejectedEvent==event)
225         return false;
226 
227     if(!GTK_IS_CONTAINER(widget))
228         return true;
229 
230     // if widget is a notebook, accept if there is no hovered tab
231     if (GTK_IS_NOTEBOOK(widget)) {
232         return (!Tab::hasVisibleArrows(GTK_NOTEBOOK(widget)) &&
233                 Tab::currentHoveredIndex(widget) == -1 &&
234                 childrenUseEvent(widget, event, false));
235     } else {
236         return childrenUseEvent(widget, event, false);
237     }
238 }
239 
240 static gboolean
startDelayedDrag(void *)241 startDelayedDrag(void*)
242 {
243     if (dragWidget) {
244         gdk_threads_enter();
245         trigger(dragWidget, lastX, lastY);
246         gdk_threads_leave();
247     }
248     return false;
249 }
250 
251 static bool
isWindowDragWidget(GtkWidget * widget,GdkEventButton * event)252 isWindowDragWidget(GtkWidget *widget, GdkEventButton *event)
253 {
254     if (opts.windowDrag && (!event || (withinWidget(widget, event) &&
255                                        useEvent(widget, event)))) {
256         store(widget, event);
257         // Start timer
258         stopTimer();
259         timer = g_timeout_add(qtSettings.startDragTime,
260                               (GSourceFunc)startDelayedDrag, nullptr);
261         return true;
262     }
263     lastRejectedEvent=event;
264     return false;
265 }
266 
267 static gboolean
buttonPress(GtkWidget * widget,GdkEventButton * event,void *)268 buttonPress(GtkWidget *widget, GdkEventButton *event, void*)
269 {
270     if (GDK_BUTTON_PRESS == event->type && 1 == event->button &&
271         isWindowDragWidget(widget, event)) {
272         dragWidget = widget;
273         return true;
274     }
275     return false;
276 }
277 
278 static bool
dragEnd()279 dragEnd()
280 {
281     if (dragWidget) {
282         //gtk_grab_remove(widget);
283         gdk_pointer_ungrab(CurrentTime);
284         reset();
285         return true;
286     }
287 
288     return false;
289 }
290 
cleanup(GtkWidget * widget)291 static void cleanup(GtkWidget *widget)
292 {
293     GtkWidgetProps props(widget);
294     if (props->wmMoveHacked) {
295         if (widget == dragWidget)
296             reset();
297         props->wmMoveDestroy.disconn();
298         props->wmMoveStyleSet.disconn();
299         props->wmMoveMotion.disconn();
300         props->wmMoveLeave.disconn();
301         props->wmMoveButtonPress.disconn();
302         props->wmMoveHacked = false;
303     }
304 }
305 
306 static gboolean
styleSet(GtkWidget * widget,GtkStyle *,void *)307 styleSet(GtkWidget *widget, GtkStyle*, void*)
308 {
309     cleanup(widget);
310     return false;
311 }
312 
313 static gboolean
destroy(GtkWidget * widget,GdkEvent *,void *)314 destroy(GtkWidget *widget, GdkEvent*, void*)
315 {
316     cleanup(widget);
317     return false;
318 }
319 
320 static gboolean
motion(GtkWidget * widget,GdkEventMotion * event,void *)321 motion(GtkWidget *widget, GdkEventMotion *event, void*)
322 {
323     if (dragWidget == widget) {
324         // check displacement with respect to drag start
325         const int distance = (std::abs(lastX - event->x_root) +
326                               std::abs(lastY - event->y_root));
327 
328         if (distance > 0)
329             stopTimer();
330 
331         /* if (distance < qtSettings.startDragDist) */
332         /*     return false; */
333         trigger(widget, event->x_root, event->y_root);
334         return true;
335     }
336     return false;
337 }
338 
339 static gboolean
leave(GtkWidget *,GdkEventMotion *,void *)340 leave(GtkWidget*, GdkEventMotion*, void*)
341 {
342     return dragEnd();
343 }
344 
345 void
setup(GtkWidget * widget)346 setup(GtkWidget *widget)
347 {
348     QTC_RET_IF_FAIL(widget);
349     GtkWidget *parent = nullptr;
350 
351     if (GTK_IS_WINDOW(widget) &&
352         !gtk_window_get_decorated(GTK_WINDOW(widget))) {
353         return;
354     }
355 
356     if (GTK_IS_EVENT_BOX(widget) &&
357         gtk_event_box_get_above_child(GTK_EVENT_BOX(widget)))
358         return;
359 
360     parent = gtk_widget_get_parent(widget);
361 
362     // widgets used in tabs also must be ignored (happens, unfortunately)
363     if (GTK_IS_NOTEBOOK(parent) && Tab::isLabel(GTK_NOTEBOOK(parent), widget))
364         return;
365 
366     /*
367       check event mask (for now we only need to do that for GtkWindow)
368       The idea is that if the window has been set to receive button_press
369       and button_release events (which is not done by default), it likely
370       means that it does something with such events, in which case we should
371       not use them for grabbing
372     */
373     if (oneOf(gTypeName(widget), "GtkWindow") &&
374         (gtk_widget_get_events(widget) &
375          (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK)))
376         return;
377 
378     GtkWidgetProps props(widget);
379     if (!isFakeGtk() && !props->wmMoveHacked) {
380         props->wmMoveHacked = true;
381         gtk_widget_add_events(widget, GDK_BUTTON_RELEASE_MASK |
382                               GDK_BUTTON_PRESS_MASK | GDK_LEAVE_NOTIFY_MASK |
383                               GDK_BUTTON1_MOTION_MASK);
384         registerBtnReleaseHook();
385         props->wmMoveDestroy.conn("destroy-event", destroy);
386         props->wmMoveStyleSet.conn("style-set", styleSet);
387         props->wmMoveMotion.conn("motion-notify-event", motion);
388         props->wmMoveLeave.conn("leave-notify-event", leave);
389         props->wmMoveButtonPress.conn("button-press-event", buttonPress);
390     }
391 }
392 
393 }
394 }
395