Graduately/src/priroda.c
pepaskopec-blip 59d40c886a Add short transitions for navigation, answers and progress on every platform.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-08 21:13:32 +02:00

720 lines
23 KiB
C

#include "graduately.h"
/* Základy přírodopisných věd: chemistry and biology share one subject.
* Each area is a vertical list of lessons, then slides and a quiz. */
typedef struct {
const char *map_page;
const char *back_page;
const char *title_key;
const char *sub_key;
char **progress_path;
NetLesson *lessons;
const char *unit_keys[SCI_N];
const char *sub_keys[SCI_N];
GtkWidget *nodes[SCI_N];
int note_last_w;
} SciTrack;
static SciTrack sci_tracks[2];
static GtkWidget *sci_rail;
static gboolean sci_ready;
static void sci_ensure(void) {
static const char *chem_units[] = {
"chem_unit1", "chem_unit2", "chem_unit3", "chem_unit4",
"chem_unit5", "chem_unit6", "chem_unit7", "chem_unit8",
};
static const char *chem_subs[] = {
"chem_unit1_sub", "chem_unit2_sub", "chem_unit3_sub", "chem_unit4_sub",
"chem_unit5_sub", "chem_unit6_sub", "chem_unit7_sub", "chem_unit8_sub",
};
static const char *bio_units[] = {
"bio_unit1", "bio_unit2", "bio_unit3", "bio_unit4",
"bio_unit5", "bio_unit6", "bio_unit7", "bio_unit8",
};
static const char *bio_subs[] = {
"bio_unit1_sub", "bio_unit2_sub", "bio_unit3_sub", "bio_unit4_sub",
"bio_unit5_sub", "bio_unit6_sub", "bio_unit7_sub", "bio_unit8_sub",
};
if (sci_ready)
return;
sci_tracks[0].map_page = "chemmap";
sci_tracks[0].back_page = "scimap";
sci_tracks[0].title_key = "Chemie";
sci_tracks[0].sub_key = "chem_sub";
sci_tracks[0].progress_path = &app_progress_chem;
sci_tracks[0].lessons = chem_lessons;
sci_tracks[0].note_last_w = -1;
sci_tracks[1].map_page = "biomap";
sci_tracks[1].back_page = "scimap";
sci_tracks[1].title_key = "Biologie";
sci_tracks[1].sub_key = "bio_sub";
sci_tracks[1].progress_path = &app_progress_bio;
sci_tracks[1].lessons = bio_lessons;
sci_tracks[1].note_last_w = -1;
for (int i = 0; i < SCI_N; i++) {
sci_tracks[0].unit_keys[i] = chem_units[i];
sci_tracks[0].sub_keys[i] = chem_subs[i];
sci_tracks[1].unit_keys[i] = bio_units[i];
sci_tracks[1].sub_keys[i] = bio_subs[i];
}
sci_ready = TRUE;
}
static void sci_save(SciTrack *T) {
GKeyFile *kf;
char *path;
gchar *data;
if (!T->progress_path || !*T->progress_path)
return;
path = *T->progress_path;
kf = g_key_file_new();
for (int i = 0; i < SCI_N; i++) {
gchar key[8];
g_snprintf(key, sizeof(key), "%d", i + 1);
g_key_file_set_boolean(kf, "done", key, T->lessons[i].done);
}
if (g_mkdir_with_parents(PROGRESS_DIR, 0755) != 0) {
g_key_file_free(kf);
return;
}
data = g_key_file_to_data(kf, NULL, NULL);
g_key_file_free(kf);
if (data) {
GError *err = NULL;
g_file_set_contents(path, data, -1, &err);
if (err)
g_error_free(err);
g_free(data);
}
}
static void sci_load_one(SciTrack *T) {
GKeyFile *kf;
GError *err = NULL;
const char *path;
if (!T->progress_path || !*T->progress_path)
return;
path = *T->progress_path;
kf = g_key_file_new();
if (!g_key_file_load_from_file(kf, path, G_KEY_FILE_NONE, &err)) {
if (err)
g_error_free(err);
g_key_file_free(kf);
return;
}
for (int i = 0; i < SCI_N; i++) {
gchar key[8];
g_snprintf(key, sizeof(key), "%d", i + 1);
T->lessons[i].done = g_key_file_get_boolean(kf, "done", key, NULL);
}
g_key_file_free(kf);
}
void sci_load_progress(void) {
sci_ensure();
sci_load_one(&sci_tracks[0]);
sci_load_one(&sci_tracks[1]);
}
static void sci_refresh(SciTrack *T) {
for (int i = 0; i < SCI_N; i++) {
GtkWidget *node = T->nodes[i];
GtkWidget *icon = T->lessons[i].done_icon;
if (!node)
continue;
if (T->lessons[i].done) {
gtk_widget_remove_css_class(node, "current");
gtk_widget_add_css_class(node, "done");
if (icon)
gtk_widget_set_visible(icon, TRUE);
} else {
gtk_widget_remove_css_class(node, "done");
gtk_widget_add_css_class(node, "current");
if (icon)
gtk_widget_set_visible(icon, FALSE);
}
}
}
static void sci_mark_done(SciTrack *T, int lesson_id) {
if (lesson_id < 0 || lesson_id >= SCI_N)
return;
if (T->lessons[lesson_id].done)
return;
T->lessons[lesson_id].done = TRUE;
sci_save(T);
sci_refresh(T);
refresh_stats_ui();
}
void progress_for_sci(ProgressSum *out) {
if (!out)
return;
sci_ensure();
for (int a = 0; a < 2; a++) {
SciTrack *T = &sci_tracks[a];
out->total_ex += SCI_N;
out->open_units += SCI_N;
for (int i = 0; i < SCI_N; i++) {
if (T->lessons[i].done) {
out->done_ex++;
out->done_units++;
}
}
}
}
static void sci_open_unit(GtkButton *button, gpointer data) {
NetLesson *L = data;
(void)button;
if (!L)
return;
L->idx = 0;
if (L->stack) {
char name[16];
g_snprintf(name, sizeof(name), "s%u", L->idx);
gtk_stack_set_visible_child_name(GTK_STACK(L->stack), name);
if (L->prev_btn)
gtk_widget_set_sensitive(L->prev_btn, FALSE);
if (L->next_btn)
gtk_button_set_label(GTK_BUTTON(L->next_btn), tr("net_slide_next"));
}
show_page(L->unit_page, 1);
}
static GtkWidget *sci_lesson_button(SciTrack *T, int index) {
GtkWidget *btn;
GtkWidget *row;
GtkWidget *num;
GtkWidget *texts;
GtkWidget *title;
GtkWidget *sub;
GtkWidget *icon;
char *num_text;
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_hexpand(btn, TRUE);
gtk_widget_set_size_request(btn, -1, 84);
row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 14);
gtk_widget_set_halign(row, GTK_ALIGN_START);
gtk_widget_set_valign(row, GTK_ALIGN_CENTER);
gtk_widget_set_margin_start(row, 18);
gtk_widget_set_margin_end(row, 18);
gtk_button_set_child(GTK_BUTTON(btn), row);
num_text = g_strdup_printf("%d", index + 1);
num = gtk_label_new(num_text);
g_free(num_text);
gtk_widget_add_css_class(num, "unit-number");
gtk_widget_set_valign(num, GTK_ALIGN_CENTER);
gtk_box_append(GTK_BOX(row), num);
texts = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
gtk_widget_set_halign(texts, GTK_ALIGN_START);
gtk_widget_set_hexpand(texts, TRUE);
gtk_box_append(GTK_BOX(row), texts);
title = gtk_label_new(NULL);
i18n_bind(title, T->unit_keys[index], 0);
gtk_widget_set_halign(title, GTK_ALIGN_START);
gtk_widget_add_css_class(title, "unit-name");
gtk_box_append(GTK_BOX(texts), title);
sub = gtk_label_new(NULL);
i18n_bind(sub, T->sub_keys[index], 0);
gtk_widget_set_halign(sub, GTK_ALIGN_START);
gtk_label_set_wrap(GTK_LABEL(sub), TRUE);
gtk_label_set_xalign(GTK_LABEL(sub), 0.0);
gtk_widget_add_css_class(sub, "hint");
gtk_box_append(GTK_BOX(texts), sub);
icon = icon_area_new(draw_check_icon, 0.1176, 0.1176, 0.1804, 18);
gtk_widget_set_visible(icon, FALSE);
gtk_widget_set_valign(icon, GTK_ALIGN_CENTER);
gtk_box_append(GTK_BOX(row), icon);
T->lessons[index].done_icon = icon;
T->nodes[index] = btn;
g_signal_connect(btn, "clicked", G_CALLBACK(sci_open_unit),
&T->lessons[index]);
return btn;
}
static GtkWidget *sci_build_list(SciTrack *T) {
GtkWidget *page;
GtkWidget *scroll;
GtkWidget *list;
sci_ensure();
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(T->back_page, T->title_key, T->sub_key));
scroll = gtk_scrolled_window_new();
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_widget_set_vexpand(scroll, TRUE);
gtk_widget_set_margin_top(scroll, 18);
gtk_box_append(GTK_BOX(page), scroll);
list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
gtk_widget_set_halign(list, GTK_ALIGN_CENTER);
gtk_widget_set_valign(list, GTK_ALIGN_START);
gtk_widget_set_size_request(list, 520, -1);
gtk_widget_set_margin_bottom(list, 12);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), list);
for (int i = 0; i < SCI_N; i++)
gtk_box_append(GTK_BOX(list), sci_lesson_button(T, i));
sci_refresh(T);
return page;
}
GtkWidget *build_chemmap_page(void) { return sci_build_list(&sci_tracks[0]); }
GtkWidget *build_biomap_page(void) { return sci_build_list(&sci_tracks[1]); }
#define SCI_BUBBLE SUB_BUBBLE
#define SCI_LABEL_W 180.0
#define SCI_CW 560
#define SCI_CH 280
static double sci_cx[2];
static double sci_cy[2];
static void draw_sci_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_move_to(cr, sci_cx[0], sci_cy[0]);
cairo_line_to(cr, sci_cx[1], sci_cy[1]);
cairo_set_line_width(cr, 14);
cairo_set_source_rgb(cr, rail.r, rail.g, rail.b);
cairo_stroke(cr);
cairo_move_to(cr, sci_cx[0], sci_cy[0]);
cairo_line_to(cr, sci_cx[1], sci_cy[1]);
cairo_set_line_width(cr, 9);
cairo_set_source_rgb(cr, slate.r, slate.g, slate.b);
cairo_stroke(cr);
}
void sci_rail_theme_reset(void) {
if (sci_rail)
gtk_widget_queue_draw(sci_rail);
}
GtkWidget *build_scimap_page(void) {
GtkWidget *page;
GtkWidget *scroll;
GtkWidget *wrap;
GtkWidget *fixed;
GtkWidget *rail;
static const char *keys[] = {"Chemie", "Biologie"};
static const char *initials[] = {"C", "B"};
static const char *targets[] = {"chemmap", "biomap"};
sci_cx[0] = 160.0;
sci_cy[0] = 110.0;
sci_cx[1] = 400.0;
sci_cy[1] = 110.0;
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", "Základy Přírodopisných věd", "sci_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, SCI_CW, SCI_CH);
gtk_center_box_set_center_widget(GTK_CENTER_BOX(wrap), fixed);
rail = gtk_drawing_area_new();
gtk_widget_set_size_request(rail, SCI_CW, SCI_CH);
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(rail), draw_sci_rail,
NULL, NULL);
gtk_fixed_put(GTK_FIXED(fixed), rail, 0, 0);
sci_rail = rail;
for (int i = 0; i < 2; i++) {
GtkWidget *btn = gtk_button_new();
GtkWidget *letter = gtk_label_new(initials[i]);
GtkWidget *name = gtk_label_new(NULL);
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)SCI_BUBBLE, (int)SCI_BUBBLE);
gtk_widget_add_css_class(letter, "unit-number");
gtk_button_set_child(GTK_BUTTON(btn), letter);
g_object_set_data_full(G_OBJECT(btn), "target",
g_strdup(targets[i]), g_free);
g_signal_connect(btn, "clicked", G_CALLBACK(on_nav_clicked), NULL);
i18n_bind(name, keys[i], 0);
gtk_widget_set_size_request(name, (int)SCI_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_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)(sci_cx[i] - SCI_BUBBLE / 2.0),
(int)(sci_cy[i] - SCI_BUBBLE / 2.0));
gtk_fixed_move(GTK_FIXED(fixed), name,
(int)(sci_cx[i] - SCI_LABEL_W / 2.0),
(int)(sci_cy[i] + SCI_BUBBLE / 2.0 + 10.0));
}
return page;
}
static void sci_slide_apply(NetLesson *L) {
char name[16];
if (!L || !L->stack)
return;
g_snprintf(name, sizeof(name), "s%u", L->idx);
gtk_stack_set_visible_child_name(GTK_STACK(L->stack), name);
if (L->prev_btn) {
gtk_button_set_label(GTK_BUTTON(L->prev_btn), tr("net_slide_prev"));
gtk_widget_set_sensitive(L->prev_btn, L->idx > 0);
}
if (L->next_btn) {
gtk_button_set_label(
GTK_BUTTON(L->next_btn),
L->idx + 1 >= L->n_slides ? tr("net_slide_start")
: tr("net_slide_next"));
}
}
static void sci_slide_prev(GtkButton *button, gpointer data) {
NetLesson *L = data;
(void)button;
if (!L || L->idx == 0)
return;
L->idx--;
sci_slide_apply(L);
}
static void sci_slide_next(GtkButton *button, gpointer data) {
NetLesson *L = data;
(void)button;
if (!L)
return;
if (L->idx + 1 < L->n_slides) {
L->idx++;
sci_slide_apply(L);
} else {
show_page(L->ex_page, 1);
}
}
static void sci_rescale(SciTrack *T, int w) {
int body, head, kick;
if (w < 160)
w = 160;
body = w / 46;
if (body < 14) body = 14;
if (body > 26) body = 26;
head = body + 14;
if (head > 46) head = 46;
kick = body - 3;
if (kick < 11) kick = 11;
for (int i = 0; i < SCI_N; i++)
net_rescale_lesson_notes(&T->lessons[i], body, head, kick);
}
static gboolean sci_note_tick(GtkWidget *w, GdkFrameClock *clock,
gpointer data) {
SciTrack *T = data;
int width = gtk_widget_get_allocated_width(w);
(void)clock;
if (width != T->note_last_w) {
T->note_last_w = width;
sci_rescale(T, width);
}
return G_SOURCE_CONTINUE;
}
GtkWidget *sci_unit_page(int area, NetLesson *L, const char *title_key,
const char *sub_key, const NetSlide *slides,
guint n_slides) {
SciTrack *T;
GtkWidget *page;
GtkWidget *scroll;
GtkWidget *nav;
sci_ensure();
if (area < 0 || area > 1)
area = 0;
T = &sci_tracks[area];
net_lesson_notes_ensure(L);
net_notes_target = L;
L->n_slides = n_slides;
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_margin_start(page, 28);
gtk_widget_set_margin_end(page, 28);
gtk_widget_set_margin_top(page, 20);
gtk_widget_set_margin_bottom(page, 20);
gtk_box_append(GTK_BOX(page), top_bar(T->map_page, title_key, sub_key));
scroll = gtk_scrolled_window_new();
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_widget_set_vexpand(scroll, TRUE);
gtk_widget_set_margin_top(scroll, 10);
gtk_box_append(GTK_BOX(page), scroll);
L->stack = gtk_stack_new();
gtk_stack_set_transition_type(GTK_STACK(L->stack),
GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), L->stack);
for (guint s = 0; s < n_slides; s++) {
GtkWidget *stage = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
GtkWidget *card;
char name[16];
gtk_widget_set_vexpand(stage, TRUE);
g_snprintf(name, sizeof(name), "s%u", s);
gtk_stack_add_named(GTK_STACK(L->stack), stage, name);
card = gtk_box_new(GTK_ORIENTATION_VERTICAL, 16);
gtk_widget_add_css_class(card, "notes-card");
gtk_widget_set_valign(card, GTK_ALIGN_CENTER);
gtk_box_append(GTK_BOX(stage), card);
net_note_kicker(card, slides[s].kicker);
net_note_title(card, slides[s].title);
for (int i = 0; i < 8 && slides[s].line[i]; i++)
net_note_line(card, slides[s].line[i], FALSE);
if (slides[s].tip)
net_note_line(card, slides[s].tip, TRUE);
}
gtk_widget_add_tick_callback(L->stack, sci_note_tick, T, NULL);
T->note_last_w = -1;
sci_rescale(T, 0);
net_notes_target = NULL;
nav = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
gtk_box_set_homogeneous(GTK_BOX(nav), TRUE);
gtk_widget_set_margin_top(nav, 10);
gtk_box_append(GTK_BOX(page), nav);
L->prev_btn = gtk_button_new_with_label(tr("net_slide_prev"));
gtk_widget_add_css_class(L->prev_btn, "btn-primary");
g_signal_connect(L->prev_btn, "clicked", G_CALLBACK(sci_slide_prev), L);
gtk_box_append(GTK_BOX(nav), L->prev_btn);
L->next_btn = gtk_button_new_with_label(tr("net_slide_next"));
gtk_widget_add_css_class(L->next_btn, "btn-primary");
g_signal_connect(L->next_btn, "clicked", G_CALLBACK(sci_slide_next), L);
gtk_box_append(GTK_BOX(nav), L->next_btn);
L->idx = 0;
sci_slide_apply(L);
return page;
}
void sci_lessons_apply_lang(void) {
sci_ensure();
for (int a = 0; a < 2; a++) {
for (int i = 0; i < SCI_N; i++)
sci_slide_apply(&sci_tracks[a].lessons[i]);
}
}
typedef struct {
const ChoiceQ *qs;
int n;
int n_opts;
SciTrack *track;
int lesson_id;
GtkWidget *feedback;
GtkToggleButton **toggles;
GtkWidget **hints;
} SciMcqCtx;
static void sci_mcq_check(GtkButton *button, gpointer data) {
SciMcqCtx *ctx = data;
int ok = 0;
(void)button;
for (int i = 0; i < ctx->n; i++) {
int active = -1;
for (int o = 0; o < ctx->n_opts; o++) {
GtkToggleButton *tb = ctx->toggles[i * ctx->n_opts + o];
gtk_widget_remove_css_class(GTK_WIDGET(tb), "ok");
gtk_widget_remove_css_class(GTK_WIDGET(tb), "wrong");
if (gtk_toggle_button_get_active(tb))
active = o;
}
if (active == ctx->qs[i].correct) {
ok++;
gtk_widget_add_css_class(
GTK_WIDGET(ctx->toggles[i * ctx->n_opts + active]), "ok");
} else if (active >= 0) {
gtk_widget_add_css_class(
GTK_WIDGET(ctx->toggles[i * ctx->n_opts + active]), "wrong");
}
if (ctx->hints && ctx->hints[i])
gtk_widget_set_visible(ctx->hints[i], TRUE);
}
if (ok == ctx->n) {
set_feedback(ctx->feedback, TRUE, tr("feedback_ok"));
sci_mark_done(ctx->track, ctx->lesson_id);
} else {
set_feedback(ctx->feedback, FALSE, tr("feedback_retry_short"));
}
}
GtkWidget *sci_mcq_page(int area, int lesson_id, const char *back_page,
const char *title_key, const char *heading_key,
const ChoiceQ *qs, const char **hints, int n) {
GtkWidget *page;
GtkWidget *scroll;
GtkWidget *body;
GtkWidget *check;
GtkWidget *btns;
GtkWidget *head;
GtkWidget *intro;
SciMcqCtx *ctx = g_new0(SciMcqCtx, 1);
int n_opts = qs[0].n_options;
sci_ensure();
if (area < 0 || area > 1)
area = 0;
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
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(back_page, title_key, NULL));
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, 12);
gtk_box_append(GTK_BOX(page), scroll);
body = gtk_box_new(GTK_ORIENTATION_VERTICAL, 14);
gtk_widget_set_halign(body, GTK_ALIGN_FILL);
gtk_widget_set_hexpand(body, TRUE);
gtk_widget_set_margin_start(body, 4);
gtk_widget_set_margin_end(body, 4);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), body);
head = gtk_label_new(NULL);
i18n_bind(head, heading_key, 0);
gtk_widget_set_halign(head, GTK_ALIGN_START);
gtk_label_set_wrap(GTK_LABEL(head), TRUE);
gtk_widget_set_margin_top(head, 4);
gtk_widget_add_css_class(head, "quiz-heading");
gtk_box_append(GTK_BOX(body), head);
intro = gtk_label_new(NULL);
i18n_bind(intro, "net_quiz_intro", 0);
gtk_widget_set_halign(intro, GTK_ALIGN_START);
gtk_label_set_wrap(GTK_LABEL(intro), TRUE);
gtk_widget_add_css_class(intro, "quiz-intro");
gtk_widget_set_margin_bottom(intro, 4);
gtk_box_append(GTK_BOX(body), intro);
ctx->qs = qs;
ctx->n = n;
ctx->n_opts = n_opts;
ctx->track = &sci_tracks[area];
ctx->lesson_id = lesson_id;
ctx->toggles = g_new0(GtkToggleButton *, n * n_opts);
ctx->hints = g_new0(GtkWidget *, n);
for (int i = 0; i < n; i++) {
GtkWidget *card = mcq_append_question(body, i + 1, &qs[i],
&ctx->toggles[i * n_opts]);
ctx->hints[i] = meaning_add(card, hints[i]);
}
btns = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
gtk_widget_set_margin_top(btns, 8);
gtk_widget_set_margin_bottom(btns, 8);
gtk_box_append(GTK_BOX(body), btns);
check = gtk_button_new();
i18n_bind(check, "check", 1);
gtk_widget_add_css_class(check, "btn-primary");
gtk_widget_set_halign(check, GTK_ALIGN_START);
gtk_box_append(GTK_BOX(btns), check);
g_signal_connect(check, "clicked", G_CALLBACK(sci_mcq_check), ctx);
ctx->feedback = gtk_label_new("");
gtk_widget_set_halign(ctx->feedback, GTK_ALIGN_START);
gtk_box_append(GTK_BOX(body), ctx->feedback);
return page;
}