I’ve been playing with GTKmm, writing some code to see how to do GUI apps in GTKmm and see how easy it is. Its not too bad, there are some gotchas. This is what I’ve learned so far. I’ll post it as a kind of a tutorial, maybe it will help someone else.
Firstly, I’d like to say that GTKmm gets a lot of unfair stick out there on the ‘net. Google it vs QT or wxWidgets, you’ll see what I mean. But my experience is showing me that its actually a good API, with good underlying design.
We’ll start off with a simple application, called “SimpleApp”. We’re going to use Autotools to manage it. Don’t be scared! Its actually not too hard.
Before you start, I suggest you install Eclipse CDT. Its not bad for a C++ editor, though I wouldn’t bother with it’s managed projects. You’ll also need a bunch of packages. As I use Ubuntu as my development environment, I’ll show you the ubuntu commands, and you’ll have to work out how to do it in your environment.
You’ll need at least the following, I think (let me know if this isn’t enough):
sudo apt-get install build-essential libglademm-2.4-dev libglibmm-2.4-dev libgtkmm-2.4-dev autoconf automake
To make an autotools project, we only need a few files, configure.ac, and a [b]makefile.am[b] for each directory that contains stuff to be built.
Make yourself a project in Eclipse. File -> New -> C++ Project -> [Makefile project] Hello World C++ Project. Call it “SimpleApp”. Once it’s created, the Makefile and the cpp file it drops in there. You don’t need them.
Make a directory structure with some blank files like this:
SimpleApp/
configure.ac
Makefile.am
src/
Makefile.am
SimpleApp.cpp
MainWindow.cpp
PreferencesWindow.cpp
include/
MainWindow.h
PreferencesWindow.h
configure.ac looks like this: (http://pastebin.ca/962273)
AC_PREREQ(2.61)
AC_INIT([simpleapp], [1.0.0], [You@that.place])
AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION)
AM_MAINTAINER_MODE
AM_CONFIG_HEADER(config.h)
AC_ISC_POSIX
AC_PROG_CXX
AM_PROG_CC_STDC
AC_HEADER_STDC
PKG_CHECK_MODULES(SIMPLEAPP,
[gtkmm-2.4 >= 2.8 libglademm-2.4 >= 2.6 ])
AC_SUBST(SIMPLEAPP_CFLAGS)
AC_SUBST(SIMPLEAPP_LIBS)
AC_OUTPUT([
Makefile
src/Makefile
])
Basically this file tells autoconf what bits and peices we’re using. We’re using glade, and gtkmm. We also want it to output a Makefile in src, so stuff can be built there. I don’t really understand it much, but its pretty easy to understand as you go, it seems.
The first Makefile.am in the project dir contains just this one line:
SUBDIRS = src
Because we’re not building anything in the project directory (as it is about to be filled with hundreds of files).
the Makefile.am in the src/ directory needs the following: (http://pastebin.ca/962279)
gladedir = $(datadir)/glade
glade_DATA = simpleapp.glade
INCLUDES = \
-DPACKAGE_LOCALE_DIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \
-DPACKAGE_SRC_DIR=\""$(srcdir)"\" \
-DPACKAGE_DATA_DIR=\""$(datadir)"\" \
$(SIMPLEAPP_CFLAGS) \
-I/usr/local/include \
-I../include
AM_CFLAGS =\
-Wall\
-g
bin_PROGRAMS = simpleapp
simpleapp_SOURCES = \
SimpleApp.cpp MainWindow.cpp PreferencesWindow.cpp
simpleapp_LDFLAGS =
simpleapp_LDADD = $(SIMPLEAPP_LIBS)
EXTRA_DIST = $(glade_DATA)
Most of the time, you just need to edit the SOURCES line, every time you add a new source file, and then rerun the autoreconf steps (coming next). If you have any libraries you need to link against, add them to LDFLAGS. Include paths get added to the INCLUDES line. Feel free to mess with the CFLAGS to suit yourself; the defaults are designed to be sane.
Once you’ve done this, go to the project directory and run autoreconf. This will fail, with output like the following:
chrome@shuttle:~/Development/workspace/SimpleApp$ autoreconf
/usr/share/aclocal/gtk--.m4:10: warning: underquoted definition of AM_PATH_GTKMM
/usr/share/aclocal/gtk--.m4:10: run info '(automake)Extending aclocal'
/usr/share/aclocal/gtk--.m4:10: or see http://sources.redhat.com/automake/automake.html#Extending-aclocal
configure.ac:5: required file `./missing' not found
configure.ac:5: `automake --add-missing' can install `missing'
configure.ac:5: required file `./install-sh' not found
configure.ac:5: `automake --add-missing' can install `install-sh'
src/Makefile.am: required file `./depcomp' not found
src/Makefile.am: `automake --add-missing' can install `depcomp'
Makefile.am: required file `./INSTALL' not found
Makefile.am: `automake --add-missing' can install `INSTALL'
Makefile.am: required file `./NEWS' not found
Makefile.am: required file `./README' not found
Makefile.am: required file `./AUTHORS' not found
Makefile.am: required file `./ChangeLog' not found
Makefile.am: required file `./COPYING' not found
Makefile.am: `automake --add-missing' can install `COPYING'
autoreconf: automake failed with exit status: 1
The warnings at the start are nothing we can do about at the moment. Seems to be a slightly older version of the m4 macros installed on Ubuntu 7.10, and I can only assume it’ll be fixed eventually. Doesn’t seem to harm anything.
The rest of the whinging is about missing files. automake will indeed add some of the missing files, but not all. Go ahead and add them.
automake --add-missing && touch NEWS README AUTHORS ChangeLog && autoreconf
Phew, almost there! Its a bit arduous, but you can kind of see a structure taking place here, right? It should be pretty familiar; any time you’ve downloaded a package and built it from source, it probably has these files. autotools and autoconf give you a level of build portability (even if your source code is not) so that at the very least you don’t have to worry about whether a C compiler is called gcc on one platform, of cc on another.
Now we just run ./configure, and it will spit out the Makefile we need to build our project.
chrome@shuttle:~/Development/workspace/SimpleApp$ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... no
checking for mawk... mawk
checking whether make sets $(MAKE)... yes
checking whether to enable maintainer-specific portions of Makefiles... no
checking for style of include used by make... GNU
checking for gcc... gcc
checking for C compiler default output file name... a.out
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for suffix of executables...
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking dependency style of gcc... gcc3
checking for library containing strerror... none required
checking for g++... g++
checking whether we are using the GNU C++ compiler... yes
checking whether g++ accepts -g... yes
checking dependency style of g++... gcc3
checking for gcc... (cached) gcc
checking whether we are using the GNU C compiler... (cached) yes
checking whether gcc accepts -g... (cached) yes
checking for gcc option to accept ISO C89... (cached) none needed
checking dependency style of gcc... (cached) gcc3
checking how to run the C preprocessor... gcc -E
checking for grep that handles long lines and -e... /bin/grep
checking for egrep... /bin/grep -E
checking for ANSI C header files... yes
checking for pkg-config... /usr/bin/pkg-config
checking pkg-config is at least version 0.9.0... yes
checking for SIMPLEAPP... yes
configure: creating ./config.status
config.status: creating Makefile
config.status: creating src/Makefile
config.status: creating config.h
config.status: executing depfiles commands
There we go. Now once we’ve added the code into the source files, we’ll have a working build system to build the code with!
Rather than try and paste each file into here, I’ll link to them:
MainWindow.h
MainWindow.cpp
PreferencesWindow.h
PreferencesWindow.cpp
SimpleApp.cpp
now when you type make in either the project directory or the src directory, you should get a binary output, called simpleapp, in the src directory. If you try to run it, however, it’ll core!
(simpleapp:22891): libglade-WARNING **: could not find glade file 'simpleapp.glade'
terminate called after throwing an instance of 'Gnome::Glade::XmlError'
Aborted (core dumped)
Hopefully this should be pretty obvious. We don’t have a glade file! Thats because I had you implement the code before the UI. I think this is better coding practice btw; do lots of code first, then worry about the UI later.
So, what you’ll need is the glade file.
simpleapp.glade
Explanation: We have two windows, main_window and preferences_window. There is a single button, preferences_button on the main_window, which opens the preferences_window.
This preferences_window is controlled by the class, PreferencesWindow. It is instantiated only when we need it, and deleted when we are finished. We hook into the delete_event and hide signals for the preferences_window, so that we can destroy it, and in it’s destructor, it destroys any child widgets.
Note that the so-called “Preferences Window” does not work very well. The one tickbox option there, does not save state. This is something I’ll cover in another post; for now you simply need the .glade file in the same directory that you run simpleapp from.
There are multiple ways to control GTK windows and handle the instantiation of the glade based widgets, etc. But this is the way I’m doing it right now. Alternatives:
- Code your Gtkmm widgets directly in your code.
- Use get_widget_derived instead of get_widget to get the window object. You create a subclass of Gtk::Window and then use get_widget_derived to populate the Gtk::Window bits with stuff from the glade file. I really don’t see the point in this at the moment, I’m sure a GTKmm wizz will tell me why this is better than the way I’m doing it.
Disclaimer: I’m not a GTK or GTKmm expert. This is what I’m currently doing after about a week of messing with the library. My main project is a new bittorrent client, based on libtorrent, and thats not in a usable state yet so I won’t be releasing it til it’s reasonably feature complete
But, stay tuned. (yes, I know, there are a lot of torrent clients, but it was a good project to work on to learn).