Skip to content

Instantly share code, notes, and snippets.

@KurtJacobson
Last active March 24, 2023 21:37
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KurtJacobson/57679e5036dc78e6a7a3ba5e0155dad1 to your computer and use it in GitHub Desktop.
Save KurtJacobson/57679e5036dc78e6a7a3ba5e0155dad1 to your computer and use it in GitHub Desktop.
Re-positioning Gtk widget with a mouse drag in C and Python
#include <gtk/gtk.h>
// Source:
// http://www.linuxforums.org/forum/programming-scripting/117713-gtk-moving-widget-mouse-post906490.html#post906490
// higher values make movement more performant
// lower values make movement smoother
const gint Sensitivity = 1;
const gint EvMask = GDK_BUTTON_PRESS_MASK | GDK_BUTTON1_MOTION_MASK;
GtkWidget *fixed;
int offsetx, offsety, px, py, maxx, maxy;
inline static int Min(const int a, const int b) { return b < a ? b : a; }
inline static int Max(const int a, const int b) { return b > a ? b : a; }
inline static int RoundDownToMultiple(const int i, const int m)
{
return i/m*m;
}
inline static int RoundToNearestMultiple(const int i, const int m)
{
if (i % m > (double)m / 2.0d)
return (i/m+1)*m;
return i/m*m;
}
static void destroy( GtkWidget *widget, gpointer data ) { gtk_main_quit (); }
static gboolean button_press_event( GtkWidget *w, GdkEventButton *event )
{
if (event->button == 1) {
GtkWidget* p = w->parent;
// offset == distance of parent widget from edge of screen ...
gdk_window_get_position(p->window, &offsetx, &offsety);
// plus distance from pointer to edge of widget
offsetx += (int)event->x;
offsety += (int)event->y;
// maxx, maxy both relative to the parent
// note that we're rounding down now so that these max values don't get
// rounded upward later and push the widget off the edge of its parent.
maxx = RoundDownToMultiple(p->allocation.width - w->allocation.width, Sensitivity);
maxy = RoundDownToMultiple(p->allocation.height - w->allocation.height, Sensitivity);
}
return TRUE;
}
static gboolean motion_notify_event( GtkWidget *widget, GdkEventMotion *event )
{
// x_root,x_root relative to screen
// x,y relative to parent (fixed widget)
// px,py stores previous values of x,y
// get starting values for x,y
int x = (int)event->x_root - offsetx;
int y = (int)event->y_root - offsety;
// make sure the potential coordinates x,y:
// 1) will not push any part of the widget outside of its parent container
// 2) is a multiple of Sensitivity
x = RoundToNearestMultiple(Max(Min(x, maxx), 0), Sensitivity);
y = RoundToNearestMultiple(Max(Min(y, maxy), 0), Sensitivity);
if (x != px || y != py) {
px = x;
py = y;
gtk_fixed_move(GTK_FIXED(fixed), widget, x, y);
}
return TRUE;
}
GtkWidget* make_button(const gchar* const text)
{
GtkWidget* b = gtk_button_new_with_label(text);
gtk_widget_add_events(b, EvMask);
gtk_signal_connect(GTK_OBJECT(b), "button_press_event", (GtkSignalFunc)button_press_event, NULL);
gtk_signal_connect(GTK_OBJECT(b), "motion_notify_event", (GtkSignalFunc)motion_notify_event, NULL);
gtk_widget_show(b);
return b;
}
int main(int argc, char *argv[] )
{
gtk_init (&argc, &argv);
// top level window
GtkWidget* window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_container_set_border_width (GTK_CONTAINER (window), 0);
g_signal_connect (G_OBJECT (window), "destroy",G_CALLBACK (destroy), NULL);
// fixed container
fixed=gtk_fixed_new();
gtk_container_add((GtkContainer*)window,(GtkWidget*)fixed);
gtk_widget_show (fixed);
// buttons
gtk_fixed_put(GTK_FIXED(fixed), make_button("A button"), 50, 50);
gtk_fixed_put(GTK_FIXED(fixed), make_button("Another button"), 250, 100);
gtk_widget_show (window);
gtk_main ();
return 0;
}
#!/usr/bin/env python
import os
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
from gi.repository import GObject
# higher values make movement more performant
# lower values make movement smoother
SENSITIVITY = 1
EvMask = Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON1_MOTION_MASK
offsetx = 0
offsety = 0
px = 0
py = 0
maxx = 0
maxy = 0
def Min(a, b):
if b < a:
return b
return a
def Max(a, b):
if b > a:
return b
return a
def RoundDownToMultiple(i, m):
return i/m*m
def RoundToNearestMultiple(i, m):
if i % m > m / 2:
return (i/m+1)*m
return i/m*m
def button_press_event(w, event):
if event.button == 1:
p = w.get_parent()
# offset == distance of parent widget from edge of screen ...
global offsetx, offsety
offsetx, offsety = p.get_window().get_position()
# plus distance from pointer to edge of widget
offsetx += event.x
offsety += event.y
# maxx, maxy both relative to the parent
# note that we're rounding down now so that these max values don't get
# rounded upward later and push the widget off the edge of its parent.
global maxx, maxy
maxx = RoundDownToMultiple(p.get_allocation().width - w.get_allocation().width, SENSITIVITY)
maxy = RoundDownToMultiple(p.get_allocation().height - w.get_allocation().height, SENSITIVITY)
def motion_notify_event(widget, event):
# x_root,x_root relative to screen
# x,y relative to parent (fixed widget)
# px,py stores previous values of x,y
global px, py
global offsetx, offsety
# get starting values for x,y
x = event.x_root - offsetx
y = event.y_root - offsety
# make sure the potential coordinates x,y:
# 1) will not push any part of the widget outside of its parent container
# 2) is a multiple of SENSITIVITY
x = RoundToNearestMultiple(Max(Min(x, maxx), 0), SENSITIVITY)
y = RoundToNearestMultiple(Max(Min(y, maxy), 0), SENSITIVITY)
if x != px or y != py:
px = x
py = y
fixed.move(widget, x, y)
def make_button(text):
b = Gtk.Button.new_with_label(text)
b.set_events(EvMask)
b.connect("button_press_event", button_press_event)
b.connect("motion_notify_event", motion_notify_event)
b.show()
return b
# top level window
window = Gtk.Window()
window.connect("destroy", Gtk.main_quit)
# fixed container
fixed = Gtk.Fixed()
window.add(fixed)
# buttons
fixed.put(make_button("A button"), 50, 50)
fixed.put(make_button("Another button"), 250, 100)
window.show_all()
Gtk.main()
@KurtJacobson
Copy link
Author

Here are the erratic position values returned by 'motion-notify-event':

erratic

And a video comparing the two examples:
https://youtu.be/mLjwxWCYP2Y

@KurtJacobson
Copy link
Author

KurtJacobson commented Aug 18, 2017

This problem was solved by using the event position relative to the screen root rather than to the window by changing

    # get starting values for x,y
    x = event.x - offsetx
    y = event.y - offsety

to

    # get starting values for x,y
    x = event.x_root - offsetx
    y = event.y_root - offsety

@bretantonio
Copy link

How do I do this with C++

@Fenshu28
Copy link

I get these errors, why is it?
Captura desde 2022-10-27 17-48-43

@Fenshu28
Copy link

I get these errors, why is it? Captura desde 2022-10-27 17-48-43

I already corrected the errors, they were due to ambiguities, the updated code for GTK 3 in C:
https://gist.github.com/Fenshu28/cb91acffd95d5a9cee6da2e5e61da5ae

@crab2313
Copy link

I recently encountered this problem and write a GTK4 version of this.

https://gist.github.com/crab2313/9b1087677388e4d9156b5668b1794349

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment