1
#------------------------------------------------------------------------------
3
# Copyright (c) 2005, Enthought, Inc.
6
# This software is provided without warranty under the terms of the BSD
7
# license included in enthought/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
11
# Thanks for using Enthought open source!
13
# Author: David C. Morrill
16
#------------------------------------------------------------------------------
18
""" Defines the Group class used to represent a group of items used in a
19
Traits-based user interface.
22
#-------------------------------------------------------------------------------
24
#-------------------------------------------------------------------------------
26
from __future__ import absolute_import
31
from traits.api import (Bool, Delegate, Float, Instance, List, Property, Range,
32
ReadOnly, Str, TraitError, cached_property)
34
from traits.trait_base import enumerate
36
from .view_element import ViewSubElement
38
from .item import Item
40
from .include import Include
42
from .ui_traits import SequenceTypes, ATheme, ContainerDelegate, Orientation, Layout
44
from .dock_window_theme import dock_window_theme, DockWindowTheme
46
#-------------------------------------------------------------------------------
48
#-------------------------------------------------------------------------------
50
# Delegate trait to the object being "shadowed"
51
ShadowDelegate = Delegate( 'shadow' )
53
# Amount of padding to add around item
54
Padding = Range( 0, 15, desc = 'amount of padding to add around each item' )
56
#-------------------------------------------------------------------------------
58
#-------------------------------------------------------------------------------
60
class Group ( ViewSubElement ):
61
""" Represents a grouping of items in a user interface view.
64
#---------------------------------------------------------------------------
66
#---------------------------------------------------------------------------
68
# A list of Group, Item, and Include objects in this group.
69
content = List( ViewSubElement )
71
# A unique identifier for the group.
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.
79
# Default context object for group items.
80
object = ContainerDelegate
82
# Default editor style of items in the group.
83
style = ContainerDelegate
85
# Default docking style of items in group.
86
dock = ContainerDelegate
88
# Default image to display on notebook tabs.
89
image = ContainerDelegate
91
# The theme to use for a DockWindow:
92
dock_theme = Instance( DockWindowTheme, allow_none = False )
94
# The theme to use for the group itself:
97
# The theme to use for items contained in the group:
98
item_theme = ContainerDelegate
100
# The theme to use for the labels of items contained in the group:
101
label_theme = ContainerDelegate
103
# Category of elements dragged from view.
104
export = ContainerDelegate
106
# Spatial orientation of the group's elements. Can be 'vertical' (default)
108
orientation = Orientation
110
# Layout style of the group, which can be one of the following:
112
# * 'normal' (default): Sub-groups are displayed sequentially in a single
114
# * 'flow': Sub-groups are displayed sequentially, and then "wrap" when
115
# they exceed the available space in the **orientation** direction.
116
# * 'split': Sub-groups are displayed in a single panel, separated by
117
# "splitter bars", which the user can drag to adjust the amount of space
118
# for each sub-group.
119
# * 'tabbed': Each sub-group appears on a separate tab, labeled with the
120
# sub-group's *label* text, if any.
122
# This attribute is ignored for groups that contain only items, or contain
123
# only one sub-group.
126
# Should the group be scrollable along the direction of orientation?
127
scrollable = Bool( False )
129
# The number of columns in the group
130
columns = Range( 1, 50 )
132
# Should a border be drawn around group? If set to True, the **label** text
133
# is embedded in the border. If set to False, the label appears as a banner
134
# above the elements of the group.
135
show_border = Bool( False )
137
# Should labels be added to items in group? Only items that are directly
138
# contained in the group are affected. That is, if the group contains
139
# a sub-group, the display of labels in the sub-group is not affected by
140
# the attribute on this group.
141
show_labels = Bool( True )
143
# Should labels be shown to the left of items (True) or the right (False)?
144
# Only items that are directly contained in the group are affected. That is,
145
# if the group contains a sub-group, the display of labels in the sub-group
146
# is not affected by the attribute in this group. If **show_labels** is
147
# False, this attribute is irrelevant.
148
show_left = Bool( True )
150
# Is this group the tab that is initially selected? If True, the group's
151
# tab is displayed when the view is opened. If the **layout** of the group's
152
# parent is not 'tabbed', this attribute is ignored.
153
selected = Bool( False )
155
# Should the group use extra space along its parent group's layout
157
springy = Bool( False )
159
# Optional help text (for top-level group). This help text appears in the
160
# View-level help window (created by the default help handler), for any
161
# View that contains *only* this group. Group-level help is ignored for
162
# nested groups and multiple top-level groups
165
# Pre-condition for including the group in the display. If the expression
166
# evaluates to False, the group is not defined in the display. Conditions
167
# for **defined_when** are evaluated only once, when the display is first
168
# constructed. Use this attribute for conditions based on attributes that
169
# vary from object to object, but that do not change over time.
172
# Pre-condition for showing the group. If the expression evaluates to False,
173
# the group and its items are not visible (and they disappear if they were
174
# previously visible). If the value evaluates to True, the group and items
175
# become visible. All **visible_when** conditions are checked each time
176
# that any trait value is edited in the display. Therefore, you can use
177
# **visible_when** conditions to hide or show groups in response to user
181
# Pre-condition for enabling the group. If the expression evaluates to False,
182
# the group is disabled, that is, none of the widgets accept input. All
183
# **enabled_when** conditions are checked each time that any trait value
184
# is edited in the display. Therefore, you can use **enabled_when**
185
# conditions to enable or disable groups in response to user input.
188
# Amount of padding (in pixels) to add around each item in the group. The
189
# value must be an integer between 0 and 15. (Unlike the Item class, the
190
# Group class does not support negative padding.) The padding for any
191
# individual widget is the sum of the padding for its Group, the padding
192
# for its Item, and the default spacing determined by the toolkit.
195
# Requested width of the group (calculated from widths of contents)
196
width = Property( Float, depends_on='content' )
198
# Requested height of the group (calculated from heights of contents)
199
height = Property( Float, depends_on='content' )
201
#---------------------------------------------------------------------------
202
# Initializes the object:
203
#---------------------------------------------------------------------------
205
def __init__ ( self, *values, **traits ):
206
""" Initializes the group object.
208
super( ViewSubElement, self ).__init__( **traits )
210
content = self.content
212
# Process any embedded Group options first:
214
if (isinstance(value, basestring)) and (value[0:1] in '-|'):
215
# Parse Group trait options if specified as a string:
218
# Process all of the data passed to the constructor:
220
if isinstance( value, ViewSubElement ):
221
content.append( value )
222
elif type( value ) in SequenceTypes:
223
# Map (...) or [...] to a Group():
224
content.append( Group( *value ) )
225
elif isinstance( value, basestring ):
226
if value[0:1] in '-|':
227
# We've already parsed Group trait options above:
229
elif (value[:1] == '<') and (value[-1:] == '>'):
230
# Convert string to an Include value:
231
content.append( Include( value[1:-1].strip() ) )
233
# Else let the Item class try to make sense of it:
234
content.append( Item( value ) )
236
raise TypeError, "Unrecognized argument type: %s" % value
238
# Make sure this Group is the container for all its children:
241
#-- Default Trait Values ---------------------------------------------------
243
def _dock_theme_default ( self ):
244
return dock_window_theme()
246
#---------------------------------------------------------------------------
247
# Gets the label to use for a specified Group in a specified UI:
248
#---------------------------------------------------------------------------
250
def get_label ( self, ui ):
251
""" Gets the label to use this group.
258
#---------------------------------------------------------------------------
259
# Returns whether or not the object is replacable by an Include object:
260
#---------------------------------------------------------------------------
262
def is_includable ( self ):
263
""" Returns a Boolean value indicating whether the object is replacable
264
by an Include object.
266
return (self.id != '')
268
#---------------------------------------------------------------------------
269
# Replaces any items which have an 'id' with an Include object with the
270
# same 'id', and puts the object with the 'id' into the specified
271
# ViewElements object:
272
#---------------------------------------------------------------------------
274
def replace_include ( self, view_elements ):
275
""" Replaces any items that have an **id** attribute with an Include
276
object with the same ID value, and puts the object with the ID
277
into the specified ViewElements object.
281
view_elements : ViewElements object
282
A set of Group, Item, and Include objects
284
for i, item in enumerate( self.content ):
285
if item.is_includable():
287
if id in view_elements.content:
289
"Duplicate definition for view element '%s'" % id
290
self.content[ i ] = Include( id )
291
view_elements.content[ id ] = item
292
item.replace_include( view_elements )
294
#---------------------------------------------------------------------------
295
# Returns a ShadowGroup for the Group which recursively resolves all
296
# imbedded Include objects and which replaces all imbedded Group objects
297
# with a corresponding ShadowGroup:
298
#---------------------------------------------------------------------------
300
def get_shadow ( self, ui ):
301
""" Returns a ShadowGroup object for the current Group object, which
302
recursively resolves all embedded Include objects and which replaces
303
each embedded Group object with a corresponding ShadowGroup.
307
level = ui.push_level()
308
for value in self.content:
309
# Recursively replace Include objects:
310
while isinstance( value, Include ):
311
value = ui.find( value )
313
# Convert Group objects to ShadowGroup objects, but include Item
314
# objects as is (ignore any 'None' values caused by a failed
316
if isinstance( value, Group ):
317
if self._defined_when( ui, value ):
318
content.append( value.get_shadow( ui ) )
320
elif isinstance( value, Item ):
321
if self._defined_when( ui, value ):
322
content.append( value )
324
ui.pop_level( level )
326
# Return the ShadowGroup:
327
return ShadowGroup( shadow = self, content = content, groups = groups )
329
#---------------------------------------------------------------------------
330
# Sets the correct container for the content:
331
#---------------------------------------------------------------------------
333
def set_container ( self ):
334
""" Sets the correct container for the content.
336
for item in self.content:
337
item.container = self
339
#---------------------------------------------------------------------------
340
# Returns whether the object should be defined in the user interface:
341
#---------------------------------------------------------------------------
343
def _defined_when ( self, ui, value ):
344
""" Should the object be defined in the user interface?
346
if value.defined_when == '':
348
return ui.eval_when( value.defined_when )
350
#---------------------------------------------------------------------------
351
# Parses Group options specified as a string:
352
#---------------------------------------------------------------------------
354
def _parse ( self, value ):
355
""" Parses Group options specified as a string.
357
# Override the defaults, since we only allow 'True' values to be
359
self.show_border = self.show_labels = self.show_left = False
361
# Parse all of the single or multi-character options:
362
value, empty = self._parse_label( value )
363
value = self._parse_style( value )
364
value = self._option( value, '-', 'orientation', 'horizontal' )
365
value = self._option( value, '|', 'orientation', 'vertical' )
366
value = self._option( value, '=', 'layout', 'split' )
367
value = self._option( value, '^', 'layout', 'tabbed' )
368
value = self._option( value, '>', 'show_labels', True )
369
value = self._option( value, '<', 'show_left', True )
370
value = self._option( value, '!', 'selected', True )
372
show_labels = not (self.show_labels and self.show_left)
373
self.show_left = not self.show_labels
374
self.show_labels = show_labels
376
# Parse all of the punctuation based sub-string options:
377
value = self._split( 'id', value, ':', find, 0, 1 )
381
#---------------------------------------------------------------------------
382
# Handles a label being found in the string definition:
383
#---------------------------------------------------------------------------
385
def _parsed_label ( self ):
386
""" Handles a label being found in the string definition.
388
self.show_border = True
390
#---------------------------------------------------------------------------
391
# Returns a 'pretty print' version of the Group:
392
#---------------------------------------------------------------------------
394
def __repr__ ( self ):
395
""" Returns a "pretty print" version of the Group.
398
items = ',\n'.join( [ item.__repr__() for item in self.content ] )
400
result.append( items )
402
options = self._repr_options( 'orientation', 'show_border',
403
'show_labels', 'show_left', 'selected', 'id', 'object',
404
'label', 'style', 'layout' )
405
if options is not None:
406
result.append( options )
408
content = ',\n'.join( result )
409
if len( content ) == 0:
410
return self.__class__.__name__ + '()'
412
return '%s(\n%s\n)' % (
413
self.__class__.__name__, self._indent( content ) )
415
#---------------------------------------------------------------------------
416
# Property getters/setters for width/height attributes
417
#---------------------------------------------------------------------------
420
def _get_width ( self ):
421
""" Returns the requested width of the Group.
424
for item in self.content:
426
if self.orientation == 'horizontal':
428
elif self.orientation == 'vertical':
429
width = max( width, item.width )
437
def _get_height ( self ):
438
""" Returns the requested height of the Group.
441
for item in self.content:
443
if self.orientation == 'horizontal':
444
height = max( height, item.height )
445
elif self.orientation == 'vertical':
446
height += item.height
453
#-------------------------------------------------------------------------------
455
#-------------------------------------------------------------------------------
457
class HGroup ( Group ):
458
""" A group whose items are laid out horizontally.
461
#---------------------------------------------------------------------------
463
#---------------------------------------------------------------------------
465
# Override standard Group trait defaults to give it horizontal group
467
orientation = 'horizontal'
469
#-------------------------------------------------------------------------------
471
#-------------------------------------------------------------------------------
473
class VGroup ( Group ):
474
""" A group whose items are laid out vertically.
477
#---------------------------------------------------------------------------
479
#---------------------------------------------------------------------------
481
# Override standard Group trait defaults to give it vertical group behavior:
482
orientation = 'vertical'
484
#-------------------------------------------------------------------------------
486
#-------------------------------------------------------------------------------
488
class VGrid ( VGroup ):
489
""" A group whose items are laid out in 2 columns.
492
#---------------------------------------------------------------------------
494
#---------------------------------------------------------------------------
496
# Override standard Group trait defaults to give it grid behavior:
499
#-------------------------------------------------------------------------------
501
#-------------------------------------------------------------------------------
503
class HFlow ( HGroup ):
504
""" A group in which items are laid out horizontally, and "wrap" when
505
they exceed the available horizontal space..
508
#---------------------------------------------------------------------------
510
#---------------------------------------------------------------------------
512
# Override standard Group trait defaults to give it horizontal flow
517
#-------------------------------------------------------------------------------
519
#-------------------------------------------------------------------------------
521
class VFlow ( VGroup ):
522
""" A group in which items are laid out vertically, and "wrap" when they
523
exceed the available vertical space.
526
#---------------------------------------------------------------------------
528
#---------------------------------------------------------------------------
530
# Override standard Group trait defaults to give it vertical flow behavior:
534
#-------------------------------------------------------------------------------
536
#-------------------------------------------------------------------------------
538
class VFold ( VGroup ):
539
""" A group in which items are laid out vertically and can be collapsed
540
(i.e. 'folded') by clicking their title.
543
#---------------------------------------------------------------------------
545
#---------------------------------------------------------------------------
547
# Override standard Group trait defaults to give it vertical folding group
552
#-------------------------------------------------------------------------------
554
#-------------------------------------------------------------------------------
556
class HSplit ( Group ):
557
""" A horizontal group with splitter bars to separate it from other groups.
560
#---------------------------------------------------------------------------
562
#---------------------------------------------------------------------------
564
# Override standard Group trait defaults to give it horizontal splitter
567
orientation = 'horizontal'
569
#-------------------------------------------------------------------------------
571
#-------------------------------------------------------------------------------
573
class VSplit ( Group ):
574
""" A vertical group with splitter bars to separate it from other groups.
577
#---------------------------------------------------------------------------
579
#---------------------------------------------------------------------------
581
# Override standard Group trait defaults to give it vertical splitter
584
orientation = 'vertical'
586
#-------------------------------------------------------------------------------
588
#-------------------------------------------------------------------------------
590
class Tabbed ( Group ):
591
""" A group that is shown as a tabbed notebook.
594
#---------------------------------------------------------------------------
596
#---------------------------------------------------------------------------
598
# Override standard Group trait defaults to give it tabbed notebook
603
#-------------------------------------------------------------------------------
604
# 'ShadowGroup' class:
605
#-------------------------------------------------------------------------------
607
class ShadowGroup ( Group ):
608
""" Corresponds to a Group object, but with all embedded Include
609
objects resolved, and with all embedded Group objects replaced by
610
corresponding ShadowGroup objects.
613
#---------------------------------------------------------------------------
615
#---------------------------------------------------------------------------
617
# Group object this is a "shadow" for
620
# Number of ShadowGroups in **content**
626
# User interface label for the group
627
label = ShadowDelegate
629
# Default context object for group items
630
object = ShadowDelegate
632
# Default style of items in the group
633
style = ShadowDelegate
635
# Default docking style of items in the group
636
dock = ShadowDelegate
638
# Default image to display on notebook tabs
639
image = ShadowDelegate
641
# The theme to use for a DockWindow:
642
dock_theme = ShadowDelegate
644
# The theme to use for the group itself:
645
group_theme = ShadowDelegate
647
# The theme to use for item's contained in the group:
648
item_theme = ShadowDelegate
650
# The theme to use for the labels of items contained in the group:
651
label_theme = ShadowDelegate
653
# Category of elements dragged from the view
654
export = ShadowDelegate
656
# Spatial orientation of the group
657
orientation = ShadowDelegate
659
# Layout style of the group
660
layout = ShadowDelegate
662
# Should the group be scrollable along the direction of orientation?
663
scrollable = ShadowDelegate
665
# The number of columns in the group
666
columns = ShadowDelegate
668
# Should a border be drawn around group?
669
show_border = ShadowDelegate
671
# Should labels be added to items in group?
672
show_labels = ShadowDelegate
674
# Should labels be shown to the left of items (vs. the right)?
675
show_left = ShadowDelegate
677
# Is group the initially selected page?
678
selected = ShadowDelegate
680
# Should the group use extra space along its parent group's layout
682
springy = ShadowDelegate
684
# Optional help text (for top-level group)
685
help = ShadowDelegate
687
# Pre-condition for defining the group
688
defined_when = ShadowDelegate
690
# Pre-condition for showing the group
691
visible_when = ShadowDelegate
693
# Pre-condition for enabling the group
694
enabled_when = ShadowDelegate
696
# Amount of padding to add around each item
697
padding = ShadowDelegate
699
#---------------------------------------------------------------------------
700
# Returns the contents of the ShadowGroup within a specified user interface
701
# building context. This makes sure that all Group types are of the same
702
# type (i.e. Group or Item) and that all Include objects have been replaced
703
# by their substituted values:
704
#---------------------------------------------------------------------------
706
def get_content ( self, allow_groups = True ):
707
""" Returns the contents of the Group within a specified context for
708
building a user interface.
710
This method makes sure that all Group types are of the same type (i.e.,
711
Group or Item) and that all Include objects have been replaced by their
714
# Make a copy of the content:
715
result = self.content[:]
717
# If result includes any ShadowGroups and they are not allowed,
722
while i < len( result ):
724
if isinstance( value, ShadowGroup ):
725
items = value.get_content( False )
726
result[i:i+1] = items
730
elif (self.groups != len( result )) and (self.layout == 'normal'):
734
if isinstance( item, ShadowGroup ):
735
self._flush_items( content, items )
736
content.append( item )
739
self._flush_items( content, items )
742
# Return the resulting list of objects:
745
#---------------------------------------------------------------------------
746
# Returns an id used to identify the group:
747
#---------------------------------------------------------------------------
750
""" Returns an ID for the group.
755
return ':'.join( [ item.get_id() for item in self.get_content() ] )
757
#---------------------------------------------------------------------------
758
# Sets the correct container for the content:
759
#---------------------------------------------------------------------------
761
def set_container ( self ):
762
""" Sets the correct container for the content.
766
#---------------------------------------------------------------------------
767
# Creates a sub-Group for any items contained in a specified list:
768
#---------------------------------------------------------------------------
770
def _flush_items ( self, content, items ):
771
""" Creates a sub-group for any items contained in a specified list.
774
content.append( ShadowGroup( shadow = self.shadow,
778
content = items ).set(
779
show_labels = self.show_labels,
780
show_left = self.show_left,
781
springy = self.springy,
782
orientation = self.orientation ) )
785
#---------------------------------------------------------------------------
786
# Returns a 'pretty print' version of the Group:
787
#---------------------------------------------------------------------------
789
def __repr__ ( self ):
790
""" Returns a "pretty print" version of the Group.
792
return repr( self.shadow )