Rapid Application Development with GNOME and Python ChristianEgli (cid:0) [email protected] (cid:1) June 13,2001 Abstract RAD is a programming system that enables programmers to build working programs very quickly. How this can be achieved with Free Software tools suchasPython,GladeandGNOMEwillbeshowninahands-ondemonstra- tion. WhatisRAD? SchemeorJava. Itincludesarichsetoflibrariesfornet- working,standarddatabaseAPIandevenXML-RPCfor example. RAD (Rapid Application Development) is a program- mingsystemthatenablesprogrammerstobuildworking MoreinformationaboutPython,binariesandsourcecode programs very quickly. In general, RAD systems pro- canbeobtainedfromwww.python.org. videanumberoftoolstohelpbuildgraphicaluserinter- faces that would otherwise take a largedevelopmentef- fort. TwopopularRADsystemsforWindowsareVisual Glade BasicandDelphi. Glade [4] is the GUI-Builder for GNOME. It allows RADand Free Software straightforward and simple construction of Graphical User Interfaces and gives the developer access to all of WiththecombinationoftoolssuchasGNOME,Python, theGTK+andGNOMEwidgets. GladeandLibGladetheworldofFreeSoftwarealsoof- fers an excellent RAD system. Each individual tool is powerful on its own, but it is in combination that new LibGlade heightsofrapidapplicationdevelopmentareachieved. UsuallyGladeisusedtogenerateCorC++codedirectly. GNOME However, an XML representation of the GUI that was created can also be generated. This XML file can be loaded at runtime with the help of LibGlade [5] which GNOME [1] is well established as a desktop environ- recreatestheGUIasitwasdesignedinGlade. ment. Lesserknown,italsofeaturesadevelopmentplat- formwhichoffersamultitudeoflibrariesbasedonstan- dardandopentechnologies,suchasCORBAandXML. Hands-onDemo Using this platform allows the developer to write user- friendlyandstablesoftwareeasilyandefficiently. Bywritingasmallapplication”gPizza”fortheconfigu- ration of the hacker’s favourite food, I will demonstrate GNOMEispartoftheGNUProject[2]. Moreinforma- live how Python and GNOME can be used to create a tionaboutGNOME,thesourcecode,binariesanddocu- runningprototypeinaveryshorttime. mentationcanbedownloadedfromwww.gnome.org. Highlightsofthedemowillbe: GNOME is built upon GTK+, a free multi-platform toolkitforcreatinggraphicaluserinterfaces. creationoftheGUIwithGlade (cid:2) Python (cid:2) introductionofthelayoutphilosophyofGTK+and presentation of some of the GTK+ and GNOME GUIComponents Python [3] is an interpreted, interactive, object-oriented attachment of widgets, i.e. their signals to Python (cid:2) programminglanguage.ItisoftencomparedtoTcl,Perl, callbacks useofthestandardPythondatabaseAPIandafew (cid:2) morelinesofPythoncodetoconnecttoadatabase andfetchsomerowsthatcontainallthecrucialin- formationtoconfigurepizzas. BuildingtheGUI WehaveinmindageneralideaofhowwewantourGUI tolook. Insteadofdrawingaroughsketchonapieceof paper1welaunchtheGUI-BuilderGladedirectly. Gladeopenswiththreewindows.Themainwindowcon- tainsthemenuandatoolbar. Thepalettewindowshow- inginFigure1displaysthewidgetsthatareavailablefor userinterfacedesignandthirdly,thepropertyeditorwin- dowallowsustoconfigurethecurrentlyselectedobject. Figure2: InitialGNOMEapplicationwindow leftusingthemouseandpaletteresultinginanotherdia- logaskingforthenumberofcolumns. Againwespecify two. By clicking on the label on the columned list we can directly modify its name (without having to use the propertyeditor). Let’scallthefirstcolumn“Name”and thesecondone“Price”.Thewindownowlooksasshown inFigure3. Figure1: Gladepalette Let’s start: First we create a GNOME application win- dowbyselecting“Gnome”inthepaletteandclickingon the “Gnome Application Window” icon. We get a new windowwith standardmenus, a toolbarand a statusbar asshownin Figure 2. In theproperty editor we cansee andchangealltheattributesofthisnewwindow. Figure3: Windowwithcolumnedlist Now,whatarewegoingtoputintothisapplicationwin- dow?Totheleftwewanttohavealistofallthepizzason Totheright oftheapplication windowwewouldliketo themenu. Weachievethisbygrabbingahorizontalbox seeanimage, ashortdescriptionand alistofadditional from the palette (in “GTK+ Basic”) and placing it with optionsforthecurrentlyselectedpizza. Wetakeaverti- a mouse click in the empty area in the application win- cal box from the palette and place it with three rowson dow.Onclickingadialogappearswhereweareaskedto therightside. Withafewmoreclicksweaddapixmap, specifythenumberofcolumnsforthebox.Twocolumns a textbox and a horizontalbox to thosethree rows. We are fine for now. Then we insert a columned list on the fillthehorizontalboxwithcheckbuttonsthatwillenable ustoselectdifferentpizzaoptions. Byselectingacheck 1orasallbrilliantdesigns,onausednapkin;-) buttonwecanchangeitslabeldirectly.Tobeginwithwe simply assign an image to the pixmap by entering a file namewithanimageofapizzainthepropertyeditor. LayoutphilosophyofGTK+ Our widgets are not yet placed as nicely as we would like.Thisbecomesevidentwhenwetrytoresizethemain window of our new GUI. The horizontal box with the checkbuttonsisenlargedwithalotofemptyspace.Most likelyweonlywantthetextboxtobeenlargedwhilethe sizeofthecheckbuttonsremainsthesame. Luckily,this problemcanbetakencareofwiththehelp ofthepropertyeditor.Aswedothiswewillgetaglimpse atthelayoutphilosophyofGTK+. 1. Weselectthehorizontalbox. 2. In the property editor, we select the tab “Wid- get”. Therewetoggletheoption“Homogeneous” to“Yes”. Thisensuresthatallthreecheckbuttons Figure4: Propertyeditorshowing“Packing”tab areallottedthesameamountofscreenrealestate. filter thecontentsofa columnwith thehelp ofa combo 3. Inthetab“Packing”weset“Expand”to“No”and boxinthecolumnheader.Let’sseehowmuchittakesto “Fill”to“Yes”(seealsoFigure4). addacomboboxtothecolumnheader“Price”. While we’re at it we also want to make sure that the 1. Weselectthelabel“Price”inthecolumnedlist. columnedlistdoesn’texpand. 2. Thenwe cutthe abovementionedlabel andadd a verticalboxwithtworowsinstead. 1. Weselectthecolumnedlistor,moreaccurately,the scrolledwindowthatcontainsthecolumnedlist. 3. Wepastethelabelinthetopmostrow.Inthelower rowweaddacombobox. 2. In the property editor we go to the tab “Widget” andchoose“Never”intheoptionmenufor“HPol- icy”. Thisresultsinthecolumnedlistnothavinga Voila`, there’s the combo box in the column header pro- horizontalscrollbaranymore. vidingaglimpseintotheenormousflexibilityofGTK+. Figure5showstheresultingGUI. 3. Inthetab“Packing”wealsoset“Expand”to“No”. Whenweresizethemainwindow,wecanseethatthebe- haviour has changed: the horizontal box with the check buttons and the columned list are no longer expanded. GettingimmediatefeedbackfromtheGUI-BuilderGlade enablesus notonlyto understandhowthe layoutmech- anism works but also to learn to work with it. More in- formation about the layout philosophy of GTK+ can be foundinboththeGTK+Tutorial[6]andHavocPenning- ton’sexcellentbook[7]. FlexibilityofGTK+ Thereisaproductfromanunnamedcompanylocatedin Figure5:Applicationwindowwithcomboboxincolumn Redmondthathappenstocontainalotofcolumnedlists. header This product has a cute feature that allows the user to Save So far so good. Before we proceed to save our piece of artwehavetosetafewthings intheprojectoptionsdi- Listing1: gpizza.glade alog. Ourprojectistobesavedwiththename“gPizza” <?xml version="1.0"?> in the directory linuxtag2001/src. Figure 6 illus- <GTK-Interface> trateshowthisisdone.NoteweenableGNOMEsupport <project> and(although“C”isselectedbydefault)wedonotneed <name>gPizza</name> tospecify a programming language since wedonot use <program_name>gpizza</program_name> thecodegenerationfeaturesofGlade. <directory></directory> <source_directory>src</source_directory> <pixmaps_directory>pixmaps</pixmaps_directory> <language>C</language> <gnome_support>True</gnome_support> <gettext_support>True</gettext_support> </project> <widget> <class>GnomeApp</class> <name>app1</name> <title>gPizza</title> <type>GTK_WINDOW_TOPLEVEL</type> <position>GTK_WIN_POS_NONE</position> <modal>False</modal> <allow_shrink>False</allow_shrink> <allow_grow>True</allow_grow> <auto_shrink>False</auto_shrink> Figure6: Projectoptionsdialog Afterallprojectoptionsaresetwecansavetheproject. Listing2: gpizza.glade XMLfile <widget> <class>GtkCheckButton</class> <name>checkbutton1</name> Thegraphicaluserinterfacethatwejustbuiltandstored <can_focus>True</can_focus> <label>Mozzarella</label> is saved as an XML file in linuxtag2001/src/ <active>False</active> gpizza.glade. Let’s have a quick look at it in List- <draw_indicator>True</draw_indicator> ing 1. At the top we see general information for the <child> <padding>0</padding> project such as its name, that GNOME support is en- <expand>False</expand> abled,andsoforth. <fill>False</fill> </child> Further downwe see the description ofthe widgets that </widget> we added to the project. In Listing 2, for example, we <widget> canseethecheckbuttonsforolivesandmozzarella. <class>GtkCheckButton</class> <name>checkbutton2</name> TheadvantageofsavingtheentireGUIinatextualform <can_focus>True</can_focus> <label>Olives</label> such as XML means not only that it can also be edited <active>False</active> with any text editor, but it could also be changed with <draw_indicator>True</draw_indicator> othertoolssuchasPythonorevenPerl. Itcanalsoeasily <child> bekeptunderrevisioncontrolwithfamiliartoolssuchas <padding>0</padding> <expand>False</expand> CVS. <fill>False</fill> </child> </widget> Listing3: FirstlittlePythonprogram 1 #!/usr/bin/env python 2 3 from gtk import * 4 import gnome 5 import gnome.ui, libglade 6 7 def main(): 8 xml = libglade.GladeXML(’gpizza.glade’) 9 app = xml.get_widget(’app1’) 10 app.connect("destroy", mainquit) 11 mainloop() 12 13 if __name__ == "__main__": 14 main() Firstflight Figure7: FirstflightofgPizza Now that the GUI is created and stored, we are able to Morefunctionality see how the user interface looks at runtime. For that purpose we write a tiny little Python program that does nothing more than load the XML file with the help of Looks like we successfully survived the maiden voyage LibGlade,andconstructtheGUI.Afterthatitsimplysits of“gPizza”withoutcrashing. Now,let’sputsomemore in the mainloop waiting for user input. Listing 3 shows meat on the bone. We shall create another window and thecode. addmorecallbackstoourPythonprogram. Let’shaveacloserlookattheprogram:Line1isthestan- FirstwegobacktotheGUIbuilderGlade,addanAbout dardbeginningforaPythonscript.Inlines3-5weimport Dialogandextendthetoolbar: themodulesgtk,gnome,gnome.uiandlibglade. Inthefunctionmainitstartstogetinteresting: Online8 1. In the palette we pick a “Gnome AboutDialog” theXMLfileisloadedandtheGUIisconstructed. fromthe“Gnome”tabandfillinthefields“Copy- right”,“Authors”and“Comments”intheproperty Inorderforourprogramtoreacttouserinput,i.e.tothe editor. signals that are sent by GTK+, we have to connect the 2. Inthepropertyeditorwetoggle“Visible”to“No” destroysignalforwidgetapp1(whichishowGlade inthe“Basic”tab. ThisensuresthattheAboutDi- automaticallynamedourmainwindowwhenwecreated alogisnotshownautomaticallywhentheprogram itearlier)withthefunctionmainquitinline9-10. To startsup. dothiswegetareference(throughitsname)tothewidget and doa connect onit. If the mainwindowisclosed 3. Weaddanadditionalbuttontothetoolbar,nameit nowthedestroysignalissentbyGTK+and,thanksto “Quit”inthepropertyeditor(inthe“Widget”tab) the connection, the function mainquit is invokedand andassignthe“Quit”icontoit. theprogramquits. Inline11weinvokethemainloop,causingtheprogram Inadditioncanweconnectsomemenusandbuttonswith towaitforuserinput. Finally,inline13an14,weseea callbackfunctions: standardPythonidiomwhichstartsthefunctionmain. 1. We select the menu “Exit”. In the “Signals” tab Let’sruntheprogram. A screenshotofthis firstinvoca- the property editor shows that for this widget the tionisshownin Figure7. Themenuandthebuttonsall activatesignalisconnectedtoahandlernamed lookbeautifulbutshowpreciouslittlereactiontouserin- on_exit1_activate. put. Thisofcourseisduetothefactthatsofarwehave onlyconnectedthesignaldestroyofthemainwindow 2. We change this connection to use the handler toafunction. Indeedifweclosethewindowtheprogram on_exit_activate. quits. 3. Wechangethehandlerforthemenu“About...”un- derthe“Help”menutoon_about_activate. 4. Finally, we connect the handler on_exit_activate for the “Quit” button in the toolbar with the clicked signal (see Figure8). Listing4: SecondPythonprogram #!/usr/bin/env python from gtk import * import gnome gnome.app_version = "0.1" import gnome.ui, libglade def on_exit_activate(obj): "Quit the application" mainquit() def on_about_activate(obj): "Display the about dialog" global xml about = xml.get_widget("about2") about.show () def main(): global xml xml = libglade.GladeXML(’gpizza.glade’) nameFunctionMap = { "on_exit_activate" : on_exit_activate, "on_about_activate" : on_about_activate} xml.signal_autoconnect(nameFunctionMap) mainloop() Figure 8: Connecting the clicked signal with a han- if __name__ == "__main__": dler main() Let’s have a look at the associated Python script in Listing 4. Very little needs to be changed in com- parison to the first program. We add a function on_about_activate that takes care of displaying theAboutDialog. Listing5: Object-orientedPythonprogram There is one interesting thing to note: in our first pro- #!/usr/bin/env python gram we are manually connecting signals to handlers for each widget that we want to react to user input. from gtk import * The second approach (Listing 4) is simpler. We de- import gnome fine handlers in Glade directly as outlined above. The gnome.app_version = "0.2" import gnome.ui, libglade code simply defines a mapping between handler name and the actual handler function. Using this mapping class Application: themethodsignal_autoconnectinLibGladetakes def __init__(self): careofconnectingthehandlersthatwedefinedinGlade self.xml = libglade.GladeXML(’gpizza.glade’) nameFuncMap = {} withourassociatedPythonfunctions. for key in dir(self.__class__): nameFuncMap[key] = getattr(self, key) InListing5thecodehasbeenrearrangedtobenefitfrom self.xml.signal_autoconnect(nameFuncMap) the object-oriented capabilities of Python. We create a mainloop() classApplicationandmovethefunctionsintometh- def on_exit_activate(self, obj): odsofthisclass. Insteadofinvokingthefunctionmain "Quit the application" wesimplyinstantiateanobjectofclassApplication. mainquit() The constructor now contains the code that was previ- def on_about_activate(self, obj): ouslypartofthefunctionmain. "Display the about dialog" about = self.xml.get_widget("about2") In addition, using the object-oriented and introspection about.show () capabilitiesofPythonweareabletouseanevensimpler if __name__ == "__main__": approachthaninListing4. We nolongerhavetodefine Application() themappingbetweenhandlernameandhandlerfunction manually.WeleavethistaskuptothePythoninterpreter. With the help of a little bit of introspection it finds all methodsandtheirnamesofclassApplicationbyit- Listing6: Pythonprogramwithdatabasequery self. #!/usr/bin/env python Let’s start the object-oriented program gPizza3.py. from gtk import * Withtheexceptionoftheadditionalbutton“Quit”inthe import gnome toolbar, the user interface looks exactly as it did in the gnome.app_version = "0.3" firstflight(Figure7). However,thistimethemenusand import gnome.ui, libglade the buttons for which we connected the signals to han- import pgsqldb dlersdobehavejustasweexpectthemto. class Application: def __init__(self): self.xml = libglade.GladeXML(’gpizza.glade’) Integrationwithadatabase nameFuncMap = {} for key in dir(self.__class__): nameFuncMap[key] = getattr(self, key) In the final step we want to fetch the pizza information self.xml.signal_autoconnect(nameFuncMap) fromadatabaseusingthePythonDatabaseAPI.Theuser self.fetchPizzaDescription() interfacedoesn’tchange. Wejust addanewhandler for self.initCList() thesignalselect_rowonthecolumnedlist.Thishan- self.initComboBox() dler is invoked whenever a row in the columned list is mainloop() selected. def on_exit_activate(self, obj): "Quit the application" mainquit() 1. Weselectthecolumnedlistclist1. def on_about_activate(self, obj): 2. Inthetab“Signals”inthepropertyeditorwepick "Display the about dialog" thesignalselect_row. about = self.xml.get_widget("about2") about.show () 3. We define on_clist_select_row as a han- dlerforabovesignal... def on_clist_select_row(self, obj, row, col, event): 4. ...andaddtheconnectionwith“Add” text = self.xml.get_widget(’text1’) text.delete_text(0, -1) description = self.catalog[row][2] text.insert_defaults(description) Theprogramwillbeextendedbyseverallines.Let’shave alookatitinListing6: def fetchPizzaDescription(self): db = pgsqldb.pgsqldb(’mydb’) If we compare it to Listing 5 we notice that cursor = db.query("select * from pizzas" + " order by price") there are some additional methods namely result = cursor.getresult() fetchPizzaDescription, initCList, db.close() initComboBoxandon_clist_select_row. self.catalog = [] In fetchPizzaDescription we establish a con- for name, price, description in result: nection to a PostgreSQL database “mydb” prepared self.catalog.append((name, earlier, fetch the data by issuing an SQL Statement str(price), (select * from pizzas)andstoreitinaninstancevari- description)) able. The initCList and initComboBox methods def initCList(self): serve to initialize the columned list and the combobox clist = self.xml.get_widget(’clist1’) with the data that has been fetched from the database for name, price, descr in self.catalog: previously. Theyare invokedin the constructor of class clist.append([name, price]) Application. def initComboBox(self): When a row in the columned list is selected at runtime comboBox = self.xml.get_widget(’combo1’) thehandleron_clist_select_rowwillbeinvoked. priceDict = {} Firstitwillgetareferencetothetextbox’text1’,then for name, price, descr in self.catalog: delete the old contents of the text box and finally insert priceDict[price] = price thedescriptionoftheselectedpizza. comboStrings = priceDict.keys() comboStrings.sort() Let’strythisoutbystartingtheprogram.Inthecolumned comboStrings.insert(0, ’All’) list we see the contents of table “pizzas” from the comboBox.set_popdown_strings(comboStrings) database “mydb”. If we select any row the description if __name__ == "__main__": isdisplayedtotherightinthetextbox. Figure9depicts Application() ourapplicationinaction. Figure9: Pythonprogramwithdatabaseintegration Figure10: Userinterfaceaccordingtocustomerrequire- ments SubsequentchangestotheGUI Withalittleextraeffortwewouldbeableextendourpro- Wehavenowfinishedaprototypeofthe“gPizza”appli- totype even further. The possibilities are endless. How cation. It’stimetoshowittoourcustomersandgetsome about adding a backend to a web server, or fetching feedback.Itdoesn’tcomeassurprisetohearthatwantto the pizza data from an XML file instead of a database? changeeverythingupsidedown. ThankstoLibGladewe Thanks to the many standard Python modules, features canrespondtothecustomerrequestwithagenuine“No canbedevelopedeasilyandquickly. problem”,becauseweknowthatworkinginthechanges willbe a breeze. We won’thaveto changea single line ofsourcecode. Aglimpseintothefuture We fire up Glade again and start implementing the cus- tomerrequirements: IntheworldofFreeSoftwaredevelopmentneverstands still. Let’s havea look at the general directionin which thetoolsdiscussedinthisarticleareexpectedtogo: 1. The customer wants the check buttons where the pixmap is and vice versa. No problem. We cut the horizontal boxthat contains the checkbuttons GNOMEandgnome-python Release 2.0 of GNOME and we cut the pixmap. Using the clipboard win- will be based on GTK+ 2.0. There are too many dowwherewechoosewhattopastewereinsertthe newfeaturestooenumeratehere. Aroadmapcan checkbuttonsthepixmapbackintheirnewplaces. be found on the GNOME web site [1]. The most prominentfeaturefromadeveloperspointofview 2. Thecustomeralsowantstohavethecolumnedline is probably the separation of the object system in totheleftasopposedtotheright. Again,thisisa GTK+. This will allow for a much betterintegra- jobforcutandpasteusingtheclipboardwindow. tionofthePythonbindings. TheCVSversioncur- 3. Finally the customer wants the entire application rentlyallowscreationofcustomGTK+widgetsin area,i.e.everythingbetweentoolbarandstatusbar, Python. in a notebook widget. To achieve this we cut the Glade TheCVSversionofGladeaddsmorewidgetsto topmosthorizontalbox,insertanotebookinstead thepalettewindow.ThereisnowatabforBonobo andpastethehorizontalboxinsidethefirstpageof which allows you to insert Bonobo components thenotebook. into your GUI. Bonobo [8, 9] is the GNOME ar- chitecture for creating reusable software compo- WestartthesamePythonscriptwithouthavingchanged nentsandcompounddocuments. Pythonbindings asinglelineofsourcecode.Figure10willmostcertainly forBonobohavebeenaddedtoGNOMECVSvery makeourcustomerhappy! recently. Thereisalsoatabforthegnome-dbLibraries[10] which contains a series of useful widgets for ac- Conclusion cessing any database. Unfortunately, at the mo- menttherearenoPythonbindingsforgnome-db. We have shown that by combining the tools GNOME, Python Python has a large user and developer base. Python,GladeandLibGladeaworkingprototypecanbe Therearenewreleasesoftheinterpreteratregular createdveryrapidly. intervals. Thelanguageitselfevolvesveryconser- vatively and changes are usually backward com- patible. References [1] GNOME.http://www.gnome.org/. [2] GNUProject.http://www.gnu.org/. [3] Python.http://www.python.org/. [4] DaemonChaplin.Glade.http://glade.gnome.org/. [5] James Henstridge. Libglade Reference Manual. http: //developer.gnome.org/doc/API/libglade/ libglade.html. [6] GTK+.http://www.gtk.org/. [7] Havoc Pennington. GTK+/Gnome Application Development. NewRidersPublishing,1999. [8] Miguel de Icaza. Introduction to Bonobo. http://www. ximian.com/tech/bonobo.php3,1999. [9] Bonobo. http://developer.gnome.org/arch/ component/bonobo.html. [10] RodrigoMoya.GNOME-DB.www.gnome-db.org. [11] JamesHenstridge. gnome-python. http://www.daa.com. au/˜james/pygtk/. [12] HilaireFernandes. DevelopingGnomeApplicationwithPython. http://www.linuxfocus.org/English/July2000/ article160.shtml. [13] Deirdre Saoirse. The Ten Minute Total Idiot’s Guide to us- ing Gnome/GTK+ & Glade with Python. http://www. baypiggies.org/10mintig.html. [14] Xianping Ge. Using Python + Glade. http://www.ics. uci.edu/˜xge/python-glade/python-glade.html, 2000. Abouttheauthor: ChristianEgliholdsamaster’sdegreeincomputerscienceandworks asasoftwareengineerforacompanyprovidingtelecomtestsolutions.