Fix path layout and Czech stats, and rewrite the install docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-09-17 16:02:59 +02:00
commit 2cb3565df5
16 changed files with 349 additions and 869 deletions

1114
README.md

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before After
Before After

26
czech.c
View file

@ -62,19 +62,27 @@ void czech_rail_theme_reset(void) {
gtk_widget_queue_draw(cz_rail);
}
static GtkWidget *cz_make_node(int index) {
static GtkWidget *cz_make_node(int index, gboolean locked) {
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);
if (locked) {
gtk_widget_add_css_class(btn, "locked");
gtk_widget_set_sensitive(btn, FALSE);
gtk_button_set_child(GTK_BUTTON(btn),
icon_area_new(draw_lock_icon,
0.3451, 0.3569, 0.4392, 26));
} else {
GtkWidget *label = gtk_label_new(cz_initials[index]);
gtk_widget_add_css_class(btn, "current");
gtk_widget_add_css_class(label, "unit-number");
gtk_button_set_child(GTK_BUTTON(btn), label);
}
return btn;
}
@ -126,7 +134,9 @@ GtkWidget *build_czechmap_page(void) {
cz_rail = rail;
for (int i = 0; i < CZ_NODES; i++) {
GtkWidget *btn = cz_make_node(i);
/* Literatura has no lessons yet; keep the node visible but locked. */
gboolean locked = (i == 0);
GtkWidget *btn = cz_make_node(i, locked);
GtkWidget *name = gtk_label_new(NULL);
if (i == 2) {
@ -146,6 +156,8 @@ GtkWidget *build_czechmap_page(void) {
gtk_label_set_wrap(GTK_LABEL(name), TRUE);
gtk_widget_add_css_class(name, "unit-name");
gtk_widget_add_css_class(name, "cz-label");
if (locked)
gtk_widget_add_css_class(name, "unit-name-locked");
gtk_fixed_put(GTK_FIXED(fixed), btn, 0, 0);
gtk_fixed_put(GTK_FIXED(fixed), name, 0, 0);

1
hw.c
View file

@ -366,6 +366,7 @@ GtkWidget *build_hwmap_page(void) {
g_signal_connect(va, "notify::page-size",
G_CALLBACK(hw_adjust_notify), NULL);
hw_apply_layout();
hw_relayout_later();
refresh_hw_completion_ui();

View file

@ -41,7 +41,7 @@ mkdir -p "$DEST_DIR"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT INT TERM
say "Downloading the latest release..."
say "Downloading the latest build..."
curl -fL --progress-bar -o "$tmp/$ASSET" "$URL" ||
die "the download failed. Check your internet connection."

View file

@ -40,7 +40,7 @@ fi
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT INT TERM
say "Downloading the latest release..."
say "Downloading the latest build..."
curl -fL --progress-bar -o "$tmp/$ASSET" "$URL" ||
die "the download failed. Check your internet connection."

View file

@ -25,7 +25,7 @@ set "TMP_DIR=%TEMP%\maturita-installer"
rmdir /s /q "%TMP_DIR%" 2>nul
mkdir "%TMP_DIR%" || goto :fail_tmp
echo Downloading the latest release...
echo Downloading the latest build...
curl -fL --progress-bar -o "%TMP_DIR%\%ASSET%" "%URL%" || goto :fail_download
echo Installing to %DEST%

148
main.c
View file

@ -310,6 +310,129 @@ static void setup_window_icon(GtkWindow *window) {
G_CALLBACK(on_window_realize_icon), NULL);
}
static gboolean open_settings_later(gpointer data) {
(void)data;
settings_open();
return G_SOURCE_REMOVE;
}
static gboolean save_window_png(const char *path) {
GtkWidget *widget;
int width, height;
GdkPaintable *paintable;
GtkSnapshot *snapshot;
GskRenderNode *node;
cairo_surface_t *surface;
cairo_t *cr;
cairo_status_t st;
if (!main_window || !path || !path[0])
return FALSE;
widget = GTK_WIDGET(main_window);
width = gtk_widget_get_width(widget);
height = gtk_widget_get_height(widget);
if (width < 80 || height < 80)
return FALSE;
paintable = gtk_widget_paintable_new(widget);
snapshot = gtk_snapshot_new();
gdk_paintable_snapshot(paintable, snapshot, width, height);
node = gtk_snapshot_free_to_node(snapshot);
g_object_unref(paintable);
if (!node)
return FALSE;
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
cr = cairo_create(surface);
gsk_render_node_draw(node, cr);
gsk_render_node_unref(node);
cairo_destroy(cr);
st = cairo_surface_write_to_png(surface, path);
cairo_surface_destroy(surface);
return st == CAIRO_STATUS_SUCCESS;
}
static void smoke_quit(int code) {
GtkApplication *app = main_window
? gtk_window_get_application(main_window) : NULL;
if (app)
g_application_quit(G_APPLICATION(app));
else
exit(code);
}
static gboolean shot_later(gpointer data) {
const char *path = g_getenv("MATURITA_SHOT");
int *tries = data;
if (!save_window_png(path)) {
if (tries && *tries < 20) {
(*tries)++;
return G_SOURCE_CONTINUE;
}
g_printerr("MATURITA_SHOT: could not capture window\n");
smoke_quit(1);
return G_SOURCE_REMOVE;
}
if (g_getenv("MATURITA_SHOT_QUIT"))
smoke_quit(0);
return G_SOURCE_REMOVE;
}
static gboolean run_smoke_test(gpointer data) {
static const char *pages[] = {
"welcome", "subjects", "roadmap", "stats",
"netyears", "netmap", "hwmap", "czechmap",
"mluvnice", "readinglist",
"cetba1984", "cetba1984quiz", "cetba1984dej",
"cetbaFuks", "cetbaFuksQuiz", "cetbaFuksDej",
"hwunit1", "hwex1", "hwunit2", "hwex2", "hwunit3", "hwex3",
"unit1", "unit2", "unit3",
"u1e1", "u1e13", "u2e1", "u2e19", "u3e1", "u3e15",
"u1vocab", "u2vocab", "u3vocab",
"mluve1", "mluve20",
"netunit1", "netex1", "netunit10", "netex10",
"netunit27", "netex27",
NULL
};
const char *report = g_getenv("MATURITA_SMOKE");
FILE *f;
int missing = 0;
int i;
(void)data;
f = report && report[0] ? fopen(report, "w") : stdout;
if (!f) {
g_printerr("MATURITA_SMOKE: cannot write %s\n", report);
smoke_quit(1);
return G_SOURCE_REMOVE;
}
fprintf(f, "commit=%s\n", APP_COMMIT);
fprintf(f, "css=%s\n",
g_file_test(CSS_FILE, G_FILE_TEST_IS_REGULAR) ? "ok" : "MISSING");
fprintf(f, "progress_dir=%s\n", app_progress_dir ? app_progress_dir : "?");
for (i = 0; pages[i]; i++) {
GtkWidget *child = gtk_stack_get_child_by_name(main_stack, pages[i]);
if (child) {
fprintf(f, "OK %s\n", pages[i]);
} else {
fprintf(f, "MISSING %s\n", pages[i]);
missing++;
}
}
fprintf(f, "missing=%d\n", missing);
if (f != stdout)
fclose(f);
smoke_quit(missing ? 1 : 0);
return G_SOURCE_REMOVE;
}
void activate(GtkApplication *app, gpointer user_data) {
GtkWindow *window;
GtkWidget *headerbar;
@ -463,7 +586,18 @@ void activate(GtkApplication *app, gpointer user_data) {
}
}
gtk_stack_set_visible_child_name(main_stack, "welcome");
{
const char *start = g_getenv("MATURITA_PAGE");
const char *size = g_getenv("MATURITA_SIZE");
int w, h;
if (start && *start)
gtk_stack_set_visible_child_name(main_stack, start);
else
gtk_stack_set_visible_child_name(main_stack, "welcome");
if (size && sscanf(size, "%dx%d", &w, &h) == 2)
gtk_window_set_default_size(window, w, h);
}
refresh_completion_ui();
@ -482,6 +616,18 @@ void activate(GtkApplication *app, gpointer user_data) {
update_init();
gtk_window_present(window);
if (g_getenv("MATURITA_SETTINGS"))
g_timeout_add(350, open_settings_later, NULL);
if (g_getenv("MATURITA_SMOKE"))
g_timeout_add(200, run_smoke_test, NULL);
if (g_getenv("MATURITA_SHOT")) {
static int shot_tries;
shot_tries = 0;
g_timeout_add(g_getenv("MATURITA_SETTINGS") ? 700 : 400,
shot_later, &shot_tries);
}
}
int main(int argc, char **argv) {

View file

@ -839,6 +839,7 @@ void mluvnice_init(void);
GtkWidget *build_mluvnice_page(void);
GtkWidget *build_mluvnice_exercise_page(int n);
void mluvnice_refresh_ui(void);
void progress_for_mluvnice(ProgressSum *out);
GtkWidget *build_readinglist_page(void);
GtkWidget *build_cetba1984_page(void);
GtkWidget *build_cetba1984_quiz_page(void);
@ -1124,6 +1125,7 @@ GtkWidget *make_theme_card(ThemeId id, GtkToggleButton *group);
void on_settings_clicked(GtkButton *button, gpointer user_data);
GtkWidget *build_stats_button(void);
GtkWidget *build_settings_button(void);
void settings_open(void);
void unit_meta_init(void);
void unit_configure(int idx, const char *const *names, int n);
GtkWidget *build_exercise_page(UnitCtx *u, int n);

View file

@ -71,6 +71,19 @@ void mluvnice_init(void) {
unit_load_progress(&mluv_unit);
}
void progress_for_mluvnice(ProgressSum *out) {
int d;
if (!out || mluv_unit.n_ex <= 0)
return;
d = unit_done_count(&mluv_unit);
out->total_ex += mluv_unit.n_ex;
out->open_units += 1;
out->done_ex += d;
if (d == mluv_unit.n_ex)
out->done_units++;
}
void mluvnice_refresh_ui(void) {
for (int i = 0; i < mluv_unit.n_ex; i++) {
GtkWidget *btn = mluv_unit.ex_cells[i];

1
net.c
View file

@ -687,6 +687,7 @@ GtkWidget *build_netmap_page(void) {
g_signal_connect(va, "notify::page-size",
G_CALLBACK(net_adjust_notify), NULL);
net_apply_layout();
net_relayout_later();
refresh_net_completion_ui();

View file

@ -401,6 +401,7 @@ GtkWidget *build_roadmap_page(void) {
g_signal_connect(va, "notify::page-size",
G_CALLBACK(road_adjust_notify), NULL);
road_apply_layout();
road_relayout_later();
return page;

View file

@ -4,6 +4,13 @@
/* Settings */
/* ------------------------------------------------------------------ */
static GtkWidget *settings_popover;
void settings_open(void) {
if (settings_popover)
gtk_popover_popup(GTK_POPOVER(settings_popover));
}
void draw_theme_swatch(GtkDrawingArea *area, cairo_t *cr,
int width, int height, gpointer data) {
ThemeId id = (ThemeId)GPOINTER_TO_INT(data);
@ -215,6 +222,7 @@ GtkWidget *build_settings_button(void) {
G_CALLBACK(gtk_widget_queue_draw), icon);
popover = gtk_popover_new();
settings_popover = popover;
gtk_widget_add_css_class(popover, "settings-popover");
gtk_popover_set_has_arrow(GTK_POPOVER(popover), TRUE);
gtk_popover_set_position(GTK_POPOVER(popover), GTK_POS_BOTTOM);

View file

@ -69,6 +69,8 @@ void refresh_stats_ui(void) {
progress_for_net(&all);
else if (s == HW_SUBJ)
progress_for_hw(&all);
else if (s == CZ_SUBJ)
progress_for_mluvnice(&all);
else
progress_for_units(sub_unit_start[s], sub_unit_count[s], &all);
}
@ -106,6 +108,8 @@ void refresh_stats_ui(void) {
progress_for_net(&sp);
else if (s == HW_SUBJ)
progress_for_hw(&sp);
else if (s == CZ_SUBJ)
progress_for_mluvnice(&sp);
else
progress_for_units(sub_unit_start[s], sub_unit_count[s], &sp);
if (has_content) {