GtkAda File Choosers

(Whoops! I had meant to upload this more than a year ago… yet somehow never got ’round to it!)

Today’s goal is to produce a working file chooser dialog in GtkAda. That is:

Mise-en-scène

Hi, my name is John, and I’m an Ada addict.Not a good one, not even a particularly adept one, but… well, read on.

(Hi, John!)

This is my first time attending one of these meetings, and…

Yeah, I like Ada. I mean, I really like Ada.

I also like appending random quotes to my emails. These quotes come entirely from things I’ve read, heard, and/or made up. For instance, an email from me might end with one of these gems:
  1. I’ll bet future civilizations find out more about us than we’d like them to know. (from Calvin & Hobbes)
  2. Life is but a dream, a little less inconstant. (from Pensees)
  3. “I have an incredibly open mind.”
    “What, like a head wound?“ (from Get Fuzzy)

You get the picture.

Some time back, I wrote a program in…some language… C++ perhaps? Nim? whatever the case, it read quotes from a text file with a custom format (quotation / author / quotation / author / quotation / author / …) and emit a random one to standard output. It’s a simple, CS 101-style program, but it made me happy, because Kmail will run this program, read standard output, and append the quote with each new email. Lovely.

So, time passed. As I am an Ada addict, I decided I’d have to do this with Ada.

(Uh oh!)

Yeah, I know, right? If I wanted a portable solution, I was stuck with GtkAda, and I’m not exactly a Gtk fan. Oh, well; you learn to work with the tools you actually have, not the tools you fantasize you had.

The obstacles

I set about studying GtkAda, and quickly discovered that GtkAda is one of those all-too-common beasts that’s brilliantly designed and terribly documented.

I swear I am not making this up: Here’s some honest-to-God, unedited documentation from gtk-file_chooser_dialog.ads:
--  In the simplest of cases, you can the following code to use
--  Gtk.File_Chooser_Dialog.Gtk_File_Chooser_Dialog to select a file for
--  opening:
--
--  |[ GtkWidget *dialog; GtkFileChooserAction action =
--  GTK_FILE_CHOOSER_ACTION_OPEN; gint res;
--
--  dialog = gtk_file_chooser_dialog_new ("Open File", parent_window, action,
--  _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Open"), GTK_RESPONSE_ACCEPT, NULL);
--
--  res = gtk_dialog_run (GTK_DIALOG (dialog)); if (res ==
--  GTK_RESPONSE_ACCEPT) { char *filename; GtkFileChooser *chooser =
--  GTK_FILE_CHOOSER (dialog); filename = gtk_file_chooser_get_filename
--  (chooser); open_file (filename); g_free (filename); }
--
--  gtk_widget_destroy (dialog); ]|
Three observations:
  1. It supplies example code, which would be great, if the example code were in Ada. Unfortunately, the example code is in C. It’s copied verbatim from the Gtk source. You might suspect this is just a machine copy, but…
  2. It isn’t; some documentation was modified in an attempt to explain the Ada. Despite the care and attention given to these modifications, these changes sometimes tell you blatant lies.
  3. Even if you could guess what the Ada equivalents are — no, let’s just stop there, because part of the genius behind GtkAda is that you can’t guess. It’s a thick binding that changes the structure. You simply will not find Ada equivalents for many important Gtk idioms. (This is probably a good thing.)
Here’s an example of point (2), by the way, taken straight from gtk-tree_model.ads.
   procedure Next (Tree_Model : Gtk_Tree_Model; Iter : in out Gtk_Tree_Iter);
   --  Sets Iter to point to the node following it at the current level.
   --  If there is no next Iter, False is returned and Iter is set to be
   --  invalid.
   --  "iter": the Gtk.Tree_Model.Gtk_Tree_Iter-struct
What exactly does this procedure return, and how does that differ from what it claims to return?Hint: in Ada, procedures don’t return values! The documentation isn’t just wrong; it violates the language definition! Hover over the cross if you need help working it out!

I spent hours trying to find any kind of worthwhile documentation and/or example code on GtkAda, and the best I could do consisted of examples for an obsolete version. So I’m surprised I managed to get this going in as little time as I did: a few evenings at best. The kind folks at comp.lang.ada gave me some advice, though I didn’t need it in the end.

Over the next few entries I’ll provide some code excerpts that show a correct way to do certain tasks that I did not find easily by searching online, but worked out by reading the Ada interface file and making sense of what was there.

Using a file chooser dialog

Again, our goal is to produce a “file chooser dialog”:
A really nice Gtk feature is that it provides you one of these “for free”. By “free” I mean that you don’t have to go about the business of creating a dialog, then adding a text input widget, a widget to display files, a widget to display frequently-used or important locations, a widget to create a new subfolder, and so forth. You could create a dialog like the image above, and handle the user response, like so:
GtkWidget *dialog;
GtkFileChooser *chooser; 
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
gint res;

dialog = gtk_file_chooser_dialog_new (
   "Json output path",
   parent_window,
   action,
   _("_Cancel"), GTK_RESPONSE_CANCEL,
   _("_Save"), GTK_RESPONSE_ACCEPT, NULL
);
chooser = GTK_FILE_CHOOSER (dialog);
gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
gtk_file_chooser_set_current_name (chooser, _("new_signatures.json"))

res = gtk_dialog_run (GTK_DIALOG (dialog));
if (res == GTK_RESPONSE_ACCEPT) {
  char *filename;
  filename = gtk_file_chooser_get_filename(chooser);
  save_to_file (filename);
  g_free (filename);
}
gtk_widget_destroy (dialog);
That may look like a lot of work, Note 1: save_to_file() you write yourself; Gtk provides everything else.

Note 2: Don’t quote me on this example; I simply modified the example in Gtk’s documentation. In fact, I mis-modified it at first, and found a typo when reviewing it.
but compared to what I’ve had to do in previous decades, it’s a welcome relief: You get the idea.

In Ada, you accomplish the same with a little more effort, which looks like a lot more only because of comments (+10 lines), formatting (+16 lines), and my preference to shorten package names (+3 lines):
-- pop up a filename chooser dialog to get the filename
      declare

         -- let's make our lives a little easier
         package Dialog renames Gtk.Dialog;
         use all type Dialog.Gtk_Response_Type;
         package File_Chooser renames Gtk.File_Chooser;
         package FCD renames Gtk.File_Chooser_Dialog;

         -- the dialog
         Filename_Dialog: FCD.Gtk_File_Chooser_Dialog;
         -- needed only to discard return value
         Discard: Gtk.Widget.Gtk_Widget;

      begin

         -- create chooser dialog
         FCD.Gtk_New
            (
             Dialog    => Filename_Dialog,
             Title     => "Json output path",
             Parent    => null,
             Action    => File_Chooser.Action_Save
            );

         -- add save, cancel buttons
         Discard := Dialog.Add_Button
            (
             Dialog      => Dialog.Gtk_Dialog(Filename_Dialog),
             Text        => "_Cancel",
             Response_Id => Dialog.Gtk_Response_Cancel
            );
         Discard := Dialog.Add_Button
            (
             Dialog      => Dialog.Gtk_Dialog(Filename_Dialog),
             Text        => "_Save",
             Response_Id => Dialog.Gtk_Response_Accept
            );

         -- make sure user is OK with overwriting old file
         FCD.Set_Do_Overwrite_Confirmation(Filename_Dialog, True);
         FCD.Set_Current_Name(Filename_Dialog, "new_signatures.json");

         -- run the dialog and react accordingly
         -- (if the user cancels we don't do anything)
         if FCD.Run(Filename_Dialog) = Dialog.Gtk_Response_Accept then
            declare Filename: UTF8_String := FCD.Get_Filename(Filename_Dialog);
            begin
               Write_Quotes(Data, Filename);
            end;
         end if;

         -- don't forget to destroy the dialog or it will stay there
         Gtk.Widget.Destroy(Gtk.Widget.Gtk_Widget(Filename_Dialog));

      end;
The main difference from the C code is that GtkAda requires us to add the buttons after we create the dialog in FCD.Gtk_New, whereas C allows us to create the buttons while creating the dialog in gtk_file_chooser_dialog_new().

The Ada style actually means we don’t have to “free” the filename, the way the C code does in line 22.

A few other points:

Conclusion

It reads fairly sensibly as straightforward code. The design of GtkAda takes care of some things that C would require us to worry about (memory management, as with g_free(filename). The main difficulty lay in making sense of misleading-to-mistaken documentation, but there was enough good material there, and Ada documents itself well enough, that I was able to make sense of it, and it was gratifying to see it work in the end.