~ubuntu-branches/ubuntu/oneiric/python-chaco/oneiric

« back to all changes in this revision

Viewing changes to enthought/chaco/plot_containers.py

  • Committer: Bazaar Package Importer
  • Author(s): Varun Hiremath
  • Date: 2011-07-08 20:38:02 UTC
  • mfrom: (7.2.3 sid)
  • Revision ID: james.westby@ubuntu.com-20110708203802-5t32e0ldv441yh90
Tags: 4.0.0-1
* New upstream release
* debian/control:
  - Depend on python-traitsui (Closes: #633604)
  - Bump Standards-Version to 3.9.2
* Update debian/watch file
* Remove debian/patches/* -- no longer needed

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
""" Defines various plot container classes, including stacked, grid, and overlay.
2
 
"""
3
 
# Major library imports
4
 
from numpy import amax, any, arange, array, cumsum, hstack, sum, zeros, zeros_like
5
 
 
6
 
# Enthought library imports
7
 
from enthought.traits.api import Any, Array, Either, Enum, Float, Instance, \
8
 
    List, Property, Trait, Tuple, Int
9
 
from enthought.enable.simple_layout import simple_container_get_preferred_size, \
10
 
                                            simple_container_do_layout
11
 
 
12
 
# Local relative imports
13
 
from base_plot_container import BasePlotContainer
14
 
 
15
 
 
16
 
__all__ = ["OverlayPlotContainer", "HPlotContainer", "VPlotContainer", \
17
 
           "GridPlotContainer"]
18
 
 
19
 
DEFAULT_DRAWING_ORDER = ["background", "image", "underlay",      "plot",
20
 
                         "selection", "border", "annotation", "overlay"]
21
 
 
22
 
class OverlayPlotContainer(BasePlotContainer):
23
 
    """
24
 
    A plot container that stretches all its components to fit within its
25
 
    space.  All of its components must therefore be resizable.
26
 
    """
27
 
 
28
 
    draw_order = Instance(list, args=(DEFAULT_DRAWING_ORDER,))
29
 
    
30
 
    # Do not use an off-screen backbuffer.
31
 
    use_backbuffer = False
32
 
    
33
 
    # Cache (width, height) of the container's preferred size.
34
 
    _cached_preferred_size = Tuple
35
 
 
36
 
    def get_preferred_size(self, components=None):
37
 
        """ Returns the size (width,height) that is preferred for this component.
38
 
 
39
 
        Overrides PlotComponent
40
 
        """
41
 
        return simple_container_get_preferred_size(self, components=components)
42
 
 
43
 
    def _do_layout(self):
44
 
        """ Actually performs a layout (called by do_layout()).
45
 
        """
46
 
        simple_container_do_layout(self)
47
 
        return
48
 
 
49
 
class StackedPlotContainer(BasePlotContainer):
50
 
    """
51
 
    Base class for 1-D stacked plot containers, both horizontal and vertical.
52
 
    """
53
 
 
54
 
    draw_order = Instance(list, args=(DEFAULT_DRAWING_ORDER,))
55
 
    
56
 
    # The dimension along which to stack components that are added to
57
 
    # this container.
58
 
    stack_dimension = Enum("h", "v")
59
 
    
60
 
    # The "other" dimension, i.e., the dual of the stack dimension.
61
 
    other_dimension = Enum("v", "h")
62
 
    
63
 
    # The index into obj.position and obj.bounds that corresponds to 
64
 
    # **stack_dimension**.  This is a class-level and not an instance-level 
65
 
    # attribute. It must be 0 or 1.
66
 
    stack_index = 0
67
 
    
68
 
    def get_preferred_size(self, components=None):
69
 
        """ Returns the size (width,height) that is preferred for this component.
70
 
 
71
 
        Overrides PlotComponent.
72
 
        """
73
 
        if self.fixed_preferred_size is not None:
74
 
            self._cached_preferred_size = self.fixed_preferred_size
75
 
            return self.fixed_preferred_size
76
 
 
77
 
        if self.resizable == "":
78
 
            self._cached_preferred_size = self.outer_bounds[:]
79
 
            return self.outer_bounds
80
 
        
81
 
        if components is None:
82
 
            components = self.components
83
 
 
84
 
        ndx = self.stack_index
85
 
        other_ndx = 1 - ndx
86
 
        
87
 
        no_visible_components = True
88
 
        total_size = 0
89
 
        max_other_size = 0
90
 
        for component in components:
91
 
            if not self._should_layout(component):
92
 
                continue
93
 
            
94
 
            no_visible_components = False
95
 
 
96
 
            pref_size = component.get_preferred_size()
97
 
            total_size += pref_size[ndx] + self.spacing
98
 
            if pref_size[other_ndx] > max_other_size:
99
 
                max_other_size = pref_size[other_ndx]
100
 
        
101
 
        if total_size >= self.spacing:
102
 
            total_size -= self.spacing
103
 
        
104
 
        if (self.stack_dimension not in self.resizable) and \
105
 
           (self.stack_dimension not in self.fit_components):
106
 
            total_size = self.bounds[ndx]
107
 
        elif no_visible_components or (total_size == 0):
108
 
            total_size = self.default_size[ndx]
109
 
        
110
 
        if (self.other_dimension not in self.resizable) and \
111
 
           (self.other_dimension not in self.fit_components):
112
 
            max_other_size = self.bounds[other_ndx]
113
 
        elif no_visible_components or (max_other_size == 0):
114
 
            max_other_size = self.default_size[other_ndx]
115
 
        
116
 
        if ndx == 0:
117
 
            self._cached_preferred_size = (total_size + self.hpadding,
118
 
                                           max_other_size + self.vpadding)
119
 
        else:
120
 
            self._cached_preferred_size = (max_other_size + self.hpadding,
121
 
                                           total_size + self.vpadding)
122
 
            
123
 
        return self._cached_preferred_size
124
 
 
125
 
 
126
 
    def _do_stack_layout(self, components, align):
127
 
        """ Helper method that does the actual work of layout.
128
 
        """
129
 
 
130
 
 
131
 
        size = list(self.bounds)
132
 
        if self.fit_components != "":
133
 
            self.get_preferred_size()
134
 
            if "h" in self.fit_components:
135
 
                size[0] = self._cached_preferred_size[0] - self.hpadding
136
 
            if "v" in self.fit_components:
137
 
                size[1] = self._cached_preferred_size[1] - self.vpadding
138
 
        
139
 
        ndx = self.stack_index
140
 
        other_ndx = 1 - ndx
141
 
        other_dim = self.other_dimension
142
 
 
143
 
        # Assign sizes of non-resizable components, and compute the total size
144
 
        # used by them (along the stack dimension).
145
 
        total_fixed_size = 0
146
 
        resizable_components = []
147
 
        size_prefs = {}
148
 
        total_resizable_size = 0
149
 
        
150
 
        for component in components:
151
 
            if not self._should_layout(component):
152
 
                continue
153
 
            if self.stack_dimension not in component.resizable:
154
 
                total_fixed_size += component.outer_bounds[ndx]
155
 
            else:
156
 
                preferred_size = component.get_preferred_size()
157
 
                size_prefs[component] = preferred_size
158
 
                total_resizable_size += preferred_size[ndx]
159
 
                resizable_components.append(component)
160
 
        
161
 
        new_bounds_dict = {}
162
 
 
163
 
        # Assign sizes of all the resizable components along the stack dimension
164
 
        if resizable_components:
165
 
            space = self.spacing * (len(self.components) - 1)
166
 
            avail_size = size[ndx] - total_fixed_size - space
167
 
            if total_resizable_size > 0:
168
 
                scale = avail_size / float(total_resizable_size)
169
 
                for component in resizable_components:
170
 
                    tmp = list(component.outer_bounds)
171
 
                    tmp[ndx] = int(size_prefs[component][ndx] * scale)
172
 
                    new_bounds_dict[component] = tmp
173
 
            else:
174
 
                each_size = int(avail_size / len(resizable_components))
175
 
                for component in resizable_components:
176
 
                    tmp = list(component.outer_bounds)
177
 
                    tmp[ndx] = each_size
178
 
                    new_bounds_dict[component] = tmp
179
 
        
180
 
        # Loop over all the components, assigning position and computing the
181
 
        # size in the other dimension and its position.
182
 
        cur_pos = 0
183
 
        for component in components:
184
 
            if not self._should_layout(component):
185
 
                continue
186
 
 
187
 
            position = list(component.outer_position)
188
 
            position[ndx] = cur_pos
189
 
 
190
 
            bounds = new_bounds_dict.get(component, list(component.outer_bounds))
191
 
            cur_pos += bounds[ndx] + self.spacing
192
 
            
193
 
            if (bounds[other_ndx] > size[other_ndx]) or \
194
 
                    (other_dim in component.resizable):
195
 
                # If the component is resizable in the other dimension or it exceeds the
196
 
                # container bounds, set it to the maximum size of the container
197
 
                
198
 
                #component.set_outer_position(other_ndx, 0)
199
 
                #component.set_outer_bounds(other_ndx, size[other_ndx])
200
 
                position[other_ndx] = 0
201
 
                bounds[other_ndx] = size[other_ndx]
202
 
            else:
203
 
                #component.set_outer_position(other_ndx, 0)
204
 
                #old_coord = component.outer_position[other_ndx]
205
 
                position[other_ndx] = 0
206
 
                if align == "min":
207
 
                    pass
208
 
                elif align == "max":
209
 
                    position[other_ndx] = size[other_ndx] - bounds[other_ndx]
210
 
                elif align == "center":
211
 
                    position[other_ndx] = (size[other_ndx] - bounds[other_ndx]) / 2.0
212
 
 
213
 
            component.outer_position = position
214
 
            component.outer_bounds = bounds
215
 
            component.do_layout()
216
 
        return        
217
 
 
218
 
    ### Persistence ###########################################################
219
 
 
220
 
    # PICKLE FIXME: blocked with _pickles, but not sure that was correct.
221
 
    def __getstate__(self):
222
 
        state = super(StackedPlotContainer,self).__getstate__()
223
 
        for key in ['stack_dimension', 'other_dimension', 'stack_index']:
224
 
            if state.has_key(key):
225
 
                del state[key]
226
 
        return state
227
 
 
228
 
 
229
 
class HPlotContainer(StackedPlotContainer):
230
 
    """
231
 
    A plot container that stacks all of its components horizontally. Resizable
232
 
    components share the free space evenly. All components are stacked from 
233
 
    according to **stack_order* in the same order that they appear in the
234
 
    **components** list. 
235
 
    """
236
 
 
237
 
    draw_order = Instance(list, args=(DEFAULT_DRAWING_ORDER,))
238
 
    
239
 
    # The order in which components in the plot container are laid out.
240
 
    stack_order = Enum("left_to_right", "right_to_left")
241
 
 
242
 
    # The amount of space to put between components.
243
 
    spacing = Float(0.0)
244
 
 
245
 
    # The vertical alignment of objects that don't span the full height.
246
 
    valign = Enum("bottom", "top", "center")
247
 
    
248
 
    _cached_preferred_size = Tuple
249
 
 
250
 
    def _do_layout(self):
251
 
        """ Actually performs a layout (called by do_layout()).
252
 
        """
253
 
        if self.stack_order == "left_to_right":
254
 
            components = self.components
255
 
        else:
256
 
            components = self.components[::-1]
257
 
        
258
 
        if self.valign == "bottom":
259
 
            align = "min"
260
 
        elif self.valign == "center":
261
 
            align = "center"
262
 
        else:
263
 
            align = "max"
264
 
        
265
 
        return self._do_stack_layout(components, align)
266
 
 
267
 
    ### Persistence ###########################################################
268
 
    #_pickles = ("stack_order", "spacing")
269
 
    
270
 
    def __getstate__(self):
271
 
        state = super(HPlotContainer,self).__getstate__()
272
 
        for key in ['_cached_preferred_size']:
273
 
            if state.has_key(key):
274
 
                del state[key]
275
 
        return state
276
 
 
277
 
 
278
 
 
279
 
class VPlotContainer(StackedPlotContainer):
280
 
    """
281
 
    A plot container that stacks plot components vertically.  
282
 
    """
283
 
    
284
 
    draw_order = Instance(list, args=(DEFAULT_DRAWING_ORDER,))
285
 
 
286
 
    # Overrides StackedPlotContainer.
287
 
    stack_dimension = "v"
288
 
    # Overrides StackedPlotContainer.
289
 
    other_dimension = "h"
290
 
    # Overrides StackedPlotContainer.
291
 
    stack_index = 1
292
 
    
293
 
    # VPlotContainer attributes
294
 
    
295
 
    # The horizontal alignment of objects that don't span the full width.
296
 
    halign = Enum("left", "right", "center")
297
 
    
298
 
    # The order in which components in the plot container are laid out.
299
 
    stack_order = Enum("bottom_to_top", "top_to_bottom")
300
 
 
301
 
    # The amount of space to put between components.
302
 
    spacing = Float(0.0)
303
 
 
304
 
    def _do_layout(self):
305
 
        """ Actually performs a layout (called by do_layout()).
306
 
        """
307
 
        if self.stack_order == "bottom_to_top":
308
 
            components = self.components
309
 
        else:
310
 
            components = self.components[::-1]
311
 
        if self.halign == "left":
312
 
            align = "min"
313
 
        elif self.halign == "center":
314
 
            align = "center"
315
 
        else:
316
 
            align = "max"
317
 
        
318
 
        #import pdb; pdb.set_trace()
319
 
        return self._do_stack_layout(components, align)
320
 
 
321
 
 
322
 
class GridPlotContainer(BasePlotContainer):
323
 
    """ A GridPlotContainer consists of rows and columns in a tabular format. 
324
 
    
325
 
    Each cell's width is the same as all other cells in its column, and each
326
 
    cell's height is the same as all other cells in its row.
327
 
 
328
 
    Although grid layout requires more layout information than a simple
329
 
    ordered list, this class keeps components as a simple list and exposes a 
330
 
    **shape** trait.
331
 
    """
332
 
    
333
 
    draw_order = Instance(list, args=(DEFAULT_DRAWING_ORDER,))
334
 
 
335
 
    # The amount of space to put on either side of each component, expressed
336
 
    # as a tuple (h_spacing, v_spacing).
337
 
    spacing = Either(Tuple, List, Array)
338
 
 
339
 
    # The vertical alignment of objects that don't span the full height.
340
 
    valign = Enum("bottom", "top", "center")
341
 
    
342
 
    # The horizontal alignment of objects that don't span the full width.
343
 
    halign = Enum("left", "right", "center")
344
 
 
345
 
    # The shape of this container, i.e, (rows, columns).  The items in 
346
 
    # **components** are shuffled appropriately to match this
347
 
    # specification.  If there are fewer components than cells, the remaining
348
 
    # cells are filled in with spaces.  If there are more components than cells,
349
 
    # the remainder wrap onto new rows as appropriate.
350
 
    shape = Trait((0,0), Either(Tuple, List, Array))
351
 
 
352
 
    # This property exposes the underlying grid structure of the container,
353
 
    # and is the preferred way of setting and reading its contents.
354
 
    # When read, this property returns a Numpy array with dtype=object; values
355
 
    # for setting it can be nested tuples, lists, or 2-D arrays.
356
 
    # The array is in row-major order, so that component_grid[0] is the first
357
 
    # row, and component_grid[:,0] is the first column.  The rows are ordered
358
 
    # from top to bottom.
359
 
    component_grid = Property
360
 
 
361
 
    # The internal component grid, in row-major order.  This gets updated
362
 
    # when any of the following traits change: shape, components, grid_components
363
 
    _grid = Array
364
 
 
365
 
    _cached_total_size = Any
366
 
    _h_size_prefs = Any
367
 
    _v_size_prefs = Any
368
 
 
369
 
    class SizePrefs(object):
370
 
        """ Object to hold size preferences across spans in a particular
371
 
        dimension.  For instance, if SizePrefs is being used for the row
372
 
        axis, then each element in the arrays below express sizing information
373
 
        about the corresponding column.
374
 
        """
375
 
        
376
 
        # The maximum size of non-resizable elements in the span.  If an
377
 
        # element of this array is 0, then its corresponding span had no
378
 
        # non-resizable components.
379
 
        fixed_lengths = Array
380
 
 
381
 
        # The maximum preferred size of resizable elements in the span.
382
 
        # If an element of this array is 0, then its corresponding span
383
 
        # had no resizable components with a non-zero preferred size.
384
 
        resizable_lengths = Array
385
 
 
386
 
        # The direction of resizability associated with this SizePrefs
387
 
        # object.  If this SizePrefs is sizing along the X-axis, then
388
 
        # direction should be "h", and correspondingly for the Y-axis.
389
 
        direction = Enum("h", "v")
390
 
 
391
 
        # The index into a size tuple corresponding to our orientation
392
 
        # (0 for horizontal, 1 for vertical).  This is derived from
393
 
        # **direction** in the constructor.
394
 
        index = Int(0)
395
 
 
396
 
        def __init__(self, length, direction):
397
 
            """ Initializes this prefs object with empty arrays of the given
398
 
            length and with the given direction. """
399
 
            self.fixed_lengths = zeros(length)
400
 
            self.resizable_lengths = zeros(length)
401
 
            self.direction = direction
402
 
            if direction == "h":
403
 
                self.index = 0
404
 
            else:
405
 
                self.index = 1
406
 
            return
407
 
 
408
 
        def update_from_component(self, component, index):
409
 
            """ Given a component at a particular index along this SizePref's
410
 
            axis, integrates the component's resizability and sizing information
411
 
            into self.fixed_lengths and self.resizable_lengths. """
412
 
            resizable = self.direction in component.resizable
413
 
            pref_size = component.get_preferred_size()
414
 
            self.update_from_pref_size(pref_size[self.index], index, resizable)
415
 
 
416
 
        def update_from_pref_size(self, pref_length, index, resizable):
417
 
            if resizable:
418
 
                if pref_length > self.resizable_lengths[index]:
419
 
                    self.resizable_lengths[index] = pref_length
420
 
            else:
421
 
                if pref_length > self.fixed_lengths[index]:
422
 
                    self.fixed_lengths[index] = pref_length
423
 
            return
424
 
 
425
 
        def get_preferred_size(self):
426
 
            return amax((self.fixed_lengths, self.resizable_lengths), axis=0)
427
 
 
428
 
        def compute_size_array(self, size):
429
 
            """ Given a length along the axis corresponding to this SizePref,
430
 
            returns an array of lengths to assign each cell, taking into account
431
 
            resizability and preferred sizes.
432
 
            """
433
 
            # There are three basic cases for each column:
434
 
            #   1. size < total fixed size 
435
 
            #   2. total fixed size < size < fixed size + resizable preferred size
436
 
            #   3. fixed size + resizable preferred size < size
437
 
            #
438
 
            # In all cases, non-resizable components get their full width.
439
 
            #
440
 
            # For resizable components with non-zero preferred size, the following
441
 
            # actions are taken depending on case:
442
 
            #   case 1: They get sized to 0.
443
 
            #   case 2: They get a fraction of their preferred size, scaled based on
444
 
            #           the amount of remaining space after non-resizable components
445
 
            #           get their full size.
446
 
            #   case 3: They get their full preferred size.
447
 
            #
448
 
            # For resizable components with no preferred size (indicated in our scheme
449
 
            # by having a preferred size of 0), the following actions are taken
450
 
            # depending on case:
451
 
            #   case 1: They get sized to 0.
452
 
            #   case 2: They get sized to 0.
453
 
            #   case 3: All resizable components with no preferred size split the
454
 
            #           remaining space evenly, after fixed width and resizable
455
 
            #           components with preferred size get their full size.
456
 
            fixed_lengths = self.fixed_lengths
457
 
            resizable_lengths = self.resizable_lengths
458
 
            return_lengths = zeros_like(fixed_lengths)
459
 
 
460
 
            fixed_size = sum(fixed_lengths)
461
 
            fixed_length_indices = fixed_lengths > resizable_lengths
462
 
            resizable_indices = resizable_lengths > fixed_lengths
463
 
            fully_resizable_indices = (resizable_lengths + fixed_lengths == 0)
464
 
            preferred_size = sum(fixed_lengths[fixed_length_indices]) + \
465
 
                                    sum(resizable_lengths[~fixed_length_indices])
466
 
 
467
 
            # Regardless of the relationship between available space and 
468
 
            # resizable preferred sizes, columns/rows where the non-resizable
469
 
            # component is largest will always get that amount of space.
470
 
            return_lengths[fixed_length_indices] = fixed_lengths[fixed_length_indices]
471
 
 
472
 
            if size <= fixed_size:
473
 
                # We don't use fixed_length_indices here because that mask is
474
 
                # just where non-resizable components were larger than resizable
475
 
                # ones.  If our allotted size is less than the total fixed size,
476
 
                # then we should give all non-resizable components their desired
477
 
                # size.
478
 
                indices = fixed_lengths > 0
479
 
                return_lengths[indices] = fixed_lengths[indices]
480
 
                return_lengths[~indices] = 0
481
 
 
482
 
            elif size > fixed_size and (fixed_lengths > resizable_lengths).all():
483
 
                # If we only have to consider non-resizable lengths, and we have
484
 
                # extra space available, then we need to give each column an 
485
 
                # amount of extra space corresponding to its size.
486
 
                desired_space = sum(fixed_lengths)
487
 
                if desired_space > 0:
488
 
                    scale = size / desired_space
489
 
                    return_lengths = (fixed_lengths * scale).astype(int)
490
 
 
491
 
            elif size <= preferred_size or not fully_resizable_indices.any():
492
 
                # If we don't have enough room to give all the non-fully resizable
493
 
                # components their preferred size, or we have more than enough
494
 
                # room for them and no fully resizable components to take up
495
 
                # the extra space, then we just scale the resizable components
496
 
                # up or down based on the amount of extra space available.
497
 
                delta_lengths = resizable_lengths[resizable_indices] - \
498
 
                                        fixed_lengths[resizable_indices]
499
 
                desired_space = sum(delta_lengths)
500
 
                if desired_space > 0:
501
 
                    avail_space = size - sum(fixed_lengths) #[fixed_length_indices])
502
 
                    scale = avail_space / desired_space
503
 
                    return_lengths[resizable_indices] = (fixed_lengths[resizable_indices] + \
504
 
                            scale * delta_lengths).astype(int)
505
 
 
506
 
            elif fully_resizable_indices.any():
507
 
                # We have enough room to fit all the non-resizable components
508
 
                # as well as components with preferred sizes, and room left
509
 
                # over for the fully resizable components.  Give the resizable
510
 
                # components their desired amount of space, and then give the
511
 
                # remaining space to the fully resizable components.
512
 
                return_lengths[resizable_indices] = resizable_lengths[resizable_indices]
513
 
                avail_space = size - preferred_size
514
 
                count = sum(fully_resizable_indices)
515
 
                space = avail_space / count
516
 
                return_lengths[fully_resizable_indices] = space
517
 
 
518
 
            else:
519
 
                raise RuntimeError("Unhandled sizing case in GridContainer")
520
 
 
521
 
            return return_lengths
522
 
 
523
 
 
524
 
    def get_preferred_size(self, components=None):
525
 
        """ Returns the size (width,height) that is preferred for this component.
526
 
 
527
 
        Overrides PlotComponent.
528
 
        """
529
 
        if self.fixed_preferred_size is not None:
530
 
            return self.fixed_preferred_size
531
 
 
532
 
        if components is None:
533
 
            components = self.component_grid
534
 
        else:
535
 
            # Convert to array; hopefully it is a list or tuple of list/tuples
536
 
            components = array(components)
537
 
 
538
 
        # These arrays track the maximum widths in each column and maximum
539
 
        # height in each row.
540
 
        numrows, numcols = self.shape
541
 
 
542
 
        no_visible_components = True
543
 
        self._h_size_prefs = GridPlotContainer.SizePrefs(numcols, "h")
544
 
        self._v_size_prefs = GridPlotContainer.SizePrefs(numrows, "v")
545
 
        self._pref_size_cache = {}
546
 
        for i, row in enumerate(components):
547
 
            for j, component in enumerate(row):
548
 
                if not self._should_layout(component):
549
 
                    continue
550
 
                else:
551
 
                    no_visible_components = False
552
 
                    self._h_size_prefs.update_from_component(component, j)
553
 
                    self._v_size_prefs.update_from_component(component, i)
554
 
 
555
 
        total_width = sum(self._h_size_prefs.get_preferred_size()) + self.hpadding
556
 
        total_height = sum(self._v_size_prefs.get_preferred_size()) + self.vpadding
557
 
        total_size = array([total_width, total_height])
558
 
 
559
 
        # Account for spacing.  There are N+1 of spaces, where N is the size in
560
 
        # each dimension.
561
 
        if self.spacing is None:
562
 
            spacing = zeros(2)
563
 
        else:
564
 
            spacing = array(self.spacing)
565
 
        total_spacing = array(components.shape[::-1]) * spacing * 2 * (total_size>0)
566
 
        total_size += total_spacing
567
 
        
568
 
        for orientation, ndx in (("h", 0), ("v", 1)):
569
 
            if (orientation not in self.resizable) and \
570
 
               (orientation not in self.fit_components):
571
 
                total_size[ndx] = self.outer_bounds[ndx]
572
 
            elif no_visible_components or (total_size[ndx] == 0):
573
 
                total_size[ndx] = self.default_size[ndx]
574
 
        
575
 
        self._cached_total_size = total_size
576
 
        if self.resizable == "":
577
 
            return self.outer_bounds
578
 
        else:
579
 
            return self._cached_total_size    
580
 
    
581
 
    def _do_layout(self):
582
 
        # If we don't have cached size_prefs, then we need to call
583
 
        # get_preferred_size to build them.
584
 
        if self._cached_total_size is None:
585
 
            self.get_preferred_size()
586
 
        
587
 
        # If we need to fit our components, then rather than using our
588
 
        # currently assigned size to do layout, we use the preferred
589
 
        # size we computed from our components.
590
 
        size = array(self.bounds)
591
 
        if self.fit_components != "":
592
 
            self.get_preferred_size()
593
 
            if "h" in self.fit_components:
594
 
                size[0] = self._cached_total_size[0] - self.hpadding
595
 
            if "v" in self.fit_components:
596
 
                size[1] = self._cached_total_size[1] - self.vpadding
597
 
 
598
 
        # Compute total_spacing and spacing, which are used in computing
599
 
        # the bounds and positions of all the components.
600
 
        shape = array(self._grid.shape).transpose()
601
 
        if self.spacing is None:
602
 
            spacing = array([0,0])
603
 
        else:
604
 
            spacing = array(self.spacing)
605
 
        total_spacing = spacing * 2 * shape
606
 
 
607
 
        # Compute the total space used by non-resizable and resizable components
608
 
        # with non-zero preferred sizes.
609
 
        widths = self._h_size_prefs.compute_size_array(size[0] - total_spacing[0])
610
 
        heights = self._v_size_prefs.compute_size_array(size[1] - total_spacing[1])
611
 
 
612
 
        # Set the baseline h and v positions for each cell.  Resizable components
613
 
        # will get these as their position, but non-resizable components will have
614
 
        # to be aligned in H and V.
615
 
        summed_widths = cumsum(hstack(([0], widths[:-1])))
616
 
        summed_heights = cumsum(hstack(([0], heights[-1:0:-1])))
617
 
        h_positions = (2*(arange(self._grid.shape[1])+1) - 1) * spacing[0] + summed_widths
618
 
        v_positions = (2*(arange(self._grid.shape[0])+1) - 1) * spacing[1] + summed_heights
619
 
        v_positions = v_positions[::-1]
620
 
 
621
 
        # Loop over all rows and columns, assigning position, setting bounds for
622
 
        # resizable components, and aligning non-resizable ones
623
 
        valign = self.valign
624
 
        halign = self.halign
625
 
        for j, row in enumerate(self._grid):
626
 
            for i, component in enumerate(row):
627
 
                if not self._should_layout(component):
628
 
                    continue
629
 
 
630
 
                r = component.resizable
631
 
                x = h_positions[i]
632
 
                y = v_positions[j]
633
 
                w = widths[i]
634
 
                h = heights[j]
635
 
 
636
 
                if "v" not in r:
637
 
                    # Component is not vertically resizable
638
 
                    if valign == "top":
639
 
                        y += h - component.outer_height
640
 
                    elif valign == "center":
641
 
                        y += (h - component.outer_height) / 2
642
 
                if "h" not in r:
643
 
                    # Component is not horizontally resizable
644
 
                    if halign == "right":
645
 
                        x += w - component.outer_width
646
 
                    elif halign == "center":
647
 
                        x += (w - component.outer_width) / 2
648
 
 
649
 
                component.outer_position = [x,y]
650
 
                bounds = list(component.outer_bounds)
651
 
                if "h" in r:
652
 
                    bounds[0] = w
653
 
                if "v" in r:
654
 
                    bounds[1] = h
655
 
 
656
 
                component.outer_bounds = bounds
657
 
                component.do_layout()
658
 
                    
659
 
        return
660
 
 
661
 
 
662
 
    def _reflow_layout(self):
663
 
        """ Re-computes self._grid based on self.components and self.shape.
664
 
        Adjusts self.shape accordingly.
665
 
        """
666
 
        numcells = self.shape[0] * self.shape[1]
667
 
        if numcells < len(self.components):
668
 
            numrows, numcols = divmod(len(self.components), self.shape[0])
669
 
            self.shape = (numrows, numcols)
670
 
        grid = array(self.components, dtype=object)
671
 
        grid.resize(self.shape)
672
 
        grid[grid==0] = None
673
 
        self._grid = grid
674
 
        self._layout_needed = True
675
 
        return
676
 
    
677
 
    def _shape_changed(self, old, new):
678
 
        self._reflow_layout()
679
 
 
680
 
    def __components_changed(self, old, new):
681
 
        self._reflow_layout()
682
 
 
683
 
    def __components_items_changed(self, event):
684
 
        self._reflow_layout()
685
 
 
686
 
    def _get_component_grid(self):
687
 
        return self._grid
688
 
 
689
 
    def _set_component_grid(self, val):
690
 
        grid = array(val)
691
 
        grid_set = set(grid.flatten())
692
 
 
693
 
        # Figure out which of the components in the component_grid are new,
694
 
        # and which have been removed.
695
 
        existing = set(array(self._grid).flatten())
696
 
        new = grid_set - existing
697
 
        removed = existing - grid_set
698
 
 
699
 
        for component in removed:
700
 
            if component is not None:
701
 
                component.container = None
702
 
        for component in new:
703
 
            if component is not None:
704
 
                if component.container is not None:
705
 
                    component.container.remove(component)
706
 
                component.container = self
707
 
 
708
 
        self.set(shape=grid.shape, trait_change_notify=False)
709
 
        self._components = list(grid.flatten())
710
 
 
711
 
        if self._should_compact():
712
 
            self.compact()
713
 
 
714
 
        self.invalidate_draw()
715
 
        return
716
 
 
717
 
 
718
 
### EOF
719