Enumerate files using Gio in Gtkmm

September 11, 2010 Leave a comment

This is a mini howto on enumerating files in a directory using Gio(mm). The code is quite simple. Here’s a small snippet:


void enumerate_files(const Glib::ustring &path)
{

  Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(path);
  Glib::RefPtr<Gio::FileEnumerator> child_enumeration = file->enumerate_children(G_FILE_ATTRIBUTE_STANDARD_NAME);
  std::vector<Glib::ustring> file_names;
  Glib::RefPtr<Gio::FileInfo> file_info;

  while ((file_info = child_enumeration->next_file()) != NULL)
  {
    file_names.push_back(file_info->get_name());
  }

}

Here are the steps:

  1. Create a Gio::File that points to the directory in which you want to enumerate the files.
  2. Then use (one of) the Gio::File::enumerate_children() to get a Gio::FIleEnumerator that has Gio::FileInfo objects which contain the requested attributes. A list of built-in attributes is available at the Gtk+ docs here. (table 2)
  3. Then you use the iterators to access each Gio::FileInfo object in the Gio::FileEnumerator. On each Gio::FileInfo object you perform a get_name() and get the name of the file it represents, then you store it in a std::vector for later usage.

Here is a simple and comlete Gtkmm application that uses Gio to enumerate all the files in a given directory and display them in a Gtk::TreeView

The Screenshot

The Code


#include <gtkmm/main.h>
#include <gtkmm/window.h>
#include <gtkmm/table.h>
#include <gtkmm/scrolledwindow.h>
#include <gtkmm/label.h>
#include <gtkmm/entry.h>
#include <gtkmm/button.h>
#include <gtkmm/treeview.h>
#include <gtkmm/treestore.h>
#include <glibmm/ustring.h>
#include <giomm/file.h>

class MainWindow: public Gtk::Window
{
 public:
 MainWindow();
 ~MainWindow();

 protected:

 class ModelColumns : public Gtk::TreeModel::ColumnRecord
 {
 public:
 ModelColumns()
 { add(name); }

 Gtk::TreeModelColumn<Glib::ustring> name;
 };

 Gtk::Table *container, *path_container;
 Gtk::ScrolledWindow *scrolled;
 Gtk::Label *path_label;
 Gtk::Entry *path_entry;
 Gtk::Button *path_enumerate;
 Gtk::TreeView *treeview;
 Glib::RefPtr<Gtk::TreeStore> model;
 ModelColumns columns;

 void on_enumerate_clicked();
};

MainWindow::MainWindow()
{
 container = new Gtk::Table(2);
 scrolled = new Gtk::ScrolledWindow();
 path_container = new Gtk::Table(1, 3);
 path_label = new Gtk::Label("Path: ");
 path_entry = new Gtk::Entry();
 path_enumerate = new Gtk::Button("Enumerate");
 treeview = new Gtk::TreeView;

 //setup the TreeView
 model = Gtk::TreeStore::create(columns);
 treeview->set_headers_visible(false);
 treeview->set_model(model);
 treeview->append_column("", columns.name);

 //setup the path_enumerate as default widget
 //setup the path_entry
 path_enumerate->set_can_default(true);
 set_default(*path_enumerate);
 path_entry->set_activates_default(true);

 //connect the signals
 path_enumerate->signal_clicked().connect(sigc::mem_fun(this, &MainWindow::on_enumerate_clicked));

 //add the path_* widgets to the path_container
 path_container->attach(*path_label, 0, 1, 0, 1, Gtk::SHRINK|Gtk::FILL, Gtk::SHRINK);
 path_container->attach(*path_entry, 1, 2, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK);
 path_container->attach(*path_enumerate, 2, 3, 0, 1, Gtk::SHRINK|Gtk::FILL, Gtk::SHRINK);

 //Setup the ScrolledWindow and add the TreeView in it
 scrolled->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
 scrolled->add(*treeview);

 //add the path_container and the ScrolledWindow to the main container
 container->attach(*path_container, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK);
 container->attach(*scrolled, 0, 1, 1, 2);

 //finally add the container to the main window and show all children
 add(*container);

 //set a default size
 set_default_size(400, 400);

 show_all_children();
}

MainWindow::~MainWindow()
{
 delete treeview;
 delete path_enumerate;
 delete path_entry;
 delete path_label;
 delete path_container;
 delete scrolled;
 delete container;
}

void MainWindow::on_enumerate_clicked()
{
 Glib::ustring path = path_entry->get_text();
 Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(path);
 Glib::RefPtr<Gio::FileEnumerator> child_enumeration = file->enumerate_children(G_FILE_ATTRIBUTE_STANDARD_NAME);

 std::vector<Glib::ustring> file_names;
 Glib::RefPtr<Gio::FileInfo> file_info;
 while ((file_info = child_enumeration->next_file()) != NULL)
 {
 file_names.push_back(file_info->get_name());
 }

 //clear the TreeView
 model->clear();
 //now populate the TreeView
 Gtk::TreeModel::Row row = *(model->append());

 for (unsigned int i=0; i < file_names.size(); i++)
 {
 row[columns.name] = file_names[i];
 //avoid appending a last empty row
 if (i != file_names.size()-1) row = *(model->append());
 }

}

int main(int argc, char *argv[])
{
 Gtk::Main loop(argc, argv);

 MainWindow window;
 loop.run(window);

 return 0;
}

Categories: Gio, Gtkmm Tags: ,

Startup notification system and Gtkmm

March 11, 2010 Leave a comment

Have you noticed that when you launch an application from the application’s menu that the cursor becomes busy and a new entry in the windows list appears saying “starting <application name>”? (I am refering to Gnome but probably KDE/xfce/etc have equivalent visual indications). No? Maybe a picture will refresh your memory!

Showing startup notification in action

So now that you noticed you’re probably asking yourself as a programmer: “How do I make my gtkmm application display that notification?”.

The answer is that it merely depends on the desktop environment your application is launced in. If the DE supports startup notification then all you have to do is insert StartupNotify=true in your .desktop file. The other part depends on you(or the toolkit you’re using). Luckily Gtk/Gtkmm handles this somewhat automatically. You don’t have to add extra code for the notification to appear.

When you display your main window Gtk/Gtkmm automatically does the necessary actions to inform the startup-notify mechanism that the application has launched successfully. This also makes the notification disappear and it’s place takes another entry with the name of your application’s main window. If for some reason your application doesn’t display a window then the notification disappears after a -somewhat- long timeout. This is ok if your application has crashed. However, it is NOT OK if your application hasn’t crashed but has done some other valid thing “behind the scenes”. One such example is, when a newly created instance of your application finds an older instance running, communicates with it, passes data to it and exits(with the intention that the data will be handled by the older instance). In this situation no window is displayed by the new instance and thus Gtk/Gtkmm hasn’t automatically informed the startup-notify mechanism that the application loaded succesfully. This can become a problem when a user launches multiple instances before the timeouts expire.

Eg. a media player application. When a user double-clicks on a song it may add it to the current playlist. This is actually achieved by the DE launching a new instance of the media player passing it the filename. This new instance first searches if an older instance exists. If yes, it communicates with it(usually through D-bus) and passes the filename. Then it exits. The older instance when it receives the new filename it may play it immediately or append it to the current playlist. Now imagine if the user double-clicks 5 songs one after the other. The taskbar will be crowded quite fast and it makes the application in question less professional to the eyes of a user. An example of crowdness is here:

Showing multiple startup notification entries that should be cleared.

So you’re asking yourself “what can I do to avoid this?” Well the answer is quite simple. You should use gdk_startup_notify_complete(). There’s no equivalent function in Gtkmm but you can directly call the Gtk+(C) function in your code. A pseudo-code approach would be:


#include <gtkmm/gtkmm.h>

int main(int argc, char *argv[])

Gtk::Main main_loop(argc, argv);

/* search for an older instance, (maybe using dbus)

if found, make a connection with it and pass the necessary data

(maybe wait for confirmation from the older instance, because it may have crashed/deadlocked

if confirmation doesn't arrive in a set amount of time then you should take appropriate cleaning up actions)

up until now everything is ok, so before exiting we should inform the startup-notify system too*/

gdk_notify_startup_complete();

/*exit*/

return0;

/*if we reached this point then it means that there is no older instance. We create a new Gtk::Window and start our mainloop*/

Gtk::Window *window = new Gtk::Window("foo bar");

main_loop.run(*window);

delete window;

return 0;

That’s all.

If you want more info about the startup notification mechanism have a look here:

  1. link1
  2. link2

PS: I forgot to mention that the startup-notification “mechanism” is a freedesktop protocol.

gdk_notify_startup_complete

Make a Gtkmm application accept a file from Dolphin(drag-n-drop)

If you are having trouble making your Gtkmm application to accept file drops(drag and drop) from Dolphin(KDE’s file manager) or any other Qt application then you should read my previous blog post here. I make this blog post just to make it easier for people searching for this specific topic. And to sum up my previous post: The solution is to use Gdk::ACTION_MOVE in Gtk::Widget::drag_dest_set(). Normally drag_dest_set() uses as default action the Gdk::ACTION_COPY. This action is OK if you’re accepting drops from Gtk+ based file managers like Nautilus or Thunar, but it isn’t sufficient for Dolphin.

Happy drag-and-dropping!

How to accept a drag-and-drop(-ped) file in your Gtkmm application

February 28, 2010 6 comments

This blog post will discuss on how to make your Gtkmm application to accept and open files that are drag-and-dropped (DnD). This method currently works even when the file is dragged from a Qt application like Dolphin(KDE’s file manger). First of all, you must be a little familiar on how Gtkmm works. Secondly, you should read the entry on Drag-And-Drop in the official Gtkmm tutorial located here. Now, let’s begin:

Setting up the drag-and-drop target:


main_window::main_window()

 {
 std::list<Gtk::TargetEntry> listTargets;
 listTargets.push_back(Gtk::TargetEntry("text/uri-list"));
 drag_dest_set(listTargets, Gtk::DEST_DEFAULT_MOTION | Gtk::DEST_DEFAULT_DROP, Gdk::ACTION_COPY | Gdk::ACTION_MOVE);
 signal_drag_data_received().connect(sigc::mem_fun(*this, &main_window::on_dropped_file));

 resize(350,350);
 }

Now let’s explain it line-by-line(almost):


std::list<Gtk::TargetEntry> listTargets;

This makes a std::list of Gtk::TargetEntry (-ies) that we populate later


listTargets.push_back(Gtk::TargetEntry("text/uri-list"));

This puts in our std::list a new Gtk::TargetEntry of “text/uri-list”. “text/uri-list” is supported by most modern file managers so you won’t need to add other targets.


 drag_dest_set(listTargets, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_COPY | Gdk::ACTION_MOVE);

 

This is the important part. This line sets the DROP target. The target in our situation is the whole window. We want the user to be able to drop the file anywhere on our window. If you want a specific widget to receive the drop then instead set that widget as drop target and not the whole window. Eg. we could set as target a Gtk::Label by doing “label.drag_dest_set()”.

listTargets is our std::list that contains the Gtk::TargetEntry we specified earlier. This tells the system what kind of drops our widget can handle.

Gtk::DEST_DEFAULT_ALL is used here for no important reason. A better explaination of what these flags do is in the Gtk+ docs. The Gtkmm docs currently don’t offer an explanation.

Gdk::ACTION_COPY | Gdk::ACTION_MOVE are the actions possible for a drop. Better explanation of each action on the Gtk+ docs. Again the Gtkmm docs curently don’t offer an explanation. IMPORTANT NOTE HERE: If you want to receive drops from Qt applications like Dolphin you must use Gdk::ACTION_MOVE otherwise a drop will not be possible from Qt apps.


signal_drag_data_received().connect(sigc::mem_fun(*this, &main_window::on_dropped_file));

Here we connect the drag_data_received signal to our handler. The handler will be called when a file is dropped on our target widget.

Setting up the signal handler:


void main_window::on_dropped_file(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, const Gtk::SelectionData& selection_data, guint info, guint time)
{
 if ((selection_data.get_length() >= 0) && (selection_data.get_format() == 8))
 {
 std::vector<Glib::ustring> file_list;

 file_list = selection_data.get_uris();

 if (file_list.size() > 0)
 {
 Glib::usting path = Glib::filename_from_uri(file_list[0]));
 //do something here with the 'filename'. eg open the file for reading
 context->drag_finish(true, false, time);
 return;
 }
 }

 context->drag_finish(false, false, time);
}

Let’s begin the explanation:


if ((selection_data.get_length() >= 0) && (selection_data.get_format() == 8))

Here we check if the dropped data are in the right format that we can accept. I can’t go into more details because I didn’t understand it and I didn’t find documentation that explains it.


std::vector<Glib::ustring> file_list;

 file_list = selection_data.get_uris();

Here we create an std::vector of Glib::usting to hold the URIs. We could use an std::list instead of an std::vector. Whatever suits you. Then we store on the vector the URIs that the drop contains. NOTE: The drop can contain more than one URI if the user drag-and-dropped more than one file simultaneously.


if (file_list.size() > 0)
 {
 Glib::usting path = Glib::filename_from_uri(file_list[0]));
 

Here we first check if the drop actually contained some valid URIs. Then we use Glib::filename_from_uri() to build a valid filename from the first URI in the vector. Glib::filename_from_uri() is a convenience function that transforms a URI to a filename. I strongly recommend using it, because it builds correctly the path even if it contains non-english characters. If the user dropped more files simultaneously then iterate through the vector and convert all the URIs to filenames and handle them accordingly.

After we are done we should call in our signal handler context->drag_finish() with the appropriate parameters to inform the system that the drop finished and what the result was. More info on the parameters here.

Disclaimer: Sorry for the bad code indentation. This is my first post and wordpress.com seems to remove the indentation when I paste the code.

Follow

Get every new post delivered to your Inbox.