A simple libglademm application

This article is a walk through for creating a simple application using gtkmm and libglademm. I've chosen a simple calculator as the application, since it is easy to write the logic behind it, and I can concentrate on explaining the user interface parts.

Creating the UI in glade

Start by running glade. I've attached my finished .glade file below, but even if you use it to refer to, I strongly suggest creating your own by hand anyway if you're not familiar with glade. It's very easy.

[calcwindow widget]To create a window in glade, you need only click on the window icon in the Palette. For most widgets, you select the tool in the Palette, then click on the part of the container where you want to put it. Once you've placed a widget, you can edit its properties in the tabbed 'Properties' window. This may look complex, as there are quite a few properties that you can set, but mostly they'll default to sensible things.

Create a window and change the name to 'calcwindow' in the properties window ('Widget' tab). This will be our calculator's only window. More complex apps will of course have multiple windows, so it's a good idea to get into the habit of naming them all something other than just 'window'. Change the title to 'Simple Calculator'.

Now to the contents of the calcwindow. Insert a VBox with 2 elements. You can change the name of the vbox if you want, but personally I don't bother unless I'll be manipulating the widget from the code, and the vbox is just there for layout. The top will be the display, the bottom will have the buttons in.

[display widget]For the display, we'll use a textentry widget, so put a textentry in top element of the vbox, and change name to 'display'. Set the 'editable' property to 'no' because we'll update the display all as a result of clicks on the buttons.

[button properties]Now for the buttons. Put table in lower element, we'll need 16 buttons, so I suggest 4x4. It doesn't matter how you arrange them, in fact, one of the really nice things about using libglade (and libglademm) is that even after you've compiled your code, the .glade file can still be edited to tweak the layout. Change the table to homogenous (makes all cells the same size). Note that you can select the table itself, rather than a button by right clicking on a button, and selecting the table from the menu (since it doesn't have any space not occupied by buttons any more).

[button widget]Put a button in each of the 16 cells. Change the button labels so that you have buttons for 0-9, +-/*, = and C (for clear). Name the button widgets appropriately - the code I present will assume they're named 'btn0' for the button with the label '0', 'btn1' for the button with label '1' etc, with the other buttons being named btnplus,btnminus,btndiv,btnmul,btnequals and btnclear. Under 'Packing' set each button to X expand, Y expand, X fill, Y fill. This will cause the buttons to fill the whole area of the table. (Try experimenting with these options, and the 'homogenous' property of the table widget; the code won't be affected, it's purely cosmetic.)

[accelerators]Add accelerators for each button - under 'Common' tab, click 'Accelerators', then for btn1 add Key: '1', Signal: 'clicked' (don't forget to click 'add' here), also Key: 'KP_1', Signal: 'clicked' so that either the 1 on the main keyboard or the numeric keypad will have the same effect as clicking on the button. ('/' should have keys 'slash' and 'KP_Divide', '*' should have 'asterisk' and 'KP_Multiply', '+' should have 'KP_Add' and 'plus', '-' should have 'KP_Subtract' and 'minus', '=' should have 'Return','equal' and 'KP_Enter', all to trigger the 'clicked' signal.)

Take 'focus on click' off each of the buttons, and add 'Activates Default' to the display, so that input focus is always on the display

Save as calc.glade, and we're all done with glade!

Starting the coding

Now it's time to start coding. Enter this code in calc.cc:

#include
#include

int main(int argc, char* argv[])
{
Gtk::Main kit(argc, argv);
Glib::RefPtr refXml = Gnome::Glade::Xml::create("calc.glade");
kit.run();
return 0;
}

Compile with:

g++ $(pkg-config --libs --cflags gtkmm-2.4) $(pkg-config --libs --cflags libglademm-2.4) calc.cc -o calc

Apart from the #includes, "int main" line, punctuation and the return 0, there's only three lines of code here, and yet if you run it, this will open the window, allow resize, etc.
The creation of the Gtk::Main object, and the invocation of its run() method are standard Gtkmm lines. Every Gtkmm program will have them (just 2 lines of boilerplate code is extremely neat for a GUI toolkit, if you ask me). The other line is the libglademm line, which loads the glade file and returns a reference counted pointer to a Gnome::Glade::Xml object which we can access it through.

That's not bad going for three lines of code, but it's not really that useful yet. For one thing, it doesn't even exit when the window's closed.

Referring to widgets from the glade file

Pressing on with the next step then, we need to start looking up individual parts of the glade file to link our code to. I like to have a class for each window in my applications, which owns the Gtk::Window* for it, so we'll create a CalcWin class. Its constructor will find the 'calcwindow' window from the loaded glade file, and store it. Its destructor will delete it. Note that deleting a window will delete all the widgets in that window, so even though we'll (later) get pointers to the buttons and the display, we won't need to delete those ourselves.

To find a widget in a loaded glade file, we use

refXml->get_widget("calcwindow", calcwin_);

The first parameter is the name of the widget, the second is a reference to a pointer of the appropriate type, which gets filled in. This is passed as a parameter rather than being returned so that get_widget can be overloaded by type. If the pointer is of the wrong type for the widget, or the widget doesn't exist, you get a warning in the console, and the pointer will be 0. You can use get_widget("name") (ie with a single parameter) too, which returns a Gtk::Widget* (all Gtkmm widgets inherit from Gtk::Widget), but you'd then have to downcast it to something more useful, and do the checking yourself.

We'll also add a little error handling, and deal with the cases where the file can't be found or read, and where the 'calcwindow' window can't be found within the file.


#include
#include

#include
#include

class CalcWin
{
public:
explicit CalcWin(Glib::RefPtr refXml);
~CalcWin();

Gtk::Window& get_window() const { return *calcwin_; }

private:
Gtk::Window* calcwin_;
};

CalcWin::
CalcWin(Glib::RefPtr refXml)
: calcwin_(0)
{
refXml->get_widget("calcwindow", calcwin_);
if (!calcwin_)
throw std::runtime_error("Couldn't find calcwin in calc.glade");
}

CalcWin::
~CalcWin()
{
delete calcwin_;
}

int main(int argc, char* argv[])
{
try
{
Gtk::Main kit(argc, argv);
Glib::RefPtr refXml = Gnome::Glade::Xml::create("calc.glade");
CalcWin calc(refXml);
kit.run(calc.get_window());
return 0;
}
catch (std::exception const& ex)
{
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
}

Note that I also passed a reference to the Gtk::Window to kit.run(). This will make kit.run() return when the window is closed. This can be compiled and run in the same way as before.

Now we need to hook up the buttons so that clicking on them does something useful.

In general, Gtkmm widgets will have a signal_event() method which returns a libsigc signal that you can connect your handler to. So, for example we'll add a method to CalcWin called 'void clear_pressed();' and connect it to the clicked signal of the clear button:

Gtk::Button* but = 0;
refXml->get_widget("btnclear", but);
if (!but)
throw std::runtime_error("Couldn't find clear button in calc.glade");
but->signal_clicked().connect(sigc::mem_fun(*this, &CalcWin::clear_pressed));

We find the widget from the glade file, then get the signal with but->signal_clicked() and connect() the CalcWin::clear_pressed() method to it. sigc::mem_fun() combines the instance of the class (*this) and the class member function into something that we can connect to a signal. (Note that since CalcWin has a member that is used in a sigc callback, we have to make CalcWin inherit from sigc::trackable.)

It would be tedious to write a separate function for each button, so lets be a little smarter, and write just one for the other buttons: 'void button_pressed(char b);'. Of course, the buttons' clicked signals don't pass a char parameter, so we'll have to use sigc::bind() to add it. Rather than write the code out many times, we'll add a helper function:

void CalcWin::
link_button(Glib::RefPtr refXml, std::string name, char value)
{
Gtk::Button* but = 0;
refXml->get_widget(name, but);
if (!but)
throw std::runtime_error("Couldn't find button in calc.glade");
but->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &CalcWin::button_pressed), value));
}

The widget lookup should be familiar by now. This time we've taken CalcWin::button_pressed, which needs to be called with a class instance and a parameter, used sigc::mem_fun to convert it to a function object which needs to be called with a parameter (but no class instance), then used sigc::bind to convert that to another function object which can be called with neither, by supplying 'value' to use when it needs to be called.

The actual button_pressed method is pretty boring from a Gtkmm point of view. It can call get_text() and set_text() on the Gtk::TextEntry* that refers to the 'display' widget. There's extensive documentation on the Gtkmm website (should also be installed with your gtkmm packages) that lists all the many things you can do with the widgets.

[final window]

The final code

Here's the full code for the whole calculator:

#include
#include
#include

#include
#include

class CalcWin : public sigc::trackable
{
public:
explicit CalcWin(Glib::RefPtr refXml);
~CalcWin();

Gtk::Window& get_window() const { return *calcwin_; }
void button_pressed(char b);
void clear_pressed();
Glib::ustring eval() const;

private:
void link_button(Glib::RefPtr refXml, std::string name, char value);
Gtk::Window* calcwin_;
Gtk::Entry* display_;

Glib::ustring accumulator_;
Glib::ustring value_;
char op_;
bool lastequals_;
bool doclear_;
};

CalcWin::
CalcWin(Glib::RefPtr refXml)
: calcwin_(0)
, display_(0)
, accumulator_("0")
, value_("0")
, op_('+')
, lastequals_(false)
, doclear_(true)
{
refXml->get_widget("calcwindow", calcwin_);
if (!calcwin_)
throw std::runtime_error("Couldn't find calcwin in calc.glade");
refXml->get_widget("display", display_);
if (!display_)
throw std::runtime_error("Couldn't find display in calc.glade");
display_->set_alignment(Gtk::ALIGN_RIGHT);
display_->set_text("0");

Gtk::Button* but = 0;
refXml->get_widget("btnclear", but);
if (!but)
throw std::runtime_error("Couldn't find clear button in calc.glade");
but->signal_clicked().connect(sigc::mem_fun(*this, &CalcWin::clear_pressed));

for (int c=0; c<10; ++c)
{
std::ostringstream name;
name << "btn" << c;
link_button(refXml, name.str(), name.str()[3]);
}
link_button(refXml, "btnplus", '+');
link_button(refXml, "btnminus", '-');
link_button(refXml, "btndiv", '/');
link_button(refXml, "btnmul", '*');
link_button(refXml, "btnequals", '=');
}

void CalcWin::
link_button(Glib::RefPtr refXml, std::string name, char value)
{
Gtk::Button* but = 0;
refXml->get_widget(name, but);
if (!but)
throw std::runtime_error("Couldn't find button in calc.glade");
but->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &CalcWin::button_pressed), value));
}

CalcWin::
~CalcWin()
{
delete calcwin_;
}

void CalcWin::
clear_pressed()
{
accumulator_ = "0";
value_ = "0";
op_ = '+';
display_->set_text("0");
}

void CalcWin::
button_pressed(char b)
{
Glib::ustring current(display_->get_text());
switch (b)
{
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
if (doclear_)
{
current = "";
doclear_ = false;
}
display_->set_text(current+b);
lastequals_ = false;
break;
case '+': case '-': case '/': case '*':
value_ = current;
if (b == op_)
accumulator_ = eval();
else
accumulator_ = current;
display_->set_text(accumulator_);
op_ = b;
doclear_ = true;
lastequals_ = false;
break;
case '=':
if (!lastequals_)
value_ = current;
accumulator_ = eval();
display_->set_text(accumulator_);
lastequals_ = true;
break;
}
}

Glib::ustring CalcWin::
eval() const
{
double val=0, acc=0;
std::stringstream convval;
std::stringstream convacc;

convval << value_;
convval >> val;

convacc << accumulator_;
convacc >> acc;

switch (op_)
{
case '+':
acc += val;
break;
case '-':
acc -= val;
break;
case '/':
acc /= val;
break;
case '*':
acc *= val;
break;
}
std::ostringstream oconv;
oconv << acc;
Glib::ustring result(oconv.str());
return result;
}

int main(int argc, char* argv[])
{
try
{
Gtk::Main kit(argc, argv);
Glib::RefPtr refXml = Gnome::Glade::Xml::create("calc.glade");
CalcWin calc(refXml);
kit.run(calc.get_window());
return 0;
}
catch (std::exception const& ex)
{
std::cerr << "Error: " << ex.what() << std::endl;
return 1;
}
}

AttachmentSize
calc.glade14.46 KB
calc.cc3.63 KB
Posted in

clozxy (not verified) | Fri, 2009-11-06 03:19

My Linux is Fedora 6, and gtkmm is of version 2.8.2, and I compiled your example "calc.cpp" well,
but when running, problem comes like this "symbol lookup error: /usr/lib/libglibmm-2.4.so.1: undefined symbol: g_regex_error_quark"

Could you give me some advices?

__
Zhouxy

marble | Fri, 2009-11-06 14:12

Hi,

I don't use Fedora myself, but some googling suggests that you probably need to install glib 2.14 or later.

Hope that helps!