1. Gabriele Lanaro
  2. pygabriel-blogging

Commits

Gabriele Lanaro  committed f10f84d

initial commit, pygtkhelpers article

  • Participants
  • Branches default

Comments (0)

Files changed (6)

File pygtkhelpers/article.html

View file
+<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

File pygtkhelpers/pictures/filesnake-skeleton.png

Added
New image

File pygtkhelpers/pictures/filesnake-skeleton.svg

View file
Added
New image

File pygtkhelpers/source/glade/main_win.glade

View file
+<?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>

File pygtkhelpers/source/main_win.py

View file
+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()

File pygtkhelpers/source/menu.glade

View file
+<?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>