This is the first article in the series of the articles where are are going to build Image viewer - imager. The idea is simple, we will create a minimal image viewer program and then extend the functionalities of it via "extension articles". Let's see how things will go! I'll update the following list as I publish the article(s) in this series.
tl;dc: Refer this gist to get the final version of imager.c code.
Go ahead and create a file with name imager.c and write the following stub code.
#include <gtk/gtk.h> static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
If you need brief explanation on this code, refer the Let's Build Terminal Pt. 1 article. Although, it is from "Let's Build Terminal" series but the first part is generic enough to understand such a code.
Use following command to compile and run the program.
gcc stub.c `pkg-config --cflags --libs gtk4` && ./a.out
Let's add the header bar. The plan is to have a "Open" button in header bar that allow us to select the image file to open and view.
We can create a header bar using gtk_header_bar_new() function and then give a title to it using gtk_header_bar_set_title_widget(). Finally, we can use gtk_window_set_titlebar() function to set the header bar as title bar of the window.
#include <gtk/gtk.h> static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Now, go ahead and create a button using gtk_button_new_with_label() function and then pack it to the header bar.
#include <gtk/gtk.h> static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Run the program and it should produce output like this.
When user click on button, it should open a file dialog to select the image. We need to pass the window instance as user data because it is needed to load the image into the window.
#include <gtk/gtk.h> static void _on_open_clicked(GtkWidget* button, gpointer data) { } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Let's cast and save the window passed as user data and create a file dialog using gtk_file_dialog_new() function.
#include <gtk/gtk.h> static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
In order to actually see the file dialog, we need to call the gtk_file_dialog_open() function. We need to pass 5 arguments - dialog itself, window as parent, NULL as cancellable operation, callback function, and user data.
#include <gtk/gtk.h> static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
We need to define the _on_file_opened() function. But is a asnyc callback. Due to this it accepts these parameters - source object that has started asnyc operation (file dialog) of type GObject*, result of the operation of type GAsyncResult*, and user data.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Again, let's get the values of dialog and window from the function parameters.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
We need to call the gtk_file_dialog_open_finish() function. This function finish the gtk_file_dialog_open() function and return either the selected file or an error.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
If we get the error, we need to stop and free the error memory using g_error_free(). We can also print the error message using g_printerr() function.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); if (error) { g_printerr("Error opening file %s\n", error->message); g_error_free(error); return; } } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
But, if we have file, we can proceed with rendering the image on the window. Let's create a picture widget using gtk_picture_new() function and then set it as child of the window.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); if (error) { g_printerr("Error opening file %s\n", error->message); g_error_free(error); return; } if (file) { GtkWidget* picture = gtk_picture_new(); gtk_window_set_child(window, picture); } } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
In order to actually render the picture in this widget, we need to use texture widget from the GDK library. We can create texture of selected file using gdk_texture_new_from_file() function and then use it with picture instance. The gdk_texture_new_from_file() function accepts file path and error struct. We can get the path of the selected file using g_file_get_path() function.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); if (error) { g_printerr("Error opening file %s\n", error->message); g_error_free(error); return; } if (file) { char* path = g_file_get_path(file); GtkWidget* picture = gtk_picture_new(); gtk_window_set_child(window, picture); GdkTexture* texture = gdk_texture_new_from_file(file, &error); if (error) { } else { } } } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Again, if we get the error, then we need to stop by printing the message and free the error memory.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); if (error) { g_printerr("Error opening file %s\n", error->message); g_error_free(error); return; } if (file) { char* path = g_file_get_path(file); GtkWidget* picture = gtk_picture_new(); gtk_window_set_child(window, picture); GdkTexture* texture = gdk_texture_new_from_file(file, &error); if (error) { g_printerr("Error loading image %s\n", error->message); g_error_free(error); return; } else { } } } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
In else block, we'll call gtk_picture_set_paintable() to set the texture to make it printable by casting it using GDK_PIAINTABLE() macro. We also need to unref the texture as we are done with it.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); if (error) { g_printerr("Error opening file %s\n", error->message); g_error_free(error); return; } if (file) { char* path = g_file_get_path(file); GtkWidget* picture = gtk_picture_new(); gtk_window_set_child(window, picture); GdkTexture* texture = gdk_texture_new_from_file(file, &error); if (error) { g_printerr("Error loading image %s\n", error->message); g_error_free(error); return; } else { gtk_picture_set_paintable(GTK_PICTURE(picture), GDK_PAINTABLE(texture)); g_object_unref(texture); } } } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
Don't forget to free the path memory and unref the file object.
#include <gtk/gtk.h> static void _on_file_opened(GObject* source_object, GAsyncResult* res, gpointer data) { GtkFileDialog* file_dialog = GTK_FILE_DIALOG(source_object); GtkWindow* window = GTK_WINDOW(data); GFile* file = NULL; GError* error = NULL; file = gtk_file_dialog_open_finish(file_dialog, res, &error); if (error) { g_printerr("Error opening file %s\n", error->message); g_error_free(error); return; } if (file) { char* path = g_file_get_path(file); GtkWidget* picture = gtk_picture_new(); gtk_window_set_child(window, picture); GdkTexture* texture = gdk_texture_new_from_file(file, &error); if (error) { g_printerr("Error loading image %s\n", error->message); g_error_free(error); return; } else { gtk_picture_set_paintable(GTK_PICTURE(picture), GDK_PAINTABLE(texture)); g_object_unref(texture); } g_free(path); g_object_unref(file); } } static void _on_open_clicked(GtkWidget* button, gpointer data) { GtkWindow* window = GTK_WINDOW(data); GtkFileDialog* file_dialog = gtk_file_dialog_new(); gtk_file_dialog_open(file_dialog, window, NULL, _on_file_opened, window); } static void activate(GtkApplication* app, gpointer data) { GtkWidget* window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "Imager"); gtk_window_set_default_size(GTK_WINDOW(window), 600, 800); // Header bar. GtkWidget* header_bar = gtk_header_bar_new(); gtk_header_bar_set_title_widget(GTK_HEADER_BAR(header_bar), gtk_label_new("Imager")); // Button. GtkWidget* button = gtk_button_new_with_label("Open"); g_signal_connect(button, "clicked", G_CALLBACK(_on_open_clicked), window); gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), button); gtk_window_set_titlebar(GTK_WINDOW(window), header_bar); gtk_window_present(GTK_WINDOW(window)); } int main(int argc, char** argv) { GtkApplication* app = gtk_application_new("dev.marichi.imager", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); return status; }
And with these changes, we now have the image viewer. Go ahead and run the program to confirm the image viewer!
Yay!




