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