#LyX 1.6.5 created this file. For more info see http://www.lyx.org/
\lyxformat 345
\begin_document
\begin_header
\textclass book
\begin_preamble
% This file was converted to LaTeX by Writer2LaTeX ver. 0.4b
% see http://www.hj-gym.dk/~hj/writer2latex for more info
\usepackage{multicol}
\usepackage{amsfonts}
\usepackage{textcomp}
\usepackage{color}
\usepackage{calc}
\usepackage{hyperref}
\hypersetup{colorlinks=true, linkcolor=blue, filecolor=blue, pagecolor=blue, urlcolor=blue}
% Text styles
\newcommand{\textstyleStrongEmphasis}[1]{\textbf{#1}}
% List styles
\newcommand{\liststyleLi}{%
\renewcommand\labelitemi{{\textbullet}}
\renewcommand\labelitemii{{\textbullet}}
\renewcommand\labelitemiii{{\textbullet}}
\renewcommand\labelitemiv{{\textbullet}}
}
\newcommand{\liststyleLii}{%
\renewcommand\labelitemi{{\textbullet}}
\renewcommand\labelitemii{{\textbullet}}
\renewcommand\labelitemiii{{\textbullet}}
\renewcommand\labelitemiv{{\textbullet}}
}
\newcommand{\liststyleLiii}{%
\renewcommand\labelitemi{{\textbullet}}
\renewcommand\labelitemii{{\textbullet}}
\renewcommand\labelitemiii{{\textbullet}}
\renewcommand\labelitemiv{{\textbullet}}
}
\newcommand{\liststyleLiv}{%
\renewcommand\labelitemi{${\bullet}$}
\renewcommand\labelitemii{${\circ}$}
\renewcommand\labelitemiii{${\blacksquare}$}
\renewcommand\labelitemiv{${\bullet}$}
}
\newcommand{\liststyleLv}{%
\renewcommand\labelitemi{${\bullet}$}
\renewcommand\labelitemii{${\circ}$}
\renewcommand\labelitemiii{${\blacksquare}$}
\renewcommand\labelitemiv{${\bullet}$}
}
\newcommand{\liststyleLvi}{%
\renewcommand\labelitemi{${\bullet}$}
\renewcommand\labelitemii{${\circ}$}
\renewcommand\labelitemiii{${\blacksquare}$}
\renewcommand\labelitemiv{${\bullet}$}
}
\newcommand{\liststyleLvii}{%
\renewcommand\labelitemi{{\textbullet}}
\renewcommand\labelitemii{{\textbullet}}
\renewcommand\labelitemiii{{\textbullet}}
\renewcommand\labelitemiv{{\textbullet}}
}
% Pages styles (master pages)
\makeatletter
\newcommand{\ps@Standard}{%
\renewcommand\@oddhead{}%
\renewcommand\@evenhead{}%
\renewcommand\@oddfoot{}%
\renewcommand\@evenfoot{}%
\setlength\paperwidth{21.59cm}\setlength\paperheight{27.94cm}\setlength\voffset{-1in}\setlength\hoffset{-1in}\setlength\topmargin{2cm}\setlength\headheight{12pt}\setlength\headsep{0cm}\setlength\footskip{12pt+0cm}\setlength\textheight{27.94cm-2cm-2cm-0cm-12pt-0cm-12pt}\setlength\oddsidemargin{2cm}\setlength\textwidth{21.59cm-2cm-2cm}
\renewcommand\thepage{\arabic{page}}
\setlength{\skip\footins}{0.101cm}\renewcommand\footnoterule{\vspace*{-0.018cm}\noindent\textcolor{black}{\rule{0.25\columnwidth}{0.018cm}}\vspace*{0.101cm}}
}
\makeatother
\end_preamble
\options twoside
\use_default_options false
\language english
\inputencoding utf8
\font_roman default
\font_sans default
\font_typewriter default
\font_default_family default
\font_sc false
\font_osf false
\font_sf_scale 100
\font_tt_scale 100
\graphics default
\paperfontsize 10
\spacing single
\use_hyperref false
\papersize custom
\use_geometry true
\use_amsmath 1
\use_esint 0
\cite_engine basic
\use_bibtopic false
\paperorientation portrait
\branch FirstEdition
\selected 0
\color #faf0e6
\end_branch
\branch SecondEdition
\selected 0
\color #aaffff
\end_branch
\paperwidth 20.95cm
\paperheight 27.31cm
\secnumdepth 3
\tocdepth 3
\paragraph_separation indent
\defskip medskip
\quotes_language english
\papercolumns 1
\papersides 2
\paperpagestyle default
\tracking_changes false
\output_changes false
\author ""
\author ""
\end_header
\begin_body
\begin_layout Standard
\begin_inset Note Note
status collapsed
\begin_layout Plain Layout
This book should have an overall theme, at the End of each chapter, using
selected technologies from the chapter, have a case study where the material
applied to a book long application.
Perhaps a minimized version of ZenPhoto Uploader would do the trick.
\end_layout
\begin_layout Plain Layout
\end_layout
\begin_layout Plain Layout
Take for example:
\end_layout
\begin_layout Plain Layout
.Desktop file: Setup the sample applications menu
\end_layout
\begin_layout Plain Layout
GConfig: Save the apps configuration using gconf
\end_layout
\begin_layout Plain Layout
GStreamer: application would have a little window that shows a video tutorial
\end_layout
\begin_layout Plain Layout
DBus: Allow control of the application using DBus
\end_layout
\begin_layout Plain Layout
Clutter: Animated application logo/ Animated in application slideshow
\end_layout
\begin_layout Plain Layout
Cairo: Use the printing chapter to setup printing
\end_layout
\end_inset
\end_layout
\begin_layout Title
\series bold
\size huge
PyGTK Notebook
\series default
\size default
\series bold
\size large
A Journey Through Python Gnome Technologies
\end_layout
\begin_layout Author
\begin_inset space \hfill{}
\end_inset
Peter Gill
\end_layout
\begin_layout Standard
\begin_inset ERT
status open
\begin_layout Plain Layout
\backslash
newcommand{
\backslash
blankpage}{
\backslash
thispagestyle{empty}
\backslash
quad
\backslash
newpage}
\end_layout
\end_inset
\begin_inset Note Note
status open
\begin_layout Plain Layout
Defined the command
\backslash
blankpage
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset ERT
status open
\begin_layout Plain Layout
{
\backslash
blankpage}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset ERT
status open
\begin_layout Plain Layout
\backslash
thispagestyle{empty}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset space \hfill{}
\end_inset
Version 0.10 April 02, 2010
\end_layout
\begin_layout Standard
\begin_inset ERT
status open
\begin_layout Plain Layout
{
\backslash
blankpage}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset ERT
status open
\begin_layout Plain Layout
\backslash
thispagestyle{empty}
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset Newpage newpage
\end_inset
\end_layout
\begin_layout Standard
\begin_inset CommandInset toc
LatexCommand tableofcontents
\end_inset
\end_layout
\begin_layout Standard
\begin_inset FloatList figure
\end_inset
\end_layout
\begin_layout Section*
ChangeLog
\end_layout
\begin_layout Subsection*
Version 0.03
\end_layout
\begin_layout Date
23 Dec 2008
\end_layout
\begin_layout Itemize
Added Secton on Widgets -
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Widgets - What are they"
\end_inset
\end_layout
\begin_layout Itemize
Added Section on First PyGTK Application -
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Creating your first PyGTK application"
\end_inset
\end_layout
\begin_layout Itemize
Added Section on Layout Boxes -
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Layout - Boxes"
\end_inset
\end_layout
\begin_layout Subsection*
Version 0.04
\end_layout
\begin_layout Itemize
Added Section on Callbacks -
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Callbacks - Reacting to program Events"
\end_inset
\end_layout
\begin_layout Itemize
Added PyGTK on Windows -
\begin_inset CommandInset ref
LatexCommand vref
reference "sec:Appendix PyGTK and Windows"
\end_inset
\end_layout
\begin_layout Subsection*
Version 0.05
\end_layout
\begin_layout Itemize
Added Section on PyGObject (only talks about gobject.timeout_add) -
\begin_inset CommandInset ref
LatexCommand vref
reference "sec:PyGObject"
\end_inset
\end_layout
\begin_layout Itemize
Added Section on Labels -
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Widgets - Labels"
\end_inset
\end_layout
\begin_layout Itemize
Added Section on Check Buttons -
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Widgets - Check Buttons"
\end_inset
\end_layout
\begin_layout Subsection*
Version 0.06
\end_layout
\begin_layout Date
12 Feb 2009
\end_layout
\begin_layout Itemize
Added code example for gstreamer codec installer -
\begin_inset CommandInset ref
LatexCommand vref
reference "sec: Gst - Codec Buddy"
\end_inset
\end_layout
\begin_layout Itemize
Added Section on Buttons -
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Widgets - Buttons"
\end_inset
\end_layout
\begin_layout Itemize
Added Section on Radio Buttons -
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Widgets - RadioButton"
\end_inset
\end_layout
\begin_layout Itemize
Added Section on Toggle Buttons -
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Widgets - Toggle Buttons"
\end_inset
\end_layout
\begin_layout Itemize
Added Section on Text Entry -
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Widgets - Text-Entries"
\end_inset
\end_layout
\begin_layout Subsection*
Version 0.07
\end_layout
\begin_layout Itemize
Added Section on MessageDialog -
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Widgets - MessageDialog"
\end_inset
\end_layout
\begin_layout Itemize
Added Section on Statusbar -
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Widgets - Statusbar"
\end_inset
\end_layout
\begin_layout Subsection*
Version 0.08
\end_layout
\begin_layout Itemize
Updated chapter on clutter to pyclutter 1.0
\end_layout
\begin_layout Subsection*
Version 0.09
\end_layout
\begin_layout Itemize
Book text license change to creative commons Attribution-ShareAlike 3.0 Unported
\end_layout
\begin_layout Subsection*
Version 0.10
\end_layout
\begin_layout Date
02 April 2010
\end_layout
\begin_layout Itemize
Add new chapter on IronPython and Gtk-Sharp
\end_layout
\begin_layout Itemize
Add basic gtk-sharp and IronPython example
\end_layout
\begin_layout Chapter
PyGTK Introduction
\end_layout
\begin_layout Standard
\begin_inset Box Frameless
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "100col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
\begin_inset CommandInset include
LatexCommand input
filename "chapter-heading.lyx"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Section
Introduction
\end_layout
\begin_layout Standard
This book has been created as a personal notebook that I may refer back
to when I no longer remember how to program something I once did.
There has been many a time that I have spent many hours figuring how to
do something interesting just to forget how I did it, or where on the web
it was found.
As I have become tired of doing this I have decided to collect my notes
and code samples in one location that is easy for myself to reference.
Basically I am using open source code to write an open source book.
\end_layout
\begin_layout Standard
Hopefully this information will be useful to others as I have found that
many of these topics are not currently collected together in a nice package
making it easy to use.
\end_layout
\begin_layout Standard
The materials in this book are from several sources from the Internet and
programming books that I have read in the past, or as in the instance of
the case studies, code I have done myself.
\end_layout
\begin_layout Standard
If anything is not cited or referenced properly I now apologize to the original
author and will correct it in the next edition.
\end_layout
\begin_layout Standard
Please check the books website regularly for updates and errata, the web
site is located at:
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://www.majorsilence.com/pygtk_book
\end_layout
\end_inset
\end_layout
\begin_layout Section
PyGTK Basics
\end_layout
\begin_layout Subsection
Widgets - What are they?
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Widgets - What are they"
\end_inset
\end_layout
\begin_layout Standard
Before creating your first program lets get out of the way what a widget
is.
A widget is what makes up a program.
They are all the different parts that can be used and include the following:
\end_layout
\begin_layout Itemize
Labels
\end_layout
\begin_layout Itemize
Buttons
\end_layout
\begin_layout Itemize
Menus
\end_layout
\begin_layout Itemize
Text Entries
\end_layout
\begin_layout Itemize
etc..
\end_layout
\begin_layout Standard
So basically that is what they are.
The buttons, labels, text areas of all programs are widgets.
There are many different types available when using PyGTK and many of them
will be covered in this book, but to start off this chapter will only cover
a few such as buttons, labels and text entry.
\end_layout
\begin_layout Subsection
Creating your first PyGTK application
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Creating your first PyGTK application"
\end_inset
\end_layout
\begin_layout Standard
First thing, create a window that will display a small message.
To do this pygtk and gtk must be imported.
\end_layout
\begin_layout LyX-Code
import pygtk
\end_layout
\begin_layout LyX-Code
pygtk.require("2.0")
\end_layout
\begin_layout LyX-Code
import gtk
\end_layout
\begin_layout Standard
Now create a label and a GTK window.
As you can see below to set the text of a label you just supply the text
when you instantiate it.
To make add it to the window that you have created you use the windows
add method and supply the widget (label).
Then to show everything to the user you call the windows
\emph on
show_all
\emph default
method.
Last but not least you must call the
\emph on
gtk.main()
\emph default
method.
\end_layout
\begin_layout LyX-Code
label = gtk.Label("Hello World!")
\end_layout
\begin_layout LyX-Code
win = gtk.Window()
\end_layout
\begin_layout LyX-Code
win.add(label)
\end_layout
\begin_layout LyX-Code
win.show_all()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Standard
If you do not call the
\emph on
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!main
\end_layout
\end_inset
gtk.main()
\emph default
method, nothing will happen.
It is the main loop that waits for user input and and reactions.
It runs all the code that is necessary to display your application.
\end_layout
\begin_layout Subsection
Layout - Boxes
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Layout - Boxes"
\end_inset
\end_layout
\begin_layout Standard
Adding a label to a window is good and well if not useless.
What you have to do is create a layout using horizontal and vertical boxes.
These boxes can hold PyGTK widgets or other vertical and horiztonal boxes.
You will have one main box that will hold all other boxes, this main box
will be added to the window.
To add a widget to a box, or a box to another box the
\emph on
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!HBox!pack
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
start
\end_layout
\end_inset
pack_start
\emph default
and
\emph on
pack_end
\emph default
methods are used.
\end_layout
\begin_layout Standard
Now lets expand on the first PyGTK application to include a vertical and
horizontal box to layout two labels and a button.
\end_layout
\begin_layout LyX-Code
import pygtk
\end_layout
\begin_layout LyX-Code
pygtk.require("2.0")
\end_layout
\begin_layout LyX-Code
import gtk
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
label_1 = gtk.Label("Hello World!")
\end_layout
\begin_layout LyX-Code
label_2 = gtk.Label("Still in the HBox")
\end_layout
\begin_layout LyX-Code
button = gtk.Button("This button is in the Vertical Box")
\end_layout
\begin_layout LyX-Code
vbox = gtk.VBox()
\end_layout
\begin_layout LyX-Code
hbox = gtk.HBox()
\end_layout
\begin_layout Standard
Start off by creating two labels and a button.
A buttons text is set when creating the same way as a labels is by including
the text when you create an instance of gtk.Button.
Next two layout boxes are created.
\end_layout
\begin_layout Standard
The first box created is a vertical box and the second is a horizontal box.
This boxes have the following definition gtk.HBox(homogeneous=False, spacing=0).
\emph on
Homogeneous
\emph default
is whether each object in the box has the same size.
You can have a vertical box (gtk.VBox) or a horizontal box (gtk.HBox).
This is how in PyGTK a program has its layout.
Take some time and experiment using them.
(I also recommend using Glade 3 (See
\begin_inset CommandInset ref
LatexCommand vref
reference "sec:Glade-3"
\end_inset
) to create your user interfaces instead of doing it by hand).
\end_layout
\begin_layout LyX-Code
hbox.pack_start(label_1)
\end_layout
\begin_layout LyX-Code
hbox.pack_start(label_2)
\end_layout
\begin_layout LyX-Code
# Add the hbox as the first item in the vertical box
\end_layout
\begin_layout LyX-Code
# that was created above vbox.pack_start(hbox)
\end_layout
\begin_layout LyX-Code
# Add the button as the next item in the vertical box.
\end_layout
\begin_layout LyX-Code
vbox.pack_start(button)
\end_layout
\begin_layout Standard
With the layout boxes created the labels and button must be added to them.
So now the pack_start method of the boxes is used.
The definition of these methods is pack_start(child, expand=True, fill=True,
padding=0).
You have the option of using pack_start which adds the widget to the beginning
of the box, or pack_end which appends the widget to the end of the box.
\end_layout
\begin_layout Standard
So this code adds label_1 to the first position of the horizontal box then
adds label_2 to the next position at the beginning after label_1.
Next the horizontal (hbox) is added as the first widget in the vertical
(vbox) box.
Next the button is added to the next position of the vertical box.
When you run this a window should open up with two labels above a button.
\end_layout
\begin_layout Itemize
\emph on
child
\emph default
is the widget you are adding to the box
\end_layout
\begin_layout Itemize
\emph on
expand
\emph default
argument is whether to fill the extra space in the box (gtk.HBox or gtk.VBox)
\end_layout
\begin_layout Itemize
\emph on
fill
\emph default
argument only has an effect if the expand argument is set to True.
\end_layout
\begin_layout Standard
All that is left is to run the program.
So just like in the first program a gtk.Window is created, but instead of
adding a widget such as a label directly to it a layout box is added.
Here the vertical box (vbox) is added as it is the top level box that we
used to hold all other widgets in the code above.
Then call the show_all() method on the window to make all the widgets in
the window visible.
Now to actually run the program the gtk.main() method must be invoked.
\end_layout
\begin_layout LyX-Code
win = gtk.Window()
\end_layout
\begin_layout LyX-Code
win.add(vbox)
\end_layout
\begin_layout LyX-Code
win.show_all()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Standard
Run the program and enjoy your glorious creation.
\end_layout
\begin_layout Subsection
Callbacks - Reacting to program events
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Callbacks - Reacting to program Events"
\end_inset
\end_layout
\begin_layout Standard
A program that does not react to user input is usually a useless program.
To react to user input such as a mouse click there must be assigned to
a widget a signal handler.
A signal handler is connected to a widget such as a gtk.Button and listens
for a signal.
\end_layout
\begin_layout Standard
Take for example, a signal handler could be added to a button that reacts
on a mouse click.
\end_layout
\begin_layout Standard
So lets create a button and add a signal handler:
\end_layout
\begin_layout LyX-Code
button = gtk.Button(
\begin_inset Quotes eld
\end_inset
example button
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
button.connect("clicked", on_button_clicked)
\end_layout
\begin_layout Standard
What this code does is create a button that when
\begin_inset Quotes eld
\end_inset
\emph on
clicked
\emph default
\begin_inset Quotes erd
\end_inset
will call the function
\emph on
on_button_clicked
\emph default
.
In the example below we there is no longer a gtk.HBox, only a vertical gtk.VBox
is used and the button has signal handler to connect
\emph on
clicked
\emph default
signals to the
\emph on
on_button_clicked
\emph default
callback function.
What this means is that when the button is clicked the function named on_button
_clicked will be called.
\end_layout
\begin_layout LyX-Code
import pygtk
\end_layout
\begin_layout LyX-Code
pygtk.require("2.0")
\end_layout
\begin_layout LyX-Code
import gtk
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_button_clicked(widget, data=None):
\end_layout
\begin_layout LyX-Code
label_1.set_text("Hello " + str(data))
\end_layout
\begin_layout LyX-Code
label_1 = gtk.Label("Hello World!")
\end_layout
\begin_layout LyX-Code
label_2 = gtk.Label("Still in the HBox")
\end_layout
\begin_layout LyX-Code
button = gtk.Button("Click Me")
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Connect the "clicked" signal of the button to
\end_layout
\begin_layout LyX-Code
# our callback function that we have named
\end_layout
\begin_layout LyX-Code
# on_button_clicked.
It also passes the string
\end_layout
\begin_layout LyX-Code
# "Anything can go here" to the callback function.
\end_layout
\begin_layout LyX-Code
button.connect("clicked", on_button_clicked, "Anything can go here")
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
vbox = gtk.VBox()
\end_layout
\begin_layout LyX-Code
vbox.pack_start(label_1)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(label_2)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(button)
\end_layout
\begin_layout LyX-Code
win = gtk.Window()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
win.connect("destroy", lambda wid: gtk.main_quit())
\end_layout
\begin_layout LyX-Code
win.add(vbox)
\end_layout
\begin_layout LyX-Code
win.show_all()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Section
Widgets
\end_layout
\begin_layout Standard
Many of the widgets that are going to be discussed here will make use of
a smaller gtk gui that will be shown here.
However there will be a few examples that will utilize an object oriented
design.
Here the basic gui that creates a window and adds a vertical box (gtk.VBox)
to add our test widgets into.
\begin_inset CommandInset label
LatexCommand label
name "sec:Widgets - BaseGui"
\end_inset
\end_layout
\begin_layout LyX-Code
#!/usr/bin/env python
\end_layout
\begin_layout LyX-Code
import pygtk, gtk
\end_layout
\begin_layout LyX-Code
pygtk.require('2.0')
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def main():
\end_layout
\begin_layout LyX-Code
win = gtk.Window(gtk.WINDOW_TOPLEVEL)
\end_layout
\begin_layout LyX-Code
win.connect("delete_event", lambda wid, we: gtk.main_quit())
\end_layout
\begin_layout LyX-Code
vbox = gtk.VBox(True, 2)
\end_layout
\begin_layout LyX-Code
win.add(vbox)
\end_layout
\begin_layout LyX-Code
# Add widget code here
\end_layout
\begin_layout LyX-Code
win.show_all()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
main()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Standard
So when adding the code, from widgets discussed below, make sure it is between
the win.add(vbox) and win.show_all() lines.
All the widget will be added to the widget
\emph on
vbox
\emph default
.
\end_layout
\begin_layout Subsection
Buttons
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Widgets - Buttons"
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!Button
\end_layout
\end_inset
\end_layout
\begin_layout Standard
To create a button the gtk.Button class is instantiated.
\end_layout
\begin_layout LyX-Code
button = gtk.Button(
\begin_inset Quotes eld
\end_inset
Click Me
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
button.connect("clicked", button_callback, "Button Click Me") vbox.pack_start(
button, True, True, 2)
\end_layout
\begin_layout Standard
This code creates a button that displays the text
\begin_inset Quotes eld
\end_inset
Click Me
\begin_inset Quotes erd
\end_inset
on the button.
It than connects the buttons when clicked to the
\emph on
function button_callback
\emph default
and sends the data
\begin_inset Quotes eld
\end_inset
Button Click Me
\begin_inset Quotes erd
\end_inset
as a function argument.
\end_layout
\begin_layout LyX-Code
def button_callback(widget=None, data=None):
\end_layout
\begin_layout LyX-Code
print "%s was clicked." % data
\end_layout
\begin_layout Standard
The function button_callback prints the out a small message that includes
the
\begin_inset Quotes eld
\end_inset
Button Click Me
\begin_inset Quotes erd
\end_inset
string that was sent as an argument.
\end_layout
\begin_layout Subsection
Radio Buttons
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Widgets - RadioButton"
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!RadioButton
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Radio buttons are created using the gtk.RadioButton(group, label) class.
Groups are used so that only one radio button can be selected at a time
within a group.
The label of course being the text that is displayed along with the radio
button.
\end_layout
\begin_layout Standard
To create the first radio button pass the value None in for the group.
Than for each radio button you want in the group pass the first button
in as the group.
The following code will now show this.
\end_layout
\begin_layout LyX-Code
button1 = gtk.RadioButton(None, "Radio Button 1")
\end_layout
\begin_layout LyX-Code
button2 = gtk.RadioButton(button1, label="Radio Button 2")
\end_layout
\begin_layout LyX-Code
button2 = gtk.RadioButton(button1, label="Radio Button 3")
\end_layout
\begin_layout Standard
These three lines show three radio buttons being created with the first
one having a group of None.
The second and third buttons however have the group set to button1.
This way only one of the three buttons can be selected at one time.
\end_layout
\begin_layout Standard
Now the buttons are connected to a callback.
\end_layout
\begin_layout LyX-Code
button1.connect("toggled", button_callback, "Button 1")
\end_layout
\begin_layout LyX-Code
button2.connect("toggled", button_callback, "Button 2")
\end_layout
\begin_layout LyX-Code
button3.connect("toggled", button_callback, "Button 3")
\end_layout
\begin_layout Standard
What this does is connect any toggled (switching from one button to another)
signal to the function
\emph on
button_callback
\emph default
.
\end_layout
\begin_layout LyX-Code
def button_callback(widget=None, data=None):
\end_layout
\begin_layout LyX-Code
print "%s was toggled %s" % (data, ("off","on")[widget.get_active()])
\end_layout
\begin_layout Standard
This fuction will print out the data argument
\begin_inset Quotes eld
\end_inset
on
\begin_inset Quotes erd
\end_inset
when the button is selected and
\begin_inset Quotes eld
\end_inset
off
\begin_inset Quotes erd
\end_inset
when another button is selected.
What this means is that when button1 is currently selected and then button
two is clicked it will print the lines:
\end_layout
\begin_layout LyX-Code
Button 1 was toggled off
\end_layout
\begin_layout LyX-Code
Button 2 was toggled on
\end_layout
\begin_layout Subsection
Toggle Buttons
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Widgets - Toggle Buttons"
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!ToggleButton
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
Toggle Buttons
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Toggle buttons are very much the same as normal buttons except they are
either in a state of
\emph on
on
\emph default
(clicked) or
\emph on
off
\emph default
(not clicked).
They work much the same say that radio and check buttons work.
Toggle buttons are created using the gtk.ToggleButton class and take as
an argument a label.
\end_layout
\begin_layout LyX-Code
button1 = gtk.ToggleButton("Toggle Button 1")
\end_layout
\begin_layout LyX-Code
button2 = gtk.ToggleButton("Toggle Button 2")
\end_layout
\begin_layout Standard
This code shows two toggle buttons being created.
To make them useful they are connected to the
\emph on
toggled
\emph default
signal to call the function
\emph on
button_callback
\emph default
with
\begin_inset Quotes eld
\end_inset
Button 1
\begin_inset Quotes erd
\end_inset
and
\begin_inset Quotes eld
\end_inset
Button 2
\begin_inset Quotes erd
\end_inset
as function arguments.
\end_layout
\begin_layout LyX-Code
button1.connect("toggled", button_callback, "Button 1")
\end_layout
\begin_layout LyX-Code
button2.connect("toggled", button_callback, "Button 2")
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def button_callback(widget=None, data=None):
\end_layout
\begin_layout LyX-Code
print "%s was toggled %s" % (data, ("off",
\end_layout
\begin_layout LyX-Code
"on")[widget.get_active()])
\end_layout
\begin_layout Standard
The button_callback function will print on or off for each button as they
are toggled.
The widget.get_active() method can be used to decide the code path by doing
one action when toggled and another action when it is toggled off.
\end_layout
\begin_layout Standard
All that is left is to add the buttons to the gtk.VBox that is in the user
interface code.
\end_layout
\begin_layout LyX-Code
vbox.pack_start(button1, True, True, 2)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(button2, True, True, 2)
\end_layout
\begin_layout Subsection
Check Buttons
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Widgets - Check Buttons"
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!CheckButton
\end_layout
\end_inset
\end_layout
\begin_layout Standard
To create a check button with a label of
\begin_inset Quotes eld
\end_inset
Check Me
\begin_inset Quotes erd
\end_inset
do the following
\end_layout
\begin_layout LyX-Code
check_button = gtk.CheckButton(
\begin_inset Quotes eld
\end_inset
Check Me
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout Standard
Unlike a normal button, instead of connecting to the
\emph on
clicked
\emph default
signal, a check button connects a callback to a
\emph on
toggled
\emph default
signal.
So to do some action on the above you would connect like so:
\end_layout
\begin_layout LyX-Code
check_button.connect(
\begin_inset Quotes eld
\end_inset
toggled
\begin_inset Quotes erd
\end_inset
, check_button_callback,
\begin_inset Quotes eld
\end_inset
callback data
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout Standard
So this will call the function named
\emph on
check_button_callback
\emph default
whenever the check box is toggled(clicked).
Take a look at the following example to see how to detect whether a check
button is checked or not.
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def check_button_callback(widget, data=None):
\end_layout
\begin_layout LyX-Code
print "%s was toggled: %s" % (data, ("off", "on")[widget.get_active()])
\end_layout
\begin_layout Standard
This function takes the check button widget and print the string data that
was passed in.
It also prints
\begin_inset Quotes eld
\end_inset
off
\begin_inset Quotes erd
\end_inset
for when the button is not clicked and
\begin_inset Quotes eld
\end_inset
on
\begin_inset Quotes erd
\end_inset
when the button has been clicked.
\end_layout
\begin_layout Standard
Below is the code that is needed to create the buttons and connect them
to the
\emph on
check_button_callback
\emph default
function.
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
button1 = gtk.CheckButton("check button 1")
\end_layout
\begin_layout LyX-Code
button1.connect("toggled", check_button_callback, "Button 1")
\end_layout
\begin_layout LyX-Code
vbox.pack_start(button1, True, True, 2)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
button2 = gtk.CheckButton("check button 2")
\end_layout
\begin_layout LyX-Code
button2.connect("toggled", check_button_callback, "Button 2")
\end_layout
\begin_layout LyX-Code
vbox.pack_start(button2, True, True, 2)
\end_layout
\begin_layout Subsection
Labels
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Widgets - Labels"
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!Label
\end_layout
\end_inset
\end_layout
\begin_layout Standard
To create a label just do something like this but replace the labels text
with your own.
\end_layout
\begin_layout LyX-Code
label = gtk.Label(
\begin_inset Quotes eld
\end_inset
Your label
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout Standard
If you wish to change the text later you can use the labels
\emph on
set_text
\emph default
method.
\end_layout
\begin_layout LyX-Code
label.set_text(
\begin_inset Quotes eld
\end_inset
My new label
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout Standard
Now the label will display the text
\begin_inset Quotes eld
\end_inset
My new label
\begin_inset Quotes erd
\end_inset
instead of
\begin_inset Quotes eld
\end_inset
Your label
\begin_inset Quotes erd
\end_inset
.
\end_layout
\begin_layout Subsection
Text Entries
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Widgets - Text-Entries"
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!Text Entry
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!Entry
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
Text Entry
\end_layout
\end_inset
\end_layout
\begin_layout Standard
The text entry example is slightly more complicated than the examples that
have been shown so far.
This is because besides the text entry, two buttons and a label will be
used in this example.
The first button called
\emph on
print_button
\emph default
is used to print retrieve the text from the text entry and place it into
the label.
The second button
\emph on
, clear_button,
\emph default
is used to clear the text from the text entry and label.
\end_layout
\begin_layout Standard
To create a text entry the gtk.Entry class is used.
By default it is gtk.Entry(max=0).
The max argument is the is the size of characters that the entry can hold.
If it is set to 0 then there is no limit.
\end_layout
\begin_layout Standard
The following code creates a gtk.Entry called text_box with no limit on the
size.
\end_layout
\begin_layout LyX-Code
text_box = gtk.Entry()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
print_button = gtk.Button("Print Text")
\end_layout
\begin_layout LyX-Code
print_button.connect("clicked", print_callback, text_box)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
clear_button = gtk.Button("Clear Text")
\end_layout
\begin_layout LyX-Code
clear_button.connect("clicked", clear_callback)
\end_layout
\begin_layout Standard
After creating a text box two buttons are created.
The first,
\emph on
print_button
\emph default
, is connected to the
\emph on
print_callback
\emph default
function when it is clicked and passes as an argument the text_box gtk.Entry
widget as an argument.
\end_layout
\begin_layout Standard
The
\emph on
print_callback
\emph default
funtion receives the gtk.Entry
\emph on
text_box
\emph default
as the argument data and sets the text of the global gtk.Label label to
the text that was entered in the
\emph on
text_box
\emph default
widget using the gtk.Entry method
\emph on
get_text()
\end_layout
\begin_layout LyX-Code
label = gtk.Label(
\begin_inset Quotes eld
\end_inset
Hello
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
def print_callback(widget=None, data=None):
\end_layout
\begin_layout LyX-Code
label.set_text(data.get_text())
\end_layout
\begin_layout Standard
The clear_callback function clears the text in the text entry and just for
fun the label as well.
\end_layout
\begin_layout LyX-Code
def clear_callback(widget=None, text_box=None):
\end_layout
\begin_layout LyX-Code
text_box.set_text(
\begin_inset Quotes eld
\end_inset
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
label.set_text(
\begin_inset Quotes eld
\end_inset
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout Standard
Now the widgets just need to be added to the gtk.VBox that is in the user
interface code.
\end_layout
\begin_layout LyX-Code
vbox.pack_start(label, True, True, 2)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(text_box, True, True, 2)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(print_button, True, True, 2)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(clear_button, True, True, 2)
\end_layout
\begin_layout Standard
Here are some methods available with gtk.Entry:
\end_layout
\begin_layout Itemize
insert_text(text, position=0)
\end_layout
\begin_layout Itemize
get_text()
\end_layout
\begin_layout Itemize
set_text(text)
\end_layout
\begin_layout Itemize
set_max_length(max)
\end_layout
\begin_layout Itemize
set_editable(is_editable) - True or False
\end_layout
\begin_layout Itemize
set_visibility(visible) - True or False
\end_layout
\begin_layout Itemize
select_region(start, end)
\end_layout
\begin_layout Subsection
Menus
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Widgets - Menus"
\end_inset
\begin_inset CommandInset label
LatexCommand label
name "sub:gtk!Menus"
\end_inset
\end_layout
\begin_layout Standard
\begin_inset Note Note
status collapsed
\begin_layout Plain Layout
Edition 2 should cover UIManager http://www.pygtk.org/pygtk2tutorial/sec-UIManager.
html and not just the manual creation of menus using code.
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset Float figure
wide false
sideways false
status collapsed
\begin_layout Plain Layout
\begin_inset Graphics
filename images/pygtk-introduction/menu-screenshot.png
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
File Menu Screenshot
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset CommandInset label
LatexCommand label
name "fig:Menu"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Standard
This section will cover adding menus to applications that most everyone
should be used to.
The standard menus such File -> Save, File -> Quit, and Help -> About.
Of course after reading this section you will be more than capable to add
what ever menu you wish.
\end_layout
\begin_layout Standard
The method used this section will be using is to create the menus using
straight code.
There is another method using the UIManager
\begin_inset Foot
status collapsed
\begin_layout Plain Layout
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://www.pygtk.org/pygtk2tutorial/sec-UIManager.html
\end_layout
\end_inset
\end_layout
\end_inset
and if you would like you can look into that instead.
\end_layout
\begin_layout Standard
There are three main class that are used in creating menus and they are:
\end_layout
\begin_layout Itemize
gtk.MenuBar - Is added to the the programs main window and is a container
for gtk.Menu and gtk.MenuItem
\end_layout
\begin_layout Itemize
gtk.Menu - Is a container to hold sub gtk.MenuItem items
\end_layout
\begin_layout Itemize
gtk.MenuItem - Is the actual menus items the user sees and actually clicks
such as
\begin_inset Quotes eld
\end_inset
File
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
Save
\begin_inset Quotes erd
\end_inset
, and
\begin_inset Quotes eld
\end_inset
Quit
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout Standard
Looking at the code below, it can be seen that the menu bar is created using
the class gtk.MenuBar.
This is the object that will be added to the main windows, in this case
the top of the gtk.VBox that is being used in this example.
\end_layout
\begin_layout LyX-Code
menubar = gtk.MenuBar()
\end_layout
\begin_layout LyX-Code
file_item = gtk.MenuItem("_File")
\end_layout
\begin_layout LyX-Code
help_item = gtk.MenuItem("_Help")
\end_layout
\begin_layout Standard
After the MenuBar is created two MenuItems are created,
\emph on
file_item
\emph default
and
\emph on
help_item
\emph default
, these of course will have other sub menu items attached to them that will
be displayed when they are clicked.
These are the main menu items that are seen in most applications along
the top of the window (Eg.
File, Edit, View, Tools, Help, etc...) In this case only
\emph on
File
\emph default
and
\emph on
Help
\emph default
are shown.
The underscores before the F and H indicate that
\end_layout
\begin_layout Standard
Here find the menu container
\emph on
file_item_sub
\emph default
being created as a gtk.Menu object to hold the menu items that will be apended
to the file_item MenuItem.
Save and quit are both created as gtk.MenuItem objects.
These are then added to file_item_sub.
A few lines further down, file_item_sub will be added to file_item.
\end_layout
\begin_layout LyX-Code
file_item_sub = gtk.Menu()
\end_layout
\begin_layout LyX-Code
save = gtk.MenuItem("_Save")
\end_layout
\begin_layout LyX-Code
quit = gtk.MenuItem("_Quit")
\end_layout
\begin_layout LyX-Code
file_item_sub.append(save)
\end_layout
\begin_layout LyX-Code
file_item_sub.append(quit)
\end_layout
\begin_layout Standard
As was done with creating file_item_sub so to this done here creating help_item_
sub.
This is a submenu container to hold the MenuItems for the Help MenuItem.
\end_layout
\begin_layout LyX-Code
help_item_sub = gtk.Menu()
\end_layout
\begin_layout LyX-Code
about = gtk.MenuItem("_About")
\end_layout
\begin_layout LyX-Code
help_item_sub.append(about)
\end_layout
\begin_layout Standard
Finally here can be seen the submenus being added to their respective parent
MenuItems and then the parent MenItems being added to the MenuBar.
\end_layout
\begin_layout LyX-Code
file_item.set_submenu(file_item_sub)
\end_layout
\begin_layout LyX-Code
help_item.set_submenu(help_item_sub)
\end_layout
\begin_layout LyX-Code
menubar.append(file_item)
\end_layout
\begin_layout LyX-Code
menubar.append(help_item)
\end_layout
\begin_layout Standard
To finish off each menu item that is to have a user action connects to the
activate signal that is emitted on its selection, each MenuItem calling
its respective callback function.
And lets not forget, the menubar is added to the gtk.VBox that was created
in the base user interface code (
\begin_inset CommandInset ref
LatexCommand vref
reference "sec:Widgets - BaseGui"
\end_inset
).
\end_layout
\begin_layout LyX-Code
save.connect("activate", save_callback)
\end_layout
\begin_layout LyX-Code
quit.connect("activate", quit_callback)
\end_layout
\begin_layout LyX-Code
about.connect("activate", about_callback)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(menubar, True, True, 2)
\end_layout
\begin_layout Standard
For the sake of completness these are the callback functions; very simple
and not very much, but you can use your own imagination as what should
be done in your own program.
\end_layout
\begin_layout LyX-Code
def save_callback(widget=None):
\end_layout
\begin_layout LyX-Code
print "Save menu item was pressed"
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def quit_callback(widget=None):
\end_layout
\begin_layout LyX-Code
print "Quit menu item was pressed"
\end_layout
\begin_layout LyX-Code
gtk.main_quit()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def about_callback(widget=None):
\end_layout
\begin_layout LyX-Code
print "About menu item was pressed"
\end_layout
\begin_layout Subsection
Message Dialogs
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Widgets - MessageDialog"
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
MessageDialog
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!MessageDialog
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset Float figure
wide false
sideways false
status collapsed
\begin_layout Plain Layout
\begin_inset Graphics
filename images/pygtk-introduction/MessageDialog-screenshot.png
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
MessageDialog Example
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset CommandInset label
LatexCommand label
name "fig:MessageDialog"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Message Dialogs are small windows that are smiple and easy to use.
Using them is as simple as calling the gtk.MessageDialog class.
The default constructor of this class looks like this.
\end_layout
\begin_layout LyX-Code
gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_INFO,
\end_layout
\begin_layout LyX-Code
buttons=gtk.BUTTONS_NONE, message_format=None)
\end_layout
\begin_layout Standard
The
\emph on
parent
\emph default
is either the parent window of None if none.
\end_layout
\begin_layout Standard
The flags can be one of the following:
\end_layout
\begin_layout Itemize
gtk.DIALOG_MODAL
\end_layout
\begin_layout Itemize
gtk.DIALOG_DESTROY_WITH_PARENT
\end_layout
\begin_layout Itemize
or 0 for no flags.
\end_layout
\begin_layout Standard
The
\emph on
type
\emph default
can be one of the following:
\end_layout
\begin_layout Itemize
gtk.MESSAGE_INFO - display an information icon
\end_layout
\begin_layout Itemize
gtk.MESSAGE_WARNING - display a warning icon
\end_layout
\begin_layout Itemize
gtk.MESSAGE_QUESTION - display a question icon
\end_layout
\begin_layout Itemize
gtk.MESSAGE_ERROR - display an error icon
\end_layout
\begin_layout Standard
The buttons available are:
\end_layout
\begin_layout Itemize
gtk.BUTTONS_NONE
\end_layout
\begin_layout Itemize
gtk.BUTTONS_OK
\end_layout
\begin_layout Itemize
gtk.BUTTONS_CLOSE
\end_layout
\begin_layout Itemize
gtk.BUTTONS_CANCEL
\end_layout
\begin_layout Itemize
gtk.BUTTONS_YES_NO
\end_layout
\begin_layout Itemize
gtk.BUTTONS_OK_CANCEL
\end_layout
\begin_layout Standard
These are the responses to the button types:
\end_layout
\begin_layout Itemize
gtk.RESPONSE_NONE
\end_layout
\begin_layout Itemize
gtk.RESPONSE_REJECT
\end_layout
\begin_layout Itemize
gtk.RESPONSE_ACCEPT
\end_layout
\begin_layout Itemize
gtk.RESPONSE_DELETE_EVENT
\end_layout
\begin_layout Itemize
gtk.RESPONSE_OK
\end_layout
\begin_layout Itemize
gtk.RESPONSE_CANCEL
\end_layout
\begin_layout Itemize
gtk.RESPONSE_CLOSE
\end_layout
\begin_layout Itemize
gtk.RESPONSE_YES
\end_layout
\begin_layout Itemize
gtk.RESPONSE_NO
\end_layout
\begin_layout Itemize
gtk.RESPONSE_APPLY
\end_layout
\begin_layout Itemize
gtk.RESPONSE_HELP
\end_layout
\begin_layout Standard
The
\emph on
message_format
\emph default
is the message that will be displayed.
So far this seems as if it is not complicated and it is not.
\end_layout
\begin_layout Standard
Here is an example showing a MessageDialog displaying a question with buttons
to answer yes or no.
As can be seen the message dialog is instantied with the
\emph on
parent
\emph default
set to None, the
\emph on
button
\emph default
type is gtk.BUTTONS_YES_NO, the
\emph on
flag
\emph default
is gtk.DIALOG_DESTROY_WITH_PARENT.
The
\emph on
type
\emph default
is set to gtk.MESSAGE_QUESTION to go along with the yes/no button.
The message that is displayed is
\begin_inset Quotes eld
\end_inset
Is this a good example?
\begin_inset Quotes erd
\end_inset
.
\end_layout
\begin_layout LyX-Code
def button_callback(widget=None):
\end_layout
\begin_layout LyX-Code
dialog = gtk.MessageDialog(parent = None,
\end_layout
\begin_layout LyX-Code
buttons = gtk.BUTTONS_YES_NO,
\end_layout
\begin_layout LyX-Code
flags =gtk.DIALOG_DESTROY_WITH_PARENT,
\end_layout
\begin_layout LyX-Code
type = gtk.MESSAGE_QUESTION,
\end_layout
\begin_layout LyX-Code
message_format = "Is this a good example?")
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
dialog.set_title("MessageDialog Example")
\end_layout
\begin_layout LyX-Code
result = dialog.run()
\end_layout
\begin_layout LyX-Code
dialog.destroy()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if result == gtk.RESPONSE_YES:
\end_layout
\begin_layout LyX-Code
print "Yes was clicked"
\end_layout
\begin_layout LyX-Code
elif result == gtk.RESPONSE_NO:
\end_layout
\begin_layout LyX-Code
print "No was clicked"
\end_layout
\begin_layout Standard
After the Message dialog is assigned to the variable
\emph on
dialog
\emph default
the title of the dialog window is set to
\begin_inset Quotes eld
\end_inset
MessageDialog Example
\begin_inset Quotes erd
\end_inset
.
To run a dialog you must use the dialogs
\emph on
run
\emph default
method.
The dialogs run method returns the result of the buttons that was clicked.
This can be used to determine the course of action.
\end_layout
\begin_layout Standard
As can be seen in the example if the Yes buttons is clicked the message
\begin_inset Quotes eld
\end_inset
Yes was clicked
\begin_inset Quotes erd
\end_inset
is printed and if No is clicked the message
\begin_inset Quotes eld
\end_inset
No was clicked
\begin_inset Quotes erd
\end_inset
is printed.
Also make sure that you remember to also call the dialogs
\emph on
destroy
\emph default
method otherwise it will never close.
So
\emph on
dialog.destroy()
\emph default
is called on the line immedialty following
\emph on
dialog.run()
\emph default
.
\end_layout
\begin_layout Standard
Finally, lets not forget the code to display the button that will run the
button_callback function:
\end_layout
\begin_layout LyX-Code
button = gtk.Button("Show Dialog")
\end_layout
\begin_layout LyX-Code
button.connect("clicked", button_callback)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(button, True, True, 2)
\end_layout
\begin_layout Standard
As can be seen the message dialog is easy to use and it makes it simple
to display information, warnings, errors, or questions to the user.
\end_layout
\begin_layout Subsection
Spin Buttons
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Widgets - SpinButton"
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
Spin Buttons
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!SpinButton
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset Float figure
wide false
sideways false
status collapsed
\begin_layout Plain Layout
\begin_inset Graphics
filename images/pygtk-introduction/spinbutton-screenshot.png
\end_inset
\begin_inset Caption
\begin_layout Plain Layout
SpinButton Screenshot
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset CommandInset label
LatexCommand label
name "fig:Widgets - SpinButton Screenshot"
\end_inset
\end_layout
\begin_layout Plain Layout
\end_layout
\end_inset
\end_layout
\begin_layout Standard
To create a spin button the gtk.SpinButton class is used.
\end_layout
\begin_layout LyX-Code
spin_button = gtk.SpinButton(adjustment=None, climb_rate=0.0, digits=0)
\end_layout
\begin_layout Standard
The adjustment is as follows:
\end_layout
\begin_layout LyX-Code
adjustment = gtk.Adjustment(value=0, lower=0, upper=0, step_incr=0,
\end_layout
\begin_layout LyX-Code
page_incr=0, page_size=0)
\end_layout
\begin_layout Itemize
value -initial value for the Spin Button
\end_layout
\begin_layout Itemize
lower - lower range value
\end_layout
\begin_layout Itemize
upper - upper range value
\end_layout
\begin_layout Itemize
step_incr - value to increment/decrement when pressing mouse button-1 on
a button
\end_layout
\begin_layout Itemize
step_incr - value to increment/decrement when pressing mouse button-2 on
a button
\end_layout
\begin_layout Itemize
page_size unused
\end_layout
\begin_layout Standard
In this example andjustment is created with an inital value and lower limit
of 0, an upper limit of 100, a step increment of 1, a page increment 5,
and page size of 0)
\end_layout
\begin_layout LyX-Code
#gtk.Adjustment(value=0, lower=0, upper=0, step_incr=0, page_incr=0, page_size=
0)
\end_layout
\begin_layout LyX-Code
adjustment = gtk.Adjustment(0, 0, 100, 1, 5, 0)
\end_layout
\begin_layout LyX-Code
spin = gtk.SpinButton(adjustment, 0, 0)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(spin, True, True, 2)
\end_layout
\begin_layout Standard
Here a button is added that will call the button_callback function.
\end_layout
\begin_layout LyX-Code
button = gtk.Button("Print SpinButton Value")
\end_layout
\begin_layout LyX-Code
button.connect("clicked", button_callback, spin)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(button, True, True, 2)
\end_layout
\begin_layout Standard
The button callback prints the value of the spinbutton, first as a float
and secondly as an integer.
\end_layout
\begin_layout LyX-Code
def button_callback(widget=None, spin=None):
\end_layout
\begin_layout LyX-Code
print spin.get_value()
\end_layout
\begin_layout LyX-Code
print spin.get_value_as_int()
\end_layout
\begin_layout Standard
For much more information and details on the gtk.SpinButton class see the
PyGTK tutorial at:
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://www.pygtk.org
\end_layout
\end_inset
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout Subsection
Combo Box
\end_layout
\begin_layout Standard
The easy way to create and populate a ComboBox is to use one of the following
functions:
\end_layout
\begin_layout LyX-Code
# Setup up a read only combobox
\end_layout
\begin_layout LyX-Code
item_list = gtk.combo_box_new_text()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Setup a combobox that users may add to
\end_layout
\begin_layout LyX-Code
item_list = gtk.combo_box_entry_new_text()
\end_layout
\begin_layout Standard
Using either of these functions setups a combo box and provides some easy
to use convience functions.
\begin_inset Note Note
status collapsed
\begin_layout Plain Layout
Put a link here that links to the pygtk tutorial on combo boxes in a footnote
mentiong for more advanced and indepth coverate to visit the webpage.
\end_layout
\end_inset
These are the methods that are provided when using combo_box_new_text:
\end_layout
\begin_layout Itemize
append_text(text)
\end_layout
\begin_layout Itemize
prepend_text(text)
\end_layout
\begin_layout Itemize
insert_text(position, text)
\end_layout
\begin_layout Itemize
combobox.remove_text(position)
\end_layout
\begin_layout Standard
The example that will be shown below will use the second function, gtk.combo_box_
entry_new_text, because it provides everything that the gtk.combo_box_new_text
does plus allows the user to update the list by typing in new data directly.
If this functionality is not needed then it can be avoided by using the
first function and not using the code below that pertains to adding new
list items.
\end_layout
\begin_layout Standard
Now the ComboBox example will break from using the user interface supplied
at the at the begginning of the widget section (
\begin_inset CommandInset ref
LatexCommand vref
reference "sec:Widgets - BaseGui"
\end_inset
), as it will use a slightly modified version so that it will now be used
within a class.
The example will use the same basic code but will now be within the CodeExample
class that will be created.
The only reason for this is because the author (thats me) does not like
using global variables when it can be avoided.
\end_layout
\begin_layout LyX-Code
class ComboExample:
\end_layout
\begin_layout LyX-Code
def __init__(self):
\end_layout
\begin_layout LyX-Code
win = gtk.Window(gtk.WINDOW_TOPLEVEL)
\end_layout
\begin_layout LyX-Code
win.connect("delete_event", lambda wid, we: gtk.main_quit())
\end_layout
\begin_layout LyX-Code
vbox = gtk.VBox(True, 2)
\end_layout
\begin_layout LyX-Code
win.add(vbox)
\end_layout
\begin_layout Standard
So far the code is the same except instead of the main function the user
interface code is in the __init__ method.
Now the actual code for the comboboxes.
\end_layout
\begin_layout Standard
First the list default_items is created to hold a couple of items that will
be placed in the combobox, the combo box is created right beneath this
using the function combo_box_entry_new_text.
Using this function means that this will combobox will allow its users
to enter text directly into a text entry that is provided in the combobox.
\end_layout
\begin_layout LyX-Code
default_items = ["hello", "World"]
\end_layout
\begin_layout LyX-Code
self.item_list = gtk.combo_box_entry_new_text()
\end_layout
\begin_layout LyX-Code
self.item_list.child.connect('key-press-event',
\end_layout
\begin_layout LyX-Code
self.item_list_changed)
\end_layout
\begin_layout LyX-Code
for x in default_items:
\end_layout
\begin_layout LyX-Code
self.item_list.append_text(x)
\end_layout
\begin_layout Standard
After the combobox has been created and assisigned to the variable self.item_list
it is connects the key-press-event signal to all the item_list_changed
method.
The reason for doing this is to detect when text is entered into the combobox
text entry area by the user.
Following this the default_items list is appended into the combox box using
the append_text method.
Very simple, very easy.
\end_layout
\begin_layout Standard
To show how to retrieve the selected item a button is added that when clicked
will retrieve the combobox item that is selected by calling the print_selected_
item method.
\end_layout
\begin_layout LyX-Code
button = gtk.Button("Print Selected Item")
\end_layout
\begin_layout LyX-Code
button.connect("clicked", self.print_selected_item)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(self.item_list, True, True, 2)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(button, True, True, 2)
\end_layout
\begin_layout LyX-Code
win.show_all()
\end_layout
\begin_layout Standard
The item_list_changed method is called every time there is a changed in
the combobox text entry field.
What this means is everytime a character is entered by a user this method
is called and checks what keyboard button is pressed.
If the keyboard character pressed is Return (Enter) than the text entry
is append to the item_list using the its append_text method and then sets
the combobox text entry back to an empty string.
\end_layout
\begin_layout LyX-Code
def item_list_changed(self, widget=None, event=None):
\end_layout
\begin_layout LyX-Code
key = gtk.gdk.keyval_name(event.keyval)
\end_layout
\begin_layout LyX-Code
if key == "Return":
\end_layout
\begin_layout LyX-Code
self.item_list.append_text(widget.get_text())
\end_layout
\begin_layout LyX-Code
widget.set_text("")
\end_layout
\begin_layout Standard
The print_selected_item method is called when the button is pressed.
Its sole purpose is to retrieve what item is selected in the combox.
If there are no items selected then None is returend.
Else the item is printed and also returned.
\end_layout
\begin_layout LyX-Code
def print_selected_item(self, widget=None):
\end_layout
\begin_layout LyX-Code
model = self.item_list.get_model()
\end_layout
\begin_layout LyX-Code
active = self.item_list.get_active()
\end_layout
\begin_layout LyX-Code
if active < 0:
\end_layout
\begin_layout LyX-Code
return None
\end_layout
\begin_layout LyX-Code
print model[active][0]
\end_layout
\begin_layout LyX-Code
return model[active][0]
\end_layout
\begin_layout Standard
As can be seen to retrieve the selected items the combobox item_list methods
\emph on
get_model
\emph default
and
\emph on
get_active
\emph default
most be used.
The model is a gtk.TreeModel.
If the active number is less than 0 then there are no selected items, otherwise
is the postion of the selected item.
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
ComboExample()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Subsection
Statusbar
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Widgets - Statusbar"
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
Statusbars
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!Statubar
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset Float figure
wide false
sideways false
status collapsed
\begin_layout Plain Layout
\begin_inset Graphics
filename images/pygtk-introduction/statusbar-screenshot.png
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Statusbar Example
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset CommandInset label
LatexCommand label
name "fig:Statusbar Example"
\end_inset
\end_layout
\end_inset
The status bar will break from using the user interface supplied at the
beggining of this widget section, as it will use a slightly modified version
so that it will now be used within a class.
The example will use the same basic code but will now be within the StatusbarTe
st class that will be created.
The only reason for this is because the author (thats me) does not like
using global variables for no particular reason, I just do not like doing
it unless it is a constant variable.
\end_layout
\begin_layout Standard
Now that the user interface is within a class it is easy to work with multiple
widgets by making them class level instance variables.
\end_layout
\begin_layout Standard
When working with the gtk.Statusbar class the important methods to know are:
\end_layout
\begin_layout Itemize
gtk.Statusbar() - Well not really a method but create an instance of the
class
\end_layout
\begin_layout Itemize
pop(context_id) - Remove the top level message
\end_layout
\begin_layout Itemize
push(context_id, text) - Add a new top level message
\end_layout
\begin_layout Itemize
get_context_id(context_id) - Used to retrieve the context that is used with
the
\emph on
pop
\emph default
and
\emph on
push
\emph default
methods
\end_layout
\begin_layout LyX-Code
class StatusbarTest(object):
\end_layout
\begin_layout LyX-Code
def __init__(self):
\end_layout
\begin_layout LyX-Code
win = gtk.Window(gtk.WINDOW_TOPLEVEL)
\end_layout
\begin_layout LyX-Code
win.connect("delete_event", lambda wid, we: gtk.main_quit())
\end_layout
\begin_layout LyX-Code
vbox = gtk.VBox(False, 2)
\end_layout
\begin_layout LyX-Code
win.add(vbox)
\end_layout
\begin_layout Standard
So next is the code for creating the Statusbar.
As can be seen once it is created a context_id variable is assinged by
using the statusbars
\emph on
get_context_id
\emph default
method using the context_id
\begin_inset Quotes eld
\end_inset
Status Test
\begin_inset Quotes erd
\end_inset
.
So whenever a message needs to be popped or pushed it needs to use the
context id that was created with the
\emph on
get_context_id
\emph default
method.
\end_layout
\begin_layout LyX-Code
self.statusbar = gtk.Statusbar()
\end_layout
\begin_layout LyX-Code
self.context_id = self.statusbar.get_context_id("Status Test")
\end_layout
\begin_layout Standard
The rest of the code here is common user interface code that has been common
throught the widget section, all it does is create a text entry and a button.
\end_layout
\begin_layout LyX-Code
self.text_entry = gtk.Entry()
\end_layout
\begin_layout LyX-Code
button = gtk.Button("Click Me")
\end_layout
\begin_layout LyX-Code
button.connect("clicked", self.button_callback)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
vbox.pack_start(self.text_entry, False, True, 2)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(button, True, True, 2)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(self.statusbar, False, True, 2)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
win.show_all()
\end_layout
\begin_layout Standard
Here is the rest of the interesting code.
First thing that is done in the button_callback function is to remove the
top level message using the pop method.
Next the new message is displayed to the statusbar using the push method,
the text is taken from the text entry widget.
To test it out run the code, type something into the text entry and click
the button.
\end_layout
\begin_layout LyX-Code
def button_callback(self, widget=None):
\end_layout
\begin_layout LyX-Code
self.statusbar.pop(self.context_id)
\end_layout
\begin_layout LyX-Code
self.statusbar.push(self.context_id, self.text_entry.get_text())
\end_layout
\begin_layout Standard
The rest of the boring code that is needed to run the example.
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
StatusbarTest()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Standard
See
\begin_inset CommandInset ref
LatexCommand vref
reference "fig:Statusbar Example"
\end_inset
to see what this example looks like.
\end_layout
\begin_layout Chapter
More PyGTK
\end_layout
\begin_layout Standard
\begin_inset Box Frameless
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "100col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
\begin_inset CommandInset include
LatexCommand input
filename "chapter-heading.lyx"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Section
Drag and Drop
\end_layout
\begin_layout Standard
I will not be writing very much about drag and drop
\begin_inset Index
status collapsed
\begin_layout Plain Layout
Drag and Drop
\end_layout
\end_inset
, just enough to be useful in the slide show demonstration program that
this notebook is leading towards.
There are a few things we need to know.
\end_layout
\begin_layout Standard
The only part of drag and drop that we care about for this program is drag_dest_
set(flags, targets, actions).
\end_layout
\begin_layout Standard
flags
\begin_inset Foot
status collapsed
\begin_layout Plain Layout
Take a look at:
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://pygtk.org/pygtk2tutorial/sec-DNDMethods.html
\end_layout
\end_inset
\end_layout
\end_inset
- according to the PyGTK tutorial, flags are:
\end_layout
\begin_layout Itemize
gtk.DEST_DEFAULT_MOTION: If set for a widget, GTK+, during a drag over this
widget will check if the drag matches this widget's list of possible targets
and actions.
GTK+ will then call drag_status() as appropriate.
\end_layout
\begin_layout Itemize
gtk.DEST_DEFAULT_HIGHLIGHT: If set for a widget, GTK+ will draw a highlight
on this widget as long as a drag is over this widget and the widget drag
format and action is acceptable.
\end_layout
\begin_layout Itemize
gtk.DEST_DEFAULT_DROP: If set for a widget, when a drop occurs, GTK+ will
check if the drag matches this widget's list of possible targets and actions.
If so, GTK+ will call drag_get_data() on behalf of the widget.
Whether or not the drop is successful, GTK+ will call drag_finish().
If the action was a move and the drag was successful, then TRUE will be
passed for the delete parameter to drag_finish().
\end_layout
\begin_layout Itemize
gtk.DEST_DEFAULT_ALL: If set, specifies that all default actions should be
taken.
\end_layout
\begin_layout Standard
targets -- is a list of target data types that are supported along with
in app information such as mime types of those files that can be dragged
along with some.
\end_layout
\begin_layout Standard
actions -- are the actions that are to be taken with the drag and include
the following:
\end_layout
\begin_layout Itemize
gtk.gdk.ACTION_DEFAULT
\end_layout
\begin_layout Itemize
gtk.gdk.ACTION_COPY
\end_layout
\begin_layout Itemize
gtk.gdk.ACTION_MOVE
\end_layout
\begin_layout Itemize
gtk.gdk.ACTION_LINK
\end_layout
\begin_layout Itemize
gtk.gdk.ACTION_PRIVATE
\end_layout
\begin_layout Itemize
gtk.gdk.ACTION_ASK
\end_layout
\begin_layout Standard
The only action we will be using is gtk.gdk.ACTION_COPY and this is only on
non win32 systems.
For whatever reason I do not believe anything really works on a Windows
system properly.
I believe this actually because I have never properly been able to get
a target properly specified, thus it never works on Windows so I have never
bothered to go beyond drag_dest_set(0, [], 0).
I see no point.
\end_layout
\begin_layout Standard
With that you can drag a file(s) anywhere into application then bother sorting
out where it goes based on the file type that it is.
I am sure in more complicated applications that this would not be enough
but I have never personally needed more then this.
\end_layout
\begin_layout Standard
\begin_inset Note Note
status open
\begin_layout Plain Layout
See if others have successfully gotten drag and drop targets working properly
on windows with mime-types or whatever is going wrong.
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Now back to targets on anything other then Windows (Linux programs).
\begin_inset space \space{}
\end_inset
For a target we will want to set up its file type.
It will be in the form of (string, int, int).
\end_layout
\begin_layout Standard
So what we will end up with for the target will be something such as (
\begin_inset Quotes eld
\end_inset
text/plain
\begin_inset Quotes erd
\end_inset
, 0, TARGET_STRING).
TARGET_STRING must be an integer assigned above.
It is a number that keeps track of the target throughout the program.
\end_layout
\begin_layout Standard
For flags we will probably just want to go with gtk.DEST_DEFAULT_ALL covering
all the flags leaving us with less typing.
\end_layout
\begin_layout Standard
As I said before we will only use gtk.gdk.ACTION_COPY for the actions part
and this will only be for the part that are running on Linux systems.
\end_layout
\begin_layout Standard
So what we end up with on Linux is a function call that looks like this:
\end_layout
\begin_layout LyX-Code
drag_dest_set(gtk.DEST_DEFAULT_DROP, [(
\begin_inset Quotes eld
\end_inset
text/plain
\begin_inset Quotes erd
\end_inset
, 0,
\end_layout
\begin_layout LyX-Code
TARGET_STRING), (
\begin_inset Quotes eld
\end_inset
image/*
\begin_inset Quotes erd
\end_inset
, 0, TARGET_IMAGE)],
\end_layout
\begin_layout LyX-Code
gtk.gdk.ACTION_COPY)
\end_layout
\begin_layout Standard
While on windows we will only be using a much smaller:
\end_layout
\begin_layout LyX-Code
drag_dest_set(0, [], 0)
\end_layout
\begin_layout Standard
We will need to attach this to a widget.
In our case the widget will be the main window:
\end_layout
\begin_layout LyX-Code
win = gtk.Window()
\end_layout
\begin_layout LyX-Code
win.set_size_request(400, 400)
\end_layout
\begin_layout LyX-Code
if sys.platform ==
\begin_inset Quotes eld
\end_inset
win32
\begin_inset Quotes erd
\end_inset
:
\end_layout
\begin_layout LyX-Code
win.drag_dest_set(0, [], 0)
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
win.drag_dest_set(gtk.DEST_DEFAULT_DROP,
\end_layout
\begin_layout LyX-Code
[(
\begin_inset Quotes eld
\end_inset
text/plain
\begin_inset Quotes erd
\end_inset
, 0, TARGET_STRING),
\end_layout
\begin_layout LyX-Code
(
\begin_inset Quotes eld
\end_inset
image/*, 0, TARGET_IMAGE)],
\end_layout
\begin_layout LyX-Code
gtk.gdk.ACTION_COPY)
\end_layout
\begin_layout Standard
The thing is that using more then the method that is being used for Windows
is not needed for this program and I am only showing the other version
for Linux just to introduce flags, targets, and actions.
\end_layout
\begin_layout Standard
Now that drag_dest_set has been attached to our main window widget we need
to handle three singles:
\end_layout
\begin_layout Itemize
drag_motion
\end_layout
\begin_layout Itemize
drag_drop
\end_layout
\begin_layout Itemize
drag_data_received
\end_layout
\begin_layout Standard
What we do is connect them to three functions like so:
\end_layout
\begin_layout LyX-Code
win.connect(
\begin_inset Quotes eld
\end_inset
drag_motion
\begin_inset Quotes erd
\end_inset
, self.motion_cb)
\end_layout
\begin_layout LyX-Code
win.connect(
\begin_inset Quotes eld
\end_inset
drag_drop
\begin_inset Quotes erd
\end_inset
, self.drop_cb)
\end_layout
\begin_layout LyX-Code
win.connect(
\begin_inset Quotes eld
\end_inset
drag_data_received
\begin_inset Quotes erd
\end_inset
, self.drag_data_received)
\end_layout
\begin_layout Standard
How this works is not very important for our purposes.
We just want it accepting images for us.
If you want more information on how this works check out the PyGTK drag
and drop tutorial at
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://pygtk.org/pygtk2tutorial/ch-DragAndDrop.html
\end_layout
\end_inset
or check out the drag and drop demo included in the PyGTK source code found
at
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://www.pygtk.org
\end_layout
\end_inset
.
\end_layout
\begin_layout Standard
One last thing that I want to mention is that in the function drag_data_received
we will be detecting if the files are in an accepted list of file types.
If they are, in this example we add them to a list.
What we will do in the slide show program is add them to the Item list
in the GUI using a TreeView.
\end_layout
\begin_layout Standard
What you should end up with when everything is said and done is some source
code that is similar to the following.
\end_layout
\begin_layout LyX-Code
import pygtk
\end_layout
\begin_layout LyX-Code
import gtk
\end_layout
\begin_layout LyX-Code
import sys
\end_layout
\begin_layout LyX-Code
import os
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
class DragDropExample:
\end_layout
\begin_layout LyX-Code
def __init__(self):
\end_layout
\begin_layout LyX-Code
TARGET_STRING = 82
\end_layout
\begin_layout LyX-Code
TARGET_IMAGE = 83
\end_layout
\begin_layout LyX-Code
self.file_list=[] # list to hold our images
\end_layout
\begin_layout LyX-Code
self.accepted_types = [
\begin_inset Quotes eld
\end_inset
jpg
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
jpeg
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
png
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
gif
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
bmp
\begin_inset Quotes erd
\end_inset
]
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
win = gtk.Window()
\end_layout
\begin_layout LyX-Code
win.set_size_request(400, 400)
\end_layout
\begin_layout LyX-Code
win.connect(
\begin_inset Quotes eld
\end_inset
delete_event
\begin_inset Quotes erd
\end_inset
, lambda w,e: gtk.main_quit())
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
vbox = gtk.VBox(False, 0)
\end_layout
\begin_layout LyX-Code
hello = gtk.Label(
\begin_inset Quotes eld
\end_inset
Test label to drag images to.
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(hello, True, True, 0)
\end_layout
\begin_layout LyX-Code
win.add(vbox)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if sys.platform==
\begin_inset Quotes erd
\end_inset
win32
\begin_inset Quotes erd
\end_inset
:
\end_layout
\begin_layout LyX-Code
# gtk.DEST_DEFAULT_DROP, does not work on windows
\end_layout
\begin_layout LyX-Code
# because will not match list of possible target
\end_layout
\begin_layout LyX-Code
# matches if you set anything besides a blank []
\end_layout
\begin_layout LyX-Code
# for target on Microsoft windows, it will not call
\end_layout
\begin_layout LyX-Code
# drop_data_received.
So we might as well leave it
\end_layout
\begin_layout LyX-Code
# like so and do your own detecting of the files
\end_layout
\begin_layout LyX-Code
# and what to do with them in drag_data_received.
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
win.drag_dest_set(0, [], 0)
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
win.drag_dest_set(gtk.DEST_DEFAULT_DROP,
\end_layout
\begin_layout LyX-Code
[(
\begin_inset Quotes eld
\end_inset
text/plain
\begin_inset Quotes erd
\end_inset
, 0, TARGET_STRING),
\end_layout
\begin_layout LyX-Code
(
\begin_inset Quotes eld
\end_inset
image/*
\begin_inset Quotes erd
\end_inset
, 0, TARGET_IMAGE)],
\end_layout
\begin_layout LyX-Code
gtk.gdk.ACTION_COPY)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
win.connect(
\begin_inset Quotes eld
\end_inset
drag_motion
\begin_inset Quotes erd
\end_inset
, self.motion_cb)
\end_layout
\begin_layout LyX-Code
win.connect(
\begin_inset Quotes eld
\end_inset
drag_drop
\begin_inset Quotes erd
\end_inset
, self.drop_cb)
\end_layout
\begin_layout LyX-Code
win.connect(
\begin_inset Quotes eld
\end_inset
drag_data_received
\begin_inset Quotes erd
\end_inset
,
\end_layout
\begin_layout LyX-Code
self.drag_data_received)
\end_layout
\begin_layout LyX-Code
win.show_all()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def motion_cb(self, wid, context, x, y, time):
\end_layout
\begin_layout LyX-Code
context.drag_status(gtk.gdk.ACTION_COPY, time)
\end_layout
\begin_layout LyX-Code
return True
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def drop_cb(self, wid, context, x, y, time):
\end_layout
\begin_layout LyX-Code
print
\begin_inset Quotes eld
\end_inset
drop
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout LyX-Code
if context.targets:
\end_layout
\begin_layout LyX-Code
wid.drag_get_data(context, context.targets[0], time)
\end_layout
\begin_layout LyX-Code
print
\begin_inset Quotes eld
\end_inset
\begin_inset Quotes erd
\end_inset
.join([str(t) for t in context.targets])
\end_layout
\begin_layout LyX-Code
return True
\end_layout
\begin_layout LyX-Code
return False
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def drag_data_received(self, img, context, x, y, data, info, time):
\end_layout
\begin_layout LyX-Code
if data.format == 8:
\end_layout
\begin_layout LyX-Code
print
\begin_inset Quotes eld
\end_inset
Received %s
\begin_inset Quotes erd
\end_inset
% data.data
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Checking for valid file types
\end_layout
\begin_layout LyX-Code
test_data = os.path.splitext(data.data)[1][1:4].lower().strip()
\end_layout
\begin_layout LyX-Code
if test_data in self.accepted_types:
\end_layout
\begin_layout LyX-Code
if sys.platform==
\begin_inset Quotes erd
\end_inset
win32
\begin_inset Quotes erd
\end_inset
:
\end_layout
\begin_layout LyX-Code
# Remove the file:/// on window systems.
\end_layout
\begin_layout LyX-Code
self.file_list.append(data.data[8:])
\end_layout
\begin_layout LyX-Code
print data.data[8:]
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
# Remove the file:// on linux systems.
\end_layout
\begin_layout LyX-Code
self.file_list.append(data.data[7:])
\end_layout
\begin_layout LyX-Code
print data.data[7:]
\end_layout
\begin_layout LyX-Code
context.finish(True, False, time)
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
context.finish(False, False, time)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if __name__ ==
\begin_inset Quotes eld
\end_inset
__main__
\begin_inset Quotes erd
\end_inset
:
\end_layout
\begin_layout LyX-Code
DragDropExample()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Section
List Boxes - gtk.TreeView
\end_layout
\begin_layout Standard
A list box
\begin_inset Index
status collapsed
\begin_layout Plain Layout
list box
\end_layout
\end_inset
in PyGTK is a little more difficult then programming one on Windows with
winforms.
With PyGTK you must use a TreeView
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!TreeView
\end_layout
\end_inset
.
A true view is relatively complicated to use for just a list box, but it
is all that is available.
A wrapper can be made around a TreeView to form a generic list box.
But this will not be included in this code.
\end_layout
\begin_layout Standard
A treeview takes the form of gtk.TreeView(model).
The model is the type of the item being stored.
What will be used here is gtk.ListStore(type)
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!ListStore
\end_layout
\end_inset
.
\end_layout
\begin_layout Standard
The type of a ListStore is can be any valid python type (str, int, etc...).
This stores the type data and each type becomes a column in a row.
\end_layout
\begin_layout Standard
With the information we now have we can create the tree like so:
\end_layout
\begin_layout LyX-Code
liststore = gtk.ListStore(str)
\end_layout
\begin_layout LyX-Code
treeview = gtk.TreeView(liststore)
\end_layout
\begin_layout Standard
The above code will create a list box with 1 column.
Also it is possible to set the type of modal of the TreeView after creating
an instance.
\end_layout
\begin_layout LyX-Code
treeview.set_model(liststore)
\end_layout
\begin_layout Standard
Now, to make this useful a CellRenderer
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!CellRenderer
\end_layout
\end_inset
is needed.
I will be using a CellRendererText.
\end_layout
\begin_layout LyX-Code
cell = gtk.CellRendererText()
\end_layout
\begin_layout Standard
The cell is what is used to display the data from the treeview model (liststore)
to the user.
The cell is then added to a gtk.TreeViewColumn
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!TreeViewColumn
\end_layout
\end_inset
like so:
\end_layout
\begin_layout LyX-Code
treeviewcolumn = gtk.TreeViewColumn(
\begin_inset Quotes eld
\end_inset
Button Pushed
\begin_inset Quotes erd
\end_inset
, cell, text=0)
\end_layout
\begin_layout Standard
The above code will create a TreeViewColumn with a column header of
\begin_inset Quotes eld
\end_inset
Button Pressed
\begin_inset Quotes erd
\end_inset
assigned the data from the CellRendererText
\begin_inset Quotes eld
\end_inset
cell
\begin_inset Quotes erd
\end_inset
and display the cells text to column 0.
\end_layout
\begin_layout Standard
With the treeviewcolumn created we go ahead and append it to the treeview
that we created:
\end_layout
\begin_layout LyX-Code
treeview.append_column(treeviewcolumn)
\end_layout
\begin_layout Standard
To append data to a treeview you use the following code:
\end_layout
\begin_layout LyX-Code
model = treeview.get_model()
\end_layout
\begin_layout LyX-Code
model.append([
\begin_inset Quotes eld
\end_inset
Your Message
\begin_inset Quotes erd
\end_inset
])
\end_layout
\begin_layout Standard
To remove a selected row from a TreeView you would use the following code:
\end_layout
\begin_layout LyX-Code
selection = self.treeview.get_selection()
\end_layout
\begin_layout LyX-Code
model, iter = selection.get_selected()
\end_layout
\begin_layout LyX-Code
if iter:
\end_layout
\begin_layout LyX-Code
model.remove(iter)
\end_layout
\begin_layout LyX-Code
return
\end_layout
\begin_layout Standard
If you want more then 1 column you have to create a CellRenderer and TreeViewCol
umn for each and append to the treeview.
You must also have a data type in the ListStore for each column that you
will be using.
Examine the code below to see how this is applied to making a small program
with two columns.
\end_layout
\begin_layout LyX-Code
import pygtk
\end_layout
\begin_layout LyX-Code
pygtk.require(
\begin_inset Quotes eld
\end_inset
2.0
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
import gtk
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
class TreeViewExample:
\end_layout
\begin_layout LyX-Code
def __init__(self):
\end_layout
\begin_layout LyX-Code
# Count the items in the item list
\end_layout
\begin_layout LyX-Code
self.counter = 0
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.win = gtk.Window()
\end_layout
\begin_layout LyX-Code
self.win.set_size_request(400, 400)
\end_layout
\begin_layout LyX-Code
self.win.connect(
\begin_inset Quotes eld
\end_inset
delete_event
\begin_inset Quotes erd
\end_inset
, lambda w,e: gtk.main_quit())
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
vbox = gtk.VBox(False, 0)
\end_layout
\begin_layout LyX-Code
hbox = gtk.HBox(False, 0)
\end_layout
\begin_layout LyX-Code
add_button = gtk.Button(
\begin_inset Quotes eld
\end_inset
Add Item
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
add_button.connect(
\begin_inset Quotes eld
\end_inset
clicked
\begin_inset Quotes erd
\end_inset
, self.add_button_clicked)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
remove_button = gtk.Button(
\begin_inset Quotes eld
\end_inset
Remove Item
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
remove_button.connect(
\begin_inset Quotes eld
\end_inset
clicked
\begin_inset Quotes erd
\end_inset
, self.remove_button_clicked)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Treeview Stuff
\end_layout
\begin_layout LyX-Code
self.liststore = gtk.ListStore(str, str)
\end_layout
\begin_layout LyX-Code
self.treeview = gtk.TreeView(self.liststore)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Add cell and column.
\end_layout
\begin_layout LyX-Code
# data added to treeview.
\end_layout
\begin_layout LyX-Code
self.cell = gtk.CellRendererText()
\end_layout
\begin_layout LyX-Code
self.cell2 = gtk.CellRendererText()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# text=number is the column the text is displayed from
\end_layout
\begin_layout LyX-Code
self.treeviewcolumn = gtk.TreeViewColumn(
\begin_inset Quotes eld
\end_inset
Button Pushed
\begin_inset Quotes erd
\end_inset
,
\end_layout
\begin_layout LyX-Code
self.cell, text=0)
\end_layout
\begin_layout LyX-Code
self.treeviewcolumn2 = gtk.TreeViewColumn(
\end_layout
\begin_layout LyX-Code
\begin_inset Quotes eld
\end_inset
Second Useless Column
\begin_inset Quotes erd
\end_inset
, self.cell2, text=1)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.treeview.append_column(self.treeviewcolumn)
\end_layout
\begin_layout LyX-Code
self.treeview.append_column(self.treeviewcolumn2)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
vbox.pack_start(self.treeview, True, True, 0)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(hbox, False, True, 0)
\end_layout
\begin_layout LyX-Code
hbox.pack_start(add_button, True, True, 0)
\end_layout
\begin_layout LyX-Code
hbox.pack_start(remove_button, True, True, 0)
\end_layout
\begin_layout LyX-Code
self.win.add(vbox)
\end_layout
\begin_layout LyX-Code
self.win.show_all()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def add_button_clicked(self, w):
\end_layout
\begin_layout LyX-Code
self.counter += 1
\end_layout
\begin_layout LyX-Code
model = self.treeview.get_model()
\end_layout
\begin_layout LyX-Code
model.append([
\begin_inset Quotes eld
\end_inset
Add Button Pushed %s times
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout LyX-Code
% self.counter,
\begin_inset Quotes eld
\end_inset
Column 2 Message
\begin_inset Quotes erd
\end_inset
])
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def remove_button_clicked(self, w):
\end_layout
\begin_layout LyX-Code
selection = self.treeview.get_selection()
\end_layout
\begin_layout LyX-Code
model, iter = selection.get_selected()
\end_layout
\begin_layout LyX-Code
if iter:
\end_layout
\begin_layout LyX-Code
model.remove(iter)
\end_layout
\begin_layout LyX-Code
return
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if __name__ ==
\begin_inset Quotes eld
\end_inset
__main__
\begin_inset Quotes erd
\end_inset
:
\end_layout
\begin_layout LyX-Code
TreeViewExample()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Standard
For a much more detailed look at the available options in a TreeView visit:
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://pygtk.org/pygtk2tutorial/ch-TreeViewWidget.html
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
Single Click - Multiple Select
\end_layout
\begin_layout Standard
Say that multiple items in the list need to be selected and by single clicking.
This will be difficult to accomplish quickly wading through the official
documentation
\begin_inset Foot
status collapsed
\begin_layout Plain Layout
Oh do I ever know it.
Talk about wasted hours of my life I am never getting back.
\end_layout
\end_inset
.
Basically a few things need to be added to the above TreeView example.
\end_layout
\begin_layout Standard
First of all the
\emph on
selection
\emph default
that is created in
\emph on
remove_button_clicked
\emph default
needs to be removed as it will now be created in the __init__ method.
Now selection is a class instance variable
\emph on
self.selection
\emph default
, change the code to match.
\end_layout
\begin_layout Standard
So in the __init__ method after
\end_layout
\begin_layout LyX-Code
self.treeview = gtk.TreeView(self.liststore)
\end_layout
\begin_layout Standard
Please add the following two lines of code.
\end_layout
\begin_layout LyX-Code
self.selection = self.treeview.get_selection()
\end_layout
\begin_layout LyX-Code
self.selection.set_mode(gtk.SELECTION_MULTIPLE)
\end_layout
\begin_layout Standard
These two lines create the selection as a class level instance and set it
up to allow multiple selections.
Now to work with this the
\emph on
changed
\emph default
signal is emitting and needs to be connected to.
\end_layout
\begin_layout LyX-Code
self.selection.connect("changed", self.on_media_files_changed)
\end_layout
\begin_layout Standard
The above lines connects the
\emph on
changed
\emph default
signal that is emitted by single clicks on items to call
\emph on
self.on_treeview_changed
\emph default
.
\end_layout
\begin_layout LyX-Code
def on_media_files_changed(self, widget=None, event=None):
\end_layout
\begin_layout LyX-Code
model, path = self.selection.get_selected_rows()
\end_layout
\begin_layout LyX-Code
for x in path:
\end_layout
\begin_layout LyX-Code
print model[x[0]][0] # model[path][column]
\end_layout
\begin_layout Standard
This method does not do much in its current form.
What it does do is retrieve all the selected rows and prints out their
values from column one.
\end_layout
\begin_layout Section
Status Icons
\end_layout
\begin_layout Standard
Status Icons
\begin_inset Index
status collapsed
\begin_layout Plain Layout
Status Icon
\end_layout
\end_inset
can be useful for different reasons.
Personally I like to use them to hide long running applications such as
my music player.
I set it playing then just minimize it to the notification area on my panel.
If I want to to do something with it I left click the status icon and my
music player pops up.
If I want to switch songs I right click on it and it pops up menu with
some options, one of which includes moving to the next song.
\end_layout
\begin_layout Standard
Creating a status icons
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!status icon
\end_layout
\end_inset
is a matter of one line of code to make it display.
\end_layout
\begin_layout LyX-Code
icon = gtk.status_icon_new_from_stock(gtk.STOCK_ABOUT)
\end_layout
\begin_layout Standard
This creates a status icon with an icon set to the stock GTK icon
\begin_inset Foot
status collapsed
\begin_layout Plain Layout
For a full listing of GTK stock icons take a look at the list of stock icons
on page
\begin_inset CommandInset ref
LatexCommand vpageref
reference "sec:Appendix Stock Icons"
\end_inset
or the pygtk website at:
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://www.pygtk.org/docs/pygtk/gtk-stock-items.html
\end_layout
\end_inset
\end_layout
\end_inset
about.
\end_layout
\begin_layout Standard
Then it is a matter of adding two more lines of code to add left and right
click ability to it.
\end_layout
\begin_layout LyX-Code
icon.connect('popup-menu', on_right_click)
\end_layout
\begin_layout LyX-Code
icon.connect('activate', on_left_click)
\end_layout
\begin_layout Standard
The first line here adds signal handling to catch the
\shape italic
popup-menu
\shape default
signal.
This is caught on when a right click happens.
When the popup-menu signal is detected the on_right_click function is called.
\end_layout
\begin_layout Standard
The second line detects the
\shape italic
activate
\shape default
signal when the status icon is left clicked and calls the on_left_click
function.
\end_layout
\begin_layout Standard
As the example below will show, the programmer is responsible for creating
the popup menu.
The Status Icon Example creates a status icon, and then connects to the
\shape italic
popup-menu
\shape default
and
\shape italic
activate
\shape default
signal.
When the popup-menu signal is activated, the on_right_click function creates
and shows a popup menu by calling the make_menu function.
\end_layout
\begin_layout Standard
The make_menu function displays a menu with the options Open App and Close
App.
Clicking on Open App will call the function open_app which will display
a message dialog by calling the function message.
The same thing happens when Close App is clicked.
\end_layout
\begin_layout Standard
Basically this is how a status icon works; just substitute the actions and
functions here for what is needed for your application.
\end_layout
\begin_layout Standard
Status Icon Example
\end_layout
\begin_layout LyX-Code
#!/usr/bin/env python
\end_layout
\begin_layout LyX-Code
import gtk
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def message(data=None):
\end_layout
\begin_layout LyX-Code
"""
\end_layout
\begin_layout LyX-Code
Function to display messages to the user.
\end_layout
\begin_layout LyX-Code
"""
\end_layout
\begin_layout LyX-Code
msg=gtk.MessageDialog
\begin_inset Index
status collapsed
\begin_layout Plain Layout
Message Dialog
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!MessageDialog
\end_layout
\end_inset
(None, gtk.DIALOG_MODAL,
\end_layout
\begin_layout LyX-Code
gtk.MESSAGE_INFO, gtk.BUTTONS_OK, data)
\end_layout
\begin_layout LyX-Code
msg.run()
\end_layout
\begin_layout LyX-Code
msg.destroy()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def open_app(data=None):
\end_layout
\begin_layout LyX-Code
message(data)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def close_app(data=None):
\end_layout
\begin_layout LyX-Code
message(data)
\end_layout
\begin_layout LyX-Code
gtk.main_quit()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def make_menu(event_button, event_time, data=None):
\end_layout
\begin_layout LyX-Code
menu = gtk.Menu()
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!Menu
\end_layout
\end_inset
\end_layout
\begin_layout LyX-Code
open_item = gtk.MenuItem("Open App")
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!MenuItem
\end_layout
\end_inset
\end_layout
\begin_layout LyX-Code
close_item = gtk.MenuItem("Close App")
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
#Append the menu items
\end_layout
\begin_layout LyX-Code
menu.append(open_item)
\end_layout
\begin_layout LyX-Code
menu.append(close_item)
\end_layout
\begin_layout LyX-Code
#add callbacks
\end_layout
\begin_layout LyX-Code
open_item.connect_object("activate", open_app, "Open App")
\end_layout
\begin_layout LyX-Code
close_item.connect_object("activate", close_app, "Close App")
\end_layout
\begin_layout LyX-Code
#Show the menu items
\end_layout
\begin_layout LyX-Code
open_item.show()
\end_layout
\begin_layout LyX-Code
close_item.show()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
#Popup the menu
\end_layout
\begin_layout LyX-Code
menu.popup(None, None, None, event_button, event_time)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_right_click(data, event_button, event_time):
\end_layout
\begin_layout LyX-Code
make_menu(event_button, event_time)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_left_click(event):
\end_layout
\begin_layout LyX-Code
message("Status Icon Left Clicked")
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if __name__ == '__main__':
\end_layout
\begin_layout LyX-Code
icon = gtk.status_icon_new_from_stock(gtk.STOCK_ABOUT)
\end_layout
\begin_layout LyX-Code
icon.connect('popup-menu', on_right_click)
\end_layout
\begin_layout LyX-Code
icon.connect('activate', on_left_click)
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout Section
File choosers
\end_layout
\begin_layout Standard
File choosers
\begin_inset Index
status collapsed
\begin_layout Plain Layout
File Chooser
\end_layout
\end_inset
are used to select files to open or to display a save dialog to the user.
This section will cover the gtk.FileChooserDialog, gtk.FileChooserButton,
and will also cover using native Windows file choosers when on Windows.
\end_layout
\begin_layout Subsection
gtk.FileChooserDialog
\end_layout
\begin_layout Standard
The
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!FileChooserDialog
\end_layout
\end_inset
FileChooserDialog
\begin_inset Index
status collapsed
\begin_layout Plain Layout
File Chooser!FileChooserDialog
\end_layout
\end_inset
class provides an easy to use way to display a file chooser or save dialog
to end users.
It is created with a few options and then is run returning succuss or failure.
To start off here is a GUI with two buttons and a file filter declard that
will be used to launch the file chooser and save dialog.
\begin_inset Index
status collapsed
\begin_layout Plain Layout
File Filter
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
File Chooser!FileFilter
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!FileFilter
\end_layout
\end_inset
\end_layout
\begin_layout LyX-Code
def main():
\end_layout
\begin_layout LyX-Code
#file filters used with the filechoosers
\end_layout
\begin_layout LyX-Code
text_filter=gtk.FileFilter()
\end_layout
\begin_layout LyX-Code
text_filter.set_name("Text files")
\end_layout
\begin_layout LyX-Code
text_filter.add_mime_type("text/*")
\end_layout
\begin_layout LyX-Code
all_filter=gtk.FileFilter()
\end_layout
\begin_layout LyX-Code
all_filter.set_name("All files")
\end_layout
\begin_layout LyX-Code
all_filter.add_pattern("*")
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
\end_layout
\begin_layout LyX-Code
window.set_title("Filechooser Example")
\end_layout
\begin_layout LyX-Code
window.connect("destroy", lambda wid: gtk.main_quit())
\end_layout
\begin_layout LyX-Code
window.connect("delete_event", lambda e1,e2:gtk.main_quit())
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
button_save = gtk.Button("Save File")
\end_layout
\begin_layout LyX-Code
button_open = gtk.Button("Open File")
\end_layout
\begin_layout LyX-Code
button_save.connect("clicked", on_save_clicked, text_filter, all_filter)
\end_layout
\begin_layout LyX-Code
button_open.connect("clicked", on_open_clicked, text_filter, all_filter)
\end_layout
\begin_layout LyX-Code
hbox = gtk.HBox(True, 0) hbox.pack_start(button_save, True, True, 5)
\end_layout
\begin_layout LyX-Code
hbox.pack_start(button_open, True, True, 5)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
window.add(hbox) window.show_all()
\end_layout
\begin_layout Standard
As can be seen in the code above, the first thing that is done is to seta
gtk.FileFilter.
One filter for text files and one filter that will be for all file types.
The text that is displayed with a file filter is created with the method
set_name and the pattern is set using the set_pattern method.
For every pattern that is to be matched against there needs to be an instance
of the gtk.FileFilter.
\end_layout
\begin_layout Standard
Then the GTK window is created.
After this two buttons are created; the button_save and button_open buttons.
When these buttons are clicked they pass the filters that were created
at the top of the function to their respective callback functions.
\end_layout
\begin_layout Standard
Now to focus on on the details of filechooser dialogs
\begin_inset Index
status collapsed
\begin_layout Plain Layout
File Chooser!FileChooserDialog
\end_layout
\end_inset
.
First is the save dialog.
\end_layout
\begin_layout LyX-Code
def on_save_clicked(widget, text_filter=None, all_filter=None):
\end_layout
\begin_layout LyX-Code
filename=None
\end_layout
\begin_layout LyX-Code
dialog=gtk.FileChooserDialog(title="Select a File",
\end_layout
\begin_layout LyX-Code
action=gtk.FILE_CHOOSER_ACTION_SAVE,
\end_layout
\begin_layout LyX-Code
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE,
\end_layout
\begin_layout LyX-Code
gtk.RESPONSE_OK))
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if (text_filter != None) and (all_filter != None):
\end_layout
\begin_layout LyX-Code
dialog.add_filter(text_filter)
\end_layout
\begin_layout LyX-Code
dialog.add_filter(all_filter)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
response = dialog.run()
\end_layout
\begin_layout LyX-Code
if response == gtk.RESPONSE_OK:
\end_layout
\begin_layout LyX-Code
filename = dialog.get_filename()
\end_layout
\begin_layout LyX-Code
elif response == gtk.RESPONSE_CANCEL:
\end_layout
\begin_layout LyX-Code
print 'Cancel Clicked' dialog.destroy()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if filename != None:
\end_layout
\begin_layout LyX-Code
save_file=open(filename, 'w')
\end_layout
\begin_layout LyX-Code
save_file.write("Sample Data")
\end_layout
\begin_layout LyX-Code
save_file.close()
\end_layout
\begin_layout LyX-Code
print filename
\end_layout
\begin_layout Standard
The on_save_clicked function starts off by setting the filename to None
and quickly sets up the dialog.
The dialog title is set to
\begin_inset Quotes eld
\end_inset
Select a File
\begin_inset Quotes erd
\end_inset
.
The action type of the dialog is set to save using gtk.FILE_CHOOSER_ACTION_SAVE
\begin_inset Index
status collapsed
\begin_layout Plain Layout
File Chooser!FILE
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
CHOOSER
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
ACTION
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
SAVE
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!FILE
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
CHOOSER
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
ACTION
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
SAVE
\end_layout
\end_inset
.
The buttons are set with a tuple.
The button uses the stock cancel using the gtk.RESPONSE_CANCEL
\begin_inset Index
status collapsed
\begin_layout Plain Layout
File Chooser!RESPONSE
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
CANCEL
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!RESPONSE
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
CANCEL
\end_layout
\end_inset
and the stock save button that uses the gtk.RESPONSE_OK
\begin_inset Index
status collapsed
\begin_layout Plain Layout
File Chooser!RESPONSE
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
OK
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!RESPONSE
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
OK
\end_layout
\end_inset
when it is clicked.
\end_layout
\begin_layout Standard
After this the function checks to see if there are any filters that should
be applied and if so it applies them.
\end_layout
\begin_layout Standard
After the filters are added, the dialog is run with its return value assigned
to the variable response.
\end_layout
\begin_layout LyX-Code
response = dialog.run()
\end_layout
\begin_layout Standard
It then checks the value of response to be of gtk.RESPONSE_OK and if so assigns
the name of the file to the variable filename using:
\end_layout
\begin_layout LyX-Code
filename = dialog.get_filename()
\end_layout
\begin_layout Standard
If the response is set to gtk.RESPONSE_CANCEL, no actions are taken.
\end_layout
\begin_layout Standard
The last action to take with the dialog is to call the destroy method.
If the destroy method is not called the dialog will stay on the screen.
\end_layout
\begin_layout LyX-Code
dialog.destroy()
\end_layout
\begin_layout Standard
The final part of the on_save_clicked function is to save the string
\begin_inset Quotes eld
\end_inset
Sample Data
\begin_inset Quotes erd
\end_inset
to the file that was specified to save to.
\end_layout
\begin_layout Standard
The on_open_clicked function is very similar to the on_save_clicked function.
Instead of opening a dialog to save a file it opens a dialog to select
a file for the application to load.
\end_layout
\begin_layout LyX-Code
def on_open_clicked(widget, text_filter=None, all_filter=None):
\end_layout
\begin_layout LyX-Code
filename=None
\end_layout
\begin_layout LyX-Code
dialog=gtk.FileChooserDialog(title="Select a File",
\end_layout
\begin_layout LyX-Code
action=gtk.FILE_CHOOSER_ACTION_OPEN,
\end_layout
\begin_layout LyX-Code
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
\end_layout
\begin_layout LyX-Code
gtk.STOCK_OPEN, gtk.RESPONSE_OK))
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if (text_filter != None) and (all_filter != None):
\end_layout
\begin_layout LyX-Code
dialog.add_filter(text_filter)
\end_layout
\begin_layout LyX-Code
dialog.add_filter(all_filter)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
response = dialog.run()
\end_layout
\begin_layout LyX-Code
if response == gtk.RESPONSE_OK:
\end_layout
\begin_layout LyX-Code
filename = dialog.get_filename()
\end_layout
\begin_layout LyX-Code
elif response == gtk.RESPONSE_CANCEL:
\end_layout
\begin_layout LyX-Code
print 'Cancel Clicked'
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
dialog.destroy()
\end_layout
\begin_layout LyX-Code
print "File Choosen: ", filename
\end_layout
\begin_layout Standard
Just like in the on_save_clicked function the on_open_clicked starts off
by setting the filename to None.
Then it sets up the open dialog using the gtk.FileChooserDialog.
It sets the dialog title to
\begin_inset Quotes eld
\end_inset
Select a File
\begin_inset Quotes erd
\end_inset
, the action to open with gtk.FILE_CHOOSER_ACTION_OPEN.
The buttons for the dialog are set as a tuple with the button type and
button response next to each other.
It sets a cancel button with gtk.STOCK_CANCEL with a response of gtk.RESPONSE
and open button with gtk.STOCK_OPEN with a response of gtk.RESPONSE_OK.
\end_layout
\begin_layout Standard
After it checks to see if there are filters set and if so adds filters to
the dialog using the add_filter method.
\end_layout
\begin_layout Standard
The dialog is run using the run method and assigns the response to the variable
response like so:
\end_layout
\begin_layout LyX-Code
response = dialog.run()
\end_layout
\begin_layout Standard
The on_open_clicked function then checks the value of the response variable.
If the response is gtk.RESPONSE_OK the file name is set by using the dialogs
get_filename() method.
\end_layout
\begin_layout LyX-Code
filename = dialog.get_filename()
\end_layout
\begin_layout Standard
If the response is gtk.RESPONSE_CANCEL no action is taken.
The very last action that is taken is to call the dialogs destroy method.
\end_layout
\begin_layout LyX-Code
dialog.destroy()
\end_layout
\begin_layout Standard
If the destroy method is not called the dialog will stay on screen.
\end_layout
\begin_layout Subsection
gtk.FileChooserButton
\end_layout
\begin_layout Standard
The gtk.FileChooserButton
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!FileChooserButton
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
File Chooser!FileChooserButton
\end_layout
\end_inset
eases the use of a open file dialog by taking care of the run and destroy
code and also provides a button.
This is easier than the previous section on the FileChooserDialog.
\end_layout
\begin_layout Standard
File Chooser Button
\end_layout
\begin_layout LyX-Code
def main():
\end_layout
\begin_layout LyX-Code
#file filters used with the filechoosers
\end_layout
\begin_layout LyX-Code
text_filter=gtk.FileFilter()
\end_layout
\begin_layout LyX-Code
text_filter.set_name("Text files")
\end_layout
\begin_layout LyX-Code
text_filter.add_mime_type("text/*")
\end_layout
\begin_layout LyX-Code
all_filter=gtk.FileFilter()
\end_layout
\begin_layout LyX-Code
all_filter.set_name("All files")
\end_layout
\begin_layout LyX-Code
all_filter.add_pattern("*")
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
\end_layout
\begin_layout LyX-Code
window.set_title("Native Filechooser")
\end_layout
\begin_layout LyX-Code
window.connect("destroy", lambda wid: gtk.main_quit())
\end_layout
\begin_layout LyX-Code
window.connect("delete_event", lambda e1,e2:gtk.main_quit())
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
button_open = gtk.FileChooserButton("Open File")
\end_layout
\begin_layout LyX-Code
button_open.add_filter(text_filter)
\end_layout
\begin_layout LyX-Code
button_open.add_filter(all_filter)
\end_layout
\begin_layout LyX-Code
button_open.connect("selection-changed", on_file_selected)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
window.add(button_open)
\end_layout
\begin_layout LyX-Code
window.show_all()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_file_selected(widget):
\end_layout
\begin_layout LyX-Code
filename = widget.get_filename()
\end_layout
\begin_layout LyX-Code
print "File Choosen: ", filename
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
main()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Standard
This example starts by creating two filter types using the gtk.FileFilter
class.
One filter for text files and one filter for any type of file.
Skip a few lines and a FileChooserButton is created like this:
\end_layout
\begin_layout LyX-Code
button_open = gtk.FileChooserButton(
\begin_inset Quotes eld
\end_inset
Open File
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout Standard
To retrieve the selected file from a FileChooserButton it must connect the
\shape italic
selection-changed
\shape default
\begin_inset Index
status collapsed
\begin_layout Plain Layout
signals!selection-changed
\end_layout
\end_inset
signal to a function.
So this example connects the selection-changed signal to the on_file_selected
function.
The on_file_selected function retrieves the filename that was choosen and
then prints it.
\end_layout
\begin_layout Subsection
Windows File Chooser
\end_layout
\begin_layout Standard
The native GTK filechoosers are generally ok, but they are very ugly if
the GTK application is running on Windows.
For PyGTK apps that are running on Windows the option exists to use a native
Windows file chooser dialog.
The following example will show how to open a file and to save a file.
This example will require that the pywin32 package be installed
\begin_inset Foot
status collapsed
\begin_layout Plain Layout
See section
\begin_inset CommandInset ref
LatexCommand vref
reference "sec:Appendix PyGTK and Windows"
\end_inset
for instructions on using PyGTK on Windows for more information.
Or just go to
\begin_inset CommandInset href
LatexCommand href
target "http://sourceforge.net/projects/pywin32/files/"
\end_inset
and download and install it.
\end_layout
\end_inset
.
\end_layout
\begin_layout Standard
First off the os, win32con, and win32gui modules will need to be imported
along with the pygtk and gtk modules.
\end_layout
\begin_layout LyX-Code
import os
\end_layout
\begin_layout LyX-Code
import win32gui, win32con
\end_layout
\begin_layout Standard
Like all the other examples about file choosers the Windows file chooser
will start off with some GUI code.
\end_layout
\begin_layout LyX-Code
def main():
\end_layout
\begin_layout LyX-Code
file_filter="""Text files
\backslash
0*.txt
\backslash
0All Files
\backslash
0*.*
\backslash
0"""
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
\end_layout
\begin_layout LyX-Code
window.set_title("Windows Filechooser Example")
\end_layout
\begin_layout LyX-Code
window.connect("destroy", lambda wid: gtk.main_quit())
\end_layout
\begin_layout LyX-Code
window.connect("delete_event", lambda e1,e2:gtk.main_quit())
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
button_save = gtk.Button("Save File")
\end_layout
\begin_layout LyX-Code
button_open = gtk.Button("Open File")
\end_layout
\begin_layout LyX-Code
button_save.connect("clicked", on_save_clicked, file_filter)
\end_layout
\begin_layout LyX-Code
button_open.connect("clicked", on_open_clicked, file_filter)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
hbox = gtk.HBox(True, 0)
\end_layout
\begin_layout LyX-Code
hbox.pack_start(button_save, True, True, 5)
\end_layout
\begin_layout LyX-Code
hbox.pack_start(button_open, True, True, 5)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
window.add(hbox) window.show_all()
\end_layout
\begin_layout Standard
First thing that is done is to create a file filter that will be used with
the open and save dialogs.
The file filter is in the form of
\begin_inset Quotes eld
\end_inset
Display Name, Seperator, File Type, Seperator, Display Name, Seperator,
File Type, Seperator
\begin_inset Quotes erd
\end_inset
and looks like this:
\end_layout
\begin_layout LyX-Code
file_filter="""Text files
\backslash
0*.txt
\backslash
0All Files
\backslash
0*.*
\backslash
0"""
\end_layout
\begin_layout Standard
The GUI creates one button to launch the save dialog and one to launch the
open dialog.
The button called button_save is clicked it will call the on_save_clicked
function passing along the file filter.
When the button called button_open is clicked, it will call the on_open_clicked
function passing along the file filter.
\end_layout
\begin_layout Standard
The on_save_clicked and on_open_clicked function are very similar in form
with some minor differences.
Here is the on_save_clicked function.
\end_layout
\begin_layout LyX-Code
def on_save_clicked(widget, file_filter=None):
\end_layout
\begin_layout LyX-Code
filename=None
\end_layout
\begin_layout LyX-Code
try:
\end_layout
\begin_layout LyX-Code
filename, customfilter, flags=win32gui.GetSaveFileNameW(
\end_layout
\begin_layout LyX-Code
InitialDir=os.path.join(os.environ['USERPROFILE'],"My Documents"),
\end_layout
\begin_layout LyX-Code
Flags=win32con.OFN_ALLOWMULTISELECT|win32con.OFN_EXPLORER, File='',
\end_layout
\begin_layout LyX-Code
DefExt='txt', Title='Save a File', Filter=file_filter, FilterIndex=0)
\end_layout
\begin_layout LyX-Code
except win32gui.error:
\end_layout
\begin_layout LyX-Code
print "Cancel clicked"
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
print filename
\end_layout
\begin_layout LyX-Code
if filename != None:
\end_layout
\begin_layout LyX-Code
save_file = open(filename, 'w')
\end_layout
\begin_layout LyX-Code
save_file.write("Test Save Data")
\end_layout
\begin_layout LyX-Code
save_file.close()
\end_layout
\begin_layout LyX-Code
return filename
\end_layout
\begin_layout Standard
This is a simple funtion that takes a file filter as an argument and sets
it as the filter for Windows save dialog.
To use and display the save dialog the win32gui.GetSaveFileNameW
\begin_inset Index
status collapsed
\begin_layout Plain Layout
GetSaveFileNameW
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
File Chooser!GetSaveFileNameW
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
win32gui!GetSaveFileNameW
\end_layout
\end_inset
function is used.
Arguments that are used with it include Initial Directory, Flags, File,
Default Extention, Title, File Filter, and FilterIndex.
As can be seen the inital directory is set to the users My Documents folder.
Flags are set to allow multiple selection.
The default extention type is txt.
When it is called it must be done by assigning its return value to three
variables; filename, customfilter, flags.
\end_layout
\begin_layout Standard
The GetSaveFileNameW function must be used with exception handling as it
will through an exception if the cancel button is clicked.
So this example catches win32gui.error exceptions and prints the message
\begin_inset Quotes eld
\end_inset
Cancel clicked
\begin_inset Quotes erd
\end_inset
instead of crashing.
\end_layout
\begin_layout Standard
If a file has been selected to save this example saves it with the string
\begin_inset Quotes eld
\end_inset
Test Save Data
\begin_inset Quotes erd
\end_inset
.
\end_layout
\begin_layout Standard
The GetOpenFileNameW
\begin_inset Index
status collapsed
\begin_layout Plain Layout
File Chooser!GetOpenFileNameW
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
win32gui!GetOpenFileNameW
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
GetOpenFileNameW
\end_layout
\end_inset
function is used to select and open file on Windows.
It is very simliar to the GetSaveFileNameW function covered above.
Here is the on_open_clicked function that uses the Windows open dialog.
\end_layout
\begin_layout LyX-Code
def on_open_clicked(widget, file_filter=None):
\end_layout
\begin_layout LyX-Code
filename=None
\end_layout
\begin_layout LyX-Code
try:
\end_layout
\begin_layout LyX-Code
filename, customfilter, flags=win32gui.GetOpenFileNameW(
\end_layout
\begin_layout LyX-Code
InitialDir=os.path.join(os.environ['USERPROFILE'],"My Documents"),
\end_layout
\begin_layout LyX-Code
Flags=win32con.OFN_ALLOWMULTISELECT|win32con.OFN_EXPLORER, File='',
\end_layout
\begin_layout LyX-Code
DefExt='txt', Title='Select a File', Filter=file_filter, FilterIndex=0)
\end_layout
\begin_layout LyX-Code
except win32gui.error:
\end_layout
\begin_layout LyX-Code
print "Cancel clicked"
\end_layout
\begin_layout LyX-Code
print 'open file names:', filename
\end_layout
\begin_layout LyX-Code
return filename
\end_layout
\begin_layout Standard
The GetOpenFileNameW functions takes as arguments Intial Directory, Flags,
File, Default Extention, Title, File Filter, and Filter Index.
As can be seen the inital directory is set to the users My Documents folder.
Flags are set to allow multiple selection.
The default extention type is txt.
When calling this function the return value must be assigned to three variables
; these being the filename, customfilter, and flags.
\end_layout
\begin_layout Standard
Like the save GetSaveFileNameW the GetOpenFileNameW function requires that
it used with exception handling as it will give win32gui.error if the cancel
button is pressed.
If everything goes as planed the function should continue to the end where
it prints the message
\begin_inset Quotes eld
\end_inset
open file names: filename
\begin_inset Quotes erd
\end_inset
.
\end_layout
\begin_layout Section
Glade 3
\begin_inset CommandInset label
LatexCommand label
name "sec:Glade-3"
\end_inset
\end_layout
\begin_layout Standard
\begin_inset Index
status collapsed
\begin_layout Plain Layout
Glade
\end_layout
\end_inset
Glade is a program that allows the creation of the user interface graphical.
Windows and dialogs can be created.
Widgets can be dragged and dropped into place.
Names assigned to widgets, callback functions assigned.
All this is saved to a xml file with an extension of .glade.
\end_layout
\begin_layout Standard
Docked on the left side of glade is the palette.
The palette contains the top level elements such as:
\end_layout
\begin_layout Itemize
windows (gtk.Window)
\end_layout
\begin_layout Itemize
dialogs (gtk.Dialog etc...)
\end_layout
\begin_layout Standard
Under the Toplevels is are the Containers.
The containers contain:
\end_layout
\begin_layout Itemize
Horizontal Box (gtk.HBox)
\end_layout
\begin_layout Itemize
Vertical box (gtk.VBox)
\end_layout
\begin_layout Itemize
Table (gtk.Table)
\end_layout
\begin_layout Itemize
Notebook (gtk.Notebook)
\end_layout
\begin_layout Itemize
Frame (gtk.Frame)
\end_layout
\begin_layout Itemize
etc...
\end_layout
\begin_layout Standard
After and under the Containers are the Control and Display widgets, they
contain:
\end_layout
\begin_layout Itemize
Button (gtk.Button)
\end_layout
\begin_layout Itemize
Toggle Button (gtk.ToggleButton)
\end_layout
\begin_layout Itemize
Check Button (gtk.CheckButton)
\end_layout
\begin_layout Itemize
Spin Button (gtk.SpinButton)
\end_layout
\begin_layout Itemize
Raido Button (gtk.RadioButton)
\end_layout
\begin_layout Itemize
etc...
\end_layout
\begin_layout Standard
To create a simple application, from the Toplevels select and add a Window.
Next select a Horizontal Box and add it to the Window.
When prompted for how many items, select two.
When this is done the window will be split in half horizontally with a
line going down through the center (see figure
\begin_inset CommandInset ref
LatexCommand vref
reference "fig:Basic Glade User Interface"
\end_inset
).
Each of these can hold a widget.
\end_layout
\begin_layout Standard
\begin_inset Float figure
wide false
sideways false
status open
\begin_layout Plain Layout
\begin_inset Graphics
filename images/more-pygtk/Screenshot-glade-example.png
scale 30
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Basic Glade User Interface Designer
\end_layout
\end_inset
\begin_inset CommandInset label
LatexCommand label
name "fig:Basic Glade User Interface"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Next add two buttons from the container.
The one on the left label Message and the one on the right label About.
Also change the names to message and about.
To do this click the first button.
On the right hand side the editor should change for a button type (see
figure
\begin_inset CommandInset ref
LatexCommand vref
reference "fig:Glade Editor with Button"
\end_inset
).
As can be seen in figure
\begin_inset CommandInset ref
LatexCommand ref
reference "fig:Glade Editor with Button"
\end_inset
; the class is of type GtkButton, the name is set to message meaning that
when it is called with PyGTK it uses the name message.
For the Label it is set to Message.
The label is what is displayed to the user as the button text.
\end_layout
\begin_layout Standard
\begin_inset Float figure
wide false
sideways false
status collapsed
\begin_layout Plain Layout
\begin_inset Graphics
filename images/more-pygtk/Screenshot-glade-example-button-editor.png
scale 50
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Glade Editor with Button
\end_layout
\end_inset
\begin_inset CommandInset label
LatexCommand label
name "fig:Glade Editor with Button"
\end_inset
\end_layout
\begin_layout Plain Layout
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Once the buttons have been added and setup with the names and labels then
the signals that are to be caught should be added (see figure
\begin_inset CommandInset ref
LatexCommand vref
reference "fig:Glade Signal Handler Specified"
\end_inset
).
To add signal methods to the buttons first select the message button.
Then in the editor window select the Signals tab.
Under GtkButton there will be a signal called
\emph on
clicked
\emph default
.
For clicked add a handler.
If the handler space is clicked it will provide a default list to choose
from.
To see what it should look like look at figure
\begin_inset CommandInset ref
LatexCommand vref
reference "fig:Glade Signal Handler Specified"
\end_inset
.
What is typed as the Handler is the function or method in the python code
that will be called.
\end_layout
\begin_layout Standard
\begin_inset Float figure
wide false
sideways false
status collapsed
\begin_layout Plain Layout
\begin_inset Graphics
filename images/more-pygtk/Screenshot-glade-example-signal-handler.png
scale 50
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Signal Handler Specified
\end_layout
\end_inset
\begin_inset CommandInset label
LatexCommand label
name "fig:Glade Signal Handler Specified"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Now that the buttons have been added to the main window (whose name is window1)
it is time to make sure that this window is visible.
Select the main window and in the editor select the Common tab.
Once in the Common tab find the
\emph on
Visible
\emph default
option and make sure it is set to
\emph on
Yes
\emph default
(see figure
\begin_inset CommandInset ref
LatexCommand vref
reference "fig:Glade Main Windows Set as Visible"
\end_inset
).
\end_layout
\begin_layout Standard
\begin_inset Float figure
wide false
sideways false
status collapsed
\begin_layout Plain Layout
\begin_inset Graphics
filename images/more-pygtk/Screenshot-glade-example-main-window-visible.png
scale 50
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Main Windows Set as Visible
\end_layout
\end_inset
\begin_inset CommandInset label
LatexCommand label
name "fig:Glade Main Windows Set as Visible"
\end_inset
\end_layout
\begin_layout Plain Layout
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Now the main window is done.
Save your work.
Next an about dialog will be added.
To add an about dialog it is selected from the Toplevel elements on the
palette.
Leave it with the default name
\emph on
aboutdialog1
\emph default
.
The about dialog will be used to show how to interact with more than one
window in glade.
\end_layout
\begin_layout Standard
A PyGTK program interacts with the created glade file using
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!glade
\end_layout
\end_inset
gtk.glade.
\end_layout
\begin_layout LyX-Code
import pygtk
\end_layout
\begin_layout LyX-Code
pygtk.require('2.0')
\end_layout
\begin_layout LyX-Code
import gtk
\end_layout
\begin_layout LyX-Code
import gtk.glade
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
class GladeExample(object):
\end_layout
\begin_layout LyX-Code
def __init__(self):
\end_layout
\begin_layout LyX-Code
self.gladefile = gtk.glade.XML("glade-example.glade")
\end_layout
\begin_layout LyX-Code
self.gladefile.signal_autoconnect(self)
\end_layout
\begin_layout LyX-Code
self.main_window = self.gladefile.get_widget("window1")
\end_layout
\begin_layout LyX-Code
self.about_dialog = self.gladefile.get_widget("aboutdialog1")
\end_layout
\begin_layout LyX-Code
self.message_dialog = self.gladefile.get_widget("messagedialog1")
\end_layout
\begin_layout Standard
Here the class GladeExample is declared with an intiation method that connects
to the glade file that was created.
The glade file is loaded using the gtk.glade.XML class.
It takes as arguments the glade file and optionally a widget and translation
domain.
\end_layout
\begin_layout Standard
Then to use a widget as if it was created using PyGTK code it must be retrieved
using the get_widget method.
The get_widget method works by taking as an argument the name of the widget.
In the glade example above the main windows name is window1, the about
dialogs name is aboutdialog1, and the message dialog is messagedialog1.
As can be seen the main window is assigned to self.main_window and so on
with the about and message dialog.
\end_layout
\begin_layout Standard
What can be noticed that the buttons that were adding to the glade file
to launch the about and message dialog were not assigned with the get_widget
method.
This is because they were set to automatically call handler functions and
do not need to write code for each button to connect them.
This is handled with one line of code, self.gladefile.signal_autoconnect(self).
This one line will automatically connect any signal handlers that were
specified in the glade file without having to write any extra code.
\end_layout
\begin_layout LyX-Code
def on_about_clicked(self, widget):
\end_layout
\begin_layout LyX-Code
self.about_dialog.run()
\end_layout
\begin_layout LyX-Code
self.about_dialog.destroy()
\end_layout
\begin_layout Standard
As was specified with glade, when the about button is clicked, the method
on_about_clicked is called.
This method displays the about dialog that was created with glade and destroys
the dialog when it is closed.
\end_layout
\begin_layout LyX-Code
def on_message_clicked(self, widget):
\end_layout
\begin_layout LyX-Code
self.message_dialog.run()
\end_layout
\begin_layout LyX-Code
self.message_dialog.destroy()
\end_layout
\begin_layout Standard
As was specified with glade, when the message button is clicked, the method
on_message_clicked is called.
This method displays the message dialog that was created with glade and
destroys the dialog when it is closed.
\end_layout
\begin_layout LyX-Code
def on_window1_delete_event(self, widget, event):
\end_layout
\begin_layout LyX-Code
gtk.main_quit()
\end_layout
\begin_layout Standard
the on_window1_delete_event will quite the PyGTK application when the main
window(window1) is closed.
This to is specified with glade under the main windows Signal tab; GtkWidget
--> delete-event.
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
app = GladeExample()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Standard
And of course a few lines that runs the glade example.
\end_layout
\begin_layout Section
Builder
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sec: Gtk Builder Convert"
\end_inset
\end_layout
\begin_layout Standard
Builder refers to gtk.Builder which is the future as it is a replacement
for gtk.glade.
Basically what it is is including support for xml files to build applications
inside of GTK itself, unlike glade which is a library.
Currently the glade program does not support saving to the Builder format,
but it will soon.
In the mean time glade files must be converted to Builder files using
\emph on
gtk-builder-convert
\emph default
\begin_inset Foot
status collapsed
\begin_layout Plain Layout
For more information on gtk-builder-convert visit:
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://library.gnome.org/devel/gtk/2.12/gtk-builder-convert.html
\end_layout
\end_inset
.
\end_layout
\begin_layout Plain Layout
Also if you plan on using gtk-builder-convert, gtk development files must
be installed to have it installed.
This is accomplished on Ubuntu by installing libgtk2.0-dev.
\end_layout
\end_inset
.
This program will take a glade xml file and convert it to a Builder xml
file.
\end_layout
\begin_layout Standard
To convert a glade file to a Builder file the following command is issued:
\end_layout
\begin_layout LyX-Code
gtk-builder-convert glade-example.glade glade-example.xml
\end_layout
\begin_layout Standard
Now instead of using gtk.glade.XML to access this new builder xml file, gtk.Builder
is used as shown here.
\end_layout
\begin_layout LyX-Code
builder = gtk.Builder()
\end_layout
\begin_layout LyX-Code
builder.add_from_file(
\begin_inset Quotes eld
\end_inset
glade-example.xml
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout Standard
Also instead of using get_widget like in the glade example (see
\begin_inset CommandInset ref
LatexCommand ref
reference "sec:Glade-3"
\end_inset
), the method
\emph on
get_object
\emph default
is used.
\end_layout
\begin_layout LyX-Code
main_window = builder.get_object(
\begin_inset Quotes eld
\end_inset
window1
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
about_dialog = builder.get_object(
\begin_inset Quotes eld
\end_inset
aboutdialog1
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
message_dialog = builder.get_object(
\begin_inset Quotes eld
\end_inset
messagedialog1
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout Standard
With this done, the widgets can be used as if they were programmed normally
with PyGTK.
\end_layout
\begin_layout Standard
To auto connect the signals like is avialbe using glade the following code
is used.
\end_layout
\begin_layout LyX-Code
builder.connect_signals(self)
\end_layout
\begin_layout Standard
Remember this needs to be done from within a class.
\end_layout
\begin_layout Section
Loading Images
\end_layout
\begin_layout Standard
To load an image with PyGTK an instance of the gtk.Image
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!Image
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
Image Loading
\end_layout
\end_inset
class must be created.
With this becomes available several methods for loading different types
of images.
This example will cover loading images from file and from the GTK stock
images.
\end_layout
\begin_layout Standard
Loading Images
\end_layout
\begin_layout LyX-Code
import pygtk, gtk
\end_layout
\begin_layout LyX-Code
def main():
\end_layout
\begin_layout LyX-Code
win = gtk.Window()
\end_layout
\begin_layout LyX-Code
win.connect("delete_event", lambda w,e: gtk.main_quit())
\end_layout
\begin_layout LyX-Code
vbox = gtk.VBox(False, 0)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
image1 = gtk.Image()
\end_layout
\begin_layout LyX-Code
image1.set_from_stock(gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_DND)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
image2 = gtk.Image()
\end_layout
\begin_layout LyX-Code
image2.set_from_file("flower.jpg")
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
vbox.pack_start(image1, False, False, 5)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(image2, False, False, 5)
\end_layout
\begin_layout LyX-Code
win.add(vbox)
\end_layout
\begin_layout LyX-Code
win.show_all()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
main()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Standard
This example creates a window with a gtk.VBox and adds two images.
The first image is set from stock gtk images created with the set_from
stock method.
The set_from_stock method requires a GTK stock image and a stock size.
The stock types available can be found in the appendix (
\begin_inset CommandInset ref
LatexCommand vpageref
reference "sec:Appendix Stock Icons"
\end_inset
).
The stock sizes include:
\end_layout
\begin_layout Itemize
gtk.ICON_SIZE_MENU
\end_layout
\begin_layout Itemize
gtk.ICON_SIZE_SMALL_TOOLBAR
\end_layout
\begin_layout Itemize
gtk.ICON_SIZE_LARGE_TOOLBAR
\end_layout
\begin_layout Itemize
gtk.ICON_SIZE_BUTTON
\end_layout
\begin_layout Itemize
gtk.ICON_SIZE_DND
\end_layout
\begin_layout Itemize
gtk.ICON_SIZE_DIALOG
\end_layout
\begin_layout Standard
The second image is loaded using set_from_file method.
All this method requires is location on the computer to the image.
\end_layout
\begin_layout Standard
All that needs to be done once the images are loaded is add them to a widget.
In this example they are added to gtk.VBox.
\end_layout
\begin_layout Standard
There are many different methods for loading images and they can be found
at the PyGTK reference site
\begin_inset Foot
status collapsed
\begin_layout Plain Layout
The PyGTK image class can be found at:
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://www.pygtk.org/docs/pygtk/class-gtkimage.html
\end_layout
\end_inset
\end_layout
\end_inset
.
\end_layout
\begin_layout Standard
\begin_inset Branch SecondEdition
status open
\begin_layout Section
Color Selector
\end_layout
\begin_layout Standard
This section is not yet written :)
\end_layout
\begin_layout Section
Textarea Hyperlinks
\end_layout
\begin_layout Standard
This section is not yet written :)
\end_layout
\begin_layout Section
Label Hyperlinks
\end_layout
\begin_layout Standard
This section is not yet written :)
\end_layout
\end_inset
\end_layout
\begin_layout Section
Tooltips
\end_layout
\begin_layout Standard
A tooltip
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!Tooltip
\end_layout
\end_inset
is used to display useful information to the screen a user puts a mouse
over a widget such as label or button.
To use a simple tooltip requires only on method call on the widget: set_tooltip
_text
\end_layout
\begin_layout LyX-Code
label = gtk.Label("Display Tooltip")
\end_layout
\begin_layout LyX-Code
label.set_tooltip_text("This is a Tooltip")
\end_layout
\begin_layout Standard
When a mouse is placed over this label a tooltip will display the text
\begin_inset Quotes eld
\end_inset
This is a Tooltip
\begin_inset Quotes erd
\end_inset
.
Very simple to use and there is nothing more to be said on that.
\end_layout
\begin_layout Standard
For more fancy tooltips a custom tooltip must be created.
To do this the has_tooltip property must be set to True.
Then the widget that is to display the custom tooltip must connect to the
query-tooltip
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!Tooltip!query-tooltip
\end_layout
\end_inset
signal.
For example, the callback function can create a new tooltip by creating
an gtk.HBox that holds an image and text then use set_custom on the tooltip
to use this hbox.
\end_layout
\begin_layout Standard
Here is an example
\begin_inset Index
status collapsed
\begin_layout Plain Layout
gtk!Tooltip!Custom Tooltip
\end_layout
\end_inset
.
\end_layout
\begin_layout LyX-Code
fancy_label = gtk.Label("A fancy Tooltip")
\end_layout
\begin_layout LyX-Code
fancy_label.props.has_tooltip = True
\end_layout
\begin_layout LyX-Code
fancy_label.connect("query-tooltip", on_query_tooltip)
\end_layout
\begin_layout Standard
So this creates a label, sets the tooltip to true using fancy_label.props.has_tool
tip property, and then connects the query-tooltip signal to the function
on_query_tooltip.
\end_layout
\begin_layout Standard
Here is an example of the on_query_tooltip function.
This function creates a label and an image that is displayed instead of
plain text.
\end_layout
\begin_layout LyX-Code
def on_query_tooltip(widget, x, y, keyboard_tip, tooltip):
\end_layout
\begin_layout LyX-Code
hbox = gtk.HBox()
\end_layout
\begin_layout LyX-Code
label = gtk.Label('Fancy Tooltip with an Image')
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
image = gtk.Image()
\end_layout
\begin_layout LyX-Code
image.set_from_stock(gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_DND)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
hbox.pack_start(image, False, False, 0)
\end_layout
\begin_layout LyX-Code
hbox.pack_start(label, False, False, 0)
\end_layout
\begin_layout LyX-Code
hbox.show_all()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
tooltip.set_custom(hbox)
\end_layout
\begin_layout LyX-Code
return True
\end_layout
\begin_layout Standard
As can be seen this creates a gtk.HBox to hold a label and an Image.
It then uses the tooltip argument to set it to a custom tooltip.
A custom tooltip can be anything but this example has kept it simple for
understandability sake.
For more tooltip options visit the PyGTK tooltip reference page
\begin_inset Foot
status collapsed
\begin_layout Plain Layout
The PyGTK tooltip reference page can be found at:
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://www.pygtk.org/docs/pygtk/class-gtktooltip.html
\end_layout
\end_inset
\end_layout
\end_inset
.
\end_layout
\begin_layout Section
Summary
\end_layout
\begin_layout Standard
This section is not yet written :)
\end_layout
\begin_layout Chapter
Cairo
\end_layout
\begin_layout Standard
\begin_inset Box Frameless
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "100col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
\begin_inset CommandInset include
LatexCommand input
filename "chapter-heading.lyx"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Section
Introduction
\end_layout
\begin_layout Standard
Welcome to the chapter on cairo
\begin_inset Index
status collapsed
\begin_layout Plain Layout
cairo
\end_layout
\end_inset
.
What is cairo? Cario is a powerful 2d graphics library that lets you output
to many different surfaces.
Surfaces that are supported include image surfaces (png) pdf, postscript,
win32, svg, quartz, and xlib.
What all these different surfaces achieve will be discussed throughout
the chapter; however every surface type here supports writing to png.
Besides including png write support, cairo also includes png import support.
\end_layout
\begin_layout Standard
While reading about cairo in other sources you may find that it is suggested
to think of cairo is as a canvas that you will paint on.
This kind of works for me.
You have the canvas that you can put different layers of paint on that
when combined and finished produces your final output.
But that is about as far I will be using this metaphor in this chapter.
\end_layout
\begin_layout Standard
Things that cairo can be used for include creating graphics, combining work,
doing layout for printing, or even creating reports as pdf documents.
Really the only limitation to cairo is your imagination.
\end_layout
\begin_layout Standard
Dig in and see what you can learn.
\end_layout
\begin_layout Section
Basics
\end_layout
\begin_layout Standard
The first example with cairo will be some simple drawing.
It will create a surface and draw a line saving to a image file.
When working with cairo it must be remembered that the
\shape italic
cairo
\shape default
module is needed and must be imported.
\end_layout
\begin_layout LyX-Code
import cairo
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
WIDTH, HEIGHT = 400, 400
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Setup Cairo
\end_layout
\begin_layout LyX-Code
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
\end_layout
\begin_layout LyX-Code
context = cairo.Context(surface)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Set thickness of brush
\end_layout
\begin_layout LyX-Code
context.set_line_width(15)
\end_layout
\begin_layout LyX-Code
# Draw Vertical Line
\end_layout
\begin_layout LyX-Code
context.move_to(200, 150)
\end_layout
\begin_layout LyX-Code
context.line_to(200, 250)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Draw horizontal line
\end_layout
\begin_layout LyX-Code
context.move_to(150, 200)
\end_layout
\begin_layout LyX-Code
context.line_to(250, 200)
\end_layout
\begin_layout LyX-Code
context.stroke()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Output a PNG file
\end_layout
\begin_layout LyX-Code
surface.write_to_png("cairo-draw-line1.png")
\end_layout
\begin_layout Standard
This example creates an ImageSurface with a width and height of 400.
The ImageSurface is set to use the cairo.FORMAT_ARG32 (See below for details).
The context is what actually keeps track of everything that is done to
the surface and is used to draw.
It is used to control how the drawing operations are used.
\end_layout
\begin_layout Standard
With a context set it is now possible to draw or perform other actions.
First thing that is done is to set the line width to 15 using the
\emph on
context.set_line_width(15)
\emph default
\begin_inset Index
status collapsed
\begin_layout Plain Layout
cairo!set
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
line
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
width
\end_layout
\end_inset
method.
The
\emph on
set_line
\emph default
method sets the width of a line for a context.
\end_layout
\begin_layout Standard
Next, using the contexts move_to
\begin_inset Index
status collapsed
\begin_layout Plain Layout
cairo!move
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
to
\end_layout
\end_inset
method moves the position of the brush to the position specified; which
in the line is x position 200 and y position 150.
X coordinates
\begin_inset Index
status collapsed
\begin_layout Plain Layout
cairo!coordinates
\end_layout
\end_inset
are measured from the left most part of the surface.
Y coordinates are measured from the top most part of the surface.
\end_layout
\begin_layout Standard
Using the contexts
\emph on
line_to
\emph default
method will draw a line from the current position, that was specified with
the move_to method, to the new position specified with the line_to method.
To display what has been drawn with the
\emph on
line_to
\emph default
method the contexts
\emph on
stroke
\emph default
method must be called.
Once
\emph on
context.stroke()
\emph default
is called then the lines are actually applied to the surface.
\end_layout
\begin_layout Standard
See figure
\begin_inset CommandInset ref
LatexCommand ref
reference "fig:Cairo - Two Striaght Lines"
\end_inset
to see what the output should look like.
\end_layout
\begin_layout Standard
\begin_inset Float figure
wide false
sideways false
status open
\begin_layout Plain Layout
\begin_inset Graphics
filename images/cairo/cairo-draw-line1.png
scale 50
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Two Straight Lines
\end_layout
\end_inset
\begin_inset CommandInset label
LatexCommand label
name "fig:Cairo - Two Striaght Lines"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Subsection
Cairo Surface Format
\end_layout
\begin_layout Standard
There are four surface options available
\begin_inset Foot
status collapsed
\begin_layout Plain Layout
The list of formats available are taken from the cairo website and can be
found at:
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://www.cairographics.org/manual/cairo-Image-Surfaces.html
\end_layout
\end_inset
\end_layout
\end_inset
and they are:
\end_layout
\begin_layout List
\labelwidthstring 00.00.0000
cairo.FORMAT_ARGB32 - each pixel is a 32-bit quantity, with alpha in the
upper 8 bits, then red, then green, then blue.
The 32-bit quantities are stored native-endian.
Pre-multiplied alpha is used.
(That is, 50% transparent red is 0x80800000, not 0x80ff0000.)
\end_layout
\begin_layout List
\labelwidthstring 00.00.0000
cairo.FORMAT_RGB24 - each pixel is a 32-bit quantity, with the upper 8 bits
unused.
Red, Green, and Blue are stored in the remaining 24 bits in that order.
\end_layout
\begin_layout List
\labelwidthstring 00.00.0000
cairo.FORMAT_A8 - each pixel is a 8-bit quantity holding an alpha value.
\end_layout
\begin_layout List
\labelwidthstring 00.00.0000
cairo.FORMAT_A1 - each pixel is a 1-bit quantity holding an alpha value.
Pixels are packed together into 32-bit quantities.
The ordering of the bits matches the endianess of the platform.
On a big-endian machine, the first pixel is in the uppermost bit, on a
little-endian machine the first pixel is in the least-significant bit.
\end_layout
\begin_layout Standard
In most cases cairo.FORMAT_ARGB32 or cairo.FORMAT_RGB24 will be used.
\end_layout
\begin_layout Subsection
Cairo Surfaces
\end_layout
\begin_layout Standard
\begin_inset Note Note
status open
\begin_layout Plain Layout
Fill this section out a little with some commentary and a very very small
code snippet.
\end_layout
\end_inset
\end_layout
\begin_layout List
\labelwidthstring 00.00.0000
cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT) - Use to render to
memory buffers.
\end_layout
\begin_layout List
\labelwidthstring 00.00.0000
cairo.PDFSurface("drawings.pdf", WIDTH, HEIGHT) - Renders to the specified
PDF file.
\end_layout
\begin_layout List
\labelwidthstring 00.00.0000
cairo.PSSurface("drawings.ps", WIDTH, HEIGHT) - Renders to the specified Postscrip
t file.
\end_layout
\begin_layout List
\labelwidthstring 00.00.0000
cairo.SVGSurface("drawings.svg", WIDTH, HEIGHT) - Renders to the specified
SVG file.
\end_layout
\begin_layout Section
Drawing Context
\end_layout
\begin_layout Standard
As discussed above, a context is what allows the programmer to use the cairo
surface.
This section will discover different uses of the context class by making
use of several different examples.
For a list of the context methods used in this section please skip ahead
to
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Cairo Context Methods"
\end_inset
.
\end_layout
\begin_layout Subsection
Paths: Lines, Curves, Arcs
\end_layout
\begin_layout Standard
To start off this section lets take a look at line drawing again, but using
it to draw more than two straight lines.
This example will use the
\emph on
line_to
\emph default
method to create a rectangle and triangle.
It will also use a new method,
\emph on
arc
\emph default
, to create a circle.
Along with with these two methods, the color of the context will be set
using the
\emph on
set_source_rgb
\emph default
value.
These methods set points that are then used to create a path.
\end_layout
\begin_layout Standard
This example starts by calling the
\emph on
main
\emph default
function.
Inside the main function it creates a cairo ImageSurface with an alpha
RGB format and a width and height of 400.
It then creates a context from this surface.
The ImageSurface renders to a memory buffer and not an image.
To save to an image the surface must call the
\emph on
write_to_png
\emph default
method that is available to all surface types.
\end_layout
\begin_layout Standard
Next it sets the line with of the context to 15.
Immediately after this it calls the draw_rectangle, draw_triangle, and
draw_circle functions.
These are functions that are defined in this example and are not cairo
builtin methods.
While cairo contexts do have a builtin method to create rectangles, this
example is doing it manually just to show how to use the line_to method
in different ways.
\end_layout
\begin_layout Standard
Cairo Context Basics
\end_layout
\begin_layout LyX-Code
#!/usr/bin/env python
\end_layout
\begin_layout LyX-Code
import cairo
\end_layout
\begin_layout LyX-Code
import math
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def draw_rectangle(context=None):
\end_layout
\begin_layout LyX-Code
x1, y1 = 25, 150 # top left corner
\end_layout
\begin_layout LyX-Code
x2, y2 = 25, 250 # bottom left corner
\end_layout
\begin_layout LyX-Code
x3, y3 = 125, 250 # bottom right corner
\end_layout
\begin_layout LyX-Code
x4, y4 = 125, 150 # top right corner
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
context.set_source_rgb(1.0, 0.0, 0.0) # red
\end_layout
\begin_layout LyX-Code
context.move_to(x1, y1)
\end_layout
\begin_layout LyX-Code
context.line_to(x2, y2)
\end_layout
\begin_layout LyX-Code
context.line_to(x3, y3)
\end_layout
\begin_layout LyX-Code
context.line_to(x4, y4)
\end_layout
\begin_layout LyX-Code
context.close_path()
\end_layout
\begin_layout LyX-Code
context.stroke()
\end_layout
\begin_layout Standard
The draw_rectangle function starts off by defining four corners that will
make up the rectangle.
\end_layout
\begin_layout Standard
These four x and four y coordinates create the four corners of the rectangle.
Top left, bottom left, bottom right and the top right corners.
To draw a rectangle the function first uses the
\emph on
move_to(x1, y1)
\emph default
method on the context to move the starting position to the first corner.
Then it uses the
\emph on
line_to(x2, y2)
\emph default
method to create a line from the first corner to the second.
Then it again uses the
\emph on
line_to
\emph default
method with x3 and y3 to create a line from the second corner to the third
corner.
And last with the
\emph on
line_to
\emph default
method it creates a line from the third to the fourth corner.
\end_layout
\begin_layout Standard
Now if you follow that lines that were created, you will notice only the
left side, bottom, and right side where drawn, but all four corners were
used.
The line_to method could be used again to draw a line from x4 and y4 to
x1 and y1, but instead the
\emph on
close_path
\begin_inset Index
status collapsed
\begin_layout Plain Layout
cairo!Context!close
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
path
\end_layout
\end_inset
\emph default
method is used.
The close path method will draw a line from the current position to the
first position (since the last time the
\emph on
stroke
\emph default
method was called).
\end_layout
\begin_layout Standard
Also in the draw_rectangle function the context is set to draw these lines
in red using the set_source_rgb(red, green, blue)
\begin_inset Index
status collapsed
\begin_layout Plain Layout
cairo!Context!set
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
source
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
rgb
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
cairo!color
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
cairo!Context!ontext!color
\end_layout
\end_inset
method.
This method takes 3 variables each with a value between 0.0 and 1.0, with
0.0 being none of that color and 1.0 being a solid color.
The lower the value, the higher the opacity.
\end_layout
\begin_layout LyX-Code
def draw_triangle(context=None):
\end_layout
\begin_layout LyX-Code
context.set_source_rgb(0.0, 1.0, 0.0) # green
\end_layout
\begin_layout LyX-Code
context.move_to(275, 175)
\end_layout
\begin_layout LyX-Code
context.line_to(375, 375)
\end_layout
\begin_layout LyX-Code
context.rel_line_to(-200, 0)
\end_layout
\begin_layout LyX-Code
context.close_path()
\end_layout
\begin_layout LyX-Code
context.stroke()
\end_layout
\begin_layout Standard
The draw_triangle method is similar to the draw_rectangle function in that
it also uses the move_to, line_to and close_path methods.
But it also uses the
\emph on
rel_line_to
\emph default
\begin_inset Index
status collapsed
\begin_layout Plain Layout
cairo!Context!rel
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
line
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
to
\end_layout
\end_inset
method; this method stand for relative_line_to, and moves to a new position
based on the current location instead of using the absolute value of the
surfaces width and height.
\end_layout
\begin_layout Standard
Like in the rectangle function, the triangle function sets the color
\begin_inset Index
status collapsed
\begin_layout Plain Layout
cairo!Context!color
\end_layout
\end_inset
(to green)
\end_layout
\begin_layout Standard
Next it starts by moving the starting coordinate to x coordinate 275 and
y coordinate 175.
Then draws a line to x 375 and y 375.
\end_layout
\begin_layout Standard
After this instead of drawing based on absolute coordinates of the surface
width and height, it uses the
\emph on
rel_line_to
\emph default
method to draw from x 375 and 375.
It uses -200 x which moves from 375 to 175 and moves 0 from y.
This means there is a line drawn from (375, 375) to (175, 375).
\end_layout
\begin_layout Standard
Finally it closes the path and uses the
\emph on
stroke
\emph default
method to apply the lines to the surface.
\end_layout
\begin_layout LyX-Code
def draw_circle(context=None):
\end_layout
\begin_layout LyX-Code
width, height = 100, 100
\end_layout
\begin_layout LyX-Code
radius = min(width, height)
\end_layout
\begin_layout LyX-Code
context.set_source_rgb(0.0, 0.0, 1.0) # blue
\end_layout
\begin_layout LyX-Code
context.arc(275, 100, radius / 2.0 - 20, 0, 2 * math.pi)
\end_layout
\begin_layout LyX-Code
context.stroke()
\end_layout
\begin_layout Standard
The draw_circle function introduces the a new method;
\emph on
arc
\emph default
\begin_inset Index
status collapsed
\begin_layout Plain Layout
cairo!Context!arc
\end_layout
\end_inset
.
\end_layout
\begin_layout Standard
The start_angle and stop_angle are specified in radians.
If you do not know how to work with radians take a look at section
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Radians and Degrees"
\end_inset
.
Here the start angle is set to 0.
The stop_angle is set to 2 * math.pi, which is 360 degrees.
This arc therefore forms a full circle.
\end_layout
\begin_layout Standard
Other parts of the arc method is the x and y coordinate positions for the
center of the arc.
After the x and y coordinates come the radius of the arc.
\end_layout
\begin_layout LyX-Code
def draw_curve(context=None):
\end_layout
\begin_layout LyX-Code
context.set_source_rgb(0.5, 0.0, 0.3)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
context.move_to(20, 20)
\end_layout
\begin_layout LyX-Code
context.curve_to (60, 100, 100, 20, 140, 100)
\end_layout
\begin_layout LyX-Code
context.stroke()
\end_layout
\begin_layout Standard
The draw_curve function is used to draw a cubic Bézier spline from the current
position to x3 and y3, using x1, x2, y1, y2 as control points.
If no current position is set, x1 and y1 are used as the starting position.
This is accomplished using the curve_to method.
The curve_to method is defined as context.curve_to(x1, y1, x2, y2, x3, y3).
\end_layout
\begin_layout LyX-Code
def main():
\end_layout
\begin_layout LyX-Code
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 400, 400)
\end_layout
\begin_layout LyX-Code
context = cairo.Context(surface)
\end_layout
\begin_layout LyX-Code
context.set_line_width(15)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
draw_rectangle(context)
\end_layout
\begin_layout LyX-Code
draw_triangle(context)
\end_layout
\begin_layout LyX-Code
draw_circle(context)
\end_layout
\begin_layout LyX-Code
draw_curve(context)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
surface.write_to_png("cairo-basics.png")
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
main()
\end_layout
\begin_layout Subsubsection
Radians and Degrees
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Radians and Degrees"
\end_inset
\end_layout
\begin_layout Standard
If you do not know how to work with radians you are in luck, as it is very
simple.
\end_layout
\begin_layout LyX-Code
radians=degree*(math.pi/180)
\end_layout
\begin_layout Standard
If you want to know what the degrees of a radian is that is simple as well.
Switch the degree with the radian and divide 180 by PI.
\end_layout
\begin_layout LyX-Code
degree=radians*(180/math.pi)
\end_layout
\begin_layout Subsection
Text
\end_layout
\begin_layout Standard
Drawing text is with cairo is the same as drawing a line or an arc but using
some specific functions for text.
Start off like any other cairo application setting the type of surface
and setup a context.
\end_layout
\begin_layout LyX-Code
import cairo
\end_layout
\begin_layout LyX-Code
text = "Hello to the Great Text."
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 800, 75)
\end_layout
\begin_layout LyX-Code
context = cairo.Context(surface)
\end_layout
\begin_layout LyX-Code
context.set_source_rgb(0.0, 0.0, 0.0) # set to black
\end_layout
\begin_layout Standard
What is then needed is to set the type of font and its size.
Here a Monospace font is set with a normal slant and is set to be bold
(see section
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Cairo - Font Styles"
\end_inset
for more styles).
The size of the font is set to 50.
\end_layout
\begin_layout LyX-Code
context.select_font_face("Monospace", cairo.FONT_SLANT_NORMAL,
\end_layout
\begin_layout LyX-Code
cairo.FONT_WEIGHT_BOLD)
\end_layout
\begin_layout LyX-Code
context.set_font_size(50)
\end_layout
\begin_layout Standard
Using the context that was created it is possible to retrieve information
on the text that is being used with the text_extents method.
\end_layout
\begin_layout LyX-Code
x_bearing, y_bearing, width, height = context.text_extents(text)[:4]
\end_layout
\begin_layout Standard
Last is to move the context to the location that it should be drawn.
Here the text is set to draw a X coordinate 5 and at a Y coordinate that
is is the height of the text.
To apply the text the show_text method is now called.
This method adds text to the cairo context.
To show the text the stroke method is called.
To finish off it is saved to a file called cairo-draw-text1.png.
\end_layout
\begin_layout LyX-Code
context.move_to(5, height)
\end_layout
\begin_layout LyX-Code
context.show_text(text)
\end_layout
\begin_layout LyX-Code
context.stroke()
\end_layout
\begin_layout LyX-Code
surface.write_to_png("cairo-draw-text1.png")
\end_layout
\begin_layout Subsubsection
Font Styles
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Cairo - Font Styles"
\end_inset
\end_layout
\begin_layout Standard
There are more than two types of font face styles available with cairo;
there are five.
\end_layout
\begin_layout Itemize
cairo.FONT_SLANT_ITALIC
\end_layout
\begin_layout Itemize
cairo.FONT_SLANT_NORMAL
\end_layout
\begin_layout Itemize
cairo.FONT_SLANT_OBLIQUE
\end_layout
\begin_layout Itemize
cairo.FONT_WEIGHT_BOLD
\end_layout
\begin_layout Itemize
cairo.FONT_WEIGHT_NORMAL
\end_layout
\begin_layout Subsection
Antialias
\end_layout
\begin_layout Standard
First lets define antialias so there is no confusion.
\end_layout
\begin_layout Description
Antialias: Is the technique of minimizing the distortion artifacts created
while drawing.
\end_layout
\begin_layout Standard
But what does this mean? Basically nothing if a straight line is being drawn.
However if a curve or arc is being drawn it will look distorted or jagged,
not very smooth at all.
However with antialiasing turned on it will look smooth by setting the
color correctly around the edges.
The best way to understand this is to view an image.
Take a look at figure
\begin_inset CommandInset ref
LatexCommand ref
reference "fig:Cairo - Antialias Example"
\end_inset
and see if you can tell the difference.
\end_layout
\begin_layout Standard
\begin_inset Float figure
wide false
sideways false
status open
\begin_layout Plain Layout
\begin_inset Graphics
filename images/cairo/cairo-antialias.png
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Antialias Example - As can be seen the circle on the left uses the default
cairo antialias while the circle on the left turns antialias off.
As can be seen when antialias is turned off the curves become jagged/distorted.
\end_layout
\end_inset
\begin_inset CommandInset label
LatexCommand label
name "fig:Cairo - Antialias Example"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Now the question is why would you want to turn off antialiasing? I cannot
think of to many reasons, but one that I can think of is for the program
DeVeDe.
It is a GUI application that uses a few command line applications to create
DVDs from video files.
\end_layout
\begin_layout Standard
One of the programs that DeVeDe uses is dvdauthor.
One of the functions of dvdauthor is to create dvd menus.
And one part of the menu system is not able to handle more than four colors
in an image including the alpha channel.
With antialiasing turned on it will output images with many colors, because
to make a curve look smooth it uses different shades of the color being
used.
However if antialias is set to none the image created with cairo will only
have the colors specified and will be able to be used with dvdauthor.
\end_layout
\begin_layout Subsubsection
Changing Antialias
\end_layout
\begin_layout Standard
To change the default antialias the contexts set_antialias method is used.
\end_layout
\begin_layout LyX-Code
context.set_antialias(Antialias Type)
\end_layout
\begin_layout Standard
To find out what the current setting is just use the context get_antialias()
method.
\end_layout
\begin_layout Standard
The example below sets up a normal cairo surface and context.
It then draws two circles.
The first circles draws with the default antialias, which is cairo.ANTIALIAS_DEF
AULT, and the second circle is drawn with antialias turned off.
\end_layout
\begin_layout LyX-Code
import cairo, math
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def draw_circle(context, xc, yc):
\end_layout
\begin_layout LyX-Code
radius = 150
\end_layout
\begin_layout LyX-Code
context.set_source_rgb(0.0, 0.0, 1.0)
\end_layout
\begin_layout LyX-Code
context.arc(xc, yc, radius / 2.0 - 20, 0, 2 * math.pi)
\end_layout
\begin_layout LyX-Code
context.stroke()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 300, 200)
\end_layout
\begin_layout LyX-Code
context = cairo.Context(surface)
\end_layout
\begin_layout LyX-Code
context.set_line_width(20)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
draw_circle(context, 75, 100)
\end_layout
\begin_layout LyX-Code
context.set_antialias(cairo.ANTIALIAS_NONE)
\end_layout
\begin_layout LyX-Code
draw_circle(context, 225, 100)
\end_layout
\begin_layout LyX-Code
surface.write_to_png("cairo-antialias.png")
\end_layout
\begin_layout Standard
To turn off antialias, the context set_antialias method must be given the
cairo.ANTIALIAS_NONE type.
To see what this looks like take a look at figure
\begin_inset CommandInset ref
LatexCommand ref
reference "fig:Cairo - Antialias Example"
\end_inset
.
\end_layout
\begin_layout Subsubsection
Antialias Types
\end_layout
\begin_layout Standard
The four options available for antialias are:
\end_layout
\begin_layout Itemize
cairo.ANTIALIAS_DEFAULT
\end_layout
\begin_layout Itemize
cairo.ANTIALIAS_GRAY
\end_layout
\begin_layout Itemize
cairo.ANTIALIAS_SUBPIXEL
\end_layout
\begin_layout Itemize
cairo.ANTIALIAS_NONE
\end_layout
\begin_layout Subsection
Context Methods
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Cairo Context Methods"
\end_inset
\end_layout
\begin_layout Description
set_source_rgb(R,G,B) This allows setting the color value of the context
\end_layout
\begin_layout Description
rel_curve_to(x1,y1,x2,y2,x3,y3) Create a curve instead of a straight line
from the current position to x3 and y3, using x1/y1 and x2/y2 as control
point.
Where x1, x2, x3, y1, y2, y3 are relative to the current position.
\end_layout
\begin_layout Description
curve_to(x1,y1,x2,y2,x3,y4) Create a curve instead of a straight line from
the current position to x3 and y3, using x1/y1 and x2/y2 as control point
\end_layout
\begin_layout Description
rel_line_to(x,y) Draw a line relative to the current position with an offset
of x and of y
\end_layout
\begin_layout Description
line_to(x,y) Draw a line from the current position to the new position
\end_layout
\begin_layout Description
rel_mov_to(x,y) Move the position relative to the current position
\end_layout
\begin_layout Description
move_to(x,y) Move by an absolute position
\end_layout
\begin_layout Description
set_font_size(size) set the size of the font
\end_layout
\begin_layout Description
arc Draw an arc
\end_layout
\begin_layout Description
fill Color the path that as been set with rectangle or line_to with the
color that has been set
\end_layout
\begin_layout Description
rectangle(x1,y1,x2,y2) Draw a rectangle
\end_layout
\begin_layout Description
set_antialias(type) Set the the type of antialias that is to be used
\end_layout
\begin_layout Description
close_path Draw a line from the starting position since the last time stroke
was called from the current position, thus closing the path
\end_layout
\begin_layout Section
Cairo and PyGTK
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sec:Cairo - Cairo and PyGTK"
\end_inset
\end_layout
\begin_layout Standard
\begin_inset Float figure
wide false
sideways false
status collapsed
\begin_layout Plain Layout
\begin_inset Graphics
filename images/cairo/cairo-gtk-screenshot.png
scale 70
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset Caption
\begin_layout Plain Layout
Custom PyGTK widget with Cairo
\end_layout
\end_inset
\end_layout
\begin_layout Plain Layout
\begin_inset CommandInset label
LatexCommand label
name "fig:Cairo - PyGTK Cairo Custom Widget"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Cairo can be used with PyGTK by creating a custom widget.
The custom widget discussed here will extend the gtk.DrawingArea class and
override
\begin_inset Foot
status collapsed
\begin_layout Plain Layout
Take a look at
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://www.sicem.biz/personal/lgs/docs/docs/gobject-python/gobject-tutorial.html
\end_layout
\end_inset
for a tutorial on creating custom properties and signals.
Overriding signals is also covered.
\end_layout
\end_inset
the expose_event
\begin_inset Index
status collapsed
\begin_layout Plain Layout
signals!expose
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
event
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
expose
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
event
\end_layout
\end_inset
signal callback method;
\emph on
do_expose_event
\emph default
\begin_inset Index
status collapsed
\begin_layout Plain Layout
do
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
expose
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
event
\end_layout
\end_inset
.
\end_layout
\begin_layout LyX-Code
class CairoGtkOverride(gtk.DrawingArea):
\end_layout
\begin_layout LyX-Code
__gsignals__ = {"expose_event": "override" }
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def __init__(self):
\end_layout
\begin_layout LyX-Code
gtk.DrawingArea.__init__(self)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def do_expose_event(self, event):
\end_layout
\begin_layout LyX-Code
context = self.window.cairo_create()
\end_layout
\begin_layout LyX-Code
context.rectangle(event.area.x, event.area.y,
\end_layout
\begin_layout LyX-Code
event.area.width, event.area.height)
\end_layout
\begin_layout LyX-Code
context.clip()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.draw(context, *self.window.get_size())
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def draw(self, context, width, height):
\end_layout
\begin_layout LyX-Code
context.set_source_rgb(0.5, 0.0, 0.0)
\end_layout
\begin_layout LyX-Code
context.rectangle(0, 0, width, height)
\end_layout
\begin_layout LyX-Code
context.fill()
\end_layout
\begin_layout Standard
To properly override a signal in PyGTK set the class variable __gsignals__
to override the expose_event signal.
In the __init__ method the class initiates its base class.
\end_layout
\begin_layout Standard
The
\emph on
do_expose_event
\emph default
method is the callback for the expose_event signal.
It sets up a cairo context, creates a rectangle to the size of the widget.
It uses the event to retrieve the size that is needed;
\emph on
event.area.x
\emph default
and
\emph on
event.area.y
\emph default
are the starting x and y coordinates while
\emph on
event.area.width
\emph default
and
\emph on
event.area.height
\emph default
are the width and height of the widget.
Then the widget is set to only draw to the size of the rectangle using
\emph on
context.clip()
\emph default
.
The last part is to call the classes
\emph on
draw
\emph default
method on every expose event.
\end_layout
\begin_layout Standard
The draw method takes as arguments a cairo context and a width and height
of the widget.
The draw method is where you can use cairo just as if it were not with
PyGTK.
The draw method in CairoGtkOverride draws a red rectangle.
\end_layout
\begin_layout Standard
Now that a custom widget class has been created it can be extend as much
as is wanted and the draw method overwritten to draw what is desired.
\end_layout
\begin_layout LyX-Code
class Circle(CairoGtkOverride):
\end_layout
\begin_layout LyX-Code
def draw(self, context, width, height):
\end_layout
\begin_layout LyX-Code
context.set_source_rgb(1.0, 0.0, 0.0)
\end_layout
\begin_layout LyX-Code
radius = min(width, height)
\end_layout
\begin_layout LyX-Code
context.arc(width / 2.0, height / 2.0,
\end_layout
\begin_layout LyX-Code
radius / 2.0 - 20, 0, 2 * math.pi)
\end_layout
\begin_layout LyX-Code
context.stroke()
\end_layout
\begin_layout Standard
The above code extend the gtk custom widget class that was created further
up and draws a circle instead of a red rectangle.
\end_layout
\begin_layout Standard
To run the code just add these widgets to your PyGTK application the same
way you would any other widget.
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
win = gtk.Window()
\end_layout
\begin_layout LyX-Code
win.connect("delete-event", gtk.main_quit)
\end_layout
\begin_layout LyX-Code
vbox = gtk.VBox()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
override_widget = CairoGtkOverride()
\end_layout
\begin_layout LyX-Code
circle_widget = Circle()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
vbox.pack_start(override_widget, True, True, 0)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(circle_widget, True, True, 0)
\end_layout
\begin_layout LyX-Code
win.add(vbox)
\end_layout
\begin_layout LyX-Code
win.show_all()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Section
Summary
\end_layout
\begin_layout Standard
For more examples on PyGTK and cairo you can take a look at the following
resources:
\end_layout
\begin_layout Itemize
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://blog.eikke.com/index.php/ikke/2007/02/17/python_cairo_xshape_and_clocks
\end_layout
\end_inset
\end_layout
\begin_layout Itemize
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://ralph-glass.homepage.t-online.de/clock/readme.html
\end_layout
\end_inset
\end_layout
\begin_layout Itemize
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://ralph-glass.homepage.t-online.de/shogi/readme.html
\end_layout
\end_inset
\end_layout
\begin_layout Itemize
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://www.cairographics.org/pycairo/resources/
\end_layout
\end_inset
\end_layout
\begin_layout Itemize
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://www.tortall.net/mu/wiki/CairoTutorial
\end_layout
\end_inset
\end_layout
\begin_layout Itemize
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://www.tortall.net/mu/wiki/PyGTKCairoTutorial
\end_layout
\end_inset
\end_layout
\begin_layout Itemize
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://www.pygtk.org/articles/cairo-pygtk-widgets/cairo-pygtk-widgets.htm
\end_layout
\end_inset
\end_layout
\begin_layout Itemize
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://www.pygtk.org/articles/cairo-pygtk-widgets/cairo-pygtk-widgets2.htm
\end_layout
\end_inset
\end_layout
\begin_layout Standard
Remember, if you want to see what is available in your cairo install, use
dir(cairo) from within python to see what is available.
\end_layout
\begin_layout LyX-Code
import cairo
\end_layout
\begin_layout LyX-Code
dir(cairo)
\end_layout
\begin_layout Chapter
Printing
\end_layout
\begin_layout Standard
\begin_inset Box Frameless
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "100col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
\begin_inset CommandInset include
LatexCommand input
filename "chapter-heading.lyx"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Standard
A requirement for printing with PyGTK is cairo so it will be helpful to
read the chapter on cairo first.
However it is only necessary if you wish to know what is going on.
If all you want is quick and easy printing than this chapter by itself
should suffice.
\end_layout
\begin_layout Standard
Cairo is not only used for drawing pretty pictures.
It can be used with PyGTK to print documents or whatever it is you wish
to print.
\end_layout
\begin_layout Section
Print Example
\end_layout
\begin_layout Standard
This chapter will provide a simple python class that takes as arguments:
\end_layout
\begin_layout Itemize
action - The action to be performed (see section
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Cario - Print Actions"
\end_inset
)
\end_layout
\begin_layout Itemize
data - print the provided string
\end_layout
\begin_layout Itemize
filename - open a text file to be printed
\end_layout
\begin_layout Standard
To use the PrintExample class all you have to do is create an instance specifyin
g some data to print and the type of print action that is to be taken.
For example lets say that the text
\begin_inset Quotes eld
\end_inset
This text is Printed
\begin_inset Quotes erd
\end_inset
is to be printed with a print dialog being opened to the user, then the
following code would be used.
\end_layout
\begin_layout LyX-Code
printer = PrintExample(gtk.PRINT_OPERATION_ACTION_PRINT_DIALOG,
\end_layout
\begin_layout LyX-Code
\begin_inset Quotes eld
\end_inset
This text is Printed
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout Standard
Inside the __init__ method of the PrintExample class the paper size(see
section
\begin_inset CommandInset ref
LatexCommand vref
reference "sub:Cario - Paper Sizes"
\end_inset
) is set, page setup information is created, and a print operation is initiated.
\end_layout
\begin_layout LyX-Code
class PrintExample:
\end_layout
\begin_layout LyX-Code
def __init__(self, action=None, data=None, filename=None):
\end_layout
\begin_layout LyX-Code
self.text = data
\end_layout
\begin_layout LyX-Code
self.layout = None
\end_layout
\begin_layout LyX-Code
self.font_size=12
\end_layout
\begin_layout LyX-Code
self.lines_per_page=0
\end_layout
\begin_layout LyX-Code
if action==None:
\end_layout
\begin_layout LyX-Code
action = gtk.PRINT_OPERATION_ACTION_PREVIEW
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
paper_size = gtk.PaperSize(gtk.PAPER_NAME_A4)
\end_layout
\begin_layout LyX-Code
setup = gtk.PageSetup()
\end_layout
\begin_layout LyX-Code
setup.set_paper_size(paper_size)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
print_ = gtk.PrintOperation()
\end_layout
\begin_layout LyX-Code
print_.set_default_page_setup(setup)
\end_layout
\begin_layout LyX-Code
print_.set_unit(gtk.UNIT_MM)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
print_.connect("begin_print", self.begin_print)
\end_layout
\begin_layout LyX-Code
print_.connect("draw_page", self.draw_page)
\end_layout
\begin_layout LyX-Code
if action == gtk.PRINT_OPERATION_ACTION_EXPORT:
\end_layout
\begin_layout LyX-Code
print_.set_export_filename(filename)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
response = print_.run(action)
\end_layout
\begin_layout Standard
So first off in the __init__ method a few instance variables are created.
\end_layout
\begin_layout Description
self.text: Is used to hold the data that is to be printed
\end_layout
\begin_layout Description
self.layout: Is used to hold an pango layout instance
\end_layout
\begin_layout Description
self.font_size: Is used to hold the font size that will be use with the layout
with a pango.FontDescription instance
\end_layout
\begin_layout Description
self.lines_per_page: Is used to store how many lines are available per page
\end_layout
\begin_layout Standard
Next it checks to see what action as been set.
If there is no action it will set as default to show a print preview.
\end_layout
\begin_layout LyX-Code
action = gtk.PRINT_OPERATION_ACTION_PREVIEW
\end_layout
\begin_layout Standard
Next an instance of the gtk.PaperSize class is created with a paper type
of gtk.PAPER_NAME_A4 and is assigned to the variable
\emph on
paper_size
\emph default
.
After this an instance of gtk.PageSetup is created and has a page size set
by the just created instance of gtk.PaperSize
\emph on
paper_size
\emph default
.
\end_layout
\begin_layout Standard
The print operation instance is assigned to the variable print_ using the
gtk.PrintOperation class.
It uses the print setup created above and sets the unit size to millimeters.
\end_layout
\begin_layout LyX-Code
print_.set_default_page_setup(setup)
\end_layout
\begin_layout LyX-Code
print_.set_unit(gtk.UNIT_MM)
\end_layout
\begin_layout Standard
It then connects the signals needed to print to their methods in the PrintExampl
e class.
The needed signals are
\emph on
begin_print
\emph default
and
\emph on
draw_page
\emph default
.
The begin_print signal calls a method that sets up the needed information
for the print operation.
The
\emph on
draw_page
\emph default
signal calls a method that uses the the information from the
\emph on
begin_print
\emph default
method to print each individual page.
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
print_.connect("begin_print", self.begin_print)
\end_layout
\begin_layout LyX-Code
print_.connect("draw_page", self.draw_page)
\end_layout
\begin_layout Standard
Lastly, if the print action is to export it also sets the filename that
it is to be exported.
\end_layout
\begin_layout Standard
As stated above the begin_print method is called with the begin_print signal
and will setup the information that is needed to print using the draw_page
method.
\end_layout
\begin_layout LyX-Code
def begin_print(self, operation, context):
\end_layout
\begin_layout LyX-Code
width = context.get_width()
\end_layout
\begin_layout LyX-Code
height = context.get_height()
\end_layout
\begin_layout LyX-Code
self.layout = context.create_pango_layout()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.layout.set_font_description(
\end_layout
\begin_layout LyX-Code
pango.FontDescription("Sans " + str(self.font_size)) )
\end_layout
\begin_layout LyX-Code
self.layout.set_width(int(width*pango.SCALE))
\end_layout
\begin_layout LyX-Code
self.layout.set_text(self.text)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
num_lines = self.layout.get_line_count()
\end_layout
\begin_layout LyX-Code
self.lines_per_page = math.floor(
\end_layout
\begin_layout LyX-Code
context.get_height() / (self.font_size/2) )
\end_layout
\begin_layout LyX-Code
pages = ( int(math.ceil( float(num_lines) /
\end_layout
\begin_layout LyX-Code
float(self.lines_per_page) ) ) )
\end_layout
\begin_layout LyX-Code
operation.set_n_pages(pages)
\end_layout
\begin_layout Standard
The begin_print method has the arguments
\emph on
operation
\emph default
and
\emph on
context
\emph default
.
The operation argument will be used to set the number of pages.
The context is used to get the information needed and create a pango layout.
Pango is the part of gtk that is used for fonts and is needed for setting
the font type, setting the width of the page and setting the text.
\end_layout
\begin_layout Standard
The the first two lines retrieve the width and the height of the of the
context argument (which is a cairo context).
It then creates a pango instance using the context.create_pango_layout()
method and assigns this to the class instance variable self.layout from
this point out obviously become a pango.Layout instance.
\end_layout
\begin_layout Standard
The next part now uses self.layout to set the font type to Sans 12.
The self.font_size is set as a class instance variable in the __init__ method
so that it can be used from both the begin_print and draw_page methods.
It sets the self.layout with to the cairo
\emph on
context
\emph default
width multiplied by the pango.SCALE constant (1024).
After this the text of the pango layout is then set to the text that is
held in the variable self.text; which was set in the __init__ method.
\end_layout
\begin_layout Standard
The number of lines in the whole document is retrieved with by calling self.layou
t.get_line_count().
The number of lines per page is calculated using the context height and
dividing by the font size.
The font size is divided by two so the lines are not spaced to far apart
\begin_inset Foot
status collapsed
\begin_layout Plain Layout
There is a different way to do this but I found this the easiest way to
start off with.
\end_layout
\end_inset
\begin_inset Note Note
status collapsed
\begin_layout Plain Layout
TODO: Add the second way to do the begin_print and draw_page methods using
1024(pango.SCALE) :)
\end_layout
\end_inset
.
\end_layout
\begin_layout Standard
The number pages is calculated by dividing the number of lines in the whole
document by the number of lines per page.
It then sets the number pages by calling the operation.set_n_pages method.
\end_layout
\begin_layout Standard
The draw_page method is called directly after the begin_print method.
It uses the information that was stored in class instance variables and
in the operation argument to print each page.
It also has the argument page_number.
This holds the current page number that is being printed.
Remember that the draw_page method is not called once, it is called once
for each page that is to be printed.
\end_layout
\begin_layout LyX-Code
def draw_page (self, operation, context, page_number):
\end_layout
\begin_layout LyX-Code
cr = context.get_cairo_context()
\end_layout
\begin_layout LyX-Code
cr.set_source_rgb(0, 0, 0)
\end_layout
\begin_layout LyX-Code
start_line = page_number * self.lines_per_page
\end_layout
\begin_layout LyX-Code
if page_number + 1 != operation.props.n_pages:
\end_layout
\begin_layout LyX-Code
end_line = start_line + self.lines_per_page
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
end_line = self.layout.get_line_count()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
cr.move_to(0, 0)
\end_layout
\begin_layout LyX-Code
iter = self.layout.get_iter()
\end_layout
\begin_layout LyX-Code
i=0
\end_layout
\begin_layout LyX-Code
while 1:
\end_layout
\begin_layout LyX-Code
if i > start_line:
\end_layout
\begin_layout LyX-Code
line = iter.get_line()
\end_layout
\begin_layout LyX-Code
cr.rel_move_to(0, self.font_size/2)
\end_layout
\begin_layout LyX-Code
cr.show_layout_line(line)
\end_layout
\begin_layout LyX-Code
i += 1
\end_layout
\begin_layout LyX-Code
if not (i < end_line and iter.next_line()):
\end_layout
\begin_layout LyX-Code
break
\end_layout
\begin_layout Standard
First off the draw_page method creates a cairo context by calling context.get_cai
ro_context().
The context is assigned to cr.
It then sets the color of the text to black using cr.set_source_rgb(0, 0,
0).
After this the starting line for the current page to print is calculated
by multiplying the current page by the number of lines per page.
\end_layout
\begin_layout Standard
It then calculates the last line that is on the page.
If it is not the last page of the document the last line is the start line
plus the lines per page.
If it is the last page to be printed the end line is the line count of
the whole document.
\end_layout
\begin_layout LyX-Code
if page_number + 1 != operation.props.n_pages:
\end_layout
\begin_layout LyX-Code
end_line = start_line + self.lines_per_page
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
end_line = self.layout.get_line_count()
\end_layout
\begin_layout Standard
With this information the method is now able to draw the text using cairo.
The context is set to the upper most left part of the page using
\emph on
cr.move_to(0, 0)
\emph default
.
\end_layout
\begin_layout Standard
It creates an iter of the layout that is used to iterate through each line
of the document that is left.
A while loop is used to move through the lines.
Each time through the while loop the variable i is incremented.
Once I is greater than the start line, that was calculated for this page,
the line is retrieved using
\emph on
iter.get_line()
\emph default
.
The context is moved relative to its current position by the font size
divided by two.
Then the text is drawn to the context using the cr.show_layout_line method.
\end_layout
\begin_layout Standard
Once the variable is as incremented to a greater value then the end line,
or there are no more lines in the iter to iterate through, break is called
ending the while loop and exiting the draw_page method.
\end_layout
\begin_layout Section
Print Actions
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Cario - Print Actions"
\end_inset
\end_layout
\begin_layout Standard
There are several print actions that can be used with printing.
\end_layout
\begin_layout Description
gtk.PRINT_OPERATION_ACTION_PREVIEW Show the print preview
\end_layout
\begin_layout Description
gtk.PRINT_OPERATION_ACTION_EXPORT Export to a file.
This requires the "export-filename" property to be set
\end_layout
\begin_layout Description
gtk.PRINT_OPERATION_ACTION_PRINT_DIALOG Show the print dialog
\end_layout
\begin_layout Description
gtk.PRINT_OPERATION_ACTION_PRINT Start printing immediately without showing
the print dialog.
Based on the current print settings.
\end_layout
\begin_layout Section
Paper Sizes
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:Cario - Paper Sizes"
\end_inset
\end_layout
\begin_layout Standard
There are several different predefined paper sizes that can be used with
PyGTK printing.
These are listed below.
There is also the possibility to use a custom paper size, but this is not
discussed here.
\end_layout
\begin_layout Description
gtk.PAPER_NAME_A3 Name for the A3 paper size.
\end_layout
\begin_layout Description
gtk.PAPER_NAME_A4 Name for the A4 paper size.
\end_layout
\begin_layout Description
gtk.PAPER_NAME_A5 Name for the A5 paper size.
\end_layout
\begin_layout Description
gtk.PAPER_NAME_B5 Name for the B5 paper size.
\end_layout
\begin_layout Description
gtk.PAPER_NAME_LETTER Name for the Letter paper size.
\end_layout
\begin_layout Description
gtk.PAPER_NAME_EXECUTIVE Name for the Executive paper size.
\end_layout
\begin_layout Description
gtk.PAPER_NAME_LEGAL for the Legal paper size.
\end_layout
\begin_layout Section
Summary
\end_layout
\begin_layout Standard
In summary, printing using cairo sucks but at least it is not to bad.
\end_layout
\begin_layout Chapter
Gnome Desktop Integration
\end_layout
\begin_layout Standard
\begin_inset Box Frameless
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "100col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
\begin_inset CommandInset include
LatexCommand input
filename "chapter-heading.lyx"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset Branch SecondEdition
status open
\begin_layout Section
Storing Passwords
\end_layout
\begin_layout Standard
This section is not yet written :)
\end_layout
\begin_layout Standard
keyring
\end_layout
\end_inset
\end_layout
\begin_layout Section
GConfig
\end_layout
\begin_layout Standard
Save your applications configuration file using GConfig.
This example is based off the gconfig-basic-app.py file that comes with
the pygtk source code, I have changed it into something I find easier to
understand.
\end_layout
\begin_layout LyX-Code
import gconf, gobject, gtk
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
class GConfigExample:
\end_layout
\begin_layout LyX-Code
def __init__(self):
\end_layout
\begin_layout LyX-Code
client = gconf.client_get_default()
\end_layout
\begin_layout LyX-Code
client.add_dir ("/apps/pygtk-book-gconf-example-app",
\end_layout
\begin_layout LyX-Code
gconf.CLIENT_PRELOAD_NONE)
\end_layout
\begin_layout Standard
Before even creating the gtk window, get the default gconf client, then
tell the gconf client that we are interested in the given directory.
This means the gconf client will receive notification of changes to this
directory, and will also cache keys under this directory.
To avoid getting a copy of the whole gconf database do not add
\begin_inset Quotes eld
\end_inset
/
\begin_inset Quotes erd
\end_inset
as that would specify the entire database.
Also gconf.CLIENT_PRELOAD_NONE is used to avoid loading all config keys
on startup.
If the application reads all the config keys on startup, then preloading
the cache may make sense, otherwise preload none is the way to go.
\end_layout
\begin_layout Standard
After setting up the initial gconf code the gtk window is created.
\end_layout
\begin_layout LyX-Code
self.window = gtk.Window()
\end_layout
\begin_layout LyX-Code
self.window.set_title(
\begin_inset Quotes eld
\end_inset
GConfig Example
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
vbox = gtk.VBox(False, 5)
\end_layout
\begin_layout LyX-Code
self.window.add(vbox)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout Standard
Next, the program will have eight labels that will show the database directory
path as well as the value that is being stored.
The method create_configurable_widget is used to create, display, and
hook up the labels to be updated on changes to the gconf database.
\end_layout
\begin_layout LyX-Code
config = self.create_configurable_widget(client,
\end_layout
\begin_layout LyX-Code
"/apps/pygtk-book-gconf-example-app/foo")
\end_layout
\begin_layout LyX-Code
vbox.pack_start(config, True, True)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
config = self.create_configurable_widget(client,
\end_layout
\begin_layout LyX-Code
"/apps/pygtk-book-gconf-example-app/bar")
\end_layout
\begin_layout LyX-Code
vbox.pack_start(config, True, True)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
config = self.create_configurable_widget(client,
\end_layout
\begin_layout LyX-Code
"/apps/pygtk-book-gconf-example-app/baz")
\end_layout
\begin_layout LyX-Code
vbox.pack_start (config, True, True)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
config = self.create_configurable_widget(client,
\end_layout
\begin_layout LyX-Code
"/apps/pygtk-book-gconf-example-app/blah")
\end_layout
\begin_layout LyX-Code
vbox.pack_start(config, True, True)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.window.connect(
\begin_inset Quotes eld
\end_inset
delete_event
\begin_inset Quotes erd
\end_inset
, lambda wid, we: gtk.main_quit())
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout Standard
Here we use the set_data method on the applications main window, setting
the key to
\begin_inset Quotes eld
\end_inset
client
\begin_inset Quotes erd
\end_inset
and the value to the gconf object that was created abouve;
\emph on
client
\emph default
.
As well a preferences button is created and added to the window.
The preferences button will open a preference dialog that will edit the
gconfig entries directly and does not interact at all with the GConfigExample
class that shows reading from gconf.
\end_layout
\begin_layout LyX-Code
self.window.set_data (
\begin_inset Quotes eld
\end_inset
client
\begin_inset Quotes erd
\end_inset
, client)
\end_layout
\begin_layout LyX-Code
prefs_button = gtk.Button ("Preferences")
\end_layout
\begin_layout LyX-Code
vbox.pack_end (prefs_button, False, False)
\end_layout
\begin_layout LyX-Code
prefs_button.connect (
\begin_inset Quotes eld
\end_inset
clicked
\begin_inset Quotes erd
\end_inset
, self.prefs_button_clicked_callback)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.window.show_all()
\end_layout
\begin_layout Standard
Once the widget monitoring notification that was created in the create_configura
ble_widget method is destroyed, the notification callback is removed.
\end_layout
\begin_layout LyX-Code
def configurable_widget_destroy_callback(self, widget):
\end_layout
\begin_layout LyX-Code
client = widget.get_data(
\begin_inset Quotes eld
\end_inset
client
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
notify_id = widget.get_data(
\begin_inset Quotes eld
\end_inset
notify_id
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if notify_id:
\end_layout
\begin_layout LyX-Code
client.notify_remove (notify_id)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout Standard
Here there is a notification callback for the value label widgets that monitor
the current value of a gconf key, when a gconf value is changed so is the
label within the program.
Note that the
\emph on
value
\emph default
can be None (unset) or it can have the wrong type.
The program needs to check to make sure it can survive
\emph on
gconftool --break-key
\emph default
.
\end_layout
\begin_layout LyX-Code
def configurable_widget_config_notify(self, client, cnxn_id, entry, label):
\end_layout
\begin_layout LyX-Code
if not entry.value:
\end_layout
\begin_layout LyX-Code
label.set_text(
\begin_inset Quotes eld
\end_inset
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
elif entry.value.type == gconf.VALUE_STRING:
\end_layout
\begin_layout LyX-Code
label.set_text( entry.value.to_string() )
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
label.set_text(
\begin_inset Quotes eld
\end_inset
!type error!
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout Standard
This is the create_configurable_widget method that creates the labels that
are displayed.
Each gconf database directory will have a label to show the location as
well as one label to show the value.
\end_layout
\begin_layout LyX-Code
def create_configurable_widget(self, client, config_key):
\end_layout
\begin_layout LyX-Code
hbox = gtk.HBox(True)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
key_label = gtk.Label(config_key + ": ")
\end_layout
\begin_layout LyX-Code
label = gtk.Label (
\begin_inset Quotes eld
\end_inset
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
hbox.pack_start(key_label)
\end_layout
\begin_layout LyX-Code
hbox.pack_start(label)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
s = client.get_string(config_key)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if s:
\end_layout
\begin_layout LyX-Code
label.set_text(s)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
notify_id = client.notify_add(config_key, self.configurable_widget_config_noti
fy, label)
\end_layout
\begin_layout Standard
It should be noted here that notify_id will be 0 if there is an error, so
that is handled in the destroy callback.
\end_layout
\begin_layout LyX-Code
label.set_data(
\begin_inset Quotes eld
\end_inset
notify_id
\begin_inset Quotes erd
\end_inset
, notify_id)
\end_layout
\begin_layout LyX-Code
label.set_data(
\begin_inset Quotes eld
\end_inset
client
\begin_inset Quotes erd
\end_inset
, client)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
label.connect(
\begin_inset Quotes eld
\end_inset
destroy
\begin_inset Quotes erd
\end_inset
, self.configurable_widget_destroy_callback)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
return hbox
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def prefs_button_clicked_callback(self, widget):
\end_layout
\begin_layout LyX-Code
client = self.window.get_data(
\begin_inset Quotes eld
\end_inset
client
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
prefs_dialog = EditConfigValues(client)
\end_layout
\begin_layout Standard
Next is the code for the preference dialog.
the code will be in the EditConfigValues class.
It is important to know that the preference dialog will never directly
edit any values in the main window, it will only edit values in the gconf
database.
This is to test that the program works correctly as sometimes the values
will be edited using gconf-editor instead of the applications preference
window.
\end_layout
\begin_layout LyX-Code
class EditConfigValues:
\end_layout
\begin_layout LyX-Code
def __init__(self, client):
\end_layout
\begin_layout LyX-Code
self.dialog = gtk.Dialog ("GConfig Example Preferences",
None, 0, (gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT))
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.dialog.connect('response', lambda wid,ev: wid.destroy ())
\end_layout
\begin_layout LyX-Code
self.dialog.set_default_response (gtk.RESPONSE_ACCEPT)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
vbox = gtk.VBox(False, 5)
\end_layout
\begin_layout Standard
Create four labels and four text entries that are used to display the gconf
location as well as the current value in an entry area, this is accomplished
using the create_config_entry method.
\end_layout
\begin_layout LyX-Code
self.dialog.vbox.pack_start(vbox)
\end_layout
\begin_layout LyX-Code
entry = self.create_config_entry(client,
\end_layout
\begin_layout LyX-Code
"/apps/pygtk-book-gconf-example-app/foo", True)
\end_layout
\begin_layout LyX-Code
vbox.pack_start (entry, False, False)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
entry = self.create_config_entry(client,
\end_layout
\begin_layout LyX-Code
"/apps/pygtk-book-gconf-example-app/bar")
\end_layout
\begin_layout LyX-Code
vbox.pack_start (entry, False, False)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
entry = self.create_config_entry (client,
\end_layout
\begin_layout LyX-Code
"/apps/pygtk-book-gconf-example-app/baz")
\end_layout
\begin_layout LyX-Code
vbox.pack_start (entry, False, False)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
entry = self.create_config_entry (client,
\end_layout
\begin_layout LyX-Code
"/apps/pygtk-book-gconf-example-app/blah")
\end_layout
\begin_layout LyX-Code
vbox.pack_start (entry, False, False)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.dialog.show_all()
\end_layout
\begin_layout Standard
The config_entry_commit method does as its names says and commits changes
to the gconf database.
If the
\emph on
text
\emph default
string is zero-length it is unset, otherwise it is set.
\end_layout
\begin_layout LyX-Code
def config_entry_commit(self, entry, *args):
\end_layout
\begin_layout LyX-Code
client = entry.get_data(
\begin_inset Quotes eld
\end_inset
client
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
text = entry.get_chars(0, -1)
\end_layout
\begin_layout LyX-Code
key = entry.get_data ('key')
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if text:
\end_layout
\begin_layout LyX-Code
client.set_string(key, text)
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
client.unset(key)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout Standard
The create_config_entry method takes as arguments the gconf client, the
config key that is to be created, as well as whether the text entry has
focus.
This method creates a label that shows the config key and a text entry
that shows the value.
Editing the text entry changes the value of the gconf value.
\end_layout
\begin_layout LyX-Code
def create_config_entry(self, client, config_key, focus=False):
\end_layout
\begin_layout LyX-Code
hbox = gtk.HBox(False, 5)
\end_layout
\begin_layout LyX-Code
label = gtk.Label(config_key)
\end_layout
\begin_layout LyX-Code
entry = gtk.Entry()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
hbox.pack_start(label, False, False, 0)
\end_layout
\begin_layout LyX-Code
hbox.pack_end(entry, False, False, 0)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout Standard
Calling client.get_string(config_key) will print an error via the default
error handler if the key is not set to a string.
\end_layout
\begin_layout LyX-Code
s = client.get_string(config_key)
\end_layout
\begin_layout LyX-Code
if s:
\end_layout
\begin_layout LyX-Code
entry.set_text(s)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
entry.set_data(
\begin_inset Quotes eld
\end_inset
client
\begin_inset Quotes erd
\end_inset
, client)
\end_layout
\begin_layout LyX-Code
entry.set_data(
\begin_inset Quotes eld
\end_inset
key
\begin_inset Quotes erd
\end_inset
, config_key)
\end_layout
\begin_layout Standard
The changes will be commited if the user moves focus away from the text
entry they are in, or if they hit enter; Changes are not commited on the
\emph on
changed
\emph default
signal as that would mean every new character entered would be sent, instead
it waits for the user to finish first.
Finally if the gconf client key is not writable the text entry is set to
not writtable.
\end_layout
\begin_layout LyX-Code
entry.connect(
\begin_inset Quotes eld
\end_inset
focus_out_event
\begin_inset Quotes erd
\end_inset
, self.config_entry_commit)
\end_layout
\begin_layout LyX-Code
entry.connect (
\begin_inset Quotes eld
\end_inset
activate
\begin_inset Quotes erd
\end_inset
, self.config_entry_commit)
\end_layout
\begin_layout LyX-Code
entry.set_sensitive( client.key_is_writable(config_key) )
\end_layout
\begin_layout LyX-Code
if focus:
\end_layout
\begin_layout LyX-Code
entry.grab_focus()
\end_layout
\begin_layout LyX-Code
return hbox
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
GConfigExample()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Section
PyGobject
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sec:PyGObject"
\end_inset
\end_layout
\begin_layout Standard
I am not going to cover very much in this section because that would be
a lot, maybe in a later verion.
For now this section will cover one useful function.
For more information check out its documentation at
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://pygtk.org/docs/pygobject/index.html
\end_layout
\end_inset
.
\end_layout
\begin_layout Description
gobject.timeout_add(interval,callback) is a function that will call the function
specified in the callback as often as is specified by the interval until
the callback function returns False.
\end_layout
\begin_layout List
\labelwidthstring 00.00.0000
Interval The number of seconds between calls.
Eg.
1 for one second, 100 for 100 seconds.
That is pretty simple
\end_layout
\begin_layout List
\labelwidthstring 00.00.0000
Callback The function that will be called at each interval.
\end_layout
\begin_layout Standard
I find this is a useful function to use when I want to periodically check
to see if a long running process has finished.
Another good example is lets say you have a music player with a progress
bar, once a second while a song is playing you would want to update the
progress bar.
To do this you could setup a
\emph on
gobject.timeout_add
\emph default
to call an update function that checks the position of the currently playing
song and update the progress bar with that information.
\end_layout
\begin_layout Standard
\begin_inset Branch SecondEdition
status open
\begin_layout Section
Gnome Virtual Filesystems
\end_layout
\begin_layout Standard
This section is not yet written :)
\end_layout
\begin_layout Standard
The new GVFS/GIO stuff
\end_layout
\end_inset
\end_layout
\begin_layout Section
Gnome Menus (.desktop files)
\end_layout
\begin_layout Standard
If an application is to be added to the main menu it will need an appname.desktop
\begin_inset Index
status collapsed
\begin_layout Plain Layout
desktop
\end_layout
\end_inset
file with details about the application.
The .desktop file will hold various information about the application including
the name, how to execute it, tool tip comment, icon, category and more.
\end_layout
\begin_layout Subsection
Keys
\end_layout
\begin_layout Standard
The way that a .desktop file holds information is with keys
\begin_inset Index
status collapsed
\begin_layout Plain Layout
desktop!keys
\end_layout
\end_inset
.
There are several keys and a few of them are required.
The required keys are:
\end_layout
\begin_layout Itemize
Type
\begin_inset Index
status collapsed
\begin_layout Plain Layout
desktop!Type
\end_layout
\end_inset
- Application, Link, Directory
\end_layout
\begin_layout Itemize
Name
\begin_inset Index
status collapsed
\begin_layout Plain Layout
desktop!Name
\end_layout
\end_inset
- The name of the application and what will show up in the menu
\end_layout
\begin_layout Itemize
Exec
\begin_inset Index
status collapsed
\begin_layout Plain Layout
desktop!Exec
\end_layout
\end_inset
- The program to execute with arguments
\end_layout
\begin_layout Itemize
URL
\begin_inset Index
status collapsed
\begin_layout Plain Layout
desktop!URL
\end_layout
\end_inset
- Only required if the entry is a Link Type
\end_layout
\begin_layout Standard
There are several other keys besides the required ones.
To see what is available visit the .desktop files specification web page
\begin_inset Foot
status collapsed
\begin_layout Plain Layout
For more information on keys that can be used with .desktop files please
visit:
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s05.html
\end_layout
\end_inset
\end_layout
\end_inset
.
\end_layout
\begin_layout Standard
.desktop Example
\end_layout
\begin_layout LyX-Code
[Desktop Entry]
\end_layout
\begin_layout LyX-Code
Version=1.0
\end_layout
\begin_layout LyX-Code
Encoding=UTF-8
\end_layout
\begin_layout LyX-Code
Name=Hello World
\end_layout
\begin_layout LyX-Code
GenericName=Display Hello World
\end_layout
\begin_layout LyX-Code
Comment=This is my first PyGTK application
\end_layout
\begin_layout LyX-Code
X-MultipleArgs=false
\end_layout
\begin_layout LyX-Code
Type=Application
\end_layout
\begin_layout LyX-Code
TryExec=helloworld
\end_layout
\begin_layout LyX-Code
Exec=helloworld
\end_layout
\begin_layout LyX-Code
Categories=Utility
\end_layout
\begin_layout LyX-Code
Icon=helloworld
\end_layout
\begin_layout Standard
Save this example as helloworld.desktop.
When it is viewed with a file manger it will show up as
\begin_inset Quotes eld
\end_inset
Hello World
\begin_inset Quotes erd
\end_inset
because that is what the Name key is set to.
\end_layout
\begin_layout Standard
This Example sets up a .desktop with a version of 1.0.
The encoding type is UTF-8.
The GenericName is a generic name to describe the application.
It is assigned to the Application type.
It will try to execute helloworld.
The category is Utility.
The utility category means that it will be placed in the Accessories category
in the menu.
The comment is the tooltip for the that will be displayed on hovering over
it.
And last the icon is set to helloworld.
\end_layout
\begin_layout Standard
When using Icons it must be set to the absolute path or be installed in
a location that it is able to be found.
This helloworld icon is a image with the name helloworld.png and can be
found on the books website.
Supported icon image types are png, xpm and svg.
\end_layout
\begin_layout Subsection
Category Information
\end_layout
\begin_layout Standard
Included in the keys that can be used with a .desktop file is the category
key.
The category is the category that the Application, Link, or Directory will
be included under.
If for example we have an Application and it is in the Office category;
then when the main menu is opened and the office subcategory is opened
the application will show up there.
\end_layout
\begin_layout Standard
Here is a list of the default categories.
More categories can be found on the menu specification web page
\begin_inset Foot
status collapsed
\begin_layout Plain Layout
If you would like more information on categories please visit:
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://standards.freedesktop.org/menu-spec/menu-spec-1.0.html
\end_layout
\end_inset
\end_layout
\end_inset
.
\end_layout
\begin_layout Itemize
AudioVideo - A multimedia (audio/video) application
\end_layout
\begin_layout Itemize
Audio - An audio application Desktop entry must include AudioVideo as well
\end_layout
\begin_layout Itemize
Video - A video application Desktop entry must include AudioVideo as well
\end_layout
\begin_layout Itemize
Development - An application for development
\end_layout
\begin_layout Itemize
Education - Educational software
\end_layout
\begin_layout Itemize
Game - A game
\end_layout
\begin_layout Itemize
Graphics - Graphical application
\end_layout
\begin_layout Itemize
Network - Network application such as a web browser
\end_layout
\begin_layout Itemize
Office - An office type application
\end_layout
\begin_layout Itemize
Settings - Settings applications Entries may appear in a separate menu or
as part of a "Control Center"
\end_layout
\begin_layout Itemize
System - System application, "System Tools" such as say a log viewer or
network monitor
\end_layout
\begin_layout Itemize
Utility - Small utility application, "Accessories"
\end_layout
\begin_layout Subsection
Installing and Using .desktop files
\end_layout
\begin_layout Standard
Creating a .desktop file without installing
\begin_inset Index
status collapsed
\begin_layout Plain Layout
desktop!installing
\end_layout
\end_inset
is pointless.
It must be installed to be used.
This section is going to use a small sample PyGTK application with a .desktop
file to show how they work together.
Then a small shell script will be created to install or uninstall the applicati
on, application data, and related .desktop file.
\end_layout
\begin_layout Standard
First lets create a small python program that is the main file to create
the GUI and a second python file that will only have one function that
returns a small message.
These two files are used to show how it can be installed and set the path
in the main python file to the correct install location of the supporting
python modules that are included in the application
\begin_inset Foot
status collapsed
\begin_layout Plain Layout
A better way would probably be to install all the files to the library directory
including the main python file.
Then install a shell script to the binary directory that looks for and
launches the directory.
This way it does not need to append the to the system path the location
of the applications python modules.
\end_layout
\end_inset
.
\end_layout
\begin_layout LyX-Code
#!/usr/bin/env python
\end_layout
\begin_layout LyX-Code
import sys
\end_layout
\begin_layout LyX-Code
sys.path.append("/usr/local/lib/helloworld")
\end_layout
\begin_layout LyX-Code
import gtk
\end_layout
\begin_layout LyX-Code
import helloworld_message
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if __name__ == '__main__':
\end_layout
\begin_layout LyX-Code
win = gtk.Window()
\end_layout
\begin_layout LyX-Code
win.connect("delete_event", lambda w,e: gtk.main_quit())
\end_layout
\begin_layout LyX-Code
label = gtk.Label(helloworld_message.message())
\end_layout
\begin_layout LyX-Code
win.add(label)
\end_layout
\begin_layout LyX-Code
win.show_all()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Standard
At the very top of this example sys is import and the location /usr/local/lib/he
lloworld is append to the system path.
The reason this is done is because this is where all the applications modules
will be installed.
If it does not append this directory then importing the helloworld_message
module will fail.
\end_layout
\begin_layout Standard
The helloworld_message.py file only contains one function and is only two
lines long.
\end_layout
\begin_layout LyX-Code
def message():
\end_layout
\begin_layout LyX-Code
return ".desktop example program"
\end_layout
\begin_layout Standard
Now that there is a working application and a desktop file that was created
above it is time to install everything.
For the purposes of installing the helloworld.desktop, helloworld.py, and
helloworld_message.py files a bash shell script will be used.
\end_layout
\begin_layout Standard
The shell script will take one argument that may be either --install or
--uninstall.
Anything other then that will display how to use this shell script.
This script has been kept very simple so that it will be easy to understand.
\end_layout
\begin_layout Standard
To start off lets cover the beginning of the script.
\end_layout
\begin_layout LyX-Code
#!/bin/bash
\end_layout
\begin_layout LyX-Code
# Get script directory path.
\end_layout
\begin_layout LyX-Code
scriptdir="`dirname ${0}`"
\end_layout
\begin_layout LyX-Code
DESTDIR="${DESTDIR:-}"
\end_layout
\begin_layout Standard
These first few lines set the shell script to be run by bash and set the
variables
\begin_inset Quotes eld
\end_inset
scriptdir
\begin_inset Quotes erd
\end_inset
and
\begin_inset Quotes eld
\end_inset
DESTDIR
\begin_inset Quotes erd
\end_inset
.
\end_layout
\begin_layout Standard
Next is the installation function.
This function will install the main python file as a binary and the supporting
python modules and data files.
\end_layout
\begin_layout LyX-Code
install_program() # arg1=bindir, arg2=datadir, arg3=pkglibdir,
\end_layout
\begin_layout LyX-Code
# arg4=pkgdatadir, arg5=pkgdocdir.
\end_layout
\begin_layout LyX-Code
{
\end_layout
\begin_layout LyX-Code
echo ${DESTDIR}
\end_layout
\begin_layout LyX-Code
# Install binary data - /usr/local/bin/helloworld
\end_layout
\begin_layout LyX-Code
install -m 755 -d "${DESTDIR}${1}"
\end_layout
\begin_layout LyX-Code
install -m 755 "${scriptdir}/helloworld.py" "${DESTDIR}${1}/helloworld"
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Install package library - /usr/local/lib/helloworld
\end_layout
\begin_layout LyX-Code
install -m 755 -d "${DESTDIR}${3}"
\end_layout
\begin_layout LyX-Code
install "${scriptdir}"/helloworld_*.py "${DESTDIR}${3}/"
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Install package data /usr/local/share/helloworld
\end_layout
\begin_layout LyX-Code
#install -m 755 -d "${DESTDIR}${4}"
\end_layout
\begin_layout LyX-Code
#install -m 644 "${scriptdir}/helloworld.png" "${DESTDIR}${4}/"
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Install data directory - /usr/local/share/pixmaps
\end_layout
\begin_layout LyX-Code
install -m 755 -d "${DESTDIR}${2}/pixmaps"
\end_layout
\begin_layout LyX-Code
install -m 644 "${scriptdir}/helloworld.png" "${DESTDIR}${2}/pixmaps/"
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# /usr/local/share/applications
\end_layout
\begin_layout LyX-Code
install -m 755 -d "${DESTDIR}${2}/applications"
\end_layout
\begin_layout LyX-Code
install -m 644 "${scriptdir}/helloworld.desktop"
\backslash
\end_layout
\begin_layout LyX-Code
"${DESTDIR}${2}/applications/"
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
echo "Finished Install"
\end_layout
\begin_layout LyX-Code
}
\end_layout
\begin_layout Standard
This function takes five arguments that specifiy where the binary, data,
library, package data, and documentation are to be installed.
It installs the helloworld.py file to /usr/local/bin/helloworld so it may
be run by executing helloworld.
It then install all python files that start with
\begin_inset Quotes eld
\end_inset
helloworld_
\begin_inset Quotes erd
\end_inset
to the /usr/local/lib/helloworld directory.
If there were any data files they would be installed to /usr/local/share/hellow
orld directory, but since there were none those lines are commented out(they
are only using the helloworld.png file as an example).
\end_layout
\begin_layout Standard
The helloworld.png file is installed to the /usr/local/share/pixmaps directory,
making it usable as an icon from the helloworld.desktop file.
And at the very last, helloworld.desktop is installed to the /usr/local/share/ap
plications directory.
Once this is completed the the helloworld application should show up in
the menu (Applications -> Accessories -> Hello World).
\end_layout
\begin_layout Standard
The next and last function in the install script is used to uninstall the
helloworld application and is much smaller then the install function.
\end_layout
\begin_layout LyX-Code
uninstall_program() # arg1=bindir, arg2=datadir, arg3=pkglibdir,
\end_layout
\begin_layout LyX-Code
# arg4=pkgdatadir, arg5=pkgdocdir.
\end_layout
\begin_layout LyX-Code
{
\end_layout
\begin_layout LyX-Code
rm -f "${DESTDIR}${1}/helloworld"
\end_layout
\begin_layout LyX-Code
rm -f "${DESTDIR}${1}/helloworld.py"
\end_layout
\begin_layout LyX-Code
rm -rf "${DESTDIR}${3}"
\end_layout
\begin_layout LyX-Code
rm -rf "${DESTDIR}${4}"
\end_layout
\begin_layout LyX-Code
rm -rf "${DESTDIR}${5}"
\end_layout
\begin_layout LyX-Code
rm -f "${DESTDIR}${2}/pixmaps/helloworld.png"
\end_layout
\begin_layout LyX-Code
rm -f "${DESTDIR}${2}/applications/helloworld.desktop"
\end_layout
\begin_layout LyX-Code
echo "Finished Uninstall"
\end_layout
\begin_layout LyX-Code
}
\end_layout
\begin_layout Standard
The uninstall function deletes all the files that were installed and all
the directories that were created by the install function.
This is very simple and there is no more to say about it.
\end_layout
\begin_layout Standard
The last part is to read the arguments given to the shell script and call
the right function.
\end_layout
\begin_layout LyX-Code
# First arg to the script
\end_layout
\begin_layout LyX-Code
action=$1
\end_layout
\begin_layout LyX-Code
if test "$action" = --install
\end_layout
\begin_layout LyX-Code
then
\end_layout
\begin_layout LyX-Code
echo "install selected"
\end_layout
\begin_layout LyX-Code
install_program "/usr/local/bin"
\backslash
\end_layout
\begin_layout LyX-Code
"/usr/local/share"
\backslash
\end_layout
\begin_layout LyX-Code
"/usr/local/lib/helloworld"
\backslash
\end_layout
\begin_layout LyX-Code
"/usr/local/share/helloworld"
\backslash
\end_layout
\begin_layout LyX-Code
"/usr/local/share/doc/helloworld"
\end_layout
\begin_layout LyX-Code
elif test "$action" = --uninstall
\end_layout
\begin_layout LyX-Code
then
\end_layout
\begin_layout LyX-Code
echo "uninstall selected"
\end_layout
\begin_layout LyX-Code
uninstall_program "/usr/local/bin"
\backslash
\end_layout
\begin_layout LyX-Code
"/usr/local/share"
\backslash
\end_layout
\begin_layout LyX-Code
"/usr/local/lib/helloworld"
\backslash
\end_layout
\begin_layout LyX-Code
"/usr/local/share/helloworld"
\backslash
\end_layout
\begin_layout LyX-Code
"/usr/local/share/doc/helloworld"
\end_layout
\begin_layout LyX-Code
else
\end_layout
\begin_layout LyX-Code
echo ""
\end_layout
\begin_layout LyX-Code
echo "Usage:"
\end_layout
\begin_layout LyX-Code
echo " --install - Use this argument to install"
\end_layout
\begin_layout LyX-Code
echo " --uninstall - Use this argument to uninstall"
\end_layout
\begin_layout LyX-Code
echo ""
\end_layout
\begin_layout LyX-Code
fi
\end_layout
\begin_layout Standard
This part of the install script reads the first argument to it and assigns
it to the variable action.
Then action is tested to see if it should install, uninstall, or display
the accepted arguments.
\end_layout
\begin_layout Standard
That is all to creating a .desktop file for use with an application.
\end_layout
\begin_layout Standard
\begin_inset Branch SecondEdition
status open
\begin_layout Section
Mimetypes
\end_layout
\begin_layout Standard
This section is not yet written :)
\end_layout
\begin_layout Standard
mimetypes and adding mimetypes
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset Branch SecondEdition
status open
\begin_layout Section
CD and DVD Burning
\end_layout
\begin_layout Standard
This section is not yet written :)
\end_layout
\begin_layout Standard
nautilus-burn
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\end_layout
\begin_layout Chapter
Audio and Video Playback - GStreamer
\end_layout
\begin_layout Standard
\begin_inset Box Frameless
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "100col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
\begin_inset CommandInset include
LatexCommand input
filename "chapter-heading.lyx"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Section
Introduction
\end_layout
\begin_layout Standard
GStreamer is a multimedia framework that can be used from the simple to
the more advanced.
The possibilities range from playing a simple audio file or video file
to creating an advanced audio/video editor.
\end_layout
\begin_layout Standard
When you are finished reading this chapter you will be able to use a high
level playbin factory element to play audio and video, detect missing codecs
and automatically install them, discover the file information about your
audio or video files and apply all these to your very own PyGTK program.
\end_layout
\begin_layout Standard
This chapter does not cover any advanced topics but it does show you how
to very quickly add the ability to play audio or video to your own program.
\end_layout
\begin_layout Standard
Enjoy the journey.
\end_layout
\begin_layout Section
The Beginnings
\end_layout
\begin_layout Subsection
Playbin
\end_layout
\begin_layout Standard
The playbin
\begin_inset Index
status collapsed
\begin_layout Plain Layout
playbin
\end_layout
\end_inset
element is a very high level, automatic video/audio player.
It will automatically detect your multimedia file type and take the correct
actions for it to be played.
All that needs to be done to use it is to supply the playbin with a location
of a multimedia file and set the state of it to play.
\end_layout
\begin_layout Standard
\series bold
playbin Features:
\end_layout
\begin_layout Itemize
Audio and video output (
\begin_inset Quotes eld
\end_inset
audio-sink
\begin_inset Quotes erd
\end_inset
and
\begin_inset Quotes eld
\end_inset
video-sink
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout Itemize
Error Handling
\end_layout
\begin_layout Itemize
EOS handling(end of stream)
\end_layout
\begin_layout Itemize
State handling
\end_layout
\begin_layout Itemize
Seeking
\end_layout
\begin_layout Itemize
Buffers network sources
\end_layout
\begin_layout Itemize
Visualization for audio supported
\end_layout
\begin_layout Itemize
Subtitle support (
\begin_inset Quotes eld
\end_inset
suburi
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout Standard
\series bold
Playbin element:
\end_layout
\begin_layout Standard
What is needed in every GStreamer application is an element to play your
media with.
In the case of this chapter all that we are going to use is the
\begin_inset Quotes eld
\end_inset
playbin
\begin_inset Quotes erd
\end_inset
factory.
You create this using the gst.element_make_factory(factory, element_name)
\begin_inset Index
status collapsed
\begin_layout Plain Layout
GStreamer!element
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
make
\begin_inset ERT
status collapsed
\begin_layout Plain Layout
\backslash
_
\end_layout
\end_inset
factory
\end_layout
\end_inset
method like so:
\end_layout
\begin_layout LyX-Code
player_name = gst.element_make_factory(
\begin_inset Quotes eld
\end_inset
playbin
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
YourElementName
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout Standard
\series bold
Set location of the multimedia file:
\end_layout
\begin_layout Standard
Setting the location of the file is done with the player_name.set_property(
\begin_inset Quotes eld
\end_inset
uri
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
location
\begin_inset Quotes erd
\end_inset
) like so:
\end_layout
\begin_layout LyX-Code
player_name.set_property(
\begin_inset Quotes eld
\end_inset
uri
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
file:///home/peter/myvideo.avi
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout Standard
\series bold
Set state of the multimedia file:
\end_layout
\begin_layout Standard
Some states that the multimedia file may be set to include:
\end_layout
\begin_layout Itemize
gst.STATE_PLAYING -- Used to start playing
\end_layout
\begin_layout Itemize
gst.STATE_PAUSED -- Used to pause file
\end_layout
\begin_layout Itemize
gst.STATE_NULL -- Used to stop file
\end_layout
\begin_layout Standard
To set the state of the
\begin_inset Quotes eld
\end_inset
player_name
\begin_inset Quotes erd
\end_inset
just created above to play the file you would set_state(state) method like
so:
\end_layout
\begin_layout LyX-Code
player_name.set_state(gst.STATE_PLAYING)
\end_layout
\begin_layout Standard
To set the file to be paused you would:
\end_layout
\begin_layout LyX-Code
player_name.set_state(gst.STATE_PAUSED)
\end_layout
\begin_layout Standard
To altogether stop the file that is playing you would set the state like
so:
\end_layout
\begin_layout LyX-Code
player_name.set_state(gst.STATE_NULL)
\end_layout
\begin_layout Subsection
Bus - watching for GStreamer signals
\end_layout
\begin_layout Standard
The GStreamer
\begin_inset Index
status collapsed
\begin_layout Plain Layout
GStreamer!bus
\end_layout
\end_inset
bus
\begin_inset Index
status collapsed
\begin_layout Plain Layout
bus
\end_layout
\end_inset
is what allows for receiving signals from GStreamer.
It is important because it will allow your program to detect things such
as errors or the end of the audio or video stream.
\end_layout
\begin_layout Standard
When the end of stream is detected it is the programs responsibility to
set the state back to gst.STATE_NULL.
Otherwise if you try to load in another file or play the same file again
it will not play because the state is already set to gst.STATE_PLAYING.
\end_layout
\begin_layout Standard
The bus is not difficult to use and it will only add a few more lines to
the program and one extra function to handle the messages.
\end_layout
\begin_layout Standard
So if we have created a player bin using the gst.element_make_factory method
and have called it
\emph on
player_name
\emph default
then we can create a bus and watch it like so:
\end_layout
\begin_layout LyX-Code
bus = player_name.get_bus()
\end_layout
\begin_layout LyX-Code
bus.add_signal_watch()
\end_layout
\begin_layout LyX-Code
bus.connect(
\begin_inset Quotes eld
\end_inset
message
\begin_inset Quotes erd
\end_inset
, on_message)
\end_layout
\begin_layout Standard
This creates a bus from the player_name playbin, adds a signal watcher,
and connects the bus to send signals to the function on_message when messages
are detected.
\end_layout
\begin_layout Standard
The function message can detect whatever type of message that GStreamer
has but in the examples in this chapter it will focus on errors and detecting
when the end of stream has occurred so that the program will reset the
state to gst.STATE_NULL.
\end_layout
\begin_layout Standard
An example message function looks like this:
\end_layout
\begin_layout LyX-Code
def on_message(self, bus, message):
\end_layout
\begin_layout LyX-Code
# Detect end of stream and set state to to NULL
\end_layout
\begin_layout LyX-Code
if message.type == gst.MESSAGE_EOS:
\end_layout
\begin_layout LyX-Code
self.player_name.set_state(gst.STATE_NULL)
\end_layout
\begin_layout LyX-Code
elif message.type == gst.MESSAGE_ERROR:
\end_layout
\begin_layout LyX-Code
self.player_name.set_state(gst.STATE_NULL)
\end_layout
\begin_layout LyX-Code
(err, debug) = message.parse_error()
\end_layout
\begin_layout LyX-Code
print
\begin_inset Quotes eld
\end_inset
Error: %s
\begin_inset Quotes erd
\end_inset
% err, debug
\end_layout
\begin_layout Section
Playing Audio
\end_layout
\begin_layout Standard
Playing audio with PyGST is a very simple matter that only requires a few
lines of code to get the audio playing.
\end_layout
\begin_layout Standard
As the was just covered we will use the gst.element_make_factory function
and set it up with a PyGTK GUI.
But besides that we will create a false video sink using the gst.element_make_fa
ctory function so that if the multimedia file is a video, only the audio
portion is played.
This is because we are using the high level
\begin_inset Quotes eld
\end_inset
playbin
\begin_inset Quotes erd
\end_inset
element which will automatically play everything.
So if all you want played is the audio, the video must be redirected.
\end_layout
\begin_layout Standard
Create a multimedia playbin to play the audio and redirect all video to
a fake video sink that is added to the multimedia pipeline:
\end_layout
\begin_layout LyX-Code
# Create the player_name sink
\end_layout
\begin_layout LyX-Code
player_name = gst.element_make_factory(
\begin_inset Quotes eld
\end_inset
playbin
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
Multimedia Player
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
# Create the fake video sink
\end_layout
\begin_layout LyX-Code
fake_video_sink = gst.element_make_factory(
\begin_inset Quotes eld
\end_inset
fakesink
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
Fake sink for Videos
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
#Add the fake video sink to the player
\end_layout
\begin_layout LyX-Code
player_name.set_property(
\begin_inset Quotes eld
\end_inset
videosink
\begin_inset Quotes erd
\end_inset
, fake_video_sink)
\end_layout
\begin_layout Standard
If a fake video sink is not created and a video file is played ,it will
pop up a window with the video playing in it.
This will really subtract from the professional feel of your application.
\end_layout
\begin_layout Standard
Now what is needed is to add the audio source using the player_name.set_property
method and set the state to playing.
This is just like what is discussed earlier and is now shown below:
\end_layout
\begin_layout LyX-Code
player_name.set_property(
\begin_inset Quotes eld
\end_inset
uri
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
file:///home/peter/mymusic.mp3
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
player_name.set_state(gst.PLAYING)
\end_layout
\begin_layout Standard
It really is that simple.
\end_layout
\begin_layout Standard
For a full example of how to hook up audio and video to a PyGTK application
please review the example at the end of the chapter.
\end_layout
\begin_layout Section
Playing Video
\end_layout
\begin_layout Standard
Playing audio is very simple and is much like playing audio except that
with playing video there is no fake video sink created to hide the video.
\end_layout
\begin_layout Standard
To play video a playbin must be created using the gst.element_make_factory
function.
Then set the location of the video file with the newly created playbin
and then set the playbin state to gst.PLAYING.
\end_layout
\begin_layout LyX-Code
# Create the player_name sink
\end_layout
\begin_layout LyX-Code
player_name = gst.element_make_factory(
\begin_inset Quotes eld
\end_inset
playbin
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
Multimedia Player
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
# Set the location of the video file
\end_layout
\begin_layout LyX-Code
player_name.set_property(
\begin_inset Quotes eld
\end_inset
uri
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
file:///home/peter/myvideo.avi
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
# Start playing the video.
\end_layout
\begin_layout LyX-Code
player_name.set_state(gst.PLAYING)
\end_layout
\begin_layout Standard
It really is much shorter to play a video then it is an audio file.
But remember that you should also hook up a bus, as shown in the section
\begin_inset Quotes eld
\end_inset
bus -- watching for GStreamer signals
\begin_inset Quotes erd
\end_inset
, to your video to catch messages.
However there is a problem with this code.
\end_layout
\begin_layout Standard
If you play a video with this code the video will open up in its own window.
If the video opening in its own window is good enough for your program
so be it; however I believe that for most programs the video will be better
suited in a widget inside of the application.
\end_layout
\begin_layout Subsection
\series bold
Play the Video in you Application
\end_layout
\begin_layout Standard
To play a video file in your own application you use a gtk.DrawingArea widget
to play the video.
You create a gtk.DrawingArea and sync it with the video using the bus that
has been created to watch for GStreamer messages.
\end_layout
\begin_layout Standard
Create your gtk.DrawingArea like so:
\end_layout
\begin_layout LyX-Code
self.videowidget = gtk.DrawingArea()
\end_layout
\begin_layout LyX-Code
self.videowidget.set_size_request(400, 250)
\end_layout
\begin_layout Standard
Then add this widget to your PyGTK window.
\end_layout
\begin_layout Standard
Now you sync the video to your videowidget using the bus.
If your bus name is
\emph on
bus
\emph default
you would enable sync messages and connect it to a function with the following
code:
\end_layout
\begin_layout LyX-Code
bus.enable_sync_message_emission()
\end_layout
\begin_layout LyX-Code
bus.connect(
\begin_inset Quotes eld
\end_inset
sync-message::element
\begin_inset Quotes erd
\end_inset
, self.on_sync_message)
\end_layout
\begin_layout Standard
This code enables the sync message and then connects any signals to be forwarded
to the
\emph on
self.on_sync_message
\emph default
function.
The on_sync_message function will hook the video up to the gtk.DrawingArea
widget that has been created to show the video.
\end_layout
\begin_layout Standard
Here is an example function showing how to play a video.
\end_layout
\begin_layout LyX-Code
def on_sync_message(self, bus, message):
\end_layout
\begin_layout LyX-Code
if message.structure is None:
\end_layout
\begin_layout LyX-Code
return False
\end_layout
\begin_layout LyX-Code
if message.structure.get_name() ==
\begin_inset Quotes eld
\end_inset
prepare-xwindow-id
\begin_inset Quotes erd
\end_inset
:
\end_layout
\begin_layout LyX-Code
if sys.platform ==
\begin_inset Quotes eld
\end_inset
win32
\begin_inset Quotes erd
\end_inset
:
\end_layout
\begin_layout LyX-Code
win_id = self.videowidget.window.handle
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
win_id = self.videowidget.window.xid
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
assert win_id
\end_layout
\begin_layout LyX-Code
imagesink = message.src
\end_layout
\begin_layout LyX-Code
imagesink.set_property(
\begin_inset Quotes eld
\end_inset
force-aspect-ratio
\begin_inset Quotes erd
\end_inset
, True)
\end_layout
\begin_layout LyX-Code
imagesink.set_xwindow_id(win_id)
\end_layout
\begin_layout Standard
Now when the state of your playbin element is set to play using gst.PLAYING,
the video will be played inside of your application instead of opening
up in its own window.
\end_layout
\begin_layout Standard
For a full example of how to hook up audio and video to a PyGTK application
please review the example at the end of the chapter.
\end_layout
\begin_layout Subsection
Play Video Example
\end_layout
\begin_layout Standard
This example will be referred to in following sections and when adding things
such as seeking and will be expanded upon in the file example at the end
of the chapter.
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "Simple Video Player Example"
\end_inset
\end_layout
\begin_layout LyX-Code
#!/usr/bin/env python
\end_layout
\begin_layout LyX-Code
import pygst
\end_layout
\begin_layout LyX-Code
pygst.require("0.10")
\end_layout
\begin_layout LyX-Code
import gst, pygtk, gtk
\end_layout
\begin_layout LyX-Code
import sys
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
class Main(object):
\end_layout
\begin_layout LyX-Code
def __init__(self):
\end_layout
\begin_layout LyX-Code
self.multimedia_file=""
\end_layout
\begin_layout LyX-Code
# Create the GUI
\end_layout
\begin_layout LyX-Code
self.win = gtk.Window()
\end_layout
\begin_layout LyX-Code
self.win.set_title("Play Video Example")
\end_layout
\begin_layout LyX-Code
self.win.connect("delete_event",
\end_layout
\begin_layout LyX-Code
lambda w,e: gtk.main_quit())
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
vbox = gtk.VBox(False, 0)
\end_layout
\begin_layout LyX-Code
hbox = gtk.HBox(False, 0)
\end_layout
\begin_layout LyX-Code
self.load_file =
\end_layout
\begin_layout LyX-Code
gtk.FileChooserButton("Choose Audio File")
\end_layout
\begin_layout LyX-Code
self.play_button =
\end_layout
\begin_layout LyX-Code
gtk.Button("Play", gtk.STOCK_MEDIA_PLAY)
\end_layout
\begin_layout LyX-Code
self.pause_button =
\end_layout
\begin_layout LyX-Code
gtk.Button("Pause", gtk.STOCK_MEDIA_PAUSE)
\end_layout
\begin_layout LyX-Code
self.stop_button =
\end_layout
\begin_layout LyX-Code
gtk.Button("Stop", gtk.STOCK_MEDIA_STOP)
\end_layout
\begin_layout LyX-Code
self.videowidget = gtk.DrawingArea()
\end_layout
\begin_layout LyX-Code
# You want to expand the video widget or
\end_layout
\begin_layout LyX-Code
# else you cannot see it
\end_layout
\begin_layout LyX-Code
self.videowidget.set_size_request(400, 250)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.load_file.connect("selection-changed",
\end_layout
\begin_layout LyX-Code
self.on_file_selected)
\end_layout
\begin_layout LyX-Code
self.play_button.connect("clicked", self.on_play_clicked)
\end_layout
\begin_layout LyX-Code
self.pause_button.connect("clicked", self.on_pause_clicked)
\end_layout
\begin_layout LyX-Code
self.stop_button.connect("clicked", self.on_stop_clicked)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
hbox.pack_start(self.play_button, False, True, 0)
\end_layout
\begin_layout LyX-Code
hbox.pack_start(self.pause_button, False, True, 0)
\end_layout
\begin_layout LyX-Code
hbox.pack_start(self.stop_button, False, True, 0)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(self.load_file, False, True, 0)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(self.videowidget, True, True, 0)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
vbox.pack_start(hbox, False, True, 0)
\end_layout
\begin_layout LyX-Code
self.win.add(vbox)
\end_layout
\begin_layout LyX-Code
self.win.show_all()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Setup GStreamer
\end_layout
\begin_layout LyX-Code
self.player = gst.element_factory_make(
\end_layout
\begin_layout LyX-Code
"playbin", "MultimediaPlayer")
\end_layout
\begin_layout LyX-Code
bus = self.player.get_bus()
\end_layout
\begin_layout LyX-Code
bus.add_signal_watch()
\end_layout
\begin_layout LyX-Code
bus.enable_sync_message_emission()
\end_layout
\begin_layout LyX-Code
#used to get messages that GStreamer emits
\end_layout
\begin_layout LyX-Code
bus.connect("message", self.on_message)
\end_layout
\begin_layout LyX-Code
#used for connecting video to your application
\end_layout
\begin_layout LyX-Code
bus.connect("sync-message::element",
\end_layout
\begin_layout LyX-Code
self.on_sync_message)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_file_selected(self, widget):
\end_layout
\begin_layout LyX-Code
self.multimedia_file = self.load_file.get_filename()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_play_clicked(self, widget):
\end_layout
\begin_layout LyX-Code
self.player.set_property('uri',
\end_layout
\begin_layout LyX-Code
"file://" + self.multimedia_file)
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_PLAYING)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_pause_clicked(self, widget):
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_PAUSED)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_stop_clicked(self, widget):
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_NULL)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_message(self, bus, message):
\end_layout
\begin_layout LyX-Code
if message.type == gst.MESSAGE_EOS:
\end_layout
\begin_layout LyX-Code
# End of Stream
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_NULL)
\end_layout
\begin_layout LyX-Code
elif message.type == gst.MESSAGE_ERROR:
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_NULL)
\end_layout
\begin_layout LyX-Code
(err, debug) = message.parse_error()
\end_layout
\begin_layout LyX-Code
print "Error: %s" % err, debug
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_sync_message(self, bus, message):
\end_layout
\begin_layout LyX-Code
if message.structure is None:
\end_layout
\begin_layout LyX-Code
return False
\end_layout
\begin_layout LyX-Code
if message.structure.get_name() == "prepare-xwindow-id":
\end_layout
\begin_layout LyX-Code
if sys.platform ==
\begin_inset Quotes eld
\end_inset
win32
\begin_inset Quotes erd
\end_inset
:
\end_layout
\begin_layout LyX-Code
win_id = self.videowidget.window.handle
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
win_id = self.videowidget.window.xid
\end_layout
\begin_layout LyX-Code
assert win_id
\end_layout
\begin_layout LyX-Code
imagesink = message.src
\end_layout
\begin_layout LyX-Code
imagesink.set_property("force-aspect-ratio", True)
\end_layout
\begin_layout LyX-Code
imagesink.set_xwindow_id(win_id)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
Main()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Section
Multimedia Info
\end_layout
\begin_layout Standard
Now what I should mention here is that this code is more or less the unmodified
example that comes with the PyGST source code.
Copyright (C) 2006 Andy Wingo, LGPL Version 2.
\end_layout
\begin_layout Standard
Lets say that you are going to play a video and you want to know some informatio
n about it.
Maybe you want to know what the video width and height is to set a proper
size on your video widget.
Or maybe you want to know the length of the video.
Well this information is very easy to find out using GStreamer.
\end_layout
\begin_layout Standard
First you will have to import the GStreamer discoverer like so:
\end_layout
\begin_layout LyX-Code
from gst.extend import discoverer
\end_layout
\begin_layout Standard
Now that you have the discoverer imported you can access information about
the video file with only a few lines of code.
\end_layout
\begin_layout Standard
We create a discover function that will be the main work area that hooks
everything together.
\end_layout
\begin_layout Standard
The discover functions includes an in-line function that is connected to
using a gobject main loop since this is a command line example.
If this code is used in a PyGTK GUI it will will run fine without the gobject
main loop because it is already running in the applications GTK main loop.
\end_layout
\begin_layout Standard
If the the file is discovered to be a multimedia file it is then sent to
the succeed function where it prints out information about the file.
\end_layout
\begin_layout Standard
If it fails and is not recognized as a multimedia file then it prints out
an error message and exits the gobject main loop.
\end_layout
\begin_layout LyX-Code
def discover(path):
\end_layout
\begin_layout LyX-Code
def discovered(d, is_media):
\end_layout
\begin_layout LyX-Code
if is_media:
\end_layout
\begin_layout LyX-Code
succeed(d)
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
print
\begin_inset Quotes eld
\end_inset
error: %r does not appear to be a media file
\begin_inset Quotes erd
\end_inset
% path
\end_layout
\begin_layout LyX-Code
# Exit the gobject main loop
\end_layout
\begin_layout LyX-Code
# Remove this in a pygtk program.
\end_layout
\begin_layout LyX-Code
sys.exit(1)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
d = discoverer.Discoverer(path)
\end_layout
\begin_layout LyX-Code
# Connect discovered to the inline function discovered.
\end_layout
\begin_layout LyX-Code
d.connect(
\begin_inset Quotes eld
\end_inset
discovered
\begin_inset Quotes erd
\end_inset
, discovered)
\end_layout
\begin_layout LyX-Code
d.discover()
\end_layout
\begin_layout LyX-Code
# comment out the gobject.MainLoop.run() in a pygtk program.
\end_layout
\begin_layout LyX-Code
gobject.MainLoop().run()
\end_layout
\begin_layout Standard
The succeed method is called from the discover function when the file is
detected as a multimedia file.
It can be used to print out or save information about the video or audio
file.
\end_layout
\begin_layout Standard
Data available for video files include:
\end_layout
\begin_layout Itemize
is_video
\end_layout
\begin_layout Itemize
video_length
\end_layout
\begin_layout Itemize
fps - videorate.num / videorate.denom
\end_layout
\begin_layout Itemize
videocaps
\end_layout
\begin_layout Itemize
videowidth
\end_layout
\begin_layout Itemize
videoheight
\end_layout
\begin_layout Standard
Data available for audio files include:
\end_layout
\begin_layout Itemize
is_audio
\end_layout
\begin_layout Itemize
audiocaps
\end_layout
\begin_layout Itemize
audiofloat
\end_layout
\begin_layout Itemize
audiorate
\end_layout
\begin_layout Itemize
audiowidth
\end_layout
\begin_layout Itemize
audiodepth
\end_layout
\begin_layout Itemize
audiolength
\end_layout
\begin_layout Itemize
audiochannels
\end_layout
\begin_layout LyX-Code
def succeed(d):
\end_layout
\begin_layout LyX-Code
print(
\begin_inset Quotes eld
\end_inset
media type
\begin_inset Quotes erd
\end_inset
, d.mimetype)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
print(
\begin_inset Quotes eld
\end_inset
has video
\begin_inset Quotes erd
\end_inset
, d.is_video)
\end_layout
\begin_layout LyX-Code
if d.is_video:
\end_layout
\begin_layout LyX-Code
print(
\begin_inset Quotes eld
\end_inset
video length (ms)
\begin_inset Quotes erd
\end_inset
, d.videolength / gst.MSECOND)
\end_layout
\begin_layout LyX-Code
print(
\begin_inset Quotes eld
\end_inset
framerate (fps)
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
%s/%s
\begin_inset Quotes erd
\end_inset
% (d.videorate.num, d.videorate.denom))
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
print(
\begin_inset Quotes eld
\end_inset
has audio
\begin_inset Quotes erd
\end_inset
, d.is_audio)
\end_layout
\begin_layout LyX-Code
if d.is_audio:
\end_layout
\begin_layout LyX-Code
print(
\begin_inset Quotes eld
\end_inset
audio caps
\begin_inset Quotes erd
\end_inset
, d.audiocaps)
\end_layout
\begin_layout LyX-Code
print(
\begin_inset Quotes eld
\end_inset
audio format
\begin_inset Quotes erd
\end_inset
, d.audiofloat and
\begin_inset Quotes eld
\end_inset
floating-point
\begin_inset Quotes erd
\end_inset
or
\begin_inset Quotes eld
\end_inset
integer
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
print(
\begin_inset Quotes eld
\end_inset
audio length (ms)
\begin_inset Quotes erd
\end_inset
, d.audiolength / gst.MSECOND)
\end_layout
\begin_layout LyX-Code
# Exit gobject main loop.
\end_layout
\begin_layout LyX-Code
sys.exit(0)
\end_layout
\begin_layout Standard
All that is left is to run the discover file with a path to a multimedia
file specified.
To read in the file location and do the proper handling of it you could
use the following code:
\end_layout
\begin_layout LyX-Code
if __name__ ==
\begin_inset Quotes eld
\end_inset
__main__
\begin_inset Quotes erd
\end_inset
:
\end_layout
\begin_layout LyX-Code
if len(sys.argv) != 2:
\end_layout
\begin_layout LyX-Code
print >> sys.stderr,
\begin_inset Quotes eld
\end_inset
usage: script_name.py PATH-TO-MEDIA-FILE
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout LyX-Code
sys.exit(1)
\end_layout
\begin_layout LyX-Code
path = sys.argv.pop()
\end_layout
\begin_layout LyX-Code
if not os.path.isfile(path):
\end_layout
\begin_layout LyX-Code
print >> sys.stderr,
\begin_inset Quotes eld
\end_inset
error: file %r does not exist
\begin_inset Quotes erd
\end_inset
% path
\end_layout
\begin_layout LyX-Code
print >> sys.stderr,
\begin_inset Quotes eld
\end_inset
usage: gst-discover PATH-TO-MEDIA-FILE
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout LyX-Code
sys.exit(1)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
discover(path)
\end_layout
\begin_layout Standard
For a full example of how to retrieve information from an audio or video
file from a PyGTK application, please review MediaInfo class in the example
at the end of the chapter.
Also you can check out the PyGST examples on this books website.
\end_layout
\begin_layout Section
Codec Buddy - Auto install multimedia Codecs
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sec: Gst - Codec Buddy"
\end_inset
\end_layout
\begin_layout Standard
Tested with Ubuntu 8.10
\end_layout
\begin_layout Standard
Taking advantage of the gst.pbutils allows a program to automatically install
available codecs or provide the user of the program a choice of actions
to take.
\end_layout
\begin_layout LyX-Code
#!/usr/bin/python
\end_layout
\begin_layout LyX-Code
import pygst, gst, pygtk, gtk
\end_layout
\begin_layout LyX-Code
pygst.require("0.10")
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
class InstallMissingCodecExample(object):
\end_layout
\begin_layout LyX-Code
def __init__(self):
\end_layout
\begin_layout LyX-Code
# Gtk Gui
\end_layout
\begin_layout LyX-Code
self.win = gtk.Window()
\end_layout
\begin_layout LyX-Code
self.win.set_title("Install Missing Codec Example")
\end_layout
\begin_layout LyX-Code
self.win.connect("delete_event", lambda w,e: gtk.main_quit())
\end_layout
\begin_layout LyX-Code
self.load_file = gtk.FileChooserButton("Choose Audio File")
\end_layout
\begin_layout LyX-Code
self.load_file.connect("selection-changed", self.on_file_selected)
\end_layout
\begin_layout LyX-Code
self.win.add(self.load_file)
\end_layout
\begin_layout LyX-Code
self.win.show_all()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Setup GStreamer
\end_layout
\begin_layout LyX-Code
self.player = gst.element_factory_make("playbin",
\end_layout
\begin_layout LyX-Code
"MultimediaPlayer")
\end_layout
\begin_layout LyX-Code
bus = self.player.get_bus()
\end_layout
\begin_layout LyX-Code
bus.add_signal_watch()
\end_layout
\begin_layout LyX-Code
bus.connect("message", self.on_message)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_file_selected(self, widget):
\end_layout
\begin_layout LyX-Code
print "Selected: ", self.load_file.get_filename()
\end_layout
\begin_layout LyX-Code
multimedia_file = self.load_file.get_filename()
\end_layout
\begin_layout LyX-Code
self.player.set_property('uri', "file://" + multimedia_file)
\end_layout
\begin_layout LyX-Code
self.play()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def play(self):
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_PLAYING)
\end_layout
\begin_layout LyX-Code
# Codec Buddy Methods
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_message(self, bus, message):
\end_layout
\begin_layout LyX-Code
import gst
\end_layout
\begin_layout LyX-Code
if message.type == gst.MESSAGE_ERROR:
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_NULL)
\end_layout
\begin_layout LyX-Code
(err, debug) = message.parse_error()
\end_layout
\begin_layout LyX-Code
print "Error: %s" % err, debug
\end_layout
\begin_layout LyX-Code
elif message.type == gst.MESSAGE_EOS:
\end_layout
\begin_layout LyX-Code
# End of Stream
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_NULL)
\end_layout
\begin_layout LyX-Code
elif message.type == gst.MESSAGE_ELEMENT:
\end_layout
\begin_layout LyX-Code
""" CodicBuddy Stuff """
\end_layout
\begin_layout LyX-Code
st = message.structure
\end_layout
\begin_layout LyX-Code
if st and st.get_name().startswith('missing-'):
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_NULL)
\end_layout
\begin_layout LyX-Code
if gst.pygst_version >= (0, 10, 10):
\end_layout
\begin_layout LyX-Code
import gst.pbutils
\end_layout
\begin_layout LyX-Code
detail = gst.pbutils.missing_plugin_message_get_installer_detail(message
) context = gst.pbutils.InstallPluginsContext()
\end_layout
\begin_layout LyX-Code
gst.pbutils.install_plugins_async([detail],
\end_layout
\begin_layout LyX-Code
context, self.install_plugin)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def install_plugin(self, result):
\end_layout
\begin_layout LyX-Code
if result == gst.pbutils.INSTALL_PLUGINS_SUCCESS:
\end_layout
\begin_layout LyX-Code
gst.update_registry()
\end_layout
\begin_layout LyX-Code
self.play()
\end_layout
\begin_layout LyX-Code
return
\end_layout
\begin_layout LyX-Code
if result == gst.pbutils.INSTALL_PLUGINS_USER_ABORT:
\end_layout
\begin_layout LyX-Code
dialog = gtk.MessageDialog(parent=None,
\end_layout
\begin_layout LyX-Code
flags=gtk.DIALOG_MODAL, type=gtk.MESSAGE_INFO,
\end_layout
\begin_layout LyX-Code
buttons=gtk.BUTTONS_OK, message_format=
\end_layout
\begin_layout LyX-Code
"Plugin installation aborted.")
\end_layout
\begin_layout LyX-Code
dialog.run()
\end_layout
\begin_layout LyX-Code
dialog.hide()
\end_layout
\begin_layout LyX-Code
return
\end_layout
\begin_layout LyX-Code
error.show("Error", "failed to install plugins: %s" %
\end_layout
\begin_layout LyX-Code
str(result))
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
InstallMissingCodecExample()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Section
Seeking - Basic Position Seeking
\end_layout
\begin_layout Standard
Seeking allows multimedia software to display the position in the audio
or video stream and also may allow the user to skip to a different section
of the media file they are watching or listening to.
\end_layout
\begin_layout Subsection
Displaying the Current Position
\end_layout
\begin_layout Standard
When playing a media file it may be a good idea to display the current position
relative to the duration of the file as a courtesy to the user.
\end_layout
\begin_layout Standard
Displaying the duration of the file and current time position will require
adding two methods to the play video (
\begin_inset CommandInset ref
LatexCommand vref
reference "Simple Video Player Example"
\end_inset
) example found earlier in the chapter.
\end_layout
\begin_layout Standard
In the __init__ method of the Main class the variables time_format, duration
and is_playing are added.
\end_layout
\begin_layout LyX-Code
self.time_format = gst.Format(gst.FORMAT_TIME)
\end_layout
\begin_layout LyX-Code
self.duration = None
\end_layout
\begin_layout LyX-Code
self.is_playing = False
\end_layout
\begin_layout Standard
The time_format will be used when seeking the the duration of the media
file and seeking the current position of the file.
The duration will be a string to display the length of the media file.
the is_playing variable will be used to let the methods that are going
to soon be added know if the player is playing or not.
\end_layout
\begin_layout Standard
The is_playing variable must be set to False anytime the file is not playing.
This includes the end of stream message in the on_message method and when
the pause and stop buttons are clicked.
\end_layout
\begin_layout Standard
Further down in the __init__ method a label called time_label is added and
that is it for the GUI changes to display the time.
\end_layout
\begin_layout LyX-Code
self.time_label = gtk.Label(
\begin_inset Quotes eld
\end_inset
00:00 / 00:00
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout Standard
Going through the different methods, besides setting is_playing to False
in the on_stop_clicked and on_pause_clicked methods, in the on_play_clicked
it must be set to True.
But after setting it to playing by clicking the play button the application
must be able to update the GUI with the new current position every second.
\end_layout
\begin_layout Standard
To update the GUI every second the on_play_clicked button adds the following:
\end_layout
\begin_layout LyX-Code
timer = gobject.timeout_add(1000, self.update_time_label)
\end_layout
\begin_layout Standard
This will create a timer that is executed every one second calling the method
update_time_label.
It will execute every one second as long a update_time_label returns true.
\end_layout
\begin_layout Standard
Skipping down to below the on_sync_message method there is the new function
update_time_label.
\end_layout
\begin_layout LyX-Code
def update_time_label(self):
\end_layout
\begin_layout LyX-Code
"""
\end_layout
\begin_layout LyX-Code
Update the time_label to display the current location
\end_layout
\begin_layout LyX-Code
in the media file as well as update the seek bar
\end_layout
\begin_layout LyX-Code
"""
\end_layout
\begin_layout LyX-Code
if self.is_playing == False:
\end_layout
\begin_layout LyX-Code
print "return false"
\end_layout
\begin_layout LyX-Code
return False
\end_layout
\begin_layout LyX-Code
print "update_time_label"
\end_layout
\begin_layout LyX-Code
if self.duration == None:
\end_layout
\begin_layout LyX-Code
try:
\end_layout
\begin_layout LyX-Code
self.length = self.player.query_duration(self.time_format, None)[0]
\end_layout
\begin_layout LyX-Code
self.duration = self.convert_time(self.length)
\end_layout
\begin_layout LyX-Code
except:
\end_layout
\begin_layout LyX-Code
self.duration = None
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if self.duration != None:
\end_layout
\begin_layout LyX-Code
self.current_position = self.player.query_position(self.time_format, None)[0]
\end_layout
\begin_layout LyX-Code
current_position_formated = self.convert_time(self.current_position)
\end_layout
\begin_layout LyX-Code
self.time_label.set_text(current_position_formated + "/" + self.duration)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Update the seek bar
\end_layout
\begin_layout LyX-Code
# gtk.Adjustment(value=0, lower=0, upper=0, step_incr=0, page_incr=0,
page_size=0)
\end_layout
\begin_layout LyX-Code
percent = (float(self.current_position)/float(self.length))*100.0
\end_layout
\begin_layout LyX-Code
adjustment = gtk.Adjustment(percent, 0.00, 100.0, 0.1, 1.0, 1.0) self.seeker.s
et_adjustment(adjustment)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
return True
\end_layout
\begin_layout Standard
If the
\emph on
is_playing
\emph default
variable is set to False the
\emph on
update_time_label
\emph default
method will return False.
This method starts to be called when the play button is called and is
called every one second until it returns False.
\end_layout
\begin_layout Standard
If the duration of the file has not yet been set it will be set here.
The duration is found in nanoseconds and is converted to a string by passing
it into the convert_time method.
The duration variable will be reset to None each time a new media file
is added to be played.
If the duration is not None then it never set again unless a new file is
loaded.
\end_layout
\begin_layout Standard
After duration of the file is found the
\emph on
current_position
\emph default
variable is calculated every time the
\emph on
update_time_label
\emph default
is called.
The current position is found the same was as the duration except that
the
\emph on
query_position
\emph default
function is used.
Then the time in nanoseconds is converted to a person understandable string.
\end_layout
\begin_layout Standard
Once the duration and the current position is found it is displayed to the
user by setting the text of the time_label like so:
\end_layout
\begin_layout LyX-Code
self.time_label.set_text(current_position_formated +
\begin_inset Quotes eld
\end_inset
/
\begin_inset Quotes erd
\end_inset
+ self.duration)
\end_layout
\begin_layout Standard
As was just discussed above, the convert_time function is used to convert
the time of the media file from nanoseconds to human readable string.
This code is adapted from a tutorial
\begin_inset Foot
status collapsed
\begin_layout Plain Layout
This is licensed under the LGPL Version 3 and can be found at:
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://pygstdocs.berlios.de/pygst-tutorial/seeking.html
\end_layout
\end_inset
\end_layout
\end_inset
found on the PyGST documentation site.
It takes the time in nanoseconds and converts it to human readable string
in the format of HH::MM::SS and then returns it.
\end_layout
\begin_layout LyX-Code
def convert_time(self, time=None):
\end_layout
\begin_layout LyX-Code
# convert_ns function from:
\end_layout
\begin_layout LyX-Code
# http://pygstdocs.berlios.de/pygst-tutorial/seeking.html
\end_layout
\begin_layout LyX-Code
# LGPL Version 3 - Copyright: Jens Persson
\end_layout
\begin_layout LyX-Code
if time==None:
\end_layout
\begin_layout LyX-Code
return None
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
hours = 0
\end_layout
\begin_layout LyX-Code
minutes = 0
\end_layout
\begin_layout LyX-Code
seconds = 0
\end_layout
\begin_layout LyX-Code
time_string = ""
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
time = time / 1000000000 # gst.NSECOND
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if time >= 3600:
\end_layout
\begin_layout LyX-Code
hours = time / 3600
\end_layout
\begin_layout LyX-Code
time = time - (hours * 3600)
\end_layout
\begin_layout LyX-Code
if time >= 60:
\end_layout
\begin_layout LyX-Code
minutes = time / 60
\end_layout
\begin_layout LyX-Code
time = time - (minutes * 60)
\end_layout
\begin_layout LyX-Code
#remaining time is seconds
\end_layout
\begin_layout LyX-Code
seconds = time
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
time_string = time_string + str(hours).zfill(2) + ":" +
\end_layout
\begin_layout LyX-Code
str(minutes).zfill(2) + ":" + str(seconds).zfill(2)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
#return time in Hours:Minutes:Seconds format
\end_layout
\begin_layout LyX-Code
return time_string
\end_layout
\begin_layout Standard
If the time passed in is None it immediately returns None.
The method divides the passed in time by 1 000 000 000 and then proceeds
to calculate the hours, minutes, and seconds; creating a nice string HH:MM:SS
to view by a human.
\end_layout
\begin_layout Standard
When the time_string has been completed it is returned to be used by the
user interface.
\end_layout
\begin_layout Subsection
Seeking a New Position
\end_layout
\begin_layout Standard
Like displaying the position and duration, seeking a new position in a media
file will use the methods convert_time as well as update_time_label, but
it will also use a horizontal scaler to that it will let the user slide
to a new position.
\begin_inset Note Note
status open
\begin_layout Plain Layout
TODO: Sometime fix the code so the scaler updates as the video is playing.
In other words the scaler should do more then just allow the user to seek
a new position.
\end_layout
\end_inset
\end_layout
\begin_layout Standard
To allow a user to update the position of the media file a horizontal scaler
needs to be added.
To use a scaler you must create an adjustment first.
\end_layout
\begin_layout LyX-Code
self.adjustment = gtk.Adjustment(0.0, 0.00, 100.0, 0.1, 1.0, 1.0)
\end_layout
\begin_layout Standard
This creates a new adjustment with a starting value of 0, lower limit of
0.00, upper limit of 100.0, and step increment of 0.1, page increment of 1.0,
and page size of 1.0.
The adjustment is used with the the horizontal scaler that is to be created
to control the place in the media file.
\end_layout
\begin_layout LyX-Code
self.seeker = gtk.HScale(self.adjustment)
\end_layout
\begin_layout LyX-Code
self.seeker.set_draw_value(False)
\end_layout
\begin_layout LyX-Code
self.seeker.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
\end_layout
\begin_layout Standard
The first line creates the seeker and the set_draw_value(False) line keeps
the format-value signal from being admitted and the value of the current
position is not displayed.
\end_layout
\begin_layout Standard
On the third line the seeker is set to update in a discontinuous way.
What this means is that it will only be updated when a button-release-event
signal is emitted.
\end_layout
\begin_layout Standard
The new method being added for seeking is seeker_button_release_event and
the signals is connected like this:
\end_layout
\begin_layout LyX-Code
self.seeker.connect(
\begin_inset Quotes eld
\end_inset
button-release-event
\begin_inset Quotes erd
\end_inset
, self.seeker_button_release_event)
\end_layout
\begin_layout Standard
When the scaler button is released this method is called and the media file
is set to a new position.
\end_layout
\begin_layout LyX-Code
def seeker_button_release_event(self, widget, event):
\end_layout
\begin_layout LyX-Code
print "seeker_button_release_event"
\end_layout
\begin_layout LyX-Code
value = widget.get_value()
\end_layout
\begin_layout LyX-Code
if self.is_playing == True:
\end_layout
\begin_layout LyX-Code
duration = self.player.query_duration(self.time_format, None)[0]
\end_layout
\begin_layout LyX-Code
time = value * (duration / 100)
\end_layout
\begin_layout LyX-Code
print self.convert_time(time)
\end_layout
\begin_layout LyX-Code
self.player.seek_simple(self.time_format, gst.SEEK_FLAG_FLUSH, time)
\end_layout
\begin_layout Standard
When the self.seeker is released, its current position is retrieved and the
position to reposition the media file is calculated.
\end_layout
\begin_layout LyX-Code
time = value * (duration / 100)
\end_layout
\begin_layout Standard
After the new position is determined, the media file is set to it using
the seek_simple function.
The seek_simple function takes a GStreamer time format, a seek flag, and
the new time
\begin_inset Foot
status open
\begin_layout Plain Layout
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer/html/GstElement.
html
\end_layout
\end_inset
\end_layout
\end_inset
, returning True if it succeeds.
\end_layout
\begin_layout Section
Volume Control
\end_layout
\begin_layout Standard
Adding the option to control the volume of audio or video from individual
programs is accomplished using the gtk.VolumeButton.
\end_layout
\begin_layout Standard
A volume button is created like any other widget in PyGTK.
\end_layout
\begin_layout LyX-Code
volume_button = gtk.VolumeButton()
\end_layout
\begin_layout Standard
Then hook the volume button up to the value-changed signal with a method
to control the volume.
\end_layout
\begin_layout LyX-Code
volume_button.connect(
\begin_inset Quotes eld
\end_inset
value-changed
\begin_inset Quotes erd
\end_inset
, self.on_volume_changed)
\end_layout
\begin_layout Standard
The last piece to complete is to create the method that is being used to
increase and decrease the volume.
\end_layout
\begin_layout LyX-Code
def on_file_selected(self, widget, value=0.5)
\end_layout
\begin_layout LyX-Code
self.player.set_property(
\begin_inset Quotes eld
\end_inset
volume
\begin_inset Quotes erd
\end_inset
, float(value))
\end_layout
\begin_layout LyX-Code
return True
\end_layout
\begin_layout Standard
What this method does is to control the volume and increase by the percent
raised on the volume slider.
The default value is 0.5 if it is not specified.
\end_layout
\begin_layout Subsection
Volume Control Example
\end_layout
\begin_layout LyX-Code
class Main(object):
\end_layout
\begin_layout LyX-Code
def __init__(self):
\end_layout
\begin_layout LyX-Code
self.win = gtk.Window()
\end_layout
\begin_layout LyX-Code
self.win.set_title("Volume Control Example")
\end_layout
\begin_layout LyX-Code
self.win.set_default_size(200, -1)
\end_layout
\begin_layout LyX-Code
self.win.connect("delete_event", lambda w,e: gtk.main_quit())
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
hbox = gtk.HBox(False, 0)
\end_layout
\begin_layout LyX-Code
self.load_file = gtk.FileChooserButton("Choose Audio File")
\backslash
\end_layout
\begin_layout LyX-Code
self.load_file.connect("selection-changed", self.on_file_selected)
\end_layout
\begin_layout LyX-Code
volume_button = gtk.VolumeButton()
\end_layout
\begin_layout LyX-Code
volume_button.connect("value-changed", self.on_volume_changed)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
hbox.pack_start(self.load_file, True, True, 0)
\end_layout
\begin_layout LyX-Code
hbox.pack_start(volume_button, False, True, 0)
\end_layout
\begin_layout LyX-Code
self.win.add(hbox)
\end_layout
\begin_layout LyX-Code
self.win.show_all()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.player = gst.element_factory_make("playbin", "MultimediaPlayer")
\end_layout
\begin_layout LyX-Code
bus = self.player.get_bus()
\end_layout
\begin_layout LyX-Code
bus.add_signal_watch()
\end_layout
\begin_layout LyX-Code
bus.enable_sync_message_emission()
\end_layout
\begin_layout LyX-Code
bus.connect("message", self.on_message)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_file_selected(self, widget):
\end_layout
\begin_layout LyX-Code
self.player.set_property("uri", "file://" + self.load_file.get_filename())
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_PLAYING)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_volume_changed(self, widget, value=10):
\end_layout
\begin_layout LyX-Code
self.player.set_property("volume", float(value))
\end_layout
\begin_layout LyX-Code
return True
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_message(self, bus, message):
\end_layout
\begin_layout LyX-Code
if message.type == gst.MESSAGE_EOS:
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_NULL)
\end_layout
\begin_layout LyX-Code
elif message.type == gst.MESSAGE_ERROR:
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_NULL)
\end_layout
\begin_layout LyX-Code
(err, debug) = message.parse_error()
\end_layout
\begin_layout LyX-Code
print "Error: %s" % err, debug
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
Main()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Section
Example
\end_layout
\begin_layout Standard
The purpose of this section is to provide a full media example that includes
a user interface written with PyGTK.
The application will be able to play, pause, or stop audio and video.
Also the program will be able to discover information about the media file
such as its length, width and height, and audio format using the MediaInfo
class.
\end_layout
\begin_layout Standard
Using the information found with the MediaInfo class the user interface
will be able to resize its video display to match the width and height
of the video.
\end_layout
\begin_layout Standard
The GstPlayer class will wrap the GStreamer functions to make it easy to
separate the multimedia and user interface functionality.
The user interface will use a separate class called VideoWidget to display
the video in also to make it easier to reuse.
\end_layout
\begin_layout Subsection
MediaInfo Class
\end_layout
\begin_layout Standard
The MediaInfo class is very similar to the media info section covered earlier
in the chapter.
What the MediaInfo class does here is to create an easy to use wrapper
around the GStreamer discoverer functions, providing accessor methods to
the data.
\end_layout
\begin_layout Standard
Though there are many different pieces of information that can be discovered
about a media file, for this example it will be kept short.
For more information on the different variables that can be used to access
the information please refer to the
\shape italic
multimedia info
\shape default
section earlier in this chapter.
\end_layout
\begin_layout Standard
The only information that is of interest at the moment is if the media file
is audio or video.
If it is a video file, the width and the video height is of interest.
\begin_inset space \space{}
\end_inset
If seeking is involved then the length of the file is also of interest,
but this is not covered in this example.
\end_layout
\begin_layout LyX-Code
class MediaInfo:
\end_layout
\begin_layout LyX-Code
def __init__(self, path):
\end_layout
\begin_layout LyX-Code
def discovered(d, is_media):
\end_layout
\begin_layout LyX-Code
if is_media:
\end_layout
\begin_layout LyX-Code
self.succeed(d)
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
self.fail(path)
\end_layout
\begin_layout LyX-Code
self.__finished = False
\end_layout
\begin_layout LyX-Code
self.__is_media=False
\end_layout
\begin_layout LyX-Code
self.__video_width = 0
\end_layout
\begin_layout LyX-Code
self.__video_height = 0
\end_layout
\begin_layout LyX-Code
self.__is_video = False
\end_layout
\begin_layout LyX-Code
self.__is_audio = False
\end_layout
\begin_layout LyX-Code
self.__video_length = 0.0
\end_layout
\begin_layout LyX-Code
self.__frame_rate =
\begin_inset Quotes eld
\end_inset
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.__is_fullscreen = False
\end_layout
\begin_layout LyX-Code
print
\begin_inset Quotes eld
\end_inset
path:
\begin_inset Quotes eld
\end_inset
, path
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
d = discoverer.Discoverer(path)
\end_layout
\begin_layout LyX-Code
#print help(d.discover)
\end_layout
\begin_layout LyX-Code
d.connect(
\begin_inset Quotes eld
\end_inset
discovered
\begin_inset Quotes erd
\end_inset
, discovered)
\end_layout
\begin_layout LyX-Code
d.discover()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def fail(self, path):
\end_layout
\begin_layout LyX-Code
print
\begin_inset Quotes eld
\end_inset
error: %r does not appear to be a media file
\begin_inset Quotes erd
\end_inset
% path
\end_layout
\begin_layout LyX-Code
self.__is_media = False
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def succeed(self, d):
\end_layout
\begin_layout LyX-Code
print
\begin_inset Quotes eld
\end_inset
File discover success
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout LyX-Code
self.__is_media = True
\end_layout
\begin_layout LyX-Code
self.__mimetype = d.mimetype
\end_layout
\begin_layout LyX-Code
self.__is_video = d.is_video
\end_layout
\begin_layout LyX-Code
if self.__is_video:
\end_layout
\begin_layout LyX-Code
self.__video_width = d.videowidth
\end_layout
\begin_layout LyX-Code
self.__video_height = d.videoheight
\end_layout
\begin_layout LyX-Code
# Retrieve the video length in minute
\end_layout
\begin_layout LyX-Code
self.__video_length = ((d.videolength / gst.MSECOND) / 1000) /
60
\end_layout
\begin_layout LyX-Code
self.__frame_rate =
\begin_inset Quotes eld
\end_inset
%s/%s
\begin_inset Quotes erd
\end_inset
% (d.videorate.num, d.videorate.denom)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.__finished = True
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def poll(self):
\end_layout
\begin_layout LyX-Code
return self.__finished
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def is_media(self):
\end_layout
\begin_layout LyX-Code
return self.__is_media
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def is_video(self):
\end_layout
\begin_layout LyX-Code
return self.__is_video
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def is_audio(self):
\end_layout
\begin_layout LyX-Code
return self.__is_audio
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def get_width(self):
\end_layout
\begin_layout LyX-Code
return self.__video_width
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def get_height(self):
\end_layout
\begin_layout LyX-Code
return self.__video_height
\end_layout
\begin_layout Standard
The MediaInfo class starts off by creating an inline function called discovered
which is called by connecting the
\shape italic
discovered
\shape default
signal, near the end of the init function, to the
\shape italic
discovered
\shape default
function.
The init function also creates sever class instance variables that is used
to store information about the media file.
\end_layout
\begin_layout Standard
If the file loaded is detected as being a media file it is sent to the method
\shape italic
succeed
\shape default
.
If the file is not a media file the fail method is called, an error message
is printed to the console, and the class variable self.__is_media is set
to false indicating the file is not a media file.
\end_layout
\begin_layout Standard
If the succeed method is called it will set the class variable self.__is_media
to true.
It will then set the mime type using the variable self.__mimetype.
It will check to see if the media file is an audio file or a video file.
It will set self.__is_video to true if it is a video file.
\end_layout
\begin_layout Standard
The height and width of a video file is stored respectively in the variables
self.__video_height and self.__video_width.
\end_layout
\begin_layout Standard
Then there are a few methods to retrieve these variables that are of interest
to the programmer.
The methods return the variables that are obviously in their names.
get_height() returns self.__video_height and so on with the other methods.
\end_layout
\begin_layout Standard
The only other method that is included is the
\shape italic
poll
\shape default
method.
Since the discovery of media information is not instantaneous, if you attempt
to use any of the get methods to retrieve information such as the height
or width of a video, it may return the initialization values of the variables
which is zero.
\end_layout
\begin_layout Standard
The
\shape italic
poll
\shape default
method will return True when the
\shape italic
succeed
\shape default
method has finished, indicating that all the variables have been assigned.
\end_layout
\begin_layout Standard
If the poll method returns False you must wait to use the information provided
by the MediaInfo class.
\end_layout
\begin_layout Subsection
GstPlayer Class
\end_layout
\begin_layout Standard
The GstPlayer class is used to control the GStreamer instance, watch the
bus, handle GStreamer errors and messages, and sync the GStreamer video
to video widget created with the VideoWidget class below.
\end_layout
\begin_layout LyX-Code
class GstPlayer(object):
\end_layout
\begin_layout LyX-Code
def __init__(self, videowidget):
\end_layout
\begin_layout LyX-Code
# Setup GStreamer
\end_layout
\begin_layout LyX-Code
self.videowidget = videowidget
\end_layout
\begin_layout LyX-Code
self.player = gst.element_factory_make(
\begin_inset Quotes eld
\end_inset
playbin
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
MultimediaPlayer
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
bus = self.player.get_bus()
\end_layout
\begin_layout LyX-Code
bus.add_signal_watch()
\end_layout
\begin_layout LyX-Code
bus.enable_sync_message_emission()
\end_layout
\begin_layout LyX-Code
#used to get messages that gstreamer emits
\end_layout
\begin_layout LyX-Code
bus.connect(
\begin_inset Quotes eld
\end_inset
message
\begin_inset Quotes erd
\end_inset
, self.on_message)
\end_layout
\begin_layout LyX-Code
#used for connecting video to your application
\end_layout
\begin_layout LyX-Code
bus.connect(
\begin_inset Quotes eld
\end_inset
sync-message::element
\begin_inset Quotes erd
\end_inset
, self.on_sync_message)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def set_location(self, location):
\end_layout
\begin_layout LyX-Code
self.player.set_property(
\begin_inset Quotes eld
\end_inset
uri
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
file://
\begin_inset Quotes erd
\end_inset
+ location)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def play(self):
\end_layout
\begin_layout LyX-Code
print
\begin_inset Quotes eld
\end_inset
playing
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_PLAYING)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def pause(self):
\end_layout
\begin_layout LyX-Code
print
\begin_inset Quotes eld
\end_inset
paused
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_PAUSED)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def stop(self):
\end_layout
\begin_layout LyX-Code
print
\begin_inset Quotes eld
\end_inset
stoped
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_NULL)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_message(self, bus, message):
\end_layout
\begin_layout LyX-Code
if message.type == gst.MESSAGE_EOS: # End of Stream
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_NULL)
\end_layout
\begin_layout LyX-Code
elif message.type == gst.MESSAGE_ERROR:
\end_layout
\begin_layout LyX-Code
self.player.set_state(gst.STATE_NULL)
\end_layout
\begin_layout LyX-Code
(err, debug) = message.parse_error()
\end_layout
\begin_layout LyX-Code
print
\begin_inset Quotes eld
\end_inset
Error: %s
\begin_inset Quotes erd
\end_inset
% err, debug
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_sync_message(self, bus, message):
\end_layout
\begin_layout LyX-Code
if message.structure is None:
\end_layout
\begin_layout LyX-Code
return False
\end_layout
\begin_layout LyX-Code
if message.structure.get_name() ==
\begin_inset Quotes eld
\end_inset
prepare-xwindow-id
\begin_inset Quotes erd
\end_inset
:
\end_layout
\begin_layout LyX-Code
self.videowidget.set_sink(message.src)
\end_layout
\begin_layout LyX-Code
message.src.set_property(
\begin_inset Quotes eld
\end_inset
force-aspect-ratio
\begin_inset Quotes erd
\end_inset
, True)
\end_layout
\begin_layout Standard
The GstPlayer class starts off with a video widget being passed in.
This video widget is used to sync the GStreamer video with.
After this the GStreamer pipeline is created using gst.element_factory_make
using the
\shape italic
playbin
\shape default
element and with the identifier MultimediaPlayer.
\end_layout
\begin_layout LyX-Code
self.player = gst.element_factory_make(
\begin_inset Quotes eld
\end_inset
playbin
\begin_inset Quotes erd
\end_inset
,
\begin_inset Quotes eld
\end_inset
MultimediaPlayer
\begin_inset Quotes erd
\end_inset
)
\end_layout
\begin_layout Standard
Next a bus is created to be used with the pipeline and signal watching is
added.
Signals are connected to the self._on_message method.
\end_layout
\begin_layout LyX-Code
bus = self.player.get_bus()
\end_layout
\begin_layout LyX-Code
bus.add_signal_watch()
\end_layout
\begin_layout LyX-Code
bus.connect(
\begin_inset Quotes eld
\end_inset
message
\begin_inset Quotes erd
\end_inset
, self.on_message)
\end_layout
\begin_layout Standard
After this is used the enable_sync_message_emission() method on the bus
to enable the player to sync the GStreamer video with the video widget
that is being used.
If this is not done, a separate window will be opened that is outside of
the running application to play the video in.
The sync emissions signals are directed to the self.on_sync_message method.
\end_layout
\begin_layout LyX-Code
bus.enable_sync_message_emission()
\end_layout
\begin_layout LyX-Code
bus.connect(
\begin_inset Quotes eld
\end_inset
sync-message::element
\begin_inset Quotes erd
\end_inset
, self.on_sync_message)
\end_layout
\begin_layout Standard
The on_sync_message method uses the video widget that has been passed in
during the initialization of GstPlayer to attach the video to the application.
This is a little hocus pocus that I am not really sure of what is happening.
It seems to be communicating somewhat with the underlying X Windows about
the window id.
\end_layout
\begin_layout Standard
Then the set_sink method that is in video widget class is set to the message
src.
The set_sink method is used for convenience.
The VideoWidget class that is discussed in the next section.
\end_layout
\begin_layout Standard
The very last part is to set force the aspect ratio of the video.
\end_layout
\begin_layout LyX-Code
def on_sync_message(self, bus, message):
\end_layout
\begin_layout LyX-Code
if message.structure is None:
\end_layout
\begin_layout LyX-Code
return False
\end_layout
\begin_layout LyX-Code
if message.structure.get_name() ==
\begin_inset Quotes eld
\end_inset
prepare-xwindow-id
\begin_inset Quotes erd
\end_inset
:
\end_layout
\begin_layout LyX-Code
self.videowidget.set_sink(message.src)
\end_layout
\begin_layout LyX-Code
message.src.set_property(
\begin_inset Quotes eld
\end_inset
force-aspect-ratio
\begin_inset Quotes erd
\end_inset
, True)
\end_layout
\begin_layout Standard
The rest of what is covered by the GstPlayer class is very self explanatory.
A
\shape italic
play
\shape default
,
\shape italic
stop
\shape default
, and
\shape italic
pause
\shape default
method to play, pause, or stop the media file.
And there is also a method call
\shape italic
set_location
\shape default
that sets the location of the media file that is to be played.
\end_layout
\begin_layout Subsection
VideoWidget
\end_layout
\begin_layout Standard
The VideoWidget class is a subclass of gtk.DrawingArea.
It is created to ease the use of displaying videos inside of applications
and is used in conjunction with the user interface and GstPlayer class.
\end_layout
\begin_layout LyX-Code
class VideoWidget(gtk.DrawingArea):
\end_layout
\begin_layout LyX-Code
\begin_inset Quotes eld
\end_inset
\begin_inset Quotes erd
\end_inset
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout LyX-Code
Extend gtk.DrawingArea to create our own video widget.
\end_layout
\begin_layout LyX-Code
\begin_inset Quotes eld
\end_inset
\begin_inset Quotes erd
\end_inset
\begin_inset Quotes erd
\end_inset
\end_layout
\begin_layout LyX-Code
def __init__(self):
\end_layout
\begin_layout LyX-Code
gtk.DrawingArea.__init__(self)
\end_layout
\begin_layout LyX-Code
self.imagesink = None
\end_layout
\begin_layout LyX-Code
self.unset_flags(gtk.DOUBLE_BUFFERED)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def do_expose_event(self, event):
\end_layout
\begin_layout LyX-Code
if self.imagesink:
\end_layout
\begin_layout LyX-Code
self.imagesink.expose()
\end_layout
\begin_layout LyX-Code
return False
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
return True
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def set_sink(self, sink):
\end_layout
\begin_layout LyX-Code
if sys.platform ==
\begin_inset Quotes eld
\end_inset
win32
\begin_inset Quotes erd
\end_inset
:
\end_layout
\begin_layout LyX-Code
win_id = self.window.handle
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
win_id = self.window.xid
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
assert win_id
\end_layout
\begin_layout LyX-Code
self.imagesink = sink
\end_layout
\begin_layout LyX-Code
self.imagesink.set_xwindow_id(win_id)
\end_layout
\begin_layout Standard
VideoWidget starts off with the initialization of the DrawingArea parent
class.
It then proceeds to set the imagesink to none.
Last in the initialization is to unset the double buffering.
\end_layout
\begin_layout Standard
The set_sink method asserts that the window xid is available and then proceeds
to set the imagesink to the sink that is passed in.
The sink is passed in from the GstPlayer class.
Then while still in the set_sink method the imagesink sets the id of the
xwindow to self.window.xid.
\end_layout
\begin_layout Standard
With the VideoWidget class now completed it is simple to use to display
videos.
To use it with a PyGTK application you would just initialize in your PyGTK
GUI code like so:
\end_layout
\begin_layout LyX-Code
videowidget = VideoWidget()
\end_layout
\begin_layout Subsection
User Interface
\end_layout
\begin_layout Standard
A good user interface is required to make the ability to play media files
useful.
The user interface will allow the user to interact with the program, open
audio or video files, and if the file is a video then resize it to fullscreen.
\end_layout
\begin_layout LyX-Code
class Main(object):
\end_layout
\begin_layout LyX-Code
"""
\end_layout
\begin_layout LyX-Code
The Main class is the Gui.
It creates an instance of the
\end_layout
\begin_layout LyX-Code
GstPlayer class and the FileInfo class.
It is what the user
\end_layout
\begin_layout LyX-Code
interacts with and controls what happens.
\end_layout
\begin_layout LyX-Code
"""
\end_layout
\begin_layout LyX-Code
def __init__(self):
\end_layout
\begin_layout LyX-Code
#Store the location of the multimedia file
\end_layout
\begin_layout LyX-Code
self.multimedia_file = None
\end_layout
\begin_layout LyX-Code
# To be used with the FileInfo Class
\end_layout
\begin_layout LyX-Code
self.file_info = None
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Create the GUI
\end_layout
\begin_layout LyX-Code
self.win = gtk.Window()
\end_layout
\begin_layout LyX-Code
self.win.set_title("Play Video Example 2")
\end_layout
\begin_layout LyX-Code
self.win.connect("delete_event", lambda w,e:
\end_layout
\begin_layout LyX-Code
gtk.main_quit())
\end_layout
\begin_layout LyX-Code
vbox = gtk.VBox(False, 0)
\end_layout
\begin_layout LyX-Code
self.control_box = gtk.HBox(False, 0)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Control Buttons
\end_layout
\begin_layout LyX-Code
self.load_file_button = gtk.FileChooserButton(
\end_layout
\begin_layout LyX-Code
"Choose Audio File")
\end_layout
\begin_layout LyX-Code
self.play_button = gtk.Button("Play",
\end_layout
\begin_layout LyX-Code
gtk.STOCK_MEDIA_PLAY)
\end_layout
\begin_layout LyX-Code
self.pause_button = gtk.Button("Pause",
\end_layout
\begin_layout LyX-Code
gtk.STOCK_MEDIA_PAUSE)
\end_layout
\begin_layout LyX-Code
self.stop_button = gtk.Button("Stop", gtk.STOCK_MEDIA_STOP)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Video Widget Stuff
\end_layout
\begin_layout LyX-Code
self.videowidget = VideoWidget()
\end_layout
\begin_layout LyX-Code
self.videowidget.set_size_request(400, 250)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Signals and Callbacks
\end_layout
\begin_layout LyX-Code
self.load_file_button.connect("selection-changed",
\end_layout
\begin_layout LyX-Code
self.on_file_selected)
\end_layout
\begin_layout LyX-Code
self.play_button.connect("clicked", self.on_play_clicked)
\end_layout
\begin_layout LyX-Code
self.pause_button.connect("clicked", self.on_pause_clicked)
\end_layout
\begin_layout LyX-Code
self.stop_button.connect("clicked", self.on_stop_clicked)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Fullscreen stuff
\end_layout
\begin_layout LyX-Code
self.win.connect("key-press-event",
\end_layout
\begin_layout LyX-Code
self.on_win_key_press_event)
\end_layout
\begin_layout LyX-Code
self.win.connect("window-state-event",
\end_layout
\begin_layout LyX-Code
self.on_window_state_event)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.control_box.pack_start(self.play_button,
\end_layout
\begin_layout LyX-Code
False, True, 0)
\end_layout
\begin_layout LyX-Code
self.control_box.pack_start(self.pause_button,
\end_layout
\begin_layout LyX-Code
False, True, 0)
\end_layout
\begin_layout LyX-Code
self.control_box.pack_start(self.stop_button,
\end_layout
\begin_layout LyX-Code
False, True, 0)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(self.load_file_button, False, True, 0)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(self.videowidget, True, True, 0)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# You want to expand the video widget or else you
\end_layout
\begin_layout LyX-Code
#cannot see it
\end_layout
\begin_layout LyX-Code
vbox.pack_start(self.control_box, False, True, 0)
\end_layout
\begin_layout LyX-Code
self.win.add(vbox)
\end_layout
\begin_layout LyX-Code
self.win.show_all()
\end_layout
\begin_layout LyX-Code
self.gst_player = GstPlayer(self.videowidget)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def fullscreen_mode(self):
\end_layout
\begin_layout LyX-Code
"""
\end_layout
\begin_layout LyX-Code
Called from the on_win_key_press_event method.
If the
\end_layout
\begin_layout LyX-Code
program is in fullscreen this method will unfullscreen
\end_layout
\begin_layout LyX-Code
it.
If the program is not in fullscreen it will set it
\end_layout
\begin_layout LyX-Code
to fullscreen.
This method will also hide the controls
\end_layout
\begin_layout LyX-Code
while in fullscreen mode.
\end_layout
\begin_layout LyX-Code
"""
\end_layout
\begin_layout LyX-Code
if self.__is_fullscreen:
\end_layout
\begin_layout LyX-Code
self.win.unfullscreen()
\end_layout
\begin_layout LyX-Code
self.control_box.show()
\end_layout
\begin_layout LyX-Code
self.load_file_button.show()
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
self.control_box.hide()
\end_layout
\begin_layout LyX-Code
self.load_file_button.hide()
\end_layout
\begin_layout LyX-Code
self.win.fullscreen()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_win_key_press_event(self, widget, event):
\end_layout
\begin_layout LyX-Code
"""
\end_layout
\begin_layout LyX-Code
Handle any key press event on the main window.
\end_layout
\begin_layout LyX-Code
This method is being used to detect when the ESC key
\end_layout
\begin_layout LyX-Code
is being pressed in fullscreen to take the
\end_layout
\begin_layout LyX-Code
window out of fullscreen
\end_layout
\begin_layout LyX-Code
"""
\end_layout
\begin_layout LyX-Code
key = gtk.gdk.keyval_name(event.keyval)
\end_layout
\begin_layout LyX-Code
if key == "Escape" or key == "f":
\end_layout
\begin_layout LyX-Code
self.fullscreen_mode()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_window_state_event(self, widget, event):
\end_layout
\begin_layout LyX-Code
"""
\end_layout
\begin_layout LyX-Code
Detect window state events to determine whether in
\end_layout
\begin_layout LyX-Code
fullscreen or not in fullscreen
\end_layout
\begin_layout LyX-Code
"""
\end_layout
\begin_layout LyX-Code
self.__is_fullscreen = bool(event.new_window_state &
gtk.gdk.WINDOW_STATE_FULLSCREEN)
\end_layout
\begin_layout LyX-Code
print "Is fullscreen: ", self.__is_fullscreen
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_file_selected(self, widget):
\end_layout
\begin_layout LyX-Code
print "Selected: ", self.load_file_button.get_filename()
\end_layout
\begin_layout LyX-Code
self.multimedia_file = self.load_file_button.get_filename()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Do not call method from here immediately.
\end_layout
\begin_layout LyX-Code
# FileInfo.poll() will return false when it is ready.
\end_layout
\begin_layout LyX-Code
# Usually a second or two.
\end_layout
\begin_layout LyX-Code
self.file_info = MediaInfo(self.multimedia_file)
\end_layout
\begin_layout LyX-Code
self.gst_player.set_location(
\end_layout
\begin_layout LyX-Code
self.multimedia_file )
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_play_clicked(self, widget):
\end_layout
\begin_layout LyX-Code
print "play clicked"
\end_layout
\begin_layout LyX-Code
print "Video (width, height): ",
\end_layout
\begin_layout LyX-Code
self.file_info.get_width(),
\end_layout
\begin_layout LyX-Code
self.file_info.get_height()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.videowidget.set_size_request(
\end_layout
\begin_layout LyX-Code
self.file_info.get_width(),
\end_layout
\begin_layout LyX-Code
self.file_info.get_height() )
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.gst_player.play()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_pause_clicked(self, widget):
\end_layout
\begin_layout LyX-Code
print "pause clicked"
\end_layout
\begin_layout LyX-Code
self.gst_player.pause()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_stop_clicked(self, widget):
\end_layout
\begin_layout LyX-Code
print "stop clicked"
\end_layout
\begin_layout LyX-Code
self.gst_player.stop()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
Main()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Standard
The Main class starts off by initializing a few variables and creating the
required user interface code.
\end_layout
\begin_layout Standard
The multimedia_file is set to None and will store the location of the media
file that is to be played.
\end_layout
\begin_layout Standard
The file_info variable is set to None and will be later used to initialize
the MediaInfo class.
\end_layout
\begin_layout Standard
After declaring the first class instance variables the Main class creates
the user interface, sets the title to
\begin_inset Quotes eld
\end_inset
Play Video Example 2
\begin_inset Quotes erd
\end_inset
, and adds a few buttons to play, pause, and stop the video.
It also adds a gtk.FileChooserButton video to select the media files that
will be played.
\end_layout
\begin_layout Standard
Next it creates a video widget using the VideoWidget class that was described
above.
\end_layout
\begin_layout Standard
At the very bottom of the of the __init__ method the class variable self.gst_play
er is initialized as an instance of the GstPlayer class using the self.videowidge
t instance that was created.
\end_layout
\begin_layout LyX-Code
self.gst_player = GstPlayer(self.videowidget)
\end_layout
\begin_layout Standard
On the clicked signal is emitted from the play, pause, or stop button; then
the methods on_play_clicked, on_pause_clicked, and on_stop_clicked are
called respective to their buttons.
\end_layout
\begin_layout Standard
When a file is selected with the load_file_button the on_file_selected method
is called.
\end_layout
\begin_layout Standard
In the section of the code commented as full screen stuff it will connect
key-press-event signals and window-state-event signals to the on_win_key_press_
event and on_window_state_event methods.
The on_win_key_press_event method will detect if the key pressed is the
\begin_inset Quotes eld
\end_inset
F
\begin_inset Quotes erd
\end_inset
or
\begin_inset Quotes eld
\end_inset
Esc
\begin_inset Quotes erd
\end_inset
key and if so call the fullscreen_mode() method.
If it is fullscreen it will unfullscreen the video.
If it is not in full screen it will set it to fullscreen.
\end_layout
\begin_layout Standard
The on_window_state_event detects changes in the window state.
All that it is used for is to set the variable self.__is_fullscreen to True
or false.
This variable is used in the method fullscreen_mode() to either hide the
control buttons (play, pause, stop, load_file) or show them.
If the variable is set to False it will set these widgets to be displayed
and unfullscreen the video widget.
If the self.__full_screen is True it will hide all the control widgets and
set the video widget to fullscreen with self.win.fullscreen().
\end_layout
\begin_layout Standard
Next is the on_file_selected method.
This method is called when the a file selected from the gtk.FileChooserButton
load_file_button.
It stores the location of the file in the variable self.multimedia_file.
After the file location has been stored it is used to create an instance
of the MediaInfo class.
\end_layout
\begin_layout LyX-Code
self.file_info = MediaInfo(self.multimedia_file)
\end_layout
\begin_layout Standard
And finally in the on_file_selected method the location of the media file
is loaded into the the GstPlayer instance self.gst_player.
\end_layout
\begin_layout LyX-Code
self.gst_player.set_location(self.multimedia_file)
\end_layout
\begin_layout Standard
The on_play_clicked method will use the MediaInfo instance self.file_info
to get the width and height of the video and set the size of the self.videowdige
t to the correct dimensions to display the video.
\end_layout
\begin_layout Standard
After this it will set the video playing by calling the play method in the
GstPlayer class:
\end_layout
\begin_layout LyX-Code
self.gst_player.play()
\end_layout
\begin_layout Standard
As with the on_play_clicked method the on_pause_clicked and on_stop_clicked
methods will use call the pause and stop methods from the GstPlayer class.
\end_layout
\begin_layout LyX-Code
self.gst_player.pause()
\end_layout
\begin_layout LyX-Code
self.gst_player.stop()
\end_layout
\begin_layout Standard
The only thing that is left to do is to make sure the Main class is called
and this is accomplished at the bottom of the source code, which detects
if this is file is the main file being run and enters the GTK mainloop.
\end_layout
\begin_layout LyX-Code
if __name__ ==
\begin_inset Quotes eld
\end_inset
__main
\begin_inset Quotes erd
\end_inset
:
\end_layout
\begin_layout LyX-Code
Main()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Section
Summary
\end_layout
\begin_layout Standard
Basically GStreamer is a very powerful framework with many options available.
Even though this chapter only covered a small portion of what is available,
as a programmer you should now be able to add audio and video play back
to your application.
\end_layout
\begin_layout Standard
As well as seek the position of the media file and display the current location
and length of the file to the user.
\end_layout
\begin_layout Standard
For more information using GStreamer visit the following sites:
\end_layout
\begin_layout Enumerate
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://pygstdocs.berlios.de/
\end_layout
\end_inset
Contains tutorials and documentation on python GStreamer
\end_layout
\begin_layout Enumerate
The main C documentation.
If you do not know C this may not be of use, but it may, anyway
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://gstreamer.freedesktop.org/data/doc/gstreamer/head/manual/html/index.html
\end_layout
\end_inset
\end_layout
\begin_layout Enumerate
Check out all the examples that come with the PyGST source at
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://webcvs.freedesktop.org/gstreamer/gst-python/examples/
\end_layout
\end_inset
\end_layout
\begin_layout Enumerate
And of course check out the examples that come with this books website
\begin_inset Flex URL
status collapsed
\begin_layout Plain Layout
http://www.majorsilence.com/rubbish/pygtk-book/
\end_layout
\end_inset
\end_layout
\begin_layout Chapter
Dbus Interprocess Communication
\end_layout
\begin_layout Standard
\begin_inset Box Frameless
position "t"
hor_pos "c"
has_inner_box 1
inner_pos "t"
use_parbox 0
width "100col%"
special "none"
height "1in"
height_special "totalheight"
status open
\begin_layout Plain Layout
\begin_inset CommandInset include
LatexCommand input
filename "chapter-heading.lyx"
\end_inset
\end_layout
\end_inset
\end_layout
\begin_layout Section
Introduction
\end_layout
\begin_layout Standard
DBus is used for interprocess communication between applications.
Simply put this means that applications can retrieve information from one
another by accessing special methods that are provided by DBus.
\end_layout
\begin_layout Description
ObjectPath
\begin_inset CommandInset label
LatexCommand label
name "des:ObjectPath-An-application"
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
DBus!ObjectPath
\end_layout
\end_inset
An application may export an object to represent itself or different parts
of itself.
For example Rhythmbox exports an object representing the Play List and
an object representing the current playing song.
Eg.
/org/gnome/Rhythmbox/Player
\end_layout
\begin_layout Description
BusName
\begin_inset CommandInset label
LatexCommand label
name "des:BusName-The-name"
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
DBus!BusName
\end_layout
\end_inset
The name of the application as exposed through DBus.
Eg.
org.gnome.Rhythmbox
\end_layout
\begin_layout Description
Interface
\begin_inset CommandInset label
LatexCommand label
name "des:Interface-Is-used"
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
DBus!Interface
\end_layout
\end_inset
Is used to access methods through DBus.
Eg.
org.gnome.Rhythmbox
\end_layout
\begin_layout Standard
This chapters purpose is to show how to control other applications with
DBus and how to add DBus to your PyGTK applications so that you can expose
functionality of your applications to others.
\end_layout
\begin_layout Section
Controlling Applications
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sec:DBus - Controlling Applications"
\end_inset
\end_layout
\begin_layout Standard
First off is going to be an example of how to use dbus to communicate with
another application.
This example will communicate with the rhythmbox music player.
The reason for using rhythmbox is because it is it is rather ubiquitous
in the gnome distro world.
\end_layout
\begin_layout LyX-Code
#!/usr/bin/env python
\end_layout
\begin_layout LyX-Code
import os, gobject, dbus
\end_layout
\begin_layout LyX-Code
from dbus.mainloop.glib import DBusGMainLoop
\end_layout
\begin_layout LyX-Code
import gtk
\end_layout
\begin_layout Standard
The above code imports the needed code to work with this example.
What is needed to work with DBus is the
\emph on
dbus
\emph default
module and
\emph on
DBusGMainLoop
\emph default
.
The dbus module is used for the common dbus interactions while DBusGMainLoop
is used to work with gobject main loops, which PyGTK uses.
\end_layout
\begin_layout Standard
Here the class DBusExample is created with the __init__ method setting up
the dbus.
\end_layout
\begin_layout LyX-Code
class DBusExample(object):
\end_layout
\begin_layout LyX-Code
def __init__(self):
\end_layout
\begin_layout LyX-Code
# Do before session or system bus is created.
\end_layout
\begin_layout LyX-Code
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
\end_layout
\begin_layout LyX-Code
self.bus = dbus.SessionBus()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.proxy_object = self.bus.get_object('org.gnome.Rhythmbox',
\end_layout
\begin_layout LyX-Code
'/org/gnome/Rhythmbox/Player')
\end_layout
\begin_layout LyX-Code
self.player = dbus.Interface(self.proxy_object,
\end_layout
\begin_layout LyX-Code
'org.gnome.Rhythmbox.Player')
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.bus.add_signal_receiver(self.on_song_changed,
\end_layout
\begin_layout LyX-Code
dbus_interface="org.gnome.Rhythmbox.Player",
\end_layout
\begin_layout LyX-Code
signal_name="playingUriChanged")
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.init_gui()
\end_layout
\begin_layout LyX-Code
self.list_available_commands()
\end_layout
\begin_layout Standard
To begin with a DBus SessionBus is created.
This allows connecting to other applications.
If this example were connecting to a system process it would use a SystemBus.
Once the bus is created, a proxy object is assigned to self.proxy_object
using the self.bus.get_object method.
The get_object method takes as arguments the applications Bus Name(
\begin_inset CommandInset ref
LatexCommand pageref
reference "des:BusName-The-name"
\end_inset
) and Object Path(
\begin_inset CommandInset ref
LatexCommand pageref
reference "des:ObjectPath-An-application"
\end_inset
).
\end_layout
\begin_layout Standard
After the proxy_object has been created it is used to create an interface
to the available methods.
The interface self.player is created using the dbus.Interface class.
It is initlized with the self.proxy_object and using the org.gnome.Rhythmbox.Player
interface.
This interface provides for methods to control and retrieve information
on the currently playing song.
\end_layout
\begin_layout Standard
Below this a signal handler is created on the bus to catch the playingUriChanged
Signal from the interface org.gnome.Rhythmbox.Player and call the on_song_changed
method.
\end_layout
\begin_layout Standard
Lastly in the __init__ method the init_gui method is called.
The init_gui method is rather insignificant as it creates a small gui with
a few buttons.
However the callback methods for those buttons use the self.player interface
to control rhythmbox.
\end_layout
\begin_layout LyX-Code
def init_gui(self):
\end_layout
\begin_layout LyX-Code
win = gtk.Window()
\end_layout
\begin_layout LyX-Code
win.connect("delete_event", lambda w,e:gtk.main_quit())
\end_layout
\begin_layout LyX-Code
vbox = gtk.VBox()
\end_layout
\begin_layout LyX-Code
hbox = gtk.HBox()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.output = gtk.Label("")
\end_layout
\begin_layout LyX-Code
vbox.pack_start(self.output, False, True, 0)
\end_layout
\begin_layout LyX-Code
mute=gtk.Button("Mute")
\end_layout
\begin_layout LyX-Code
play_pause=gtk.Button("Play/Pause")
\end_layout
\begin_layout LyX-Code
previous=gtk.Button("Previous")
\end_layout
\begin_layout LyX-Code
next=gtk.Button("Next")
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
mute.connect("clicked", self.on_mute_clicked)
\end_layout
\begin_layout LyX-Code
play_pause.connect("clicked", self.on_play_pause_clicked)
\end_layout
\begin_layout LyX-Code
previous.connect("clicked", self.on_previous_clicked)
\end_layout
\begin_layout LyX-Code
next.connect("clicked", self.on_next_clicked)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
hbox.pack_start(mute, False, True, 0)
\end_layout
\begin_layout LyX-Code
hbox.pack_start(play_pause, False, True, 0)
\end_layout
\begin_layout LyX-Code
hbox.pack_start(previous, False, True, 0)
\end_layout
\begin_layout LyX-Code
hbox.pack_start(next, False, True, 0)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(hbox, False, True, 0)
\end_layout
\begin_layout LyX-Code
win.add(vbox)
\end_layout
\begin_layout LyX-Code
win.show_all()
\end_layout
\begin_layout Standard
The init_gui method above creates a small user interface with a play/pause,
mute, previous and next button to control rhythmbox.
It also as a label that is used by the on_song_changed callback method,
that was specified in the __init__ method, to display the path and name
of the current playing song.
\end_layout
\begin_layout LyX-Code
def on_mute_clicked(self, widget):
\end_layout
\begin_layout LyX-Code
if self.player.getMute():
\end_layout
\begin_layout LyX-Code
self.player.setMute(False)
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
self.player.setMute(True)
\end_layout
\begin_layout Standard
The on_mute_clicked method checks to see if rhythmbox is muted, if it is
it will unmute it.
If it is not muted it will set it to mute.
This is accomplished using the self.player interface with the setMute method,
which takes a boolean argument.
\end_layout
\begin_layout LyX-Code
def on_play_pause_clicked(self, widget):
\end_layout
\begin_layout LyX-Code
if self.player.getPlaying():
\end_layout
\begin_layout LyX-Code
self.player.playPause(False)
\end_layout
\begin_layout LyX-Code
else:
\end_layout
\begin_layout LyX-Code
self.player.playPause(True)
\end_layout
\begin_layout Standard
The on_play_pause_clicked method will set rhythmbox to play if it is paused
and pause it if it is playing.
This is accomplished using the self.player interface with the playPause
method, which takes a boolean argument.
\end_layout
\begin_layout LyX-Code
def on_previous_clicked(self, widget):
\end_layout
\begin_layout LyX-Code
self.player.previous()
\end_layout
\begin_layout Standard
The on_previous_clicked method will set rhythmbox to play the previous played
song.
This is accomplished using the self.player interface with the previous method.
\end_layout
\begin_layout LyX-Code
def on_next_clicked(self, widget):
\end_layout
\begin_layout LyX-Code
self.player.next()
\end_layout
\begin_layout Standard
The on_next_clicked method will set rhythmbox to play the next song.
This is accomplished using the self.player interface with the next method.
\end_layout
\begin_layout LyX-Code
def on_song_changed(self, data):
\end_layout
\begin_layout LyX-Code
path, filename = os.path.split(self.player.getPlayingUri())
\end_layout
\begin_layout LyX-Code
self.output.set_text("Path: " + path + "
\backslash
nFilename: " + filename)
\end_layout
\begin_layout Standard
The on_song_changed method is called when the playingUriChanged signal is
emitted.
It retrieves the current songs current uri, splitting it into a path and
file name, and displays it using a gtk label.
It should also be pointed out that instead of using the getPlayUri() method,
the data argument could be used as it is the uri of the current song as
well.
\end_layout
\begin_layout Standard
And last lets not forget the small amount of code to run this example
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
app = DBusExample()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Section
Adding DBus to your Applications
\end_layout
\begin_layout Standard
Controlling other applications using DBus is one thing but it is not enough
if you application needs to allow others to control it.
To let other applications have access to your program requires explosing
methods of sub class of dbus.service.Object.
\end_layout
\begin_layout Subsection
Creating a DBus Service
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:DBus - Creating a DBus Service"
\end_inset
\end_layout
\begin_layout Standard
To start off a few modlues need to be imported.
The import ones are the DBus ones.
\end_layout
\begin_layout LyX-Code
#!/usr/bin/env python
\end_layout
\begin_layout LyX-Code
import os, gobject, dbus, dbus.service
\end_layout
\begin_layout LyX-Code
from dbus.mainloop.glibimport DBusGMainLoop
\end_layout
\begin_layout LyX-Code
import gtk
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
output_label = None
\end_layout
\begin_layout Standard
So of the above modules dbus, dbus.service and DBusGMainLoop are what are
important for allowing other applications to connect to his one.
After the imports there is the output_label which will be used as a global
to create a gtk.Label to display messages that are received through DBus.
\end_layout
\begin_layout Standard
After this DBusObject class is created; it can be named whatever you want
as long as it subclasses dbus.service.Object.
As you will see it is not necessary to create __init__ method with this
class as the parent classes can be used.
\end_layout
\begin_layout LyX-Code
class DBusObject(dbus.service.Object):
\end_layout
\begin_layout LyX-Code
# Display and message to gtk label and return message to caller
\end_layout
\begin_layout LyX-Code
@dbus.service.method('com.majorsilence.MessageInterface',
\end_layout
\begin_layout LyX-Code
in_signature='', out_signature='s')
\end_layout
\begin_layout LyX-Code
def display_welcome_message(self):
\end_layout
\begin_layout LyX-Code
global output_label
\end_layout
\begin_layout LyX-Code
output_label.set_text("Welcome to dbus.")
\end_layout
\begin_layout LyX-Code
return "Welcome to dbus."
\end_layout
\begin_layout Standard
To expose methods for the @dbus.service.method decorator is used, specifying
the DBus Interface that the method will available on and the methods in
(arguments) and out (return value) signatures.
Here the interface is specified as com.majorsilence.MessageInterface, so
any application calling this method would have to use com.majorsilence.MessageInt
erface.
After the decorator declare the method as normal.
The method name is the same name that will be exposed.
\end_layout
\begin_layout Standard
So what we end up with here is a method called display_welcome_message that
returns a string, s meaning it is a dbus.String type (see
\begin_inset CommandInset ref
LatexCommand vref
reference "sec:Dbus - Types"
\end_inset
).
As can be seen it sets the label to
\begin_inset Quotes eld
\end_inset
Welcome to dbus
\begin_inset Quotes erd
\end_inset
and returns the same message to the calling program.
\end_layout
\begin_layout Standard
Moving on to the next method, it takes a string as an argument emits a signal
and completion and returns nothing.
\end_layout
\begin_layout LyX-Code
# Set gtk label to the message that is passed
\end_layout
\begin_layout LyX-Code
@dbus.service.method(dbus_interface='com.majorsilence.MessageInterface', in_signat
ure='s', out_signature='')
\end_layout
\begin_layout LyX-Code
def set_message(self, s):
\end_layout
\begin_layout LyX-Code
global output_label
\end_layout
\begin_layout LyX-Code
if not isinstance(s, dbus.String):
\end_layout
\begin_layout LyX-Code
print "not string"
\end_layout
\begin_layout LyX-Code
return
\end_layout
\begin_layout LyX-Code
output_label.set_text(s)
\end_layout
\begin_layout LyX-Code
#emit signal
\end_layout
\begin_layout LyX-Code
self.message_signal()
\end_layout
\begin_layout Standard
As before and like all exposed DBus methods the @dbus.service.method decorator
is used.
This method has the same DBus Interface as the first method, com.majorsilence.Mes
sageInterface, and an in_signature of s meaning a dbus.String (see
\begin_inset CommandInset ref
LatexCommand vref
reference "sec:Dbus - Types"
\end_inset
).
\end_layout
\begin_layout Standard
The method is set_message, it takes as an argument a string.
It checks to make sure it was passed a string, if it was it will set the
label to the string that was passed in.
The interesting thing about this method compared to the first one is that
it emits a signal on completion.
It does this by calling the self.message_signal() method as its last act.
\end_layout
\begin_layout Standard
The self.message_signal is the method that is described next.
It to uses a dbus decorator, but instead of using the @dbus.service.method
decorator, it uses the @dbus.service.signal decorator.
What this means is that when this method is called it will emit a signal
that can be caught using the add_signal_receiver method that was described
in
\begin_inset CommandInset ref
LatexCommand vref
reference "sec:DBus - Controlling Applications"
\end_inset
.
\end_layout
\begin_layout LyX-Code
@dbus.service.signal('com.majorsilence.MessageInterface')
\end_layout
\begin_layout LyX-Code
def message_signal(self):
\end_layout
\begin_layout LyX-Code
return
\end_layout
\begin_layout Standard
As can be seen the message_signal method uses the @dbus.service.signal decorator
and specifies the com.majorsilence.MessageInterface.
If it is to include data with its signal it should also have a out_signature
specifying the correct type.
\end_layout
\begin_layout Standard
All that is left is the the main() function that is used to setup a very
small PyGTK GUI and create the neccesary DBus initiation.
\end_layout
\begin_layout LyX-Code
def main():
\end_layout
\begin_layout LyX-Code
# Create GTK Gui
\end_layout
\begin_layout LyX-Code
global output_label
\end_layout
\begin_layout LyX-Code
win = gtk.Window()
\end_layout
\begin_layout LyX-Code
win.connect("delete_event", lambda w,e:gtk.main_quit())
\end_layout
\begin_layout LyX-Code
output_label = gtk.Label("This message will change through using dbus.")
\end_layout
\begin_layout LyX-Code
win.add(output_label)
\end_layout
\begin_layout LyX-Code
win.show_all()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
# Start DBus Service
\end_layout
\begin_layout LyX-Code
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
\end_layout
\begin_layout LyX-Code
session_bus = dbus.SessionBus()
\end_layout
\begin_layout LyX-Code
name = dbus.service.BusName("com.majorsilence.MessageService", session_bus)
\end_layout
\begin_layout LyX-Code
object = DBusObject(session_bus, "/TestObject")
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Standard
The important part of the code starts after the # Start DBus Service comment.
These four lines of code are what makes available dbus and makes it possible
to expose method of the application to any other DBus capable program.
First DBus must be set to use the glib gobject main loop (the same that
PyGTK uses), without this it will not work.
Next it creates a session bus that allows applications to connect to a
bus.
After this it uses the session bus to create a bus name using the dbus.service.Bu
sName class.
It takes as arguements the session bus that was created and the interface
com.majorsilence.MessageService.
\end_layout
\begin_layout Standard
Finally the object is create calling the DBusObject class that we have created,
using the session bus that we have created and using the /TestObject object
path.
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
main()
\end_layout
\begin_layout Standard
Of course do not forget to call the main function that runs the the example
PyGTK DBus service application.
\end_layout
\begin_layout Subsection
Connecting to your DBus Service
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sub:DBus - Connecting to your DBus Service"
\end_inset
\end_layout
\begin_layout Standard
Controlling your own application through DBus is very similiar to how the
first example controlled Rhythmbox.
This is a small application that will call the two exposed methods from
\begin_inset CommandInset ref
LatexCommand ref
reference "sub:DBus - Creating a DBus Service"
\end_inset
and handle the signal that is emitted.
\end_layout
\begin_layout LyX-Code
#!/usr/bin/env python
\end_layout
\begin_layout LyX-Code
import os, gobject,dbus
\end_layout
\begin_layout LyX-Code
from dbus.mainloop.glib import DBusGMainLoop
\end_layout
\begin_layout LyX-Code
import gtk
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
class DBusClient(object):
\end_layout
\begin_layout LyX-Code
def __init__(self):
\end_layout
\begin_layout LyX-Code
# Do before session or system bus is created.
\end_layout
\begin_layout LyX-Code
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
\end_layout
\begin_layout LyX-Code
self.bus = dbus.SessionBus()
\end_layout
\begin_layout LyX-Code
self.proxy = self.bus.get_object('com.majorsilence.MessageService',
\end_layout
\begin_layout LyX-Code
'/TestObject')
\end_layout
\begin_layout LyX-Code
self.control_interface = dbus.Interface(self.proxy,
\end_layout
\begin_layout LyX-Code
'com.majorsilence.MessageInterface')
\end_layout
\begin_layout LyX-Code
self.bus.add_signal_receiver(self.on_message_recieved,
\end_layout
\begin_layout LyX-Code
dbus_interface="com.majorsilence.MessageInterface",
\end_layout
\begin_layout LyX-Code
signal_name="message_signal")
\end_layout
\begin_layout Standard
As can be seen, connect to the bus name com.majorsilence.MessageSerive using
the objec path /TestObject.
Next the interface is created using self.proxy and the interface com.majorsilence.
MessageInterface.
Finally the signal message_signal is handled by connecting it to the self.on_mes
sage_recieved method when it is emitted from the com.majorsilence.MessageInterface
interface.
\end_layout
\begin_layout LyX-Code
win = gtk.Window()
\end_layout
\begin_layout LyX-Code
win.connect("delete_event", lambda w,e:gtk.main_quit())
\end_layout
\begin_layout LyX-Code
vbox = gtk.VBox()
\end_layout
\begin_layout LyX-Code
hbox = gtk.HBox()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
self.text_message=gtk.Entry()
\end_layout
\begin_layout LyX-Code
set_message=gtk.Button("Set Message")
\end_layout
\begin_layout LyX-Code
display_message=gtk.Button("Display Welcome Message")
\end_layout
\begin_layout LyX-Code
set_message.connect("clicked", self.on_set_message_clicked)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
display_message.connect("clicked",
\end_layout
\begin_layout LyX-Code
self.on_display_message_clicked)
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
hbox.pack_start(set_message, False, True, 0)
\end_layout
\begin_layout LyX-Code
hbox.pack_start(display_message, False, True, 0)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(self.text_message, False, True, 0)
\end_layout
\begin_layout LyX-Code
vbox.pack_start(hbox, False, True, 0)
\end_layout
\begin_layout LyX-Code
win.add(vbox)
\end_layout
\begin_layout LyX-Code
win.show_all()
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
def on_message_recieved(self):
\end_layout
\begin_layout LyX-Code
print "message_signal caught"
\end_layout
\begin_layout Standard
When the signal is emitted it does nothing prints a message to the console.
\begin_inset Note Note
status collapsed
\begin_layout Plain Layout
Probably want to change this to displaying in a gtk.label, Can always do
this later.
\end_layout
\end_inset
\end_layout
\begin_layout LyX-Code
def on_set_message_clicked(self, widget):
\end_layout
\begin_layout LyX-Code
message = self.text_message.get_text()
\end_layout
\begin_layout LyX-Code
self.control_interface.set_message(message)
\end_layout
\begin_layout Standard
When the set message button is clicked it grabs the text from the text entry
and uses the self.control_interface to set the label in the serve appliction
to whatever text was typed in.
\end_layout
\begin_layout LyX-Code
def on_display_message_clicked(self, widget):
\end_layout
\begin_layout LyX-Code
print self.control_interface.display_welcome_message()
\end_layout
\begin_layout Standard
When the display message button is clicked it calls the exposed method display_w
elcome_message() which is a method with a predfined message that is displayed
to the DBus service applications label.
\end_layout
\begin_layout LyX-Code
if __name__ == "__main__":
\end_layout
\begin_layout LyX-Code
app = DBusClient()
\end_layout
\begin_layout LyX-Code
gtk.main()
\end_layout
\begin_layout Standard
The code to to actually run the example.
\end_layout
\begin_layout Section
Finding Exposed Methods
\end_layout
\begin_layout Standard
\begin_inset Index
status collapsed
\begin_layout Plain Layout
DBus!Finding Exposed Methods
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
DBus!Introspection
\end_layout
\end_inset
\begin_inset Index
status collapsed
\begin_layout Plain Layout
DBus!List all Exposed Methods, Properties, and Signals
\end_layout
\end_inset
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sec:DBus - Finding Exposed Methods"
\end_inset
\end_layout
\begin_layout Standard
Now you are asking yourself
\begin_inset Quotes eld
\end_inset
it is all good and well that I can access functionalty throught DBus, but
how do I find what is available?
\begin_inset Quotes erd
\end_inset
.
Well this is actionally fairly simple and is accomplished using introspection.
Basically form is
\end_layout
\begin_layout LyX-Code
your_interface.Introspect(dbus_interface='org.freedesktop.DBus.Introspectable')
\end_layout
\begin_layout LyX-Code
def list_available_commands(self):
\end_layout
\begin_layout Standard
Here is an example using rhythmbox.
It lists all the available methods, signals and properties of the interface
that is used.
It is printed as xml as that is the form that DBus uses.
\end_layout
\begin_layout LyX-Code
import gobject, dbus
\end_layout
\begin_layout LyX-Code
from dbus.mainloop.glib import DBusGMainLoop
\end_layout
\begin_layout LyX-Code
\end_layout
\begin_layout LyX-Code
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
\end_layout
\begin_layout LyX-Code
bus = dbus.SessionBus()
\end_layout
\begin_layout LyX-Code
proxy_object = bus.get_object('org.gnome.Rhythmbox',
\end_layout
\begin_layout LyX-Code
'/org/gnome/Rhythmbox/Player')
\end_layout
\begin_layout LyX-Code
player = dbus.Interface(proxy_object,
\end_layout
\begin_layout LyX-Code
'org.gnome.Rhythmbox.Player')
\end_layout
\begin_layout LyX-Code
print player.Introspect(dbus_interface=
\end_layout
\begin_layout LyX-Code
'org.freedesktop.DBus.Introspectable')
\end_layout
\begin_layout Standard
That is all that is to it, very simple, very easy.
\end_layout
\begin_layout Section
Types
\end_layout
\begin_layout Standard
\begin_inset CommandInset label
LatexCommand label
name "sec:Dbus - Types"
\end_inset
\end_layout
\begin_layout Standard
When using introspection a print out of the xml will be displayed, for instance
a piece of it may look like this.
\end_layout
\begin_layout LyX-Code