File indexing completed on 2024-04-21 05:47:02

0001 /*****************************************************************************
0002  *   Copyright 2003 - 2010 Craig Drummond <craig.p.drummond@gmail.com>       *
0003  *   Copyright 2013 - 2015 Yichao Yu <yyc1992@gmail.com>                     *
0004  *                                                                           *
0005  *   This program is free software; you can redistribute it and/or modify    *
0006  *   it under the terms of the GNU Lesser General Public License as          *
0007  *   published by the Free Software Foundation; either version 2.1 of the    *
0008  *   License, or (at your option) version 3, or any later version accepted   *
0009  *   by the membership of KDE e.V. (or its successor approved by the         *
0010  *   membership of KDE e.V.), which shall act as a proxy defined in          *
0011  *   Section 6 of version 3 of the license.                                  *
0012  *                                                                           *
0013  *   This program is distributed in the hope that it will be useful,         *
0014  *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
0015  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       *
0016  *   Lesser General Public License for more details.                         *
0017  *                                                                           *
0018  *   You should have received a copy of the GNU Lesser General Public        *
0019  *   License along with this library. If not,                                *
0020  *   see <http://www.gnu.org/licenses/>.                                     *
0021  *****************************************************************************/
0022 
0023 #include "wmmove.h"
0024 
0025 #include <qtcurve-utils/gtkprops.h>
0026 #include <qtcurve-utils/x11wmmove.h>
0027 
0028 #include <gdk/gdkx.h>
0029 #include "helpers.h"
0030 #include "qt_settings.h"
0031 #include "tab.h"
0032 
0033 namespace QtCurve {
0034 namespace WMMove {
0035 
0036 static int lastX = -1;
0037 static int lastY = -1;
0038 static int timer = 0;
0039 static GtkWidget *dragWidget = nullptr;
0040 //! keep track of the last rejected button event to reject it again if passed to some parent widget
0041 /*! this spares some time (by not processing the same event twice), and prevents some bugs */
0042 GdkEventButton *lastRejectedEvent = nullptr;
0043 
0044 static int btnReleaseSignalId = 0;
0045 static int btnReleaseHookId = 0;
0046 
0047 static bool dragEnd();
0048 
0049 static gboolean
0050 btnReleaseHook(GSignalInvocationHint*, unsigned, const GValue*, void*)
0051 {
0052     if (dragWidget)
0053         dragEnd();
0054     return true;
0055 }
0056 
0057 static void
0058 registerBtnReleaseHook()
0059 {
0060     if (btnReleaseSignalId == 0 && btnReleaseHookId == 0) {
0061         btnReleaseSignalId =
0062             g_signal_lookup("button-release-event", GTK_TYPE_WIDGET);
0063         if (btnReleaseSignalId) {
0064             btnReleaseHookId =
0065                 g_signal_add_emission_hook(btnReleaseSignalId,
0066                                            (GQuark)0, btnReleaseHook,
0067                                            nullptr, nullptr);
0068         }
0069     }
0070 }
0071 
0072 static void
0073 stopTimer()
0074 {
0075     if (timer)
0076         g_source_remove(timer);
0077     timer = 0;
0078 }
0079 
0080 static void
0081 reset()
0082 {
0083     lastX = -1;
0084     lastY = -1;
0085     dragWidget = nullptr;
0086     lastRejectedEvent = nullptr;
0087     stopTimer();
0088 }
0089 
0090 static void
0091 store(GtkWidget *widget, GdkEventButton *event)
0092 {
0093     lastX = event ? event->x_root : -1;
0094     lastY = event ? event->y_root : -1;
0095     dragWidget = widget;
0096 }
0097 
0098 static void
0099 trigger(GtkWidget *w, int x, int y)
0100 {
0101     GtkWindow *topLevel = GTK_WINDOW(gtk_widget_get_toplevel(w));
0102     xcb_window_t wid =
0103         GDK_WINDOW_XID(gtk_widget_get_window(GTK_WIDGET(topLevel)));
0104     qtcX11MoveTrigger(wid, x, y);
0105     dragEnd();
0106 }
0107 
0108 static bool
0109 withinWidget(GtkWidget *widget, GdkEventButton *event)
0110 {
0111     // get top level widget
0112     GtkWidget *topLevel=gtk_widget_get_toplevel(widget);;
0113     GdkWindow *window = topLevel ? gtk_widget_get_window(topLevel) : nullptr;
0114 
0115     if (window) {
0116         QtcRect allocation;
0117         int           wx=0, wy=0, nx=0, ny=0;
0118 
0119         // translate widget position to topLevel
0120         gtk_widget_translate_coordinates(widget, topLevel, wx, wy, &wx, &wy);
0121 
0122         // translate to absolute coordinates
0123         gdk_window_get_origin(window, &nx, &ny);
0124         wx += nx;
0125         wy += ny;
0126 
0127         // get widget size.
0128         // for notebooks, only consider the tabbar rect
0129         if (GTK_IS_NOTEBOOK(widget)) {
0130             QtcRect widgetAlloc = Widget::getAllocation(widget);
0131             allocation = Tab::getTabbarRect(GTK_NOTEBOOK(widget));
0132             allocation.x += wx - widgetAlloc.x;
0133             allocation.y += wy - widgetAlloc.y;
0134         } else {
0135             allocation = Widget::getAllocation(widget);
0136             allocation.x = wx;
0137             allocation.y = wy;
0138         }
0139 
0140         return allocation.x<=event->x_root && allocation.y<=event->y_root &&
0141             (allocation.x+allocation.width)>event->x_root && (allocation.y+allocation.height)>event->y_root;
0142     }
0143     return true;
0144 }
0145 
0146 static bool
0147 isBlackListed(GObject *object)
0148 {
0149     static const char *widgets[] = {
0150         "GtkPizza", "GladeDesignLayout", "MetaFrames", "SPHRuler",
0151         "SPVRuler", nullptr
0152     };
0153 
0154     for (int i = 0;widgets[i];i++) {
0155         if (objectIsA(object, widgets[i])) {
0156             return true;
0157         }
0158     }
0159     return false;
0160 }
0161 
0162 static bool
0163 childrenUseEvent(GtkWidget *widget, GdkEventButton *event, bool inNoteBook)
0164 {
0165     // accept, by default
0166     bool usable = true;
0167 
0168     // get children and check
0169     GList *children = gtk_container_get_children(GTK_CONTAINER(widget));
0170     for (GList *child = children;child && usable;child = g_list_next(child)) {
0171         // cast child to GtkWidget
0172         if (GTK_IS_WIDGET(child->data)) {
0173             GtkWidget *childWidget = GTK_WIDGET(child->data);
0174             GdkWindow *window = nullptr;
0175 
0176             // check widget state and type
0177             if (gtk_widget_get_state(childWidget) == GTK_STATE_PRELIGHT) {
0178                 // if widget is prelight, we don't need to check where event
0179                 // happen, any prelight widget indicate we can't do a move
0180                 usable = false;
0181                 continue;
0182             }
0183 
0184             window = gtk_widget_get_window(childWidget);
0185             if (!(window && gdk_window_is_visible(window)))
0186                 continue;
0187 
0188             if (GTK_IS_NOTEBOOK(childWidget))
0189                 inNoteBook = true;
0190 
0191             if(!(event && withinWidget(childWidget, event)))
0192                 continue;
0193 
0194             // check special cases for which grab should not be enabled
0195             if((isBlackListed(G_OBJECT(childWidget))) ||
0196                (GTK_IS_NOTEBOOK(widget) && Tab::isLabel(GTK_NOTEBOOK(widget),
0197                                                          childWidget)) ||
0198                (GTK_IS_BUTTON(childWidget) &&
0199                 gtk_widget_get_state(childWidget) != GTK_STATE_INSENSITIVE) ||
0200                (gtk_widget_get_events(childWidget) &
0201                 (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK)) ||
0202                (GTK_IS_MENU_ITEM(childWidget)) ||
0203                (GTK_IS_SCROLLED_WINDOW(childWidget) &&
0204                 (!inNoteBook || gtk_widget_is_focus(childWidget)))) {
0205                 usable = false;
0206             }
0207 
0208             // if child is a container and event has been accepted so far,
0209             // also check it, recursively
0210             if (usable && GTK_IS_CONTAINER(childWidget)) {
0211                 usable = childrenUseEvent(childWidget, event, inNoteBook);
0212             }
0213         }
0214     }
0215     if (children) {
0216         g_list_free(children);
0217     }
0218     return usable;
0219 }
0220 
0221 static bool
0222 useEvent(GtkWidget *widget, GdkEventButton *event)
0223 {
0224     if(lastRejectedEvent && lastRejectedEvent==event)
0225         return false;
0226 
0227     if(!GTK_IS_CONTAINER(widget))
0228         return true;
0229 
0230     // if widget is a notebook, accept if there is no hovered tab
0231     if (GTK_IS_NOTEBOOK(widget)) {
0232         return (!Tab::hasVisibleArrows(GTK_NOTEBOOK(widget)) &&
0233                 Tab::currentHoveredIndex(widget) == -1 &&
0234                 childrenUseEvent(widget, event, false));
0235     } else {
0236         return childrenUseEvent(widget, event, false);
0237     }
0238 }
0239 
0240 static gboolean
0241 startDelayedDrag(void*)
0242 {
0243     if (dragWidget) {
0244         gdk_threads_enter();
0245         trigger(dragWidget, lastX, lastY);
0246         gdk_threads_leave();
0247     }
0248     return false;
0249 }
0250 
0251 static bool
0252 isWindowDragWidget(GtkWidget *widget, GdkEventButton *event)
0253 {
0254     if (opts.windowDrag && (!event || (withinWidget(widget, event) &&
0255                                        useEvent(widget, event)))) {
0256         store(widget, event);
0257         // Start timer
0258         stopTimer();
0259         timer = g_timeout_add(qtSettings.startDragTime,
0260                               (GSourceFunc)startDelayedDrag, nullptr);
0261         return true;
0262     }
0263     lastRejectedEvent=event;
0264     return false;
0265 }
0266 
0267 static gboolean
0268 buttonPress(GtkWidget *widget, GdkEventButton *event, void*)
0269 {
0270     if (GDK_BUTTON_PRESS == event->type && 1 == event->button &&
0271         isWindowDragWidget(widget, event)) {
0272         dragWidget = widget;
0273         return true;
0274     }
0275     return false;
0276 }
0277 
0278 static bool
0279 dragEnd()
0280 {
0281     if (dragWidget) {
0282         //gtk_grab_remove(widget);
0283         gdk_pointer_ungrab(CurrentTime);
0284         reset();
0285         return true;
0286     }
0287 
0288     return false;
0289 }
0290 
0291 static void cleanup(GtkWidget *widget)
0292 {
0293     GtkWidgetProps props(widget);
0294     if (props->wmMoveHacked) {
0295         if (widget == dragWidget)
0296             reset();
0297         props->wmMoveDestroy.disconn();
0298         props->wmMoveStyleSet.disconn();
0299         props->wmMoveMotion.disconn();
0300         props->wmMoveLeave.disconn();
0301         props->wmMoveButtonPress.disconn();
0302         props->wmMoveHacked = false;
0303     }
0304 }
0305 
0306 static gboolean
0307 styleSet(GtkWidget *widget, GtkStyle*, void*)
0308 {
0309     cleanup(widget);
0310     return false;
0311 }
0312 
0313 static gboolean
0314 destroy(GtkWidget *widget, GdkEvent*, void*)
0315 {
0316     cleanup(widget);
0317     return false;
0318 }
0319 
0320 static gboolean
0321 motion(GtkWidget *widget, GdkEventMotion *event, void*)
0322 {
0323     if (dragWidget == widget) {
0324         // check displacement with respect to drag start
0325         const int distance = (std::abs(lastX - event->x_root) +
0326                               std::abs(lastY - event->y_root));
0327 
0328         if (distance > 0)
0329             stopTimer();
0330 
0331         /* if (distance < qtSettings.startDragDist) */
0332         /*     return false; */
0333         trigger(widget, event->x_root, event->y_root);
0334         return true;
0335     }
0336     return false;
0337 }
0338 
0339 static gboolean
0340 leave(GtkWidget*, GdkEventMotion*, void*)
0341 {
0342     return dragEnd();
0343 }
0344 
0345 void
0346 setup(GtkWidget *widget)
0347 {
0348     QTC_RET_IF_FAIL(widget);
0349     GtkWidget *parent = nullptr;
0350 
0351     if (GTK_IS_WINDOW(widget) &&
0352         !gtk_window_get_decorated(GTK_WINDOW(widget))) {
0353         return;
0354     }
0355 
0356     if (GTK_IS_EVENT_BOX(widget) &&
0357         gtk_event_box_get_above_child(GTK_EVENT_BOX(widget)))
0358         return;
0359 
0360     parent = gtk_widget_get_parent(widget);
0361 
0362     // widgets used in tabs also must be ignored (happens, unfortunately)
0363     if (GTK_IS_NOTEBOOK(parent) && Tab::isLabel(GTK_NOTEBOOK(parent), widget))
0364         return;
0365 
0366     /*
0367       check event mask (for now we only need to do that for GtkWindow)
0368       The idea is that if the window has been set to receive button_press
0369       and button_release events (which is not done by default), it likely
0370       means that it does something with such events, in which case we should
0371       not use them for grabbing
0372     */
0373     if (oneOf(gTypeName(widget), "GtkWindow") &&
0374         (gtk_widget_get_events(widget) &
0375          (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK)))
0376         return;
0377 
0378     GtkWidgetProps props(widget);
0379     if (!isFakeGtk() && !props->wmMoveHacked) {
0380         props->wmMoveHacked = true;
0381         gtk_widget_add_events(widget, GDK_BUTTON_RELEASE_MASK |
0382                               GDK_BUTTON_PRESS_MASK | GDK_LEAVE_NOTIFY_MASK |
0383                               GDK_BUTTON1_MOTION_MASK);
0384         registerBtnReleaseHook();
0385         props->wmMoveDestroy.conn("destroy-event", destroy);
0386         props->wmMoveStyleSet.conn("style-set", styleSet);
0387         props->wmMoveMotion.conn("motion-notify-event", motion);
0388         props->wmMoveLeave.conn("leave-notify-event", leave);
0389         props->wmMoveButtonPress.conn("button-press-event", buttonPress);
0390     }
0391 }
0392 
0393 }
0394 }