~ubuntu-branches/ubuntu/natty/miro/natty

« back to all changes in this revision

Viewing changes to portable/frontends/widgets/cellpack.py

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2011-01-22 02:46:33 UTC
  • mfrom: (1.4.10 upstream) (1.7.5 experimental)
  • Revision ID: james.westby@ubuntu.com-20110122024633-kjme8u93y2il5nmf
Tags: 3.5.1-1ubuntu1
* Merge from debian.  Remaining ubuntu changes:
  - Use python 2.7 instead of python 2.6
  - Relax dependency on python-dbus to >= 0.83.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Miro - an RSS based video player application
2
 
# Copyright (C) 2005-2010 Participatory Culture Foundation
3
 
#
4
 
# This program is free software; you can redistribute it and/or modify
5
 
# it under the terms of the GNU General Public License as published by
6
 
# the Free Software Foundation; either version 2 of the License, or
7
 
# (at your option) any later version.
8
 
#
9
 
# This program is distributed in the hope that it will be useful,
10
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
# GNU General Public License for more details.
13
 
#
14
 
# You should have received a copy of the GNU General Public License
15
 
# along with this program; if not, write to the Free Software
16
 
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17
 
#
18
 
# In addition, as a special exception, the copyright holders give
19
 
# permission to link the code of portions of this program with the OpenSSL
20
 
# library.
21
 
#
22
 
# You must obey the GNU General Public License in all respects for all of
23
 
# the code used other than OpenSSL. If you modify file(s) with this
24
 
# exception, you may extend this exception to your version of the file(s),
25
 
# but you are not obligated to do so. If you do not wish to do so, delete
26
 
# this exception statement from your version. If you delete this exception
27
 
# statement from all source files in the program, then also delete it here.
28
 
 
29
 
"""``miro.frontends.widgets.cellpack`` -- Code to layout
30
 
CustomTableCells.
31
 
 
32
 
We use the hbox/vbox model to lay things out with a couple changes.
33
 
The main difference here is that layouts are one-shot.  We don't keep
34
 
state around inside the cell renderers, so we just set up the objects
35
 
at the start, then use them to calculate info.
36
 
"""
37
 
 
38
 
class Margin(object):
39
 
    """Helper object used to calculate margins.
40
 
    """
41
 
    def __init__(self , margin):
42
 
        if margin is None:
43
 
            margin = (0, 0, 0, 0)
44
 
        self.margin_left = margin[3]
45
 
        self.margin_top = margin[0]
46
 
        self.margin_width = margin[1] + margin[3]
47
 
        self.margin_height = margin[0] + margin[2]
48
 
 
49
 
    def inner_rect(self, x, y, width, height):
50
 
        """Returns the x, y, width, height of the inner
51
 
        box.
52
 
        """
53
 
        return (x + self.margin_left,
54
 
                y + self.margin_top,
55
 
                width - self.margin_width,
56
 
                height - self.margin_height)
57
 
 
58
 
    def outer_size(self, inner_size):
59
 
        """Returns the width, height of the outer box.
60
 
        """
61
 
        return (inner_size[0] + self.margin_width,
62
 
                inner_size[1] + self.margin_height)
63
 
 
64
 
    def point_in_margin(self, x, y, width, height):
65
 
        """Returns whether a given point is inside of the
66
 
        margins.
67
 
        """
68
 
        return ((0 <= x - self.margin_left < width - self.margin_width) and
69
 
                (0 <= y  - self.margin_top < height - self.margin_height))
70
 
 
71
 
class Packing(object):
72
 
    """Helper object used to layout Boxes.
73
 
    """
74
 
    def __init__(self, child, expand):
75
 
        self.child = child
76
 
        self.expand = expand
77
 
 
78
 
    def calc_size(self, translate_func):
79
 
        return translate_func(*self.child.get_size())
80
 
 
81
 
    def draw(self, context, x, y, width, height):
82
 
        self.child.draw(context, x, y, width, height)
83
 
 
84
 
class WhitespacePacking(object):
85
 
    """Helper object used to layout Boxes.
86
 
    """
87
 
    def __init__(self, size, expand):
88
 
        self.size = size
89
 
        self.expand = expand
90
 
 
91
 
    def calc_size(self, translate_func):
92
 
        return self.size, 0
93
 
 
94
 
    def draw(self, context, x, y, width, height):
95
 
        pass
96
 
 
97
 
class Packer(object):
98
 
    """Base class packing objects.  Packer objects work similarly to widgets,
99
 
    but they only used in custom cell renderers so there's a couple
100
 
    differences.  The main difference is that cell renderers don't keep state
101
 
    around.  Therefore Packers just get set up, used, then discarded.
102
 
    Also Packers can't receive events directly, so they have a different
103
 
    system to figure out where mouse clicks happened (the Hotspot class).
104
 
    """
105
 
 
106
 
    def render_layout(self, context):
107
 
        """position the child elements then call draw() on them."""
108
 
        self._layout(context, 0, 0, context.width, context.height)
109
 
 
110
 
    def draw(self, context, x, y, width, height):
111
 
        """Included so that Packer objects have a draw() method that matches
112
 
        ImageSurfaces, TextBoxes, etc.
113
 
        """
114
 
        self._layout(context, x, y, width, height)
115
 
 
116
 
    def _find_child_at(self, x, y, width, height):
117
 
        raise NotImplementedError()
118
 
 
119
 
    def get_size(self):
120
 
        """Get the minimum size required to hold the Packer.  """
121
 
        try:
122
 
            return self._size
123
 
        except AttributeError:
124
 
            self._size = self._calc_size()
125
 
            return self._size
126
 
 
127
 
    def get_current_size(self):
128
 
        """Get the minimum size required to hold the Packer at this point
129
 
 
130
 
        Call this method if you are going to change the packer after the call,
131
 
        for example if you have more children to pack into a box.  get_size()
132
 
        saves caches it's result which is can mess things up.
133
 
        """
134
 
        return self._calc_size()
135
 
 
136
 
    def find_hotspot(self, x, y, width, height):
137
 
        """Find the hotspot at (x, y).  width and height are the size of the
138
 
        cell this Packer is rendering.
139
 
 
140
 
        If a hotspot is found, return the tuple (name, x, y, width, height)
141
 
        where name is the name of the hotspot, x, y is the position relative
142
 
        to the top-left of the hotspot area and width, height are the
143
 
        dimensions of the hotspot.
144
 
 
145
 
        If no Hotspot is found return None.
146
 
        """
147
 
        child_pos = self._find_child_at(x, y, width, height)
148
 
        if child_pos:
149
 
            child, child_x, child_y, child_width, child_height = child_pos
150
 
            try:
151
 
                return child.find_hotspot(x - child_x, y - child_y,
152
 
                        child_width, child_height)
153
 
            except AttributeError:
154
 
                pass # child is a TextBox, Button or something like that
155
 
        return None
156
 
 
157
 
    def _layout(self, context, x, y, width, height):
158
 
        """Layout our children and call ``draw()`` on them.
159
 
        """
160
 
        raise NotImplementedError()
161
 
 
162
 
    def _calc_size(self):
163
 
        """Calculate the size needed to hold the box.  The return value gets
164
 
        cached and return in ``get_size()``.
165
 
        """
166
 
        raise NotImplementedError()
167
 
 
168
 
class Box(Packer):
169
 
    """Box is the base class for VBox and HBox.  Box objects lay out children
170
 
    linearly either left to right or top to bottom.
171
 
    """
172
 
 
173
 
    def __init__(self, spacing=0):
174
 
        """Create a new Box.  spacing is the amount of space to place
175
 
        in-between children.
176
 
        """
177
 
        self.spacing = spacing
178
 
        self.children = []
179
 
        self.children_end = []
180
 
        self.expand_count = 0
181
 
 
182
 
    def pack(self, child, expand=False):
183
 
        """Add a new child to the box.  The child will be placed after all the
184
 
        children packed before with pack_start.
185
 
 
186
 
        :param child: child to pack.  It can be anything with a
187
 
                      ``get_size()`` method, including TextBoxes,
188
 
                      ImageSurfarces, Buttons, Boxes and Backgrounds.
189
 
        :param expand: If True, then the child will enlarge if space
190
 
                       available is more than the space required.
191
 
        """
192
 
        if not (hasattr(child, 'draw') and hasattr(child, 'get_size')):
193
 
            raise TypeError("%s can't be drawn" % child)
194
 
        self.children.append(Packing(child, expand))
195
 
        if expand:
196
 
            self.expand_count += 1
197
 
 
198
 
    def pack_end(self, child, expand=False):
199
 
        """Add a new child to the end box.  The child will be placed before
200
 
        all the children packed before with pack_end.
201
 
 
202
 
        :param child: child to pack.  It can be anything with a
203
 
                      ``get_size()`` method, including TextBoxes,
204
 
                      ImageSurfarces, Buttons, Boxes and Backgrounds.
205
 
        :param expand: If True, then the child will enlarge if space
206
 
                       available is more than the space required.
207
 
        """
208
 
        if not (hasattr(child, 'draw') and hasattr(child, 'get_size')):
209
 
            raise TypeError("%s can't be drawn" % child)
210
 
        self.children_end.append(Packing(child, expand))
211
 
        if expand:
212
 
            self.expand_count += 1
213
 
 
214
 
    def pack_space(self, size, expand=False):
215
 
        """Pack whitespace into the box.
216
 
        """
217
 
        self.children.append(WhitespacePacking(size, expand))
218
 
        if expand:
219
 
            self.expand_count += 1
220
 
 
221
 
    def pack_space_end(self, size, expand=False):
222
 
        """Pack whitespace into the end of box.
223
 
        """
224
 
        self.children_end.append(WhitespacePacking(size, expand))
225
 
        if expand:
226
 
            self.expand_count += 1
227
 
 
228
 
    def _calc_size(self):
229
 
        length = 0
230
 
        breadth = 0 
231
 
        for packing in self.children + self.children_end:
232
 
            child_length, child_breadth = packing.calc_size(self._translate)
233
 
            length += child_length
234
 
            breadth = max(breadth, child_breadth)
235
 
        total_children = len(self.children) + len(self.children_end)
236
 
        length += self.spacing * (total_children - 1)
237
 
        return self._translate(length, breadth)
238
 
 
239
 
    def _extra_space_iter(self, total_extra_space):
240
 
        """Generate the amount of extra space for children with expand set."""
241
 
        if total_extra_space <= 0:
242
 
            while True:
243
 
                yield 0
244
 
        average_extra_space, leftover = \
245
 
                divmod(total_extra_space, self.expand_count)
246
 
        while leftover > 1:
247
 
            # expand_count doesn't divide equally into total_extra_space,
248
 
            # yield average_extra_space+1 for each extra pixel
249
 
            yield average_extra_space + 1
250
 
            leftover -= 1
251
 
        # if there's a fraction of a pixel leftover, add that in
252
 
        yield average_extra_space + leftover 
253
 
        while True:
254
 
            # no more leftover space
255
 
            yield average_extra_space
256
 
 
257
 
    def _position_children(self, total_length):
258
 
        my_length, my_breadth = self._translate(*self.get_size())
259
 
        extra_space_iter = self._extra_space_iter(total_length - my_length)
260
 
 
261
 
        pos = 0
262
 
        for packing in self.children:
263
 
            child_length, child_breadth = packing.calc_size(self._translate)
264
 
            if packing.expand:
265
 
                child_length += extra_space_iter.next()
266
 
            yield packing, pos, child_length
267
 
            pos += child_length + self.spacing
268
 
 
269
 
        pos = total_length
270
 
        for packing in self.children_end:
271
 
            child_length, child_breadth = packing.calc_size(self._translate)
272
 
            if packing.expand:
273
 
                child_length += extra_space_iter.next()
274
 
            pos -= child_length
275
 
            yield packing, pos, child_length
276
 
            pos -= self.spacing
277
 
 
278
 
    def _layout(self, context, x, y, width, height):
279
 
        total_length, total_breadth = self._translate(width, height)
280
 
        pos, offset = self._translate(x, y)
281
 
        position_iter = self._position_children(total_length)
282
 
        for packing, child_pos, child_length in position_iter:
283
 
            x, y = self._translate(pos + child_pos, offset)
284
 
            width, height = self._translate(child_length, total_breadth)
285
 
            packing.draw(context, x, y, width, height)
286
 
 
287
 
    def _find_child_at(self, x, y, width, height):
288
 
        total_length, total_breadth = self._translate(width, height)
289
 
        pos, offset = self._translate(x, y)
290
 
        position_iter = self._position_children(total_length)
291
 
        for packing, child_pos, child_length in position_iter:
292
 
            if child_pos <= pos < child_pos + child_length:
293
 
                x, y = self._translate(child_pos, 0)
294
 
                width, height = self._translate(child_length, total_breadth)
295
 
                return packing.child, x, y, width, height
296
 
            elif child_pos > pos:
297
 
                break
298
 
        return None
299
 
 
300
 
    def _translate(self, x, y):
301
 
        """Translate (x, y) coordinates into (length, breadth) and
302
 
        vice-versa.
303
 
        """
304
 
        raise NotImplementedError()
305
 
 
306
 
class HBox(Box):
307
 
    def _translate(self, x, y):
308
 
        return x, y
309
 
 
310
 
class VBox(Box):
311
 
    def _translate(self, x, y):
312
 
        return y, x
313
 
 
314
 
class Table(Packer):
315
 
    def __init__(self, row_length=1, col_length=1,
316
 
                 row_spacing=0, col_spacing=0):
317
 
        """Create a new Table.
318
 
 
319
 
        :param row_length: how many rows long this should be
320
 
        :param col_length: how many rows wide this should be
321
 
        :param row_spacing: amount of spacing (in pixels) between rows
322
 
        :param col_spacing: amount of spacing (in pixels) between columns
323
 
        """
324
 
        assert min(row_length, col_length) > 0
325
 
        assert isinstance(row_length, int) and isinstance(col_length, int)
326
 
        self.row_length = row_length
327
 
        self.col_length = col_length
328
 
        self.row_spacing = row_spacing
329
 
        self.col_spacing = col_spacing
330
 
        self.table_multiarray = self._generate_table_multiarray()
331
 
 
332
 
    def _generate_table_multiarray(self):
333
 
        table_multiarray = []
334
 
        row_count = 0
335
 
        table_multiarray = [
336
 
            [None for col in range(self.col_length)]
337
 
            for row in range(self.row_length)]
338
 
        return table_multiarray
339
 
 
340
 
    def pack(self, child, row, column, expand=False):
341
 
        # TODO: flesh out "expand" ability, maybe?
342
 
        #
343
 
        # possibly throw a special exception if outside the range.
344
 
        # For now, just allowing an IndexError to be thrown.
345
 
        self.table_multiarray[row][column] = Packing(child, expand)
346
 
    
347
 
    def _get_grid_sizes(self):
348
 
        """Get the width and eights for both rows and columns
349
 
        """
350
 
        row_sizes = {}
351
 
        col_sizes = {}
352
 
        for row_count, row in enumerate(self.table_multiarray):
353
 
            row_sizes.setdefault(row_count, 0)
354
 
            for col_count, col_packing in enumerate(row):
355
 
                col_sizes.setdefault(col_count, 0)
356
 
                if col_packing:
357
 
                    x, y = col_packing.calc_size(self._translate)
358
 
                    if y > row_sizes[row_count]:
359
 
                        row_sizes[row_count] = y
360
 
                    if x > col_sizes[col_count]:
361
 
                        col_sizes[col_count] = x
362
 
        return col_sizes, row_sizes
363
 
 
364
 
    def _find_child_at(self, x, y, width, height):
365
 
        col_sizes, row_sizes = self._get_grid_sizes()
366
 
        row_distance = 0
367
 
        for row_count, row in enumerate(self.table_multiarray):
368
 
            col_distance = 0
369
 
            for col_count, packing in enumerate(row):
370
 
                child_width, child_height = packing.calc_size(self._translate)
371
 
                if packing.child:
372
 
                    if (col_distance <= x < col_distance + child_width
373
 
                            and row_distance <= y < row_distance + child_height):
374
 
                        return (packing.child,
375
 
                                col_distance, row_distance,
376
 
                                child_width, child_height)
377
 
                col_distance += col_sizes[col_count] + self.col_spacing
378
 
            row_distance += row_sizes[row_count] + self.row_spacing
379
 
 
380
 
    def _calc_size(self):
381
 
        col_sizes, row_sizes = self._get_grid_sizes()
382
 
        x = sum(col_sizes.values()) + ((self.col_length - 1) * self.col_spacing)
383
 
        y = sum(row_sizes.values()) + ((self.row_length - 1) * self.row_spacing)
384
 
        return x, y
385
 
        
386
 
    def _layout(self, context, x, y, width, height):
387
 
        col_sizes, row_sizes = self._get_grid_sizes()
388
 
  
389
 
        row_distance = 0
390
 
        for row_count, row in enumerate(self.table_multiarray):
391
 
            col_distance = 0
392
 
            for col_count, packing in enumerate(row):
393
 
                if packing:
394
 
                    child_width, child_height = packing.calc_size(self._translate)
395
 
                    packing.child.draw(context,
396
 
                                       x + col_distance, y + row_distance,
397
 
                                       child_width, child_height)
398
 
                col_distance += col_sizes[col_count] + self.col_spacing
399
 
            row_distance += row_sizes[row_count] + self.row_spacing
400
 
 
401
 
    def _translate(self, x, y):
402
 
        return x, y
403
 
 
404
 
 
405
 
class Alignment(Packer):
406
 
    """Positions a child inside a larger space.
407
 
    """
408
 
    def __init__(self, child, xscale=1.0, yscale=1.0, xalign=0.0, yalign=0.0,
409
 
            min_width=0, min_height=0):
410
 
        self.child = child
411
 
        self.xscale = xscale
412
 
        self.yscale = yscale
413
 
        self.xalign = xalign
414
 
        self.yalign = yalign
415
 
        self.min_width = min_width
416
 
        self.min_height = min_height
417
 
 
418
 
    def _calc_size(self):
419
 
        width, height = self.child.get_size()
420
 
        return max(self.min_width, width), max(self.min_height, height)
421
 
 
422
 
    def _calc_child_position(self, width, height):
423
 
        req_width, req_height = self.child.get_size()
424
 
        child_width = req_width + self.xscale * (width-req_width)
425
 
        child_height = req_height + self.yscale * (height-req_height)
426
 
        child_x = round(self.xalign * (width - child_width))
427
 
        child_y = round(self.yalign * (height - child_height))
428
 
        return child_x, child_y, child_width, child_height
429
 
 
430
 
    def _layout(self, context, x, y, width, height):
431
 
        child_x, child_y, child_width, child_height = \
432
 
                self._calc_child_position(width, height)
433
 
        self.child.draw(context, x + child_x, y + child_y, child_width,
434
 
                child_height)
435
 
 
436
 
    def _find_child_at(self, x, y, width, height):
437
 
        child_x, child_y, child_width, child_height = \
438
 
                self._calc_child_position(width, height)
439
 
        if ((child_x <= x < child_x + child_width) and
440
 
                (child_y <= y < child_y + child_height)):
441
 
            return self.child, child_x, child_y, child_width, child_height
442
 
        else:
443
 
            return None # (x, y) is in the empty space around child
444
 
 
445
 
class DrawingArea(Packer):
446
 
    """Area that uses custom drawing code.
447
 
    """
448
 
    def __init__(self, width, height, callback, *args):
449
 
        self.width = width
450
 
        self.height = height
451
 
        self.callback_info = (callback, args)
452
 
 
453
 
    def _calc_size(self):
454
 
        return self.width, self.height
455
 
 
456
 
    def _layout(self, context, x, y, width, height):
457
 
        callback, args = self.callback_info
458
 
        callback(context, x, y, width, height, *args)
459
 
 
460
 
    def _find_child_at(self, x, y, width, height):
461
 
        return None
462
 
 
463
 
class Background(Packer):
464
 
    """Draws a background behind a child element.
465
 
    """
466
 
    def __init__(self, child, min_width=0, min_height=0, margin=None):
467
 
        self.child = child
468
 
        self.min_width = min_width
469
 
        self.min_height = min_height
470
 
        self.margin = Margin(margin)
471
 
        self.callback_info = None
472
 
 
473
 
    def set_callback(self, callback, *args):
474
 
        self.callback_info = (callback, args)
475
 
 
476
 
    def _calc_size(self):
477
 
        width, height = self.child.get_size()
478
 
        width = max(self.min_width, width)
479
 
        height = max(self.min_height, height)
480
 
        return self.margin.outer_size((width, height))
481
 
 
482
 
    def _layout(self, context, x, y, width, height):
483
 
        if self.callback_info:
484
 
            callback, args = self.callback_info
485
 
            callback(context, x, y, width, height, *args)
486
 
        self.child.draw(context, *self.margin.inner_rect(x, y, width, height))
487
 
 
488
 
    def _find_child_at(self, x, y, width, height):
489
 
        if not self.margin.point_in_margin(x, y, width, height):
490
 
            return None
491
 
        return (self.child,) + self.margin.inner_rect(0, 0, width, height)
492
 
 
493
 
class Padding(Packer):
494
 
    """Adds padding to the edges of a packer.
495
 
    """
496
 
    def __init__(self, child, top=0, right=0, bottom=0, left=0):
497
 
        self.child = child
498
 
        self.margin = Margin((top, right, bottom, left))
499
 
 
500
 
    def _calc_size(self):
501
 
        return self.margin.outer_size(self.child.get_size())
502
 
 
503
 
    def _layout(self, context, x, y, width, height):
504
 
        self.child.draw(context, *self.margin.inner_rect(x, y, width, height))
505
 
 
506
 
    def _find_child_at(self, x, y, width, height):
507
 
        if not self.margin.point_in_margin(x, y, width, height):
508
 
            return None
509
 
        return (self.child,) + self.margin.inner_rect(0, 0, width, height)
510
 
 
511
 
class TextBoxPacker(Packer):
512
 
    """Base class for ClippedTextLine and ClippedTextBox.
513
 
    """
514
 
    def _layout(self, context, x, y, width, height):
515
 
        self.textbox.draw(context, x, y, width, height)
516
 
 
517
 
    def _find_child_at(self, x, y, width, height):
518
 
        # We could return the TextBox here, but we know it doesn't have a 
519
 
        # find_hotspot() method
520
 
        return None 
521
 
 
522
 
class ClippedTextBox(TextBoxPacker):
523
 
    """A TextBox that gets clipped if it's larger than it's allocated
524
 
    width.
525
 
    """
526
 
    def __init__(self, textbox, min_width=0, min_height=0):
527
 
        self.textbox = textbox
528
 
        self.min_width = min_width
529
 
        self.min_height = min_height
530
 
 
531
 
    def _calc_size(self):
532
 
        height = max(self.min_height, self.textbox.font.line_height())
533
 
        return self.min_width, height
534
 
 
535
 
class ClippedTextLine(TextBoxPacker):
536
 
    """A single line of text that gets clipped if it's larger than the
537
 
    space allocated to it.  By default the clipping will happen at character
538
 
    boundaries.
539
 
    """
540
 
    def __init__(self, textbox, min_width=0):
541
 
        self.textbox = textbox
542
 
        self.textbox.set_wrap_style('char')
543
 
        self.min_width = min_width
544
 
 
545
 
    def _calc_size(self):
546
 
        return self.min_width, self.textbox.font.line_height()
547
 
 
548
 
class TruncatedTextLine(ClippedTextLine):
549
 
    def __init__(self, textbox, min_width=0):
550
 
        ClippedTextLine.__init__(self, textbox, min_width)
551
 
        self.textbox.set_wrap_style('truncated-char')
552
 
 
553
 
class Hotspot(Packer):
554
 
    """A Hotspot handles mouse click tracking.  It's only purpose is
555
 
    to store a name to return from ``find_hotspot()``.  In terms of
556
 
    layout, it simply renders it's child in it's allocated space.
557
 
    """
558
 
    def __init__(self, name, child):
559
 
        self.name = name
560
 
        self.child = child
561
 
 
562
 
    def _calc_size(self):
563
 
        return self.child.get_size()
564
 
 
565
 
    def _layout(self, context, x, y, width, height):
566
 
        self.child.draw(context, x, y, width, height)
567
 
 
568
 
    def find_hotspot(self, x, y, width, height):
569
 
        return self.name, x, y, width, height
570
 
 
571
 
class Stack(Packer):
572
 
    """Packer that stacks other packers on top of each other.
573
 
    """
574
 
    def __init__(self):
575
 
        self.children = []
576
 
 
577
 
    def pack(self, packer):
578
 
        self.children.append(packer)
579
 
 
580
 
    def pack_below(self, packer):
581
 
        self.children.insert(0, packer)
582
 
 
583
 
    def _layout(self, context, x, y, width, height):
584
 
        for packer in self.children:
585
 
            packer._layout(context, x, y, width, height)
586
 
 
587
 
    def _calc_size(self):
588
 
        """Calculate the size needed to hold the box.  The return value gets
589
 
        cached and return in get_size().
590
 
        """
591
 
        width = height = 0
592
 
        for packer in self.children:
593
 
            child_width, child_height = packer.get_size()
594
 
            width = max(width, child_width)
595
 
            height = max(height, child_height)
596
 
        return width, height
597
 
 
598
 
    def _find_child_at(self, x, y, width, height):
599
 
        # Return the topmost packer
600
 
        try:
601
 
            top = self.children[-1]
602
 
        except IndexError:
603
 
            return None
604
 
        else:
605
 
            return top._find_child_at(x, y, width, height)
606
 
 
607
 
def align_left(packer):
608
 
    """Align a packer to the left side of it's allocated space."""
609
 
    return Alignment(packer, xalign=0.0, xscale=0.0)
610
 
 
611
 
def align_right(packer):
612
 
    """Align a packer to the right side of it's allocated space."""
613
 
    return Alignment(packer, xalign=1.0, xscale=0.0)
614
 
 
615
 
def align_top(packer):
616
 
    """Align a packer to the top side of it's allocated space."""
617
 
    return Alignment(packer, yalign=0.0, yscale=0.0)
618
 
 
619
 
def align_bottom(packer):
620
 
    """Align a packer to the bottom side of it's allocated space."""
621
 
    return Alignment(packer, yalign=1.0, yscale=0.0)
622
 
 
623
 
def align_middle(packer):
624
 
    """Align a packer to the middle of it's allocated space."""
625
 
    return Alignment(packer, yalign=0.5, yscale=0.0)
626
 
 
627
 
def align_center(packer):
628
 
    """Align a packer to the center of it's allocated space."""
629
 
    return Alignment(packer, xalign=0.5, xscale=0.0)
630
 
 
631
 
def pad(packer, top=0, left=0, bottom=0, right=0):
632
 
    """Add padding to a packer."""
633
 
    return Padding(packer, top, right, bottom, left)