Refactor networks quizzes, progress tracking, and page wiring.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-09-11 18:34:06 +02:00
commit 02c994fb5e
7 changed files with 406 additions and 945 deletions

View file

@ -72,17 +72,17 @@ GTK 4 interface.
8. Přepojování – slides + quiz 8. Přepojování – slides + quiz
9. Vrstevnaté modely a protokoly – slides + quiz 9. Vrstevnaté modely a protokoly – slides + quiz
10. Model ISO/OSI a TCP/IP – slides + quiz 10. Model ISO/OSI a TCP/IP – slides + quiz
Progress for networks is **not** saved yet (unlike Deutsch); the Statistics Progress for networks is saved to `progress/net.conf` (one flag per unit);
page still treats networks as without tracked units completed network units show a checkmark on the path and count in Statistics
- **Progress tracking** – completed exercises are marked green; progress is - **Progress tracking** – completed exercises are marked green; progress is
saved per unit to `progress/unit1.conf`, `progress/unit2.conf` and saved per unit to `progress/unit1.conf`, `progress/unit2.conf` and
`progress/unit3.conf` and restored on startup `progress/unit3.conf` and restored on startup
- **Statistics** – chart icon in the header opens a per-subject progress - **Statistics** – chart icon in the header opens a per-subject progress
overview: summary totals (exercises done, percent, finished units) at the overview: summary totals (exercises done, percent, finished units) at the
top, then every subject with its own progress bar (subjects without tracked top, then every subject with its own progress bar (subjects without tracked
content yet — including networks for now — are shown as locked), and a content yet are shown as locked; **networks** use lesson completion from
per-unit breakdown with progress bars for Deutsch; numbers update as soon `progress/net.conf`), and a per-unit breakdown with progress bars for
as you finish an exercise Deutsch; numbers update as soon as you finish an exercise
- **Settings** – gear icon in the top-right header opens a panel where you can - **Settings** – gear icon in the top-right header opens a panel where you can
choose: choose:
- **mode**: dark or light, as a segmented control - **mode**: dark or light, as a segmented control
@ -295,15 +295,15 @@ When launched, the application shows the welcome screen. From there:
screen. screen.
7. Open **Statistiky / Statistics** (chart icon, top-right) to see progress 7. Open **Statistiky / Statistics** (chart icon, top-right) to see progress
grouped per subject (overall totals plus, for Deutsch, a per-unit grouped per subject (overall totals plus, for Deutsch, a per-unit
breakdown). Network progress is not tracked in Statistics yet. breakdown). Network lesson completion is included in the subject totals.
8. Open **Nastavení / Settings** (gear icon, top-right) to switch dark/light 8. Open **Nastavení / Settings** (gear icon, top-right) to switch dark/light
mode, pick a color theme, or change the interface language. mode, pick a color theme, or change the interface language.
German units 1–3 and network units 1–10 are currently implemented. Locked German units 1–3 and network units 1–10 are currently implemented. Locked
nodes do nothing – they are placeholders until lessons are added. Completed nodes do nothing – they are placeholders until lessons are added. Completed
Deutsch exercises stay green and are saved per unit to `progress/unit1.conf` / Deutsch exercises stay green and are saved per unit to `progress/unit1.conf` /
`progress/unit2.conf` / `progress/unit3.conf` (created next to the app on first `progress/unit2.conf` / `progress/unit3.conf`. Completed network units are
finish). saved to `progress/net.conf`.
#### Keyboard shortcuts #### Keyboard shortcuts
@ -347,6 +347,7 @@ progress/ created at runtime
unit1.conf unit 1 exercise completion state unit1.conf unit 1 exercise completion state
unit2.conf unit 2 exercise completion state unit2.conf unit 2 exercise completion state
unit3.conf unit 3 exercise completion state unit3.conf unit 3 exercise completion state
net.conf computer-networks lesson completion
settings.conf theme, dark/light mode and language preference settings.conf theme, dark/light mode and language preference
``` ```
@ -382,7 +383,7 @@ German in both languages.
- [x] Unit 2 ("Aus aller Welt") – 19 interactive exercises (incl. Hangman) - [x] Unit 2 ("Aus aller Welt") – 19 interactive exercises (incl. Hangman)
- [x] Unit 3 ("Bei uns zu Hause") – 15 interactive exercises - [x] Unit 3 ("Bei uns zu Hause") – 15 interactive exercises
- [x] Computer networks units 1–10 (IP/VLSM + nine theory units with quizzes) - [x] Computer networks units 1–10 (IP/VLSM + nine theory units with quizzes)
- [ ] Progress tracking / statistics for the networks path - [x] Progress tracking / statistics for the networks path
- [ ] Lesson progression and unlock system for the remaining units - [ ] Lesson progression and unlock system for the remaining units
- [ ] Add audio pronunciation features - [ ] Add audio pronunciation features
- [x] Add user settings panel (theme, dark/light mode, language) - [x] Add user settings panel (theme, dark/light mode, language)
@ -466,15 +467,15 @@ moderním GTK 4 rozhraním stylovaným přes CSS.
8. Přepojování – snímky + kvíz 8. Přepojování – snímky + kvíz
9. Vrstevnaté modely a protokoly – snímky + kvíz 9. Vrstevnaté modely a protokoly – snímky + kvíz
10. Model ISO/OSI a TCP/IP – snímky + kvíz 10. Model ISO/OSI a TCP/IP – snímky + kvíz
Postup u sítí se **zatím neukládá** (na rozdíl od Deutsch); ve Statistikách Postup u sítí se ukládá do `progress/net.conf` (příznak na jednotku);
se sítě stále berou jako předmět bez trackovaných jednotek dokončené jednotky mají fajfku na cestě a počítají se ve Statistikách
- **Ukládání postupu** – hotová cvičení zezelenají; stav se ukládá do - **Ukládání postupu** – hotová cvičení zezelenají; stav se ukládá do
`progress/unit1.conf`, `progress/unit2.conf` a `progress/unit3.conf` a při `progress/unit1.conf`, `progress/unit2.conf` a `progress/unit3.conf` a při
startu se načte zpět startu se načte zpět
- **Statistiky** – ikona grafu v hlavičce ukáže postup po předmětech (součty, - **Statistiky** – ikona grafu v hlavičce ukáže postup po předmětech (součty,
procenta, dokončené jednotky), progress bary u každého předmětu (předměty procenta, dokončené jednotky), progress bary u každého předmětu (předměty
bez trackovaného obsahu — včetně sítí — jsou zatím jako zamčené) a rozpad bez trackovaného obsahu jsou zamčené; **sítě** berou dokončení lekcí z
po jednotkách u Deutsch `progress/net.conf`) a rozpad po jednotkách u Deutsch
- **Nastavení** – ozubené kolečko vpravo nahoře: - **Nastavení** – ozubené kolečko vpravo nahoře:
- **režim**: tmavý / světlý - **režim**: tmavý / světlý
- **téma**: 10 barevných palet - **téma**: 10 barevných palet
@ -684,15 +685,16 @@ Po spuštění se zobrazí úvodní obrazovka. Odtud:
Jednotky 11–30 zůstávají zamčené. Jednotky 11–30 zůstávají zamčené.
6. Tlačítkem **zpět** vlevo nahoře se vrátíte na předchozí obrazovku. 6. Tlačítkem **zpět** vlevo nahoře se vrátíte na předchozí obrazovku.
7. **Statistiky** (ikona grafu vpravo nahoře) ukazují postup po předmětech 7. **Statistiky** (ikona grafu vpravo nahoře) ukazují postup po předmětech
(u Deutsch i po jednotkách). Postup sítí se ve Statistikách zatím (u Deutsch i po jednotkách). Dokončené síťové lekce se započítávají do
nesleduje. součů předmětu.
8. **Nastavení** (ozubené kolečko) – tmavý/světlý režim, barevné téma nebo 8. **Nastavení** (ozubené kolečko) – tmavý/světlý režim, barevné téma nebo
jazyk rozhraní. jazyk rozhraní.
Hotové jsou německé jednotky 1–3 a síťové jednotky 1–10. Zamčené uzly nic Hotové jsou německé jednotky 1–3 a síťové jednotky 1–10. Zamčené uzly nic
nedělají – jsou to placeholdery. Dokončená německá cvičení zůstanou zelená a nedělají – jsou to placeholdery. Dokončená německá cvičení zůstanou zelená a
ukládají se do `progress/unit1.conf` / `progress/unit2.conf` / ukládají se do `progress/unit1.conf` / `progress/unit2.conf` /
`progress/unit3.conf` (vzniknou vedle aplikace při prvním dokončení). `progress/unit3.conf`. Dokončené síťové jednotky se ukládají do
`progress/net.conf`.
#### Klávesové zkratky #### Klávesové zkratky
@ -736,6 +738,7 @@ progress/ vzniká za běhu
unit1.conf stav cvičení jednotky 1 unit1.conf stav cvičení jednotky 1
unit2.conf stav cvičení jednotky 2 unit2.conf stav cvičení jednotky 2
unit3.conf stav cvičení jednotky 3 unit3.conf stav cvičení jednotky 3
net.conf dokončení síťových lekcí
settings.conf téma, režim a jazyk settings.conf téma, režim a jazyk
``` ```
@ -768,7 +771,7 @@ rozhraní.
- [x] Jednotka 2 („Aus aller Welt“) – 19 cvičení (včetně Hangmana) - [x] Jednotka 2 („Aus aller Welt“) – 19 cvičení (včetně Hangmana)
- [x] Jednotka 3 („Bei uns zu Hause“) – 15 cvičení - [x] Jednotka 3 („Bei uns zu Hause“) – 15 cvičení
- [x] Počítačové sítě: jednotky 1–10 (IP/VLSM + 9 teoretických s kvízy) - [x] Počítačové sítě: jednotky 1–10 (IP/VLSM + 9 teoretických s kvízy)
- [ ] Ukládání postupu / statistiky pro síťovou cestu - [x] Ukládání postupu / statistiky pro síťovou cestu
- [ ] Postupné odemykání zbývajících jednotek - [ ] Postupné odemykání zbývajících jednotek
- [ ] Audio / výslovnost - [ ] Audio / výslovnost
- [x] Panel nastavení (téma, režim, jazyk) - [x] Panel nastavení (téma, režim, jazyk)

21
i18n.c
View file

@ -253,6 +253,27 @@ const TrEntry tr_ui[] = {
"3 slides about ISO/OSI and TCP/IP, then a quiz."}, "3 slides about ISO/OSI and TCP/IP, then a quiz."},
{"net_ex10_title", "Kvíz: ISO/OSI a TCP/IP", {"net_ex10_title", "Kvíz: ISO/OSI a TCP/IP",
"Quiz: ISO/OSI and TCP/IP"}, "Quiz: ISO/OSI and TCP/IP"},
{"net_quiz_intro",
"Vyberte u každé otázky jednu správnou odpověď a stiskněte Zkontrolovat.",
"Choose one correct answer for each question and press Check."},
{"net_quiz2_head", "Kvíz k základním pojmům sítí",
"Quiz on basic networking concepts"},
{"net_quiz3_head", "Kvíz ke klient–server a cloudu",
"Quiz on client–server and cloud"},
{"net_quiz4_head", "Kvíz k přenosu dat a zabezpečení",
"Quiz on data transmission and security"},
{"net_quiz5_head", "Kvíz k signálům a modulaci",
"Quiz on signals and modulation"},
{"net_quiz6_head", "Kvíz k cestě, kanálu a okruhu",
"Quiz on path, channel and circuit"},
{"net_quiz7_head", "Kvíz k multiplexu a směrům přenosu",
"Quiz on multiplexing and transmission directions"},
{"net_quiz8_head", "Kvíz k přepojování",
"Quiz on switching"},
{"net_quiz9_head", "Kvíz k vrstevnatým modelům a protokolům",
"Quiz on layered models and protocols"},
{"net_quiz10_head", "Kvíz k ISO/OSI a TCP/IP",
"Quiz on ISO/OSI and TCP/IP"},
{"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"},

47
main.c
View file

@ -171,26 +171,33 @@ void activate(GtkApplication *app, gpointer user_data) {
gtk_stack_add_named(main_stack, roadmap_page, "roadmap"); gtk_stack_add_named(main_stack, roadmap_page, "roadmap");
gtk_stack_add_named(main_stack, build_stats_page(), "stats"); gtk_stack_add_named(main_stack, build_stats_page(), "stats");
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_exercise_page(), "netex"); typedef GtkWidget *(*NetBuilder)(void);
gtk_stack_add_named(main_stack, build_net_unit2_page(), "netunit2"); static const struct {
gtk_stack_add_named(main_stack, build_net_unit2_exercise_page(), "netex2"); const char *unit_name;
gtk_stack_add_named(main_stack, build_net_unit3_page(), "netunit3"); const char *ex_name;
gtk_stack_add_named(main_stack, build_net_unit3_exercise_page(), "netex3"); NetBuilder build_unit;
gtk_stack_add_named(main_stack, build_net_unit4_page(), "netunit4"); NetBuilder build_ex;
gtk_stack_add_named(main_stack, build_net_unit4_exercise_page(), "netex4"); } net_pages[] = {
gtk_stack_add_named(main_stack, build_net_unit5_page(), "netunit5"); {"netunit1", "netex1", build_net_unit1_page, build_net_exercise_page},
gtk_stack_add_named(main_stack, build_net_unit5_exercise_page(), "netex5"); {"netunit2", "netex2", build_net_unit2_page, build_net_unit2_exercise_page},
gtk_stack_add_named(main_stack, build_net_unit6_page(), "netunit6"); {"netunit3", "netex3", build_net_unit3_page, build_net_unit3_exercise_page},
gtk_stack_add_named(main_stack, build_net_unit6_exercise_page(), "netex6"); {"netunit4", "netex4", build_net_unit4_page, build_net_unit4_exercise_page},
gtk_stack_add_named(main_stack, build_net_unit7_page(), "netunit7"); {"netunit5", "netex5", build_net_unit5_page, build_net_unit5_exercise_page},
gtk_stack_add_named(main_stack, build_net_unit7_exercise_page(), "netex7"); {"netunit6", "netex6", build_net_unit6_page, build_net_unit6_exercise_page},
gtk_stack_add_named(main_stack, build_net_unit8_page(), "netunit8"); {"netunit7", "netex7", build_net_unit7_page, build_net_unit7_exercise_page},
gtk_stack_add_named(main_stack, build_net_unit8_exercise_page(), "netex8"); {"netunit8", "netex8", build_net_unit8_page, build_net_unit8_exercise_page},
gtk_stack_add_named(main_stack, build_net_unit9_page(), "netunit9"); {"netunit9", "netex9", build_net_unit9_page, build_net_unit9_exercise_page},
gtk_stack_add_named(main_stack, build_net_unit9_exercise_page(), "netex9"); {"netunit10", "netex10", build_net_unit10_page, build_net_unit10_exercise_page},
gtk_stack_add_named(main_stack, build_net_unit10_page(), "netunit10"); };
gtk_stack_add_named(main_stack, build_net_unit10_exercise_page(), "netex10");
for (guint i = 0; i < G_N_ELEMENTS(net_pages); i++) {
gtk_stack_add_named(main_stack, net_pages[i].build_unit(),
net_pages[i].unit_name);
gtk_stack_add_named(main_stack, net_pages[i].build_ex(),
net_pages[i].ex_name);
}
}
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

@ -18,6 +18,7 @@
#define PROGRESS_U2 "progress/unit2.conf" #define PROGRESS_U2 "progress/unit2.conf"
#define PROGRESS_U3 "progress/unit3.conf" #define PROGRESS_U3 "progress/unit3.conf"
#define SETTINGS_FILE "progress/settings.conf" #define SETTINGS_FILE "progress/settings.conf"
#define PROGRESS_NET "progress/net.conf"
#define NODE_SIZE 88.0 #define NODE_SIZE 88.0
#define PATH_SPAC 240.0 #define PATH_SPAC 240.0
#define ROAD_MX 150.0 /* horizontal canvas margin */ #define ROAD_MX 150.0 /* horizontal canvas margin */
@ -44,7 +45,6 @@
#define HM_ROW3 19 /* start of the ZXCV row */ #define HM_ROW3 19 /* start of the ZXCV row */
#define HM_ROW4 26 /* start of the umlaut row */ #define HM_ROW4 26 /* start of the umlaut row */
#define NET_UNITS 30 #define NET_UNITS 30
#define NET_GRID_COLS 6
#define NET_SLIDES 4 #define NET_SLIDES 4
#define NET2_SLIDES 5 #define NET2_SLIDES 5
#define NET3_SLIDES 4 #define NET3_SLIDES 4
@ -487,6 +487,11 @@ typedef struct {
guint n_slides; guint n_slides;
const char *unit_page; const char *unit_page;
const char *ex_page; const char *ex_page;
GArray *note_kicks;
GArray *note_titles;
GArray *note_bodies;
GtkWidget *done_icon;
gboolean done;
} NetLesson; } NetLesson;
typedef struct { typedef struct {
const char *kicker; const char *kicker;
@ -498,6 +503,7 @@ typedef struct {
const ChoiceQ *qs; const ChoiceQ *qs;
int n; int n;
int n_opts; int n_opts;
int lesson_id;
GtkWidget *feedback; GtkWidget *feedback;
GtkToggleButton **toggles; GtkToggleButton **toggles;
GtkWidget **hints; GtkWidget **hints;
@ -663,9 +669,6 @@ extern const U3Kw ex14_u3_qs[];
extern const U3Let ex15_u3_rows[]; extern const U3Let ex15_u3_rows[];
extern NetLesson net_lessons[NET_LESSONS]; extern NetLesson net_lessons[NET_LESSONS];
extern GtkWidget *net_note_host; extern GtkWidget *net_note_host;
extern GArray *net_note_kicks;
extern GArray *net_note_titles;
extern GArray *net_note_bodies;
extern int net_note_last_w; extern int net_note_last_w;
extern GtkWidget *net_scroll; extern GtkWidget *net_scroll;
extern GtkWidget *net_fixed; extern GtkWidget *net_fixed;
@ -700,7 +703,6 @@ 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_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);
@ -738,6 +740,11 @@ void load_progress(void);
void all_rails_redraw(void); void all_rails_redraw(void);
void refresh_completion_ui(void); void refresh_completion_ui(void);
void mark_done(UnitCtx *u, int n); void mark_done(UnitCtx *u, int n);
void net_load_progress(void);
void net_save_progress(void);
void mark_net_done(int lesson_id);
void refresh_net_completion_ui(void);
void progress_for_net(ProgressSum *out);
GtkWidget *make_back_button(const char *target); GtkWidget *make_back_button(const char *target);
void on_nav_clicked(GtkButton *button, gpointer user_data); void on_nav_clicked(GtkButton *button, gpointer user_data);
int unit_done_count(const UnitCtx *u); int unit_done_count(const UnitCtx *u);
@ -1035,6 +1042,9 @@ 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); void net_mcq_check(GtkButton *button, gpointer data);
GtkWidget *build_net_mcq_page(int lesson_id, const char *back_page,
const char *title_key, const char *heading_key,
const ChoiceQ *qs, const char **hints, int n);
GtkWidget *build_net_unit2_exercise_page(void); GtkWidget *build_net_unit2_exercise_page(void);
GtkWidget *build_net_unit3_exercise_page(void); GtkWidget *build_net_unit3_exercise_page(void);
GtkWidget *build_net_unit4_exercise_page(void); GtkWidget *build_net_unit4_exercise_page(void);

1126
net.c

File diff suppressed because it is too large Load diff

View file

@ -52,6 +52,7 @@ void unit_load_progress(UnitCtx *u) {
void load_progress(void) { void load_progress(void) {
for (int i = 0; i < NUM_UNLOCKED; i++) for (int i = 0; i < NUM_UNLOCKED; i++)
unit_load_progress(&units[i]); unit_load_progress(&units[i]);
net_load_progress();
} }
/* Redraw the exercise rails of every unit that already has a page. */ /* Redraw the exercise rails of every unit that already has a page. */
@ -122,6 +123,7 @@ void refresh_completion_ui(void) {
} }
all_rails_redraw(); all_rails_redraw();
refresh_net_completion_ui();
refresh_stats_ui(); refresh_stats_ui();
} }
@ -132,3 +134,94 @@ void mark_done(UnitCtx *u, int n) {
refresh_completion_ui(); refresh_completion_ui();
unit_save_progress(u); unit_save_progress(u);
} }
void net_load_progress(void) {
GKeyFile *kf = g_key_file_new();
GError *err = NULL;
if (!g_key_file_load_from_file(kf, PROGRESS_NET, G_KEY_FILE_NONE, &err)) {
if (err)
g_error_free(err);
g_key_file_free(kf);
return;
}
for (int i = 0; i < NET_LESSONS; i++) {
gchar key[8];
g_snprintf(key, sizeof(key), "%d", i + 1);
net_lessons[i].done = g_key_file_get_boolean(kf, "done", key, NULL);
}
g_key_file_free(kf);
}
void net_save_progress(void) {
GKeyFile *kf = g_key_file_new();
for (int i = 0; i < NET_LESSONS; i++) {
gchar key[8];
g_snprintf(key, sizeof(key), "%d", i + 1);
g_key_file_set_boolean(kf, "done", key, net_lessons[i].done);
}
if (g_mkdir_with_parents(PROGRESS_DIR, 0755) != 0) {
g_key_file_free(kf);
return;
}
gchar *data = g_key_file_to_data(kf, NULL, NULL);
g_key_file_free(kf);
if (data) {
GError *err = NULL;
g_file_set_contents(PROGRESS_NET, data, -1, &err);
if (err)
g_error_free(err);
g_free(data);
}
}
void refresh_net_completion_ui(void) {
for (int i = 0; i < NET_LESSONS; i++) {
GtkWidget *node = net_nodes[i];
GtkWidget *icon = net_lessons[i].done_icon;
if (!node)
continue;
if (net_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);
}
}
}
void mark_net_done(int lesson_id) {
if (lesson_id < 0 || lesson_id >= NET_LESSONS)
return;
if (net_lessons[lesson_id].done)
return;
net_lessons[lesson_id].done = TRUE;
net_save_progress();
refresh_net_completion_ui();
refresh_stats_ui();
}
void progress_for_net(ProgressSum *out) {
out->total_ex += NET_LESSONS;
out->open_units += NET_LESSONS;
for (int i = 0; i < NET_LESSONS; i++) {
if (net_lessons[i].done) {
out->done_ex++;
out->done_units++;
}
}
}

17
stats.c
View file

@ -53,8 +53,12 @@ void refresh_stats_ui(void) {
if (!stats_ui.ex_value) if (!stats_ui.ex_value)
return; return;
for (int s = 0; s < NUM_SUBJECTS; s++) for (int s = 0; s < NUM_SUBJECTS; s++) {
if (s == NET_SUBJ)
progress_for_net(&all);
else
progress_for_units(sub_unit_start[s], sub_unit_count[s], &all); progress_for_units(sub_unit_start[s], sub_unit_count[s], &all);
}
pct = all.total_ex > 0 ? (all.done_ex * 100) / all.total_ex : 0; pct = all.total_ex > 0 ? (all.done_ex * 100) / all.total_ex : 0;
@ -79,11 +83,15 @@ void refresh_stats_ui(void) {
GtkWidget *count = stats_ui.subj[s].count; GtkWidget *count = stats_ui.subj[s].count;
GtkWidget *bar = stats_ui.subj[s].bar; GtkWidget *bar = stats_ui.subj[s].bar;
ProgressSum sp = {0}; ProgressSum sp = {0};
gboolean has_content = (s == NET_SUBJ) || sub_unit_count[s] > 0;
if (!count || !bar) if (!count || !bar)
continue; continue;
if (s == NET_SUBJ)
progress_for_net(&sp);
else
progress_for_units(sub_unit_start[s], sub_unit_count[s], &sp); progress_for_units(sub_unit_start[s], sub_unit_count[s], &sp);
if (sub_unit_count[s] > 0) { if (has_content) {
tmp = g_strdup_printf(tr("stats_ex_fmt"), sp.done_ex, sp.total_ex); tmp = g_strdup_printf(tr("stats_ex_fmt"), sp.done_ex, sp.total_ex);
gtk_label_set_text(GTK_LABEL(count), tmp); gtk_label_set_text(GTK_LABEL(count), tmp);
g_free(tmp); g_free(tmp);
@ -345,7 +353,8 @@ GtkWidget *build_stats_page(void) {
GtkWidget *name; GtkWidget *name;
GtkWidget *count; GtkWidget *count;
GtkWidget *bar; GtkWidget *bar;
gboolean has_units = sub_unit_count[s] > 0; gboolean has_units = (s == NET_SUBJ) || sub_unit_count[s] > 0;
gboolean has_deutsch_units = sub_unit_count[s] > 0;
subject = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); subject = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_add_css_class(subject, "stats-subject"); gtk_widget_add_css_class(subject, "stats-subject");
@ -379,7 +388,7 @@ GtkWidget *build_stats_page(void) {
gtk_box_append(GTK_BOX(subject), bar); gtk_box_append(GTK_BOX(subject), bar);
stats_ui.subj[s].bar = bar; stats_ui.subj[s].bar = bar;
if (has_units) { if (has_deutsch_units) {
GtkWidget *units_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); GtkWidget *units_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_add_css_class(units_box, "stats-units"); gtk_widget_add_css_class(units_box, "stats-units");