Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

A Simpler MRU for GTKMM C++

4.47/5 (6 votes)
28 Mar 2018CPOL5 min read 16K   4  
Sometimes you need a hammer for a job, not a bazooka

I guess I'm a glutton for punishment, or like the little bird who flies against the wind; I really enjoy developing desktop applications. Now it's 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 my philosophy goes. All components of application design should follow that simple rule where it 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; it's 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, it's 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.

Using the Code

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);

C++
"<Menuitem action=<'FileOpen'/>"
"<separator/>"
"<menuitem action='file1'/>"
"<menuitem action='file2'/>"
"<menuitem action='file3'/>"
"<menuitem action='file4'/>"

(And from my app's Gtk::ActionGroup object):

C++
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. Those menu entries must be hidden on our application' initialization, OR filled in with the list generated from the last session;

C++
Gtk::Widget* file1 = m_refUIManager->get_widget("/ui/MenuBar/FileMenu/file1");
Gtk::Widget* file2 = m_refUIManager->get_widget("/ui/MenuBar/FileMenu/file2");
Gtk::Widget* file3 = m_refUIManager->get_widget("/ui/MenuBar/FileMenu/file3");
Gtk::Widget* file4 = m_refUIManager->get_widget("/ui/MenuBar/FileMenu/file4");

file1->hide();
file2->hide();
file3->hide();
file4->hide();

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 'mru_sig' later):

C++
std::deque<Glib::ustring> 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:

C++
// If the deque is more than four, we need to pop one file off it
if(app->mru.size() >= 4)
   app->mru.pop_back();

// then add the new file
app->mru.push_front(str);

// may not be nessessary
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 every 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:

C++
Glib::RefPtr<Gtk::UIManager> m_refUIManager; // Typical GTKMM stuff
Gtk::Widget* file[4];

file[0] = m_refUIManager->get_widget("/ui/MenuBar/FileMenu/file1");
file[1] = m_refUIManager->get_widget("/ui/MenuBar/FileMenu/file2");
file[2] = m_refUIManager->get_widget("/ui/MenuBar/FileMenu/file3");
file[3] = m_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:

C++
int n = 0;

for(deque<Glib::ustring>::iterator it =
   app->mru.begin(); it < app->mru.end(); ++it) {
   const Glib::ustring& label =
      (*it).substr((*it).find_last_of("//") + 1, (*it).length());

   dynamic_cast<Gtk::MenuItem*>(file[n])->set_label(label.c_str());
   app->mru_sig[n].disconnect();

   app->mru_sig[n] = dynamic_cast<Gtk::MenuItem*>(file[n])->signal_activate().
      connect(sigc::bind(sigc::mem_fun(*this, &ExampleWindow::on_mru), label));

   if(dynamic_cast<Gtk::MenuItem*>(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:

C++
on_mru(Glib::ustring& label)
{
   deque<Glib::ustring>::iterator it = _app->mru.begin();
   for(; it < _app->mru.end(); ++it) {
      const Glib::ustring& text =
         (*it).substr((*it).find_last_of("//") + 1, (*it).length());

      if(text.find(label) != string::npos)
         // this is the file the user clicked on in the MRU
         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.

Points of Interest

I'm not sure that the dummy method in the initialization area of the dynamic menu's is entirely necessary, I need to play around with the code and use the actual "on_mru" method if possible, I think I used this technique because I didn't have any data to bind to the signal, but an empty string may have been able to serve just as well.

The "on_mru" method just takes the full path to the file from the deque object and displays it in the console, obviously you'll want to pass that off to the application's open file method.

The sample requires libgtkmm 2.4, and build with:

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

History

  • 8/15/2014: First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)