A Simpler MRU for GTKMM C++

08/15/2014 § Leave a comment

I guess I’m a glutton for punishment, or like the little bird who flies against the wind; I really enjoy developing desktop applications. Becomming something of a lost art, now its all about apps for phones and web applications. But I still really enjoy using my desktop and writing applications for it.

The complaint I most often hear from people opposed to adopting Linux as their desktop os is the lack of applications for it. Well, that won’t change until people start writing those apps.

As far as desktop application design goes, everything should be as simple as possible, or so m philosophy goes. All components of application design should follow that simple rule where its makes sense, and I see no reason for that principal to not be included in the humble RUF, or Recently Used File list, also known as the MRU, or Most recent, so on…

The MRU (from now on) is a tradition, should be available on any mouse driven GUI, and should be easy to implement in any toolkit or framework. Imagine my surprise when I started doing the research on using the MRU object as implemented in GTKMM; its pretty much the most complicated collection of classes I’ve ever seen. And to display only the recent files relevant to your application you need to employ some sort of filter, and then there’s all the complaints I read about those objects on the web, and after that I stopped worrying about it and wrote my own class.

I have a lot of respect for Murry Cumming and the GTKMM team, and the whole GTK+ project, its a huge effort to maintain a unified set of APIs and keep them working for a moving platform like GNU/Linux/Gnome, I am surely aware. I’m also aware that there are usually a huge number of underlying reasons as to why a developer or organization implements a feature set the way they do. But sometimes you just want a thing to just work the way you want them to.

When I got a little deeper into how GTKMM’s RecentChooser classes (there’s the base class Widget, a dialog object, an action object, as well as a menu object, then the filter you need to employ, and on and on) I simply shrugged my shoulders and told myself “I’m not doing that”. I get all the variations, obviously the action object is so you can add a history option to an edit menu, whatever. I just wanted the user to click on a dynamic menu that contained an MRU.

So with the history out of the way I bring you a simpler method using the STL and GTKMM’s own menu api:

My current application for the gnome Desktop is a classic Model-View-Controller impl with a Menu Bar, and of course under the File item is my MRU.

An MRU at the simplest level is a FIFO, and std::deque is perfect for that job. In my application’s data class (a collections of structs, really) reference to a std::deque object.

I started by adding some file menu entries, file 1..4, and binding them to a dummy function. I knew I could change the menu label and bind them to an actual function later (from my application’s Glib::ustring ui_info);
(word press is having conniptions with the xml-heavy Gtk UI string, so look in the sample code for this)
(And from my app’s Gtk::ActionGroup object):
m_refActionGroup->add(Gtk::Action::create("file1",
"file1", "Reopen this file"),
sigc::mem_fun(*this, &ExampleWindow::on_dummy));
m_refActionGroup->add(Gtk::Action::create("file2",
"file1", "Reopen this file"),
sigc::mem_fun(*this, &ExampleWindow::on_dummy));
m_refActionGroup->add(Gtk::Action::create("file3",
"file1", "Reopen this file"),
sigc::mem_fun(*this, &ExampleWindow::on_dummy));
m_refActionGroup->add(Gtk::Action::create("file4",
"file1", "Reopen this file"),
sigc::mem_fun(*this, &ExampleWindow::on_dummy));

The “on_dummy” method is just an empty method, we need that because the ActionGroup template demands it be filled in, we’ll fill it in with the real method later.

My file open menu item, when activated, as well as opening a file, takes the path it got from a file picker and sends it to a method that inserts the file into the deque object, after checking the current size of the deque:

In a header file we have these declarations (more on the signal array later):
std::deque mru; // our deque object
sigc::connection mru_sig[4]; // dynamic menu signals

Then in the implementation file, in our “mru manager” method, app is just a pointer to an “application structure”, a struct with the deque object, among other things:

// If the deque is more than four, we need to pop one file off the que
if(app->mru.size() >= 4)
app->mru.pop_back();
// then add the new file
app->mru.push_front(str);
app->mru.resize(4);

Pretty simple stuff. Now, every time a file is opened it’ll be placed in our deque object, and round robin rotated to the bottom of the list ever time a new file is placed on it. In this case I’m keeping the number of recent files at 4, but it would be simple enough to adjust that number or make it user configurable if one wanted by adding an integer class member and using it instead of the “4” constant above.

Then comes the re-assignment of the menu signals, earlier in the method code I point to some empty Gtk::Widgets with a simple array of pointers:

Glib::RefPtr _refUIManager; // Typical GTKMM stuff
Gtk::Widget* file[4];

file[0] = _refUIManager->get_widget("/ui/MenuBar/FileMenu/file1");
file[1] = _refUIManager->get_widget("/ui/MenuBar/FileMenu/file2");
file[2] = _refUIManager->get_widget("/ui/MenuBar/FileMenu/file3");
file[3] = _refUIManager->get_widget("/ui/MenuBar/FileMenu/file4");

The paths refer to the ui file menus in the Gtk XML gui, if you’re familiar with Gtk desktop programming with GTKMM you should be aware of how that works. We’ll need these as references for the dynamic menus we’ll be connecting the activation signals to. Speaking of which, here’s how those signals are connected to our menu items;
int n = 0;
for(deque::iterator it =
app->mru.begin(); it mru.end(); ++it) {
const Glib::ustring& label =
(*it).substr((*it).find_last_of("//") + 1, (*it).length());
dynamic_cast(file[n])->set_label(label.c_str());
app->mru_sig[n].disconnect();
app->mru_sig[n] = dynamic_cast(file[n])->signal_activate().
connect(sigc::bind(sigc::mem_fun(*this, &ExampleWindow::on_mru), label));

if(dynamic_cast(file[n])->get_label().length() > 0)
file[n++]->show();
}

We iterate through our list of 4 file paths, take out the last bit for the name we display in the menus, and then we do a generic signal disconnect on the item. If we don’t signals will stack up on the item and we’d have several file paths flying at our “open file” method.

We then connect a new signal bound with the path data we want the menu item to open.

The signal method is simplicity itself:

on_mru(Glib::ustring& label)
{
deque::iterator it = _app->mru.begin();
for(; it mru.end(); ++it) {
const Glib::ustring& text =
(*it).substr((*it).find_last_of("//") + 1, (*it).length());
if(text.find(label) != string::npos)
cout << (*it).c_str() << endl;
}
}

The bound text from the label is searched for in the deque object, if we have a match we have our full path to the recently processed file.

There, a functioning MRU for a Gnome Desktop Application without the hassle of Gtk:: RecentChooser. Couple that with a way of serializing the list* for between sessions and you have a full fledged MRU feature for your app.

There’s always room for improvement; traditionally MRU’s have key accelerators “1, 2, 3…” and that would be a nice touch, and simple to add. And ideally this should be a fully encapsulated object, a drop-in, rather than the collection of methods to a main class as presented here.

The sample code shows a simple implementation using GTKMM 2.4, but I don’t believe there’s anything in the code preventing it being converted to 3.0. I hope this will help developers create more applications for Linux, we need all we can get!

There’s a very simple sample you can grab here, build it with:

g++ -Wall -std=c++11 examplewindow.cc main.cc -o menu `pkg-config --cflags --libs gtkmm-2.4`

*Keep your fingers crossed, I may publish a much simpler alternative to Gconfmm.

Let’s Hack XFC

04/10/2012 § Leave a comment

I was delighted to find an OSS solution to address Xfce delopment with C++ in XFC, it uses GTKMM as its model and development with it is very similar. I was also delighted when Bo Lorentsen. XFC’s current chief maintainer, agreed to take me on to assist. I’ve been wanting to participate in an OSS effort and had a hard time finding one until now. I wanted two things out of an OSS project; one that really needed help, and one that I really believed in. There’s no shortage of the first condition, but the second, that was a little tough. Web 2.0 is still raging hard, web technologies are popping up faster than the problems they are desgined to remedy, and systems and native programming solutions are taking a back seat to these technologies. Where UI/UX once referred generically to any GUI technology now almost exlusively describes web presentation. Its only natural and certainly makes sense in a web 2.0 world. But there’s still a need for native code, at least until the perfect web os comes along (everyone hold your breath…)

Jeff Franks, the original XFC code master, has been MIA since 2005, and Bo has been stepping up to the plate since then. He’s had help here and there, but I think he’s been going it alone for the most part since then. This is a perfect fit for me, I’m really excited to help out, and I hope he feels the same. I’ve set up a repository on github and made some updates to the web site, eventually I hope to get access to update the site directly, releiving Bo from that responsiblity.

The same google scanning that informed me of Jeff Frank’s current status also revealed some very positive reviews of the initial releases of the kit. Some commentors even went so far as to say it was superior to Gtkmm in a lot of ways.

Due in no small part to Linus Torvalds’ recent comment that Gnome 3 had become an ‘unholy mess’, it seems to me that more Linux users are going to seek out desktop alternatives, and with Xfce especially, we should see much more support. Xfce is a natural alternative for users who want a sleeker, more responsive alternative to Gnome. With that we need toolkits to enable devlopers to write native API it. XFC is one answer that problem. I hope you can support it.

Enough With Gnome!

03/13/2012 § Leave a comment

Image

Believe it or not, I’m really tired of grousing about Gnome. Or Gnome 3, to be specific. In earlier episodes of my whining (and it wasn’t my intent to write a complaint blog) I’ve gone at length about my dual head set up, how important it was to me to be just so, and blah and blah…

I thought I had it fixed by simply switching to Mint, and then that turned out to be a bust after another update. Well I never fixed it completly, settling for a dual mode set up that left the apps & system menu on my net book. Well it simply wasn’t what I wanted so I went ahead and made the switch to Xfce.

It was relatively painless, I simply flipped on the on the Xfce packages in Synaptic & let’r rip. (I’ve tried using that other package manager thing but its slow (I blame python) and you can only select one package at a time. What is that?) I selected everything I thought I would need for Xfce, like Thunar, and any other related libs. I also read that most apps would probably be compatible with Xfce as well, even gsettings had an Xfce-specifc lib. After downloading the nessessary components I logged out, logged back in, and had a GUI that was completely recognizable. And y’know what else? The desktop was much more responsive. This counts on an underpowered netbook.

Where Am I?

You are currently browsing the UI/UX category at Twittech Conditional Behavior Modification.