1#
2# Reimplementation of gtk.Layout in python
3# Example on how to implement a scrollable container in python
4#
5# Johan Dahlin <johan@gnome.org>, 2006
6#
7# Requires PyGTK 2.8.0 or later
8
9import pygtk
10pygtk.require('2.0')
11import gobject
12import gtk
13from gtk import gdk
14
15class Child:
16    widget = None
17    x = 0
18    y = 0
19
20def set_adjustment_upper(adj, upper, always_emit):
21    changed = False
22    value_changed = False
23
24    min = max(0.0, upper - adj.page_size)
25
26    if upper != adj.upper:
27        adj.upper = upper
28        changed = True
29
30    if adj.value > min:
31        adj.value = min
32        value_changed = True
33
34    if changed or always_emit:
35        adj.changed()
36    if value_changed:
37        adj.value_changed()
38
39def new_adj():
40    return gtk.Adjustment(0.0, 0.0, 0.0,
41                          0.0, 0.0, 0.0)
42
43class Layout(gtk.Container):
44    __gsignals__ = dict(set_scroll_adjustments=
45                        (gobject.SIGNAL_RUN_LAST, None,
46                         (gtk.Adjustment, gtk.Adjustment)))
47    def __init__(self):
48        self._children = []
49        self._width = 100
50        self._height = 100
51        self._hadj = None
52        self._vadj = None
53        self._bin_window = None
54        self._hadj_changed_id = -1
55        self._vadj_changed_id = -1
56        gtk.Container.__init__(self)
57
58        if not self._hadj or not self._vadj:
59            self._set_adjustments(self._vadj or new_adj(),
60                                  self._hadj or new_adj())
61
62    # Public API
63
64    def put(self, widget, x=0, y=0):
65        child = Child()
66        child.widget = widget
67        child.x = x
68        child.y = y
69        self._children.append(child)
70
71        if self.flags() & gtk.REALIZED:
72            widget.set_parent_window(self._bin_window)
73
74        widget.set_parent(self)
75
76    def set_size(self, width, height):
77        if self._width != width:
78            self._width = width
79        if self._height != height:
80            self._height = height
81        if self._hadj:
82            set_adjustment_upper(self._hadj, self._width, False)
83        if self._vadj:
84            set_adjustment_upper(self._vadj, self._height, False)
85
86        if self.flags() & gtk.REALIZED:
87            self._bin_window.resize(max(width, self.allocation.width),
88                                    max(height, self.allocation.height))
89
90    # GtkWidget
91
92    def do_realize(self):
93        self.set_flags(gtk.REALIZED)
94
95        self.window = gdk.Window(
96            self.get_parent_window(),
97            window_type=gdk.WINDOW_CHILD,
98            x=self.allocation.x,
99            y=self.allocation.y,
100            width=self.allocation.width,
101            height=self.allocation.height,
102            wclass=gdk.INPUT_OUTPUT,
103            colormap=self.get_colormap(),
104            event_mask=gdk.VISIBILITY_NOTIFY_MASK)
105        self.window.set_user_data(self)
106
107        self._bin_window = gdk.Window(
108            self.window,
109            window_type=gdk.WINDOW_CHILD,
110            x=int(-self._hadj.value),
111            y=int(-self._vadj.value),
112            width=max(self._width, self.allocation.width),
113            height=max(self._height, self.allocation.height),
114            colormap=self.get_colormap(),
115            wclass=gdk.INPUT_OUTPUT,
116            event_mask=(self.get_events() | gdk.EXPOSURE_MASK |
117                        gdk.SCROLL_MASK))
118        self._bin_window.set_user_data(self)
119
120        self.set_style(self.style.attach(self.window))
121        self.style.set_background(self.window, gtk.STATE_NORMAL)
122        self.style.set_background(self._bin_window, gtk.STATE_NORMAL)
123
124        for child in self._children:
125            child.widget.set_parent_window(self._bin_window)
126        self.queue_resize()
127
128    def do_unrealize(self):
129        self._bin_window.set_user_data(None)
130        self._bin_window.destroy()
131        self._bin_window = None
132        gtk.Container.do_unrealize(self)
133
134    def _do_style_set(self, style):
135        gtk.Widget.do_style_set(self, style)
136
137        if self.flags() & gtk.REALIZED:
138            self.style.set_background(self._bin_window, gtk.STATE_NORMAL)
139
140    def do_expose_event(self, event):
141        if event.window != self._bin_window:
142            return False
143        gtk.Container.do_expose_event(self, event)
144        return False
145
146    def do_map(self):
147        self.set_flags(gtk.MAPPED)
148        for child in self._children:
149            flags = child.widget.flags()
150            if flags & gtk.VISIBLE:
151                if not (flags & gtk.MAPPED):
152                    child.widget.map()
153        self._bin_window.show()
154        self.window.show()
155
156    def do_size_request(self, req):
157        req.width = 0
158        req.height = 0
159        for child in self._children:
160            child.widget.size_request()
161
162    def do_size_allocate(self, allocation):
163        self.allocation = allocation
164        for child in self._children:
165            self._allocate_child(child)
166
167        if self.flags() & gtk.REALIZED:
168            self.window.move_resize(*allocation)
169            self._bin_window.resize(max(self._width, allocation.width),
170                                    max(self._height, allocation.height))
171
172        self._hadj.page_size = allocation.width
173        self._hadj.page_increment = allocation.width * 0.9
174        self._hadj.lower = 0
175        set_adjustment_upper(self._hadj,
176                             max(allocation.width, self._width), True)
177
178        self._vadj.page_size = allocation.height
179        self._vadj.page_increment = allocation.height * 0.9
180        self._vadj.lower = 0
181        self._vadj.upper = max(allocation.height, self._height)
182        set_adjustment_upper(self._vadj,
183                             max(allocation.height, self._height), True)
184
185    def do_set_scroll_adjustments(self, hadj, vadj):
186        self._set_adjustments(hadj, vadj)
187
188    # GtkContainer
189
190    def do_forall(self, include_internals, callback, data):
191        for child in self._children:
192            callback(child.widget, data)
193
194    def do_add(self, widget):
195        self.put(widget)
196
197    def do_remove(self, widget):
198        child = self._get_child_from_widget(widget)
199        self._children.remove(child)
200        widget.unparent()
201
202    # Private
203
204    def _set_adjustments(self, hadj, vadj):
205        if not hadj and self._hadj:
206            hadj = new_adj()
207
208        if not vadj and self._vadj:
209            vadj = new_adj()
210
211        if self._hadj and self._hadj != hadj:
212            self._hadj.disconnect(self._hadj_changed_id)
213
214        if self._vadj and self._vadj != vadj:
215            self._vadj.disconnect(self._vadj_changed_id)
216
217        need_adjust = False
218
219        if self._hadj != hadj:
220            self._hadj = hadj
221            set_adjustment_upper(hadj, self._width, False)
222            self._hadj_changed_id = hadj.connect(
223                "value-changed",
224                self._adjustment_changed)
225            need_adjust = True
226
227        if self._vadj != vadj:
228            self._vadj = vadj
229            set_adjustment_upper(vadj, self._height, False)
230            self._vadj_changed_id = vadj.connect(
231                "value-changed",
232                self._adjustment_changed)
233            need_adjust = True
234
235        if need_adjust and vadj and hadj:
236            self._adjustment_changed()
237
238    def _adjustment_changed(self, adj=None):
239        if self.flags() & gtk.REALIZED:
240            self._bin_window.move(int(-self._hadj.value),
241                                  int(-self._vadj.value))
242            self._bin_window.process_updates(True)
243
244    def _get_child_from_widget(self, widget):
245        for child in self._children:
246            if child.widget == widget:
247                return child
248        else:
249            raise AssertionError
250
251    def _allocate_child(self, child):
252        allocation = gdk.Rectangle()
253        allocation.x = child.x
254        allocation.y = child.y
255        req = child.widget.get_child_requisition()
256        allocation.width = req[0]
257        allocation.height = req[1]
258        child.widget.size_allocate(allocation)
259
260Layout.set_set_scroll_adjustments_signal('set-scroll-adjustments')
261
262def main():
263    window = gtk.Window()
264    window.set_size_request(300, 300)
265    window.connect('delete-event', gtk.main_quit)
266
267    sw = gtk.ScrolledWindow()
268    sw.set_policy(gtk.POLICY_ALWAYS, gtk.POLICY_ALWAYS)
269    window.add(sw)
270
271    layout = Layout()
272    layout.set_size(1000, 1000)
273    sw.add(layout)
274
275    b = gtk.Button('foobar')
276    layout.put(b, 100, 100)
277
278    window.show_all()
279    gtk.main()
280
281if __name__ == '__main__':
282    main()
283