6. Contêineres de Layout

Enquanto muitos toolkits de GUI exigem que você coloque precisamente widgets em uma janela, usando posicionamento absoluto, o GTK+ usa uma abordagem diferente. Em vez de especificar a posição e o tamanho de cada widget na janela, você pode organizar seus widgets em linhas, colunas e/ou tabelas. O tamanho da sua janela pode ser determinado automaticamente, com base nos tamanhos dos widgets que ela contém. E os tamanhos dos widgets, por sua vez, são determinados pela quantidade de texto que eles contêm, ou os tamanhos mínimo e máximo que você especifica, e/ou como você solicitou que o espaço disponível seja compartilhado entre conjuntos de widgets. Você pode aperfeiçoar seu layout especificando a distância de preenchimento e os valores de centralização para cada um de seus widgets. O GTK+ usa todas essas informações para redimensionar e reposicionar tudo de maneira sensata e suave quando o usuário manipula a janela.

O GTK+ organiza widgets hierarquicamente, usando contêineres. Eles são invisíveis para o usuário-final e são inseridos em uma janela ou colocados entre si para os componentes do layout. Existem dois tipos de contêineres: contêineres filho único, todos descendentes de Gtk.Bin, e contêineres com vários filhos, que são descendentes de Gtk.Container. Os mais usados são caixas verticais ou horizontais (Gtk.Box) e grades (Gtk.Grid).

6.1. Box

As cauxas Box são contêineres invisíveis nos quais podemos empacotar nossos widgets. Ao agrupar widgets em uma caixa horizontal, os objetos são inseridos horizontalmente da esquerda para a direita ou da direita para a esquerda, dependendo se Gtk.Box.pack_start() ou Gtk.Box.pack_end() for usado. Em uma caixa vertical, os widgets são empacotados de cima para baixo ou vice-versa. Você pode usar qualquer combinação de caixas dentro ou ao lado de outras caixas para criar o efeito desejado.

6.1.1. Exemplo

Vamos dar uma olhada em uma versão ligeiramente modificada do exemplo estendido com dois botões.

_images/layout_box_example.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk


class MyWindow(Gtk.Window):
    def __init__(self):
        super().__init__(title="Hello World")

        self.box = Gtk.Box(spacing=6)
        self.add(self.box)

        self.button1 = Gtk.Button(label="Hello")
        self.button1.connect("clicked", self.on_button1_clicked)
        self.box.pack_start(self.button1, True, True, 0)

        self.button2 = Gtk.Button(label="Goodbye")
        self.button2.connect("clicked", self.on_button2_clicked)
        self.box.pack_start(self.button2, True, True, 0)

    def on_button1_clicked(self, widget):
        print("Hello")

    def on_button2_clicked(self, widget):
        print("Goodbye")


win = MyWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

Primeiro, criamos um contêiner de caixa orientado horizontalmente, onde 6 pixels são colocados entre os filhos. Esta caixa se torna o filho da janela de nível superior.

        self.box = Gtk.Box(spacing=6)
        self.add(self.box)

Posteriormente, adicionamos dois botões diferentes ao contêiner da caixa.

        self.button1 = Gtk.Button(label="Hello")
        self.button1.connect("clicked", self.on_button1_clicked)
        self.box.pack_start(self.button1, True, True, 0)

        self.button2 = Gtk.Button(label="Goodbye")
        self.button2.connect("clicked", self.on_button2_clicked)
        self.box.pack_start(self.button2, True, True, 0)

Enquanto com os widgets Gtk.Box.pack_start() estão posicionados da esquerda para a direita, Gtk.Box.pack_end() os posiciona da direita para a esquerda.

6.2. Grade

Gtk.Grid é um contêiner que organiza seus widgets filhos em linhas e colunas, mas você não precisa especificar as dimensões no construtor. Os filhos são adicionados usando Gtk.Grid.attach(). Eles podem abranger várias linhas ou colunas. O método Gtk.Grid.attach() usa cinco parâmetros:

  1. O parâmetro child é o Gtk.Widget para adicionar.

  2. left é o número da coluna para anexar o lado esquerdo de child em.

  3. top indica o número da linha para anexar o lado superior do child.

  4. width e height indicam o número de colunas que o child irá abranger, e o número de linhas que o child irá abranger, respectivamente.

Também é possível adicionar um child ao lado de um child existente, usando Gtk.Grid.attach_next_to(), que também usa cinco parâmetros:

  1. child é o Gtk.Widget para adicionar, como acima.

  2. sibling é um widget filho existente de self (uma instância Gtk.Grid) ou None. O widget child será colocado próximo ao sibling, ou se sibling for None, no início ou no final da grade.

  3. side é um Gtk.PositionType indicando o lado do sibling que child é posicionado ao lado de.

  4. width e height indicam o número de colunas e linhas que o widget child abrangerá, respectivamente.

Finalmente, Gtk.Grid pode ser usado como Gtk.Box usando apenas Gtk.Grid.add(), que colocará os filhos um ao lado do outro na direção determinada pela propriedade “orientation” (o padrão é Gtk.Orientation.HORIZONTAL).

6.2.1. Exemplo

_images/layout_grid_example.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk


class GridWindow(Gtk.Window):
    def __init__(self):

        super().__init__(title="Grid Example")

        button1 = Gtk.Button(label="Button 1")
        button2 = Gtk.Button(label="Button 2")
        button3 = Gtk.Button(label="Button 3")
        button4 = Gtk.Button(label="Button 4")
        button5 = Gtk.Button(label="Button 5")
        button6 = Gtk.Button(label="Button 6")

        grid = Gtk.Grid()
        grid.add(button1)
        grid.attach(button2, 1, 0, 2, 1)
        grid.attach_next_to(button3, button1, Gtk.PositionType.BOTTOM, 1, 2)
        grid.attach_next_to(button4, button3, Gtk.PositionType.RIGHT, 2, 1)
        grid.attach(button5, 1, 2, 1, 1)
        grid.attach_next_to(button6, button5, Gtk.PositionType.RIGHT, 1, 1)

        self.add(grid)


win = GridWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

6.3. ListBox

A Gtk.ListBox é um contêiner vertical que contém Gtk.ListBoxRow filhos. Essas linhas podem ser classificadas e filtradas dinamicamente e os cabeçalhos podem ser adicionados dinamicamente, dependendo do conteúdo da linha. Também permite navegação e seleção de teclado e mouse como uma lista típica.

Using Gtk.ListBox is often an alternative to Gtk.TreeView, especially when the list content has a more complicated layout than what is allowed by a Gtk.CellRenderer, or when the content is interactive (e.g. has a button in it).

Embora um Gtk.ListBox deva ter apenas Gtk.ListBoxRow filhos, você pode adicionar qualquer tipo de widget a ele via Gtk.Container.add() e um Gtk.ListBoxRow widget será automaticamente inserido entre a lista e o widget.

6.3.1. Exemplo

_images/listbox_example.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk


class ListBoxRowWithData(Gtk.ListBoxRow):
    def __init__(self, data):
        super().__init__()
        self.data = data
        self.add(Gtk.Label(label=data))


class ListBoxWindow(Gtk.Window):
    def __init__(self):
        super().__init__(title="ListBox Demo")
        self.set_border_width(10)

        box_outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        self.add(box_outer)

        listbox = Gtk.ListBox()
        listbox.set_selection_mode(Gtk.SelectionMode.NONE)
        box_outer.pack_start(listbox, True, True, 0)

        row = Gtk.ListBoxRow()
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50)
        row.add(hbox)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        hbox.pack_start(vbox, True, True, 0)

        label1 = Gtk.Label(label="Automatic Date & Time", xalign=0)
        label2 = Gtk.Label(label="Requires internet access", xalign=0)
        vbox.pack_start(label1, True, True, 0)
        vbox.pack_start(label2, True, True, 0)

        switch = Gtk.Switch()
        switch.props.valign = Gtk.Align.CENTER
        hbox.pack_start(switch, False, True, 0)

        listbox.add(row)

        row = Gtk.ListBoxRow()
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50)
        row.add(hbox)
        label = Gtk.Label(label="Enable Automatic Update", xalign=0)
        check = Gtk.CheckButton()
        hbox.pack_start(label, True, True, 0)
        hbox.pack_start(check, False, True, 0)

        listbox.add(row)

        row = Gtk.ListBoxRow()
        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=50)
        row.add(hbox)
        label = Gtk.Label(label="Date Format", xalign=0)
        combo = Gtk.ComboBoxText()
        combo.insert(0, "0", "24-hour")
        combo.insert(1, "1", "AM/PM")
        hbox.pack_start(label, True, True, 0)
        hbox.pack_start(combo, False, True, 0)

        listbox.add(row)

        listbox_2 = Gtk.ListBox()
        items = "This is a sorted ListBox Fail".split()

        for item in items:
            listbox_2.add(ListBoxRowWithData(item))

        def sort_func(row_1, row_2, data, notify_destroy):
            return row_1.data.lower() > row_2.data.lower()

        def filter_func(row, data, notify_destroy):
            return False if row.data == "Fail" else True

        listbox_2.set_sort_func(sort_func, None, False)
        listbox_2.set_filter_func(filter_func, None, False)

        def on_row_activated(listbox_widget, row):
            print(row.data)

        listbox_2.connect("row-activated", on_row_activated)

        box_outer.pack_start(listbox_2, True, True, 0)
        listbox_2.show_all()


win = ListBoxWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

6.4. Stack e StackSwitcher

A Gtk.Stack é um contêiner que mostra apenas um de seus filhos por vez. Em contraste com Gtk.Notebook, Gtk.Stack não fornece um meio para os usuários alterarem o filho visível. Em vez disso, o widget Gtk.StackSwitcher pode ser usado com Gtk.Stack para fornecer essa funcionalidade.

Transições entre páginas podem ser animadas como slides ou fades. Isso pode ser controlado com Gtk.Stack.set_transition_type(). Essas animações respeitam a configuração “gtk-enable-animations”.

A velocidade de transição pode ser ajustada com Gtk.Stack.set_transition_duration()

O widget Gtk.StackSwitcher atua como um controlador para um Gtk.Stack; Ele mostra uma linha de botões para alternar entre as várias páginas do widget de pilha associado.

Todo o conteúdo para os botões vem das propriedades filho do Gtk.Stack.

É possível associar múltiplos widgets Gtk.StackSwitcher com o mesmo widget Gtk.Stack.

6.4.1. Exemplo

_images/stack_example.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk


class StackWindow(Gtk.Window):
    def __init__(self):
        super().__init__(title="Stack Demo")
        self.set_border_width(10)

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        self.add(vbox)

        stack = Gtk.Stack()
        stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
        stack.set_transition_duration(1000)

        checkbutton = Gtk.CheckButton(label="Click me!")
        stack.add_titled(checkbutton, "check", "Check Button")

        label = Gtk.Label()
        label.set_markup("<big>A fancy label</big>")
        stack.add_titled(label, "label", "A label")

        stack_switcher = Gtk.StackSwitcher()
        stack_switcher.set_stack(stack)
        vbox.pack_start(stack_switcher, True, True, 0)
        vbox.pack_start(stack, True, True, 0)


win = StackWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

6.5. HeaderBar

A Gtk.HeaderBar é semelhante a uma horizontal Gtk.Box, permite colocar filhos no início ou no final. Além disso, permite que um título seja exibido. O título será centrado em relação à largura da caixa, mesmo que os filhos de ambos os lados ocupem diferentes quantidades de espaço.

Como o GTK+ agora tem suporte a Client Side Decoration, um Gtk.HeaderBar pode ser usado no lugar da barra de título (que é renderizada pelo Gerenciador de Janelas).

A Gtk.HeaderBar geralmente está localizado na parte superior de uma janela e deve conter controles comumente usados que afetam o conteúdo abaixo. Eles também fornecem acesso a controles de janela, incluindo o botão de fechar janela e o menu de janela.

6.5.1. Exemplo

_images/headerbar_example.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio


class HeaderBarWindow(Gtk.Window):
    def __init__(self):
        super().__init__(title="HeaderBar Demo")
        self.set_border_width(10)
        self.set_default_size(400, 200)

        hb = Gtk.HeaderBar()
        hb.set_show_close_button(True)
        hb.props.title = "HeaderBar example"
        self.set_titlebar(hb)

        button = Gtk.Button()
        icon = Gio.ThemedIcon(name="mail-send-receive-symbolic")
        image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
        button.add(image)
        hb.pack_end(button)

        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
        Gtk.StyleContext.add_class(box.get_style_context(), "linked")

        button = Gtk.Button()
        button.add(
            Gtk.Arrow(arrow_type=Gtk.ArrowType.LEFT, shadow_type=Gtk.ShadowType.NONE)
        )
        box.add(button)

        button = Gtk.Button.new_from_icon_name("pan-end-symbolic", Gtk.IconSize.MENU)
        box.add(button)

        hb.pack_start(box)

        self.add(Gtk.TextView())


win = HeaderBarWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

6.6. FlowBox

Nota

Este exemplo requer pelo menos GTK+ 3.12.

A Gtk.FlowBox é um contêiner que posiciona widgets filhos em sequência de acordo com sua orientação.

Por exemplo, com a orientação horizontal, os widgets serão organizados da esquerda para a direita, iniciando uma nova linha na linha anterior, quando necessário. Reduzir a largura neste caso exigirá mais linhas, portanto, uma altura maior será solicitada.

Da mesma forma, com a orientação vertical, os widgets serão organizados de cima para baixo, iniciando uma nova coluna à direita quando necessário. Reduzir a altura exigirá mais colunas, portanto será solicitada uma largura maior.

Os filhos de uma Gtk.FlowBox podem ser classificados e filtrados dinamicamente.

Embora uma Gtk.FlowBox deva ter apenas filhos Gtk.FlowBoxChild, você pode adicionar qualquer tipo de widget a ele via Gtk.Container.add(), e um widget Gtk.FlowBoxChild será automaticamente inserido entre a caixa e o widget.

6.6.1. Exemplo

_images/flowbox_example.png
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk


class FlowBoxWindow(Gtk.Window):
    def __init__(self):
        super().__init__(title="FlowBox Demo")
        self.set_border_width(10)
        self.set_default_size(300, 250)

        header = Gtk.HeaderBar(title="Flow Box")
        header.set_subtitle("Sample FlowBox app")
        header.props.show_close_button = True

        self.set_titlebar(header)

        scrolled = Gtk.ScrolledWindow()
        scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)

        flowbox = Gtk.FlowBox()
        flowbox.set_valign(Gtk.Align.START)
        flowbox.set_max_children_per_line(30)
        flowbox.set_selection_mode(Gtk.SelectionMode.NONE)

        self.create_flowbox(flowbox)

        scrolled.add(flowbox)

        self.add(scrolled)
        self.show_all()

    def on_draw(self, widget, cr, data):
        context = widget.get_style_context()

        width = widget.get_allocated_width()
        height = widget.get_allocated_height()
        Gtk.render_background(context, cr, 0, 0, width, height)

        r, g, b, a = data["color"]
        cr.set_source_rgba(r, g, b, a)
        cr.rectangle(0, 0, width, height)
        cr.fill()

    def color_swatch_new(self, str_color):
        rgba = Gdk.RGBA()
        rgba.parse(str_color)

        button = Gtk.Button()

        area = Gtk.DrawingArea()
        area.set_size_request(24, 24)
        area.connect("draw", self.on_draw, {"color": rgba})

        button.add(area)

        return button

    def create_flowbox(self, flowbox):
        colors = [
            "AliceBlue",
            "AntiqueWhite",
            "AntiqueWhite1",
            "AntiqueWhite2",
            "AntiqueWhite3",
            "AntiqueWhite4",
            "aqua",
            "aquamarine",
            "aquamarine1",
            "aquamarine2",
            "aquamarine3",
            "aquamarine4",
            "azure",
            "azure1",
            "azure2",
            "azure3",
            "azure4",
            "beige",
            "bisque",
            "bisque1",
            "bisque2",
            "bisque3",
            "bisque4",
            "black",
            "BlanchedAlmond",
            "blue",
            "blue1",
            "blue2",
            "blue3",
            "blue4",
            "BlueViolet",
            "brown",
            "brown1",
            "brown2",
            "brown3",
            "brown4",
            "burlywood",
            "burlywood1",
            "burlywood2",
            "burlywood3",
            "burlywood4",
            "CadetBlue",
            "CadetBlue1",
            "CadetBlue2",
            "CadetBlue3",
            "CadetBlue4",
            "chartreuse",
            "chartreuse1",
            "chartreuse2",
            "chartreuse3",
            "chartreuse4",
            "chocolate",
            "chocolate1",
            "chocolate2",
            "chocolate3",
            "chocolate4",
            "coral",
            "coral1",
            "coral2",
            "coral3",
            "coral4",
        ]

        for color in colors:
            button = self.color_swatch_new(color)
            flowbox.add(button)


win = FlowBoxWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

6.7. Notebook

O widget Gtk.Notebook é um Gtk.Container cujos filhos são páginas que podem ser alternadas usando rótulos de guias ao longo de uma borda.

There are many configuration options for GtkNotebook. Among other things, you can choose on which edge the tabs appear (see Gtk.Notebook.set_tab_pos()), whether, if there are too many tabs to fit the notebook should be made bigger or scrolling arrows added (see Gtk.Notebook.set_scrollable()), and whether there will be a popup menu allowing the users to switch pages (see Gtk.Notebook.popup_enable(), Gtk.Notebook.popup_disable()).

6.7.1. Exemplo

_images/notebook_plain_example.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import Gtk


class MyWindow(Gtk.Window):
    def __init__(self):
        super().__init__(title="Simple Notebook Example")
        self.set_border_width(3)

        self.notebook = Gtk.Notebook()
        self.add(self.notebook)

        self.page1 = Gtk.Box()
        self.page1.set_border_width(10)
        self.page1.add(Gtk.Label(label="Default Page!"))
        self.notebook.append_page(self.page1, Gtk.Label(label="Plain Title"))

        self.page2 = Gtk.Box()
        self.page2.set_border_width(10)
        self.page2.add(Gtk.Label(label="A page with an image for a Title."))
        self.notebook.append_page(
            self.page2, Gtk.Image.new_from_icon_name("help-about", Gtk.IconSize.MENU)
        )


win = MyWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()