Gtkdialog Calendar

For discussions about programming, and for programming questions and advice


Moderator: Forum moderators

User avatar
Jasper
Posts: 1821
Joined: Wed Sep 07, 2022 1:20 pm
Has thanked: 895 times
Been thanked: 384 times

Re: Gtkdialog Calendar

Post by Jasper »

@greengeek

Have you tried just creating a script for the application?

From rockedge's post:

https://forum.puppylinux.com/viewtopic. ... 47#p125647

on my Fossapup95

kalendarmuks-fossapup95.png

.......... it does create a lot of text files in the a directory of images.

or as per usual am I 'barking' up the wrong tree here? :lol:

User avatar
rockedge
Site Admin
Posts: 7022
Joined: Mon Dec 02, 2019 1:38 am
Location: Connecticut,U.S.A.
Has thanked: 3149 times
Been thanked: 2934 times
Contact:

Re: Gtkdialog Calendar

Post by rockedge »

Here is the one written in C and compiled on a NoblePup64:

Screenshot(19).jpg
Screenshot(19).jpg (32.96 KiB) Viewed 593 times

Compiled from this code by @superhik:
calendar.c

Code: Select all

#include <gtk/gtk.h>
#include <glib.h>
#include <cairo.h>

typedef struct {
    GtkWidget *window;
    GtkWidget *drawing_area;
    GtkWidget *text_view;
    GtkWidget *save_button;
    GtkWidget *remove_button;
    GtkWidget *spin_year;
    GtkWidget *combo_month;
    gchar *diary_dir;
    guint current_year;
    guint current_month;
    guint selected_day;
    GHashTable *marked_dates;
} AppWidgets;

static gboolean draw_calendar(GtkWidget *widget, cairo_t *cr, AppWidgets *app) {
    const gchar *day_names[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};

    guint days_in_month = g_date_get_days_in_month(app->current_month + 1, app->current_year);
    GDate *date = g_date_new_dmy(1, app->current_month + 1, app->current_year);
    GDateWeekday first_weekday = g_date_get_weekday(date);
    g_date_free(date);

    GDateTime *now = g_date_time_new_now_local();
    guint today_day = g_date_time_get_day_of_month(now);
    guint today_month = g_date_time_get_month(now);
    guint today_year = g_date_time_get_year(now);
    g_date_time_unref(now);

    GtkAllocation allocation;
    gtk_widget_get_allocation(widget, &allocation);
    gint cell_width = allocation.width / 7;
    gint cell_height = allocation.height / 7;  // Adjusted for day names
    cairo_set_source_rgba(cr, 1, 1, 1, 0.5);
    cairo_rectangle(cr, cell_width, cell_height, cell_width, cell_height);
    cairo_paint(cr);

    // Centering calculations
    gint text_width, text_height;
    cairo_text_extents_t extents;
    cairo_set_font_size(cr, cell_height * 0.4); // Font size for day names
    cairo_text_extents(cr, day_names[0], &extents);
    text_width = extents.width;
    text_height = extents.height;

    // Calculate starting point for day names to center them
    gint start_x = (cell_width - text_width) / 2;
    gint start_y = cell_height * 0.6;

    cairo_set_source_rgb(cr, 0, 0, 0);
    // Draw day names
    for (int i = 0; i < 7; i++) {
        cairo_move_to(cr, i * cell_width / 2, cell_height * 0.6 / 2);
        if (i == 6) {
            // Draw Sunday in red
            cairo_set_source_rgb(cr, 1, 0, 0);
        } else if (i == 5) {
           cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
        } else {
            cairo_set_source_rgb(cr, 0, 0, 0);
        }
        cairo_text_extents(cr, day_names[i], &extents);
        text_width = extents.width;
        cairo_move_to(cr, i * cell_width + start_x, start_y);
        cairo_show_text(cr, day_names[i]);
    }

    // Draw grid
    cairo_set_line_width(cr, 1);
    cairo_set_source_rgb(cr, 0, 0, 0);
    for (int i = 0; i < 7; i++) {
        for (int j = 1; j < 7; j++) {
            cairo_rectangle(cr, i * cell_width, j * cell_height, cell_width, cell_height);
        }
    }
    cairo_stroke(cr);
    cairo_set_font_size(cr, cell_height * 0.5); // Adjust font size proportionally
    // Draw days
    for (guint day = 1; day <= days_in_month; day++) {
        gint row = (day + first_weekday - 2) / 7 + 1;
        gint column = (day + first_weekday - 2) % 7;
        gchar *text = g_strdup_printf("%2u", day);

        // Calculate text extents for day number
        cairo_text_extents(cr, text, &extents);
        text_width = extents.width;
        text_height = extents.height;
        
        // Calculate starting point to center day number
        start_x = column * cell_width + (cell_width - text_width) / 2;
        start_y = row * cell_height + cell_height / 2 + text_height / 2;
        if (column == 6) {
            // Draw Sunday dates in red
            cairo_set_source_rgb(cr, 1, 0, 0);
        } else if (column == 5) {
           cairo_set_source_rgb(cr, 0.2, 0.2, 0.2);
        } else {
            cairo_set_source_rgb(cr, 0, 0, 0);
        }
        cairo_move_to(cr, start_x, start_y);
        cairo_show_text(cr, text);
        g_free(text);

        if (g_hash_table_contains(app->marked_dates, GINT_TO_POINTER(day))) {
            cairo_set_source_rgb(cr, 0, 0, 0); 
            // Calculate dynamic radius based on cell dimensions
            double radius = MIN(cell_width, cell_height) / 10.0;
            cairo_arc(cr, column * cell_width + cell_width - 1.5 * radius, row * cell_height + 1.5 * radius, radius, 0, 2 * G_PI);

            cairo_close_path(cr);
            cairo_fill(cr);
            cairo_set_source_rgb(cr, 0, 0, 0);
        }

        // Highlight the selected day
        if (day == app->selected_day) {
            cairo_set_source_rgba(cr, 1, 0.7, 1, 0.07);
            cairo_set_source_rgb(cr, 0, 0, 1);  // Blue color for selected day
            cairo_rectangle(cr, column * cell_width, row * cell_height, cell_width, cell_height);
            cairo_set_line_width(cr, 3);
            cairo_stroke(cr);
            cairo_set_source_rgba(cr, 0, 0.7, 1, 0.1);
            cairo_rectangle(cr, column * cell_width, row * cell_height, cell_width, cell_height);
            cairo_fill(cr);
            cairo_set_source_rgb(cr, 0, 0, 0);  // Set back to black for text
        }

        // Highlight today's date
        if (day == today_day && app->current_month + 1 == today_month && app->current_year == today_year) {
            cairo_set_source_rgba(cr, 0, 1, 0, 0.3);  // Green color with transparency
            cairo_rectangle(cr, column * cell_width, row * cell_height, cell_width, cell_height);
            cairo_fill(cr);
            cairo_set_source_rgb(cr, 0, 0, 0);  // Set back to black for text
        }

    }

    return FALSE;
}

static void mark_dates_with_entries(AppWidgets *app) {
    g_hash_table_remove_all(app->marked_dates);

    GDir *dir = g_dir_open(app->diary_dir, 0, NULL);
    if (!dir) return;

    const gchar *filename;
    while ((filename = g_dir_read_name(dir))) {
        guint entry_year, entry_month, entry_day;
        if (sscanf(filename, "%4u-%2u-%2u.txt", &entry_year, &entry_month, &entry_day) == 3) {
            if (entry_year == app->current_year && entry_month == app->current_month + 1) {
                g_hash_table_add(app->marked_dates, GINT_TO_POINTER(entry_day));
            }
        }
    }
    g_dir_close(dir);
    gtk_widget_queue_draw(app->drawing_area);
}

static void on_day_selected(GtkWidget *widget, GdkEventButton *event, AppWidgets *app) {
    guint days_in_month = g_date_get_days_in_month(app->current_month + 1, app->current_year);
    GDate *date = g_date_new_dmy(1, app->current_month + 1, app->current_year);
    GDateWeekday first_weekday = g_date_get_weekday(date);
    g_date_free(date);

    GtkAllocation allocation;
    gtk_widget_get_allocation(widget, &allocation);
    gint cell_width = allocation.width / 7;
    gint cell_height = allocation.height / 7;  // Adjusted for day names

    gint column = event->x / cell_width;
    gint row = (event->y / cell_height) - 1;  // Adjust for day names
    gint day = row * 7 + column - first_weekday + 2;

    if (day < 1 || day > days_in_month) return;

    gchar *filename = g_strdup_printf("%s/%u-%02u-%02u.txt", app->diary_dir, app->current_year, app->current_month + 1, day);
    gchar *content = NULL;
    g_file_get_contents(filename, &content, NULL, NULL);
    GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(app->text_view));
    gtk_text_buffer_set_text(buffer, content ? content : "", -1);
    g_free(content);
    g_free(filename);

    app->selected_day = day;
    mark_dates_with_entries(app);
}

static void on_save_clicked(GtkButton *button, AppWidgets *app) {
    if (app->selected_day == 0) return; // No day selected

    GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(app->text_view));
    GtkTextIter start, end;
    gtk_text_buffer_get_bounds(buffer, &start, &end);
    gchar *content = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);

    gchar *filename = g_strdup_printf("%s/%u-%02u-%02u.txt", app->diary_dir, app->current_year, app->current_month + 1, app->selected_day);
    g_file_set_contents(filename, content, -1, NULL);
    g_free(filename);
    g_free(content);

    mark_dates_with_entries(app);
}

static void on_month_changed(GtkComboBox *combo, AppWidgets *app) {
    app->current_month = gtk_combo_box_get_active(combo);
    mark_dates_with_entries(app);
}

static void on_year_changed(GtkSpinButton *spin, AppWidgets *app) {
    app->current_year = gtk_spin_button_get_value_as_int(spin);
    mark_dates_with_entries(app);
}

static void on_remove_entry_clicked(GtkWidget *button, AppWidgets *app) {
    if (app->selected_day == 0) return;

    gchar *filename = g_strdup_printf("%s/%u-%02u-%02u.txt", app->diary_dir, app->current_year, app->current_month + 1, app->selected_day);
    remove(filename);
    g_free(filename);

    GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(app->text_view));
    gtk_text_buffer_set_text(buffer, "", -1);

    mark_dates_with_entries(app);
}

static void on_today_button_clicked(GtkWidget *button, AppWidgets *app) {
    GDateTime *now = g_date_time_new_now_local();
    guint today_year = g_date_time_get_year(now);
    guint today_month = g_date_time_get_month(now) - 1; // Adjust for zero-based index
    guint today_day = g_date_time_get_day_of_month(now);
    g_date_time_unref(now);

    // Update the current year, month, and selected day
    app->current_year = today_year;
    app->current_month = today_month;
    app->selected_day = today_day;

    // Update the spin button for the year
    gtk_spin_button_set_value(GTK_SPIN_BUTTON(app->spin_year), app->current_year);

    // Update the combo box for the month
    gtk_combo_box_set_active(GTK_COMBO_BOX(app->combo_month), app->current_month);

    // Redraw the calendar
    gtk_widget_queue_draw(app->drawing_area);

    // Mark dates with entries
    mark_dates_with_entries(app);
}

int main(int argc, char *argv[]) {
    gtk_init(&argc, &argv);

    AppWidgets *app = g_slice_new(AppWidgets);

    // Get the current date
    GDateTime *now = g_date_time_new_now_local();
    app->current_year = g_date_time_get_year(now);
    app->current_month = g_date_time_get_month(now) - 1; // g_date_time_get_month() is 1-based
    app->selected_day = g_date_time_get_day_of_month(now);
    g_date_time_unref(now);

    app->diary_dir = g_build_filename(g_get_user_data_dir(), "diary_entries", NULL);
    g_mkdir_with_parents(app->diary_dir, 0755);
    app->marked_dates = g_hash_table_new(g_direct_hash, g_direct_equal);

    app->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(app->window), "Custom Calendar Widget");
    gtk_window_set_default_size(GTK_WINDOW(app->window), 500, 600);
    gtk_window_set_icon_name(GTK_WINDOW(app->window), "xfcalendar");

    GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
    gtk_container_add(GTK_CONTAINER(app->window), vbox);

    GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);

    app->spin_year = gtk_spin_button_new_with_range(1900, 2100, 1);
    gtk_spin_button_set_value(GTK_SPIN_BUTTON(app->spin_year), app->current_year);
    gtk_box_pack_start(GTK_BOX(hbox), app->spin_year, FALSE, FALSE, 0);

    app->combo_month = gtk_combo_box_text_new();
    const gchar *months[] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};
    for (int i = 0; i < 12; i++) {
        gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(app->combo_month), months[i]);
    }
    gtk_combo_box_set_active(GTK_COMBO_BOX(app->combo_month), app->current_month);
    gtk_box_pack_start(GTK_BOX(hbox), app->combo_month, FALSE, FALSE, 0);

    GtkWidget *current_day_button = gtk_button_new_with_label("Today");
    g_signal_connect(current_day_button, "clicked", G_CALLBACK(on_today_button_clicked), app);
    gtk_box_pack_start(GTK_BOX(hbox), current_day_button, FALSE, FALSE, 0);

    app->drawing_area = gtk_drawing_area_new();
    gtk_box_pack_start(GTK_BOX(vbox), app->drawing_area, TRUE, TRUE, 0);
    gtk_widget_set_size_request(app->drawing_area, 500, 350);
    g_signal_connect(app->drawing_area, "draw", G_CALLBACK(draw_calendar), app);
    g_signal_connect(app->drawing_area, "button-press-event", G_CALLBACK(on_day_selected), app);
    gtk_widget_add_events(app->drawing_area, GDK_BUTTON_PRESS_MASK);

    GtkWidget *text_label = gtk_label_new("Date Entry:");
    gtk_box_pack_start(GTK_BOX(vbox), text_label, FALSE, FALSE, 0);

    app->text_view = gtk_text_view_new();
    gtk_box_pack_start(GTK_BOX(vbox), app->text_view, TRUE, TRUE, 0);

    GtkWidget *hboxb = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
    gtk_container_add(GTK_CONTAINER(vbox), hboxb);

    app->remove_button = gtk_button_new_with_label("Remove Entry");
    g_signal_connect(app->remove_button, "clicked", G_CALLBACK(on_remove_entry_clicked), app);
    gtk_box_pack_start(GTK_BOX(hboxb), app->remove_button, FALSE, FALSE, 0);

    app->save_button = gtk_button_new_with_label("Save Entry");
    gtk_box_pack_start(GTK_BOX(hboxb), app->save_button, FALSE, FALSE, 0);
    g_signal_connect(app->save_button, "clicked", G_CALLBACK(on_save_clicked), app);

    g_signal_connect(app->spin_year, "value-changed", G_CALLBACK(on_year_changed), app);
    g_signal_connect(app->combo_month, "changed", G_CALLBACK(on_month_changed), app);

    gtk_widget_show_all(app->window);
    g_signal_connect(app->window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    mark_dates_with_entries(app);

    gtk_main();

    g_hash_table_destroy(app->marked_dates);
    g_free(app->diary_dir);
    g_slice_free(AppWidgets, app);

    return 0;
}

Added a button to select today's date.
Added a label "Date entry" to inform about the text entry.
Added a button to remove an entry.
This application needs an adequate name but I still haven't decided.

Attachments
calendar.tar.xz
Extract the binary!
(6.83 KiB) Downloaded 30 times
vektor_alian
Posts: 77
Joined: Thu Apr 01, 2021 12:36 am
Been thanked: 24 times

Re: Gtkdialog Calendar

Post by vektor_alian »

Hi rockedge

Thanks for the cool calendar app.

As usual I like to break things, so I saved a long one-liner (200) and restarted the app. The window was as wide as the line with no word wrap.
I then saved a multi-line (100) entry and upon restart the window bottom went down past the monitor with no way to access the buttons. (no scroll)

Conclusion: use for notes and not for essays.

That was fun

Vektor

User avatar
stemsee
Posts: 835
Joined: Sun Jul 26, 2020 8:11 am
Location: lattitude 8
Has thanked: 195 times
Been thanked: 144 times
Contact:

Re: Gtkdialog Calendar

Post by stemsee »

I see this thread is about creating a calendar front end.

I had coded a calendar function which uniquely could go bc and ad without limit. To illustrate the problems with date calculations I post this screenshot showing fatdog tray calendar and cal command.

cal.png
cal.png (89.08 KiB) Viewed 200 times

Look closely at September 1752. It was found that Easter start alignment was out of sync. Not that the universe was early or late, but rather scientist and mathematicians had got the calculations wrong. So in the image we see cal shows what they did in September 1752! The governments of Europe had to compensate businesses for one weeks loss of earnings. And the government was one week short of taxes at the end of the year. There were serious repercussions, but without an accurate calendar society, harvest, taxes, school terms , everything would fail. But they don't teach this in school. Those cultures which stuck with the lunar calendar had no such problem afaik.

The lxqt calendar widget was calculated backwards from linux time January 1970. Which means year zero in cal and year zero in the widget will not align. In fact no calendar aligns with year zero ... because year zero doesn't exist!!! There is year 1 and year -1.

When calculating a calendar, the maths must account for the leap year, in which February has an extra day. Also every four hundred years there is also an extra day. The calendar demonstrates precession in which Monday of January one year falls on Thursday the 27th, but the next year it falls on Thursday 26th. lxqt calendar widget can go negative -0999, while cal goes to year 1. Which gives less than 3000 years of human historic time. So in the end my calendar may be one of the more accurate calendars ever programmed. :roll:

So I was wondering if my calendar function could be optimized and used to populate your calendar front-ends, rather than the inaccurate and limited binaries now being used?

Post Reply

Return to “Programming”