GTK+ New Years Eve Countdown App

I’ve been educating myself about GTK+ programming lately. So I wrote this semi-short GTK app to countdown the time to New Years Day to learn more about GTK+, Cairo drawing, and Pango font library.

#include <gtk/gtk.h>
#include <time.h>
#include <glib/gprintf.h>

static char *
nye_countdown_str()
{
    struct tm nyd = {0}; /* inits struct members to 0 */
    nyd.tm_year = 2025 - 1900;
    nyd.tm_mday = 1;

    time_t nyd_time = mktime(&nyd);
    time_t diff = difftime(nyd_time, time(NULL));

    GString *buff = g_string_new("");
    if ((int)(diff / 86400))
        g_string_append_printf(buff, " %d %s\n", (int)(diff / 86400), "days");
    if ((int)((diff % 86400) / 3600))
        g_string_append_printf(buff, "%d %s\n", (int)(diff % 86400) / 3600, "hours");
    if ((int)(diff % 3600) / 60)
        g_string_append_printf(buff, "%d %s\n", (int)(diff % 3600) / 60, "minutes");
    if ((int)(diff % 60))
        g_string_append_printf(buff, "%d %s\n", (int)(diff % 60), "seconds");

    return buff->str;
}

static void
draw_countdown(GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer user_data)
{
    // make the ball drop one pixel every second through the top of minute
    time_t now = time(NULL);
    struct tm *currentTime = localtime(&now);

    cairo_set_source_rgba(cr, 0, 0, 0, 0.8);
    cairo_paint(cr);

    cairo_set_source_rgb(cr, 0, 0, 0);
    cairo_rectangle(cr, width / 2 - 20, (width - 500) / 2 + 20, 40, height);
    cairo_fill(cr);

    float colorShift = currentTime->tm_sec / 60 + 0.5;
    if (colorShift > 1)
        colorShift = 0.85;

    cairo_set_source_rgb(cr, 1, 1, colorShift);

    cairo_arc(cr, width / 2, 80 + currentTime->tm_sec, 200 / 2, 0,
              2 * 3.14159);

    cairo_fill(cr);

    cairo_set_source_rgb(cr, 1, 1, 1);

    PangoLayout *pangLayout;
    PangoFontDescription *pangDesc;

    pangLayout = pango_cairo_create_layout(cr);
    pango_layout_set_text(pangLayout, nye_countdown_str(), -1);
    pangDesc = pango_font_description_from_string("American Typewriter 40");
    pango_layout_set_font_description(pangLayout, pangDesc);
    pango_font_description_free(pangDesc);

    pango_layout_set_alignment(pangLayout, PANGO_ALIGN_CENTER);

    PangoRectangle pLExtents = {0};
    pango_layout_get_extents(pangLayout, NULL, &pLExtents);
    pango_extents_to_pixels(NULL, &pLExtents);

    cairo_move_to(cr, (width / 2) - (pLExtents.width / 2), 300);
    pango_cairo_update_layout(cr, pangLayout);
    pango_cairo_show_layout(cr, pangLayout);

    pangLayout = pango_cairo_create_layout(cr);
    pango_layout_set_text(pangLayout, "2025", -1);
    pangDesc = pango_font_description_from_string("American Typewriter Bold 50");
    pango_layout_set_font_description(pangLayout, pangDesc);
    pango_font_description_free(pangDesc);

    pango_layout_set_alignment(pangLayout, PANGO_ALIGN_CENTER);
    pango_layout_get_extents(pangLayout, NULL, &pLExtents);
    pango_extents_to_pixels(NULL, &pLExtents);

    cairo_move_to(cr, 0, 120);
    cairo_rotate(cr, -45 * (M_PI / 180));
 
    pango_cairo_update_layout(cr, pangLayout);
    pango_cairo_show_layout(cr, pangLayout);
}

static gboolean
time_handler(GtkWidget *widget)
{
    gtk_widget_queue_draw(widget);

    return TRUE;
}

static void
app_activate(GApplication *app, gpointer user_data)
{
    GtkWidget *win, *countdown, *box, *creator_labl;
    int curWindowHeight;

    win = gtk_application_window_new(GTK_APPLICATION(app));
    gtk_window_set_title(GTK_WINDOW(win), "Countdown to 2025");
    gtk_window_set_default_size(GTK_WINDOW(win), 600, 600);

    box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
    gtk_window_set_child(GTK_WINDOW(win), box);

    countdown = gtk_drawing_area_new();
    gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(countdown), draw_countdown, NULL, NULL);

    gtk_window_get_default_size(GTK_WINDOW(win), NULL, &curWindowHeight);

    gtk_drawing_area_set_content_height(GTK_DRAWING_AREA(countdown), hgcurWindowHeight);
    gtk_box_append(GTK_BOX(box), countdown);

    creator_labl = gtk_label_new("Created by Andy Arthur, December 29, 2024");
    gtk_box_append(GTK_BOX(box), creator_labl);

    g_timeout_add(1000,  (GSourceFunc)time_handler, (gpointer)countdown);
    gtk_window_present(GTK_WINDOW(win));
}

static void
app_open(GApplication *app, GFile **files, gint n_files, gchar *hint, gpointer user_data)
{
    app_activate(app, user_data);
}

int main(int argc, char **argv)
{
    GtkApplication *app;
    int stat;

    app = gtk_application_new("org.andyarthur.nye", G_APPLICATION_HANDLES_OPEN);
    g_signal_connect(app, "activate", G_CALLBACK(app_activate), NULL);
    g_signal_connect(app, "open", G_CALLBACK(app_open), NULL);
    stat = g_application_run(G_APPLICATION(app), argc, argv);
    g_object_unref(app);
    return stat;
}

Map: Alma Pond
Map: Dobbins Memorial State Forest
Map: Donahue Woods State Forest
Map: Little John Wildlife Management Area
Map: Otter Lake
Map: South Hill State Forest (Oneida 23)
Map: Summer Hill State Forest
Map: West Parishville State Forest
SVGZ Graphic: albany-snow-depth
SVGZ Graphic: college-rate
SVGZ Graphic: december-holidays
SVGZ Graphic: ht2025
SVGZ Graphic: Increase in Price for a Gallon of Regular Gas Since May 13, 2025 [Expires May 27 2026]
SVGZ Graphic: lt2025
SVGZ Graphic: May-sunset [Expires May 27 2026]
SVGZ Graphic: Places Named Bethlehem
SVGZ Graphic: Towns with Most Similiar Land Cover to the Town of Bethlehem
Terrain Map: Happy World Milk Day!
Photo: Wooded Meadows In Northern Catskills
Photo: Dolly Sods Road
Photo: Hot And Humid Afternoon
Photo: Mount Ginseng
Photo: Trees Along The Lake
Photo: Later In The Morning
Photo: Sunlight
Photo: Further Up Cole Hill
Photo: Farm Along NY 12B
Photo: Bully Hill Road

Leave a Reply

Your email address will not be published. Required fields are marked *