diff --git a/i18n.c b/i18n.c index aebcc37..95baea0 100644 --- a/i18n.c +++ b/i18n.c @@ -199,6 +199,12 @@ const TrEntry tr_ui[] = { "4 slides with the method, then an exercise."}, {"net_ex_title", "Cvičení: výpočet IP adres", "Exercise: IP address calculation"}, + {"net_unit2", "Základní pojmy a rozdělení sítí", + "Basic concepts and network types"}, + {"net_unit2_sub", "5 snímků s pojmy a pak kvíz.", + "5 slides with the concepts, then a quiz."}, + {"net_ex2_title", "Kvíz: základní pojmy sítí", + "Quiz: basic networking concepts"}, {"net_slide_next", "Další", "Next"}, {"net_slide_prev", "Zpět", "Back"}, {"net_slide_start", "Přejít na cvičení", "Go to the exercise"}, @@ -237,6 +243,20 @@ const TrEntry tr_content[] = { {"„siebzehn“ = sedmnáct (17)", NULL, "“siebzehn” = seventeen (17)"}, {"„zwanzig“ = dvacet (20)", NULL, "“zwanzig” = twenty (20)"}, {"„sechs“ = šest (6)", NULL, "“sechs” = six (6)"}, + {"1 B = 8 b", NULL, "1 B = 8 b"}, + {"2⁸ = 256 hodnot (0–255)", NULL, "2⁸ = 256 values (0–255)"}, + {"Kabely – síť zůstává připojená i po přenosu", NULL, + "Cables – the network stays connected after transfer"}, + {"WAN = Wide Area Network, např. internet", NULL, + "WAN = Wide Area Network, e.g. the internet"}, + {"Např. hvězda, kruh, sběrnice", NULL, + "E.g. star, ring, bus"}, + {"Architektura = topologie + standard", NULL, + "Architecture = topology + standard"}, + {"Bez centrálního serveru – stanice si sdílejí data a tiskárny", NULL, + "No central server – stations share data and printers"}, + {"Server řídí a poskytuje služby, má vyšší výkon", NULL, + "A server controls and provides services; higher performance"}, {"čtrnáct", NULL, "fourteen"}, {"sedmnáct", NULL, "seventeen"}, {"dvacet", NULL, "twenty"}, @@ -807,7 +827,7 @@ void refresh_welcome_heading(void) { } void refresh_stats_ui(void); -void net_slide_apply(void); +void net_lessons_apply_lang(void); void net_rail_theme_reset(void); void apply_language(void) { @@ -818,5 +838,5 @@ void apply_language(void) { i18n_apply_one(&g_array_index(i18n_binds, I18nBind, i)); refresh_welcome_heading(); refresh_stats_ui(); - net_slide_apply(); + net_lessons_apply_lang(); } diff --git a/main.c b/main.c index 29d8fae..e7a6b79 100644 --- a/main.c +++ b/main.c @@ -173,6 +173,8 @@ void activate(GtkApplication *app, gpointer user_data) { gtk_stack_add_named(main_stack, build_netmap_page(), "netmap"); gtk_stack_add_named(main_stack, build_net_unit1_page(), "netunit1"); gtk_stack_add_named(main_stack, build_net_exercise_page(), "netex"); + gtk_stack_add_named(main_stack, build_net_unit2_page(), "netunit2"); + gtk_stack_add_named(main_stack, build_net_unit2_exercise_page(), "netex2"); for (int i = 0; i < NUM_UNLOCKED; i++) { UnitCtx *u = &units[i]; diff --git a/maturita.h b/maturita.h index 8ee25a9..61df066 100644 --- a/maturita.h +++ b/maturita.h @@ -46,6 +46,8 @@ #define NET_UNITS 30 #define NET_GRID_COLS 6 #define NET_SLIDES 4 +#define NET2_SLIDES 5 +#define NET_LESSONS 2 typedef enum { THEME_CATPPUCCIN = 0, @@ -469,6 +471,29 @@ typedef struct { int id; gboolean infeasible; } NetQz; +typedef struct { + GtkWidget *stack; + GtkWidget *prev_btn; + GtkWidget *next_btn; + guint idx; + guint n_slides; + const char *unit_page; + const char *ex_page; +} NetLesson; +typedef struct { + const char *kicker; + const char *title; + const char *tip; + const char *line[8]; +} NetSlide; +typedef struct { + const ChoiceQ *qs; + int n; + int n_opts; + GtkWidget *feedback; + GtkToggleButton **toggles; + GtkWidget **hints; +} NetMcqCtx; extern GtkStack *main_stack; extern GtkWindow *main_window; @@ -628,10 +653,7 @@ extern const char *ex13_u3_pool[]; extern const U3Fill ex13_u3_rows[]; extern const U3Kw ex14_u3_qs[]; extern const U3Let ex15_u3_rows[]; -extern GtkWidget *net_slide_stack; -extern GtkWidget *net_prev_btn; -extern GtkWidget *net_next_btn; -extern guint net_slide_idx; +extern NetLesson net_lessons[NET_LESSONS]; extern GtkWidget *net_note_host; extern GArray *net_note_kicks; extern GArray *net_note_titles; @@ -670,7 +692,7 @@ void i18n_apply_one(const I18nBind *b); void i18n_bind(GtkWidget *widget, const char *key, int kind); void refresh_welcome_heading(void); void refresh_stats_ui(void); -void net_slide_apply(void); +void net_lessons_apply_lang(void); void net_rail_theme_reset(void); void apply_language(void); char *read_css_file(void); @@ -961,7 +983,8 @@ GtkWidget *net_slide_card(const char *cls); void net_note_kicker(GtkWidget *box, const char *text); void net_note_title(GtkWidget *box, const char *text); void net_note_line(GtkWidget *box, const char *text, gboolean tip); -void net_slide_apply(void); +void net_slide_apply(NetLesson *L); +void net_lessons_apply_lang(void); void net_open_unit(GtkButton *button, gpointer data); void net_slide_prev(GtkButton *button, gpointer data); void net_slide_next(GtkButton *button, gpointer data); @@ -980,7 +1003,10 @@ void net_draw_rail(GtkDrawingArea *area, cairo_t *cr, void net_rail_theme_reset(void); void net_add_node(GtkFixed *fixed, int index); GtkWidget *build_netmap_page(void); +GtkWidget *build_net_unit_page(NetLesson *L, const char *title_key, + const NetSlide *slides, guint n_slides); GtkWidget *build_net_unit1_page(void); +GtkWidget *build_net_unit2_page(void); GtkWidget *net_qz_combo(void); void net_qz_set_prefix(GtkComboBoxText *c, int pfx); int net_qz_prefix(GtkComboBoxText *c); @@ -991,6 +1017,8 @@ void net_qz_fill(GtkButton *button, gpointer data); void net_qz_header(GtkWidget *box, const char *text); GtkWidget *net_qz_make_entry(void); GtkWidget *build_net_exercise_page(void); +void net_mcq_check(GtkButton *button, gpointer data); +GtkWidget *build_net_unit2_exercise_page(void); void activate(GtkApplication *app, gpointer user_data); int main(int argc, char **argv); diff --git a/net.c b/net.c index bfeb479..b36b62b 100644 --- a/net.c +++ b/net.c @@ -3,10 +3,10 @@ /* ---- Computer networks: subject path, lesson & exercise ------------ */ -GtkWidget *net_slide_stack; -GtkWidget *net_prev_btn; -GtkWidget *net_next_btn; -guint net_slide_idx; +NetLesson net_lessons[NET_LESSONS] = { + { .n_slides = NET_SLIDES, .unit_page = "netunit1", .ex_page = "netex" }, + { .n_slides = NET2_SLIDES, .unit_page = "netunit2", .ex_page = "netex2" }, +}; void net_paragraph(GtkWidget *box, const char *text) { GtkWidget *l = gtk_label_new(text); @@ -34,6 +34,14 @@ GArray *net_note_titles; GArray *net_note_bodies; int net_note_last_w = -1; +void net_notes_ensure(void) { + if (net_note_kicks) + return; + net_note_kicks = g_array_new(FALSE, FALSE, sizeof(GtkWidget *)); + net_note_titles = g_array_new(FALSE, FALSE, sizeof(GtkWidget *)); + net_note_bodies = g_array_new(FALSE, FALSE, sizeof(GtkWidget *)); +} + void net_note_font(GtkWidget *l, int px) { PangoAttrList *attrs = pango_attr_list_new(); @@ -46,6 +54,8 @@ void net_rescale_notes(void) { int w = net_note_host ? gtk_widget_get_allocated_width(net_note_host) : 0; int body, head, kick; + if (!net_note_kicks) + return; if (w < 160) w = 160; body = w / 46; @@ -112,52 +122,66 @@ void net_note_line(GtkWidget *box, const char *text, gboolean tip) { g_array_append_val(net_note_bodies, l); } -void net_slide_apply(void) { +void net_slide_apply(NetLesson *L) { char name[16]; - if (!net_slide_stack) + if (!L || !L->stack) return; - g_snprintf(name, sizeof(name), "net_slide%u", net_slide_idx); - gtk_stack_set_visible_child_name(GTK_STACK(net_slide_stack), name); + g_snprintf(name, sizeof(name), "net_slide%u", L->idx); + gtk_stack_set_visible_child_name(GTK_STACK(L->stack), name); - if (net_prev_btn) { - gtk_button_set_label(GTK_BUTTON(net_prev_btn), tr("net_slide_prev")); - gtk_widget_set_sensitive(net_prev_btn, net_slide_idx > 0); + 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 (net_next_btn) { + if (L->next_btn) { gtk_button_set_label( - GTK_BUTTON(net_next_btn), - net_slide_idx + 1 >= NET_SLIDES ? tr("net_slide_start") - : tr("net_slide_next")); + GTK_BUTTON(L->next_btn), + L->idx + 1 >= L->n_slides ? tr("net_slide_start") + : tr("net_slide_next")); } } +void net_lessons_apply_lang(void) { + for (int i = 0; i < NET_LESSONS; i++) + net_slide_apply(&net_lessons[i]); +} + void net_open_unit(GtkButton *button, gpointer data) { + NetLesson *L = data; + (void)button; - (void)data; - net_slide_idx = 0; - net_slide_apply(); - gtk_stack_set_visible_child_name(main_stack, "netunit1"); + if (!L) + return; + L->idx = 0; + net_slide_apply(L); + gtk_stack_set_visible_child_name(main_stack, L->unit_page); } void net_slide_prev(GtkButton *button, gpointer data) { + NetLesson *L = data; + (void)button; - (void)data; - if (net_slide_idx > 0) { - net_slide_idx--; - net_slide_apply(); + if (!L) + return; + if (L->idx > 0) { + L->idx--; + net_slide_apply(L); } } void net_slide_next(GtkButton *button, gpointer data) { + NetLesson *L = data; + (void)button; - (void)data; - if (net_slide_idx + 1 < NET_SLIDES) { - net_slide_idx++; - net_slide_apply(); + if (!L) + return; + if (L->idx + 1 < L->n_slides) { + L->idx++; + net_slide_apply(L); } else { - gtk_stack_set_visible_child_name(main_stack, "netex"); + gtk_stack_set_visible_child_name(main_stack, L->ex_page); } } @@ -400,13 +424,14 @@ void net_rail_theme_reset(void) { void net_add_node(GtkFixed *fixed, int index) { int num = index + 1; - gboolean locked = index != 0; + gboolean locked = index >= NET_LESSONS; GtkWidget *card; GtkWidget *vbox; GtkWidget *number; GtkWidget *icon; GtkWidget *name; char *text; + static const char *unit_keys[NET_LESSONS] = { "net_unit1", "net_unit2" }; card = gtk_button_new(); gtk_widget_add_css_class(card, "unit-node"); @@ -437,7 +462,8 @@ void net_add_node(GtkFixed *fixed, int index) { icon = icon_area_new(draw_check_icon, 0.1176, 0.1176, 0.1804, 18); gtk_widget_set_visible(icon, FALSE); gtk_box_append(GTK_BOX(vbox), icon); - g_signal_connect(card, "clicked", G_CALLBACK(net_open_unit), NULL); + g_signal_connect(card, "clicked", G_CALLBACK(net_open_unit), + &net_lessons[index]); } net_nodes[index] = card; @@ -449,8 +475,8 @@ void net_add_node(GtkFixed *fixed, int index) { 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"); - if (index == 0) { - i18n_bind(name, "net_unit1", 0); + if (index < NET_LESSONS) { + i18n_bind(name, unit_keys[index], 0); } else { gtk_widget_add_css_class(name, "unit-name-locked"); } @@ -522,13 +548,86 @@ GtkWidget *build_netmap_page(void) { return page; } +GtkWidget *build_net_unit_page(NetLesson *L, const char *title_key, + const NetSlide *slides, guint n_slides) { + GtkWidget *page; + GtkWidget *scroll; + GtkWidget *nav; + + net_notes_ensure(); + 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("netmap", title_key, NULL)); + + 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); + net_note_host = 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), "net_slide%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, net_note_tick, NULL, NULL); + net_note_last_w = -1; + net_rescale_notes(); + + 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(net_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(net_slide_next), L); + gtk_box_append(GTK_BOX(nav), L->next_btn); + + L->idx = 0; + net_slide_apply(L); + + return page; +} + GtkWidget *build_net_unit1_page(void) { - typedef struct { - const char *kicker; - const char *title; - const char *tip; - const char *line[7]; - } NetSlide; static const NetSlide slides[NET_SLIDES] = { { "1 / 4 • VLSM", "Podsíťování sítí", @@ -586,84 +685,74 @@ GtkWidget *build_net_unit1_page(void) { }, }, }; - GtkWidget *page; - GtkWidget *scroll; - GtkWidget *nav; - net_note_kicks = g_array_new(FALSE, FALSE, sizeof(GtkWidget *)); - net_note_titles = g_array_new(FALSE, FALSE, sizeof(GtkWidget *)); - net_note_bodies = g_array_new(FALSE, FALSE, sizeof(GtkWidget *)); + return build_net_unit_page(&net_lessons[0], "net_unit1", slides, NET_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); +GtkWidget *build_net_unit2_page(void) { + static const NetSlide slides[NET2_SLIDES] = { + { + "1 / 5 • Jednotky", "Bit, byte a počítačová síť", + "Tip: 1 B = 8 b → 2⁸ = 256 možných hodnot", + { + "Bit (1 b): základní jednotka informace – 0 nebo 1.", + "0 = nevede proud, 1 = vede proud.", + "Byte (1 B): 1 B = 8 b, nabývá 256 hodnot.", + "Počítačová síť: propojení uzlů (PC a dalších prvků).", + "Vyžaduje hardwarové i softwarové prostředky.", + NULL, + }, + }, + { + "2 / 5 • Propojení", "Trvalé, dočasné a důvody zapojení", + NULL, + { + "Trvalé propojení: kabely – síť zůstává připojená i po přenosu.", + "Dočasné propojení: modem, Wi‑Fi – po přenosu se odpojuje.", + "Důvody zapojení do sítě:", + "sdílení dat (nutná podpora aplikací)", + "sdílení HW (tiskárny, disky) a SW (databáze)", + "vyšší spolehlivost, bezpečnost (přístupová práva)", + "komunikace (e‑mail, hlas)", + NULL, + }, + }, + { + "3 / 5 • Pojmy", "Terminologie sítí", + "Architektura = topologie + standard", + { + "Uzel: jakékoli zařízení (stanice, server, aktivní prvek).", + "Topologie: způsob propojení (hvězda, kruh, sběrnice).", + "Standard: normy pro zpracování dat.", + "Architektura: topologie + standard.", + NULL, + }, + }, + { + "4 / 5 • Rozlehlost", "WAN, MAN a LAN", + NULL, + { + "WAN: rozsáhlé sítě (státy, kontinenty), např. internet.", + "MAN: městské sítě – propojují lokální sítě.", + "LAN: lokální sítě (budova), kabeláž v řádu km,", + "jednotná architektura.", + NULL, + }, + }, + { + "5 / 5 • Uzly", "Server, stanice a peer‑to‑peer", + NULL, + { + "Server: nadřazený, řídící, poskytuje služby, vyšší výkon.", + "Pracovní stanice: pro uživatele, jednodušší konfigurace.", + "Peer‑to‑peer: síť bez serverů – jen stanice,", + "které sdílejí data a tiskárny.", + NULL, + }, + }, + }; - gtk_box_append(GTK_BOX(page), - top_bar("netmap", "net_unit1", NULL)); - - 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); - - net_slide_stack = gtk_stack_new(); - gtk_stack_set_transition_type(GTK_STACK(net_slide_stack), - GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT); - gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), - net_slide_stack); - net_note_host = net_slide_stack; - - for (int s = 0; s < NET_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), "net_slide%d", s); - gtk_stack_add_named(GTK_STACK(net_slide_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 < 7 && 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(net_slide_stack, net_note_tick, NULL, NULL); - net_note_last_w = -1; - net_rescale_notes(); - - 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); - - net_prev_btn = gtk_button_new_with_label(tr("net_slide_prev")); - gtk_widget_add_css_class(net_prev_btn, "btn-primary"); - g_signal_connect(net_prev_btn, "clicked", G_CALLBACK(net_slide_prev), NULL); - gtk_box_append(GTK_BOX(nav), net_prev_btn); - - net_next_btn = gtk_button_new_with_label(tr("net_slide_next")); - gtk_widget_add_css_class(net_next_btn, "btn-primary"); - g_signal_connect(net_next_btn, "clicked", G_CALLBACK(net_slide_next), NULL); - gtk_box_append(GTK_BOX(nav), net_next_btn); - - net_slide_idx = 0; - net_slide_apply(); - - return page; + return build_net_unit_page(&net_lessons[1], "net_unit2", slides, NET2_SLIDES); } /* Solutions below are the canonical "largest subnet first, in order A–D" @@ -1015,3 +1104,172 @@ GtkWidget *build_net_exercise_page(void) { return page; } + +/* ---- Unit 2: multiple-choice quiz on basic networking concepts ---- */ + +const ChoiceQ net2_qs[] = { + {"Kolik bitů má jeden byte?", + {"4", "8", "16", "32"}, 4, 1}, + {"Kolik různých hodnot nabývá jeden byte?", + {"128", "255", "256", "512"}, 4, 2}, + {"Co je trvalé propojení sítě?", + {"Modem", "Wi‑Fi", "Kabely", "Bluetooth"}, 4, 2}, + {"Co označuje zkratka WAN?", + {"Lokální síť v budově", "Městskou síť", + "Rozsáhlou síť (státy, kontinenty)", "Bezdrátovou síť"}, 4, 2}, + {"Co je topologie sítě?", + {"Norma pro zpracování dat", "Způsob propojení uzlů", + "Název serveru", "Rychlost přenosu"}, 4, 1}, + {"Architektura sítě je:", + {"Jen topologie", "Jen standard", "Topologie + standard", + "Pouze typ kabelu"}, 4, 2}, + {"Peer‑to‑peer síť znamená:", + {"Síť s jedním výkonným serverem", + "Síť bez serverů – stanice sdílejí data", + "Jen internetovou síť", "Síť pouze s tiskárnami"}, 4, 1}, + {"Server v síti je:", + {"Jednoduchá stanice pro uživatele", + "Nadřazený uzel poskytující služby", + "Jen aktivní prvek kabeláže", "Dočasné Wi‑Fi připojení"}, 4, 1}, +}; + +const char *net2_hints[] = { + "1 B = 8 b", + "2⁸ = 256 hodnot (0–255)", + "Kabely – síť zůstává připojená i po přenosu", + "WAN = Wide Area Network, např. internet", + "Např. hvězda, kruh, sběrnice", + "Architektura = topologie + standard", + "Bez centrálního serveru – stanice si sdílejí data a tiskárny", + "Server řídí a poskytuje služby, má vyšší výkon", +}; + +void net_mcq_check(GtkButton *button, gpointer data) { + NetMcqCtx *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")); + else + set_feedback(ctx->feedback, FALSE, tr("feedback_retry_short")); +} + +GtkWidget *build_net_unit2_exercise_page(void) { + GtkWidget *page; + GtkWidget *scroll; + GtkWidget *body; + GtkWidget *check; + GtkWidget *btns; + NetMcqCtx *ctx = g_new0(NetMcqCtx, 1); + int n = (int)G_N_ELEMENTS(net2_qs); + int n_opts = net2_qs[0].n_options; + + 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("netunit2", "net_ex2_title", 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, 10); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), body); + + net_heading(body, "Kvíz k základním pojmům sítí"); + net_paragraph(body, + "Vyberte u každé otázky jednu správnou odpověď " + "a stiskněte Zkontrolovat."); + + ctx->qs = net2_qs; + ctx->n = n; + ctx->n_opts = n_opts; + ctx->toggles = g_new0(GtkToggleButton *, n * n_opts); + ctx->hints = g_new0(GtkWidget *, n); + + for (int i = 0; i < n; i++) { + GtkWidget *prompt; + GtkWidget *row; + GtkToggleButton *first = NULL; + char *qtext; + + qtext = g_strdup_printf("%d.) %s", i + 1, net2_qs[i].prompt); + prompt = gtk_label_new(qtext); + g_free(qtext); + gtk_widget_set_halign(prompt, GTK_ALIGN_START); + gtk_label_set_wrap(GTK_LABEL(prompt), TRUE); + gtk_widget_add_css_class(prompt, "ex-prompt"); + gtk_widget_set_margin_top(prompt, 8); + 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 < n_opts; o++) { + GtkWidget *tb = + gtk_toggle_button_new_with_label(net2_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); + } + + ctx->hints[i] = meaning_add(body, net2_hints[i]); + } + + btns = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8); + gtk_widget_set_margin_top(btns, 12); + gtk_box_append(GTK_BOX(body), btns); + + check = gtk_button_new(); + i18n_bind(check, "check", 1); + gtk_widget_add_css_class(check, "pill"); + gtk_widget_set_halign(check, GTK_ALIGN_START); + gtk_box_append(GTK_BOX(btns), check); + g_signal_connect(check, "clicked", G_CALLBACK(net_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; +}