Davyd ([info]davyd) wrote,
@ 2008-02-06 17:29:00
Previous Entry  Add to memories!  Tell a Friend!  Next Entry
writing clean GTK by subclassing
Suppose you have an application that uses a lot of GtkFileChoosers. If someone chooses a file, you want your program to remember the directory they were last in next time they open a file chooser.

Simple you think, you'll just set the current folder before you show the chooser and get the current folder after the user has made their selection. Of course, this means you'll have to put that code around every GtkFileChooser instance.

The solution is subclassing.

By creating a subclass, we can override methods like the constructor and run() to hook in our directory save and restore code. We can also set default properties that would otherwise be required for every instance. The method for subclassing an object depends on the language you're using, but is possible even in C. For this example, I'll use Python:
class MyFileChooserDialog (gtk.FileChooserDialog):
	CWD = None

	def __init__ (self, *args, **kwargs):
		super (MyFileChooserDialog, self).__init__ (*args, **kwargs)

		# set some defaults
		self.set_default_response (gtk.RESPONSE_OK)
		self.set_property ('do-overwrite-confirmation', True)

		# set the working directory if available
		if MyFileChooserDialog.CWD is not None:
			self.set_current_folder (MyFileChooserDialog.CWD)
	
	def run (self):
		response = super (MyFileChooserDialog, self).run ()

		# get the working directory if the user clicked ok
		if response == gtk.RESPONSE_OK:
			MyFileChooserDialog.CWD = self.get_current_folder ()

		return response

gobject.type_register (MyFileChooserDialog)
The class being created is MyFileChooserDialog, which inherits from GtkFileChooserDialog. It has a class-global variable called CWD. The constructor accepts all arguments it is called with, and passes them unmodified to the parent's constructor. It then sets some default properties and finally, sets the current folder for the instance to the global variable CWD.

The run() method also immediately calls its parent's method. It then checks the response code, if the user didn't cancel the action, we store the instance's current folder in the global CWD and then return the value from our parent. All other methods from the parent work as usual, allowing us to transparently replace all instances of gtk.FileChooserDialog with MyFileChooserDialog.

New methods, not implemented in the parent, can also be added to the class. Or depending on your language, new polymorphic methods to support additional data types could be implemented. Python doesn't have true polymorphism, but say we wanted to extend the functionality of add_filter() to make it easier to use in our app:
	def add_filter (self, filter, *args):
		if isinstance (filter, gtk.FileFilter):
			super (MyFileChooserDialog, self).add_filter (filter)
		elif isinstance (filter, MyFileChooserFilterEnum):
			pass # do something with an enum
		else:
			super (MyFileChooserDialog, self).add_filter (create_filter (filter, *args))

def create_filter (name, *patterns):
	filter = gtk.FileFilter ()
	filter.set_name (name)
	for pattern in patterns: filter.add_pattern (pattern)
	return filter
Allowing us to do something like dialog.add_filter ("X Files", "*.x") to create the GtkFileFilter on the fly.

Subclassing allows us to extend the functionality of widgets in reusable way.

Play Along At Home: feel free to port this example into your language of choice, comment with text or a link!


(Post a new comment)


(Anonymous)
2008-02-06 10:49 am UTC (link)
GTK subclassing in C = worst nightmare :D

(Reply to this) (Thread)


[info]davyd
2008-02-06 02:38 pm UTC (link)
It's not that bad, I wrote a macro to do it. I'll port this example to C if you like.

(Reply to this) (Parent)(Thread)

c-example
(Anonymous)
2008-02-06 04:05 pm UTC (link)
Hi!

Yes, please port the example to C, it's very interesting stuff!

(Reply to this) (Parent)


[info]colleenizuje
2008-07-11 03:53 am UTC (link)
GTK is C, Qt is C++. C can be wrapped in OO clothing well enough that it seems to be able to make everyone happy, language wise.

(Reply to this) (Parent)

Multimethods in Python
(Anonymous)
2008-02-06 10:53 am UTC (link)
Check out this post from Guido:

http://www.artima.com/weblogs/viewpost.jsp?thread=101605

I like the subclassing stuff, very nice!

(Reply to this) (Thread)

Re: Multimethods in Python
[info]davyd
2008-02-06 02:46 pm UTC (link)
Awesome.

We were discussing how to implement this at work this afternoon, and figured that a decorator was the solution.

(Reply to this) (Parent)

gtkmm
[info]murraycu
2008-02-06 12:40 pm UTC (link)
With C++ and gtkmm this looks something like this, I guess:

class MyFileChooserDialog : public Gtk::FileChooserDialog
{
public:
MyFileChooserDialog()
{
set_default_response(Gtk::RESPONSE_OK)
property_do_overwrite_confirmation() = true;
}

virtual void on_show()
{
if(!m_cwd.empty())
set_current_folder(m_cwd);
}

virtual void on_response()
{
if response == gtk.RESPONSE_OK:
m_cwd = self.get_current_folder ()
}

protected:
std::string m_cwd;
};


I subclass all my windows in C++, and some of my widgets, and it weird how pygtk coders tend to use containment instead. And I hate how C coders use the struct+functions technique.

(Reply to this) (Thread)

Re: gtkmm
[info]davyd
2008-02-06 02:40 pm UTC (link)
So, I probably should have overloaded do_response(), rather than run(), it would make it work when you're not using run().

Even in C, I subclass a lot of widgets. This is probably an indication that I should be using C++, but I just can't find myself enjoying writing it.

(Reply to this) (Parent)

'True' polymorphism in Python
[info]http://djangopeople.net/edcrypt/
2008-02-06 12:42 pm UTC (link)
"Python doesn't have true polymorphism"
By 'true polymorphism' I see that you mean dispatch by type... as Python is duck typed, the prefered idiom (by must, AFAIK) is EAFP: trying to do something when the filter is a FileFilter(-like), catching the excpetion, trying to do something with a supposed MyFileChooserFilterEnum(-like), etc. The else part would be still the same :)

(Reply to this) (Thread)

Re: 'True' polymorphism in Python
[info]davyd
2008-02-06 02:49 pm UTC (link)
Maybe I spent too many years cutting C, but I still find myself a little dizzy when I think about handling things by exception rather than testing it would be ok beforehand.

(Reply to this) (Parent)

Python style?
[info]mgedmin
2008-02-06 04:57 pm UTC (link)
Thanks for the example.

*bookmarks*

By the way, any particular reason for avoiding the nearly universally accepted Python style guidelines? Not that I'm complaining (it's your code after all), just curious.

(Reply to this) (Thread)

Re: Python style?
[info]davyd
2008-02-06 11:27 pm UTC (link)
I'd never actually read PEP 8. I assume you're referring to my use of tabs instead of spaces and how I write my function calls "like (this)" instead of "like(this)".

I use tabs because that's what my tab key is configured to produce. I write my function calls with a space between them because it is certainly more readable in C (it's also the style that most of GTK+ is written in).

(Reply to this) (Parent)

C#
[info]nohatmatt
2008-02-06 05:45 pm UTC (link)
Warning: untested, and my C# isn't particularly hot so I've probably got something a bit wrong - but it'll be mostly right.

using Gtk;

class MyFileChooserDialog : FileChooserDialog
{
  public static string CWD = null;
  public MyFileChooserDialog()
  {
    DefaultResponse = ResponseType.Ok;
    DoOverWriteConfirmation = true;
    if (CWD != null) CurrentFolder = CWD;
  }
  public override int Run()
  {
    int response = base.Run();
    if (response == (int)ResponseType.Ok)
      CWD = CurrentFolder;
    return response;
  }
}


What always bugs me is having to cast ResponseTypes in comparisons because Dialog.Run() returns an int instead of a ResponseType. Strange decision in Gtk# there. Strongly-typed languages are used for a reason...

(Reply to this) (Thread)

Re: C#
(Anonymous)
2008-02-08 05:32 am UTC (link)
It's the same in C -- the response signal uses an int instead of the enumeration. That's because you can actually supply any integer value you want for the response id, to augment the predefined response types in GtkResponseType. Notice that those enumeration values are actually negative.

Having said that, i don't think i've ever used a response that wasn't part of GtkResponseType. It's rather annoying for binding maintainers, honestly.

(Reply to this) (Parent)(Thread)

Re: C#
[info]davyd
2008-02-08 05:49 am UTC (link)
I've used them in C, creating the extra enumeration can be useful so that you don't overload the meanings of the other response types and create confusion.

(Reply to this) (Parent)(Thread)

Re: C#
[info]nohatmatt
2008-02-08 07:04 am UTC (link)
I hadn't thought of that.

By the way, I've since actually done this... it needs to be public new int Run() because of the way C# works - you can only override a virtual or abstract method, and Gtk# doesn't declare Run() virtual. Therefore you create a new one with the same signature, but it doesn't then do polymorphic dispatch like a virtual method would.

Although in this case that's not really a problem, as you're unlikely to want to treat your special dialog as an instance of its base class.

(Reply to this) (Parent)

With Glade?
[info]murraycu
2008-02-10 12:24 pm UTC (link)
I wonder how this can be done while still instantiating the window's layout from Glade? With gtkmm, we can do this with get_widget_derived(), which instantiates your MyWindow class as the C++ wrapper instanace instead of using the regular Gtk::Window class.

(Reply to this) (Thread)

Re: With Glade?
[info]davyd
2008-02-10 12:26 pm UTC (link)
This feature of GTKmm is pretty hot. I've often thought that something similar could be implemented in Python with enough effort.

In my ideal world however, teaching Glade about custom widgets for a project would be really straight-forward. How would this work? I don't actually know.

(Reply to this) (Parent)


Create an Account
Forgot your login or password?
Login w/ OpenID
English • Español • Deutsch • Русский…