5
\$\begingroup\$

I'm learning Python and Gtk so I decided to write a simple application that will take some image let user annotate with a pen and send results through email or save to a file.

So far I have the skeleton of the interface:

from gi.repository import Gtk
import os, sys  

class GUI:

image = 0;

def __init__(self):

    self.window = Gtk.Window()
    self.window.set_title ("Imagine")
    self.window.connect_after('destroy', self.destroy)
    self.window.set_border_width(10)
    self.window.set_default_size(400, 200)

    self.window.vbox = Gtk.Box()
    self.window.vbox.set_spacing (5)
    self.window.vbox.set_orientation (Gtk.Orientation.VERTICAL)
    self.window.add (self.window.vbox)


    self.window.hbox = Gtk.ButtonBox()
    self.window.vbox.add(self.window.hbox)

    self.window.new_button = Gtk.Button(label="New")
    self.window.new_button.connect("clicked", self.on_new_clicked)
    self.window.hbox.pack_start(self.window.new_button, False, False, 0)

    self.window.save_button = Gtk.Button(label="Save")
    self.window.save_button.connect("clicked", self.on_save_clicked)
    self.window.hbox.pack_start(self.window.save_button, False, False, 0)

    self.window.email_button = Gtk.Button(label="eMail")
    self.window.email_button.connect("clicked", self.on_email_clicked)
    self.window.hbox.pack_start(self.window.email_button, False, False, 0)

    self.window.pen_button = Gtk.Button(label="Pen")
    self.window.pen_button.connect("clicked", self.on_pen_clicked)
    self.window.hbox.pack_start(self.window.pen_button, False, False, 0)

    self.image = Gtk.Image()
    self.window.vbox.pack_start (self.image, False, False, 0)

    self.window.show_all()

def on_save_clicked(self, widget):
    print("fixme: Save Clicked") 

def on_email_clicked(self, widget):
    print("fixme: eMail Clicked") 

def on_pen_clicked(self, widget):
    print("fixme: Pen Clicked") 

def on_new_clicked (self, button):
    print("fixme: New Clicked")#code below is just to test the image panel works
    dialog = Gtk.FileChooserDialog ("Open Image", button.get_toplevel(), Gtk.FileChooserAction.OPEN);
    dialog.add_button ("Cancel", 0)
    dialog.add_button ("Ok", 1)
    dialog.set_default_response(1)

    filefilter = Gtk.FileFilter ()
    filefilter.add_pixbuf_formats ()
    dialog.set_filter(filefilter)

    if dialog.run() == 1:
        self.image.set_from_file(dialog.get_filename())

    dialog.destroy()

def destroy(self, window):
    Gtk.main_quit()

class Controls:

    def send_email (self):
        print("fixme: Send Email")

    def pen_controls (self):
        print("fixme: Pen Controls") 


    def on_save_clicked(self, widget):
        print("fixme: Save Clicked") 


def main () :
    win = GUI()
    Gtk.main()

if __name__ == "__main__":
    sys.exit(main())

Can you please review my code and let me know what could have been done better? Any documentation along will be highly appreciated.

Also, my main doubts are:

  • Is my code compliant with the object oriented Python?
  • Will vbox and hbox exist in two places in my window because I created them in self.window and then added to the window again with self.window.add?
  • Should I have created vbox and hbox outside of window and then used the window.add command?
  • The idea is to use Gtk3 PyGObject and Python 3
\$\endgroup\$
6
\$\begingroup\$

I don't think your considerations are a big problem.

However what I think is a bigger problem is the amount of verbosity and repetition in your code. While the code is readable as is, adding more elements in the future will make your __init__() method inflate and become unreadable.

This is because you are adding each Element individually. In large applications this is largely unfeasible when you have more than a dozen widgets, for reasons I am sure you can understand.

There are a couple of solutions to this:

Generate Layout using code

instead of doing this:

self.window.new_button = Gtk.Button(label="New")
self.window.new_button.connect("clicked", self.on_new_clicked)
self.window.hbox.pack_start(self.window.new_button, False, False, 0)

# ... repeat for all buttons

you can do this

buttons = [("New", self.on_new_clicked),
           ("Save", self.on_save_clicked),
           ("eMail", self.on_email_clicked),
           ("Pen", self.on_pen_clicked),
          ]

for name, callback in buttons:
    b = Gtk.Button(label=button)
    b.connect("clicked", callback)
    self.window.hbox.pack_start(b, False, False, 0)

Subclass standard widgets

Often you can also clean up your application by subclassing other widgets:

class myButtonBox(Gtk.ButtonBox):
    def __init__(self, *args, **kwargs):

        # add all buttons to self here

        super().__init__(*args, **kwargs)
        # note: super() without args works only in new versions of Python

Seperate Interface and logic by using GtkBuilder xml

I am mentioning this last since this is the best option, but not very frequently worth the effort.

The Gtk 3 Tutorial has a very nice page on GtkBuilder

The Gtk.Builder class offers you the opportunity to design user interfaces without writing a single line of code. This is possible through describing the interface by a XML file and then loading the XML description at runtime and create the objects automatically, which the Builder class does for you. For the purpose of not needing to write the XML manually the Glade application lets you create the user interface in a WYSIWYG (what you see is what you get) manner

This method has several advantages:

  • Less code needs to be written.
  • UI changes can be seen more quickly, so UIs are able to improve.
  • Designers without programming skills can create and edit UIs.
  • The description of the user interface is independent from the programming language being used.

There is still code required for handling interface changes triggered by the user, but Gtk.Builder allows you to focus on implementing that functionality.

\$\endgroup\$
  • \$\begingroup\$ Good review thanks! Just a question: I'm not sure I understand the second point Subclass standard widgets. Would this work essentially as the extends in inheritance ? So if I did that for every button they would all be a subclass implementation of ButtonBox instead of a new Button Object? \$\endgroup\$ – Fawix Sep 27 '15 at 14:10
  • 2
    \$\begingroup\$ It is just a way of separating your Code into more parts, your extends analogy is pretty fitting. In this case myButtonBox would be a ButtonBox with your Buttons included automatically. I have updated my post to be more clear \$\endgroup\$ – Azsgy Sep 27 '15 at 14:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.