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:
- I’ll bet future civilizations find out more about us
than we’d like them to know. (from Calvin & Hobbes)
- Life is but a dream, a little less inconstant. (from Pensees)
- “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 my file of quotes grew,
I wanted to add more information than just quotation and author.
- As I learned more about programming standards, in particular the Json format,
I thought it would be nice to convert the file of quotes
from my rather lame custom format to a more meaningful Json file.
As text editing is sometimes fun, but often tedious,
I decided it would be a fun challenge to write an editor in graphical format.
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:
- 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…
- 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.
- 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,
procedure
s 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:
- no worrying about the width and height;
- no worrying about setting up callback hooks;
- no worrying about writing code to read from the file system
and display it in the file list; …
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.