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

« back to all changes in this revision

Viewing changes to lib/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)