File indexing completed on 2024-04-28 13:42:28
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 }