#include "maturita.h" /* ------------------------------------------------------------------ */ /* Choice exercises (3 + 9) */ /* ------------------------------------------------------------------ */ const ChoiceQ ex3_questions[] = { {"Welche Zahl ist „vierzehn“?", {"4", "14", "40"}, 3, 1}, {"Welche Zahl ist „siebzehn“?", {"7", "17", "70"}, 3, 1}, {"Welche Zahl ist „zwanzig“?", {"2", "12", "20"}, 3, 2}, {"Welche Zahl ist „sechs“?", {"6", "16", "60"}, 3, 0}, }; const char *ex3_meaning[] = { "„vierzehn“ = čtrnáct (14)", "„siebzehn“ = sedmnáct (17)", "„zwanzig“ = dvacet (20)", "„sechs“ = šest (6)", }; const ChoiceQ ex9_questions[] = { {"___ heißt du?", {"Wer", "Wie", "Wo"}, 3, 1}, {"___ kommst du?", {"Woher", "Wie", "Wer"}, 3, 0}, {"___ wohnst du?", {"Wo", "Wer", "Was"}, 3, 0}, {"___ ist das?", {"Wer", "Wo", "Wie"}, 3, 0}, {"___ alt bist du?", {"Wie", "Woher", "Was"}, 3, 0}, }; const char *ex9_meaning[] = { "Jak se jmenuješ?", "Odkud jsi?", "Kde bydlíš?", "Kdo to je?", "Kolik je ti let?", }; void choice_check(GtkButton *button, gpointer data) { ChoiceCtx *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 (ok == ctx->n) { set_feedback(ctx->feedback, TRUE, tr("feedback_ok")); mark_done(ctx->unit, ctx->ex_num); } else { set_feedback(ctx->feedback, FALSE, tr("feedback_retry_short")); } meaning_reveal_all(ctx->trans, ctx->n); } GtkWidget *build_choice(UnitCtx *unit, const char *title, const char *subtitle, int ex_num, const ChoiceQ *qs, const char **meanings, int n) { GtkWidget *body, *feedback, *check; GtkWidget *page = ex_page_shell(unit->page, title, subtitle, "check", &body, &feedback, &check); ChoiceCtx *ctx = g_new0(ChoiceCtx, 1); int n_opts = qs[0].n_options; ctx->qs = qs; ctx->n = n; ctx->n_opts = n_opts; ctx->unit = unit; ctx->ex_num = ex_num; ctx->feedback = feedback; ctx->toggles = g_new0(GtkToggleButton *, n * n_opts); ctx->trans = g_new0(GtkWidget *, n); for (int i = 0; i < n; i++) { GtkWidget *prompt = gtk_label_new(qs[i].prompt); GtkWidget *row; GtkToggleButton *first = NULL; gtk_widget_set_halign(prompt, GTK_ALIGN_START); gtk_widget_add_css_class(prompt, "ex-prompt"); gtk_box_append(GTK_BOX(body), prompt); row = gtk_flow_box_new(); gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(row), GTK_SELECTION_NONE); gtk_flow_box_set_min_children_per_line(GTK_FLOW_BOX(row), n_opts); gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(row), n_opts); gtk_widget_set_halign(row, GTK_ALIGN_START); gtk_box_append(GTK_BOX(body), row); for (int o = 0; o < qs[i].n_options; o++) { GtkWidget *tb = gtk_toggle_button_new_with_label(qs[i].options[o]); gtk_widget_add_css_class(tb, "pill"); gtk_toggle_button_set_group(GTK_TOGGLE_BUTTON(tb), first); if (!first) first = GTK_TOGGLE_BUTTON(tb); gtk_flow_box_append(GTK_FLOW_BOX(row), tb); ctx->toggles[i * n_opts + o] = GTK_TOGGLE_BUTTON(tb); } if (meanings[i]) ctx->trans[i] = meaning_add(body, meanings[i]); } g_signal_connect(check, "clicked", G_CALLBACK(choice_check), ctx); return page; } /* ------------------------------------------------------------------ */ /* Exercise 4: Freie Antwort */ /* ------------------------------------------------------------------ */ const FreeQ ex4_questions[] = { {"Wie heißt du?", "Jak se jmenuješ?", "Ich heiße Anna.", "Jmenuji se Anna."}, {"Woher kommst du?", "Odkud pocházíš?", "Ich komme aus Tschechien.", "Pocházím z Česka."}, {"Wie alt bist du?", "Kolik je ti let?", "Ich bin sechzehn Jahre alt.", "Je mi šestnáct let."}, {"Wie geht es dir?", "Jak se máš?", "Mir geht es gut, danke.", "Mám se dobře, děkuji."}, }; void ex4_fill_sample(GtkWidget *label) { const FreeQ *q = g_object_get_data(G_OBJECT(label), "fq"); char *txt; if (!q) return; txt = g_strdup_printf(tr("sample_fmt"), tr(q->q_cs), q->sample, tr(q->a_cs)); gtk_label_set_text(GTK_LABEL(label), txt); g_free(txt); } void ex4_reveal(GtkButton *button, gpointer data) { GtkWidget *sample = data; (void)button; ex4_fill_sample(sample); gtk_widget_set_visible(sample, !gtk_widget_get_visible(sample)); } void ex4_finish(GtkButton *button, gpointer data) { Ex4Ctx *ctx = data; (void)button; set_feedback(ctx->feedback, TRUE, tr("feedback_ok")); for (int i = 0; i < ctx->n; i++) { ex4_fill_sample(ctx->samples[i]); gtk_widget_set_visible(ctx->samples[i], TRUE); } mark_done(ctx->unit, ctx->ex_num); } GtkWidget *build_free_answer(UnitCtx *unit, int ex_num, const char *title, const char *subtitle, const char *tip_key, const FreeQ *qs, int n) { GtkWidget *body, *feedback, *check; Ex4Ctx *ctx; GtkWidget *page = ex_page_shell(unit->page, title, subtitle, "finish", &body, &feedback, &check); ctx = g_new0(Ex4Ctx, 1); ctx->unit = unit; ctx->ex_num = ex_num; ctx->feedback = feedback; ctx->n = n; ctx->samples = g_new0(GtkWidget *, n); if (tip_key) { GtkWidget *tip = gtk_label_new(NULL); i18n_bind(tip, tip_key, 0); gtk_widget_set_halign(tip, GTK_ALIGN_START); gtk_widget_add_css_class(tip, "hint"); gtk_box_append(GTK_BOX(body), tip); } for (int i = 0; i < n; i++) { const FreeQ *q = &qs[i]; GtkWidget *ql = gtk_label_new(q->question); GtkWidget *entry; GtkWidget *reveal; GtkWidget *sample; gtk_widget_set_halign(ql, GTK_ALIGN_START); gtk_widget_add_css_class(ql, "ex-prompt"); gtk_box_append(GTK_BOX(body), ql); entry = gtk_entry_new(); i18n_bind(entry, "your_answer", 3); gtk_box_append(GTK_BOX(body), entry); reveal = gtk_button_new(); i18n_bind(reveal, "show_sample", 1); gtk_widget_add_css_class(reveal, "pill"); gtk_widget_set_halign(reveal, GTK_ALIGN_START); gtk_box_append(GTK_BOX(body), reveal); sample = gtk_label_new(NULL); g_object_set_data(G_OBJECT(sample), "fq", (gpointer)q); gtk_widget_set_halign(sample, GTK_ALIGN_START); gtk_widget_add_css_class(sample, "hint"); gtk_label_set_wrap(GTK_LABEL(sample), TRUE); gtk_widget_set_visible(sample, FALSE); gtk_box_append(GTK_BOX(body), sample); ctx->samples[i] = sample; g_signal_connect(reveal, "clicked", G_CALLBACK(ex4_reveal), sample); } g_signal_connect(check, "clicked", G_CALLBACK(ex4_finish), ctx); return page; } GtkWidget *build_ex4(UnitCtx *unit) { return build_free_answer(unit, 4, "Freie Antwort", "sub_free", "tip_ss", ex4_questions, (int)G_N_ELEMENTS(ex4_questions)); } /* ------------------------------------------------------------------ */ /* Exercise 5: Zahlen (digits -> word) */ /* ------------------------------------------------------------------ */ const NumberQ ex5_data[] = { {"14", "vierzehn"}, {"17", "siebzehn"}, {"20", "zwanzig"}, {"6", "sechs"}, {"13", "dreizehn"}, }; const char *ex5_pool[] = { "vierzehn", "siebzehn", "zwanzig", "sechs", "dreizehn", }; const char *ex5_meaning[] = { "čtrnáct", "sedmnáct", "dvacet", "šest", "třináct", }; GtkWidget *build_ex5(UnitCtx *unit) { GtkWidget *body, *feedback, *check; GtkWidget *page = ex_page_shell(unit->page, "Zahlen", "sub_zahlen", "check", &body, &feedback, &check); ComboListCtx *ctx = combo_list_ctx_new(unit, 5, feedback); for (guint i = 0; i < G_N_ELEMENTS(ex5_data); i++) { GtkWidget *row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8); GtkWidget *digits = gtk_label_new(ex5_data[i].digits); GtkWidget *eq = gtk_label_new("="); GtkWidget *combo; gtk_widget_set_halign(row, GTK_ALIGN_START); gtk_widget_add_css_class(digits, "number-big"); gtk_widget_set_valign(digits, GTK_ALIGN_CENTER); gtk_box_append(GTK_BOX(row), digits); gtk_widget_set_valign(eq, GTK_ALIGN_CENTER); gtk_widget_add_css_class(eq, "ex-prompt"); gtk_box_append(GTK_BOX(row), eq); combo = make_word_combo(ex5_pool, G_N_ELEMENTS(ex5_pool)); gtk_widget_set_size_request(combo, 140, -1); gtk_widget_set_valign(combo, GTK_ALIGN_CENTER); gtk_box_append(GTK_BOX(row), combo); combo_list_add(ctx, combo, ex5_data[i].answer); gtk_box_append(GTK_BOX(body), row); combo_list_add_trans(ctx, meaning_add(body, ex5_meaning[i])); } g_signal_connect(check, "clicked", G_CALLBACK(combo_list_check), ctx); return page; } /* ------------------------------------------------------------------ */ /* Exercise 6: Wie viel? */ /* ------------------------------------------------------------------ */ const CountQ ex6_data[] = { {"🍎", 2, "Äpfel", "zwei"}, {"⭐", 5, "Sterne", "fünf"}, {"🐱", 7, "Katzen", "sieben"}, {"🌸", 9, "Blumen", "neun"}, {"🚗", 3, "Autos", "drei"}, {"📚", 11, "Bücher", "elf"}, {"🕯️", 4, "Kerzen", "vier"}, {"⚽", 8, "Bälle", "acht"}, {"🐦", 6, "Vögel", "sechs"}, }; const char *ex6_pool[] = { "zwei", "fünf", "sieben", "neun", "drei", "elf", "vier", "acht", "sechs", }; const char *ex6_meaning[] = { "dvě jablka", "pět hvězd", "sedm koček", "devět květin", "tři auta", "jedenáct knih", "čtyři svíčky", "osm míčů", "šest ptáků", }; GtkWidget *build_ex6(UnitCtx *unit) { GtkWidget *body, *feedback, *check; GtkWidget *page = ex_page_shell(unit->page, "Wie viel?", "sub_wieviel", "check", &body, &feedback, &check); ComboListCtx *ctx = combo_list_ctx_new(unit, 6, feedback); for (guint i = 0; i < G_N_ELEMENTS(ex6_data); i++) { const CountQ *q = &ex6_data[i]; GtkWidget *row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); GString *em = g_string_new(NULL); GtkWidget *icons; GtkWidget *noun; GtkWidget *combo; for (int c = 0; c < q->count; c++) { if (c) g_string_append_c(em, ' '); g_string_append(em, q->emoji); } icons = gtk_label_new(em->str); g_string_free(em, TRUE); gtk_widget_set_valign(icons, GTK_ALIGN_CENTER); gtk_box_append(GTK_BOX(row), icons); noun = gtk_label_new(q->noun); gtk_widget_set_valign(noun, GTK_ALIGN_CENTER); gtk_widget_add_css_class(noun, "ex-prompt"); gtk_box_append(GTK_BOX(row), noun); combo = make_word_combo(ex6_pool, G_N_ELEMENTS(ex6_pool)); gtk_widget_set_size_request(combo, 130, -1); gtk_widget_set_valign(combo, GTK_ALIGN_CENTER); gtk_box_append(GTK_BOX(row), combo); combo_list_add(ctx, combo, q->answer); gtk_box_append(GTK_BOX(body), row); combo_list_add_trans(ctx, meaning_add(body, ex6_meaning[i])); } g_signal_connect(check, "clicked", G_CALLBACK(combo_list_check), ctx); return page; } /* ------------------------------------------------------------------ */ /* Exercise 7: Zahlenreihe */ /* ------------------------------------------------------------------ */ const SeqQ ex7_data[] = { {"sechs, sieben, ", ", neun", "acht"}, {"zwölf, dreizehn, ", ", fünfzehn", "vierzehn"}, {"achtzehn, neunzehn, ", ", einundzwanzig", "zwanzig"}, {"vier, fünf, ", ", sieben", "sechs"}, {"zehn, elf, ", ", dreizehn", "zwölf"}, }; const char *ex7_pool[] = { "acht", "vierzehn", "zwanzig", "sechs", "zwölf", }; const char *ex7_meaning[] = { "šest, sedm, osm, devět", "dvanáct, třináct, čtrnáct, patnáct", "osmnáct, devatenáct, dvacet, dvacet jedna", "čtyři, pět, šest, sedm", "deset, jedenáct, dvanáct, třináct", }; GtkWidget *build_ex7(UnitCtx *unit) { GtkWidget *body, *feedback, *check; GtkWidget *page = ex_page_shell(unit->page, "Zahlenreihe", "sub_reihe", "check", &body, &feedback, &check); ComboListCtx *ctx = combo_list_ctx_new(unit, 7, feedback); for (guint i = 0; i < G_N_ELEMENTS(ex7_data); i++) { const SeqQ *q = &ex7_data[i]; GtkWidget *row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); GtkWidget *before = gtk_label_new(q->before); GtkWidget *after = gtk_label_new(q->after); GtkWidget *combo; gtk_widget_set_halign(row, GTK_ALIGN_START); gtk_widget_set_valign(before, GTK_ALIGN_CENTER); gtk_widget_add_css_class(before, "ex-prompt"); gtk_box_append(GTK_BOX(row), before); combo = make_word_combo(ex7_pool, G_N_ELEMENTS(ex7_pool)); gtk_widget_set_size_request(combo, 140, -1); gtk_widget_set_valign(combo, GTK_ALIGN_CENTER); gtk_box_append(GTK_BOX(row), combo); combo_list_add(ctx, combo, q->answer); gtk_widget_set_valign(after, GTK_ALIGN_CENTER); gtk_widget_add_css_class(after, "ex-prompt"); gtk_box_append(GTK_BOX(row), after); gtk_box_append(GTK_BOX(body), row); combo_list_add_trans(ctx, meaning_add(body, ex7_meaning[i])); } g_signal_connect(check, "clicked", G_CALLBACK(combo_list_check), ctx); return page; } /* ------------------------------------------------------------------ */ /* Exercise 8: Verb einsetzen (word selection) */ /* ------------------------------------------------------------------ */ /* Complete German sentence for a before/after gap row (with any trailing * "(…)" hint such as "(kommen)" removed) – shown after Check. */ char *verbq_german(const char *before, const char *answer, const char *after) { GString *s; const char *p; gboolean inside = FALSE; char *out; s = g_string_new(NULL); if (before && before[0]) g_string_append(s, before); if (answer && answer[0]) g_string_append(s, answer); if (after && after[0]) g_string_append(s, after); out = s->str; s->str = NULL; g_string_free(s, TRUE); s = g_string_new(NULL); for (p = out; *p; p++) { if (*p == '(') { inside = TRUE; continue; } if (*p == ')') { inside = FALSE; continue; } if (!inside) g_string_append_c(s, *p); } g_free(out); while (s->len > 0 && g_ascii_isspace(s->str[s->len - 1])) g_string_truncate(s, s->len - 1); if (s->len == 0) { g_string_free(s, TRUE); return NULL; } return g_string_free(s, FALSE); } const VerbQ ex8_data[] = { {"Ich ", " Peter.", "heiße"}, {"Woher ", " du?", "kommst"}, {"Ich ", " in Prag.", "wohne"}, {"Wie alt ", " du?", "bist"}, {"Ich ", " Fußball.", "spiele"}, {"Was ", " du gern?", "machst"}, }; const char *ex8_pool[] = { "heißen", "kommen", "wohnen", "sein", "spielen", "machen", "heiße", "kommst", "wohne", "bist", "spiele", "machst", }; const char *ex8_meaning[] = { "Jmenuji se Petr.", "Odkud jsi?", "Bydlím v Praze.", "Kolik je ti let?", "Hraji fotbal.", "Co rád/a děláš?", }; GtkWidget *build_ex8(UnitCtx *unit) { GtkWidget *body, *feedback, *check; GtkWidget *page = ex_page_shell(unit->page, "Verb einsetzen", "sub_verb", "check", &body, &feedback, &check); ComboListCtx *ctx = combo_list_ctx_new(unit, 8, feedback); ctx->trans_upfront = TRUE; ctx->reveal_german = TRUE; for (guint i = 0; i < G_N_ELEMENTS(ex8_data); i++) { const VerbQ *q = &ex8_data[i]; GtkWidget *row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); GtkWidget *before = gtk_label_new(q->before); GtkWidget *after = gtk_label_new(q->after); GtkWidget *combo; gtk_widget_set_halign(row, GTK_ALIGN_START); gtk_widget_set_valign(before, GTK_ALIGN_CENTER); gtk_widget_add_css_class(before, "ex-prompt"); gtk_box_append(GTK_BOX(row), before); combo = make_word_combo(ex8_pool, G_N_ELEMENTS(ex8_pool)); gtk_widget_set_size_request(combo, 120, -1); gtk_widget_set_valign(combo, GTK_ALIGN_CENTER); gtk_box_append(GTK_BOX(row), combo); combo_list_add(ctx, combo, q->answer); gtk_widget_set_valign(after, GTK_ALIGN_CENTER); gtk_widget_add_css_class(after, "ex-prompt"); gtk_box_append(GTK_BOX(row), after); gtk_box_append(GTK_BOX(body), row); { GtkWidget *mean = meaning_add(body, ex8_meaning[i]); GtkWidget *w; char *sentence; gtk_widget_set_visible(mean, TRUE); combo_list_add_trans(ctx, mean); sentence = verbq_german(q->before, q->answer, q->after); if (sentence && sentence[0]) { w = model_answer_add(body, sentence); g_array_append_val(ctx->models, w); } g_free(sentence); } } g_signal_connect(check, "clicked", G_CALLBACK(combo_list_check), ctx); return page; } /* ------------------------------------------------------------------ */ /* Exercise 10: Wörter trennen (word arrangement) */ /* ------------------------------------------------------------------ */ const AssemblyItem ex10_items[] = { {"wiespätistes", {"wie", "spät", "ist", "es"}, 4}, {"ichheißeanna", {"ich", "heiße", "anna"}, 3}, {"woherkommstdu", {"woher", "kommst", "du"}, 3}, {"aufwiedersehen", {"auf", "wiedersehen"}, 2}, {"dankeschön", {"danke", "schön"}, 2}, }; const char *ex10_meaning[] = { "Kolik je hodin?", "Jmenuji se Anna.", "Odkud jsi?", "Na shledanou.", "Moc děkuji.", }; /* ------------------------------------------------------------------ */ /* Exercise 12: Was macht er/sie? */ /* ------------------------------------------------------------------ */ const VerbClueQ ex12_data[] = { {"🏊", "Er ", " im See.", "schwimmt"}, {"🎤", "Sie ", " ein Lied.", "singt"}, {"🍳", "Er ", " Suppe.", "kocht"}, {"📖", "Sie ", " ein Buch.", "liest"}, {"🚗", "Er ", " Auto.", "fährt"}, {"🎨", "Sie ", " ein Bild.", "malt"}, {"⚽", "Er ", " Fußball.", "spielt"}, {"🛏️", "Sie ", " .", "schläft"}, }; const char *ex12_pool[] = { "schwimmt", "singt", "kocht", "liest", "fährt", "malt", "spielt", "schläft", }; const char *ex12_meaning[] = { "Plave v jezeře.", "Zpívá píseň.", "Vaří polévku.", "Čte knihu.", "Řídí auto.", "Maluje obraz.", "Hraje fotbal.", "Spí.", }; GtkWidget *build_ex12(UnitCtx *unit) { GtkWidget *body, *feedback, *check; GtkWidget *page = ex_page_shell(unit->page, "Was macht er/sie?", "sub_bild", "check", &body, &feedback, &check); ComboListCtx *ctx = combo_list_ctx_new(unit, 12, feedback); ctx->trans_upfront = TRUE; ctx->reveal_german = TRUE; for (guint i = 0; i < G_N_ELEMENTS(ex12_data); i++) { const VerbClueQ *q = &ex12_data[i]; GtkWidget *row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); GtkWidget *emoji = gtk_label_new(q->emoji); GtkWidget *before = gtk_label_new(q->before); GtkWidget *after = gtk_label_new(q->after); GtkWidget *combo; gtk_widget_set_halign(row, GTK_ALIGN_START); gtk_widget_set_valign(emoji, GTK_ALIGN_CENTER); gtk_box_append(GTK_BOX(row), emoji); gtk_widget_set_valign(before, GTK_ALIGN_CENTER); gtk_widget_add_css_class(before, "ex-prompt"); gtk_box_append(GTK_BOX(row), before); combo = make_word_combo(ex12_pool, G_N_ELEMENTS(ex12_pool)); gtk_widget_set_size_request(combo, 120, -1); gtk_widget_set_valign(combo, GTK_ALIGN_CENTER); gtk_box_append(GTK_BOX(row), combo); combo_list_add(ctx, combo, q->answer); gtk_widget_set_valign(after, GTK_ALIGN_CENTER); gtk_widget_add_css_class(after, "ex-prompt"); gtk_box_append(GTK_BOX(row), after); gtk_box_append(GTK_BOX(body), row); { GtkWidget *mean = meaning_add(body, ex12_meaning[i]); GtkWidget *w; char *sentence; gtk_widget_set_visible(mean, TRUE); combo_list_add_trans(ctx, mean); sentence = verbq_german(q->before, q->answer, q->after); if (sentence && sentence[0]) { w = model_answer_add(body, sentence); g_array_append_val(ctx->models, w); } g_free(sentence); } } g_signal_connect(check, "clicked", G_CALLBACK(combo_list_check), ctx); return page; } /* ------------------------------------------------------------------ */ /* Assign exercises (11 + 13) */ /* ------------------------------------------------------------------ */ const AssignItem ex11_items[] = { {NULL, "Hallo", 0}, {NULL, "Guten Morgen", 0}, {NULL, "Guten Tag", 0}, {NULL, "Guten Abend", 0}, {NULL, "Grüß Gott", 0}, {NULL, "Servus", 0}, {NULL, "Tschüss", 1}, {NULL, "Auf Wiedersehen", 1}, {NULL, "Bis bald", 1}, {NULL, "Bis morgen", 1}, {NULL, "Gute Nacht", 1}, {NULL, "Bis später", 1}, }; const AssignItem ex13_items[] = { {"🥨", "Brezel", 0}, {"🌭", "Bratwurst", 0}, {"🏰", "Brandenburger Tor", 0}, {"🚗", "Volkswagen", 0}, {"🎻", "Mozart", 1}, {"🍰", "Sachertorte", 1}, {"🥩", "Wiener Schnitzel", 1}, {"🧀", "Käse", 2}, {"⛰️", "Matterhorn", 2}, {"🍫", "Schokolade", 2}, }; const char *ex11_groups[] = {"Begrüßung", "Verabschiedung"}; const char *ex13_groups[] = {"Deutschland (D)", "Österreich (A)", "Schweiz (CH)"}; const char *ex11_meaning[] = { "Ahoj", "Dobré ráno", "Dobrý den", "Dobrý večer", "Dobrý den (Bavorsko, Rakousko)", "Ahoj / Čau", "Čau", "Na shledanou", "Brzy na viděnou", "Do zítřka", "Dobrou noc", "Zatím / Na viděnou", }; const char *ex13_meaning[] = { "Preclík", "Klobása", "Braniborská brána", "Značka aut", "Rakouský skladatel", "Čokoládový dort (Vídeň)", "Vídeňský řízek", "Sýr", "Švýcarská hora", "Čokoláda", }; void assign_rebuild(AssignCtx *ac) { for (int i = 0; i < ac->n_items; i++) { GtkWidget *parent = gtk_widget_get_parent(ac->items[i]); if (parent != NULL && GTK_IS_FLOW_BOX_CHILD(parent)) gtk_flow_box_child_set_child(GTK_FLOW_BOX_CHILD(parent), NULL); } flow_clear(GTK_FLOW_BOX(ac->pool)); for (int g = 0; g < ac->n_groups; g++) flow_clear(GTK_FLOW_BOX(ac->group_flow[g])); for (int i = 0; i < ac->n_items; i++) { if (ac->current_group[i] < 0) gtk_flow_box_insert(GTK_FLOW_BOX(ac->pool), ac->items[i], -1); else gtk_flow_box_insert(GTK_FLOW_BOX(ac->group_flow[ac->current_group[i]]), ac->items[i], -1); } } void assign_chip_clicked(GtkButton *button, gpointer data) { AssignChipRef *ref = data; AssignCtx *ac = ref->ac; int idx = ref->idx; double sx = 0.0, sy = 0.0; GtkWidget *chip = GTK_WIDGET(button); (void)button; if (ac->flying) return; if (ac->stage && gtk_widget_translate_coordinates(chip, ac->stage, 0, 0, &sx, &sy)) { if (ac->current_group[idx] < 0) ac->current_group[idx] = ac->active_group; else ac->current_group[idx] = -1; assign_rebuild(ac); asm_begin_fly(chip, ac->stage, &ac->flying, sx, sy); } else { /* No usable animation layer yet – just move instantly. */ if (ac->current_group[idx] < 0) ac->current_group[idx] = ac->active_group; else ac->current_group[idx] = -1; assign_rebuild(ac); } } void assign_group_toggled(GtkToggleButton *button, gpointer data) { AssignCtx *ac = data; if (!gtk_toggle_button_get_active(button)) return; for (int g = 0; g < ac->n_groups; g++) { if (GTK_TOGGLE_BUTTON(ac->group_btn[g]) == button) { ac->active_group = g; break; } } } void assign_check(GtkButton *button, gpointer data) { AssignCtx *ac = data; int ok = 0; (void)button; for (int i = 0; i < ac->n_items; i++) if (ac->current_group[i] == ac->items_data[i].correct_group) ok++; if (ok == ac->n_items) { set_feedback(ac->feedback, TRUE, tr("feedback_ok")); mark_done(ac->unit, ac->ex_num); } else { set_feedback(ac->feedback, FALSE, tr("feedback_retry")); } for (int i = 0; i < ac->n_items; i++) if (ac->trans[i]) gtk_widget_set_visible(ac->trans[i], TRUE); } GtkWidget *build_assign(UnitCtx *unit, const char *title, const char *subtitle, int ex_num, const AssignItem *items, int n_items, const char **group_labels, int n_groups, const char **meanings) { GtkWidget *body, *feedback, *check; GtkWidget *page = ex_page_shell(unit->page, title, subtitle, "check", &body, &feedback, &check); AssignCtx *ac = g_new0(AssignCtx, 1); GtkWidget *hint; GtkWidget *row; GtkWidget *holder; GtkWidget *content; GtkWidget *stage; GtkToggleButton *first = NULL; int *order; ac->n_items = n_items; ac->n_groups = n_groups; ac->active_group = 0; ac->unit = unit; ac->ex_num = ex_num; ac->feedback = feedback; ac->items_data = items; ac->items = g_new0(GtkWidget *, n_items); ac->trans = g_new0(GtkWidget *, n_items); ac->current_group = g_new0(int, n_items); for (int i = 0; i < n_items; i++) ac->current_group[i] = -1; holder = gtk_overlay_new(); gtk_widget_set_hexpand(holder, TRUE); gtk_box_append(GTK_BOX(body), holder); content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 14); gtk_widget_set_halign(content, GTK_ALIGN_FILL); gtk_overlay_set_child(GTK_OVERLAY(holder), content); stage = gtk_fixed_new(); gtk_widget_set_halign(stage, GTK_ALIGN_FILL); gtk_widget_set_valign(stage, GTK_ALIGN_FILL); gtk_widget_set_can_target(stage, FALSE); gtk_overlay_add_overlay(GTK_OVERLAY(holder), stage); ac->stage = stage; hint = gtk_label_new(NULL); i18n_bind(hint, "assign_hint", 0); gtk_label_set_wrap(GTK_LABEL(hint), TRUE); gtk_widget_set_halign(hint, GTK_ALIGN_START); gtk_widget_add_css_class(hint, "ex-sub"); gtk_box_append(GTK_BOX(content), hint); row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8); gtk_widget_set_halign(row, GTK_ALIGN_START); gtk_box_append(GTK_BOX(content), row); for (int g = 0; g < n_groups; g++) { GtkWidget *gb = gtk_toggle_button_new_with_label(group_labels[g]); gtk_widget_add_css_class(gb, "group-btn"); gtk_toggle_button_set_group(GTK_TOGGLE_BUTTON(gb), first); if (!first) first = GTK_TOGGLE_BUTTON(gb); gtk_box_append(GTK_BOX(row), gb); ac->group_btn[g] = gb; g_signal_connect(gb, "toggled", G_CALLBACK(assign_group_toggled), ac); } gtk_toggle_button_set_active(first, TRUE); ac->pool = gtk_flow_box_new(); gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(ac->pool), GTK_SELECTION_NONE); gtk_flow_box_set_min_children_per_line(GTK_FLOW_BOX(ac->pool), 2); gtk_widget_set_size_request(ac->pool, -1, 48); gtk_box_append(GTK_BOX(content), ac->pool); for (int g = 0; g < n_groups; g++) { GtkWidget *panel = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6); GtkWidget *gl = gtk_label_new(group_labels[g]); GtkWidget *gf = gtk_flow_box_new(); gtk_widget_add_css_class(panel, "group-panel"); gtk_widget_set_halign(gl, GTK_ALIGN_START); gtk_widget_add_css_class(gl, "group-panel-label"); gtk_box_append(GTK_BOX(panel), gl); gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(gf), GTK_SELECTION_NONE); gtk_flow_box_set_min_children_per_line(GTK_FLOW_BOX(gf), 2); gtk_widget_set_size_request(gf, -1, 48); ac->group_flow[g] = gf; gtk_box_append(GTK_BOX(panel), gf); gtk_box_append(GTK_BOX(content), panel); } order = g_new0(int, n_items); for (int i = 0; i < n_items; i++) order[i] = i; shuffle_indices(order, n_items); for (int k = 0; k < n_items; k++) { int i = order[k]; GString *lab = g_string_new(NULL); GtkWidget *box; GtkWidget *chip; GtkWidget *m = NULL; AssignChipRef *ref = g_new(AssignChipRef, 1); if (items[i].emoji) { g_string_append(lab, items[i].emoji); g_string_append(lab, " "); } g_string_append(lab, items[i].label); box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2); gtk_widget_set_halign(box, GTK_ALIGN_CENTER); chip = gtk_button_new_with_label(lab->str); g_string_free(lab, TRUE); gtk_widget_add_css_class(chip, "chip"); gtk_widget_set_halign(chip, GTK_ALIGN_CENTER); gtk_box_append(GTK_BOX(box), chip); if (meanings[i]) { m = gtk_label_new(NULL); i18n_bind(m, meanings[i], 0); gtk_label_set_xalign(GTK_LABEL(m), 0.5); gtk_widget_set_halign(m, GTK_ALIGN_CENTER); gtk_widget_add_css_class(m, "meaning"); gtk_label_set_wrap(GTK_LABEL(m), TRUE); gtk_label_set_max_width_chars(GTK_LABEL(m), 16); gtk_widget_set_visible(m, FALSE); gtk_box_append(GTK_BOX(box), m); ac->trans[i] = m; } g_object_ref(box); ac->items[i] = box; ref->ac = ac; ref->idx = i; g_signal_connect(chip, "clicked", G_CALLBACK(assign_chip_clicked), ref); gtk_flow_box_insert(GTK_FLOW_BOX(ac->pool), box, -1); } g_free(order); g_signal_connect(check, "clicked", G_CALLBACK(assign_check), ac); return page; }