Introduction

A simple example showing how to make it possible to drag and reposition GTK+ widgets with the mouse. Code very similar to this is used in the Drag-n-Drop screen layout editor in hazzy.

Synopsis

On button_press_event (called once at drag begin) get the initial location of the pointer and calculate the maximum change in position of the widget such that is will not be moved outside of the border of its parent window.

On motion-notify_event (called during drag motion) calculate the change in position of the pointer and determine the position to move the widget to, taking into account that the widget must not exceed the bounds of its parent.

Result

Example Code

move_button.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/env python

import gi

gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

class MoveButton(Gtk.Fixed):

    def __init__(self):
        Gtk.Fixed.__init__(self)

        # Initial event position
        self.initial_x = 0
        self.initial_y = 0

        # Initial button position
        self.initial_pos_x = 0
        self.initial_pos_y = 0


    # Called on "drag start"
    def button_press_event(self, widget, event):

        # Get the parent window size
        pw = self.get_allocation().width
        ph = self.get_allocation().height

        # Get the button location and size
        x = self.child_get_property(widget, 'x')
        y = self.child_get_property(widget, 'y')
        w = widget.get_allocation().width
        h = widget.get_allocation().height

        # Calculate the maximum change in position such that the
        # button does not move outside the bounds of its parent
        self.dx_max = pw - (x + w)
        self.dy_max = ph - (y + h)

        # Save the initial values
        self.initial_x = event.x_root
        self.initial_y = event.y_root
        self.initial_pos_x = x
        self.initial_pos_y = y

    # Called on "drag motion"
    def motion_notify_event(self, widget, event):

        # Calculate the change in position of the pointer
        dx = event.x_root - self.initial_x
        dy = event.y_root - self.initial_y

        # Determine the new position such that the button
        # does not move outside the bounds of its parent
        x = self.initial_pos_x + max(min(dx, self.dx_max), -self.initial_pos_x)
        y = self.initial_pos_y + max(min(dy, self.dy_max), -self.initial_pos_y)

        # Actualy move the button
        self.child_set_property(widget, 'x', x)
        self.child_set_property(widget, 'y', y)


def make_button(text):

    # Helper function to make the buttons
    b = Gtk.Button.new_with_label(text)
    b.connect("button_press_event", move_btn.button_press_event)
    b.connect("motion_notify_event", move_btn.motion_notify_event)
    b.show()
    return b

# New top level window
window = Gtk.Window()
window.connect("destroy", Gtk.main_quit)

# Initialize the MoveButton container
move_btn = MoveButton()
window.add(move_btn)

# Add some buttons
move_btn.put(make_button("A button"), 50, 50)
move_btn.put(make_button("Another button"), 250, 100)

window.show_all()

# Start the main loop
Gtk.main()