PyGTK教程

肯定来过(rockety), [email protected]

更新:20051221

1. 1. 引言

……

2. 2. 开始

我们以尽可能最简单的程序开始介绍PyGTK。本程序将创建一个 200x200 象素的窗口,这个窗口没办法退出,除非从 shell 中杀掉进程。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# FileName: base.py

import pygtk
pygtk.require( '2.0' )
import gtk

class Base:
    '''第一个pygtk程序'''
    
    def __init__( self ):
        '''初始化'''
        
        self.window = gtk.Window( gtk.WINDOW_TOPLEVEL )
        self.window.show()
        
    def main( self ):
        gtk.main()
        
print __name__

if __name__ == '__main__':
    base = Base()
    base.main()

可以这样运行上面的程序:

python base.py

如果 base.py 被设置为可执行,还可以这样运行:

base.py

在这个例子中,程序第 1 行调用 python 来运行 base.py。第 5-6 行用于区别你系统中可能安装的不同的 PyGTK 版本。 这几行指明我们要使用 PyGTK 的 2.0 版,它包括所有主版本号是 2 的 PyGTK 发行。这样就会阻止程序使用较早的 PyGTK 版本,假如你的系统中恰巧安装的有的话。第 23-25 行检查 name 变量的值是否是 main,它显示该程序是直接运行的还是被导入的。本例程创建了 Base 类的一个新实例并将对它的引用保存在变量 base 中。然后它调用 main() 方法开启 GTK 事件处理循环。

一个类似下图的窗口将会出现。

程序第一行允许 base.py 从 Linux/Unix shell命令行被调用,假定从你的 PATH 可以找到 python。所有例程中的第一行都会是这样。

5-7 行导入 PyGTK2 模块并且初始化 GTK+ 环境。PyGTK 模块定义了在程序中将要使用到的 GTK+ 函数的 python 接口。熟悉 GTK+ 的人知道,这种初始化包括对 gtk_init() 函数的调用。它为我们设置了一些东西,比如默认外观和颜色映射、默认信号处理器,同时检查你从命令行传给程序的参数,看看它们当中是否有下述这些:

  • --gtk-module
  • --g-fatal-warnings
  • --gtk-debug
  • --gtk-no-debug
  • --gdk-debug
  • --gdk-no-debug
  • --display
  • --sync
  • --name
  • --class

有的话会被从参数列表删除,其余它不能识别的,将留给你的应用程序来解析或忽略。这就创建了可以被所有 GTK 程序接受的一组标准参数。

9-19 行定义了一个叫 Base 的 python 类,这个类定义了一个初始化方法 init()。它创建了一个顶级窗口(第15行)并指示 GTK+ 来显示它(第16行)。第15行使用 gtk.WINDOW_TOPLEVEL 参数创建了一个 gtk.Window,这的意思是我们直接让窗口管理器来负责它的装饰及摆放位置。默认创建的没有内容的窗口的大小不是 0x0,而是 200x200,这样你就可以操纵它。

18-19 行定义了 main() 方法,它调用 PyGTK 的 main() 函数,这实际上就是调用 GTK+ 的主事件处理循环来鼠标键盘事件。

23-25 行允许程序直接运行或作为参数传递给 python 解释器执行,这时程序包含在 python 变量 name中的字符串是 main,24-25 行的代码将被执行。否则如果程序是被导入正在运行着的 python 解释器,上述两行将不会被执行。

第 24 行创建了 Base 类的一个实例 base。其实就是创建并显示一个 gtk.window。

第 25 行调用 Base 类的 main() 方法,它开启 GTK+ 事件处理循环。程序运行到这时,GTK+ 会停下来等待 X 事件出现(比如按下按纽或按键),超时,文件 IO 通知等。在我们这个简单的例子中,事件被忽略了。

2.1. 2.1. PyGTK 的 Hello World

现在看一个有器件(按纽)的程序。这是经典的 hello world 的 PyGTK 版本 (helloworld.py)。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# FileName: helloworld.py

import pygtk
pygtk.require( '2.0' )
import gtk

class HelloWorld:
    '''helloworld use pygtk'''
    
    def hello( self, widget, data=None ):
        '''This is a callback function. The data arguments are ignored
        in this example. More on callbacks below.'''
        
        print 'Hello World!'
        
    def delete_event( self, widget, event, data=None ):
        '''If you return FALSE in the 'delete_event' signal
        handler, GTK will emit the 'destroy' signal. Return TRUE
        means you don't want the window to be destroyed.
        This is useful for popping up 'are you sure you want to
        quit?' type dialogs.'''
        
        print 'delete event occurred'
        
        return False
        
    def destroy( self, widget, data=None ):
        '''Another callback'''
        
        gtk.main_quit()
        
    def __init__( self ):
        '''Create a new window'''
        
        self.window = gtk.Window( gtk.WINDOW_TOPLEVEL )
        self.window.connect( 'delete_event', self.delete_event )
        self.window.connect( 'destroy', self.destroy )
        self.window.set_border_width( 10 )
        self.button = gtk.Button( 'Hello World' )
        self.button.connect( 'clicked', self.hello, None )
        self.button.connect_object( 'clicked', gtk.Widget.destroy, \
            self.window )
        self.window.add( self.button )
        self.button.show()
        self.window.show()
        
    def main( self ):
        gtk.main()
        
if __name__ == '__main__':
    hello = HelloWorld()
    hello.main()

下图展示了 helloworld.py 创建的窗口。

PyGTK 模块中定义的变量和函数被命名为 gtk.*。例如 helloworld.py 使用了:

False
gtk.mainquit()
gtk.Window()
gtk.Button()

在接下来的章节里,我可能不会在叙述中指明这个 gtk 前缀。例程中当然还是会写这个前缀的。

2.2. 2.2. 信号和回调原理

GTK+ 是一套事件驱动的工具包,也就是说,当执行 gtk.main() 后,进程会进入等待状态,等待事件的发生,从而触发相应的动作。

当事件发生时,相应的 GTK+ 部件会发出信号(Signals),不同的 Signals 代表不同的事件,从而调用相应的函数。要使一个按钮完成一个动作。我们可以设置一个信号处理器来捕获这个按钮按下的信号,再调用相应的函数完成指定动作。这可通过 GtkWidget 方法来做,如:

handler_id = object.connect(name, func, func_data)

object 是 GtkWidget 的一个实例,它将会发送一个信号。connect 方法把实例与信号关联起来,并返回一个处理句柄,通过它我们可断开或阻塞这个句柄,也就是说使该操作失效。connect 的第一个参数 name 代表你想捕获的信号,第二个参数代表你想调用的函数,第三个参数代表要传递给函数的参数。

第二个参数调用的函数我们叫它“回调函数”。定义方法如下:

def callback_func(widget, callback_data):

第一个参数是指向发出信号的 widget 的指针,第二个参数是一个指向 func_data 的指针,用以接收传递过来的数据。

如果回调函数是对象方法,则可写成:

def callback_method(self, widget, callback_data):

还有一种调用方法是 connect_object(),它和 connect() 类似:

handler_id = object.connect_object(name, func, slot_object)
def callback_func(object)
def callback_method(self,object)

2.3. 2.3. 事件(events)

除有前面描述的信号机制外,还有一套 events 反映 X 事件机制。回调函数可以与这些事件连接。这些事件是:

  • event
  • button_press_event
  • button_release_event
  • scroll_event
  • motion_notify_event
  • delete_event
  • destroy_event
  • expose_event
  • key_press_event
  • key_release_event
  • enter_notify_event
  • leave_notify_event
  • configure_event
  • focus_in_event
  • focus_out_event
  • map_event
  • unmap_event
  • property_notify_event
  • selection_clear_event
  • selection_request_event
  • selection_notify_event
  • proximity_in_event
  • proximity_out_event
  • visibility_notify_event
  • client_event
  • no_expose_event
  • window_state_event

为了连接一个回调函数到这些事件之一,你使用方法 connect(),像前面介绍的一样,用上面事件名之一作为 name 参数。事件的回调函数(方法)与信号的回调函数有一点点不同:

def callback_func( widget, event, callback_data ):

def callback_meth( self, widget, event, callback_data ):

GdkEvent 是一个 python 对象类型,它的类型显示上述事件中的哪个事件发生了。事件的其它属性将依赖于这个事件的类型。类型的可能的值有:

  • NOTHING
  • DELETE
  • DESTROY
  • EXPOSE
  • MOTION_NOTIFY
  • BUTTON_PRESS
  • _2BUTTON_PRESS
  • _3BUTTON_PRESS
  • BUTTON_RELEASE
  • KEY_PRESS
  • KEY_RELEASE
  • ENTER_NOTIFY
  • LEAVE_NOTIFY
  • FOCUS_CHANGE
  • CONFIGURE
  • MAP
  • UNMAP
  • PROPERTY_NOTIFY
  • SELECTION_CLEAR
  • SELECTION_REQUEST
  • SELECTION_NOTIFY
  • PROXIMITY_IN
  • PROXIMITY_OUT
  • DRAG_ENTER
  • DRAG_LEAVE
  • DRAG_MOTION
  • DRAG_STATUS
  • DROP_START
  • DROP_FINISHED
  • CLIENT_EVENT
  • VISIBILITY_NOTIFY
  • NO_EXPOSE
  • SCROLL
  • WINDOW_STATE
  • SETTING

这些值可以使用前缀 gtk.gdk 来访问,例如 gtk.gdk.DRAG_ENTER。

所以,连接一个回调函数到这些事件之一,我们会这样用:

button.connect( 'button_press_event', button_press_callback )

这里假定 button 是一个 GtkButton 构件。现在,当鼠标位于按钮上并按一下鼠标时,函数 button_press_callback 会被调用。这个函数也许应该这样定义:

def button_press_callback( widget, event, data ):

这个函数的返回值指示这个事件是否应该由 GTK+ 事件处理机制做进一步的传播。返回 TRUE 指示这个事件已经处理了,且不应该做进一步传播。返回 FALSE 继续正常的事件处理。详见高级事件和信号处理这一章。

GDK 选中区和拖放的接口函数也发出许多事件,在 GTK+ 中用信号来反映。下列信号的内容详见源构件上的信号和目的构件上的信号这两章:

  • selection_received
  • selection_get
  • drag_begin_event
  • drag_end_event
  • drag_data_delete
  • drag_motion
  • drag_drop
  • drag_data_get
  • drag_data_received

2.4. 2.4. Hello World 分步骤解析

现在我们知道基本理论了,让我们来详细分析 helloworld.py 示例程序。

9-50 行定义了 HelloWorld 类,它包括了所有的对象的回调方法以及对象的初始化方法。先来看回调方法。

12-16 行定义了 hello() 回调方法,它会在点击按纽的时候被调用。调用这个方法,会在控制台打印出 “Hello World”。在这个示例中我们忽略了对象引用、器件以及 data 参数,但大多数回调用使用它们。data 被定义了一个默认值 None,因为如果 connect() 中不包括 data 的话,PyGTK将不传递它。这会引发一个错误,因为调用期望得到三个参数但只收到了两个。定义一个值 None 就能使用两个或三个参数来调用回调函数而不发生错误。本例这种情况,data 参数就可以总是被忽略,因为 hello() 方法总是可以只使用两个参数(不使用 data )来调用。下一个例子将使用 data 参数来告诉我们哪个按纽被按下了。

def hello( self, widget, data = None ):
    print 'Hello World'

接下来的一个回调函数(18-27)有点特殊。“delete_event” 在窗口管理器发送这个事件给应用程序时发生。我们在这里可以选择对这些事件做什么。可以忽略它们,可以做一点响应,或是简单地退出程序。

这个回调函数返回的值让 GTK+ 知道该如何去做。返回 TRUE,让它知道我们不想让 “destroy” 信号被发出,保持程序继续运行。返回 FALSE,我们让 “destroy” 信号发出。这接着会调用 "destroy" 信号处理函数。注意,为了看起来清楚,注释被删除了。

def delete_event( widget, event, data = None ):
    print 'delete event occurred'
    return False

destroy() 回调方法(29-32)通过调用 gtk_main_quit() 来退出程序。这个函数告诉 GTK+ 当控制权返回给它时就从 gtk_main() 退出。

def destroy( widget, data = None ):
    print 'destroy signal occurred'
    gtk.main_quit()

34-47 行定义 HelloWorld 对象引用的初始化方法 __init__(),它创建程序使用的窗口和器件。

34 行建立了一个新窗口,但是这个窗口直到在程序最后我们让 GTK+ 显示它的时候才会显示出来。对这个窗口的引用保存在一个对象引用属性(self.window)中,以便以后访问。

self.window = gtk.Window( gtk.WINDOW_TOPLEVEL )

38-39 行是两个连接一个信号处理器到一个对象 (本例中,就是 window ) 的示例。这里,“delete_event” 和 “destroy” 信号被捕获。当我们用窗口管理器去关闭窗口或调用方法 GtkWidget destroy() 时,“delete_event” 信号发出。当我们在 “delete_event” 信号处理器中返回 FALSE 值时,"destroy" 信号发出。

self.window.connect( 'delete_event', self.delete_event )
self.window.connect( 'destroy', self.destroy )

40 行设置容器对象(本例中,即 window)的一个属性,使它拥有一个10象素的边界区域,里面不放置任何器件。类似的方法还会在设置构件属性这一章看到。

self.window.set_border_width( 10 )

41 行创建创建一个新按钮并将对它的引用存储到 self.button。它显示后上面会有个 “Hello World” 标签。

self.button = gtk.Button( 'Hello World' )

42 行我们给按钮设置信号处理器,这样当它发出 “clicked” 信号时,我们的 hello() 回调方法就会被调用。我们没有传递任何 data 给 hello(),只是简单地传送了 None 作为一个 data。显而易见,当我们用鼠标点击按钮时,信号 "clicked" 被发出。这里的用户数据参数 data 是不必要的,可以被删除。这样回调方法在调用时可以少写一个参数。

self.button.connect( 'clicked', self.hello, None )

我们也要使用这个按钮退出程序。43 行演示了 “destroy” 信号怎样由窗口管理器引发,或由我们的程序引发。当我们按下按钮时,和上面一样,它首先调用 hello() 回调函数,然后是紧挨着它的那个函数,依赖于它们被设置的顺序。你可以拥有许多回调函数,所有的回调按你设置连接的顺序依次执行。

因为 GtkWidget destroy() 方法只接受一个参数( 要被销毁的器件,本例中即 window),我们使用 connect_object() 方法并且将对窗口的引用传递给它。

当 gtk.Widget destroy() 方法被调用时,它将导致窗口发出 “destroy” 信号,这事实上是调用了 HelloWorld destroy() 方法从而结束程序。

self.button.connect_object( 'clicked', gtk.Widget.destroy, self.window )

45 行是一个组装调用,在组装构件这一章将作深入讲解。不过它相当容易理解。它只是告诉 GTK+ 要把按钮放在窗口里,也就是它显示的地方。注意一个 GTK+ 容器只能包含一个构件。还有其它的构件,在后面介绍,设计为用来以各种方法布局多个构件。

self.window.add( self.button )

一切准备就绪。所有信号处理函数连接好了,按钮也放进了窗口,我们让 GTK+ 在屏幕上“显示”这些构件。窗口构件最后显示,这样整个窗口会一下弹出,而不是先见到窗口弹出后再见到按钮。虽然这个简单的示例中,你不会注意到。

self.button.show()
self.window.show()

49-50 行定义 main() 方法,它调用 gtk_main() 函数。

def main( self ):
    gtk.main()

52-54 行允许程序自动运行,如果它直接被调用或作为参数传递给 python 解释器的话。53 行创建了一个 HelloWorld 类的实例,并将对它的引用存储到 hello 变量。54 行调用 HelloWorld 类的 main() 方法开启 GTK+ 事件处理循环。

if __name__ == '__main__':
    hello = HelloWorld()
    hello.main()

现在,当我们用鼠标点击一个 GTK+ 按钮,构件发出一个 “clicked” 信号。为了让我们利用这个信息,程序设置了一个信号处理器来捕获那个信号,它按我们的选择依次调用函数。在我们的示例中,当按下按钮时,以 None 作为参数调用函数 hello(),然后调用该信号的下一个处理函数,该函数调用 destroy() 函数,把窗口构件作为参数传递给它,销毁窗口构件。这导致窗口发出 “destroy” 信号,它被捕获,并且调用我们的 HelloWorld destroy() 方法。

如果用窗口管理器去关闭窗口,它会引发 “delete_event”。这会调用我们的 “delete_event” 处理函数。如果我们在函数中返回 TRUE,窗口还是留在那里,什么事也不发生。返回 FALSE,会使 GTK+ 发出 “destroy” 信号,它当然会调用 “destroy” 回调,退出 GTK。

3. 3. 继续

3.1. 3.1. 深入信号处理器

让我们再来看一下 connect() 调用。

object.connect( name, func, func_data )

从 connect() 调用返回的值是一个整型标签,它标识你的回调函数。前面讲过,每个信号和每个对象可以有多个回调函数,并且它们会按设置的顺序依次运行。

利用这个标识,你可以用下面的函数从列表中删除这个回调:

object.disconnect( id )

这样,通过传递某个信号连接方法返回的标签,你可以中断一个信号处理器的连接。

你也可以用 signal_handler_block() 和 signal_handler_unblock() 这对函数来暂时断开信号处理函数的连接。

object.signal_handler_block( handler_id )
object.signal_handler_unblock( handler_id )

3.2. 3.2. Hello World 升级版

#!/usr/bin/env python
# FileName: helloworld2.py

import pygtk
pygtk.require( '2.0' )
import gtk

class HelloWorld2:
    
    def __init__( self ):
        
        self.window = gtk.Window( gtk.WINDOW_TOPLEVEL )
        self.window.set_title( 'Hello Buttons!' )
        self.window.connect( 'delete_event', self.delete_event )
        self.window.set_border_width( 10 )
        self.box1 = gtk.HBox( False, 0 )
        self.window.add( self.box1 )
        self.button1 = gtk.Button( 'Button 1' )
        self.button1.connect( 'clicked', self.callback, 'button 1' )
        self.box1.pack_start( self.button1, True, True, 0 )
        self.button1.show()
        self.button2 = gtk.Button( 'Button 2' )
        self.button2.connect( 'clicked', self.callback, 'button 2' )
        self.box1.pack_start( self.button2, True, True, 0 )
        self.button2.show()
        self.box1.show()
        self.window.show()
        
    def callback( self, widget, data ):
        print 'Hello again - %s was pressed' % data
        
    def delete_event( self, widget, event, data=None ):
        gtk.main_quit()
        return False
        
def main():
    gtk.main()
        
if __name__ == '__main__':
    hello = HelloWorld2()
    main()

运行 helloworld2.py 会显示一个如图的窗口。

你会注意到,这次不能轻易的退出,你必须使用窗口管理器或命令行来杀掉它。对读者来说,插入一个 “Quit” 按纽来使程序退出是个很好的练习。你也可能想在读下一章时用这个程序测试 pack_start() 的各种选项。试试改变窗口的大小,并观察其行为。

代码中的简短注释与前一个 helloworld 程序稍有不同。

正如你在上面看到的,这个升级版的 helloworld 没有 “destroy” 事件处理器。

29-30 行定义了一个回调方法,它同前一个 helloworld 程序中的 hello() 回调方法类似。不同的是它打印的信息包括传递进来的 data。

13 行设置了显示在窗口标题栏上的标题字符串(见上图)。

16 行创建了一个水平盒子(gtk.HBox)来装载 18 和 22 行创建的两个按纽。17 行将这个水平盒子添加到窗口窗口中。

19 和 23 行将 callback() 方法与按纽的 “clicked” 信号连接起来。每个按纽都设置一个在被调用时传递给 callback() 方法的字符串。

20 和 24 行将按纽放进水平盒子。21 和 25 行让 GTK 来显示这两个按纽。

26-27行则是让 GTK 显示例子及窗口。

4. 4. 组装构件

创建一个应用软件的时候,你可能希望在窗口里放置超过一个以上的构件。我们的第一个 helloworld 示例仅用了一个构件,因此我们能够简单地使用 gtk.Container add() 来“组装”这个构件到窗口中。但当你想要放置更多的构件到一个窗口中时,如何控制各个构件的定位呢?这时就要用到组装(Packing)了。

4.1. 4.1. 组装盒的原理

多数组装是通过创建一些“盒(boxes)”来达成的,这是些不可见的构件容器,它们有两种形式:一种是横向盒(horizontal box),一种是纵向盒(vertical box)。当我们组装构件到横向盒里时,这些构件就依着我们调用的顺序由左至右或从右到左水平地插入进去。在纵向盒里,则从顶部到底部或相反地组装构件,你可以使用任意的盒组合,比如盒套盒或者盒挨着盒,用以产生你想要的效果。

要创建一个新的横向盒我们调用 gtk.HBox(),对于纵向盒,用 gtk.VBox()。pack_start() 和 pack_end() 方法用来将对象组装到这些容器中。pack_start() 将对象从上到下组装到纵向盒中,或者从左到右组装到横向盒中。pack_end() 则相反,从下到上组装到纵向盒中,或者从右到左组装到横向盒中。使用这些函数允许我们调整自己的构件向左或向右对齐,同时也可以混入一些其它的方法来达到我们想要的设计效果。在我们的示例中多数使用 pack_start()。被组装的对象可以是另一个容器或构件。事实上,许多构件本身就是容器,包括按钮,只不过我们通常在按钮中只放入一个标签。

通过使用这些调用,GTK 就会知道要把构件放到哪里去,并且会自动做调整大小及其它美化的事情。至于如何组装你的构件这里还有一些选项。正如你能想到的,在放置和创建构件时,这些方法给了我们很多的弹性。

4.2. 4.2. 盒的细节

由于存在这样的弹性,所以在一开始使用 GTK 中的组装盒(packing box)的时候会有点让人迷惑。这里有许多选项,并且它们不容易一眼看出是如何组合在一起的。然而到最后,这里基本上只有五种不同的风格。下图显示的是使用参数 1 运行 packbox.py 的结果:

每一行包含一个带有若干按钮的横向盒。组装是组装每个按钮到横向盒(hbox)的简写。每个按钮都是以同样的方式组装到横向盒里的(例如,以同样参数调用 pack_start() 方法)。

这是 pack_start() 方法的一个示例。

box.pack_start( child, expand, fill, padding )

/box/ 是你要把对象组装进去的盒,第一个就是该 /child/ 对象。目前这些对象将都是按钮,即我们要将这些按钮组装到盒中。

pack_start() 和 pack_end() 中的 expand 参数是用来控制构件在盒中是充满所有多余空间,这样盒会扩展到充满所有分配给它的空间(TURE);还是盒收缩到仅仅符合构件的大小(FALSE)。设置 expand 为 FALSE 将允许你向左或向右对齐你的构件。否则,它们会在盒中展开,同样的效果只要用 pack_start() 或 pack_end() 之一就能实现。

/fill/ 参数在 pack 方法中控制多余空间是分配给对象本身(TRUE),还是让多余空间围绕在这些对象周围分布(FALSE)。它只有在 expand 参数也为 TRUE 时才会生效。

Python 允许一个方法或函数的默认参数值或参数关键字。本教程中,我会不断的向你展示如何使用合适的默认值或关键字绑定来定义函数和方法。例如这样定义 pack_start() 方法:

box.pack_start( child, expand = True, fill = True, padding = 0 )

box.pack_end( child, expand = True, fill = True, padding = 0 )

/child/、/expand/、/fill/ 和 /padding/ 都是关键字。/expand/、/fill/ 和 /padding/ 参数有默认值。/child/ 参数的值必须被指明。

当创建一个新盒时,函数看起来像下面这样:

hbox = gtk.HBox( homogeneous = False, spacing = 0 )

vbox = gtk.VBox( homogeneous = False, spacing = 0 )

/homogeneous/ 参数对 gtk.HBox() 和 gtk.VBox() 来说,就是控制是否盒里的每个对象具有相同的大小(例如,在横向盒中等宽,或在纵向盒中等高)。若它被设置,pack 常规函数的 expand 参数就被忽略了,它本质上总被开启。

/spacing/(当盒被创建时设置)和 /padding/(当元素被组装时设置)有什么区别呢?Spacing 是加在对象之间,而 papadding 加在对象的每一边。看下图应该会明白一点,这是以参数 2 运行 packbox.py 的结果:

下图显示的是使用 pack_end() 方法(使用参数 3 运行 packbox.py)的结果。标签 “end” 使用 pack_end() 方法摆放。无论窗口怎样缩放,它始终粘附在窗口的右边框。

4.3. 4.3. 组装示范程序

这里是产生上面这些图片的代码,其中做了不少注释,所以我希望你看下去不会有任何问题。自己运行一下玩玩吧。

#!/usr/bin/env python

# example packbox.py

import pygtk
pygtk.require('2.0')
import gtk
import sys, string

# Helper function that makes a new hbox filled with button-labels. Arguments
# for the variables we're interested are passed in to this function.  We do
# not show the box, but do show everything inside.

def make_box(homogeneous, spacing, expand, fill, padding):

    # Create a new hbox with the appropriate homogeneous
    # and spacing settings
    box = gtk.HBox(homogeneous, spacing)

    # Create a series of buttons with the appropriate settings
    button = gtk.Button("box.pack")
    box.pack_start(button, expand, fill, padding)
    button.show()

    button = gtk.Button("(button,")
    box.pack_start(button, expand, fill, padding)
    button.show()

    # Create a button with the label depending on the value of
    # expand.
    if expand == True:
        button = gtk.Button("True,")
    else:
        button = gtk.Button("False,")

    box.pack_start(button, expand, fill, padding)
    button.show()

    # This is the same as the button creation for "expand"
    # above, but uses the shorthand form.
    button = gtk.Button(("False,", "True,")[fill==True])
    box.pack_start(button, expand, fill, padding)
    button.show()

    padstr = "%d)" % padding

    button = gtk.Button(padstr)
    box.pack_start(button, expand, fill, padding)
    button.show()
    return box

class PackBox1:
    def delete_event(self, widget, event, data=None):
        gtk.main_quit()
        return False

    def __init__(self, which):

        # Create our window
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)

        # You should always remember to connect the delete_event signal
        # to the main window. This is very important for proper intuitive
        # behavior
        self.window.connect("delete_event", self.delete_event)
        self.window.set_border_width(10)
    
        # We create a vertical box (vbox) to pack the horizontal boxes into.
        # This allows us to stack the horizontal boxes filled with buttons one
        # on top of the other in this vbox.
        box1 = gtk.VBox(False, 0)
    
        # which example to show. These correspond to the pictures above.
        if which == 1:
            # create a new label.
            label = gtk.Label("HBox(False, 0)")
        
            # Align the label to the left side.  We'll discuss this method
            # and others in the section on Widget Attributes.
            label.set_alignment(0, 0)

            # Pack the label into the vertical box (vbox box1).  Remember that 
            # widgets added to a vbox will be packed one on top of the other in
            # order.
            box1.pack_start(label, False, False, 0)
        
            # Show the label
            label.show()
        
            # Call our make box function - homogeneous = False, spacing = 0,
            # expand = False, fill = False, padding = 0
            box2 = make_box(False, 0, False, False, 0)
            box1.pack_start(box2, False, False, 0)
            box2.show()

            # Call our make box function - homogeneous = False, spacing = 0,
            # expand = True, fill = False, padding = 0
            box2 = make_box(False, 0, True, False, 0)
            box1.pack_start(box2, False, False, 0)
            box2.show()
        
            # Args are: homogeneous, spacing, expand, fill, padding
            box2 = make_box(False, 0, True, True, 0)
            box1.pack_start(box2, False, False, 0)
            box2.show()
        
            # Creates a separator, we'll learn more about these later, 
            # but they are quite simple.
            separator = gtk.HSeparator()
        
            # Pack the separator into the vbox. Remember each of these
            # widgets is being packed into a vbox, so they'll be stacked
            # vertically.
            box1.pack_start(separator, False, True, 5)
            separator.show()
        
            # Create another new label, and show it.
            label = gtk.Label("HBox(True, 0)")
            label.set_alignment(0, 0)
            box1.pack_start(label, False, False, 0)
            label.show()
        
            # Args are: homogeneous, spacing, expand, fill, padding
            box2 = make_box(True, 0, True, False, 0)
            box1.pack_start(box2, False, False, 0)
            box2.show()
        
            # Args are: homogeneous, spacing, expand, fill, padding
            box2 = make_box(True, 0, True, True, 0)
            box1.pack_start(box2, False, False, 0)
            box2.show()
        
            # Another new separator.
            separator = gtk.HSeparator()
            # The last 3 arguments to pack_start are:
            # expand, fill, padding.
            box1.pack_start(separator, False, True, 5)
            separator.show()
        elif which == 2:
            # Create a new label, remember box1 is a vbox as created 
            # near the beginning of __init__()
            label = gtk.Label("HBox(False, 10)")
            label.set_alignment( 0, 0)
            box1.pack_start(label, False, False, 0)
            label.show()
        
            # Args are: homogeneous, spacing, expand, fill, padding
            box2 = make_box(False, 10, True, False, 0)
            box1.pack_start(box2, False, False, 0)
            box2.show()
        
            # Args are: homogeneous, spacing, expand, fill, padding
            box2 = make_box(False, 10, True, True, 0)
            box1.pack_start(box2, False, False, 0)
            box2.show()
        
            separator = gtk.HSeparator()
            # The last 3 arguments to pack_start are:
            # expand, fill, padding.
            box1.pack_start(separator, False, True, 5)
            separator.show()
        
            label = gtk.Label("HBox(False, 0)")
            label.set_alignment(0, 0)
            box1.pack_start(label, False, False, 0)
            label.show()
        
            # Args are: homogeneous, spacing, expand, fill, padding
            box2 = make_box(False, 0, True, False, 10)
            box1.pack_start(box2, False, False, 0)
            box2.show()
        
            # Args are: homogeneous, spacing, expand, fill, padding
            box2 = make_box(False, 0, True, True, 10)
            box1.pack_start(box2, False, False, 0)
            box2.show()
        
            separator = gtk.HSeparator()
            # The last 3 arguments to pack_start are:
            # expand, fill, padding.
            box1.pack_start(separator, False, True, 5)
            separator.show()

        elif which == 3:

            # This demonstrates the ability to use pack_end() to
            # right justify widgets. First, we create a new box as before.
            box2 = make_box(False, 0, False, False, 0)

            # Create the label that will be put at the end.
            label = gtk.Label("end")
            # Pack it using pack_end(), so it is put on the right
            # side of the hbox created in the make_box() call.
            box2.pack_end(label, False, False, 0)
            # Show the label.
            label.show()
        
            # Pack box2 into box1
            box1.pack_start(box2, False, False, 0)
            box2.show()
        
            # A separator for the bottom.
            separator = gtk.HSeparator()
            
            # This explicitly sets the separator to 400 pixels wide by 5
            # pixels high. This is so the hbox we created will also be 400
            # pixels wide, and the "end" label will be separated from the
            # other labels in the hbox. Otherwise, all the widgets in the
            # hbox would be packed as close together as possible.
            separator.set_size_request(400, 5)
            # pack the separator into the vbox (box1) created near the start 
            # of __init__()
            box1.pack_start(separator, False, True, 5)
            separator.show()
    
        # Create another new hbox.. remember we can use as many as we need!
        quitbox = gtk.HBox(False, 0)
    
        # Our quit button.
        button = gtk.Button("Quit")
    
        # Setup the signal to terminate the program when the button is clicked
        button.connect("clicked", lambda w: gtk.main_quit())
        # Pack the button into the quitbox.
        # The last 3 arguments to pack_start are:
        # expand, fill, padding.
        quitbox.pack_start(button, True, False, 0)
        # pack the quitbox into the vbox (box1)
        box1.pack_start(quitbox, False, False, 0)
    
        # Pack the vbox (box1) which now contains all our widgets, into the
        # main window.
        self.window.add(box1)
    
        # And show everything left
        button.show()
        quitbox.show()
    
        box1.show()
        # Showing the window last so everything pops up at once.
        self.window.show()

def main():
    # And of course, our main loop.
    gtk.main()
    # Control returns here when main_quit() is called
    return 0         

if __name__ =="__main__":
    if len(sys.argv) != 2:
        sys.stderr.write("usage: packbox.py num, where num is 1, 2, or 3.\n")
        sys.exit(1)
    PackBox1(string.atoi(sys.argv[1]))
    main()