enthought / traitsui
1
# ------------------------------------------------------------------------------
2
#
3
#  Copyright (c) 2005, Enthought, Inc.
4
#  All rights reserved.
5
#
6
#  This software is provided without warranty under the terms of the BSD
7
#  license included in LICENSE.txt and may be redistributed only
8
#  under the conditions described in the aforementioned license.  The license
9
#  is also available online at http://www.enthought.com/licenses/BSD.txt
10
#
11
#  Thanks for using Enthought open source!
12
#
13
#  Author: David C. Morrill
14
#  Date:   10/07/2004
15
#
16
# ------------------------------------------------------------------------------
17

18 4
""" Defines the Group class used to represent a group of items used in a
19
    Traits-based user interface.
20
"""
21

22

23

24 4
from traits.api import (
25
    Bool,
26
    Delegate,
27
    Float,
28
    Instance,
29
    List,
30
    Property,
31
    Range,
32
    ReadOnly,
33
    Str,
34
    TraitError,
35
    cached_property,
36
)
37

38 4
from .view_element import ViewSubElement
39

40 4
from .item import Item
41

42 4
from .include import Include
43

44 4
from .ui_traits import SequenceTypes, ContainerDelegate, Orientation, Layout
45

46 4
from .dock_window_theme import dock_window_theme, DockWindowTheme
47

48

49
# -------------------------------------------------------------------------
50
#  Trait definitions:
51
# -------------------------------------------------------------------------
52

53
# Delegate trait to the object being "shadowed"
54 4
ShadowDelegate = Delegate("shadow")
55

56
# Amount of padding to add around item
57 4
Padding = Range(0, 15, desc="amount of padding to add around each item")
58

59

60 4
class Group(ViewSubElement):
61
    """ Represents a grouping of items in a user interface view.
62
    """
63

64
    # -------------------------------------------------------------------------
65
    # Trait definitions:
66
    # -------------------------------------------------------------------------
67

68
    #: A list of Group, Item, and Include objects in this group.
69 4
    content = List(ViewSubElement)
70

71
    #: A unique identifier for the group.
72 4
    id = Str()
73

74
    #: User interface label for the group. How the label is displayed depends
75
    #: on the **show_border** attribute, and on the **layout** attribute of
76
    #: the group's parent group or view.
77 4
    label = Str()
78

79 4
    style_sheet = Str()
80

81
    #: Default context object for group items.
82 4
    object = ContainerDelegate
83

84
    #: Default editor style of items in the group.
85 4
    style = ContainerDelegate
86

87
    #: Default docking style of items in group.
88 4
    dock = ContainerDelegate
89

90
    #: Default image to display on notebook tabs.
91 4
    image = ContainerDelegate
92

93
    #: The theme to use for a DockWindow:
94 4
    dock_theme = Instance(DockWindowTheme, allow_none=False)
95

96
    #: Category of elements dragged from view.
97 4
    export = ContainerDelegate
98

99
    #: Spatial orientation of the group's elements. Can be 'vertical' (default)
100
    #: or 'horizontal'.
101 4
    orientation = Orientation
102

103
    #: Layout style of the group, which can be one of the following:
104
    #:
105
    #: * 'normal' (default): Sub-groups are displayed sequentially in a single
106
    #:   panel.
107
    #: * 'flow': Sub-groups are displayed sequentially, and then "wrap" when
108
    #:   they exceed the available space in the **orientation** direction.
109
    #: * 'split': Sub-groups are displayed in a single panel, separated by
110
    #:   "splitter bars", which the user can drag to adjust the amount of space
111
    #:   for each sub-group.
112
    #: * 'tabbed': Each sub-group appears on a separate tab, labeled with the
113
    #:   sub-group's *label* text, if any.
114
    #:
115
    #: This attribute is ignored for groups that contain only items, or contain
116
    #: only one sub-group.
117 4
    layout = Layout
118

119
    #: Should the group be scrollable along the direction of orientation?
120 4
    scrollable = Bool(False)
121

122
    #: The number of columns in the group
123 4
    columns = Range(1, 50)
124

125
    #: Should a border be drawn around group? If set to True, the **label** text
126
    #: is embedded in the border. If set to False, the label appears as a banner
127
    #: above the elements of the group.
128 4
    show_border = Bool(False)
129

130
    #: Should labels be added to items in group? Only items that are directly
131
    #: contained in the group are affected. That is, if the group contains
132
    #: a sub-group, the display of labels in the sub-group is not affected by
133
    #: the attribute on this group.
134 4
    show_labels = Bool(True)
135

136
    #: Should labels be shown to the left of items (True) or the right (False)?
137
    #: Only items that are directly contained in the group are affected. That is,
138
    #: if the group contains a sub-group, the display of labels in the sub-group
139
    #: is not affected by the attribute in this group. If **show_labels** is
140
    #: False, this attribute is irrelevant.
141 4
    show_left = Bool(True)
142

143
    #: Is this group the tab that is initially selected? If True, the group's
144
    #: tab is displayed when the view is opened. If the **layout** of the group's
145
    #: parent is not 'tabbed', this attribute is ignored.
146 4
    selected = Bool(False)
147

148
    #: Should the group use extra space along its parent group's layout
149
    #: orientation?
150 4
    springy = Bool(False)
151

152
    #: Optional help text (for top-level group). This help text appears in the
153
    #: View-level help window (created by the default help handler), for any
154
    #: View that contains *only* this group. Group-level help is ignored for
155
    #: nested groups and multiple top-level groups
156 4
    help = Str()
157

158
    #: Pre-condition for including the group in the display. If the expression
159
    #: evaluates to False, the group is not defined in the display. Conditions
160
    #: for **defined_when** are evaluated only once, when the display is first
161
    #: constructed. Use this attribute for conditions based on attributes that
162
    #: vary from object to object, but that do not change over time.
163 4
    defined_when = Str()
164

165
    #: Pre-condition for showing the group. If the expression evaluates to False,
166
    #: the group and its items are not visible (and they disappear if they were
167
    #: previously visible). If the value evaluates to True, the group and items
168
    #: become visible. All **visible_when** conditions are checked each time
169
    #: that any trait value is edited in the display. Therefore, you can use
170
    #: **visible_when** conditions to hide or show groups in response to user
171
    #: input.
172 4
    visible_when = Str()
173

174
    #: Pre-condition for enabling the group. If the expression evaluates to False,
175
    #: the group is disabled, that is, none of the widgets accept input. All
176
    #: **enabled_when** conditions are checked each time that any trait value
177
    #: is edited in the display. Therefore, you can use **enabled_when**
178
    #: conditions to enable or disable groups in response to user input.
179 4
    enabled_when = Str()
180

181
    #: Amount of padding (in pixels) to add around each item in the group. The
182
    #: value must be an integer between 0 and 15. (Unlike the Item class, the
183
    #: Group class does not support negative padding.) The padding for any
184
    #: individual widget is the sum of the padding for its Group, the padding
185
    #: for its Item, and the default spacing determined by the toolkit.
186 4
    padding = Padding
187

188
    #: Requested width of the group (calculated from widths of contents)
189 4
    width = Property(Float, depends_on="content")
190

191
    #: Requested height of the group (calculated from heights of contents)
192 4
    height = Property(Float, depends_on="content")
193

194 4
    def __init__(self, *values, **traits):
195
        """ Initializes the group object.
196
        """
197 4
        super(ViewSubElement, self).__init__(**traits)
198

199 4
        content = self.content
200

201
        # Process any embedded Group options first:
202 4
        for value in values:
203 4
            if (isinstance(value, str)) and (value[0:1] in "-|"):
204
                # Parse Group trait options if specified as a string:
205 4
                self._parse(value)
206

207
        # Process all of the data passed to the constructor:
208 4
        for value in values:
209 4
            if isinstance(value, ViewSubElement):
210 4
                content.append(value)
211 4
            elif type(value) in SequenceTypes:
212
                # Map (...) or [...] to a Group():
213 4
                content.append(Group(*value))
214 4
            elif isinstance(value, str):
215 4
                if value[0:1] in "-|":
216
                    # We've already parsed Group trait options above:
217 4
                    pass
218 4
                elif (value[:1] == "<") and (value[-1:] == ">"):
219
                    # Convert string to an Include value:
220 4
                    content.append(Include(value[1:-1].strip()))
221
                else:
222
                    # Else let the Item class try to make sense of it:
223 4
                    content.append(Item(value))
224
            else:
225 0
                raise TypeError("Unrecognized argument type: %s" % value)
226

227
        # Make sure this Group is the container for all its children:
228 4
        self.set_container()
229

230
    # -- Default Trait Values -------------------------------------------------
231

232 4
    def _dock_theme_default(self):
233 0
        return dock_window_theme()
234

235 4
    def get_label(self, ui):
236
        """ Gets the label to use this group.
237
        """
238 4
        if self.label != "":
239 0
            return self.label
240

241 0
        return "Group"
242

243 4
    def is_includable(self):
244
        """ Returns a Boolean value indicating whether the object is replacable
245
        by an Include object.
246
        """
247 4
        return self.id != ""
248

249 4
    def replace_include(self, view_elements):
250
        """ Replaces any items that have an **id** attribute with an Include
251
        object with the same ID value, and puts the object with the ID
252
        into the specified ViewElements object.
253

254
        Parameters
255
        ----------
256
        view_elements : ViewElements object
257
            A set of Group, Item, and Include objects
258
        """
259 4
        for i, item in enumerate(self.content):
260 4
            if item.is_includable():
261 4
                id = item.id
262 4
                if id in view_elements.content:
263 0
                    raise TraitError(
264
                        "Duplicate definition for view element '%s'" % id
265
                    )
266 4
                self.content[i] = Include(id)
267 4
                view_elements.content[id] = item
268 4
            item.replace_include(view_elements)
269

270 4
    def get_shadow(self, ui):
271
        """ Returns a ShadowGroup object for the current Group object, which
272
        recursively resolves all embedded Include objects and which replaces
273
        each embedded Group object with a corresponding ShadowGroup.
274
        """
275 4
        content = []
276 4
        groups = 0
277 4
        level = ui.push_level()
278 4
        for value in self.content:
279
            # Recursively replace Include objects:
280 4
            while isinstance(value, Include):
281 4
                value = ui.find(value)
282

283
            # Convert Group objects to ShadowGroup objects, but include Item
284
            # objects as is (ignore any 'None' values caused by a failed
285
            # Include):
286 4
            if isinstance(value, Group):
287 4
                if self._defined_when(ui, value):
288 4
                    content.append(value.get_shadow(ui))
289 4
                    groups += 1
290 4
            elif isinstance(value, Item):
291 4
                if self._defined_when(ui, value):
292 4
                    content.append(value)
293

294 4
            ui.pop_level(level)
295

296
        # Return the ShadowGroup:
297 4
        return ShadowGroup(shadow=self, content=content, groups=groups)
298

299 4
    def set_container(self):
300
        """ Sets the correct container for the content.
301
        """
302 4
        for item in self.content:
303 4
            item.container = self
304

305 4
    def _defined_when(self, ui, value):
306
        """ Should the object be defined in the user interface?
307
        """
308 4
        if value.defined_when == "":
309 4
            return True
310 0
        return ui.eval_when(value.defined_when)
311

312 4
    def _parse(self, value):
313
        """ Parses Group options specified as a string.
314
        """
315
        # Override the defaults, since we only allow 'True' values to be
316
        # specified:
317 4
        self.show_border = self.show_labels = self.show_left = False
318

319
        # Parse all of the single or multi-character options:
320 4
        value, empty = self._parse_label(value)
321 4
        value = self._parse_style(value)
322 4
        value = self._option(value, "-", "orientation", "horizontal")
323 4
        value = self._option(value, "|", "orientation", "vertical")
324 4
        value = self._option(value, "=", "layout", "split")
325 4
        value = self._option(value, "^", "layout", "tabbed")
326 4
        value = self._option(value, ">", "show_labels", True)
327 4
        value = self._option(value, "<", "show_left", True)
328 4
        value = self._option(value, "!", "selected", True)
329

330 4
        show_labels = not (self.show_labels and self.show_left)
331 4
        self.show_left = not self.show_labels
332 4
        self.show_labels = show_labels
333

334
        # Parse all of the punctuation based sub-string options:
335 4
        value = self._split("id", value, ":", str.find, 0, 1)
336 4
        if value != "":
337 0
            self.object = value
338

339 4
    def _parsed_label(self):
340
        """ Handles a label being found in the string definition.
341
        """
342 4
        self.show_border = True
343

344
    def __repr__(self):
345
        """ Returns a "pretty print" version of the Group.
346
        """
347
        result = []
348
        items = ",\n".join([item.__repr__() for item in self.content])
349
        if len(items) > 0:
350
            result.append(items)
351

352
        options = self._repr_options(
353
            "orientation",
354
            "show_border",
355
            "show_labels",
356
            "show_left",
357
            "selected",
358
            "id",
359
            "object",
360
            "label",
361
            "style",
362
            "layout",
363
            "style_sheet",
364
        )
365
        if options is not None:
366
            result.append(options)
367

368
        content = ",\n".join(result)
369
        if len(content) == 0:
370
            return self.__class__.__name__ + "()"
371

372
        return "%s(\n%s\n)" % (self.__class__.__name__, self._indent(content))
373

374
    # -------------------------------------------------------------------------
375
    #  Property getters/setters for width/height attributes
376
    # -------------------------------------------------------------------------
377

378 4
    @cached_property
379
    def _get_width(self):
380
        """ Returns the requested width of the Group.
381
        """
382 4
        width = 0.0
383 4
        for item in self.content:
384 4
            if item.width >= 1:
385 4
                if self.orientation == "horizontal":
386 0
                    width += item.width
387 4
                elif self.orientation == "vertical":
388 4
                    width = max(width, item.width)
389

390 4
        if width == 0:
391 0
            width = -1.0
392

393 4
        return width
394

395 4
    @cached_property
396
    def _get_height(self):
397
        """ Returns the requested height of the Group.
398
        """
399 0
        height = 0.0
400 4
        for item in self.content:
401 4
            if item.height >= 1:
402 4
                if self.orientation == "horizontal":
403 0
                    height = max(height, item.height)
404 4
                elif self.orientation == "vertical":
405 0
                    height += item.height
406

407 4
        if height == 0:
408 0
            height = -1.0
409

410 0
        return height
411

412

413 4
class HGroup(Group):
414
    """ A group whose items are laid out horizontally.
415
    """
416

417
    # -------------------------------------------------------------------------
418
    #  Trait definitions:
419
    # -------------------------------------------------------------------------
420

421
    #: Override standard Group trait defaults to give it horizontal group
422
    #: behavior:
423 4
    orientation = "horizontal"
424

425

426 4
class VGroup(Group):
427
    """ A group whose items are laid out vertically.
428
    """
429

430
    # -------------------------------------------------------------------------
431
    #  Trait definitions:
432
    # -------------------------------------------------------------------------
433

434
    #: Override standard Group trait defaults to give it vertical group
435
    #: behavior:
436 4
    orientation = "vertical"
437

438

439 4
class VGrid(VGroup):
440
    """ A group whose items are laid out in 2 columns.
441
    """
442

443
    # -------------------------------------------------------------------------
444
    #  Trait definitions:
445
    # -------------------------------------------------------------------------
446

447
    #: Override standard Group trait defaults to give it grid behavior:
448 4
    columns = 2
449

450

451 4
class HFlow(HGroup):
452
    """ A group in which items are laid out horizontally, and "wrap" when
453
    they exceed the available horizontal space..
454
    """
455

456
    # -------------------------------------------------------------------------
457
    #  Trait definitions:
458
    # -------------------------------------------------------------------------
459

460
    #: Override standard Group trait defaults to give it horizontal flow
461
    #: behavior:
462 4
    layout = "flow"
463 4
    show_labels = False
464

465

466 4
class VFlow(VGroup):
467
    """ A group in which items are laid out vertically, and "wrap" when they
468
    exceed the available vertical space.
469
    """
470

471
    # -------------------------------------------------------------------------
472
    #  Trait definitions:
473
    # -------------------------------------------------------------------------
474

475
    #: Override standard Group trait defaults to give it vertical flow behavior:
476 4
    layout = "flow"
477 4
    show_labels = False
478

479

480 4
class VFold(VGroup):
481
    """ A group in which items are laid out vertically and can be collapsed
482
        (i.e. 'folded') by clicking their title.
483
    """
484

485
    # -------------------------------------------------------------------------
486
    #  Trait definitions:
487
    # -------------------------------------------------------------------------
488

489
    #: Override standard Group trait defaults to give it vertical folding group
490
    #: behavior:
491 4
    layout = "fold"
492 4
    show_labels = False
493

494

495 4
class HSplit(Group):
496
    """ A horizontal group with splitter bars to separate it from other groups.
497
    """
498

499
    # -------------------------------------------------------------------------
500
    #  Trait definitions:
501
    # -------------------------------------------------------------------------
502

503
    #: Override standard Group trait defaults to give it horizontal splitter
504
    #: behavior:
505 4
    layout = "split"
506 4
    orientation = "horizontal"
507

508

509 4
class VSplit(Group):
510
    """ A vertical group with splitter bars to separate it from other groups.
511
    """
512

513
    # -------------------------------------------------------------------------
514
    #  Trait definitions:
515
    # -------------------------------------------------------------------------
516

517
    #: Override standard Group trait defaults to give it vertical splitter
518
    #: behavior:
519 4
    layout = "split"
520 4
    orientation = "vertical"
521

522

523 4
class Tabbed(Group):
524
    """ A group that is shown as a tabbed notebook.
525
    """
526

527
    # -------------------------------------------------------------------------
528
    #  Trait definitions:
529
    # -------------------------------------------------------------------------
530

531
    #: Override standard Group trait defaults to give it tabbed notebook
532
    #: behavior:
533 4
    layout = "tabbed"
534 4
    springy = True
535

536

537 4
class ShadowGroup(Group):
538
    """ Corresponds to a Group object, but with all embedded Include
539
        objects resolved, and with all embedded Group objects replaced by
540
        corresponding ShadowGroup objects.
541
    """
542

543 4
    def __init__(self, shadow, **traits):
544
        # Set the 'shadow' trait before all others, to avoid exceptions
545
        # when setting those other traits.
546 4
        self.shadow = shadow
547 4
        super(ShadowGroup, self).__init__(**traits)
548

549
    # -------------------------------------------------------------------------
550
    # Trait definitions:
551
    # -------------------------------------------------------------------------
552

553
    #: Group object this is a "shadow" for
554 4
    shadow = ReadOnly()
555

556
    #: Number of ShadowGroups in **content**
557 4
    groups = ReadOnly()
558

559
    #: Name of the group
560 4
    id = ShadowDelegate
561

562
    #: User interface label for the group
563 4
    label = ShadowDelegate
564

565
    #: Default context object for group items
566 4
    object = ShadowDelegate
567

568
    #: Default style of items in the group
569 4
    style = ShadowDelegate
570

571
    #: Default docking style of items in the group
572 4
    dock = ShadowDelegate
573

574
    #: Default image to display on notebook tabs
575 4
    image = ShadowDelegate
576

577
    #: The theme to use for a DockWindow:
578 4
    dock_theme = ShadowDelegate
579

580
    #: Category of elements dragged from the view
581 4
    export = ShadowDelegate
582

583
    #: Spatial orientation of the group
584 4
    orientation = ShadowDelegate
585

586
    #: Layout style of the group
587 4
    layout = ShadowDelegate
588

589
    #: Should the group be scrollable along the direction of orientation?
590 4
    scrollable = ShadowDelegate
591

592
    #: The number of columns in the group
593 4
    columns = ShadowDelegate
594

595
    #: Should a border be drawn around group?
596 4
    show_border = ShadowDelegate
597

598
    #: Should labels be added to items in group?
599 4
    show_labels = ShadowDelegate
600

601
    #: Should labels be shown to the left of items (vs. the right)?
602 4
    show_left = ShadowDelegate
603

604
    #: Is group the initially selected page?
605 4
    selected = ShadowDelegate
606

607
    #: Should the group use extra space along its parent group's layout
608
    #: orientation?
609 4
    springy = ShadowDelegate
610

611
    #: Optional help text (for top-level group)
612 4
    help = ShadowDelegate
613

614
    #: Pre-condition for defining the group
615 4
    defined_when = ShadowDelegate
616

617
    #: Pre-condition for showing the group
618 4
    visible_when = ShadowDelegate
619

620
    #: Pre-condition for enabling the group
621 4
    enabled_when = ShadowDelegate
622

623
    #: Amount of padding to add around each item
624 4
    padding = ShadowDelegate
625

626
    #: Style sheet for the panel
627 4
    style_sheet = ShadowDelegate
628

629 4
    def get_content(self, allow_groups=True):
630
        """ Returns the contents of the Group within a specified context for
631
        building a user interface.
632

633
        This method makes sure that all Group types are of the same type (i.e.,
634
        Group or Item) and that all Include objects have been replaced by their
635
        substituted values.
636
        """
637
        # Make a copy of the content:
638 4
        result = self.content[:]
639

640
        # If result includes any ShadowGroups and they are not allowed,
641
        # replace them:
642 4
        if self.groups != 0:
643 4
            if not allow_groups:
644 0
                i = 0
645 4
                while i < len(result):
646 0
                    value = result[i]
647 4
                    if isinstance(value, ShadowGroup):
648 0
                        items = value.get_content(False)
649 0
                        result[i : i + 1] = items
650 0
                        i += len(items)
651
                    else:
652 0
                        i += 1
653 4
            elif (self.groups != len(result)) and (self.layout == "normal"):
654 4
                items = []
655 4
                content = []
656 4
                for item in result:
657 4
                    if isinstance(item, ShadowGroup):
658 4
                        self._flush_items(content, items)
659 4
                        content.append(item)
660
                    else:
661 4
                        items.append(item)
662 4
                self._flush_items(content, items)
663 4
                result = content
664

665
        # Return the resulting list of objects:
666 4
        return result
667

668 4
    def get_id(self):
669
        """ Returns an ID for the group.
670
        """
671 4
        if self.id != "":
672 0
            return self.id
673

674 4
        return ":".join([item.get_id() for item in self.get_content()])
675

676 4
    def set_container(self):
677
        """ Sets the correct container for the content.
678
        """
679 4
        pass
680

681 4
    def _flush_items(self, content, items):
682
        """ Creates a sub-group for any items contained in a specified list.
683
        """
684 4
        if len(items) > 0:
685 4
            content.append(
686
                # Set shadow before hand to prevent delegation errors
687
                ShadowGroup(shadow=self.shadow).trait_set(
688
                    groups=0,
689
                    label="",
690
                    show_border=False,
691
                    content=items,
692
                    show_labels=self.show_labels,
693
                    show_left=self.show_left,
694
                    springy=self.springy,
695
                    orientation=self.orientation,
696
                )
697
            )
698 4
            del items[:]
699

700
    def __repr__(self):
701
        """ Returns a "pretty print" version of the Group.
702
        """
703
        return repr(self.shadow)

Read our documentation on viewing source code .

Loading