Container widgets

Container widgets are able to hold other widgets and take care of drawing them on their own surface. They are mostly used for layout purposes or complex widgets, which consist of several other widgets or which need to add additional functionality to different widget types (like the ScrolledWindow widget). They allow to bind one or more widgets as child(ren) to themselves and take over the role as parent widget.

Bin

The abstract Bin class is a container, that is able to hold exactly one child. It allows to bind und unbind a child widget and supports setting an additional padding between its surface borders and the child surface.

You usually will not create a Bin object directly, but inherit from it in your own widget classes.

The child of a Bin can be set with the child attribute or set_child() method. It is not necessary to register the child at an event manager after binding it to the Bin as this will set the event manager of the child to its own one.

label = Label ("Label for a bin")
bin.set_child (label)
# No need to set an event manager explicitly for the label.
bin.manager = eventmanager
        
This however will not be done, if the child is already bound to an event manager.

For layout purposes the Bin can make use of additional pixels to place between its outer surface edges and the child surface. This pixel amount can be modified and used using the padding attribute of the Bin. Various inheritors within the widgets module of OcempGUI make heavy use of this attribute to adjust the look of themselves. The following example demonstrates this.

from ocempgui.widgets import Button, Renderer
renderer = Renderer ()
renderer.create_screen (200, 120)
button_5px = Button ("Button with 5px padding")
button_5px.topleft = 5, 5
button_5px.padding = 5

button_10px = Button ("Button with 10px padding")
button_10px.topleft = 5, 60
button_10px.padding = 10

renderer.add_widget (button_5px, button_10px)
renderer.start ()
        

Example 34. Bin.padding example


The following example provides a complete Bin implementation which can rotate the visible of its child (and only the surface).

You can find the following example as a python script under examples/bin.py.

# Bin examples.
import pygame
from ocempgui.widgets import *
from ocempgui.widgets.Constants import *

class PivotBin (Bin):
    """PivotBin (widget) -> OwnBin

    A Bin implementation example class.

    This class does not support real rotations of widgets. Instead it
    simply rotates their image surface and displays it. Any other
    behaviour and information of the widget stay the same. Thus event
    capable widgets will not work correctly.
    """
    def __init__ (self):
        Bin.__init__ (self)
        self._orientation = ORIENTATION_HORIZONTAL

    def set_orientation (self, orientation=ORIENTATION_HORIZONTAL):
        """P.set_orientation (...) -> None

        Sets the orientation of the attached child.
        """
        if orientation not in ORIENTATION_TYPES:
            raise ValueError("orientation must be a value of ORIENATION_TYPES")
        self._orientation = orientation
        self.dirty = True

    def draw_bg (self):
        width, height = self.padding, self.padding
        cls = self.__class__

        if self.child:
            width += self.child.width
            height += self.child.height
            if self.orientation == ORIENTATION_VERTICAL:
                # Swap width and height on demand
                width, height = height, width

        # Guarantee the set minimum and maximum sizes.
        width, height = self.check_sizes (width, height)
        surface = base.GlobalStyle.engine.draw_rect (width, height, self.state,
                                                     cls, self.style)
        return surface
    
    def draw (self):
        """Draws the PivotBin and its child according to the set orientation."""
        Bin.draw (self)
        if self.child:
            rect = self.image.get_rect ()
            self.child.center = rect.center
            if self.orientation == ORIENTATION_VERTICAL:
                # Rotate the child image on demand.
                image = pygame.transform.rotate (self.child.image, 90)
                rotate_rect = image.get_rect ()
                rotate_rect.center = rect.center
                self.image.blit (image, rotate_rect)
            else:
                self.image.blit (self.child.image, self.child.rect)

    orientation = property (lambda self: self._orientation,
                            lambda self, var: self.set_orientation (var),
                            doc = "The orientation of the child.")

def rotate_bin (bin, button):
    # Set the Bin orientation and replace the button.
    if bin.orientation == ORIENTATION_HORIZONTAL:
        bin.orientation = ORIENTATION_VERTICAL
    else:
        bin.orientation = ORIENTATION_HORIZONTAL
    button.topleft = bin.left, bin.bottom + 10

if __name__ == "__main__":
    bin = PivotBin ()
    bin.topleft = 10, 10
    bin.child = Label ("Simple label in PivotBin")

    button = Button ("Switch orientation")
    button.topleft = bin.left, bin.bottom
    button.connect_signal (SIG_CLICKED, rotate_bin, bin, button)
    
    # Initialize the drawing window.
    re = Renderer ()
    re.create_screen (300, 300)
    re.title = "Bin implementation example"
    re.color = (234, 228, 223)
    re.add_widget (bin, button)

    # Start the main rendering loop.
    re.start ()

Example 35. Bin example


Container

The abstract Container widget class is similar to the Bin class, except that it can hold more than one widget.

As well as with the Bin, you usually will not create a Container object directly, but inherit from it in your own class.

The children can be added using add_child(), which allows to add multiple children at once, or the insert_child() method.

# Add a single child.
container.add_child (label1)

# Add multiple children at once.
container.add_child (label1, button1, label2, entry1)

# Insert a child at a specific position
container.insert_child (1, entry2)
        
Dependant on the Container implementation, insert_child() can cause the inserted child to appear at a specific position.

The removal of children can be done with one widget at a time only using the remove_child() method of the Container.

# Only one child can be removed at a time.
container.remove_child (button1)
        

You also can set the children directly using the children attribute. The Container will remove all its children first and then add the new list of widgets. Simply setting the children to None will remove all the widgets of the Container.

# Set a list of widgets as children of a container.
container.children = [label1, button1, entry1]

# Remove all children of the container.
container.children = None
        

Besides the padding attribute, which was already explained in the section called “Bin” the Container has a spacing attribute, which indicates the pixel amount to place between its children.

container.spacing = 10
        
The Frame examples in the later section will show you possible concrete results.

TODO: provide implementation examples

Box

The Box widgets are Containers which allow an absolute placement of their attached children. This enables you to let widgets overlap or place them next o each other according to your needs while keeping them relative to other widgets without extensive position calculations. You could e.g. place a Box into a Frame so that the contents of the Box are aligned relative to other widgets of the Frame while being positioned exactly as you need.

To create a Box you have to call its constructor with the size it should occupy.

box = Box (100, 100)
        
Now widgets can be added to it using the typical Container methods. The topleft coordinates of the attached widgets are used to position them within the Box. Thus a Button with the topleft value of 10, 10 will be placed 10 pixels from the topleft corner of the Box.
box = Box (100, 100)
button = Button ("A Button")
button.topleft = 10, 10
box.add_child (button)
        

Note

The Box widget class does not make use of the padding and spacing attributes of its Container parent class. It also does not resize itself, when a widget leaves its visible area or occupies more space than the Box.

Below you will find an example to illustrate most of the abilities of the Box widget classes. You do not need to care about other widgets like the VFrame class for now as those are explained later on.

You can find the following example as a python script under examples/box.py.

# Box examples.
from ocempgui.widgets import *
from ocempgui.widgets.Constants import *
    
def create_box_view ():
    frame = VFrame (Label ("Box example"))
    frame.topleft = 10, 10

    # The Box with 200x200 pixels in size.
    box = Box (200, 200)

    # Widgets to place into it.
    label = ImageLabel ("image.png")
    label.topleft = 10, 10

    button = Button ("A Button")
    button.topleft = 30, 30

    frame1 = VFrame (Label ("A VFrame"))
    frame1.add_child (Label ("Label in the VFrame"))
    frame1.topleft = 60, 80

    chk = CheckButton ("A CheckButton")
    chk.topleft = 130, 110

    box.children = label, button, frame1, chk
    frame.add_child (box)

    return frame

if __name__ == "__main__":
    # Initialize the drawing window.
    re = Renderer ()
    re.create_screen (300, 300)
    re.title = "Box examples"
    re.color = (234, 228, 223)
    re.show_layer_info = True
    re.add_widget (create_box_view ())
    # Start the main rendering loop.
    re.start ()

Example 36. Box example


Frames

Frame widgets are Containers, that support drawing a decorative border and title widget around their children. The basic Frame class is an abstract class, which defines the common attributes and methods for its inheritors, while the HFrame and VFrame widgets are concrete implementations, that place their children horizontally or vertically. Both subclasses only differ in the placing behaviour of their widgets and the basic aligning possibilities, so that, if not stated otherwise, the following examples always apply to both widget types, although the HFrame is used.

The creation of a HFrame is done using

frame = HFrame ()
frame = HFrame (widget)
        
The difference between both is, that the first constructor does not set up a title widget for the Frame. The title widget can be any valid BaseWidget subclass and typically be placed at the topleft corner of the Frame. If no title widget is supplied, a complete border will be drawn around the Frame, while a set title widget will discontinue that border at the topleft corner. You can change or set the title widget at any later time. A few possibilities should be shown here.
frame = HFrame (Label ("A title label"))
frame.widget = Button ("Button as frame title")
frame.set_widget (VFrame (Label ("Frame in a frame")))
        

To adjust the look of the Frame, it is possible to change its border style using the border attribute

frame.border = BORDER_NONE
frame.set_border (BORDER_SUNKEN)
        
and to change the alignment of its packed children using the align attribute.
frame.align = ALIGN_TOP
frame.align = ALIGN_LEFT
        
Here a diversion between the HFrame and VFrame has to be made, because each one only supports a subset of the alignment possibilities. The HFrame widget natively supports aligning its children at the top or bottom only
hframe.align = ALIGN_TOP
hframe.set_align (ALIGN_BOTTOM)
        
while the VFrame supports only the left and right alignment.
vframe.align = ALIGN_LEFT
vframe.set_align (ALIGN_RIGHT)
        
Both use no alignment (ALIGN_NONE) by default, which causes their children to be centered relatively to each other.

Below you will find an example to illustrate most of the abilities of the Frame widget classes. You do not need to care about other widgets like the Table class for now as those are explained later on.

You can find the following example as a python script under examples/frame.py.

# Frame examples.
import os
from ocempgui.widgets import *
from ocempgui.widgets.Constants import *

def create_frame_view ():
    table = Table (2, 3)
    table.topleft = 5, 5
    table.spacing = 5
    
    # Create and display two 'standard' frames.
    hframe = HFrame (Label ("Horizontal Frame"))
    table.add_child (0, 0, hframe)

    lbl = Label ("Vertical Frame" + os.linesep + "with 5 px spacing")
    lbl.multiline = True
    vframe = VFrame (lbl)
    vframe.spacing = 5
    table.add_child (0, 1, vframe)
    
    for i in xrange(3):
        btn = Button ("Button %d" % i)
        hframe.add_child (btn)
        btn2 = Button ("Button %d" % i)
        vframe.add_child (btn2)

    # Create framed frames.
    framed1 = VFrame (Label ("VFrame"))
    framed2 = HFrame ()
    framed3 = VFrame (Label ("VFrame as HFrame.widget"))
    framed3.add_child (Label ("Child of a VFrame"))
    framed2.widget = framed3
    framed2.add_child (Button ("Button 1"), Button ("Button 2"))
    button = Button ("Simple Button")
    framed1.add_child (framed2, button)
    table.add_child (1, 0, framed1)

    # Create a Frame with alignment.
    frame_align = VFrame (Label ("VFrame with right alignment"))
    frame_align.align = ALIGN_RIGHT
    label1 = Label ("Label")
    label2 = Label ("Even longer label")
    button = CheckButton ("A CheckButton")
    frame_align.add_child (label1, label2, button)
    table.add_child (1, 1, frame_align)

    # Add insensitive frames.
    hframe = HFrame (Label ("Insensitive HFrame"))
    hframe.sensitive = False
    table.add_child (0, 2, hframe)
    
    vframe = VFrame (Label ("Insensitive VFrame"))
    vframe.sensitive = False
    table.add_child (1, 2, vframe)
    
    for i in xrange(3):
        btn = Button ("Button %d" % i)
        hframe.add_child (btn)
        btn2 = Button ("Button %d" % i)
        vframe.add_child (btn2)
   
    return table

if __name__ == "__main__":
    # Initialize the drawing window.
    re = Renderer ()
    re.create_screen (600, 300)
    re.title = "Frame examples"
    re.color = (234, 228, 223)
    re.add_widget (create_frame_view ())

    # Start the main rendering loop.
    re.start ()

Example 37. Frame example


Table

Table widgets, which inherit from the Container class, allow a table-like placement of their children in rows and columns. The children can be placed in the table cells and each cell supports an individual alignment.

The Table constructor expects the row and column dimensions to set up for the table, so that

table = Table (2, 2)
table2 = Table (3, 5)
        
would create two tables. The first would be a quadratic one with two rows, of which each serves two columns, which allows to place four children in it, the second sets up three rows with 5 columns in each of it allowing to hold up to fifteen children.

Adding children is different from the usual Container methods. The Table needs the additional information, in which cell to place the child. Thus instead of using

table.add_child (child_widget1, child_widget2) # This does not work!
        
you have to add children by identifying the row and column to place them into.
table.add_child (row, column, child_widget1)
table.add_child (row1, column1, child_widget2)
        
Removing children from the Table in contrast works in the same way you already learned about in the section called “Container”.

Note

Assigning the children attribute of the Table with a list of widgets adds the children row for row to it.

As said above, each cell of the Table supports using an own alignment, which can be set with the set_align() method.

table.set_align (0, 1, ALIGN_TOP)
table.set_align (1, 1, ALIGN_LEFT)
        
Different alignments can be combined, although not any case makes sense and some alignment combinations conflict with each other. Thus the table uses a different aligning priority, which is explained in detail in the Table documentation.
# Aligning the child at the topleft.
table.set_align (0, 1, ALIGN_TOP | ALIGN_LEFT)

# Conflicting alignments, thus using the higher priority of ALIGN_TOP.
table.set_align (0, 1, ALIGN_TOP | ALIGN_BOTTOM)

# Again a conflict. ALIGN_NONE has the lowest priority, thus it will be
# left aligned.
table.set_align (0, 1, ALIGN_LEFT | ALIGN_NONE)
        
Two additional methods allow you to align a complete column or row using a specific alignment value.
table.set_row_align (row, alignment)
table.set_column_align (column, alignment)
        

Below you will find an example to illustrate most of the abilities of the Table widget class.

You can find the following example as a python script under examples/table.py.

# Table examples.
from ocempgui.widgets import Renderer, Table, Label, Button
from ocempgui.widgets.Constants import *

def create_table_view ():
    # Crate and display a Table.
    table = Table (9, 2)
    table.spacing = 5
    table.topleft = 5, 5

    label = Label ("Nonaligned wide Label")
    table.add_child (0, 0, label)
    table.add_child (0, 1, Button ("Simple Button"))

    label = Label ("Top align")
    table.add_child (1, 0, label)
    table.set_align (1, 0, ALIGN_TOP)
    table.add_child (1, 1, Button ("Simple Button"))

    label = Label ("Bottom align")
    table.add_child (2, 0, label)
    table.set_align (2, 0, ALIGN_BOTTOM)
    table.add_child (2, 1, Button ("Simple Button"))
    
    label = Label ("Left align")
    table.add_child (3, 0, label)
    table.set_align (3, 0, ALIGN_LEFT)
    table.add_child (3, 1, Button ("Simple Button"))
    
    label = Label ("Right align")
    table.add_child (4, 0, label)
    table.set_align (4, 0, ALIGN_RIGHT)
    table.add_child (4, 1, Button ("Simple Button"))

    label = Label ("Topleft align")
    table.add_child (5, 0, label)
    table.set_align (5, 0, ALIGN_TOP | ALIGN_LEFT)
    table.add_child (5, 1, Button ("Simple Button"))

    label = Label ("Topright align")
    table.add_child (6, 0, label)
    table.set_align (6, 0, ALIGN_TOP | ALIGN_RIGHT)
    table.add_child (6, 1, Button ("Simple Button"))

    label = Label ("Bottomleft align")
    table.add_child (7, 0, label)
    table.set_align (7, 0, ALIGN_BOTTOM |ALIGN_LEFT)
    table.add_child (7, 1, Button ("Simple Button"))

    label = Label ("Bottomright align")
    table.add_child (8, 0, label)
    table.set_align (8, 0, ALIGN_BOTTOM |ALIGN_RIGHT)
    table.add_child (8, 1, Button ("Simple Button"))

    return table

if __name__ == "__main__":
    # Initialize the drawing window.
    re = Renderer ()
    re.create_screen (250, 350)
    re.title = "Table examples"
    re.color = (234, 228, 223)
    re.add_widget (create_table_view ())
    # Start the main rendering loop.
    re.start ()

Example 38. Table example


ScrolledWindow

ScrolledWindow widgets are Bin widgets that add scrolling abilities to their attached child. This is extremely useful for situations, in which the widget to scroll should not exceed a specific size, but has to display all of its data. Putting such a widget in a ScrolledWindow allows you to respect this specific size, while the widget can grow as it wants.

You create a ScrolledWindow using

window = ScrolledWindow (width, height)
        
where width and height denote values for the size attribute of the ScrolledWindow. The newly created ScrolledWindow will not exceed this size by default.

The widget, which needs to be scrolled is packed into the ScrolledWindow the usual Bin way.

window.child = widget_to_scroll
        

To allow a flexibly scrolling behaviour, you can adjust the scrolling attribute to make the ScrolledWindow automatic scrolling, always scrolling or never scrolling.

window.scrolling = SCROLL_ALWAYS
window.scrolling = SCROLL_NEVER
window.scrolling = SCROLL_AUTO
        
This setting influences the visibility of the both ScrollBar controls attached to the window and is described in details in the set_scrolling() method documentation.

You also can influence the scrollbars of the ScrolledWindow programmatically.

window.vscrollbar.value = 0
window.hscrollbar.value = window.vscrollbar.maximum
       
The above code would cause the ScrolledWindow to scroll to the topright of its attached child.

The ScrolledWindow widget natively listens to two signals to support better navigation possibilities. Those are

  • SIG_MOUSEDOWN - Invoked, when a mouse button is pressed down on the ScrolledWindow.

  • SIG_KEYDOWN - Invoked, when a key gets pressed.

ScrolledList

The ScrolledList widget class can be used to display and manage collections in a list-style look and feel. Technically it is a ScrolledWindow, which holdes a ListViewPort that takes care of displaying the list contents.

As the ScrolledWindow the ScrolledList supports different scrolling types and anything else, while the keyboard and mouse behaviour differ slightly to match the needs of list browsing.

To create a ScrolledList you have to supply the sizing dimensions and an optional collection, which makes up the contents.

scrolledlist = ScrolledList (width, height, collection=None)
        
If the collection is not specified, a basic ListItemCollection (explained later) object is automatically bound to it. The ScrolledList however only accepts collections, which inherit from the ListItemCollection.

Items can be added and removed dynamically to the list using the items property.

for no in xrange (10):
    scrolledlist.items.append (TextListItem ("Item no. %d" % no))

# Last one was too much.
scrolledlist.items.remove (scrolledlist.items[-1])
        
The bound ListItemCollection wraps a list and fully supports all important operations of, including slicing, indexed item access and sorting. More details about the ListItemCollection can be found in the section called “ListItemCollection”.

FileList

The FileList widget is useful to display filesystem contents in a list-style manner. It supports the distinction between different filetypes through the FileListItem item type and allows you to browse filesystem contents.

As the ScrolledList, from which the FileList inherits, you need to pass the size dimensions it should occupy and optionally the initial starting directory to list the contents of.

filelist = FileList (200, 400, "/usr/home")
        
If the initial directory remains unset, the FileList will list the contents of the current directory as set in os.curdir.

It allows to list directories programmatically through the directory attribute and set_directory() method.

filelist.directory = "C:\"
filelist.set_directory ("/tmp")
        
If the user however has not the correct access rights for a specific directory, the FileList will not list it, but instead provide an acoustic signal using print "\a".

The FileList additionally supports the SIG_DOUBLECLICKED signal to allow changing directories interactively. Of course own methods can be bound to it.