From cf84bffb36824e6d18bbc05833a92418796ad265 Mon Sep 17 00:00:00 2001 From: Martin Dvorak Date: Mon, 14 Sep 2026 12:44:21 +0200 Subject: [PATCH] prepared the czech subject --- czech.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++++ i18n.c | 5 ++ main.c | 1 + maturita.h | 5 ++ stats.c | 4 +- style.css | 5 ++ subjects.c | 59 +++++++++++++++++++-- theme.c | 1 + 8 files changed, 227 insertions(+), 5 deletions(-) create mode 100644 czech.c diff --git a/czech.c b/czech.c new file mode 100644 index 0000000..578c3ce --- /dev/null +++ b/czech.c @@ -0,0 +1,152 @@ +#include "maturita.h" + +/* ---- Czech language and literature (Český jazyk a literatura) ----- */ + +#define CZ_NODES 3 +#define CZ_BUBBLE SUB_BUBBLE +#define CZ_LABEL_W 200.0 +#define CZ_CW 600 +#define CZ_CH 470 + +static const char *cz_keys[CZ_NODES] = { + "Literatura", "Mluvnice", "Maturitní četba", +}; +static const char *cz_initials[CZ_NODES] = { + "L", "M", "Č", +}; + +static GtkWidget *cz_rail; +static double cz_cx[CZ_NODES]; +static double cz_cy[CZ_NODES]; + +static void cz_geometry(void) { + cz_cx[0] = 300.0; cz_cy[0] = 120.0; /* Literatura (top) */ + cz_cx[1] = 140.0; cz_cy[1] = 350.0; /* Mluvnice (bottom-left) */ + cz_cx[2] = 460.0; cz_cy[2] = 350.0; /* Maturitní četba (bottom-right) */ +} + +/* Three bubbles joined into a triangle by thick rails. */ +static void draw_cz_rail(GtkDrawingArea *area, cairo_t *cr, + int width, int height, gpointer data) { + const Rgb rail = color_from_hex(app_theme.rail); + const Rgb slate = color_from_hex(app_theme.surface1); + + (void)area; + (void)width; + (void)height; + (void)data; + + cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); + cairo_paint(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); + + cairo_new_path(cr); + cairo_move_to(cr, cz_cx[0], cz_cy[0]); + cairo_line_to(cr, cz_cx[1], cz_cy[1]); + cairo_line_to(cr, cz_cx[2], cz_cy[2]); + cairo_close_path(cr); + + cairo_set_line_width(cr, 14); + cairo_set_source_rgb(cr, rail.r, rail.g, rail.b); + cairo_stroke_preserve(cr); + + cairo_set_line_width(cr, 9); + cairo_set_source_rgb(cr, slate.r, slate.g, slate.b); + cairo_stroke(cr); +} + +void czech_rail_theme_reset(void) { + if (cz_rail) + gtk_widget_queue_draw(cz_rail); +} + +static GtkWidget *cz_make_node(int index) { + GtkWidget *btn; + GtkWidget *label; + + btn = gtk_button_new(); + gtk_widget_add_css_class(btn, "unit-node"); + gtk_widget_add_css_class(btn, "current"); + gtk_widget_set_can_focus(btn, FALSE); + gtk_widget_set_size_request(btn, (int)CZ_BUBBLE, (int)CZ_BUBBLE); + + label = gtk_label_new(cz_initials[index]); + gtk_widget_add_css_class(label, "unit-number"); + gtk_button_set_child(GTK_BUTTON(btn), label); + + return btn; +} + +GtkWidget *build_czechmap_page(void) { + GtkWidget *page; + GtkWidget *scroll; + GtkWidget *wrap; + GtkWidget *fixed; + GtkWidget *rail; + + cz_geometry(); + + page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_set_hexpand(page, TRUE); + gtk_widget_set_vexpand(page, TRUE); + gtk_widget_set_margin_start(page, 32); + gtk_widget_set_margin_end(page, 32); + gtk_widget_set_margin_top(page, 24); + gtk_widget_set_margin_bottom(page, 24); + + gtk_box_append(GTK_BOX(page), + top_bar("subjects", "Český jazyk a literatura", + "czech_sub")); + + scroll = gtk_scrolled_window_new(); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_widget_set_vexpand(scroll, TRUE); + gtk_widget_set_margin_top(scroll, 14); + gtk_box_append(GTK_BOX(page), scroll); + + wrap = gtk_center_box_new(); + gtk_widget_set_vexpand(wrap, TRUE); + gtk_widget_set_hexpand(wrap, TRUE); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), wrap); + + fixed = gtk_fixed_new(); + gtk_widget_set_halign(fixed, GTK_ALIGN_CENTER); + gtk_widget_set_valign(fixed, GTK_ALIGN_CENTER); + gtk_widget_set_size_request(fixed, CZ_CW, CZ_CH); + gtk_center_box_set_center_widget(GTK_CENTER_BOX(wrap), fixed); + + rail = gtk_drawing_area_new(); + gtk_widget_set_size_request(rail, CZ_CW, CZ_CH); + gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(rail), draw_cz_rail, + NULL, NULL); + gtk_fixed_put(GTK_FIXED(fixed), rail, 0, 0); + cz_rail = rail; + + for (int i = 0; i < CZ_NODES; i++) { + GtkWidget *btn = cz_make_node(i); + GtkWidget *name = gtk_label_new(NULL); + + i18n_bind(name, cz_keys[i], 0); + gtk_widget_set_size_request(name, (int)CZ_LABEL_W, -1); + gtk_widget_set_halign(name, GTK_ALIGN_CENTER); + gtk_label_set_justify(GTK_LABEL(name), GTK_JUSTIFY_CENTER); + gtk_label_set_wrap(GTK_LABEL(name), TRUE); + gtk_widget_add_css_class(name, "unit-name"); + gtk_widget_add_css_class(name, "cz-label"); + + gtk_fixed_put(GTK_FIXED(fixed), btn, 0, 0); + gtk_fixed_put(GTK_FIXED(fixed), name, 0, 0); + + gtk_fixed_move(GTK_FIXED(fixed), btn, + (int)(cz_cx[i] - CZ_BUBBLE / 2.0), + (int)(cz_cy[i] - CZ_BUBBLE / 2.0)); + gtk_fixed_move(GTK_FIXED(fixed), name, + (int)(cz_cx[i] - CZ_LABEL_W / 2.0), + (int)(cz_cy[i] + CZ_BUBBLE / 2.0 + 10.0)); + } + + return page; +} diff --git a/i18n.c b/i18n.c index 789dba3..d63c298 100644 --- a/i18n.c +++ b/i18n.c @@ -369,6 +369,11 @@ const TrEntry tr_ui[] = { "Quiz: data representation"}, {"hw_quiz3_head", "Kvíz k zobrazování dat v počítači", "Quiz on data representation in computers"}, + {"czech_sub", "Vyberte oblast a začněte procvičovat.", + "Pick an area and start practicing."}, + {"Literatura", "Literatura", "Literature"}, + {"Mluvnice", "Mluvnice", "Grammar"}, + {"Maturitní četba", "Maturitní četba", "Maturita reading list"}, {NULL, NULL, NULL} }; diff --git a/main.c b/main.c index 52d276f..2494b95 100644 --- a/main.c +++ b/main.c @@ -231,6 +231,7 @@ void activate(GtkApplication *app, gpointer user_data) { gtk_stack_add_named(main_stack, build_stats_page(), "stats"); gtk_stack_add_named(main_stack, build_netmap_page(), "netmap"); gtk_stack_add_named(main_stack, build_hwmap_page(), "hwmap"); + gtk_stack_add_named(main_stack, build_czechmap_page(), "czechmap"); gtk_stack_add_named(main_stack, build_hw_unit1_page(), "hwunit1"); gtk_stack_add_named(main_stack, build_hw_unit1_exercise_page(), "hwex1"); gtk_stack_add_named(main_stack, build_hw_unit2_page(), "hwunit2"); diff --git a/maturita.h b/maturita.h index 779001f..c63c011 100644 --- a/maturita.h +++ b/maturita.h @@ -13,6 +13,7 @@ #define NUM_SUBJECTS 13 #define NET_SUBJ 1 /* index of "Správa počítačových sítí" */ #define HW_SUBJ 2 /* index of "Technické vybavení" */ +#define CZ_SUBJ 3 /* index of "Český jazyk a literatura" */ #define CSS_FILE "style.css" #define PROGRESS_DIR "progress" #define PROGRESS_U1 "progress/unit1.conf" @@ -786,8 +787,10 @@ void mark_hw_done(int lesson_id); void refresh_hw_completion_ui(void); void progress_for_hw(ProgressSum *out); void hw_rail_theme_reset(void); +void czech_rail_theme_reset(void); void hw_lessons_apply_lang(void); GtkWidget *build_hwmap_page(void); +GtkWidget *build_czechmap_page(void); GtkWidget *build_hw_unit1_page(void); GtkWidget *build_hw_unit1_exercise_page(void); GtkWidget *build_hw_unit2_page(void); @@ -796,6 +799,8 @@ GtkWidget *build_hw_unit3_page(void); GtkWidget *build_hw_unit3_exercise_page(void); void draw_chip_icon(GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer data); +void draw_czech_flag(GtkDrawingArea *area, cairo_t *cr, + int width, int height, gpointer data); void net_lesson_notes_ensure(NetLesson *L); void net_rescale_lesson_notes(NetLesson *L, int body, int head, int kick); GtkWidget *make_back_button(const char *target); diff --git a/stats.c b/stats.c index 08fe40c..b7d27e4 100644 --- a/stats.c +++ b/stats.c @@ -86,7 +86,7 @@ void refresh_stats_ui(void) { GtkWidget *bar = stats_ui.subj[s].bar; ProgressSum sp = {0}; gboolean has_content = (s == NET_SUBJ) || (s == HW_SUBJ) - || sub_unit_count[s] > 0; + || (s == CZ_SUBJ) || sub_unit_count[s] > 0; if (!count || !bar) continue; @@ -359,7 +359,7 @@ GtkWidget *build_stats_page(void) { GtkWidget *count; GtkWidget *bar; gboolean has_units = (s == NET_SUBJ) || (s == HW_SUBJ) - || sub_unit_count[s] > 0; + || (s == CZ_SUBJ) || sub_unit_count[s] > 0; gboolean has_deutsch_units = sub_unit_count[s] > 0; subject = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); diff --git a/style.css b/style.css index 6cee0cf..6c3d001 100644 --- a/style.css +++ b/style.css @@ -366,6 +366,11 @@ windowcontrols button:not(:hover) image { font-size: 12px; font-weight: 600; } +.cz-label { + background-color: @bg_base; + padding: 1px 6px; + border-radius: 8px; +} .ex-prompt { color: @fg_text; font-size: 16px; diff --git a/subjects.c b/subjects.c index 68ba55d..53614be 100644 --- a/subjects.c +++ b/subjects.c @@ -5,11 +5,13 @@ /* ------------------------------------------------------------------ */ -/* Index 0 (Deutsch), NET_SUBJ (networks) and HW_SUBJ (hardware) are unlocked. */ +/* Index 0 (Deutsch), NET_SUBJ (networks), HW_SUBJ (hardware) and CZ_SUBJ + * (Czech) are unlocked. */ const char *sub_keys[NUM_SUBJECTS] = { "Deutsch", "Správa počítačových sítí", "Technické vybavení", + "Český jazyk a literatura", "Občanská nauka", "English", "Matematika", @@ -17,7 +19,6 @@ const char *sub_keys[NUM_SUBJECTS] = { "Základy Přírodopisných věd", "Technická grafika", "Prezentační grafika", - "Český jazyk a literatura", "Programování", "Programové vybavení", }; @@ -245,6 +246,47 @@ void draw_german_flag(GtkDrawingArea *area, cairo_t *cr, cairo_restore(cr); } +/* Czech flag (white / red with a blue triangle) drawn in a clipped circle. */ +void draw_czech_flag(GtkDrawingArea *area, cairo_t *cr, + int width, int height, gpointer data) { + double cx = width / 2.0; + double cy = height / 2.0; + double r = MIN(width, height) / 2.0 - 2.0; + + (void)area; + (void)data; + + cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); + cairo_paint(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + + cairo_save(cr); + cairo_new_path(cr); + cairo_arc(cr, cx, cy, r, 0.0, 2.0 * G_PI); + cairo_clip(cr); + + cairo_new_path(cr); + cairo_rectangle(cr, 0.0, 0.0, (double)width, (double)height); + cairo_set_source_rgb(cr, 0.98, 0.98, 0.98); + cairo_fill(cr); + + cairo_new_path(cr); + cairo_rectangle(cr, 0.0, (double)height / 2.0, + (double)width, (double)height / 2.0); + cairo_set_source_rgb(cr, 0.83, 0.16, 0.18); + cairo_fill(cr); + + cairo_new_path(cr); + cairo_move_to(cr, 0.0, 0.0); + cairo_line_to(cr, (double)width * 0.55, (double)height / 2.0); + cairo_line_to(cr, 0.0, (double)height); + cairo_close_path(cr); + cairo_set_source_rgb(cr, 0.07, 0.24, 0.55); + cairo_fill(cr); + + cairo_restore(cr); +} + GtkWidget *build_subjects_page(void) { GtkWidget *page; GtkWidget *scroll; @@ -337,6 +379,17 @@ GtkWidget *build_subjects_page(void) { g_object_set_data_full(G_OBJECT(card), "target", g_strdup("hwmap"), g_free); g_signal_connect(card, "clicked", G_CALLBACK(on_nav_clicked), NULL); + } else if (i == CZ_SUBJ) { + GtkWidget *flag = gtk_drawing_area_new(); + gtk_widget_add_css_class(card, "current"); + gtk_widget_set_size_request(flag, (int)SUB_BUBBLE, + (int)SUB_BUBBLE); + gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(flag), + draw_czech_flag, NULL, NULL); + gtk_button_set_child(GTK_BUTTON(card), flag); + g_object_set_data_full(G_OBJECT(card), "target", + g_strdup("czechmap"), g_free); + g_signal_connect(card, "clicked", G_CALLBACK(on_nav_clicked), NULL); } else { GtkWidget *lock; gtk_widget_add_css_class(card, "locked"); @@ -356,7 +409,7 @@ GtkWidget *build_subjects_page(void) { gtk_label_set_justify(GTK_LABEL(lbl), GTK_JUSTIFY_CENTER); gtk_label_set_wrap(GTK_LABEL(lbl), TRUE); gtk_widget_add_css_class(lbl, "unit-name"); - if (i != 0 && i != NET_SUBJ && i != HW_SUBJ) + if (i != 0 && i != NET_SUBJ && i != HW_SUBJ && i != CZ_SUBJ) gtk_widget_add_css_class(lbl, "unit-name-locked"); sub_labels[i] = lbl; gtk_fixed_put(GTK_FIXED(fixed), lbl, 0, 0); diff --git a/theme.c b/theme.c index 770d147..635f05a 100644 --- a/theme.c +++ b/theme.c @@ -414,4 +414,5 @@ void apply_theme(void) { } net_rail_theme_reset(); hw_rail_theme_reset(); + czech_rail_theme_reset(); }