⇤ ← Revisão 1e 2008-02-19 12:31:04
10651
Comentário:
|
21039
|
Deleções são marcadas assim. | Adições são marcadas assim. |
Linha 104: | Linha 104: |
image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) | image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) |
Linha 113: | Linha 114: |
Nosso programa agora ficará assim: {{{!python |
Nosso programa ficará assim: {{{#!python |
Linha 163: | Linha 164: |
image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) | image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) |
Linha 247: | Linha 249: |
{{{!python | {{{#!python |
Linha 255: | Linha 257: |
{{{#!python def __init__(self, label_text, notebook, widgeet): # ... self.button = self.get_close_button() # ... def get_close_button(self): button = gtk.Button() image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) button.add(image) button.set_relief(gtk.RELIEF_NONE) button.connect('clicked', self.on_button_clicked) return button }}} A melhoria é evidente... attachment:diferenca-botoes2.png ...mas os botões ainda continuam grandes demais. Um bom tamanho seria algo como uns oito ''pixels'' maior que o ícone está dentro deles. Entretanto, nós não sabemos, nem temos como recuperar as dimensões de um '''[http://www.pygtk.org/docs/pygtk/class-gtkimage.html gtk.Image]''' que contenha um ícone de ''stock''. O que sabemos sobre o tal ícone é que o tamanho dele é definido pela constante '''[http://www.pygtk.org/docs/pygtk/gtk-constants.html#gtk-icon-size-constants gtk.ICON_SIZE_MENU]''', que ''não'' é o tamanho em ''pixels''. Para recuperar as dimensões de um ícone a partir das constatnes '''gtk.ICON_SIZE_*''', nós utilizamos a função '''[http://www.pygtk.org/docs/pygtk/class-gtkiconsource.html#function-gtk--icon-size-lookup gtk.icon_size_lookup()]'''. Essa função espera como argumento uma constante '''gtk.ICON_SIZE_*''' e retorna uma tupla contendo a largura e a altura do ícone. Uma vez que tenhamos obtido esses valores, basta requerir que o botão os adote, através do método '''[http://www.pygtk.org/docs/pygtk/class-gtkwidget.html#method-gtkwidget--set-size-request gtk.Button.set_size_request()]'''. Assim, podemos fazer um novo método '''tablabel.TabLabel.get_close_button()''': {{{#!python def get_close_button(self): button = gtk.Button() # Add icon and remove "visible borders" image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) button.add(image) button.set_relief(gtk.RELIEF_NONE) # Change size width, height = gtk.icon_size_lookup(image.get_pixel_size()) button.set_size_request(width+4, height+4) # Connect callback button.connect('clicked', self.on_button_clicked) return button }}} As nossas abas, agora, estão assim: attachment:diferenca-botoes3.png É bem verdade que as abas agora são maiores, mas já estão mais bonitas. Infelizmente, reduzir os botões ainda mais acaba truncando os ícones. Se alguém souber como reduzir um pouco mais o botão sem truncar os ícones, me avise :) == Acrescentando setas de rolagem e lidando com alinhamento de rótulos == Se você está procurando por páginas de '''gtk.Notebook''' que possuam botões de fechar, é provável que você vá adicionar e remover páginas arbitrariamente ao ''notebook''. Se adicionarmos muitas páginas, as abas podem forçar a janela a crescer demais. attachment:grande-demais-menor.png A solução é habilitar as barras de rolagem de abas no notebook. Isso é bastante simples: basta usar o método '''[http://www.pygtk.org/docs/pygtk/class-gtknotebook.html#method-gtknotebook--set-scrollable gtk.Notebook.set_scrollable()]'''. Assim, conseguimos o novo método '''enhanced.EnhancedNotebook.__init__()''' abaixo: {{{#!python class EnhancedNotebook(gtk.Notebook): def __init__(self): gtk.Notebook.__init__(self) self.set_scrollable(True) }}} Com essa pequena linha, conseguimos uma melhora considerável: attachment:nao-grande.png == Últimos retoques == Nosso '''EnhancedNotebook''' está ''quase'' pronto. Para terminar, vamos chamar a atenção para um detalhe chato que pode passar despercebido. Veja o ''screenshot'' abaixo. Nele, fizemos uma combinação de abas e tamanho da janela específica para deixar claro o ponto: a posição do rótulo e do botão de fechar podem variar muito de acordo com o tamanho da aba. Seria melhor, porém, que o rótulo ficasse sempre numa posição à esquerda, e o botão sempre numa posição à direita. attachment:abas-distorcidas.png Pois bem, os deslocamentos ocorrem porque os ''widgets'' por padrão tentam dividir o espaço todo.que lhes for disponível, expandindo para ocupá-lo ao máximo. Nos ''widgets'' '''gtk.Label''', o texto por padrão tenta ficar no centro do rótulo. Nos ''widgets'' '''gtk.Button''', o objeto dentro do botão (no caso, uma imagem) faz o mesmo. Para cada ''widget'', há uma solução. No caso do rótulo, basta usar o método '''[http://www.pygtk.org/docs/pygtk/class-gtkmisc.html#method-gtkmisc--set-alignment gtk.Label.set_alignment()]''', que define o alinhamento do texto. Esse método espera dois argumentos. O primeiro argumento definirá o alinhamento horizontal do texto do rótulo: se o valor desse primeiro argumento for 0, o texto ficará alinhado à esquerda; se o valor for 1, ficará alinhado à direita; se for 0,5 (o valor padrão para os '''gtk.Label'''), ficará centralizado[[FootNote(Em verdade, se pode escolher qualquer valor entre zero e um, que o texto será alinhado nessa posição, relativamente ao tamanho do ''widget''. Sugerimos que estude melhor esse método, para que pegue a exata idéia de seu funcionamento.)]]. O segundo argumento define o alinhamento vertical: se seu valor for zero, o texto ficará junto ao topo do '''gtk.Label'''; se for 1, ficará colado à base; se for 0,5, o texto ficará exatamente no meio do ''widget'', verticalmente. Nosso interesse é que o texto fique alinhado à esquerda horizontalmente, mas no meio, verticalmente. Faremos isso, então, no método '''tablabel.TabLabel.__init__()''': {{{#!python class TabLabel(gtk.HBox): def __init__(self, label_text, notebook, widget): gtk.HBox.__init__(self) self.notebook = notebook self.widget = widget self.button = self.get_close_button() self.label = gtk.Label(label_text) # Changing label alignment self.label.set_alignment(0, 0.5) self.pack_start(self.label) self.pack_start(self.button) self.show_all() }}} Quanto ao botão, nos interessa simplesmente que ele não mude de tamanho. O método '''gtk.Button.set_size_request()''' não nos serve nesse caso, mas podemos empacotá-lo de modo a que ele não se expanda. Podemos aproveitar e já empacotá-lo ao final do '''gtk.HBox''', de modo que fique sempre à direita. Para isso, basta substituir a linha {{{#!python self.pack_start(self.button) }}} do '''tablabel.TabLabel.__init__()''' por {{{#!python self.pack_end(self.button, expand=False) }}} Enfim, para deixar as abas realmente elegantes, ainda sugerimos que se adicionte um pequeno ''padding'' no empacotamento (algo como três ''pixels''). == Conclusão == Você veio aqui atrás de adicionar botões de fechar em abas de '''gtk.Notebook''', e encontrou um texto gigante falando de detalhes obscuros do processo de criar esses botões... Pois bem, se você leu até aqui, eis seu prêmio! O código abaixo oferece um ''notebook'' cujas páginas podem ser fechadas através do botãozinho de fechar em suas abas. Pode copiar, colar em um arquivo e usar. É o resultado final desse artigo. {{{#!python #!/usr/bin/env python # -*- coding: utf-8 -*- import gtk class TabLabel(gtk.HBox): """ Widget used as label for gtk.Notebook page tabs with close buttons. """ def __init__(self, label_text, notebook, widget): """ Creates all widgets needed for labeling and closing the tab. label_text A string containg the label text. notebook The gtk.Notebook instance which will contain the "closable" pages. widget The widget to be added to the gtk.Notebook page. """ gtk.HBox.__init__(self) # Received self.notebook = notebook self.widget = widget # Creating self.button = self.get_close_button() self.label = gtk.Label(label_text) self.label.set_alignment(0, 0.5) # Packing self.pack_start(self.label, padding=3) self.pack_end(self.button, expand=False, padding=3) # Showing self.show_all() def get_close_button(self): """ Returns a button configured to be used as a close button. """ button = gtk.Button() # Adding image, removing relief image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) button.add(image) button.set_relief(gtk.RELIEF_NONE) # Changing size width, height = gtk.icon_size_lookup( gtk.ICON_SIZE_MENU) button.set_size_request(width+8, height+8) # Connecting callback button.connect('clicked', self.on_button_clicked) return button def on_button_clicked(self, button): """ Callback method to be connected to the 'clicked' close button signal. button Clicked button. Argument required by signal. """ position = self.notebook.page_num(self.widget) self.notebook.remove_page(position) class EnhancedNotebook(gtk.Notebook): """ An enhanced gtk.Notebook class, which provides a method for adding pages which can be closed using a close button in its label. """ def __init__(self): gtk.Notebook.__init__(self) self.set_scrollable(True) def insert_page_with_close_button(self, child, tab_text, position=-1): """ Adds a new closabe page. child The widget to be added in the page. tab_text A string containing the text to be shown in the tab label. position The position where the page will be inserted: first, second etc. By default, the tab is inserted at the end of the notebook. You can explicit it giving -1 as this argument. """ # Note that we give the text in a string, not a widget label = TabLabel(tab_text, self, child) self.insert_page(child, label, position) }}} E, para nos despedirmos, um último ''screenshot'', mostrando o resultado final: attachment:parfait.png |
Botão de Fechar em abas de gtk.Notebook
O widget de PyGTK [http://www.pygtk.org/docs/pygtk/class-gtknotebook.html gtk.Notebook] é bastante útil para organizar telas que venham a conter vários dados. Para fazer, digamos, telas de configuração, é bastante simples. O código abaixo, por exemplo, cria um stub de tela de configuração com duas abas.
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 import gtk
4 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
5 # Creating notebook
6 notebook = gtk.Notebook()
7 # Creating pages
8 # First tab label
9 this_label = gtk.Label("Isso")
10 # First page content
11 this_content = gtk.Label('Configura\nIsso')
12 that_label = gtk.Label("Aquilo")
13 # First page content
14 that_content = gtk.Label('Configura\nAquilo')
15 # Add to notebook
16 notebook.append_page(this_content, this_label)
17 notebook.append_page(that_content, that_label)
18 # Finishing
19 window.add(notebook)
20 window.show_all()
21 gtk.main()
O resultado é...
attachment:tela-notebook1.png
Às vezes, porém, queremos criar abas dinâmicamente, e queremos poder fechá-las após criá-las. Uma maneira de fechar bastante elegante é adicionar um botão de fechar em cada aba. gtk.Notebook não faz isso automaticamente, mas a tarefa é simples. Vamos executá-la passo a passo aqui.
Acrescentando botão à aba
No código acima, o rótulo da aba é o segundo argumento do método [http://www.pygtk.org/docs/pygtk/class-gtknotebook.html gtk.Notebook.append_page()]. Para adicionar um botão ao rótulo, poderíamos trocar o objeto [http://www.pygtk.org/docs/pygtk/class-gtklabel.html gtk.Label] por um [http://www.pygtk.org/docs/pygtk/class-gtkhbox.html gtk.HBox] que contivesse tanto o rótulo quanto o botão, como na função abaixo:
1 def get_tab_label(label_text):
2 box = gtk.HBox()
3 # Creating button
4 button = gtk.Button()
5 # Retrieving "close" icon
6 image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
7 button.add(image)
8 # Creating label
9 label = gtk.Label(label_text)
10 # Putting in the gtk.HBox
11 box.pack_start(label)
12 box.pack_start(button)
13 box.show_all()
14 return box
Substituímos os gtk.Labels utilizados como rótulos de abas por uma chamada de get_tab_label:
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 import gtk
4 def get_tab_label(label_text):
5 box = gtk.HBox()
6 button = gtk.Button()
7 image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
8 button.add(image)
9 label = gtk.Label(label_text)
10 box.pack_start(label)
11 box.pack_start(button)
12 box.show_all()
13 return box
14 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
15 notebook = gtk.Notebook()
16 # Note the difference:
17 this_label = get_tab_label("Isso")
18 this_content = gtk.Label('Configura\nIsso')
19 # Note the difference:
20 that_label = get_tab_label("Aquilo")
21 that_content = gtk.Label('Configura\nAquilo')
22 # Add to notebook
23 notebook.append_page(this_content, this_label)
24 notebook.append_page(that_content, that_label)
25 # Finishing
26 window.add(notebook)
27 window.show_all()
28 gtk.main()
E o resultado é
attachment:tela-notebook2.png
A classe TabLabel
Nós escrevemos uma função para gerar nosso rótulo, mas vamos aperfeiçoar nosso código. Ao invés de uma função, faremos uma classe, descendente de gtk.HBox, que será nosso rótulo. Chamaremos essa classe de TabLabel, e sua função de inicialização é, basicamente, o mesmo que a função get_tab_label() acima. Para efeitos de teste, vamos salvá-la no arquivo tablabel.py.
1 import gtk
2
3 class TabLabel(gtk.HBox):
4 def __init__(self, label_text):
5 gtk.HBox.__init__(self)
6 self.button = gtk.Button()
7 image = gtk.image_new_from_stock(gtk.STOCK_CLOSE,
8 gtk.ICON_SIZE_MENU)
9 self.button.add(image)
10 self.label = gtk.Label(label_text)
11 self.pack_start(self.label)
12 self.pack_start(self.button)
13 # Don't forget: you should show all
14 self.show_all()
Nosso programa ficará assim:
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 import gtk
4 import tablabel
5
6 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
7 notebook = gtk.Notebook()
8 this_label = tablabel.TabLabel("Isso")
9 this_content = gtk.Label('Configura\nIsso')
10 that_label = tablabel.TabLabel("Aquilo")
11 that_content = gtk.Label('Configura\nAquilo')
12 notebook.append_page(this_content, this_label)
13 notebook.append_page(that_content, that_label)
14 window.add(notebook)
15 window.show_all()
16 gtk.main()
Usarmos uma classe, e não mais só uma função, será muito útil quando formos tornar os botões funcionais.
Removendo páginas
Para remover uma página de um gtk.Notebook, usamos o método [http://www.pygtk.org/docs/pygtk/class-gtknotebook.html#method-gtknotebook--remove-page gtk.Notebook.remove_page()]. Esse método espera como argumento um número inteiro que indicará que aba será removida: 0 para a primeira aba, 1, para a segunda etc., sendo que -1 remove a última, seja qual for.
Temos de usar esse método para remover as abas quando clicarmos nos botões, mas precisamos saber qual é o número da aba. Os métodos de inserção de página (gtk.Notebook.append_page(), [http://www.pygtk.org/docs/pygtk/class-gtknotebook.html#method-gtknotebook--prepend-page prepend_page()], [http://www.pygtk.org/docs/pygtk/class-gtknotebook.html#method-gtknotebook--insert-page insert_page()]) retornam o índice da página criada, mas esse índice pode mudar (por exemplo, se alguém fechar uma página anterior).
Uma maneira de recuperar o índice de uma página é através do método [http://www.pygtk.org/docs/pygtk/class-gtknotebook.html#method-gtknotebook--page-num gtk.Notebook.page_num()]. Esse método recebe como argumento um widget. Se alguma página possuir esse widget, o índice dessa aba é retornado. Desse modo, na classe TabLabel, podemos criar um método para ser conectado ao evento clicked do botão da aba que, usando gtk.Notebook.remove_page(), encontre a página correspondente e a remova:
Note que estamos usando campos que não criamos: TabLabel.notebook e TabLabel.widget. A solução é atualizar o método TabLabel.init(), para que receba também, como argumento, o gtk.Notebook onde a aba será usada e o widget que a página armazenará:
1 import gtk
2
3 class TabLabel(gtk.HBox):
4 def __init__(self, label_text, notebook, widget):
5 gtk.HBox.__init__(self)
6 # Values
7 self.notebook = notebook
8 self.widget = widget
9 # Widgets
10 self.button = gtk.Button()
11 image = gtk.image_new_from_stock(gtk.STOCK_CLOSE,
12 gtk.ICON_SIZE_MENU)
13 self.button.add(image)
14 self.label = gtk.Label(label_text)
15 # Packing
16 self.pack_start(self.label)
17 self.pack_start(self.button)
18 # Connecting 'on_button_clicked':
19 self.button.connect('clicked', self.on_button_clicked)
20 # Don't forget: you should show all
21 self.show_all()
22
23 def on_button_clicked(self, button):
24 position = self.notebook.page_num(self.widget)
25 self.notebook.remove_page(position)
Agora, atualizamos o programa...
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 import gtk
4 import tablabel
5
6 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
7 notebook = gtk.Notebook()
8 # Precisa criar conteúdo antes
9 this_content = gtk.Label('Configura\nIsso')
10 this_label = tablabel.TabLabel("Isso", notebook, this_content)
11 that_content = gtk.Label('Configura\nAquilo')
12 that_label = tablabel.TabLabel("Aquilo", notebook, that_content)
13 notebook.append_page(this_content, this_label)
14 notebook.append_page(that_content, that_label)
15 window.add(notebook)
16 window.show_all()
17 gtk.main()
...e voila! Nossas abas fecham. Teste executar o programa agora, e clique em um dos botões de fechar para ver as abas se fechando.
A classe EnhancedNotebook
Para simplificar o trabalho de lidar com as novas abas, que tal criar uma classe herdando de gtk.Notebook? Vamos declarar, nessa classe, o método insert_page_with_close_button() análogo ao método gtk.Notebook.insert_page().
1 import gtk
2 import tablabel
3
4 class EnhancedNotebook(gtk.Notebook):
5 def __init__(self):
6 gtk.Notebook.__init__(self)
7
8 def insert_page_with_close_button(self, child, tab_text, position=-1):
9 # Note that we give the text in a string, not a widget
10 label = tablabel.TabLabel(tab_text, self, child)
11 self.insert_page(child, label, position)
Salvemos essa classe em enhanced.py. Agora, podemos ter uma nova versão do programa:
1 import gtk
2 from enhanced import EnhancedNotebook
3
4 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
5 notebook = EnhancedNotebook()
6 this_content = gtk.Label('Configura\nIsso')
7 notebook.insert_page_with_close_button(this_content, "Isso")
8 that_content = gtk.Label('Configura\nAquilo')
9 notebook.insert_page_with_close_button(that_content, "Aquilo")
10 window.add(notebook)
11 window.show_all()
Bem mais elegante, não? Podem-se fazer outros métodos, também, correspondentes aos gtk.Notebook.append_page(), gtk.Notebook.prepend_page() etc.
Melhorias estéticas
Nossa nova classe está funcionando a contento. Entretanto, quem quer que já tenha utilizado alguma aplicação em GTK+ com abas notará que os botões são bem feinhos. Veja, por exemplo, a diferença entre nossas abas e as abas do [http://www.gnome.org/projects/gedit/ gedit]:
attachment:diferenca-botoes.png
A primeira coisa que podemos fazer para melhorar a aparência é tirar a borda (ou relief) dos botões. Para isso, basta utilizar o método [http://www.pygtk.org/docs/pygtk/class-gtkbutton.html#method-gtkbutton--set-relief gtk.Button.set_relief()] do botão da aba:
Considerando o quanto a construção do botão de fechar está ficando complicada, vamos declarar um método tablabel.TabLabel.get_close_button() só para criá-lo:
1 def __init__(self, label_text, notebook, widgeet):
2 # ...
3 self.button = self.get_close_button()
4 # ...
5
6 def get_close_button(self):
7 button = gtk.Button()
8 image = gtk.image_new_from_stock(gtk.STOCK_CLOSE,
9 gtk.ICON_SIZE_MENU)
10 button.add(image)
11 button.set_relief(gtk.RELIEF_NONE)
12 button.connect('clicked', self.on_button_clicked)
13 return button
A melhoria é evidente...
attachment:diferenca-botoes2.png
...mas os botões ainda continuam grandes demais. Um bom tamanho seria algo como uns oito pixels maior que o ícone está dentro deles. Entretanto, nós não sabemos, nem temos como recuperar as dimensões de um [http://www.pygtk.org/docs/pygtk/class-gtkimage.html gtk.Image] que contenha um ícone de stock. O que sabemos sobre o tal ícone é que o tamanho dele é definido pela constante [http://www.pygtk.org/docs/pygtk/gtk-constants.html#gtk-icon-size-constants gtk.ICON_SIZE_MENU], que não é o tamanho em pixels.
Para recuperar as dimensões de um ícone a partir das constatnes gtk.ICON_SIZE_*, nós utilizamos a função [http://www.pygtk.org/docs/pygtk/class-gtkiconsource.html#function-gtk--icon-size-lookup gtk.icon_size_lookup()]. Essa função espera como argumento uma constante gtk.ICON_SIZE_* e retorna uma tupla contendo a largura e a altura do ícone. Uma vez que tenhamos obtido esses valores, basta requerir que o botão os adote, através do método [http://www.pygtk.org/docs/pygtk/class-gtkwidget.html#method-gtkwidget--set-size-request gtk.Button.set_size_request()]. Assim, podemos fazer um novo método tablabel.TabLabel.get_close_button():
1 def get_close_button(self):
2 button = gtk.Button()
3 # Add icon and remove "visible borders"
4 image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
5 button.add(image)
6 button.set_relief(gtk.RELIEF_NONE)
7 # Change size
8 width, height = gtk.icon_size_lookup(image.get_pixel_size())
9 button.set_size_request(width+4, height+4)
10 # Connect callback
11 button.connect('clicked', self.on_button_clicked)
12 return button
As nossas abas, agora, estão assim:
attachment:diferenca-botoes3.png
É bem verdade que as abas agora são maiores, mas já estão mais bonitas. Infelizmente, reduzir os botões ainda mais acaba truncando os ícones. Se alguém souber como reduzir um pouco mais o botão sem truncar os ícones, me avise
Acrescentando setas de rolagem e lidando com alinhamento de rótulos
Se você está procurando por páginas de gtk.Notebook que possuam botões de fechar, é provável que você vá adicionar e remover páginas arbitrariamente ao notebook. Se adicionarmos muitas páginas, as abas podem forçar a janela a crescer demais.
attachment:grande-demais-menor.png
A solução é habilitar as barras de rolagem de abas no notebook. Isso é bastante simples: basta usar o método [http://www.pygtk.org/docs/pygtk/class-gtknotebook.html#method-gtknotebook--set-scrollable gtk.Notebook.set_scrollable()]. Assim, conseguimos o novo método enhanced.EnhancedNotebook.init() abaixo:
Com essa pequena linha, conseguimos uma melhora considerável:
attachment:nao-grande.png
Últimos retoques
Nosso EnhancedNotebook está quase pronto. Para terminar, vamos chamar a atenção para um detalhe chato que pode passar despercebido.
Veja o screenshot abaixo. Nele, fizemos uma combinação de abas e tamanho da janela específica para deixar claro o ponto: a posição do rótulo e do botão de fechar podem variar muito de acordo com o tamanho da aba. Seria melhor, porém, que o rótulo ficasse sempre numa posição à esquerda, e o botão sempre numa posição à direita.
attachment:abas-distorcidas.png
Pois bem, os deslocamentos ocorrem porque os widgets por padrão tentam dividir o espaço todo.que lhes for disponível, expandindo para ocupá-lo ao máximo. Nos widgets gtk.Label, o texto por padrão tenta ficar no centro do rótulo. Nos widgets gtk.Button, o objeto dentro do botão (no caso, uma imagem) faz o mesmo.
Para cada widget, há uma solução. No caso do rótulo, basta usar o método [http://www.pygtk.org/docs/pygtk/class-gtkmisc.html#method-gtkmisc--set-alignment gtk.Label.set_alignment()], que define o alinhamento do texto.
Esse método espera dois argumentos. O primeiro argumento definirá o alinhamento horizontal do texto do rótulo: se o valor desse primeiro argumento for 0, o texto ficará alinhado à esquerda; se o valor for 1, ficará alinhado à direita; se for 0,5 (o valor padrão para os gtk.Label), ficará centralizadoFootNote(Em verdade, se pode escolher qualquer valor entre zero e um, que o texto será alinhado nessa posição, relativamente ao tamanho do ''widget''. Sugerimos que estude melhor esse método, para que pegue a exata idéia de seu funcionamento.). O segundo argumento define o alinhamento vertical: se seu valor for zero, o texto ficará junto ao topo do gtk.Label; se for 1, ficará colado à base; se for 0,5, o texto ficará exatamente no meio do widget, verticalmente. Nosso interesse é que o texto fique alinhado à esquerda horizontalmente, mas no meio, verticalmente. Faremos isso, então, no método tablabel.TabLabel.init():
1 class TabLabel(gtk.HBox):
2 def __init__(self, label_text, notebook, widget):
3 gtk.HBox.__init__(self)
4 self.notebook = notebook
5 self.widget = widget
6 self.button = self.get_close_button()
7 self.label = gtk.Label(label_text)
8 # Changing label alignment
9 self.label.set_alignment(0, 0.5)
10 self.pack_start(self.label)
11 self.pack_start(self.button)
12 self.show_all()
Quanto ao botão, nos interessa simplesmente que ele não mude de tamanho. O método gtk.Button.set_size_request() não nos serve nesse caso, mas podemos empacotá-lo de modo a que ele não se expanda. Podemos aproveitar e já empacotá-lo ao final do gtk.HBox, de modo que fique sempre à direita. Para isso, basta substituir a linha
1 self.pack_start(self.button)
do tablabel.TabLabel.init() por
1 self.pack_end(self.button, expand=False)
Enfim, para deixar as abas realmente elegantes, ainda sugerimos que se adicionte um pequeno padding no empacotamento (algo como três pixels).
Conclusão
Você veio aqui atrás de adicionar botões de fechar em abas de gtk.Notebook, e encontrou um texto gigante falando de detalhes obscuros do processo de criar esses botões... Pois bem, se você leu até aqui, eis seu prêmio! O código abaixo oferece um notebook cujas páginas podem ser fechadas através do botãozinho de fechar em suas abas. Pode copiar, colar em um arquivo e usar. É o resultado final desse artigo.
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 import gtk
4
5 class TabLabel(gtk.HBox):
6 """
7 Widget used as label for gtk.Notebook page tabs with close buttons.
8 """
9
10 def __init__(self, label_text, notebook, widget):
11 """
12 Creates all widgets needed for labeling and closing the tab.
13
14 label_text
15 A string containg the label text.
16
17 notebook
18 The gtk.Notebook instance which will contain the "closable"
19 pages.
20
21 widget
22 The widget to be added to the gtk.Notebook page.
23 """
24 gtk.HBox.__init__(self)
25 # Received
26 self.notebook = notebook
27 self.widget = widget
28 # Creating
29 self.button = self.get_close_button()
30 self.label = gtk.Label(label_text)
31 self.label.set_alignment(0, 0.5)
32 # Packing
33 self.pack_start(self.label, padding=3)
34 self.pack_end(self.button, expand=False, padding=3)
35 # Showing
36 self.show_all()
37
38 def get_close_button(self):
39 """
40 Returns a button configured to be used as a close button.
41 """
42 button = gtk.Button()
43 # Adding image, removing relief
44 image = gtk.image_new_from_stock(gtk.STOCK_CLOSE,
45 gtk.ICON_SIZE_MENU)
46 button.add(image)
47 button.set_relief(gtk.RELIEF_NONE)
48 # Changing size
49 width, height = gtk.icon_size_lookup(
50 gtk.ICON_SIZE_MENU)
51 button.set_size_request(width+8, height+8)
52 # Connecting callback
53 button.connect('clicked', self.on_button_clicked)
54 return button
55
56 def on_button_clicked(self, button):
57 """
58 Callback method to be connected to the 'clicked' close
59 button signal.
60
61 button
62 Clicked button. Argument required by signal.
63 """
64 position = self.notebook.page_num(self.widget)
65 self.notebook.remove_page(position)
66
67 class EnhancedNotebook(gtk.Notebook):
68 """
69 An enhanced gtk.Notebook class, which provides a method for
70 adding pages
71 which can be closed using a close button in its label.
72 """
73
74 def __init__(self):
75 gtk.Notebook.__init__(self)
76 self.set_scrollable(True)
77
78 def insert_page_with_close_button(self, child, tab_text, position=-1):
79 """
80 Adds a new closabe page.
81
82 child
83 The widget to be added in the page.
84
85 tab_text
86 A string containing the text to be shown in the tab label.
87
88 position
89 The position where the page will be inserted: first,
90 second etc. By
91 default, the tab is inserted at the end of the notebook.
92 You can
93 explicit it giving -1 as this argument.
94 """
95 # Note that we give the text in a string, not a widget
96 label = TabLabel(tab_text, self, child)
97 self.insert_page(child, label, position)
E, para nos despedirmos, um último screenshot, mostrando o resultado final:
attachment:parfait.png