Add networks unit 2: basic concepts slides and MCQ quiz.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-09-11 17:53:32 +02:00
commit f3347e7dc3
4 changed files with 429 additions and 121 deletions

24
i18n.c
View file

@ -199,6 +199,12 @@ const TrEntry tr_ui[] = {
"4 slides with the method, then an exercise."}, "4 slides with the method, then an exercise."},
{"net_ex_title", "Cvičení: výpočet IP adres", {"net_ex_title", "Cvičení: výpočet IP adres",
"Exercise: IP address calculation"}, "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_next", "Další", "Next"},
{"net_slide_prev", "Zpět", "Back"}, {"net_slide_prev", "Zpět", "Back"},
{"net_slide_start", "Přejít na cvičení", "Go to the exercise"}, {"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)"}, {"„siebzehn“ = sedmnáct (17)", NULL, "“siebzehn” = seventeen (17)"},
{"„zwanzig“ = dvacet (20)", NULL, "“zwanzig” = twenty (20)"}, {"„zwanzig“ = dvacet (20)", NULL, "“zwanzig” = twenty (20)"},
{"„sechs“ = šest (6)", NULL, "“sechs” = six (6)"}, {"„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"}, {"čtrnáct", NULL, "fourteen"},
{"sedmnáct", NULL, "seventeen"}, {"sedmnáct", NULL, "seventeen"},
{"dvacet", NULL, "twenty"}, {"dvacet", NULL, "twenty"},
@ -807,7 +827,7 @@ void refresh_welcome_heading(void) {
} }
void refresh_stats_ui(void); void refresh_stats_ui(void);
void net_slide_apply(void); void net_lessons_apply_lang(void);
void net_rail_theme_reset(void); void net_rail_theme_reset(void);
void apply_language(void) { void apply_language(void) {
@ -818,5 +838,5 @@ void apply_language(void) {
i18n_apply_one(&g_array_index(i18n_binds, I18nBind, i)); i18n_apply_one(&g_array_index(i18n_binds, I18nBind, i));
refresh_welcome_heading(); refresh_welcome_heading();
refresh_stats_ui(); refresh_stats_ui();
net_slide_apply(); net_lessons_apply_lang();
} }

2
main.c
View file

@ -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_netmap_page(), "netmap");
gtk_stack_add_named(main_stack, build_net_unit1_page(), "netunit1"); 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_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++) { for (int i = 0; i < NUM_UNLOCKED; i++) {
UnitCtx *u = &units[i]; UnitCtx *u = &units[i];

View file

@ -46,6 +46,8 @@
#define NET_UNITS 30 #define NET_UNITS 30
#define NET_GRID_COLS 6 #define NET_GRID_COLS 6
#define NET_SLIDES 4 #define NET_SLIDES 4
#define NET2_SLIDES 5
#define NET_LESSONS 2
typedef enum { typedef enum {
THEME_CATPPUCCIN = 0, THEME_CATPPUCCIN = 0,
@ -469,6 +471,29 @@ typedef struct {
int id; int id;
gboolean infeasible; gboolean infeasible;
} NetQz; } 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 GtkStack *main_stack;
extern GtkWindow *main_window; extern GtkWindow *main_window;
@ -628,10 +653,7 @@ extern const char *ex13_u3_pool[];
extern const U3Fill ex13_u3_rows[]; extern const U3Fill ex13_u3_rows[];
extern const U3Kw ex14_u3_qs[]; extern const U3Kw ex14_u3_qs[];
extern const U3Let ex15_u3_rows[]; extern const U3Let ex15_u3_rows[];
extern GtkWidget *net_slide_stack; extern NetLesson net_lessons[NET_LESSONS];
extern GtkWidget *net_prev_btn;
extern GtkWidget *net_next_btn;
extern guint net_slide_idx;
extern GtkWidget *net_note_host; extern GtkWidget *net_note_host;
extern GArray *net_note_kicks; extern GArray *net_note_kicks;
extern GArray *net_note_titles; 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 i18n_bind(GtkWidget *widget, const char *key, int kind);
void refresh_welcome_heading(void); void refresh_welcome_heading(void);
void refresh_stats_ui(void); void refresh_stats_ui(void);
void net_slide_apply(void); void net_lessons_apply_lang(void);
void net_rail_theme_reset(void); void net_rail_theme_reset(void);
void apply_language(void); void apply_language(void);
char *read_css_file(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_kicker(GtkWidget *box, const char *text);
void net_note_title(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_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_open_unit(GtkButton *button, gpointer data);
void net_slide_prev(GtkButton *button, gpointer data); void net_slide_prev(GtkButton *button, gpointer data);
void net_slide_next(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_rail_theme_reset(void);
void net_add_node(GtkFixed *fixed, int index); void net_add_node(GtkFixed *fixed, int index);
GtkWidget *build_netmap_page(void); 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_unit1_page(void);
GtkWidget *build_net_unit2_page(void);
GtkWidget *net_qz_combo(void); GtkWidget *net_qz_combo(void);
void net_qz_set_prefix(GtkComboBoxText *c, int pfx); void net_qz_set_prefix(GtkComboBoxText *c, int pfx);
int net_qz_prefix(GtkComboBoxText *c); 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); void net_qz_header(GtkWidget *box, const char *text);
GtkWidget *net_qz_make_entry(void); GtkWidget *net_qz_make_entry(void);
GtkWidget *build_net_exercise_page(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); void activate(GtkApplication *app, gpointer user_data);
int main(int argc, char **argv); int main(int argc, char **argv);

484
net.c
View file

@ -3,10 +3,10 @@
/* ---- Computer networks: subject path, lesson & exercise ------------ */ /* ---- Computer networks: subject path, lesson & exercise ------------ */
GtkWidget *net_slide_stack; NetLesson net_lessons[NET_LESSONS] = {
GtkWidget *net_prev_btn; { .n_slides = NET_SLIDES, .unit_page = "netunit1", .ex_page = "netex" },
GtkWidget *net_next_btn; { .n_slides = NET2_SLIDES, .unit_page = "netunit2", .ex_page = "netex2" },
guint net_slide_idx; };
void net_paragraph(GtkWidget *box, const char *text) { void net_paragraph(GtkWidget *box, const char *text) {
GtkWidget *l = gtk_label_new(text); GtkWidget *l = gtk_label_new(text);
@ -34,6 +34,14 @@ GArray *net_note_titles;
GArray *net_note_bodies; GArray *net_note_bodies;
int net_note_last_w = -1; 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) { void net_note_font(GtkWidget *l, int px) {
PangoAttrList *attrs = pango_attr_list_new(); 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 w = net_note_host ? gtk_widget_get_allocated_width(net_note_host) : 0;
int body, head, kick; int body, head, kick;
if (!net_note_kicks)
return;
if (w < 160) if (w < 160)
w = 160; w = 160;
body = w / 46; 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); g_array_append_val(net_note_bodies, l);
} }
void net_slide_apply(void) { void net_slide_apply(NetLesson *L) {
char name[16]; char name[16];
if (!net_slide_stack) if (!L || !L->stack)
return; return;
g_snprintf(name, sizeof(name), "net_slide%u", net_slide_idx); g_snprintf(name, sizeof(name), "net_slide%u", L->idx);
gtk_stack_set_visible_child_name(GTK_STACK(net_slide_stack), name); gtk_stack_set_visible_child_name(GTK_STACK(L->stack), name);
if (net_prev_btn) { if (L->prev_btn) {
gtk_button_set_label(GTK_BUTTON(net_prev_btn), tr("net_slide_prev")); gtk_button_set_label(GTK_BUTTON(L->prev_btn), tr("net_slide_prev"));
gtk_widget_set_sensitive(net_prev_btn, net_slide_idx > 0); gtk_widget_set_sensitive(L->prev_btn, L->idx > 0);
} }
if (net_next_btn) { if (L->next_btn) {
gtk_button_set_label( gtk_button_set_label(
GTK_BUTTON(net_next_btn), GTK_BUTTON(L->next_btn),
net_slide_idx + 1 >= NET_SLIDES ? tr("net_slide_start") L->idx + 1 >= L->n_slides ? tr("net_slide_start")
: tr("net_slide_next")); : 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) { void net_open_unit(GtkButton *button, gpointer data) {
NetLesson *L = data;
(void)button; (void)button;
(void)data; if (!L)
net_slide_idx = 0; return;
net_slide_apply(); L->idx = 0;
gtk_stack_set_visible_child_name(main_stack, "netunit1"); net_slide_apply(L);
gtk_stack_set_visible_child_name(main_stack, L->unit_page);
} }
void net_slide_prev(GtkButton *button, gpointer data) { void net_slide_prev(GtkButton *button, gpointer data) {
NetLesson *L = data;
(void)button; (void)button;
(void)data; if (!L)
if (net_slide_idx > 0) { return;
net_slide_idx--; if (L->idx > 0) {
net_slide_apply(); L->idx--;
net_slide_apply(L);
} }
} }
void net_slide_next(GtkButton *button, gpointer data) { void net_slide_next(GtkButton *button, gpointer data) {
NetLesson *L = data;
(void)button; (void)button;
(void)data; if (!L)
if (net_slide_idx + 1 < NET_SLIDES) { return;
net_slide_idx++; if (L->idx + 1 < L->n_slides) {
net_slide_apply(); L->idx++;
net_slide_apply(L);
} else { } 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) { void net_add_node(GtkFixed *fixed, int index) {
int num = index + 1; int num = index + 1;
gboolean locked = index != 0; gboolean locked = index >= NET_LESSONS;
GtkWidget *card; GtkWidget *card;
GtkWidget *vbox; GtkWidget *vbox;
GtkWidget *number; GtkWidget *number;
GtkWidget *icon; GtkWidget *icon;
GtkWidget *name; GtkWidget *name;
char *text; char *text;
static const char *unit_keys[NET_LESSONS] = { "net_unit1", "net_unit2" };
card = gtk_button_new(); card = gtk_button_new();
gtk_widget_add_css_class(card, "unit-node"); 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); icon = icon_area_new(draw_check_icon, 0.1176, 0.1176, 0.1804, 18);
gtk_widget_set_visible(icon, FALSE); gtk_widget_set_visible(icon, FALSE);
gtk_box_append(GTK_BOX(vbox), icon); 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; 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_justify(GTK_LABEL(name), GTK_JUSTIFY_CENTER);
gtk_label_set_wrap(GTK_LABEL(name), TRUE); gtk_label_set_wrap(GTK_LABEL(name), TRUE);
gtk_widget_add_css_class(name, "unit-name"); gtk_widget_add_css_class(name, "unit-name");
if (index == 0) { if (index < NET_LESSONS) {
i18n_bind(name, "net_unit1", 0); i18n_bind(name, unit_keys[index], 0);
} else { } else {
gtk_widget_add_css_class(name, "unit-name-locked"); gtk_widget_add_css_class(name, "unit-name-locked");
} }
@ -522,13 +548,86 @@ GtkWidget *build_netmap_page(void) {
return page; 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) { 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] = { static const NetSlide slides[NET_SLIDES] = {
{ {
"1 / 4 • VLSM", "Podsíťování sítí", "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 *)); return build_net_unit_page(&net_lessons[0], "net_unit1", slides, NET_SLIDES);
net_note_titles = g_array_new(FALSE, FALSE, sizeof(GtkWidget *)); }
net_note_bodies = g_array_new(FALSE, FALSE, sizeof(GtkWidget *));
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); GtkWidget *build_net_unit2_page(void) {
gtk_widget_set_margin_start(page, 28); static const NetSlide slides[NET2_SLIDES] = {
gtk_widget_set_margin_end(page, 28); {
gtk_widget_set_margin_top(page, 20); "1 / 5 • Jednotky", "Bit, byte a počítačová síť",
gtk_widget_set_margin_bottom(page, 20); "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), return build_net_unit_page(&net_lessons[1], "net_unit2", slides, NET2_SLIDES);
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;
} }
/* Solutions below are the canonical "largest subnet first, in order A–D" /* Solutions below are the canonical "largest subnet first, in order A–D"
@ -1015,3 +1104,172 @@ GtkWidget *build_net_exercise_page(void) {
return page; 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;
}