Graduately/src/obcanka3.c
pepaskopec-blip 0ee2f8e960 Add year-3 civics lessons on the individual and the law.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-02 22:52:39 +02:00

1910 lines
66 KiB
C

#include "graduately.h"
/* Year 3 of Občanská nauka.
* Thematic unit "Člověk a právo" from the MŠMT model syllabus
* (č.j. 18 396/2002-23) and the SOV civic framework, which schools place
* in the third year. The economy and philosophy stay in the fourth year. */
#define ON3_ENTRY(n) \
{ .n_slides = 2, .unit_page = "on3unit" #n, .ex_page = "on3ex" #n }
NetLesson on3_lessons[ON3_LESSONS] = {
ON3_ENTRY(1),
ON3_ENTRY(2),
ON3_ENTRY(3),
ON3_ENTRY(4),
ON3_ENTRY(5),
ON3_ENTRY(6),
ON3_ENTRY(7),
ON3_ENTRY(8),
ON3_ENTRY(9),
ON3_ENTRY(10),
ON3_ENTRY(11),
ON3_ENTRY(12),
ON3_ENTRY(13),
ON3_ENTRY(14),
ON3_ENTRY(15),
ON3_ENTRY(16),
};
#undef ON3_ENTRY
GtkWidget *on3_scroll;
GtkWidget *on3_fixed;
GtkWidget *on3_rail;
GtkWidget *on3_nodes[ON3_UNITS];
GtkWidget *on3_labels[ON3_UNITS];
double on3_cx[ON3_UNITS];
double on3_cy[ON3_UNITS];
int on3_cols = 1;
int on3_rows = 1;
int on3_cw = (int)(2.0 * ROAD_MX + (ON3_UNITS - 1) * PATH_SPAC);
int on3_ch = (int)(2.0 * ROAD_MY);
guint on3_idle;
double on3_last_avail = -1.0;
int on3_last_cols = -1;
int on3_last_rows = -1;
int on3_last_cw = -1;
int on3_last_ch = -1;
cairo_surface_t *on3_rail_cache;
int on3_cache_w;
int on3_cache_h;
static void on3_point(double t, double *ox, double *oy) {
int k = (int)t;
double u = t - (double)k;
double u2, u3;
double p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y;
if (k < 0) { k = 0; u = 0.0; }
if (k >= ON3_UNITS - 1) { k = ON3_UNITS - 2; u = 1.0; }
p1x = on3_cx[k]; p1y = on3_cy[k];
p2x = on3_cx[k + 1]; p2y = on3_cy[k + 1];
if (k - 1 >= 0) { p0x = on3_cx[k - 1]; p0y = on3_cy[k - 1]; }
else { p0x = p1x - (p2x - p1x); p0y = p1y - (p2y - p1y); }
if (k + 2 < ON3_UNITS) { p3x = on3_cx[k + 2]; p3y = on3_cy[k + 2]; }
else { p3x = p2x + (p2x - p1x); p3y = p2y + (p2y - p1y); }
u2 = u * u;
u3 = u2 * u;
*ox = 0.5 * (2.0 * p1x + (-p0x + p2x) * u
+ (2.0 * p0x - 5.0 * p1x + 4.0 * p2x - p3x) * u2
+ (-p0x + 3.0 * p1x - 3.0 * p2x + p3x) * u3);
*oy = 0.5 * (2.0 * p1y + (-p0y + p2y) * u
+ (2.0 * p0y - 5.0 * p1y + 4.0 * p2y - p3y) * u2
+ (-p0y + 3.0 * p1y - 3.0 * p2y + p3y) * u3);
}
static void on3_path(cairo_t *cr, double t0, double t1) {
const double steps_per_unit = 24.0;
int n = (int)((t1 - t0) * steps_per_unit);
double x, y;
int s;
if (n < 1)
n = 1;
on3_point(t0, &x, &y);
cairo_move_to(cr, x, y);
for (s = 1; s <= n; s++) {
on3_point(t0 + (t1 - t0) * (double)s / (double)n, &x, &y);
cairo_line_to(cr, x, y);
}
}
static void on3_geometry(double avail) {
const double phase = 2.0 * G_PI * 1.7 / (double)(ON3_UNITS - 1);
int rows;
int r, c, i;
for (rows = 1; rows <= ON3_UNITS; rows++) {
int cols = (ON3_UNITS + rows - 1) / rows;
if (2.0 * ROAD_MX + (cols - 1) * PATH_SPAC <= avail + 1.0)
break;
}
if (rows > ON3_UNITS)
rows = ON3_UNITS;
on3_rows = rows;
on3_cols = (ON3_UNITS + rows - 1) / rows;
if (on3_cols < 1)
on3_cols = 1;
on3_cw = (int)(2.0 * ROAD_MX + (on3_cols - 1) * PATH_SPAC);
on3_ch = (int)(2.0 * ROAD_MY + (rows - 1) * ROAD_GAP);
for (r = 0; r < rows; r++) {
int base = r * on3_cols;
int len = MIN(on3_cols, ON3_UNITS - base);
int fwd = (r % 2) == 0;
for (c = 0; c < len; c++) {
i = base + c;
int cc = fwd ? c : (on3_cols - 1 - c);
on3_cx[i] = ROAD_MX + cc * PATH_SPAC;
on3_cy[i] = ROAD_MY + r * ROAD_GAP
+ ROAD_WAVE * sin((double)i * phase);
}
}
}
static void on3_apply_layout(void) {
if (!on3_fixed)
return;
if (on3_cols == on3_last_cols && on3_rows == on3_last_rows &&
on3_cw == on3_last_cw && on3_ch == on3_last_ch)
return;
on3_last_cols = on3_cols;
on3_last_rows = on3_rows;
on3_last_cw = on3_cw;
on3_last_ch = on3_ch;
if (on3_rail_cache) {
cairo_surface_destroy(on3_rail_cache);
on3_rail_cache = NULL;
}
for (int i = 0; i < ON3_UNITS; i++) {
if (!on3_nodes[i] || !on3_labels[i])
continue;
gtk_fixed_move(GTK_FIXED(on3_fixed), on3_nodes[i],
(int)(on3_cx[i] - NODE_SIZE / 2.0),
(int)(on3_cy[i] - NODE_SIZE / 2.0));
gtk_fixed_move(GTK_FIXED(on3_fixed), on3_labels[i],
(int)(on3_cx[i] - (PATH_SPAC - 20.0) / 2.0),
(int)(on3_cy[i] + NODE_SIZE / 2.0 + 10.0));
}
gtk_widget_set_size_request(on3_fixed, on3_cw, on3_ch);
gtk_widget_set_size_request(on3_rail, on3_cw, on3_ch);
gtk_fixed_move(GTK_FIXED(on3_fixed), on3_rail, 0, 0);
gtk_widget_queue_draw(on3_rail);
}
static void on3_relayout(void) {
GtkAdjustment *hadj;
double avail;
if (!on3_scroll)
return;
hadj = gtk_scrolled_window_get_hadjustment(
GTK_SCROLLED_WINDOW(on3_scroll));
avail = gtk_adjustment_get_page_size(hadj);
if (avail < 1.0)
return;
if (fabs(avail - on3_last_avail) < 1.0)
return;
on3_last_avail = avail;
on3_geometry(avail);
on3_apply_layout();
}
static gboolean on3_relayout_idle(gpointer data) {
(void)data;
on3_idle = 0;
on3_relayout();
return G_SOURCE_REMOVE;
}
static void on3_relayout_later(void) {
if (on3_idle == 0)
on3_idle = g_idle_add(on3_relayout_idle, NULL);
}
static void on3_adjust_notify(GtkAdjustment *adj, GParamSpec *ps,
gpointer data) {
(void)adj;
(void)ps;
(void)data;
on3_relayout_later();
}
static void on3_render_rail_to(cairo_t *cr) {
const double t_end = (double)(ON3_UNITS - 1);
const Rgb rail = color_from_hex(app_theme.rail);
const Rgb muted = color_from_hex(app_theme.subtext);
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_paint(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
cairo_new_path(cr);
on3_path(cr, 0.0, t_end);
cairo_set_line_width(cr, 12);
cairo_set_source_rgb(cr, rail.r, rail.g, rail.b);
cairo_stroke(cr);
cairo_set_source_rgba(cr, muted.r, muted.g, muted.b, 0.17);
cairo_set_dash(cr, (double[]){2.0, 40.0}, 2, 0.0);
cairo_new_path(cr);
on3_path(cr, 0.0, t_end);
cairo_stroke(cr);
cairo_set_dash(cr, NULL, 0, 0.0);
}
static void on3_draw_rail(GtkDrawingArea *area, cairo_t *cr,
int width, int height, gpointer data) {
(void)area;
(void)width;
(void)height;
(void)data;
if (!on3_rail_cache || on3_cache_w != on3_cw || on3_cache_h != on3_ch) {
cairo_t *rcr;
if (on3_rail_cache)
cairo_surface_destroy(on3_rail_cache);
on3_cache_w = on3_cw;
on3_cache_h = on3_ch;
if (on3_cache_w < 1)
on3_cache_w = 1;
if (on3_cache_h < 1)
on3_cache_h = 1;
on3_rail_cache = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
on3_cache_w, on3_cache_h);
rcr = cairo_create(on3_rail_cache);
on3_render_rail_to(rcr);
cairo_destroy(rcr);
}
cairo_set_source_surface(cr, on3_rail_cache, 0, 0);
cairo_paint(cr);
}
void on3_rail_theme_reset(void) {
if (on3_rail_cache) {
cairo_surface_destroy(on3_rail_cache);
on3_rail_cache = NULL;
}
if (on3_rail)
gtk_widget_queue_draw(on3_rail);
}
static void on3_open_unit(GtkButton *button, gpointer data) {
NetLesson *L = data;
(void)button;
if (!L)
return;
L->idx = 0;
if (L->stack) {
char name[16];
g_snprintf(name, sizeof(name), "on3_slide%u", L->idx);
gtk_stack_set_visible_child_name(GTK_STACK(L->stack), name);
if (L->prev_btn)
gtk_widget_set_sensitive(L->prev_btn, FALSE);
if (L->next_btn)
gtk_button_set_label(GTK_BUTTON(L->next_btn), tr("net_slide_next"));
}
gtk_stack_set_visible_child_name(main_stack, L->unit_page);
}
static void on3_add_node(GtkFixed *fixed, int index) {
int num = index + 1;
gboolean locked = index >= ON3_LESSONS;
GtkWidget *card;
GtkWidget *vbox;
GtkWidget *number;
GtkWidget *icon;
GtkWidget *name;
char *text;
static char unit_keys[ON3_LESSONS][16];
static gboolean unit_keys_ready = FALSE;
if (!unit_keys_ready) {
for (int i = 0; i < ON3_LESSONS; i++)
g_snprintf(unit_keys[i], sizeof unit_keys[i], "on3_unit%d", i + 1);
unit_keys_ready = TRUE;
}
card = gtk_button_new();
gtk_widget_add_css_class(card, "unit-node");
gtk_widget_set_can_focus(card, FALSE);
gtk_widget_set_sensitive(card, !locked);
gtk_widget_set_size_request(card, (int)NODE_SIZE, (int)NODE_SIZE);
if (locked)
gtk_widget_add_css_class(card, "locked");
else
gtk_widget_add_css_class(card, "current");
vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
gtk_widget_set_halign(vbox, GTK_ALIGN_CENTER);
gtk_widget_set_valign(vbox, GTK_ALIGN_CENTER);
gtk_button_set_child(GTK_BUTTON(card), vbox);
text = g_strdup_printf("%d", num);
number = gtk_label_new(text);
g_free(text);
gtk_widget_add_css_class(number, "unit-number");
gtk_box_append(GTK_BOX(vbox), number);
if (locked) {
icon = icon_area_new(draw_lock_icon, 0.3451, 0.3569, 0.4392, 16);
gtk_box_append(GTK_BOX(vbox), icon);
} else {
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);
on3_lessons[index].done_icon = icon;
g_signal_connect(card, "clicked", G_CALLBACK(on3_open_unit),
&on3_lessons[index]);
}
on3_nodes[index] = card;
gtk_fixed_put(fixed, card, 0, 0);
name = gtk_label_new(NULL);
gtk_widget_set_size_request(name, (int)(PATH_SPAC - 20.0), -1);
gtk_widget_set_halign(name, GTK_ALIGN_CENTER);
gtk_label_set_justify(GTK_LABEL(name), GTK_JUSTIFY_CENTER);
gtk_label_set_wrap(GTK_LABEL(name), TRUE);
gtk_widget_add_css_class(name, "unit-name");
if (index < ON3_LESSONS) {
i18n_bind(name, unit_keys[index], 0);
} else {
gtk_widget_add_css_class(name, "unit-name-locked");
}
on3_labels[index] = name;
gtk_fixed_put(fixed, name, 0, 0);
}
GtkWidget *build_on3map_page(void) {
GtkWidget *page;
GtkWidget *scroll;
GtkWidget *wrap;
GtkWidget *fixed;
GtkWidget *rail;
GtkAdjustment *ha;
GtkAdjustment *va;
on3_geometry(1000.0);
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_hexpand(page, TRUE);
gtk_widget_set_vexpand(page, TRUE);
gtk_widget_set_margin_start(page, 32);
gtk_widget_set_margin_end(page, 32);
gtk_widget_set_margin_top(page, 24);
gtk_widget_set_margin_bottom(page, 24);
gtk_box_append(GTK_BOX(page),
top_bar("onyears", "on_year3", "on3_sub"));
scroll = gtk_scrolled_window_new();
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_widget_set_vexpand(scroll, TRUE);
gtk_widget_set_margin_top(scroll, 14);
gtk_box_append(GTK_BOX(page), scroll);
on3_scroll = scroll;
wrap = gtk_center_box_new();
gtk_widget_set_vexpand(wrap, TRUE);
gtk_widget_set_hexpand(wrap, TRUE);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), wrap);
fixed = gtk_fixed_new();
gtk_widget_set_halign(fixed, GTK_ALIGN_CENTER);
gtk_widget_set_valign(fixed, GTK_ALIGN_CENTER);
gtk_widget_set_size_request(fixed, on3_cw, on3_ch);
gtk_center_box_set_center_widget(GTK_CENTER_BOX(wrap), fixed);
on3_fixed = fixed;
rail = gtk_drawing_area_new();
gtk_widget_set_size_request(rail, on3_cw, on3_ch);
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(rail), on3_draw_rail,
NULL, NULL);
gtk_fixed_put(GTK_FIXED(fixed), rail, 0, 0);
on3_rail = rail;
for (int i = 0; i < ON3_UNITS; i++)
on3_add_node(GTK_FIXED(fixed), i);
ha = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scroll));
va = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(scroll));
g_signal_connect(ha, "notify::page-size",
G_CALLBACK(on3_adjust_notify), NULL);
g_signal_connect(va, "notify::page-size",
G_CALLBACK(on3_adjust_notify), NULL);
on3_apply_layout();
on3_relayout_later();
refresh_on3_completion_ui();
return page;
}
static void on3_slide_apply(NetLesson *L);
static void on3_slide_prev(GtkButton *button, gpointer data);
static void on3_slide_next(GtkButton *button, gpointer data);
static int on3_note_last_w = -1;
static void on3_rescale_notes_at(int w) {
int body, head, kick;
if (w < 160)
w = 160;
body = w / 46;
if (body < 14) body = 14;
if (body > 26) body = 26;
head = body + 14;
if (head > 46) head = 46;
kick = body - 3;
if (kick < 11) kick = 11;
for (int i = 0; i < ON3_LESSONS; i++)
net_rescale_lesson_notes(&on3_lessons[i], body, head, kick);
}
static gboolean on3_note_tick(GtkWidget *w, GdkFrameClock *clock,
gpointer data) {
int width = gtk_widget_get_allocated_width(w);
(void)clock;
(void)data;
if (width != on3_note_last_w) {
on3_note_last_w = width;
on3_rescale_notes_at(width);
}
return G_SOURCE_CONTINUE;
}
static GtkWidget *build_on3_unit_page(NetLesson *L, const char *title_key,
const char *sub_key,
const NetSlide *slides, guint n_slides) {
GtkWidget *page;
GtkWidget *scroll;
GtkWidget *nav;
net_lesson_notes_ensure(L);
net_notes_target = L;
L->n_slides = n_slides;
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_margin_start(page, 28);
gtk_widget_set_margin_end(page, 28);
gtk_widget_set_margin_top(page, 20);
gtk_widget_set_margin_bottom(page, 20);
gtk_box_append(GTK_BOX(page), top_bar("on3map", title_key, sub_key));
scroll = gtk_scrolled_window_new();
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_widget_set_vexpand(scroll, TRUE);
gtk_widget_set_margin_top(scroll, 10);
gtk_box_append(GTK_BOX(page), scroll);
L->stack = gtk_stack_new();
gtk_stack_set_transition_type(GTK_STACK(L->stack),
GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), L->stack);
for (guint s = 0; s < n_slides; s++) {
GtkWidget *stage = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
GtkWidget *card;
char name[16];
gtk_widget_set_vexpand(stage, TRUE);
g_snprintf(name, sizeof(name), "on3_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, on3_note_tick, NULL, NULL);
on3_note_last_w = -1;
on3_rescale_notes_at(0);
net_notes_target = NULL;
nav = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
gtk_box_set_homogeneous(GTK_BOX(nav), TRUE);
gtk_widget_set_margin_top(nav, 10);
gtk_box_append(GTK_BOX(page), nav);
L->prev_btn = gtk_button_new_with_label(tr("net_slide_prev"));
gtk_widget_add_css_class(L->prev_btn, "btn-primary");
g_signal_connect(L->prev_btn, "clicked", G_CALLBACK(on3_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(on3_slide_next), L);
gtk_box_append(GTK_BOX(nav), L->next_btn);
L->idx = 0;
on3_slide_apply(L);
return page;
}
static void on3_slide_apply(NetLesson *L) {
char name[16];
if (!L || !L->stack)
return;
g_snprintf(name, sizeof(name), "on3_slide%u", L->idx);
gtk_stack_set_visible_child_name(GTK_STACK(L->stack), name);
if (L->prev_btn) {
gtk_button_set_label(GTK_BUTTON(L->prev_btn), tr("net_slide_prev"));
gtk_widget_set_sensitive(L->prev_btn, L->idx > 0);
}
if (L->next_btn) {
gtk_button_set_label(
GTK_BUTTON(L->next_btn),
L->idx + 1 >= L->n_slides ? tr("net_slide_start")
: tr("net_slide_next"));
}
}
static void on3_slide_prev(GtkButton *button, gpointer data) {
NetLesson *L = data;
(void)button;
if (!L) return;
if (L->idx > 0) {
L->idx--;
on3_slide_apply(L);
}
}
static void on3_slide_next(GtkButton *button, gpointer data) {
NetLesson *L = data;
(void)button;
if (!L) return;
if (L->idx + 1 < L->n_slides) {
L->idx++;
on3_slide_apply(L);
} else {
gtk_stack_set_visible_child_name(main_stack, L->ex_page);
}
}
void on3_lessons_apply_lang(void) {
for (int i = 0; i < ON3_LESSONS; i++)
on3_slide_apply(&on3_lessons[i]);
}
static void on3_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"));
mark_on3_done(ctx->lesson_id);
} else {
set_feedback(ctx->feedback, FALSE, tr("feedback_retry_short"));
}
}
static GtkWidget *build_on3_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 *page;
GtkWidget *scroll;
GtkWidget *body;
GtkWidget *check;
GtkWidget *btns;
GtkWidget *head;
GtkWidget *intro;
NetMcqCtx *ctx = g_new0(NetMcqCtx, 1);
int n_opts = 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(back_page, title_key, NULL));
scroll = gtk_scrolled_window_new();
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_widget_set_vexpand(scroll, TRUE);
gtk_widget_set_margin_top(scroll, 12);
gtk_box_append(GTK_BOX(page), scroll);
body = gtk_box_new(GTK_ORIENTATION_VERTICAL, 14);
gtk_widget_set_halign(body, GTK_ALIGN_FILL);
gtk_widget_set_hexpand(body, TRUE);
gtk_widget_set_margin_start(body, 4);
gtk_widget_set_margin_end(body, 4);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), body);
head = gtk_label_new(NULL);
i18n_bind(head, heading_key, 0);
gtk_widget_set_halign(head, GTK_ALIGN_START);
gtk_label_set_wrap(GTK_LABEL(head), TRUE);
gtk_widget_set_margin_top(head, 4);
gtk_widget_add_css_class(head, "quiz-heading");
gtk_box_append(GTK_BOX(body), head);
intro = gtk_label_new(NULL);
i18n_bind(intro, "net_quiz_intro", 0);
gtk_widget_set_halign(intro, GTK_ALIGN_START);
gtk_label_set_wrap(GTK_LABEL(intro), TRUE);
gtk_widget_add_css_class(intro, "quiz-intro");
gtk_widget_set_margin_bottom(intro, 4);
gtk_box_append(GTK_BOX(body), intro);
ctx->qs = qs;
ctx->n = n;
ctx->n_opts = n_opts;
ctx->lesson_id = lesson_id;
ctx->toggles = g_new0(GtkToggleButton *, n * n_opts);
ctx->hints = g_new0(GtkWidget *, n);
for (int i = 0; i < n; i++) {
GtkWidget *card = mcq_append_question(body, i + 1, &qs[i],
&ctx->toggles[i * n_opts]);
ctx->hints[i] = meaning_add(card, hints[i]);
}
btns = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
gtk_widget_set_margin_top(btns, 8);
gtk_widget_set_margin_bottom(btns, 8);
gtk_box_append(GTK_BOX(body), btns);
check = gtk_button_new();
i18n_bind(check, "check", 1);
gtk_widget_add_css_class(check, "btn-primary");
gtk_widget_set_halign(check, GTK_ALIGN_START);
gtk_box_append(GTK_BOX(btns), check);
g_signal_connect(check, "clicked", G_CALLBACK(on3_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;
}
static GtkWidget *build_on3_unit1_page(void) {
static const NetSlide slides[] = {
{
"1 / 2 • Právo", "Pravidla, která stát vynutí",
"Tip: ne každé neslušné jednání je nezákonné.",
{
"Právo je soubor pravidel, která může stát vynutit.",
"Morálka říká, co je slušné. Právo říká, co je závazné.",
"Obojí se často kryje, ale není to totéž.",
"Bez vynutitelného práva by rozhodovala síla, ne pravidlo.",
NULL,
},
},
{
"2 / 2 • Spravedlnost", "Podobné případy podobně",
"Tip: spravedlnost je cíl práva, ne jeho automatický výsledek.",
{
"Spravedlnost chce, aby se podobné případy posuzovaly podobně.",
"Právo má chránit slabšího před svévolí silnějšího.",
"Sankce následuje, když někdo pravidlo poruší.",
"Třetí ročník bere právní řád, soudy, smlouvy, rodinu, práci a tresty.",
NULL,
},
},
};
return build_on3_unit_page(&on3_lessons[0], "on3_unit1", "on3_unit1_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_on3_unit1_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co je právo?",
{
"Jen dobré mravy",
"Soubor pravidel, která může stát vynutit",
"Názor většiny na sociálních sítích",
"Zvyk jedné rodiny",
},
4, 1},
{"Je morálka totéž co právo?",
{
"Ano",
"Ne, často se kryjí, ale nejsou totéž",
"Morálka je vždy přísnější zákon",
"Právo je jen náboženství",
},
4, 1},
{"K čemu spravedlnost v právu směřuje?",
{
"Aby vyhrál hlasitější",
"Aby se podobné případy posuzovaly podobně",
"Aby trest byl vždy nejvyšší",
"Aby soudil ten, kdo je u moci",
},
4, 1},
{"Co by bez vynutitelného práva rozhodovalo?",
{
"Síla",
"Los",
"Školní řád",
"Anketa",
},
4, 0},
};
static const char *hints[] = {
"Právo je vynutitelné státní mocí",
"Slušnost a zákon se nemusí krýt",
"Stejné se má měřit stejným metrem",
"Právo má nahradit právo silnějšího",
};
return build_on3_mcq_page(0, "on3unit1", "on3_ex1_title",
"on3_quiz1_head", qs, hints, 4);
}
static GtkWidget *build_on3_unit2_page(void) {
static const NetSlide slides[] = {
{
"1 / 2 • Vázanost", "I stát má meze",
"Tip: úřad smí jen to, co mu dovoluje zákon.",
{
"V právním státě je právem vázán i stát, ne jen občan.",
"Úřad smí zasahovat do práv člověka jen tam, kde mu to zákon dovolí.",
"Moc se dělí, aby ji nikdo neužíval bez kontroly.",
"Soudy mají být nezávislé na vládě a na politicích.",
NULL,
},
},
{
"2 / 2 • Ochrana", "Kam se člověk obrátí",
"Tip: právní stát stojí na tom, že se práva dá dovolat.",
{
"Člověk se může domáhat ochrany u nezávislého soudu.",
"Rozhodnutí úřadu má být přezkoumatelné.",
"Nikdo nesmí být trestán bez zákona, který čin už předtím zakazoval.",
"Právní stát není stát bez pravidel. Je to stát, který svá pravidla sám dodržuje.",
NULL,
},
},
};
return build_on3_unit_page(&on3_lessons[1], "on3_unit2", "on3_unit2_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_on3_unit2_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Kdo je v právním státě vázán právem?",
{
"Jen občané",
"I stát",
"Jen cizinci",
"Jen firmy",
},
4, 1},
{"Kdy smí úřad zasáhnout do práv člověka?",
{
"Kdykoli se mu to hodí",
"Jen když mu to dovoluje zákon",
"Když o to požádá soused",
"Když to schválí starosta ústně",
},
4, 1},
{"Jakou roli mají soudy?",
{
"Plnit pokyny ministra",
"Rozhodovat nezávisle",
"Psát zákony místo Parlamentu",
"Vybírat daně",
},
4, 1},
{"Lze trestat čin, který v době spáchání zákon nezakazoval?",
{
"Ano, zpětně",
"Ne",
"Ano, pokud šlo o přestupek školy",
"Ano, rozhodnutím vlády",
},
4, 1},
};
static const char *hints[] = {
"Právo váže i veřejnou moc",
"Veřejná moc jedná podle zákona",
"Nezávislost soudů je součást právního státu",
"Nullum crimen sine lege",
};
return build_on3_mcq_page(1, "on3unit2", "on3_ex2_title",
"on3_quiz2_head", qs, hints, 4);
}
static GtkWidget *build_on3_unit3_page(void) {
static const NetSlide slides[] = {
{
"1 / 2 • Pyramida", "Co stojí nad čím",
"Tip: nižší předpis nesmí odporovat vyššímu.",
{
"Nejvýš stojí ústavní zákony, včetně Ústavy a Listiny.",
"Pod nimi jsou zákony, pak nařízení vlády a vyhlášky ministerstev.",
"Obec a kraj mohou vydávat vyhlášky jen v mezích zákona.",
"Platné předpisy se vyhlašují ve Sbírce zákonů.",
NULL,
},
},
{
"2 / 2 • Odvětví", "Veřejné a soukromé právo",
"Tip: trestní právo je veřejné, kupní smlouva je soukromé.",
{
"Veřejné právo chrání zájem celku. Patří sem třeba trestní a správní právo.",
"Soukromé právo upravuje vztahy mezi lidmi. Patří sem občanské, rodinné a pracovní právo.",
"Když si předpisy odporují, použije se ten vyšší.",
"Neznalost zákona neomlouvá, proto se předpisy zveřejňují.",
NULL,
},
},
};
return build_on3_unit_page(&on3_lessons[2], "on3_unit3", "on3_unit3_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_on3_unit3_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co stojí v právním řádu nejvýš?",
{
"Obecní vyhláška",
"Ústavní zákony",
"Nařízení vlády",
"Vnitřní pokyn úřadu",
},
4, 1},
{"Smí vyhláška ministerstva zrušit zákon?",
{
"Ano",
"Ne, nižší předpis nesmí odporovat vyššímu",
"Ano, pokud ji podepíše ministr",
"Ano, na jeden rok",
},
4, 1},
{"Kam patří trestní právo?",
{
"Do soukromého práva",
"Do veřejného práva",
"Mimo právní řád",
"Jen do školního řádu",
},
4, 1},
{"Kde se právní předpisy vyhlašují?",
{
"Ve Sbírce zákonů",
"Jen na nástěnce obce",
"Jen v televizi",
"V notářském zápisu",
},
4, 0},
};
static const char *hints[] = {
"Ústava a Listina jsou nad zákony",
"Pyramida předpisů se nepřeskakuje",
"Trestní právo chrání společnost",
"Bez vyhlášení předpis nezavazuje",
};
return build_on3_mcq_page(2, "on3unit3", "on3_ex3_title",
"on3_quiz3_head", qs, hints, 4);
}
static GtkWidget *build_on3_unit4_page(void) {
static const NetSlide slides[] = {
{
"1 / 2 • Osoby", "Kdo může mít práva",
"Tip: právnická osoba není člověk, ale právo s ní jedná.",
{
"Fyzická osoba je člověk.",
"Právnická osoba je třeba firma, spolek, obec nebo stát.",
"Právní osobnost má člověk od narození: může mít práva a povinnosti.",
"Právní vztah spojuje subjekty právy a povinnostmi.",
NULL,
},
},
{
"2 / 2 • Věk", "Svéprávnost a trestní odpovědnost",
"Tip: patnáct let ještě není plná svéprávnost.",
{
"Svéprávnost je způsobilost sám právně jednat.",
"Plná svéprávnost přichází zletilostí v 18 letech.",
"Soud může svéprávnost z vážných důvodů omezit.",
"Trestně odpovědný je člověk od 15 let, je-li příčetný.",
NULL,
},
},
};
return build_on3_unit_page(&on3_lessons[3], "on3_unit4", "on3_unit4_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_on3_unit4_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co je fyzická osoba?",
{
"Firma",
"Člověk",
"Úřad",
"Smlouva",
},
4, 1},
{"Od kdy má člověk právní osobnost?",
{
"Od 18 let",
"Od narození",
"Od 15 let",
"Od první smlouvy",
},
4, 1},
{"Kdy nastává plná svéprávnost?",
{
"V 15 letech",
"Zletilostí v 18 letech",
"Nástupem do práce",
"Zápisem do občanského průkazu",
},
4, 1},
{"Od kolika let je člověk trestně odpovědný?",
{
"Od 18 let",
"Od 15 let, je-li příčetný",
"Od 10 let",
"Až od první výplaty",
},
4, 1},
};
static const char *hints[] = {
"Fyzická osoba je člověk",
"Práva může mít i dítě",
"Svéprávnost je způsobilost jednat",
"Pod 15 let trestní odpovědnost není",
};
return build_on3_mcq_page(3, "on3unit4", "on3_ex4_title",
"on3_quiz4_head", qs, hints, 4);
}
static GtkWidget *build_on3_unit5_page(void) {
static const NetSlide slides[] = {
{
"1 / 2 • Věc", "Co vlastník smí",
"Tip: vlastnictví není bez mezí.",
{
"Vlastník věc drží, užívá a může s ní nakládat.",
"Zákon ho omezuje, třeba kvůli sousedům.",
"Vyvlastnit lze jen ve veřejném zájmu, na základě zákona a za náhradu.",
"Duševní vlastnictví chrání třeba autorské dílo nebo ochrannou známku.",
NULL,
},
},
{
"2 / 2 • Škoda", "Kdo ji nahradí",
"Tip: cizí dílo se nesmí jen tak zkopírovat.",
{
"Kdo způsobí škodu, má ji zpravidla nahradit.",
"Škoda může být na věci, na zdraví i v penězích, o které člověk přišel.",
"Náhrada má uvést stav co nejblíž tomu, jaký byl před škodou.",
"U autora se bere i to, že dílo někdo užije bez svolení.",
NULL,
},
},
};
return build_on3_unit_page(&on3_lessons[4], "on3_unit5", "on3_unit5_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_on3_unit5_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co vlastník se svou věcí smí?",
{
"Jen se na ni dívat",
"Držet ji, užívat ji a nakládat s ní",
"Jen ji půjčovat státu",
"Nic, dokud ji nepojistí",
},
4, 1},
{"Lze v České republice vyvlastnit bez náhrady?",
{
"Ano, kdykoli",
"Ne, jen ve veřejném zájmu, podle zákona a za náhradu",
"Ano, rozhodnutím souseda",
"Ano, pokud jde o auto",
},
4, 1},
{"Co chrání duševní vlastnictví?",
{
"Jen pozemky",
"Třeba autorské dílo",
"Jen hotovost",
"Jen nemovitosti v katastru",
},
4, 1},
{"Kdo má zpravidla nahradit způsobenou škodu?",
{
"Stát vždy",
"Ten, kdo ji způsobil",
"Poškozený sám",
"Soused",
},
4, 1},
};
static const char *hints[] = {
"Vlastnictví zahrnuje držbu, užívání i nakládání",
"Vyvlastnění má přísné podmínky",
"Dílo není totéž co hmotná věc",
"Škůdce škodu nahrazuje",
};
return build_on3_mcq_page(4, "on3unit5", "on3_ex5_title",
"on3_quiz5_head", qs, hints, 4);
}
static GtkWidget *build_on3_unit6_page(void) {
static const NetSlide slides[] = {
{
"1 / 2 • Vůle", "Smlouva zavazuje",
"Tip: ústní smlouva může platit, ale hůř se dokazuje.",
{
"Smlouva je souhlasný projev vůle dvou nebo více stran.",
"Strany si sjednají, kdo, co a za jakých podmínek plní.",
"Platná smlouva zavazuje. Nelze od ní odejít jen proto, že se to přestalo hodit.",
"Zákon u některých smluv žádá písemnou formu.",
NULL,
},
},
{
"2 / 2 • Druhy", "Koupě, nájem, dar a dílo",
"Tip: dům se nepřevede jen podáním ruky.",
{
"Kupní smlouvou přechází věc za kupní cenu.",
"Nájem přenechává věc k dočasnému užívání za nájemné.",
"Darování je bezúplatné. Smlouvou o dílo se zhotoví, opraví nebo upraví věc.",
"Vlastnictví k nemovitosti se zapisuje do katastru.",
NULL,
},
},
};
return build_on3_unit_page(&on3_lessons[5], "on3_unit6", "on3_unit6_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_on3_unit6_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co je smlouva?",
{
"Jednostranný rozkaz úřadu",
"Souhlasný projev vůle, který strany zavazuje",
"Reklama",
"Ústní slib bez obsahu",
},
4, 1},
{"Lze od každé smlouvy odejít, když se to přestane hodit?",
{
"Ano",
"Ne, platná smlouva zavazuje",
"Ano, do hodiny",
"Ano, pokud druhá strana nesouhlasí",
},
4, 1},
{"Co je nájem?",
{
"Darování věci",
"Dočasné užívání věci za nájemné",
"Trest za škodu",
"Převod nemovitosti darem",
},
4, 1},
{"Kam se zapisuje vlastnictví k nemovitosti?",
{
"Do občanského průkazu",
"Do katastru nemovitostí",
"Jen do smlouvy v šuplíku",
"Na finanční úřad",
},
4, 1},
};
static const char *hints[] = {
"Smlouva stojí na shodě stran",
"Závazek se plní",
"Nájem není převod vlastnictví",
"U nemovitosti rozhoduje vklad do katastru",
};
return build_on3_mcq_page(5, "on3unit6", "on3_ex6_title",
"on3_quiz6_head", qs, hints, 4);
}
static GtkWidget *build_on3_unit7_page(void) {
static const NetSlide slides[] = {
{
"1 / 2 • Ochrana", "Slabší strana smlouvy",
"Tip: spotřebitel je člověk, který nejedná jako podnikatel.",
{
"Spotřebitel uzavírá smlouvu s podnikatelem mimo své podnikání.",
"Zákon ho chrání, protože podnikatel zná podmínky líp.",
"Nekalé obchodní praktiky a klamavá reklama jsou zakázané.",
"Smlouva nemá schovávat podstatné podmínky do nečitelného textu.",
NULL,
},
},
{
"2 / 2 • Reklamace", "Vada a čtrnáct dnů",
"Tip: účtenka pomáhá, ale není jediný důkaz koupě.",
{
"Vadu zboží nebo služby lze reklamovat u prodávajícího.",
"Smlouvu sjednanou po internetu nebo po telefonu lze často zrušit do 14 dnů.",
"Výjimkou je třeba zboží vyrobené na míru nebo rychle se kazící věc.",
"Když obchodník neplní, pomáhá Česká obchodní inspekce.",
NULL,
},
},
};
return build_on3_unit_page(&on3_lessons[6], "on3_unit7", "on3_unit7_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_on3_unit7_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Kdo je spotřebitel?",
{
"Každá firma",
"Člověk, který s podnikatelem nejedná ve svém podnikání",
"Jen nezletilý",
"Jen vlastník obchodu",
},
4, 1},
{"Co je reklamace?",
{
"Darování věci",
"Uplatnění vady zboží nebo služby",
"Výpověď z práce",
"Žaloba na souseda",
},
4, 1},
{"Dokdy lze často odstoupit od smlouvy uzavřené po internetu?",
{
"Do 14 dnů",
"Do druhého dne",
"Do jednoho roku vždy",
"Nelze nikdy",
},
4, 0},
{"Kdo pomáhá, když obchodník spotřebiteli neplní?",
{
"Česká obchodní inspekce",
"Jen školní parlament",
"Katastr nemovitostí",
"Česká národní banka u každého nákupu",
},
4, 0},
};
static const char *hints[] = {
"Spotřebitel je slabší strana",
"Reklamace se obrací na prodávajícího",
"U smluv na dálku běží čtrnáctidenní lhůta",
"ČOI dohlíží na ochranu spotřebitele",
};
return build_on3_mcq_page(6, "on3unit7", "on3_ex7_title",
"on3_quiz7_head", qs, hints, 4);
}
static GtkWidget *build_on3_unit8_page(void) {
static const NetSlide slides[] = {
{
"1 / 2 • Nabytí", "Zákon, nebo závěť",
"Tip: dědictví lze odmítnout.",
{
"Dědí se podle závěti, a kde závěť není, podle zákona.",
"Ze zákona dědí především děti, manžel nebo partner a další blízcí.",
"Pozůstalost projednává notář jako soudní komisař.",
"Dědic může dědictví odmítnout. Odmítnutí se nedá jen tak vzít zpět.",
NULL,
},
},
{
"2 / 2 • Dluhy", "Soupis pozůstalosti",
"Tip: dědictví nejsou jen věci, ale i dluhy.",
{
"S majetkem mohou přejít i dluhy zůstavitele.",
"Bez soupisu může dědic ručit i nad hodnotu toho, co nabyl.",
"Požádá-li o soupis pozůstalosti, hradí dluhy jen do výše nabytého majetku.",
"Proto se před přijetím vyplatí vědět, co v pozůstalosti je.",
NULL,
},
},
};
return build_on3_unit_page(&on3_lessons[7], "on3_unit8", "on3_unit8_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_on3_unit8_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Podle čeho se dědí, když není závěť?",
{
"Podle losu",
"Podle zákona",
"Podle přání sousedů",
"Majetek propadá vždy obci",
},
4, 1},
{"Kdo projednává pozůstalost?",
{
"Notář",
"Starosta",
"Zaměstnavatel",
"Škola",
},
4, 0},
{"Lze dědictví odmítnout?",
{
"Ne",
"Ano",
"Jen nemovitost",
"Jen se souhlasem věřitele",
},
4, 1},
{"K čemu je soupis pozůstalosti?",
{
"Aby se dluhy hradily jen do výše nabytého majetku",
"Aby dědic přišel o všechno",
"Aby se závěť zrušila",
"Aby se dědictví nedalo odmítnout",
},
4, 0},
};
static const char *hints[] = {
"Zákon určuje okruh dědiců",
"Notář jedná jako soudní komisař",
"Odmítnutí je právo dědice",
"Soupis omezuje rozsah dluhů",
};
return build_on3_mcq_page(7, "on3unit8", "on3_ex8_title",
"on3_quiz8_head", qs, hints, 4);
}
static GtkWidget *build_on3_unit9_page(void) {
static const NetSlide slides[] = {
{
"1 / 2 • Svazek", "Manželství a partnerství",
"Tip: partnerství z roku 2025 není totéž co staré registrované partnerství.",
{
"Manželství je trvalý svazek muže a ženy.",
"Uzavírá se svobodně, zpravidla od 18 let. Výjimečně od 16 let se souhlasem soudu.",
"Dva lidé stejného pohlaví mohou od roku 2025 uzavřít partnerství.",
"Partnerství se v řadě práv podobá manželství. Dřívější registrovaná partnerství zůstávají v platnosti.",
NULL,
},
},
{
"2 / 2 • Dítě", "Péče, výživa a náhradní výchova",
"Tip: pěstoun není totéž co osvojitel.",
{
"Rodiče mají o dítě pečovat, zastupovat je a živit je.",
"Dítě má právo být slyšeno ve věcech, které se ho týkají.",
"Osvojením se dítě přijímá za vlastní. Pěstoun o něj pečuje, když nemůže být u rodičů.",
"Poručník dítě zastupuje, když to rodiče nemohou.",
NULL,
},
},
};
return build_on3_unit_page(&on3_lessons[8], "on3_unit9", "on3_unit9_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_on3_unit9_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co je manželství podle občanského zákoníku?",
{
"Jakýkoli společný byt",
"Trvalý svazek muže a ženy",
"Pracovní smlouva",
"Smlouva o nájmu",
},
4, 1},
{"Co mohou od roku 2025 uzavřít dva lidé stejného pohlaví?",
{
"Nic",
"Partnerství",
"Jen pracovní poměr",
"Už jen registrované partnerství jako dřív",
},
4, 1},
{"Od kolika let lze zpravidla uzavřít manželství?",
{
"Od 15 let",
"Od 18 let",
"Od 21 let",
"Od 16 let bez další podmínky",
},
4, 1},
{"Čím se liší pěstoun od osvojitele?",
{
"Pěstoun o dítě pečuje, osvojení je přijímá za vlastní",
"Ničím",
"Pěstoun je vždy poručník soudu",
"Osvojitel jen platí nájem",
},
4, 0},
};
static const char *hints[] = {
"Manželství je svazek muže a ženy",
"Nové registrované partnerství se už neuzavírá",
"Šestnáct let jen výjimečně se souhlasem soudu",
"Osvojení mění právní vztah rodiče a dítěte",
};
return build_on3_mcq_page(8, "on3unit9", "on3_ex9_title",
"on3_quiz9_head", qs, hints, 4);
}
static GtkWidget *build_on3_unit10_page(void) {
static const NetSlide slides[] = {
{
"1 / 2 • Smlouva", "Vznik pracovního poměru",
"Tip: zkušební doba musí být písemná.",
{
"Pracovní smlouva určí druh práce, místo výkonu a den nástupu.",
"Uzavírá se písemně.",
"Zkušební doba smí trvat nejvýše 4 měsíce, u vedoucího zaměstnance 8 měsíců.",
"Ve zkušební době lze poměr zrušit i bez důvodu, ne však v prvních 14 dnech neschopnosti.",
NULL,
},
},
{
"2 / 2 • Konec", "Výpověď a bezpečná práce",
"Tip: zaměstnavatel si důvod výpovědi nesmí vymyslet.",
{
"Zaměstnanec může dát výpověď z jakéhokoli důvodu.",
"Zaměstnavatel jen ze zákonných důvodů, třeba organizačních nebo pro porušení povinností.",
"Výpovědní doba trvá nejméně dva měsíce a začíná dnem doručení.",
"Zaměstnanec má právo na mzdu, odpočinek, dovolenou a bezpečnou práci.",
NULL,
},
},
};
return build_on3_unit_page(&on3_lessons[9], "on3_unit10", "on3_unit10_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_on3_unit10_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co musí pracovní smlouva určit?",
{
"Jen výši prémií",
"Druh práce, místo a den nástupu",
"Jméno manžela",
"Číslo občanského průkazu rodičů",
},
4, 1},
{"Jak dlouhá smí být zkušební doba řadového zaměstnance?",
{
"Nejvýše 4 měsíce",
"Nejvýše jeden rok",
"Bez omezení",
"Jen jeden týden",
},
4, 0},
{"Kdy smí zaměstnavatel dát výpověď?",
{
"Z jakéhokoli důvodu",
"Jen ze zákonných důvodů",
"Jen ústně na chodbě",
"Nikdy",
},
4, 1},
{"Jak dlouhá je základní výpovědní doba?",
{
"Jeden týden",
"Nejméně dva měsíce",
"Půl roku vždy",
"Do konce směny",
},
4, 1},
};
static const char *hints[] = {
"To jsou podstatné náležitosti",
"U vedoucího je mez osm měsíců",
"Důvody výpovědi stanoví zákoník práce",
"Běží ode dne doručení výpovědi",
};
return build_on3_mcq_page(9, "on3unit10", "on3_ex10_title",
"on3_quiz10_head", qs, hints, 4);
}
static GtkWidget *build_on3_unit11_page(void) {
static const NetSlide slides[] = {
{
"1 / 2 • Stupně", "Od okresního soudu výš",
"Tip: Ústavní soud není další odvolací stupeň.",
{
"Nejblíž lidem jsou okresní soudy. V Praze se jim říká obvodní.",
"Nad nimi jsou krajské soudy. V Praze je to Městský soud.",
"Vrchní soudy sídlí v Praze a v Olomouci.",
"Nejvyšší soud sjednocuje rozhodování obecných soudů.",
NULL,
},
},
{
"2 / 2 • Mimo řadu", "Správní a ústavní soudnictví",
"Tip: každý soudce je nezávislý.",
{
"Žaloby proti rozhodnutím úřadů řeší správní soudy.",
"Na vrcholu správního soudnictví je Nejvyšší správní soud.",
"Ústavní soud stojí mimo tuto soustavu a hlídá ústavní pořádek.",
"Soudce není podřízený ministrovi ani politikovi.",
NULL,
},
},
};
return build_on3_unit_page(&on3_lessons[10], "on3_unit11", "on3_unit11_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_on3_unit11_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jak se jmenuje první stupeň soudů v Praze?",
{
"Krajský soud",
"Obvodní soudy",
"Vrchní soud",
"Ústavní soud",
},
4, 1},
{"Kde sídlí vrchní soudy?",
{
"V Brně a Ostravě",
"V Praze a Olomouci",
"Jen v Praze",
"V každém kraji",
},
4, 1},
{"Kdo završuje správní soudnictví?",
{
"Nejvyšší správní soud",
"Obecní úřad",
"Poslanecká sněmovna",
"Notářská komora",
},
4, 0},
{"Je Ústavní soud běžný odvolací soud?",
{
"Ano, je nad každým rozsudkem automaticky",
"Ne, stojí mimo soustavu obecných soudů",
"Ano, nahrazuje okresní soud",
"Ano, rozhoduje každou pokutu",
},
4, 1},
};
static const char *hints[] = {
"Mimo Prahu jsou to okresní soudy",
"Vrchní soudy jsou dva",
"Správní soudy přezkoumávají úřady",
"Ústavní soud hlídá ústavní pořádek",
};
return build_on3_mcq_page(10, "on3unit11", "on3_ex11_title",
"on3_quiz11_head", qs, hints, 4);
}
static GtkWidget *build_on3_unit12_page(void) {
static const NetSlide slides[] = {
{
"1 / 2 • Žaloba", "Občanské soudní řízení",
"Tip: kdo žaluje, je žalobce. Koho žaluje, je žalovaný.",
{
"Občanské soudní řízení začíná zpravidla žalobou.",
"Žalobce se domáhá svého práva proti žalovanému.",
"Soud vyslechne obě strany a rozhodne rozsudkem, nebo řízení jinak skončí.",
"Proti rozsudku je odvolání. Dovolání k Nejvyššímu soudu je výjimečné.",
NULL,
},
},
{
"2 / 2 • Úřad", "Správní řízení",
"Tip: odvolání je řádný opravný prostředek.",
{
"Správní řízení vede úřad, třeba když vydává povolení nebo ukládá povinnost.",
"Řídí se správním řádem.",
"Proti rozhodnutí se lze odvolat k nadřízenému úřadu.",
"Když to nepomůže, zbývá žaloba ke správnímu soudu.",
NULL,
},
},
};
return build_on3_unit_page(&on3_lessons[11], "on3_unit12", "on3_unit12_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_on3_unit12_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Kdo je žalobce?",
{
"Soudce",
"Ten, kdo se žalobou domáhá svého práva",
"Svědek",
"Každý, kdo sedí v síni",
},
4, 1},
{"Čím občanské soudní řízení zpravidla začíná?",
{
"Výpovědí",
"Žalobou",
"Závětí",
"Reklamací v obchodě",
},
4, 1},
{"Co je odvolání?",
{
"Nová pracovní smlouva",
"Řádný opravný prostředek proti rozhodnutí",
"Odmítnutí dědictví",
"Stížnost na počasí",
},
4, 1},
{"Kam se lze obrátit, když neuspěje odvolání proti úřadu?",
{
"Ke správnímu soudu",
"Do katastru",
"K zaměstnavateli",
"Na obecnou nástěnku",
},
4, 0},
};
static const char *hints[] = {
"Žalovaný je ten, proti komu žaloba míří",
"Žaloba otevírá spor",
"Odvolání přezkoumá vyšší stupeň",
"Správní soud přezkoumává rozhodnutí úřadu",
};
return build_on3_mcq_page(11, "on3unit12", "on3_ex12_title",
"on3_quiz12_head", qs, hints, 4);
}
static GtkWidget *build_on3_unit13_page(void) {
static const NetSlide slides[] = {
{
"1 / 2 • Soud a žaloba", "Kdo rozhoduje a kdo žaluje",
"Tip: advokát není soudce.",
{
"Soudce je nezávislý, nestranný a spor rozhoduje.",
"Státní zástupce podává obžalobu a ve veřejném zájmu hlídá zákonnost trestního řízení.",
"Advokát zastupuje klienta a váže ho povinnost mlčenlivosti.",
"Kdo si advokáta nemůže dovolit, může v zákonem daných případech dostat právní pomoc.",
NULL,
},
},
{
"2 / 2 • Listiny", "Notář a exekutor",
"Tip: exekutor nevymýšlí dluh, vymáhá už uloženou povinnost.",
{
"Notář sepisuje veřejné listiny a projednává dědictví.",
"Veřejná listina má silnější důkazní váhu než obyčejné potvrzení.",
"Exekutor vymáhá splnění rozhodnutí, když je dlužník sám neplní.",
"Veřejný ochránce práv není soudce a rozsudky neruší.",
NULL,
},
},
};
return build_on3_unit_page(&on3_lessons[12], "on3_unit13", "on3_unit13_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_on3_unit13_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Kdo v trestním řízení podává obžalobu?",
{
"Advokát obžalovaného",
"Státní zástupce",
"Notář",
"Starosta",
},
4, 1},
{"Čím je vázán advokát?",
{
"Pokyny protistrany",
"Povinností mlčenlivosti vůči klientovi",
"Příkazem ministra v každém sporu",
"Ničím",
},
4, 1},
{"Kdo projednává dědictví?",
{
"Exekutor",
"Notář",
"Policie vždy",
"Zaměstnavatel",
},
4, 1},
{"Co dělá exekutor?",
{
"Ruší zákony",
"Vymáhá splnění uložené povinnosti",
"Uzavírá manželství",
"Rozhoduje o vině v trestním řízení",
},
4, 1},
};
static const char *hints[] = {
"Státní zástupce žaluje ve veřejném zájmu",
"Advokát hájí klienta, ne úřad",
"Notář je soudní komisař",
"Exekuce není nový soud o vině",
};
return build_on3_mcq_page(12, "on3unit13", "on3_ex13_title",
"on3_quiz13_head", qs, hints, 4);
}
static GtkWidget *build_on3_unit14_page(void) {
static const NetSlide slides[] = {
{
"1 / 2 • Rozdíl", "Závažnost rozhoduje",
"Tip: přestupek není malý trestný čin.",
{
"Trestný čin je závažný protiprávní čin, který tak označuje trestní zákoník.",
"Přestupek je mírnější protiprávní čin a trestným činem není.",
"Rušení nočního klidu bývá přestupek. Úmyslné těžké ublížení na zdraví je trestný čin.",
"Za každý z nich odpovídá jiný zákon a jiné řízení.",
NULL,
},
},
{
"2 / 2 • Vina", "Věk, příčetnost a presumpce",
"Tip: obviněný není odsouzený.",
{
"Trestní odpovědnost vyžaduje věk aspoň 15 let, příčetnost a zavinění.",
"Bez zavinění, tedy úmyslu nebo nedbalosti, o trestný čin nejde.",
"Dokud soud pravomocně nerozhodne, hledí se na člověka jako na nevinného.",
"Tomu se říká presumpce neviny.",
NULL,
},
},
};
return build_on3_unit_page(&on3_lessons[13], "on3_unit14", "on3_unit14_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_on3_unit14_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Čím se trestný čin liší od přestupku?",
{
"Ničím",
"Trestný čin je závažnější a popisuje ho trestní zákoník",
"Přestupek řeší jen Ústavní soud",
"Trestný čin nemá oběť",
},
4, 1},
{"Kam spíš patří rušení nočního klidu?",
{
"Mezi přestupky",
"Vždy mezi zločiny",
"Mimo právo",
"Jen do pracovní smlouvy",
},
4, 0},
{"Co presumpce neviny znamená?",
{
"Policie nesmí nikoho vyslechnout",
"Dokud soud nerozhodne, hledí se na člověka jako na nevinného",
"Trest se ukládá před soudem",
"Obviněný se nesmí hájit",
},
4, 1},
{"Stačí ke trestnému činu samotný následek bez zavinění?",
{
"Ano",
"Ne, trestní právo vyžaduje zavinění",
"Ano u každého přestupku školy",
"Ano, pokud o tom napíší noviny",
},
4, 1},
};
static const char *hints[] = {
"Závažnost a zákon, který čin upravuje",
"Jde o mírnější protiprávní čin",
"Vinu vyslovuje až soud",
"Úmysl nebo nedbalost jsou podmínkou",
};
return build_on3_mcq_page(13, "on3unit14", "on3_ex14_title",
"on3_quiz14_head", qs, hints, 4);
}
static GtkWidget *build_on3_unit15_page(void) {
static const NetSlide slides[] = {
{
"1 / 2 • Trest", "Ne jen vězení",
"Tip: trest smrti se v České republice neukládá.",
{
"Trest má chránit společnost a vést pachatele k nápravě, ne být pomstou.",
"Vedle odnětí svobody je třeba podmíněné odsouzení, obecně prospěšné práce nebo peněžitý trest.",
"Patří sem i domácí vězení nebo zákaz činnosti.",
"Tomuto posunu od pouhého zavírání se říká humanizace trestání.",
NULL,
},
},
{
"2 / 2 • Řízení", "Policie, žalobce, soud",
"Tip: obhájce má obviněný právo mít.",
{
"Policie věc prověřuje a vyšetřuje.",
"Státní zástupce rozhoduje, zda podá obžalobu.",
"O vině a trestu rozhoduje soud.",
"Obviněný se může hájit a v zákonem daných případech musí mít obhájce.",
NULL,
},
},
};
return build_on3_unit_page(&on3_lessons[14], "on3_unit15", "on3_unit15_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_on3_unit15_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jaký má být smysl trestu?",
{
"Pomsta",
"Ochrana společnosti a náprava pachatele",
"Zisk pro soudce",
"Veřejné ponížení",
},
4, 1},
{"Ukládá Česká republika trest smrti?",
{
"Ano",
"Ne",
"Ano u mladistvých",
"Ano, pokud o to oběť požádá",
},
4, 1},
{"Kdo rozhoduje o vině a trestu?",
{
"Policie",
"Soud",
"Noviny",
"Poškozený sám",
},
4, 1},
{"Co je podmíněné odsouzení?",
{
"Trest smrti s odkladem",
"Odklad výkonu trestu, pokud se odsouzený osvědčí",
"Zproštění obžaloby",
"Přestupek",
},
4, 1},
};
static const char *hints[] = {
"Humanizace odmítá pomstu jako účel",
"Trest smrti byl zrušen",
"Policie vyšetřuje, soud rozhoduje",
"Je to alternativa k nástupu do vězení",
};
return build_on3_mcq_page(14, "on3unit15", "on3_ex15_title",
"on3_quiz15_head", qs, hints, 4);
}
static GtkWidget *build_on3_unit16_page(void) {
static const NetSlide slides[] = {
{
"1 / 2 • Věk", "Děti a mladiství",
"Tip: patnáct let je hranice trestní odpovědnosti, ne dospělosti.",
{
"Dítě mladší 15 let není trestně odpovědné.",
"Mladistvý je člověk od 15 do 18 let.",
"U mladistvého zákon hledí i na výchovu, ne jen na potrestání.",
"I čin dítěte pod 15 let se řeší, třeba opatřeními na ochranu dítěte a okolí.",
NULL,
},
},
{
"2 / 2 • Pomoc", "Když se stanete obětí nebo svědkem",
"Tip: linky 158 a 112 jsou pro přivolání pomoci.",
{
"Šikanu, násilí, vydírání, lichvu nebo korupci si nenechávejte pro sebe.",
"Řekněte to důvěryhodnému dospělému, škole nebo policii.",
"Při ohrožení odejděte do bezpečí a zavolejte 158 nebo 112. Do střetu se nepouštějte.",
"Svědek má mluvit pravdu. Lichva je půjčka, která zneužívá cizí tísně, a patří na policii, ne do další půjčky.",
NULL,
},
},
};
return build_on3_unit_page(&on3_lessons[15], "on3_unit16", "on3_unit16_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_on3_unit16_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Je dítě mladší 15 let trestně odpovědné?",
{
"Ano, jako dospělý",
"Ne",
"Ano, od 10 let",
"Jen když chodí do školy",
},
4, 1},
{"Kdo je mladistvý?",
{
"Každý student",
"Člověk od 15 do 18 let",
"Člověk do 15 let",
"Jen pachatel přestupku",
},
4, 1},
{"Co udělat při šikaně nebo vydírání?",
{
"Nechat si to pro sebe",
"Říct dospělému, škole nebo policii",
"Vrátit to stejnou silou",
"Půjčit si peníze na vyplacení",
},
4, 1},
{"Co má udělat svědek u policie?",
{
"Mlčet",
"Mluvit pravdu",
"Domyslet si, co policie chce slyšet",
"Oznámit jen to, co se hodí kamarádovi",
},
4, 1},
};
static const char *hints[] = {
"Trestní odpovědnost začíná v 15 letech",
"Mladistvý už je trestně odpovědný, ale jinak než dospělý",
"Pomoc je mimo tu situaci",
"Křivá výpověď věc zhoršuje",
};
return build_on3_mcq_page(15, "on3unit16", "on3_ex16_title",
"on3_quiz16_head", qs, hints, 4);
}
void add_on3_pages(GtkStack *stack) {
typedef GtkWidget *(*OnBuilder)(void);
static const struct {
const char *unit_name;
const char *ex_name;
OnBuilder build_unit;
OnBuilder build_ex;
} pages[] = {
{"on3unit1", "on3ex1", build_on3_unit1_page, build_on3_unit1_exercise_page},
{"on3unit2", "on3ex2", build_on3_unit2_page, build_on3_unit2_exercise_page},
{"on3unit3", "on3ex3", build_on3_unit3_page, build_on3_unit3_exercise_page},
{"on3unit4", "on3ex4", build_on3_unit4_page, build_on3_unit4_exercise_page},
{"on3unit5", "on3ex5", build_on3_unit5_page, build_on3_unit5_exercise_page},
{"on3unit6", "on3ex6", build_on3_unit6_page, build_on3_unit6_exercise_page},
{"on3unit7", "on3ex7", build_on3_unit7_page, build_on3_unit7_exercise_page},
{"on3unit8", "on3ex8", build_on3_unit8_page, build_on3_unit8_exercise_page},
{"on3unit9", "on3ex9", build_on3_unit9_page, build_on3_unit9_exercise_page},
{"on3unit10", "on3ex10", build_on3_unit10_page, build_on3_unit10_exercise_page},
{"on3unit11", "on3ex11", build_on3_unit11_page, build_on3_unit11_exercise_page},
{"on3unit12", "on3ex12", build_on3_unit12_page, build_on3_unit12_exercise_page},
{"on3unit13", "on3ex13", build_on3_unit13_page, build_on3_unit13_exercise_page},
{"on3unit14", "on3ex14", build_on3_unit14_page, build_on3_unit14_exercise_page},
{"on3unit15", "on3ex15", build_on3_unit15_page, build_on3_unit15_exercise_page},
{"on3unit16", "on3ex16", build_on3_unit16_page, build_on3_unit16_exercise_page}
};
for (guint i = 0; i < G_N_ELEMENTS(pages); i++) {
gtk_stack_add_named(stack, pages[i].build_unit(), pages[i].unit_name);
gtk_stack_add_named(stack, pages[i].build_ex(), pages[i].ex_name);
}
}