After the Hiatus
It’s been more than a year since I made my last post on this blog. When I stopped posting last year, I was burned out, distracted by COVID-19, our tiny apartment was suddenly a hub of work activity for both of us, and I was still disheartened by the changes being made to GTK in version 4.
Now, I can see things a little more clearly… sort of. COVID-19 is still here and we may all go into (at least) one more round of lock-downs. But, other things are advancing…
My wife and I have both been double-vaccinated and we’ve worked out an arrangement whereby we can both work in this tiny apartment without driving each other up the wall.
Thirteen days ago, Mike Wey released GtkD 4, but the articles I’ve got on the go are all centred around GtkD 3.9. And frankly, I’m not sure I want to update to 4 because the GTK team dropped window position handling. Yes, it’s a small thing, but I see a problem with this…
The GTK team members believe window positions should be handled by the OS’s window manager. I do agree with them except for one thing: not all window managers remember window positions. I use three monitors, so this is kind of important to me. I like my applications to open in the last place I used them so I don’t have to search miles of screen real estate to find them.
So, with that in mind, I’d like to hear from you. Is anyone still interested in articles about GtkD 3.x? Please let me know in the comments below.
And with all that said, let’s dig into today’s article.
0112: GTK GIO Applications - Introduction
Up ‘til now, every example has been built up from a MainWindow
widget and a Main
struct, both of which are instantiated in the standard entry point function, main()
. (Note: TestRigWindow
—the actual object we’ve been instantiating in our examples—inherits from MainWindow
, so it amounts to the same thing.) But today, we’re looking at an alternative way of building applications, this time using the GTK/GIO Application
class modules.
I’d like to point out that I didn’t stutter in that last sentence. There are two Application
modules… the GIO Application
is the parent class and the GTK class is derived from it. This can be a bit confusing when it comes time to write code because both modules need to be imported if we want to handle GIO signals. But, there’s a simple way to keep them straight, so let’s just dive in.
Why Application?
The Application
is a more flexible framework for writing software. It doesn’t just give us the tools for building classic GUI-based software, it makes several other project types possible:
- a GUI front-end for a background service,
- the background service itself,
- remote applications controlled locally,
- local applications controlled remotely, and
- a GUI-less command line application (the kind intended to be run from a terminal).
On top of that, a GIO Application
has a signal/callback system that gives us all kinds of flexibility in how we start up our application.
Finally, it also gives us a system of flags we can use for all kinds of stuff including:
- designating the application as a service,
- passing in parameters to modify the behaviour of the running software, or
- react to the existence of environment variables on the computer where the application is running.
Old Method vs. New
The biggest difference between the MainWindow
approach and this one is this…
In the Application
paradigm, signals are used to associate callbacks with such things as activate
, startup
, and shutdown
. In the paradigm we’ve been using ‘til now, Main.init()
, Main.run()
, and Main.quit()
(respectively) take care of these things. Nothing new is going on here, but responsibility for application-level stuff has shifted from a C-style struct (Main
) to a D-style object (Application
).
MainWindow vs. ApplicationWindow
In the classical construction method, MainWindow
acts as a top-level container for our program, but the GTK/GIO Application
instead uses the ApplicationWindow
as its primary UI container. They both inherit from Window
, so we still get pretty much the same functionality. But the GTK/GIO Application
construction method adds such things as window ID
s, a pop-up help window for keyboard shortcuts, and a mechanism to handle how and when a Menu
or Menubar
is displayed. More on these as we go along, but for now, let’s dig into a barebones example…
Working with GIO/GTK Applications
Right up front, naturally, we need to do some imports to get all this working. But because the GTK and GIO modules are both named Application
, we need to put some extra effort into keeping them straight. That’s done with D’s aliasing feature:
import gio.Application : GioApplication = Application;
import gtk.Application : GtkApplication = Application;
import gtk.ApplicationWindow;
Alias names are up to you, of course, but for this demonstration, I’m emphasizing clarity of function.
How main() Differs
In the classical construction method, our main()
function had to do a few things before handing control over to the Main
struct
… I’m pointing this out because, as it turns out, Main
is a struct
, not a class
. It’s the reason we don’t sub-class it into MyMain
or some-such in order to move its definition outside of the main()
function.
With the GTK/GIO Application
construction method, however, we don’t have this restriction, so main()
only has to do one thing, instantiate the GTK Application
object:
void main(string[] args)
{
MyApplication thisApp = new MyApplication(args);
} // main()
Note that it passes any command-line arguments along to the MyApplication
constructor, another change from the old way of doing things in which we passed them to Main.init()
. And keep in mind, MyApplication
is derived from the GTK Application
object, not its GIO namesake.
Speaking of which, let’s have a look at this sub-class…
MyApplication, a GTK Application Lovechild
The preamble looks like this:
class MyApplication : GtkApplication
{
ApplicationFlags flags = ApplicationFlags.FLAGS_NONE;
string id = null; // if application uniqueness isn't important
In order to call the super-class constructor, we need two things:
- one or more flags to set up the
Application
’s type and abilities, and - an
ID
in the form of a string.
Now, you’ll note that this particular example has id
set to null
. That’s because, if we really don’t care about Application
uniqueness, we don’t have to supply an ID
. In the next example, we’ll talk more about this, but for now, let’s move on to…
The MyApplication Constructor
This is where we set up the Application
and get things going:
this(string[] args)
{
super(id, flags);
addOnActivate(&onActivate);
run(args);
} // this()
The first line isn’t all that different from what we’re used to. We call the super-class constructor, passing it the id
and flags
variables. In this example, because id
is null
and our flags
variable is NONE
, we aren’t asking anything of the super-class constructor except to start the application.
The next line, however, is a departure from the old method we’ve been using. I’m sure you recognize the function naming convention even if you don’t know the addOnActivate()
function itself. It’s a signal hook-up, as you’ve likely already guessed. Why it’s there is because (and you might wanna make an extra-large mental note of this):
Application actions are processed via signals and callbacks.
This means the GIO/GTK Application
construction approach brings external operations directly under the control of a single, high-level entity, the Application
object. I’m talking about things like window and accelerator management… or OS-related tasks such as handling command-line arguments, starting up, shutting down… In other words, because the uppercase-A Application
handles all things external, the lowercase-a application (in other words: our code) doesn’t need to be aware of them. There’s no mixing of internal and external operations and therefore better separation of code.
Looking at the last line, we find another way this new application construction method differs from the old. The command-line arguments—instead of being passed to an init()
function—are passed to the run()
function.
What’s the difference? Not much, actually. Both Main.init()
and Gio.Application.run()
count the number of command-line arguments and build a string array with one element per argument. The biggest difference here is that Main
is a C struct whereas Gio.Application
is a proper class and is more consistent with the OOP paradigm we’ve been using in every example posted on this blog.
The onActivate Callback
This is the only place, in a simple application, where we need to reference the Gio.Application
module:
void onActivate(GioApplication app) // non-advanced syntax
{
AppWindow appWindow = new AppWindow(this);
} // onActivate()
This is because the addOnActivate()
function lives in that module. There are other signal hook-up functions in Gtk.Application
and, of course, Gtk.Application
inherits from Gio.Application
, but when hooking up signals—just as we’ve seen elsewhere—we need to declare the arguments to be exactly what they are in the wrapper file.
We have one last class to look at…
The AppWindow Class
This class, as mentioned above, inherits from ApplicationWindow
which in turn inherits from GTK Window
which means for the most part, it’s just another Window
. It does have a few features not found in the generic GTK Window
, however, things like:
ID
s to make window management easier,- help overlays (more on these in a moment), and
- hideable menubars.
The first—ID
s—is more or less self-explanatory. The Application
uses ApplicationWindow
ID
s for window management. Window
s can be added, removed… you get the idea.
Help overlays, however, aren’t something we’ll find in the old construction method. These are inspired by mobile apps where the help screen slides in over top of the ApplicationWindow
and slides back out when we’re done with it.
Hideable Menubars
are also inspired by mobile apps, although they’re becoming more prevalent on desktops as well.
Anyway, that’s all nice in theory, but how about a look at the code:
class AppWindow : ApplicationWindow
{
int width = 400, height = 200;
string title = "Barebones Application";
this(MyApplication myApp)
{
super(myApp);
setSizeRequest(width, height);
setTitle(title);
showAll();
} // this()
} // class AppWindow
As with run-of-the-mill Window
s or MainWindow
s, we set up dimensions and a title in the preamble, then in the constructor we call the super-class constructor, set the size, the title, and then call showAll()
. The only thing here that departs from the old construction method is setting up an Application
pointer which we pass to the super-class constructor, so what’s up with that?
Not a lot, really. We’re setting up an association between the ApplicationWindow
and the GIO/GTK Application
so the Application
can manage the ApplicationWindow
. Makes sense, right?
Conclusion
Anyway, that’s all for today. This should give you a basic understanding of what’s going on behind the curtain when you use this alternate application construction method.
Next time, we’ll dig a little deeper. See you then.
Comments? Questions? Observations?
Did we miss a tidbit of information that would make this post even more informative? Let's talk about it in the comments.
- come on over to the D Language Forum and look for one of the gtkDcoding announcement posts,
- drop by the GtkD Forum,
- follow the link below to email me, or
- go to the gtkDcoding Facebook page.
You can also subscribe via RSS so you won't miss anything. Thank you very much for dropping by.
© Copyright 2024 Ron Tarrant