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:

  1. designating the application as a service,
  2. passing in parameters to modify the behaviour of the running software, or
  3. 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 IDs, 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

Results of this example:
Current example output
Current example output
Current example terminal output
Current example terminal output (click for enlarged view)

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:

  • IDs to make window management easier,
  • help overlays (more on these in a moment), and
  • hideable menubars.

The first—IDs—is more or less self-explanatory. The Application uses ApplicationWindow IDs for window management. Windows 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 Windows or MainWindows, 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.

You can also subscribe via RSS so you won't miss anything. Thank you very much for dropping by.

© Copyright 2024 Ron Tarrant