Let's Build Image Viewer

April 28, 2026

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.

  1. Let's Build Image Viewer

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!

No comments:

Post a Comment