Commits

Gabriele Lanaro committed f10f84d

initial commit, pygtkhelpers article

Comments (0)

Files changed (6)

pygtkhelpers/article.html

+<h2>Writing pygtk application with style, using pygtkhelpers</h2>
+
+ <a href="http://packages.python.org/pygtkhelpers/index.html">pygtkhelpers</a>
+is an awesome library for writing pygtk applications, it was developed
+by <a href="http://pida.co.uk/pida">pida</a> developers and makes the
+pygtk programming experience much better, let's start with the
+tutorial.
+
+ I've used this library for my
+project, <a href="http://bitbucket.org/gabriele/filesnake/wiki/Home">filesnake</a>,
+I want to explain my workflow.
+
+<h3> GUIs like template </h3>
+In pygtkhelpers "glade" files and hand written GUI blends together in
+a wonderful manner, in particular each piece of the GUI is separate
+from the rest and give you a lot of control, flexibility and
+mantainablity.
+
+I started with glade, writing a "skeleton gui". It's a window, with a
+VBox inside and in each part of the vbox I filled with a
+gtk.EventBox(also another gtk.VBox would have been fine for the purpose), these
+EventBoxes serves as "placeholders" for other pieces of the GUI, the
+menu, the userlist and the status bar.
+
+<img src="pictures/filesnake-skeleton.png" class="" alt="" />
+
+I've written the very little code to make the GUI
+run:
+
+[sourcecode=python]
+from pygtkhelpers.delegates import WindowView
+
+class FileSnakeGUI(WindowView):
+    builder_file = "main_win.glade"
+
+f test_run():
+    fs = FileSnakeGUI()
+    fs.show_and_run()
+
+if __name__ == '__main__':
+    test_run()
+
+[/sourcecode]
+
+It's time to write the three components:
+<ul>
+  <li>menu</li>
+  <li>userlist</li>
+  <li>statusbar</li>
+</ul>
+
+Starting from the menu, I've written another glade file with info
+about menu, and I've used the signal handling throught the stuff.
+
+Don't worry about placing only the menu in the glade file, the library
+handle the finding of the container window and unpacks the widget from
+it.
+
+The connection is made automatically with the naming convention on_widget__signal.
+
+[sourcecode]
+from pygtkhelpers.delegates import SlaveView
+
+class Menu(SlaveView):
+    
+    builder_file = "menu.glade"
+    
+    def __init__(self, parent):
+        SlaveView.__init__(self)
+        self.parent = parent
+    
+    def on_quit__activate(self,*a):
+        self.parent.hide_and_quit()
+        
+    def on_sendfile__activate(self, *a):
+        self.parent.send_file()
+[/sourcecode]
+
+<h3>Handwritten GUIs</h3>
+
+I've written the statusbar and the userlist by hand, The code can
+be as complex as you want, demostrating the flexibility and
+"invisibility" of the framework, all the code is well packed and organized on its own place.
+
+A generic Slave/WindowView (subclasses of BaseDelegate) has this methods:
+
+<ul>
+  <li>create_ui: in this method you can manually write your GUI,
+    usually you put there all the "add_slave" code</li>
+  <li>
+    on_mywidget__event: these are the signal handlers, facilities that let
+    you write cleaner code without all the self.connect stuff</li>
+  <li>__init__: you can pass custom initializer, and various control
+    code not related to the gui code (nothing stops you to do that in
+    the create_ui, it's just to add a bit of conventions)</li>
+</ul>
+
+Here's the UserList code, you can add additional methods to simplify
+external access, like add_user(). This "additional method" would be
+used in the main controller (FileSnakeGUI), for example. It's a component, and it's reusable.
+
+[sourcecode]
+# Defining a user container 
+User = namedtuple("User", "name icon address port")
+
+# UserList section
+class UserList(SlaveView):
+    
+    def create_ui(self):
+        model = gtk.ListStore(object)
+        treeview = gtk.TreeView(model)
+        treeview.set_name("User List")
+        
+        iconrend = gtk.CellRendererPixbuf()
+        inforend = gtk.CellRendererText()
+
+        iconcol = gtk.TreeViewColumn('Icon', iconrend)
+        infocol = gtk.TreeViewColumn('Info', inforend)
+        
+        iconcol.set_cell_data_func(iconrend, self._icon_data)
+        infocol.set_cell_data_func(inforend, self._info_data)
+        
+        treeview.append_column(iconcol)
+        treeview.append_column(infocol)
+        treeview.set_headers_visible(False)
+
+        self.store = model
+        self.treeview = treeview
+        self.widget.add(treeview)
+
+    def _icon_data(self, column, cell, model, iter):
+
+        row = model[iter]
+        user = row[0]
+        cell.set_property("pixbuf",gtk.gdk.pixbuf_new_from_file(user.icon))
+    
+    def _info_data(self, column, cell, model, iter):
+
+        row = model[iter]
+        user = row[0]
+        
+        template = "<big><b>{user}</b></big>\n<small><i>{address}:{port}</i></small>"
+        label = template.format(user=user.name,
+                                address=user.address,
+                                port=user.port)
+        cell.set_property("markup",label)
+
+    def add_user(self, user):
+
+        self.store.append([user])
+
+[/sourcecode]
+
+The statusbar doesn't introduce anything new. You can find the source
+appended.
+
+<h3>pygtk signals facilities, now you haven't any excuse</h3>
+
+It's trivial to add your own signals to your BaseDelegate, let's see
+how to add a "user-added" signal to my UserList:
+
+[sourcecode]
+from pygtkhelpers.utils import gsignal
+
+class UserList(SlaveView):
+    gsignal("user-added",object)   
+#   ... source code defined before...
+    def add_user(self, user):
+
+        self.emit("user-added", user)
+        self.store.append([user])
+
+[/sourcecode]
+
+
+It's just one line of code and you can connect it like a gtk widget,
+implementing in the easiest way I've seen the observer pattern. It's
+cool!
+
+ Other sweetness are pygtkhelpers.dialogs, premade dialogs for a lot
+of stuff, like yesno, open dialog etc...  There are also new widgets
+very useful for conventional liststore/treeview programming. Writing
+an editable treeview is no more a pain.
+
+You can find the full sourcecode on the blog repo: 
+
+http://bitbucket.org/gabriele/pygabriel-blogging/pygtkhelpers

pygtkhelpers/pictures/filesnake-skeleton.png

Added
New image

pygtkhelpers/pictures/filesnake-skeleton.svg

Added
New image
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="248.9411"
+   height="272.43359"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="Nuovo documento 1">
+  <defs
+     id="defs4">
+    <marker
+       inkscape:stockid="DotM"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="DotM"
+       style="overflow:visible">
+      <path
+         id="path3716"
+         d="m -2.5,-1 c 0,2.76 -2.24,5 -5,5 -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 z"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none;marker-end:none"
+         transform="matrix(0.4,0,0,0.4,2.96,0.4)" />
+    </marker>
+    <marker
+       inkscape:stockid="DotL"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="DotL"
+       style="overflow:visible">
+      <path
+         id="path3713"
+         d="m -2.5,-1 c 0,2.76 -2.24,5 -5,5 -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 z"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none;marker-end:none"
+         transform="matrix(0.8,0,0,0.8,5.92,0.8)" />
+    </marker>
+    <inkscape:path-effect
+       effect="spiro"
+       id="path-effect2850"
+       is_visible="true" />
+    <inkscape:path-effect
+       is_visible="true"
+       id="path-effect2846"
+       effect="spiro" />
+    <inkscape:path-effect
+       effect="spiro"
+       id="path-effect2842"
+       is_visible="true" />
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.4"
+     inkscape:cx="89.862542"
+     inkscape:cy="149.89284"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     showguides="false"
+     inkscape:snap-global="false"
+     inkscape:window-width="1680"
+     inkscape:window-height="975"
+     inkscape:window-x="0"
+     inkscape:window-y="24"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4470"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Livello 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-43.0625,-44)">
+    <rect
+       style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       id="rect2816"
+       width="137.85713"
+       height="271.42856"
+       x="43.57143"
+       y="44.505039"
+       ry="8.5714283" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 52.15625,44.5 c -4.748571,0 -8.59375,3.813929 -8.59375,8.5625 l 0,5 137.875,0 0,-5 c 0,-4.748571 -3.84518,-8.5625 -8.59375,-8.5625 l -120.6875,0 z"
+       id="rect2818" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 43.5625,58.10202 0,13.5625 137.875,0 0,-13.5625 -137.875,0 z"
+       id="rect2824" />
+    <path
+       sodipodi:type="arc"
+       style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       id="path2829"
+       sodipodi:cx="151.60715"
+       sodipodi:cy="51.826466"
+       sodipodi:rx="4.4642859"
+       sodipodi:ry="4.4642859"
+       d="m 156.07143,51.826466 c 0,2.465557 -1.99873,4.464285 -4.46428,4.464285 -2.46556,0 -4.46429,-1.998728 -4.46429,-4.464285 0,-2.465557 1.99873,-4.464286 4.46429,-4.464286 2.46555,0 4.46428,1.998729 4.46428,4.464286 z"
+       transform="translate(18,-0.71428574)" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       d="m 43.5625,71.54718 0,231.78127 137.875,0 0,-231.78127 -137.875,0 z"
+       id="rect2831" />
+    <path
+       transform="translate(5.8571392,-0.71428574)"
+       d="m 156.07143,51.826466 c 0,2.465557 -1.99873,4.464285 -4.46428,4.464285 -2.46556,0 -4.46429,-1.998728 -4.46429,-4.464285 0,-2.465557 1.99873,-4.464286 4.46429,-4.464286 2.46555,0 4.46428,1.998729 4.46428,4.464286 z"
+       sodipodi:ry="4.4642859"
+       sodipodi:rx="4.4642859"
+       sodipodi:cy="51.826466"
+       sodipodi:cx="151.60715"
+       id="path2836"
+       style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       sodipodi:type="arc" />
+    <path
+       sodipodi:type="arc"
+       style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       id="path2838"
+       sodipodi:cx="151.60715"
+       sodipodi:cy="51.826466"
+       sodipodi:rx="4.4642859"
+       sodipodi:ry="4.4642859"
+       d="m 156.07143,51.826466 c 0,2.465557 -1.99873,4.464285 -4.46428,4.464285 -2.46556,0 -4.46429,-1.998728 -4.46429,-4.464285 0,-2.465557 1.99873,-4.464286 4.46429,-4.464286 2.46555,0 4.46428,1.998729 4.46428,4.464286 z"
+       transform="translate(-7.0000049,-0.71428574)" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotM)"
+       d="m 151.07143,64.505038 78.57143,0"
+       id="path2840"
+       inkscape:path-effect="#path-effect2842"
+       inkscape:original-d="m 151.07143,64.505038 78.57143,0" />
+    <path
+       inkscape:original-d="m 151.07143,170.50504 78.57143,0"
+       inkscape:path-effect="#path-effect2846"
+       id="path2844"
+       d="m 151.07143,170.50504 78.57143,0"
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotM)" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;marker-start:url(#DotM)"
+       d="m 151.07143,308.50504 78.57143,0"
+       id="path2848"
+       inkscape:path-effect="#path-effect2850"
+       inkscape:original-d="m 151.07143,308.50504 78.57143,0" />
+    <flowRoot
+       xml:space="preserve"
+       id="flowRoot2852"
+       style="font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Garuda;-inkscape-font-specification:Garuda"
+       transform="translate(2.8571428,-0.35714291)"><flowRegion
+         id="flowRegion2854"><rect
+           id="rect2856"
+           width="72.5"
+           height="33.92857"
+           x="230.71428"
+           y="56.647896"
+           style="font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:Garuda;-inkscape-font-specification:Garuda" /></flowRegion><flowPara
+         id="flowPara2858">menu</flowPara></flowRoot>    <flowRoot
+       xml:space="preserve"
+       id="flowRoot2860"
+       style="font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Garuda;-inkscape-font-specification:Garuda"
+       transform="translate(1.4285714,4.2857143)"><flowRegion
+         id="flowRegion2862"><rect
+           id="rect2864"
+           width="79.285721"
+           height="40.000004"
+           x="232.14285"
+           y="157.71933"
+           style="font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:Garuda;-inkscape-font-specification:Garuda" /></flowRegion><flowPara
+         id="flowPara2866">userlist</flowPara></flowRoot>    <flowRoot
+       xml:space="preserve"
+       id="flowRoot2868"
+       style="font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Garuda;-inkscape-font-specification:Garuda"
+       transform="translate(-1.7857143,1.4285714)"><flowRegion
+         id="flowRegion2870"><rect
+           id="rect2872"
+           width="93.928574"
+           height="34.642857"
+           x="236.07143"
+           y="299.14789"
+           style="font-size:14px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:Garuda;-inkscape-font-specification:Garuda" /></flowRegion><flowPara
+         id="flowPara2874">statusbar</flowPara></flowRoot>  </g>
+</svg>

pygtkhelpers/source/glade/main_win.glade

+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkWindow" id="window1">
+    <property name="width_request">250</property>
+    <property name="height_request">450</property>
+    <child>
+      <object class="GtkVBox" id="vbox1">
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkVBox" id="menu_cont">
+            <property name="visible">True</property>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkVBox" id="userlist_cont">
+            <property name="visible">True</property>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkVBox" id="statusbar_cont">
+            <property name="visible">True</property>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>

pygtkhelpers/source/main_win.py

+from pygtkhelpers.delegates import WindowView,SlaveView
+from collections import namedtuple
+import gtk
+
+# Menu section
+class Menu(SlaveView):
+    
+    builder_file = "menu.glade"
+
+    def __init__(self, parent):
+        SlaveView.__init__(self)
+        self.parent = parent
+
+    def on_quit__activate(self,*a):
+        self.parent.hide_and_quit()
+
+    def on_sendfile__activate(self, *a):
+        self.parent.send_file()
+
+# Defining a user container
+User = namedtuple("User", "name icon address port")
+
+# UserList section
+class UserList(SlaveView):
+
+    def create_ui(self):
+        model = gtk.ListStore(object)
+        treeview = gtk.TreeView(model)
+        treeview.set_name("User List")
+
+        iconrend = gtk.CellRendererPixbuf()
+        inforend = gtk.CellRendererText()
+
+        iconcol = gtk.TreeViewColumn('Icon', iconrend)
+        infocol = gtk.TreeViewColumn('Info', inforend)
+
+        iconcol.set_cell_data_func(iconrend, self._icon_data)
+        infocol.set_cell_data_func(inforend, self._info_data)
+
+        treeview.append_column(iconcol)
+        treeview.append_column(infocol)
+        treeview.set_headers_visible(False)
+
+        self.store = model
+        self.treeview = treeview
+        self.widget.add(treeview)
+
+    def _icon_data(self, column, cell, model, iter):
+
+        row = model[iter]
+        user = row[0]
+        cell.set_property("pixbuf",gtk.gdk.pixbuf_new_from_file(user.icon))
+
+    def _info_data(self, column, cell, model, iter):
+
+        row = model[iter]
+        user = row[0]
+
+        template = "<big><b>{user}</b></big>\n<small><i>{address}:{port}</i></small>"
+        label = template.format(user=user.name,
+                                address=user.address,
+                                port=user.port)
+        cell.set_property("markup",label)
+
+    def add_user(self, user):
+
+        self.store.append([user])
+
+class Statusbar(SlaveView):
+    def __init__(self, parent):
+        self.parent = parent
+        SlaveView.__init__(self)
+        
+    def create_ui(self):
+        statusbar = gtk.StatusBar()
+        self.widget.add(statusbar)
+
+class FileSnakeGUI(WindowView):
+    
+    builder_file = "main_win.glade"
+    
+    def create_ui(self):
+        self.userlist = UserList()
+        
+        self.add_slave("statusbar_cont", Statusbar())
+        self.add_slave("menu_cont", Menu(self))
+        self.add_slave("userlist_cont", self.userlist)
+        
+    def on_window1__delete_event(self, *a):
+        self.hide_and_quit()
+
+def test_run():
+    fs = FileSnakeGUI()
+    fs.show_and_run()
+
+if __name__ == '__main__':
+    test_run()

pygtkhelpers/source/menu.glade

+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkWindow" id="window1">
+    <child>
+      <object class="GtkMenuBar" id="menu">
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkMenuItem" id="file">
+            <property name="visible">True</property>
+            <property name="label" translatable="yes">_File</property>
+            <property name="use_underline">True</property>
+            <child type="submenu">
+              <object class="GtkMenu" id="filestuff">
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkImageMenuItem" id="sendfile">
+                    <property name="label">Send File</property>
+                    <property name="visible">True</property>
+                    <property name="image">image1</property>
+                    <property name="use_stock">False</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkImageMenuItem" id="sendfolder">
+                    <property name="label">Send Folder</property>
+                    <property name="visible">True</property>
+                    <property name="image">image2</property>
+                    <property name="use_stock">False</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkImageMenuItem" id="quit">
+                    <property name="label">gtk-quit</property>
+                    <property name="visible">True</property>
+                    <property name="use_underline">True</property>
+                    <property name="use_stock">True</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkMenuItem" id="help">
+            <property name="visible">True</property>
+            <property name="label" translatable="yes">A_iuto</property>
+            <property name="use_underline">True</property>
+            <child type="submenu">
+              <object class="GtkMenu" id="helpstuff">
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkImageMenuItem" id="info">
+                    <property name="label">gtk-about</property>
+                    <property name="visible">True</property>
+                    <property name="use_underline">True</property>
+                    <property name="use_stock">True</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
+  <object class="GtkImage" id="image1">
+    <property name="visible">True</property>
+    <property name="stock">gtk-file</property>
+  </object>
+  <object class="GtkImage" id="image2">
+    <property name="visible">True</property>
+    <property name="stock">gtk-directory</property>
+  </object>
+</interface>