diff --git a/README.md b/README.md index 31c1d8e..da24963 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,8 @@ Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#ses - **Český jazyk a literatura** — literatura (4 ročníky, 32 lekcí podle osnov středních škol), mluvnice (20 cvičení) a maturitní četba (82 knih: zápisky, kvíz, sestavení děje) +- **Fyzika** — 4 ročníky, 32 lekcí podle osnov středních škol + (mechanika, teplo a vlny, elektřina a magnetismus, optika až astrofyzika) - **Základy přírodopisných věd** — chemie a biologie, v každé 8 lekcí (od atomu a buňky po biochemii a ekologii) s výkladem a kvízem - **Občanská nauka** — 1. ročník, 17 lekcí (člověk v lidském společenství); @@ -80,7 +82,7 @@ Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#ses ### Použití 1. **Pokračuj** otevře mapu předmětů. -2. Otevřený předmět (vlajka, Wi‑Fi, čip, česká vlajka, váhy, baňka) vede na učební cestu. +2. Otevřený předmět (vlajka, Wi‑Fi, čip, česká vlajka, váhy, atom, baňka) vede na učební cestu. 3. Uzly cvičení / lekcí otevřou obsah; zamčené nic nedělají. 4. **Hledat** (lupa), **Statistiky** (graf) a **Nastavení** (ozubené kolo) jsou vpravo nahoře. @@ -268,6 +270,8 @@ Intel Mac, another architecture, or hacking on the code → - **Czech language** — literature (4 years, 32 lessons from the secondary-school syllabus), grammar (20 exercises) and required reading (82 books: notes, quiz, plot ordering) +- **Physics** — 4 years, 32 lessons from the secondary-school syllabus + (mechanics, heat and waves, electricity and magnetism, optics through astrophysics) - **Natural sciences** — chemistry and biology, eight lessons each (from the atom and the cell to biochemistry and ecology), with an overview and a quiz - **Civics** — year 1, 17 lessons (the individual and society); @@ -280,7 +284,7 @@ Intel Mac, another architecture, or hacking on the code → ### Usage 1. **Continue** opens the subject map. -2. An open subject (flag, Wi‑Fi, chip, Czech flag, scales, flask) opens its path. +2. An open subject (flag, Wi‑Fi, chip, Czech flag, scales, atom, flask) opens its path. 3. Exercise / lesson nodes open content; locked nodes do nothing. 4. **Search** (magnifier), **Statistics** (chart) and **Settings** (gear) sit in the top-right. diff --git a/android/app/src/main/java/org/maturita/maturita/AppViewModel.kt b/android/app/src/main/java/org/maturita/maturita/AppViewModel.kt index ba1b79f..c99db28 100644 --- a/android/app/src/main/java/org/maturita/maturita/AppViewModel.kt +++ b/android/app/src/main/java/org/maturita/maturita/AppViewModel.kt @@ -176,6 +176,26 @@ class AppViewModel(app: Application) : AndroidViewModel(app) { refresh() } + fun markFyz(id: Int) { + progress.markFyz(id) + refresh() + } + + fun markFyz2(id: Int) { + progress.markFyz2(id) + refresh() + } + + fun markFyz3(id: Int) { + progress.markFyz3(id) + refresh() + } + + fun markFyz4(id: Int) { + progress.markFyz4(id) + refresh() + } + fun markMluv(n: Int) { progress.markMluv(n) refresh() diff --git a/android/app/src/main/java/org/maturita/maturita/MainActivity.kt b/android/app/src/main/java/org/maturita/maturita/MainActivity.kt index ecb2ddc..2ac0cab 100644 --- a/android/app/src/main/java/org/maturita/maturita/MainActivity.kt +++ b/android/app/src/main/java/org/maturita/maturita/MainActivity.kt @@ -32,6 +32,8 @@ import org.maturita.maturita.ui.HomeScreen import org.maturita.maturita.ui.HwQuizScreen import org.maturita.maturita.ui.HwYearsScreen import org.maturita.maturita.ui.LessonListScreen +import org.maturita.maturita.ui.FyzQuizScreen +import org.maturita.maturita.ui.FyzYearsScreen import org.maturita.maturita.ui.LitYearsScreen import org.maturita.maturita.ui.LessonRow import org.maturita.maturita.ui.LitQuizScreen @@ -348,6 +350,63 @@ private fun MaturitaAppBody(vm: AppViewModel) { } } is Route.Lit4Ex -> LitQuizScreen(vm, 4, r.id) + Route.FyzYears -> FyzYearsScreen(vm) + Route.FyzMap -> LessonListScreen( + vm, vm.tr("fyz_year1"), vm.tr("fyz_sub"), + vm.content.fyz.map { l -> + val id = l.int("id") + LessonRow(vm.tr(l.str("titleKey")), vm.progress.fyzDone(id), Route.FyzLesson(id)) + }, + ) + is Route.FyzLesson -> { + val l = vm.content.fyzLesson(r.id) + if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) { + vm.replace(Route.FyzEx(r.id)) + } + } + is Route.FyzEx -> FyzQuizScreen(vm, 1, r.id) + Route.Fyz2Map -> LessonListScreen( + vm, vm.tr("fyz_year2"), vm.tr("fyz2_sub"), + vm.content.fyz2.map { l -> + val id = l.int("id") + LessonRow(vm.tr(l.str("titleKey")), vm.progress.fyz2Done(id), Route.Fyz2Lesson(id)) + }, + ) + is Route.Fyz2Lesson -> { + val l = vm.content.fyz2Lesson(r.id) + if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) { + vm.replace(Route.Fyz2Ex(r.id)) + } + } + is Route.Fyz2Ex -> FyzQuizScreen(vm, 2, r.id) + Route.Fyz3Map -> LessonListScreen( + vm, vm.tr("fyz_year3"), vm.tr("fyz3_sub"), + vm.content.fyz3.map { l -> + val id = l.int("id") + LessonRow(vm.tr(l.str("titleKey")), vm.progress.fyz3Done(id), Route.Fyz3Lesson(id)) + }, + ) + is Route.Fyz3Lesson -> { + val l = vm.content.fyz3Lesson(r.id) + if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) { + vm.replace(Route.Fyz3Ex(r.id)) + } + } + is Route.Fyz3Ex -> FyzQuizScreen(vm, 3, r.id) + Route.Fyz4Map -> LessonListScreen( + vm, vm.tr("fyz_year4"), vm.tr("fyz4_sub"), + vm.content.fyz4.map { l -> + val id = l.int("id") + LessonRow(vm.tr(l.str("titleKey")), vm.progress.fyz4Done(id), Route.Fyz4Lesson(id)) + }, + ) + is Route.Fyz4Lesson -> { + val l = vm.content.fyz4Lesson(r.id) + if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) { + vm.replace(Route.Fyz4Ex(r.id)) + } + } + is Route.Fyz4Ex -> FyzQuizScreen(vm, 4, r.id) Route.SciMap -> SciMapScreen(vm) Route.ChemMap -> LessonListScreen( vm, vm.tr("Chemie"), vm.tr("chem_sub"), diff --git a/android/app/src/main/java/org/maturita/maturita/Route.kt b/android/app/src/main/java/org/maturita/maturita/Route.kt index 2e0a5d2..9862a1e 100644 --- a/android/app/src/main/java/org/maturita/maturita/Route.kt +++ b/android/app/src/main/java/org/maturita/maturita/Route.kt @@ -50,6 +50,19 @@ sealed class Route(val page: String) { data object BioMap : Route("biomap") data class BioLesson(val id: Int) : Route("biounit$id") data class BioEx(val id: Int) : Route("bioex$id") + data object FyzYears : Route("fyzyears") + data object FyzMap : Route("fyzmap") + data class FyzLesson(val id: Int) : Route("fyzunit$id") + data class FyzEx(val id: Int) : Route("fyzex$id") + data object Fyz2Map : Route("fyz2map") + data class Fyz2Lesson(val id: Int) : Route("fyz2unit$id") + data class Fyz2Ex(val id: Int) : Route("fyz2ex$id") + data object Fyz3Map : Route("fyz3map") + data class Fyz3Lesson(val id: Int) : Route("fyz3unit$id") + data class Fyz3Ex(val id: Int) : Route("fyz3ex$id") + data object Fyz4Map : Route("fyz4map") + data class Fyz4Lesson(val id: Int) : Route("fyz4unit$id") + data class Fyz4Ex(val id: Int) : Route("fyz4ex$id") data object CzechMap : Route("czechmap") data object Mluvnice : Route("mluvnice") data class MluvEx(val n: Int) : Route("mluve$n") @@ -68,7 +81,9 @@ sealed class Route(val page: String) { this is NetLesson || this is HwLesson || this is OnLesson || this is On2Lesson || this is On3Lesson || this is On4Lesson || this is LitLesson || this is LitEx || this is Lit2Lesson || this is Lit2Ex || this is Lit3Lesson || this is Lit3Ex || this is Lit4Lesson || this is Lit4Ex || - this is ChemLesson || this is ChemEx || this is BioLesson || this is BioEx + this is ChemLesson || this is ChemEx || this is BioLesson || this is BioEx || + this is FyzLesson || this is FyzEx || this is Fyz2Lesson || this is Fyz2Ex || + this is Fyz3Lesson || this is Fyz3Ex || this is Fyz4Lesson || this is Fyz4Ex /** Full stack from the practice root to this screen, so back always goes up one level. */ fun stack(): List = when (this) { @@ -120,6 +135,19 @@ sealed class Route(val page: String) { BioMap -> listOf(Subjects, SciMap, BioMap) is BioLesson -> listOf(Subjects, SciMap, BioMap, this) is BioEx -> listOf(Subjects, SciMap, BioMap, BioLesson(id), this) + FyzYears -> listOf(Subjects, FyzYears) + FyzMap -> listOf(Subjects, FyzYears, FyzMap) + is FyzLesson -> listOf(Subjects, FyzYears, FyzMap, this) + is FyzEx -> listOf(Subjects, FyzYears, FyzMap, FyzLesson(id), this) + Fyz2Map -> listOf(Subjects, FyzYears, Fyz2Map) + is Fyz2Lesson -> listOf(Subjects, FyzYears, Fyz2Map, this) + is Fyz2Ex -> listOf(Subjects, FyzYears, Fyz2Map, Fyz2Lesson(id), this) + Fyz3Map -> listOf(Subjects, FyzYears, Fyz3Map) + is Fyz3Lesson -> listOf(Subjects, FyzYears, Fyz3Map, this) + is Fyz3Ex -> listOf(Subjects, FyzYears, Fyz3Map, Fyz3Lesson(id), this) + Fyz4Map -> listOf(Subjects, FyzYears, Fyz4Map) + is Fyz4Lesson -> listOf(Subjects, FyzYears, Fyz4Map, this) + is Fyz4Ex -> listOf(Subjects, FyzYears, Fyz4Map, Fyz4Lesson(id), this) CzechMap -> listOf(Subjects, CzechMap) Mluvnice -> listOf(Subjects, CzechMap, Mluvnice) is MluvEx -> listOf(Subjects, CzechMap, Mluvnice, this) @@ -153,6 +181,11 @@ fun routeFromPage(page: String): Route? = when (page) { "scimap" -> Route.SciMap "chemmap" -> Route.ChemMap "biomap" -> Route.BioMap + "fyzyears" -> Route.FyzYears + "fyzmap" -> Route.FyzMap + "fyz2map" -> Route.Fyz2Map + "fyz3map" -> Route.Fyz3Map + "fyz4map" -> Route.Fyz4Map "czechmap" -> Route.CzechMap "mluvnice" -> Route.Mluvnice "readinglist" -> Route.ReadingList @@ -196,6 +229,30 @@ fun routeFromPage(page: String): Route? = when (page) { Regex("""bioex(\d+)""").matchEntire(page)?.let { return Route.BioEx(it.groupValues[1].toInt()) } + Regex("""fyz4unit(\d+)""").matchEntire(page)?.let { + return Route.Fyz4Lesson(it.groupValues[1].toInt()) + } + Regex("""fyz4ex(\d+)""").matchEntire(page)?.let { + return Route.Fyz4Ex(it.groupValues[1].toInt()) + } + Regex("""fyz3unit(\d+)""").matchEntire(page)?.let { + return Route.Fyz3Lesson(it.groupValues[1].toInt()) + } + Regex("""fyz3ex(\d+)""").matchEntire(page)?.let { + return Route.Fyz3Ex(it.groupValues[1].toInt()) + } + Regex("""fyz2unit(\d+)""").matchEntire(page)?.let { + return Route.Fyz2Lesson(it.groupValues[1].toInt()) + } + Regex("""fyz2ex(\d+)""").matchEntire(page)?.let { + return Route.Fyz2Ex(it.groupValues[1].toInt()) + } + Regex("""fyzunit(\d+)""").matchEntire(page)?.let { + return Route.FyzLesson(it.groupValues[1].toInt()) + } + Regex("""fyzex(\d+)""").matchEntire(page)?.let { + return Route.FyzEx(it.groupValues[1].toInt()) + } Regex("""lit4unit(\d+)""").matchEntire(page)?.let { return Route.Lit4Lesson(it.groupValues[1].toInt()) } diff --git a/android/app/src/main/java/org/maturita/maturita/data/Content.kt b/android/app/src/main/java/org/maturita/maturita/data/Content.kt index c12900e..a496309 100644 --- a/android/app/src/main/java/org/maturita/maturita/data/Content.kt +++ b/android/app/src/main/java/org/maturita/maturita/data/Content.kt @@ -39,6 +39,10 @@ class Content(root: JSONObject) { val lit4 = raw.arr("lit4") val chem = raw.arr("chem") val bio = raw.arr("bio") + val fyz = raw.arr("fyz") + val fyz2 = raw.arr("fyz2") + val fyz3 = raw.arr("fyz3") + val fyz4 = raw.arr("fyz4") val mluvnice = raw.arr("mluvnice") val books = raw.arr("books") val changelog = raw.arr("changelog") @@ -56,6 +60,10 @@ class Content(root: JSONObject) { fun lit4Lesson(id: Int) = lit4.firstOrNull { it.int("id") == id } fun chemLesson(id: Int) = chem.firstOrNull { it.int("id") == id } fun bioLesson(id: Int) = bio.firstOrNull { it.int("id") == id } + fun fyzLesson(id: Int) = fyz.firstOrNull { it.int("id") == id } + fun fyz2Lesson(id: Int) = fyz2.firstOrNull { it.int("id") == id } + fun fyz3Lesson(id: Int) = fyz3.firstOrNull { it.int("id") == id } + fun fyz4Lesson(id: Int) = fyz4.firstOrNull { it.int("id") == id } fun mluv(n: Int) = mluvnice.firstOrNull { it.int("id") == n } fun book(id: String) = books.firstOrNull { it.str("id") == id } diff --git a/android/app/src/main/java/org/maturita/maturita/data/ProgressStore.kt b/android/app/src/main/java/org/maturita/maturita/data/ProgressStore.kt index 80a6d7d..d6daf2d 100644 --- a/android/app/src/main/java/org/maturita/maturita/data/ProgressStore.kt +++ b/android/app/src/main/java/org/maturita/maturita/data/ProgressStore.kt @@ -57,6 +57,14 @@ class ProgressStore(context: Context) { fun markChem(id: Int) { prefs.edit().putBoolean("chem.$id", true).apply() } fun bioDone(id: Int) = prefs.getBoolean("bio.$id", false) fun markBio(id: Int) { prefs.edit().putBoolean("bio.$id", true).apply() } + fun fyzDone(id: Int) = prefs.getBoolean("fyz.$id", false) + fun markFyz(id: Int) { prefs.edit().putBoolean("fyz.$id", true).apply() } + fun fyz2Done(id: Int) = prefs.getBoolean("fyz2.$id", false) + fun markFyz2(id: Int) { prefs.edit().putBoolean("fyz2.$id", true).apply() } + fun fyz3Done(id: Int) = prefs.getBoolean("fyz3.$id", false) + fun markFyz3(id: Int) { prefs.edit().putBoolean("fyz3.$id", true).apply() } + fun fyz4Done(id: Int) = prefs.getBoolean("fyz4.$id", false) + fun markFyz4(id: Int) { prefs.edit().putBoolean("fyz4.$id", true).apply() } fun mluvDone(n: Int) = prefs.getBoolean("mluv.$n", false) fun markMluv(n: Int) { prefs.edit().putBoolean("mluv.$n", true).apply() } @@ -176,6 +184,38 @@ fun summarize(content: Content, p: ProgressStore): ProgressSum { sum.doneUnits += 1 } } + content.fyz.forEach { l -> + sum.totalEx += 1 + sum.openUnits += 1 + if (p.fyzDone(l.int("id"))) { + sum.doneEx += 1 + sum.doneUnits += 1 + } + } + content.fyz2.forEach { l -> + sum.totalEx += 1 + sum.openUnits += 1 + if (p.fyz2Done(l.int("id"))) { + sum.doneEx += 1 + sum.doneUnits += 1 + } + } + content.fyz3.forEach { l -> + sum.totalEx += 1 + sum.openUnits += 1 + if (p.fyz3Done(l.int("id"))) { + sum.doneEx += 1 + sum.doneUnits += 1 + } + } + content.fyz4.forEach { l -> + sum.totalEx += 1 + sum.openUnits += 1 + if (p.fyz4Done(l.int("id"))) { + sum.doneEx += 1 + sum.doneUnits += 1 + } + } sum.totalEx += content.mluvnice.size sum.openUnits += 1 val md = content.mluvnice.count { p.mluvDone(it.int("id")) } diff --git a/android/app/src/main/java/org/maturita/maturita/ui/Exercises.kt b/android/app/src/main/java/org/maturita/maturita/ui/Exercises.kt index 2e87f55..e6b1e2f 100644 --- a/android/app/src/main/java/org/maturita/maturita/ui/Exercises.kt +++ b/android/app/src/main/java/org/maturita/maturita/ui/Exercises.kt @@ -863,6 +863,30 @@ fun SciQuizScreen(vm: AppViewModel, area: String, id: Int) { } } +@Composable +fun FyzQuizScreen(vm: AppViewModel, year: Int, id: Int) { + val lesson = when (year) { + 2 -> vm.content.fyz2Lesson(id) + 3 -> vm.content.fyz3Lesson(id) + 4 -> vm.content.fyz4Lesson(id) + else -> vm.content.fyzLesson(id) + } ?: return + val spec = J(org.json.JSONObject().apply { + put("type", "choice") + put("title", vm.tr(lesson.str("exTitleKey"))) + put("sub", lesson.str("quizHeadKey")) + put("questions", lesson.o.optJSONArray("quiz")) + }) + DispatchExercise(vm, spec, vm.tr(lesson.str("exTitleKey"))) { + when (year) { + 2 -> vm.markFyz2(id) + 3 -> vm.markFyz3(id) + 4 -> vm.markFyz4(id) + else -> vm.markFyz(id) + } + } +} + @Composable fun LitQuizScreen(vm: AppViewModel, year: Int, id: Int) { val lesson = when (year) { diff --git a/android/app/src/main/java/org/maturita/maturita/ui/Screens.kt b/android/app/src/main/java/org/maturita/maturita/ui/Screens.kt index 5dd9789..c719dc2 100644 --- a/android/app/src/main/java/org/maturita/maturita/ui/Screens.kt +++ b/android/app/src/main/java/org/maturita/maturita/ui/Screens.kt @@ -25,6 +25,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.MenuBook +import androidx.compose.material.icons.filled.Bolt import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.FormatListNumbered @@ -222,6 +223,7 @@ private fun SubjectIcon(icon: String, open: Boolean) { "people" -> Icon(Icons.Filled.Groups, null, tint = tint, modifier = Modifier.size(26.dp)) "cz" -> CzechFlag(28.dp) "flask" -> Icon(Icons.Filled.Science, null, tint = tint, modifier = Modifier.size(26.dp)) + "bolt" -> Icon(Icons.Filled.Bolt, null, tint = tint, modifier = Modifier.size(26.dp)) else -> Icon(Icons.Filled.Lock, null, tint = tint) } } @@ -314,6 +316,13 @@ private fun subjectSum(vm: AppViewModel, s: J): ProgressSum { sum.doneEx = vm.content.chem.count { vm.progress.chemDone(it.int("id")) } + vm.content.bio.count { vm.progress.bioDone(it.int("id")) } } + "fyzyears", "fyzmap", "fyz2map", "fyz3map", "fyz4map" -> { + sum.totalEx = vm.content.fyz.size + vm.content.fyz2.size + vm.content.fyz3.size + vm.content.fyz4.size + sum.doneEx = vm.content.fyz.count { vm.progress.fyzDone(it.int("id")) } + + vm.content.fyz2.count { vm.progress.fyz2Done(it.int("id")) } + + vm.content.fyz3.count { vm.progress.fyz3Done(it.int("id")) } + + vm.content.fyz4.count { vm.progress.fyz4Done(it.int("id")) } + } "czechmap" -> { val litDone = vm.content.lit.count { vm.progress.litDone(it.int("id")) } + vm.content.lit2.count { vm.progress.lit2Done(it.int("id")) } + @@ -547,6 +556,27 @@ private fun buildSearch(vm: AppViewModel): List { add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "lit4unit${l.int("id")}", extra = "literatura") add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "lit4ex${l.int("id")}") } + add(vm.tr("Fyzika"), vm.tr("search_lesson"), "fyzyears", extra = "fyzika") + add(vm.tr("fyz_year1"), vm.tr("search_lesson"), "fyzmap", extra = "rocnik") + add(vm.tr("fyz_year2"), vm.tr("search_lesson"), "fyz2map", extra = "rocnik") + add(vm.tr("fyz_year3"), vm.tr("search_lesson"), "fyz3map", extra = "rocnik") + add(vm.tr("fyz_year4"), vm.tr("search_lesson"), "fyz4map", extra = "rocnik") + vm.content.fyz.forEach { l -> + add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyzunit${l.int("id")}", extra = "fyzika") + add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyzex${l.int("id")}") + } + vm.content.fyz2.forEach { l -> + add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyz2unit${l.int("id")}", extra = "fyzika") + add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyz2ex${l.int("id")}") + } + vm.content.fyz3.forEach { l -> + add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyz3unit${l.int("id")}", extra = "fyzika") + add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyz3ex${l.int("id")}") + } + vm.content.fyz4.forEach { l -> + add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyz4unit${l.int("id")}", extra = "fyzika") + add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyz4ex${l.int("id")}") + } add(vm.tr("Chemie"), vm.tr("search_lesson"), "chemmap", extra = "chemie") add(vm.tr("Biologie"), vm.tr("search_lesson"), "biomap", extra = "biologie") vm.content.chem.forEach { l -> @@ -683,6 +713,20 @@ fun LitYearsScreen(vm: AppViewModel) { } } +@Composable +fun FyzYearsScreen(vm: AppViewModel) { + DetailScaffold(vm, vm.tr("Fyzika"), vm.tr("fyz_years_sub")) { inner -> + LazyColumn(contentPadding = PaddingValues(top = inner.calculateTopPadding(), bottom = inner.calculateBottomPadding() + 16.dp)) { + item { + NavRow(title = vm.tr("fyz_year1"), subtitle = vm.tr("fyz_sub"), leading = { Text("1.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.FyzMap) } + NavRow(title = vm.tr("fyz_year2"), subtitle = vm.tr("fyz2_sub"), leading = { Text("2.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Fyz2Map) } + NavRow(title = vm.tr("fyz_year3"), subtitle = vm.tr("fyz3_sub"), leading = { Text("3.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Fyz3Map) } + NavRow(title = vm.tr("fyz_year4"), subtitle = vm.tr("fyz4_sub"), leading = { Text("4.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Fyz4Map) } + } + } + } +} + @Composable fun SciMapScreen(vm: AppViewModel) { DetailScaffold(vm, vm.tr("Základy Přírodopisných věd"), vm.tr("sci_sub")) { inner -> diff --git a/data/changelog.txt b/data/changelog.txt index 29ffc1c..9357cc3 100644 --- a/data/changelog.txt +++ b/data/changelog.txt @@ -6,6 +6,12 @@ # cs: bullet # en: bullet +--- 2026-10-06 +cs: Fyzika: čtyři ročníky, 32 lekcí podle osnov středních škol +cs: Od mechaniky přes teplo a elektřinu až po optiku, jádro a astrofyziku +en: Physics: four years, 32 lessons from the secondary-school syllabus +en: From mechanics through heat and electricity to optics, the nucleus and astrophysics + --- 2026-10-06 cs: Základy přírodopisných věd: chemie a biologie, v každé 8 lekcí podle osnov cs: Každá lekce má výklad a kvíz se čtyřmi otázkami diff --git a/ios/Graduately/AppModel.swift b/ios/Graduately/AppModel.swift index a46f6fe..fe401e2 100644 --- a/ios/Graduately/AppModel.swift +++ b/ios/Graduately/AppModel.swift @@ -178,6 +178,26 @@ final class AppModel: ObservableObject { refresh() } + func markFyz(_ id: Int) { + progress.markFyz(id) + refresh() + } + + func markFyz2(_ id: Int) { + progress.markFyz2(id) + refresh() + } + + func markFyz3(_ id: Int) { + progress.markFyz3(id) + refresh() + } + + func markFyz4(_ id: Int) { + progress.markFyz4(id) + refresh() + } + func markMluv(_ n: Int) { progress.markMluv(n) refresh() diff --git a/ios/Graduately/Data/Content.swift b/ios/Graduately/Data/Content.swift index 5a80bd7..de3a541 100644 --- a/ios/Graduately/Data/Content.swift +++ b/ios/Graduately/Data/Content.swift @@ -41,6 +41,10 @@ final class Content { let lit4: [J] let chem: [J] let bio: [J] + let fyz: [J] + let fyz2: [J] + let fyz3: [J] + let fyz4: [J] let mluvnice: [J] let books: [J] let changelog: [J] @@ -82,6 +86,10 @@ final class Content { lit4 = raw.arr("lit4") chem = raw.arr("chem") bio = raw.arr("bio") + fyz = raw.arr("fyz") + fyz2 = raw.arr("fyz2") + fyz3 = raw.arr("fyz3") + fyz4 = raw.arr("fyz4") mluvnice = raw.arr("mluvnice") books = raw.arr("books") changelog = raw.arr("changelog") @@ -100,6 +108,10 @@ final class Content { func lit4Lesson(_ id: Int) -> J? { lit4.first { $0.int("id") == id } } func chemLesson(_ id: Int) -> J? { chem.first { $0.int("id") == id } } func bioLesson(_ id: Int) -> J? { bio.first { $0.int("id") == id } } + func fyzLesson(_ id: Int) -> J? { fyz.first { $0.int("id") == id } } + func fyz2Lesson(_ id: Int) -> J? { fyz2.first { $0.int("id") == id } } + func fyz3Lesson(_ id: Int) -> J? { fyz3.first { $0.int("id") == id } } + func fyz4Lesson(_ id: Int) -> J? { fyz4.first { $0.int("id") == id } } func mluv(_ n: Int) -> J? { mluvnice.first { $0.int("id") == n } } func book(_ id: String) -> J? { books.first { $0.str("id") == id } } diff --git a/ios/Graduately/Data/ProgressStore.swift b/ios/Graduately/Data/ProgressStore.swift index e806a67..22c4707 100644 --- a/ios/Graduately/Data/ProgressStore.swift +++ b/ios/Graduately/Data/ProgressStore.swift @@ -59,6 +59,14 @@ final class ProgressStore { func markChem(_ id: Int) { defaults.set(true, forKey: "chem.\(id)") } func bioDone(_ id: Int) -> Bool { defaults.bool(forKey: "bio.\(id)") } func markBio(_ id: Int) { defaults.set(true, forKey: "bio.\(id)") } + func fyzDone(_ id: Int) -> Bool { defaults.bool(forKey: "fyz.\(id)") } + func markFyz(_ id: Int) { defaults.set(true, forKey: "fyz.\(id)") } + func fyz2Done(_ id: Int) -> Bool { defaults.bool(forKey: "fyz2.\(id)") } + func markFyz2(_ id: Int) { defaults.set(true, forKey: "fyz2.\(id)") } + func fyz3Done(_ id: Int) -> Bool { defaults.bool(forKey: "fyz3.\(id)") } + func markFyz3(_ id: Int) { defaults.set(true, forKey: "fyz3.\(id)") } + func fyz4Done(_ id: Int) -> Bool { defaults.bool(forKey: "fyz4.\(id)") } + func markFyz4(_ id: Int) { defaults.set(true, forKey: "fyz4.\(id)") } func mluvDone(_ n: Int) -> Bool { defaults.bool(forKey: "mluv.\(n)") } func markMluv(_ n: Int) { defaults.set(true, forKey: "mluv.\(n)") } @@ -183,6 +191,38 @@ func summarize(_ content: Content, _ p: ProgressStore) -> ProgressSum { sum.doneUnits += 1 } } + for l in content.fyz { + sum.totalEx += 1 + sum.openUnits += 1 + if p.fyzDone(l.int("id")) { + sum.doneEx += 1 + sum.doneUnits += 1 + } + } + for l in content.fyz2 { + sum.totalEx += 1 + sum.openUnits += 1 + if p.fyz2Done(l.int("id")) { + sum.doneEx += 1 + sum.doneUnits += 1 + } + } + for l in content.fyz3 { + sum.totalEx += 1 + sum.openUnits += 1 + if p.fyz3Done(l.int("id")) { + sum.doneEx += 1 + sum.doneUnits += 1 + } + } + for l in content.fyz4 { + sum.totalEx += 1 + sum.openUnits += 1 + if p.fyz4Done(l.int("id")) { + sum.doneEx += 1 + sum.doneUnits += 1 + } + } sum.totalEx += content.mluvnice.count sum.openUnits += 1 let md = content.mluvnice.filter { p.mluvDone($0.int("id")) }.count diff --git a/ios/Graduately/Route.swift b/ios/Graduately/Route.swift index 2ef7701..9e6e42a 100644 --- a/ios/Graduately/Route.swift +++ b/ios/Graduately/Route.swift @@ -56,6 +56,19 @@ enum Route: Hashable { case bioMap case bioLesson(Int) case bioEx(Int) + case fyzYears + case fyzMap + case fyzLesson(Int) + case fyzEx(Int) + case fyz2Map + case fyz2Lesson(Int) + case fyz2Ex(Int) + case fyz3Map + case fyz3Lesson(Int) + case fyz3Ex(Int) + case fyz4Map + case fyz4Lesson(Int) + case fyz4Ex(Int) case czechMap case mluvnice case mluvEx(Int) @@ -114,6 +127,19 @@ enum Route: Hashable { case .bioMap: return "biomap" case .bioLesson(let id): return "biounit\(id)" case .bioEx(let id): return "bioex\(id)" + case .fyzYears: return "fyzyears" + case .fyzMap: return "fyzmap" + case .fyzLesson(let id): return "fyzunit\(id)" + case .fyzEx(let id): return "fyzex\(id)" + case .fyz2Map: return "fyz2map" + case .fyz2Lesson(let id): return "fyz2unit\(id)" + case .fyz2Ex(let id): return "fyz2ex\(id)" + case .fyz3Map: return "fyz3map" + case .fyz3Lesson(let id): return "fyz3unit\(id)" + case .fyz3Ex(let id): return "fyz3ex\(id)" + case .fyz4Map: return "fyz4map" + case .fyz4Lesson(let id): return "fyz4unit\(id)" + case .fyz4Ex(let id): return "fyz4ex\(id)" case .czechMap: return "czechmap" case .mluvnice: return "mluvnice" case .mluvEx(let n): return "mluve\(n)" @@ -228,6 +254,32 @@ enum Route: Hashable { return [.sciMap, .bioMap, .bioLesson(id)] case .bioEx(let id): return [.sciMap, .bioMap, .bioLesson(id), .bioEx(id)] + case .fyzYears: + return [.fyzYears] + case .fyzMap: + return [.fyzYears, .fyzMap] + case .fyzLesson(let id): + return [.fyzYears, .fyzMap, .fyzLesson(id)] + case .fyzEx(let id): + return [.fyzYears, .fyzMap, .fyzLesson(id), .fyzEx(id)] + case .fyz2Map: + return [.fyzYears, .fyz2Map] + case .fyz2Lesson(let id): + return [.fyzYears, .fyz2Map, .fyz2Lesson(id)] + case .fyz2Ex(let id): + return [.fyzYears, .fyz2Map, .fyz2Lesson(id), .fyz2Ex(id)] + case .fyz3Map: + return [.fyzYears, .fyz3Map] + case .fyz3Lesson(let id): + return [.fyzYears, .fyz3Map, .fyz3Lesson(id)] + case .fyz3Ex(let id): + return [.fyzYears, .fyz3Map, .fyz3Lesson(id), .fyz3Ex(id)] + case .fyz4Map: + return [.fyzYears, .fyz4Map] + case .fyz4Lesson(let id): + return [.fyzYears, .fyz4Map, .fyz4Lesson(id)] + case .fyz4Ex(let id): + return [.fyzYears, .fyz4Map, .fyz4Lesson(id), .fyz4Ex(id)] case .czechMap: return [.czechMap] case .mluvnice: @@ -269,6 +321,11 @@ func routeFromPage(_ page: String) -> Route? { case "scimap": return .sciMap case "chemmap": return .chemMap case "biomap": return .bioMap + case "fyzyears": return .fyzYears + case "fyzmap": return .fyzMap + case "fyz2map": return .fyz2Map + case "fyz3map": return .fyz3Map + case "fyz4map": return .fyz4Map case "czechmap": return .czechMap case "mluvnice": return .mluvnice case "readinglist": return .readingList @@ -324,6 +381,30 @@ func routeFromPage(_ page: String) -> Route? { if let m = page.wholeMatch(of: /bioex(\d+)/), let n = Int(m.1) { return .bioEx(n) } + if let m = page.wholeMatch(of: /fyz4unit(\d+)/), let n = Int(m.1) { + return .fyz4Lesson(n) + } + if let m = page.wholeMatch(of: /fyz4ex(\d+)/), let n = Int(m.1) { + return .fyz4Ex(n) + } + if let m = page.wholeMatch(of: /fyz3unit(\d+)/), let n = Int(m.1) { + return .fyz3Lesson(n) + } + if let m = page.wholeMatch(of: /fyz3ex(\d+)/), let n = Int(m.1) { + return .fyz3Ex(n) + } + if let m = page.wholeMatch(of: /fyz2unit(\d+)/), let n = Int(m.1) { + return .fyz2Lesson(n) + } + if let m = page.wholeMatch(of: /fyz2ex(\d+)/), let n = Int(m.1) { + return .fyz2Ex(n) + } + if let m = page.wholeMatch(of: /fyzunit(\d+)/), let n = Int(m.1) { + return .fyzLesson(n) + } + if let m = page.wholeMatch(of: /fyzex(\d+)/), let n = Int(m.1) { + return .fyzEx(n) + } if let m = page.wholeMatch(of: /lit4unit(\d+)/), let n = Int(m.1) { return .lit4Lesson(n) } diff --git a/ios/Graduately/UI/Components.swift b/ios/Graduately/UI/Components.swift index 4aba45b..236a2ac 100644 --- a/ios/Graduately/UI/Components.swift +++ b/ios/Graduately/UI/Components.swift @@ -608,6 +608,7 @@ func subjectSymbol(_ icon: String) -> String { case "cz": return "text.book.closed.fill" case "people": return "person.2.fill" case "flask": return "flask.fill" + case "bolt": return "bolt.fill" default: return "lock.fill" } } diff --git a/ios/Graduately/UI/ExercisesMore.swift b/ios/Graduately/UI/ExercisesMore.swift index bd32f71..2b1f3ce 100644 --- a/ios/Graduately/UI/ExercisesMore.swift +++ b/ios/Graduately/UI/ExercisesMore.swift @@ -646,6 +646,39 @@ struct SciQuizScreen: View { } } +struct FyzQuizScreen: View { + @ObservedObject var vm: AppModel + let year: Int + let id: Int + + var body: some View { + let lesson: J? = { + switch year { + case 2: return vm.content.fyz2Lesson(id) + case 3: return vm.content.fyz3Lesson(id) + case 4: return vm.content.fyz4Lesson(id) + default: return vm.content.fyzLesson(id) + } + }() + if let lesson { + let spec = J([ + "type": "choice", + "title": vm.tr(lesson.str("exTitleKey")), + "sub": lesson.str("quizHeadKey"), + "questions": lesson.rawArray("quiz"), + ]) + DispatchExercise(vm: vm, spec: spec, titleFallback: vm.tr(lesson.str("exTitleKey"))) { + switch year { + case 2: vm.markFyz2(id) + case 3: vm.markFyz3(id) + case 4: vm.markFyz4(id) + default: vm.markFyz(id) + } + } + } + } +} + struct LitYearQuizScreen: View { @ObservedObject var vm: AppModel let year: Int diff --git a/ios/Graduately/UI/Screens.swift b/ios/Graduately/UI/Screens.swift index 4afc829..562786f 100644 --- a/ios/Graduately/UI/Screens.swift +++ b/ios/Graduately/UI/Screens.swift @@ -699,6 +699,12 @@ private func subjectSum(_ vm: AppModel, _ s: J) -> ProgressSum { sum.totalEx = vm.content.chem.count + vm.content.bio.count sum.doneEx = vm.content.chem.filter { vm.progress.chemDone($0.int("id")) }.count + vm.content.bio.filter { vm.progress.bioDone($0.int("id")) }.count + case "fyzyears", "fyzmap", "fyz2map", "fyz3map", "fyz4map": + sum.totalEx = vm.content.fyz.count + vm.content.fyz2.count + vm.content.fyz3.count + vm.content.fyz4.count + sum.doneEx = vm.content.fyz.filter { vm.progress.fyzDone($0.int("id")) }.count + + vm.content.fyz2.filter { vm.progress.fyz2Done($0.int("id")) }.count + + vm.content.fyz3.filter { vm.progress.fyz3Done($0.int("id")) }.count + + vm.content.fyz4.filter { vm.progress.fyz4Done($0.int("id")) }.count case "czechmap": let litCount = vm.content.lit.count + vm.content.lit2.count + vm.content.lit3.count + vm.content.lit4.count let litDone = vm.content.lit.filter { vm.progress.litDone($0.int("id")) }.count @@ -929,6 +935,27 @@ private func buildSearch(_ vm: AppModel) -> [Hit] { add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "lit4unit\(l.int("id"))", extra: "literatura") add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "lit4ex\(l.int("id"))") } + add(vm.tr("Fyzika"), vm.tr("search_lesson"), "fyzyears", extra: "fyzika") + add(vm.tr("fyz_year1"), vm.tr("search_lesson"), "fyzmap", extra: "rocnik") + add(vm.tr("fyz_year2"), vm.tr("search_lesson"), "fyz2map", extra: "rocnik") + add(vm.tr("fyz_year3"), vm.tr("search_lesson"), "fyz3map", extra: "rocnik") + add(vm.tr("fyz_year4"), vm.tr("search_lesson"), "fyz4map", extra: "rocnik") + for l in vm.content.fyz { + add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyzunit\(l.int("id"))", extra: "fyzika") + add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyzex\(l.int("id"))") + } + for l in vm.content.fyz2 { + add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyz2unit\(l.int("id"))", extra: "fyzika") + add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyz2ex\(l.int("id"))") + } + for l in vm.content.fyz3 { + add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyz3unit\(l.int("id"))", extra: "fyzika") + add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyz3ex\(l.int("id"))") + } + for l in vm.content.fyz4 { + add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyz4unit\(l.int("id"))", extra: "fyzika") + add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyz4ex\(l.int("id"))") + } add(vm.tr("Chemie"), vm.tr("search_lesson"), "chemmap", extra: "chemie") add(vm.tr("Biologie"), vm.tr("search_lesson"), "biomap", extra: "biologie") for l in vm.content.chem { @@ -1485,6 +1512,47 @@ struct LitYearsScreen: View { } } +struct FyzYearsScreen: View { + @ObservedObject var vm: AppModel + + var body: some View { + #if os(macOS) + let years: [(String, String, Route)] = [ + ("fyz_year1", "fyz_sub", .fyzMap), + ("fyz_year2", "fyz2_sub", .fyz2Map), + ("fyz_year3", "fyz3_sub", .fyz3Map), + ("fyz_year4", "fyz4_sub", .fyz4Map), + ] + MacFillGrid(count: years.count, minWidth: 260, minHeight: 180) { i in + NavigationLink(value: years[i].2) { + MacLinkCard(title: vm.tr(years[i].0), subtitle: vm.tr(years[i].1), badge: "\(i + 1).") + } + .buttonStyle(.plain) + } + .navigationTitle(vm.tr("Fyzika")) + .navigationSubtitle(vm.tr("fyz_years_sub")) + #else + List { + NavigationLink(value: Route.fyzMap) { + ListRowLabel(title: vm.tr("fyz_year1"), subtitle: vm.tr("fyz_sub")) + } + NavigationLink(value: Route.fyz2Map) { + ListRowLabel(title: vm.tr("fyz_year2"), subtitle: vm.tr("fyz2_sub")) + } + NavigationLink(value: Route.fyz3Map) { + ListRowLabel(title: vm.tr("fyz_year3"), subtitle: vm.tr("fyz3_sub")) + } + NavigationLink(value: Route.fyz4Map) { + ListRowLabel(title: vm.tr("fyz_year4"), subtitle: vm.tr("fyz4_sub")) + } + } + .navigationTitle(vm.tr("Fyzika")) + .navigationSubtitle(vm.tr("fyz_years_sub")) + .appListStyle() + #endif + } +} + struct SciMapScreen: View { @ObservedObject var vm: AppModel @@ -2253,6 +2321,92 @@ struct RouteDestination: View { } case .bioEx(let id): SciQuizScreen(vm: vm, area: "bio", id: id) + case .fyzYears: + FyzYearsScreen(vm: vm) + case .fyzMap: + LessonListScreen( + vm: vm, + title: vm.tr("fyz_year1"), + subtitle: vm.tr("fyz_sub"), + rows: vm.content.fyz.map { l in + let id = l.int("id") + return LessonRow( + id: "f\(id)", + title: vm.tr(l.str("titleKey")), + done: vm.progress.fyzDone(id), + destination: .fyzLesson(id) + ) + } + ) + case .fyzLesson(let id): + if let l = vm.content.fyzLesson(id) { + SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .fyzEx(id)) + } + case .fyzEx(let id): + FyzQuizScreen(vm: vm, year: 1, id: id) + case .fyz2Map: + LessonListScreen( + vm: vm, + title: vm.tr("fyz_year2"), + subtitle: vm.tr("fyz2_sub"), + rows: vm.content.fyz2.map { l in + let id = l.int("id") + return LessonRow( + id: "f2\(id)", + title: vm.tr(l.str("titleKey")), + done: vm.progress.fyz2Done(id), + destination: .fyz2Lesson(id) + ) + } + ) + case .fyz2Lesson(let id): + if let l = vm.content.fyz2Lesson(id) { + SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .fyz2Ex(id)) + } + case .fyz2Ex(let id): + FyzQuizScreen(vm: vm, year: 2, id: id) + case .fyz3Map: + LessonListScreen( + vm: vm, + title: vm.tr("fyz_year3"), + subtitle: vm.tr("fyz3_sub"), + rows: vm.content.fyz3.map { l in + let id = l.int("id") + return LessonRow( + id: "f3\(id)", + title: vm.tr(l.str("titleKey")), + done: vm.progress.fyz3Done(id), + destination: .fyz3Lesson(id) + ) + } + ) + case .fyz3Lesson(let id): + if let l = vm.content.fyz3Lesson(id) { + SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .fyz3Ex(id)) + } + case .fyz3Ex(let id): + FyzQuizScreen(vm: vm, year: 3, id: id) + case .fyz4Map: + LessonListScreen( + vm: vm, + title: vm.tr("fyz_year4"), + subtitle: vm.tr("fyz4_sub"), + rows: vm.content.fyz4.map { l in + let id = l.int("id") + return LessonRow( + id: "f4\(id)", + title: vm.tr(l.str("titleKey")), + done: vm.progress.fyz4Done(id), + destination: .fyz4Lesson(id) + ) + } + ) + case .fyz4Lesson(let id): + if let l = vm.content.fyz4Lesson(id) { + SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .fyz4Ex(id)) + } + case .fyz4Ex(let id): + FyzQuizScreen(vm: vm, year: 4, id: id) case .mluvnice: LessonListScreen( vm: vm, diff --git a/scripts/extract-android-content.py b/scripts/extract-android-content.py index 4584dd6..a55ab8b 100644 --- a/scripts/extract-android-content.py +++ b/scripts/extract-android-content.py @@ -1111,6 +1111,10 @@ def main() -> int: lit4_lessons = lit_pack("lit4") chem_lessons = lit_pack("chem") bio_lessons = lit_pack("bio") + fyz_lessons = lit_pack("fyz") + fyz2_lessons = lit_pack("fyz2") + fyz3_lessons = lit_pack("fyz3") + fyz4_lessons = lit_pack("fyz4") on_lessons = [] for i in range(1, 18): @@ -1560,7 +1564,7 @@ def main() -> int: {"key": "Občanská nauka", "open": True, "target": "onyears", "icon": "people"}, {"key": "English", "open": False, "target": None, "icon": "lock"}, {"key": "Matematika", "open": False, "target": None, "icon": "lock"}, - {"key": "Fyzika", "open": False, "target": None, "icon": "lock"}, + {"key": "Fyzika", "open": True, "target": "fyzyears", "icon": "bolt"}, {"key": "Základy Přírodopisných věd", "open": True, "target": "scimap", "icon": "flask"}, {"key": "Technická grafika", "open": False, "target": None, "icon": "lock"}, {"key": "Prezentační grafika", "open": False, "target": None, "icon": "lock"}, @@ -1585,6 +1589,10 @@ def main() -> int: "lit4": lit4_lessons, "chem": chem_lessons, "bio": bio_lessons, + "fyz": fyz_lessons, + "fyz2": fyz2_lessons, + "fyz3": fyz3_lessons, + "fyz4": fyz4_lessons, "on2": on2_lessons, "on3": on3_lessons, "on4": on4_lessons, @@ -1626,6 +1634,7 @@ def main() -> int: print(f" lit lessons: {sum(1 for L in lit_lessons + lit2_lessons + lit3_lessons + lit4_lessons if L['slides'])}") print(f" chem lessons: {sum(1 for L in chem_lessons if L['slides'])}") print(f" bio lessons: {sum(1 for L in bio_lessons if L['slides'])}") + print(f" fyz lessons: {sum(1 for L in fyz_lessons + fyz2_lessons + fyz3_lessons + fyz4_lessons if L['slides'])}") print(f" on2 lessons: {sum(1 for L in on2_lessons if L['slides'])}") print(f" on3 lessons: {sum(1 for L in on3_lessons if L['slides'])}") print(f" on4 lessons: {sum(1 for L in on4_lessons if L['slides'])}") @@ -1686,6 +1695,8 @@ def main() -> int: ("lit", lit_lessons), ("lit2", lit2_lessons), ("lit3", lit3_lessons), ("lit4", lit4_lessons), ("chem", chem_lessons), ("bio", bio_lessons), + ("fyz", fyz_lessons), ("fyz2", fyz2_lessons), + ("fyz3", fyz3_lessons), ("fyz4", fyz4_lessons), ): for L in block: if not L["slides"]: diff --git a/src/fyz_year1.c b/src/fyz_year1.c new file mode 100644 index 0000000..e673180 --- /dev/null +++ b/src/fyz_year1.c @@ -0,0 +1,615 @@ +#include "graduately.h" + +/* Year 1: mechanics. Quantities, kinematics, dynamics, energy, + * gravitation, rigid bodies, fluids. */ + +NetLesson fyz_lessons[FYZ_N] = { + { .n_slides = 3, .unit_page = "fyzunit1", .ex_page = "fyzex1" }, + { .n_slides = 3, .unit_page = "fyzunit2", .ex_page = "fyzex2" }, + { .n_slides = 3, .unit_page = "fyzunit3", .ex_page = "fyzex3" }, + { .n_slides = 3, .unit_page = "fyzunit4", .ex_page = "fyzex4" }, + { .n_slides = 3, .unit_page = "fyzunit5", .ex_page = "fyzex5" }, + { .n_slides = 3, .unit_page = "fyzunit6", .ex_page = "fyzex6" }, + { .n_slides = 3, .unit_page = "fyzunit7", .ex_page = "fyzex7" }, + { .n_slides = 3, .unit_page = "fyzunit8", .ex_page = "fyzex8" }, +}; + +static GtkWidget *build_fyz_unit1_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Měření", "Veličina a jednotka", + "Tip: základní jednotka hmotnosti je kilogram, ne gram.", + { + "Fyzikální veličina má číselnou hodnotu a jednotku.", + "Soustava SI má sedm základních jednotek: m, kg, s, A, K, mol a cd.", + "Odvozené jednotky vznikají ze základních, třeba newton nebo joule.", + "Předpona kilo znamená tisíc, mili znamená tisícinu.", + NULL, + }, + }, + { + "2 / 3 • Druhy", "Skalár a vektor", + "Tip: rychlost jako vektor má velikost i směr.", + { + "Skalár má jen velikost. Patří sem hmotnost, čas nebo teplota.", + "Vektor má velikost i směr. Patří sem síla, rychlost a zrychlení.", + "Vektory se skládají podle rovnoběžníku.", + "Opačný vektor má stejnou velikost a opačný směr.", + NULL, + }, + }, + { + "3 / 3 • Přesnost", "Chyba měření", + "Tip: relativní chyba se často udává v procentech.", + { + "Každé měření má chybu. Opakování měření ji zmenšuje.", + "Absolutní chyba je rozdíl naměřené a správné hodnoty.", + "Relativní chyba je podíl absolutní chyby a správné hodnoty.", + "Výsledek se zaokrouhluje podle přesnosti měřidla.", + NULL, + }, + }, + }; + + return fyz_unit_page(0, &fyz_lessons[0], "fyz_unit1", "fyz_unit1_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz_unit1_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Která jednotka je základní jednotka SI pro hmotnost?", + {"Gram", "Kilogram", "Newton", "Tuna"}, + 4, 1}, + {"Co má vektor navíc proti skaláru?", + {"Jen jednotku", "Směr", "Jen číselnou hodnotu", "Předponu kilo"}, + 4, 1}, + {"Co znamená předpona mili?", + {"Tisíc", "Milion", "Tisícinu", "Setinu"}, + 4, 2}, + {"Co je relativní chyba?", + {"Rozdíl naměřené a správné hodnoty", + "Podíl absolutní chyby a správné hodnoty", + "Součet všech měření", + "Nejmenší dílek měřidla"}, + 4, 1}, + }; + static const char *hints[] = { + "Gram je tisícina kilogramu. Newton je jednotka síly.", + "Hmotnost je skalár. Rychlost a síla jsou vektory.", + "Kilo je 1000, centi je 0,01, mili je 0,001.", + "Absolutní chyba je rozdíl. Relativní je tento rozdíl dělený správnou hodnotou.", + }; + + return fyz_mcq_page(0, 0, "fyzunit1", "fyz_ex1_title", "fyz_quiz1_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz_unit2_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Pohyb", "Dráha a rychlost", + "Tip: při rovnoměrném pohybu je rychlost stálá.", + { + "Trajektorie je čára, po které se bod pohybuje. Dráha je její délka.", + "Průměrná rychlost je dráha dělená časem.", + "Rovnoměrný přímočarý pohyb má stálou rychlost a přímou trajektorii.", + "Dráha rovnoměrného pohybu je s = v·t.", + NULL, + }, + }, + { + "2 / 3 • Změna rychlosti", "Zrychlení", + "Tip: jednotka zrychlení je metr za sekundu na druhou.", + { + "Zrychlení popisuje, jak rychle se mění rychlost.", + "Rovnoměrně zrychlený pohyb má stálé zrychlení.", + "Rychlost je v = v₀ + a·t.", + "Dráha z klidu je s = ½·a·t².", + NULL, + }, + }, + { + "3 / 3 • Pád", "Volný pád", + "Tip: tíhové zrychlení u Země je přibližně 9,81 m/s².", + { + "Volný pád je pohyb jen pod vlivem tíhového zrychlení.", + "Tíhové zrychlení g je u povrchu Země asi 9,81 m/s².", + "Odpor vzduchu volný pád zpomaluje. Ve vakuu padají tělesa stejně.", + "Graf rychlosti na čase je u rovnoměrně zrychleného pohybu přímka.", + NULL, + }, + }, + }; + + return fyz_unit_page(0, &fyz_lessons[1], "fyz_unit2", "fyz_unit2_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz_unit2_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jak se počítá dráha rovnoměrného pohybu?", + {"s = v / t", "s = v·t", "s = a·t", "s = v·a"}, + 4, 1}, + {"Jaká je jednotka zrychlení?", + {"m/s", "m/s²", "N", "J"}, + 4, 1}, + {"Čemu se rovná rychlost rovnoměrně zrychleného pohybu?", + {"v = v₀ + a·t", "v = s·t", "v = a / t", "v = g·m"}, + 4, 0}, + {"Jak velké je tíhové zrychlení u povrchu Země?", + {"Asi 1 m/s²", "Asi 9,81 m/s²", "Asi 100 m/s²", "Přesně rychlost světla"}, + 4, 1}, + }; + static const char *hints[] = { + "Rychlost je dráha za čas, proto dráha je rychlost krát čas.", + "Zrychlení je změna rychlosti za čas, tedy metr za sekundu za sekundu.", + "Počáteční rychlost se k přírůstku a·t přičítá.", + "Škola používá g ≈ 9,81 m/s², často i přibližně 10 m/s².", + }; + + return fyz_mcq_page(0, 1, "fyzunit2", "fyz_ex2_title", "fyz_quiz2_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz_unit3_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • První zákon", "Setrvačnost", + "Tip: zákon platí v inerciální soustavě.", + { + "Těleso zůstává v klidu nebo v rovnoměrném přímočarém pohybu, dokud na ně nepůsobí výsledná síla.", + "Setrvačnost je snaha tělesa udržet svůj pohybový stav.", + "Hmotnost je míra setrvačnosti.", + "Inerciální soustava se vůči jiné inerciální nepohybuje se zrychlením.", + NULL, + }, + }, + { + "2 / 3 • Druhý zákon", "Síla a zrychlení", + "Tip: newton je síla, která hmotnosti 1 kg udělí zrychlení 1 m/s².", + { + "Výsledná síla je F = m·a.", + "Větší síla při stejné hmotnosti znamená větší zrychlení.", + "Větší hmotnost při stejné síle znamená menší zrychlení.", + "Jednotka síly je newton, N.", + NULL, + }, + }, + { + "3 / 3 • Třetí zákon", "Akce a reakce", + "Tip: obě síly působí na dvě různá tělesa.", + { + "Síly, kterými na sebe dvě tělesa působí, jsou stejně velké a opačně orientované.", + "Akce a reakce vznikají i zanikají současně.", + "Navzájem se neruší, protože každá působí na jiné těleso.", + "Hybnost je p = m·v. V izolované soustavě se hybnost zachovává.", + NULL, + }, + }, + }; + + return fyz_unit_page(0, &fyz_lessons[2], "fyz_unit3", "fyz_unit3_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz_unit3_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Co říká první Newtonův zákon?", + {"F = m·a", + "Těleso nemění pohybový stav, dokud na ně nepůsobí výsledná síla", + "Akce a reakce se sčítají na jednom tělese", + "Hybnost se vždy ztrácí"}, + 4, 1}, + {"Jak zní druhý Newtonův zákon?", + {"F = m / a", "F = m·a", "F = m·v", "F = a / m"}, + 4, 1}, + {"Na co působí akce a reakce?", + {"Na jedno a totéž těleso a ruší se", + "Na dvě různá tělesa", + "Jen na těleso v klidu", + "Jen ve směru tíhy"}, + 4, 1}, + {"Co je hybnost?", + {"m·a", "m·v", "F·t jen jako jednotka", "m·g·h"}, + 4, 1}, + }; + static const char *hints[] = { + "První zákon je zákon setrvačnosti. Druhý je F = m·a.", + "Newton je kg·m/s². Síla a zrychlení mají stejný směr.", + "Reakce není síla, která by na stejném tělese rušila akci.", + "Hybnost je vektor. Izolovaná soustava ji zachovává.", + }; + + return fyz_mcq_page(0, 2, "fyzunit3", "fyz_ex3_title", "fyz_quiz3_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz_unit4_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Tíha", "Tíhová a třecí síla", + "Tip: tíhová síla je G = m·g.", + { + "Tíhová síla působí svisle dolů a je G = m·g.", + "Třecí síla působí proti pohybu nebo proti snaze o pohyb.", + "Smykové tření závisí na normálové síle a na součiniteli tření.", + "Valivé tření je obvykle menší než smykové.", + NULL, + }, + }, + { + "2 / 3 • Tlak", "Síla na plochu", + "Tip: pascal je newton na metr čtvereční.", + { + "Tlak je p = F / S, kde S je obsah plochy.", + "Stejná síla na menší plochu znamená větší tlak.", + "Jednotka tlaku je pascal, Pa.", + "Ostrý hrot má malou plochu, proto velký tlak.", + NULL, + }, + }, + { + "3 / 3 • Skládání", "Výslednice sil", + "Tip: dvě stejně velké opačné síly dají nulu.", + { + "Síly stejného směru se sčítají.", + "Síly opačného směru se odečítají.", + "Kolmé síly se skládají podle Pythagorovy věty.", + "Rovnováha nastane, když je výslednice nulová.", + NULL, + }, + }, + }; + + return fyz_unit_page(0, &fyz_lessons[3], "fyz_unit4", "fyz_unit4_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz_unit4_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jak se počítá tíhová síla?", + {"G = m / g", "G = m·g", "G = m·v", "G = F / S"}, + 4, 1}, + {"Na čem závisí smykové tření?", + {"Jen na barvě tělesa", + "Na normálové síle a součiniteli tření", + "Jen na objemu", + "Na rychlosti světla"}, + 4, 1}, + {"Jak se počítá tlak?", + {"p = F·S", "p = F / S", "p = m·a", "p = S / F"}, + 4, 1}, + {"Jaká je jednotka tlaku?", + {"Newton", "Pascal", "Joule", "Watt"}, + 4, 1}, + }; + static const char *hints[] = { + "g je tíhové zrychlení. Tíha má směr svisle dolů.", + "Ft = f·Fn. Drsnější styk má větší součinitel tření.", + "Menší plocha při stejné síle zvětší tlak.", + "1 Pa = 1 N/m². Atmosférický tlak je asi 101 kPa.", + }; + + return fyz_mcq_page(0, 3, "fyzunit4", "fyz_ex4_title", "fyz_quiz4_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz_unit5_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Práce", "Síla po dráze", + "Tip: síla kolmá k posunutí práci nekoná.", + { + "Mechanická práce je W = F·s·cos α.", + "α je úhel mezi silou a posunutím.", + "Když je síla kolmá k posunutí, práce je nulová.", + "Jednotka práce je joule, J.", + NULL, + }, + }, + { + "2 / 3 • Energie", "Pohybová a polohová", + "Tip: polohová energie roste s výškou.", + { + "Kinetická energie je Ek = ½·m·v².", + "Polohová energie v tíhovém poli je Ep = m·g·h.", + "Výšku h počítáme od zvolené hladiny.", + "Bez tření se součet kinetické a polohové energie zachovává.", + NULL, + }, + }, + { + "3 / 3 • Výkon", "Práce za čas", + "Tip: watt je joule za sekundu.", + { + "Výkon je P = W / t.", + "Jednotka výkonu je watt, W.", + "Účinnost je podíl užitečné a dodané energie.", + "Účinnost je vždy menší než 1, protože část energie se ztrácí.", + NULL, + }, + }, + }; + + return fyz_unit_page(0, &fyz_lessons[4], "fyz_unit5", "fyz_unit5_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz_unit5_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Kdy je mechanická práce nulová?", + {"Když je síla rovnoběžná s posunutím", + "Když je síla kolmá k posunutí", + "Když je hmotnost velká", + "Když je výkon 1 W"}, + 4, 1}, + {"Jak se počítá kinetická energie?", + {"Ek = m·g·h", "Ek = ½·m·v²", "Ek = F / s", "Ek = m·v"}, + 4, 1}, + {"Jak se počítá polohová energie v tíhovém poli?", + {"Ep = ½·m·v²", "Ep = m·g·h", "Ep = F·a", "Ep = p·V"}, + 4, 1}, + {"Co je výkon?", + {"Práce dělená časem", "Síla dělená plochou", + "Hmotnost krát zrychlení", "Hybnost za dráhu"}, + 4, 0}, + }; + static const char *hints[] = { + "cos 90° je 0, proto kolmá síla práci nekoná.", + "Dvojnásobná rychlost znamená čtyřnásobnou kinetickou energii.", + "Hladinu nulové polohové energie si volíme.", + "Watt je J/s. Účinnost je podíl energií, ne jejich součet.", + }; + + return fyz_mcq_page(0, 4, "fyzunit5", "fyz_ex5_title", "fyz_quiz5_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz_unit6_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Zákon", "Newtonova gravitace", + "Tip: gravitační síla klesá s druhou mocninou vzdálenosti.", + { + "Dvě tělesa se přitahují silou Fg = κ·m₁·m₂ / r².", + "κ je gravitační konstanta, asi 6,67·10⁻¹¹ N·m²/kg².", + "r je vzdálenost středů těles.", + "Gravitace působí na dálku a je vždy přitažlivá.", + NULL, + }, + }, + { + "2 / 3 • U Země", "Tíhové zrychlení", + "Tip: první kosmická rychlost je asi 7,9 km/s.", + { + "U povrchu Země je tíhové zrychlení přibližně 9,81 m/s².", + "Tíhová síla je výslednice gravitační síly a setrvačné síly od otáčení Země.", + "První kosmická rychlost je rychlost kruhové dráhy těsně u povrchu, asi 7,9 km/s.", + "Druhá kosmická rychlost, asi 11,2 km/s, stačí k opuštění Země.", + NULL, + }, + }, + { + "3 / 3 • Pohyby", "Keplerovy zákony", + "Tip: planety obíhají po elipsách, Slunce je v ohnisku.", + { + "Planety se pohybují po elipsách a Slunce je v jednom ohnisku.", + "Průvodič planety opíše za stejný čas stejnou plochu.", + "Poměr krychle velké poloosy a čtverce oběžné doby je pro planety stejný.", + "Družice padá kolem Země, proto zůstává na oběžné dráze.", + NULL, + }, + }, + }; + + return fyz_unit_page(0, &fyz_lessons[5], "fyz_unit6", "fyz_unit6_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz_unit6_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jak závisí gravitační síla na vzdálenosti?", + {"Je přímo úměrná vzdálenosti", + "Je nepřímo úměrná druhé mocnině vzdálenosti", + "Na vzdálenosti nezávisí", + "Je úměrná třetí mocnině vzdálenosti"}, + 4, 1}, + {"Jaká je přibližně gravitační konstanta κ?", + {"9,81", "6,67·10⁻¹¹ N·m²/kg²", "3·10⁸ m/s", "101 kPa"}, + 4, 1}, + {"Co je první kosmická rychlost?", + {"Rychlost světla", + "Rychlost kruhové dráhy těsně u Země, asi 7,9 km/s", + "Rychlost zvuku", + "Rychlost volného pádu z 1 m"}, + 4, 1}, + {"Jaký tvar mají dráhy planet podle Keplera?", + {"Kružnice se Sluncem ve středu vždy", + "Elipsy a Slunce je v ohnisku", + "Přímky", + "Paraboly se Zemí v ohnisku"}, + 4, 1}, + }; + static const char *hints[] = { + "Dvojnásobná vzdálenost znamená čtvrtinovou sílu.", + "κ je velmi malé číslo. g = 9,81 m/s² je zrychlení, ne konstanta κ.", + "Druhá kosmická rychlost je asi 11,2 km/s.", + "První Keplerův zákon mluví o elipse a ohnisku.", + }; + + return fyz_mcq_page(0, 5, "fyzunit6", "fyz_ex6_title", "fyz_quiz6_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz_unit7_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Otáčení", "Moment síly", + "Tip: moment je větší, když síla zabírá dál od osy.", + { + "Tuhé těleso nemění působením sil svůj tvar.", + "Moment síly je M = F·d, kde d je rameno síly.", + "Rameno je kolmá vzdálenost osy od přímky síly.", + "Jednotka momentu je newton metr, N·m.", + NULL, + }, + }, + { + "2 / 3 • Páka", "Rovnováha", + "Tip: na páce v rovnováze jsou momenty na obou stranách stejné.", + { + "Těleso je v rovnováze, když je výslednice sil i výsledný moment nulový.", + "Na dvojzvratné páce platí F₁·d₁ = F₂·d₂.", + "Páka, kladka a nakloněná rovina jsou jednoduché stroje.", + "Jednoduchý stroj práci neušetří, jen změní velikost síly nebo dráhu.", + NULL, + }, + }, + { + "3 / 3 • Těžiště", "Stabilita", + "Tip: těleso je stabilní, když se po malém vychýlení vrací.", + { + "Těžiště je působiště tíhové síly.", + "U stejnorodého pravidelného tělesa je těžiště ve středu.", + "Stabilní poloha se po vychýlení vrací, labilní se převrací.", + "Nízké těžiště a široká základna zvětšují stabilitu.", + NULL, + }, + }, + }; + + return fyz_unit_page(0, &fyz_lessons[6], "fyz_unit7", "fyz_unit7_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz_unit7_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jak se počítá moment síly?", + {"M = F / d", "M = F·d", "M = m·g·h", "M = p·V"}, + 4, 1}, + {"Kdy je páka v rovnováze?", + {"Když jsou síly vždy stejně velké bez ohledu na rameno", + "Když se momenty na obou stranách rovnají", + "Když je jedno rameno nulové", + "Když páka koná práci zdarma"}, + 4, 1}, + {"Co je těžiště?", + {"Místo největšího tlaku kapaliny", + "Působiště tíhové síly", + "Ohnisko elipsy", + "Střed elektrického náboje"}, + 4, 1}, + {"Která poloha je stabilní?", + {"Po malém vychýlení se těleso vrací", + "Po malém vychýlení se těleso převrátí", + "Těleso zůstane v nové poloze vždy", + "Těžiště je nad bodem dotyku co nejvýš a základna je bod"}, + 4, 0}, + }; + static const char *hints[] = { + "Delší rameno znamená větší moment při stejné síle.", + "F₁·d₁ = F₂·d₂. Menší silou na delším rameni zvedneme větší náklad.", + "Těleso se chová, jako by v těžišti působila celá tíha.", + "Labilní poloha se po vychýlení vzdaluje. Volná zůstane, kde je.", + }; + + return fyz_mcq_page(0, 6, "fyzunit7", "fyz_ex7_title", "fyz_quiz7_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz_unit8_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Tlak", "Hydrostatický tlak", + "Tip: tlak v kapalině roste s hloubkou.", + { + "Tlak v kapalině způsobený tíhou je p = h·ρ·g.", + "h je hloubka, ρ je hustota kapaliny.", + "V téže hloubce je tlak ve všech směrech stejný.", + "Pascalův zákon: tlak v uzavřené kapalině se šíří všemi směry stejně.", + NULL, + }, + }, + { + "2 / 3 • Vztlak", "Archimédův zákon", + "Tip: těleso plave, když se jeho průměrná hustota rovná hustotě kapaliny nebo je menší.", + { + "Na těleso v tekutině působí vztlaková síla svisle vzhůru.", + "Vztlak se rovná tíze tekutiny tělesem vytlačené.", + "Těleso se potápí, když je jeho hustota větší než hustota tekutiny.", + "Těleso se vznáší, když jsou hustoty stejné.", + NULL, + }, + }, + { + "3 / 3 • Plyny", "Atmosféra a proudění", + "Tip: normální atmosférický tlak je asi 101,3 kPa.", + { + "Atmosférický tlak u hladiny moře je přibližně 101,3 kPa.", + "S výškou atmosférický tlak klesá.", + "V užší části trubice proudí kapalina rychleji a tlak tam klesá.", + "Na tom stojí vztlak křídla i rozprašovač.", + NULL, + }, + }, + }; + + return fyz_unit_page(0, &fyz_lessons[7], "fyz_unit8", "fyz_unit8_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz_unit8_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jak se počítá hydrostatický tlak?", + {"p = h / ρ", "p = h·ρ·g", "p = F·S", "p = m·v"}, + 4, 1}, + {"Co říká Archimédův zákon?", + {"Vztlak se rovná tíze vytlačené tekutiny", + "Tlak nezávisí na hloubce", + "Kapaliny se nedají stlačit vůbec a nemají vztlak", + "Vztlak působí dolů"}, + 4, 0}, + {"Kdy se těleso v kapalině potápí?", + {"Když má menší hustotu než kapalina", + "Když má větší hustotu než kapalina", + "Když má stejnou hustotu", + "Když je hladina v klidu"}, + 4, 1}, + {"Jak velký je přibližně normální atmosférický tlak?", + {"1 Pa", "101 kPa", "9,81 Pa", "6,67·10⁻¹¹ Pa"}, + 4, 1}, + }; + static const char *hints[] = { + "Hustší kapalina a větší hloubka znamenají větší tlak.", + "Vytlačená tekutina má objem ponořené části tělesa.", + "Vznášení je při stejné hustotě. Plování je při menší průměrné hustotě.", + "1013 hPa je totéž co asi 101,3 kPa.", + }; + + return fyz_mcq_page(0, 7, "fyzunit8", "fyz_ex8_title", "fyz_quiz8_head", + qs, hints, 4); +} + +void add_fyz_pages(GtkStack *stack) { + typedef GtkWidget *(*FyzBuilder)(void); + static const struct { + const char *unit_name; + const char *ex_name; + FyzBuilder build_unit; + FyzBuilder build_ex; + } pages[] = { + {"fyzunit1", "fyzex1", build_fyz_unit1_page, build_fyz_unit1_exercise_page}, + {"fyzunit2", "fyzex2", build_fyz_unit2_page, build_fyz_unit2_exercise_page}, + {"fyzunit3", "fyzex3", build_fyz_unit3_page, build_fyz_unit3_exercise_page}, + {"fyzunit4", "fyzex4", build_fyz_unit4_page, build_fyz_unit4_exercise_page}, + {"fyzunit5", "fyzex5", build_fyz_unit5_page, build_fyz_unit5_exercise_page}, + {"fyzunit6", "fyzex6", build_fyz_unit6_page, build_fyz_unit6_exercise_page}, + {"fyzunit7", "fyzex7", build_fyz_unit7_page, build_fyz_unit7_exercise_page}, + {"fyzunit8", "fyzex8", build_fyz_unit8_page, build_fyz_unit8_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); + } +} diff --git a/src/fyz_year2.c b/src/fyz_year2.c new file mode 100644 index 0000000..9110f4f --- /dev/null +++ b/src/fyz_year2.c @@ -0,0 +1,621 @@ +#include "graduately.h" + +/* Year 2: kinetic theory, heat, the structure of matter, changes of + * state, mechanical oscillation and waves, sound. */ + +NetLesson fyz2_lessons[FYZ_N] = { + { .n_slides = 3, .unit_page = "fyz2unit1", .ex_page = "fyz2ex1" }, + { .n_slides = 3, .unit_page = "fyz2unit2", .ex_page = "fyz2ex2" }, + { .n_slides = 3, .unit_page = "fyz2unit3", .ex_page = "fyz2ex3" }, + { .n_slides = 3, .unit_page = "fyz2unit4", .ex_page = "fyz2ex4" }, + { .n_slides = 3, .unit_page = "fyz2unit5", .ex_page = "fyz2ex5" }, + { .n_slides = 3, .unit_page = "fyz2unit6", .ex_page = "fyz2ex6" }, + { .n_slides = 3, .unit_page = "fyz2unit7", .ex_page = "fyz2ex7" }, + { .n_slides = 3, .unit_page = "fyz2unit8", .ex_page = "fyz2ex8" }, +}; + +static GtkWidget *build_fyz2_unit1_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Částice", "Z čeho je látka", + "Tip: částice se neustále neuspořádaně pohybují.", + { + "Látky se skládají z atomů, molekul nebo iontů.", + "Částice se neuspořádaně pohybují. Tomu říkáme tepelný pohyb.", + "Mezi částicemi působí přitažlivé i odpudivé síly.", + "Brownův pohyb a difuze jsou důkazy pohybu částic.", + NULL, + }, + }, + { + "2 / 3 • Skupenství", "Pevné, kapalné, plynné", + "Tip: v plynu jsou částice daleko od sebe a nevyplňují stálý tvar.", + { + "V pevné látce částice kmitají kolem rovnovážných poloh.", + "V kapalině jsou částice blízko, ale mohou se přesouvat.", + "V plynu se částice pohybují volně a narážejí do stěn.", + "Plazma je ionizovaný plyn a bývá čtvrté skupenství.", + NULL, + }, + }, + { + "3 / 3 • Energie", "Vnitřní energie", + "Tip: vnitřní energie roste s teplotou.", + { + "Vnitřní energie je součet kinetické a potenciální energie částic.", + "Teplo je energie předaná díky rozdílu teplot.", + "Práce i teplo mohou vnitřní energii změnit.", + "Teplota popisuje stav, teplo popisuje děj.", + NULL, + }, + }, + }; + + return fyz_unit_page(1, &fyz2_lessons[0], "fyz2_unit1", "fyz2_unit1_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz2_unit1_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Co dokazuje Brownův pohyb?", + {"Že částice stojí", "Že se částice neustále pohybují", + "Že světlo je vlna", "Že tlak nezávisí na teplotě"}, + 4, 1}, + {"Jak se částice chovají v pevné látce?", + {"Volně létají nádobou", + "Kmitají kolem rovnovážných poloh", + "Nemají žádnou energii", + "Jsou od sebe jako v řídkém plynu"}, + 4, 1}, + {"Co je teplo?", + {"Stavová veličina stejná jako teplota", + "Energie předaná díky rozdílu teplot", + "Hmotnost částic", + "Tlak nasycené páry"}, + 4, 1}, + {"Co je vnitřní energie?", + {"Jen polohová energie celého tělesa m·g·h", + "Součet kinetické a potenciální energie částic", + "Práce tíhové síly", + "Výkon ohřívače"}, + 4, 1}, + }; + static const char *hints[] = { + "Pylová zrnka ve vodě poskakují, protože do nich narážejí molekuly.", + "Kapalina teče, plyn vyplní nádobu. Pevná látka drží tvar.", + "Teplota je stav. Teplo je přenos energie.", + "Zahřátí zvýší kinetickou energii částic, a tím vnitřní energii.", + }; + + return fyz_mcq_page(1, 0, "fyz2unit1", "fyz2_ex1_title", "fyz2_quiz1_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz2_unit2_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Stupnice", "Celsiova a termodynamická", + "Tip: 0 °C je 273,15 K. Velikost jednoho stupně je stejná.", + { + "Celsiova teplota má nulu při tání ledu a 100 °C při varu vody za normálního tlaku.", + "Termodynamická teplota T se měří v kelvinech.", + "T = t + 273,15. Absolutní nula je 0 K, tedy −273,15 °C.", + "Částice se při absolutní nule nezastaví úplně, ale klasická teorie tam končí.", + NULL, + }, + }, + { + "2 / 3 • Teplo", "Kalorimetrická rovnice", + "Tip: měrná tepelná kapacita vody je asi 4180 J/(kg·K).", + { + "Teplo přijaté tělesem je Q = m·c·Δt.", + "c je měrná tepelná kapacita, u vody přibližně 4180 J/(kg·K).", + "Tepelná kapacita C = m·c říká, kolik tepla ohřeje těleso o 1 K.", + "V izolované soustavě je teplo odevzdané rovno teplu přijatému.", + NULL, + }, + }, + { + "3 / 3 • Přenos", "Vedení, proudění, záření", + "Tip: vakuum nevede teplo částicemi, záření jím projde.", + { + "Vedení přenáší energii srážkami částic, hlavně v pevných látkách.", + "Proudění přenáší teplo pohybem kapaliny nebo plynu.", + "Záření přenáší energii elektromagnetickými vlnami i ve vakuu.", + "Kovy vedou teplo dobře, vzduch a pěnové izolace špatně.", + NULL, + }, + }, + }; + + return fyz_unit_page(1, &fyz2_lessons[1], "fyz2_unit2", "fyz2_unit2_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz2_unit2_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Kolik kelvinů je 0 °C?", + {"0 K", "100 K", "273,15 K", "373,15 K"}, + 4, 2}, + {"Jak se počítá přijaté teplo?", + {"Q = m·c·Δt", "Q = m·g·h", "Q = ½·m·v²", "Q = p·V"}, + 4, 0}, + {"Jaká je přibližně měrná tepelná kapacita vody?", + {"4180 J/(kg·K)", "9,81 J/(kg·K)", "101 J/(kg·K)", "3·10⁸ J/(kg·K)"}, + 4, 0}, + {"Který přenos tepla funguje i ve vakuu?", + {"Vedení", "Proudění", "Záření", "Difuzi částic"}, + 4, 2}, + }; + static const char *hints[] = { + "100 °C je 373,15 K. Rozdíl 1 °C je stejný jako rozdíl 1 K.", + "Δt je změna teploty. Větší hmotnost i větší c znamenají více tepla.", + "Voda má velkou měrnou tepelnou kapacitu, proto se ohřívá pomalu.", + "Vedení a proudění potřebují látku. Záření ne.", + }; + + return fyz_mcq_page(1, 1, "fyz2unit2", "fyz2_ex2_title", "fyz2_quiz2_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz2_unit3_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Kalorimetr", "Směšovací rovnice", + "Tip: teplejší těleso teplo odevzdá, chladnější přijme.", + { + "V izolované soustavě platí Q odevzdané = Q přijaté.", + "Při smíchání dvou dávek téže kapaliny je m₁·c·(t₁ − t) = m₂·c·(t − t₂).", + "c se u stejné látky zkrátí.", + "Kalorimetr sám také přijme část tepla. Přesný výpočet s ním počítá.", + NULL, + }, + }, + { + "2 / 3 • Skupenství", "Teplo bez změny teploty", + "Tip: při tání a varu se teplota čisté látky nemění.", + { + "Skupenské teplo tání je Lt = m·lt.", + "Skupenské teplo varu je Lv = m·lv.", + "Měrné skupenské teplo tání ledu je asi 334 kJ/kg.", + "Měrné skupenské teplo varu vody je asi 2,26 MJ/kg.", + NULL, + }, + }, + { + "3 / 3 • 1. věta", "Zákon zachování", + "Tip: perpetuum mobile prvního druhu odporuje první větě.", + { + "První termodynamická věta: ΔU = W + Q.", + "Znaménka závisí na dohodě, škola často bere teplo přijaté a práci vykonanou na soustavě.", + "Energie nevzniká ani nezaniká, jen se přeměňuje.", + "Stroj nemůže trvale konat práci bez přísunu energie.", + NULL, + }, + }, + }; + + return fyz_unit_page(1, &fyz2_lessons[2], "fyz2_unit3", "fyz2_unit3_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz2_unit3_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Co platí v izolované soustavě při výměně tepla?", + {"Teplo odevzdané je větší než přijaté", + "Teplo odevzdané se rovná teplu přijatému", + "Teplota obou těles zůstane původní", + "Hmotnost se zdvojnásobí"}, + 4, 1}, + {"Co se děje s teplotou čisté látky při tání?", + {"Stoupá rovnoměrně", "Nemění se, dokud látka taje", + "Klesá k absolutní nule", "Skáče o 100 K"}, + 4, 1}, + {"Jak se počítá skupenské teplo tání?", + {"Lt = m·c·Δt", "Lt = m·lt", "Lt = p·V", "Lt = ½·m·v²"}, + 4, 1}, + {"Co říká první termodynamická věta?", + {"Teplo se nedá nikam předat", + "Změna vnitřní energie souvisí s teplem a prací", + "Teplota je vždy 273 K", + "Účinnost tepelného stroje je 100 %"}, + 4, 1}, + }; + static const char *hints[] = { + "Kalorimetrická rovnice z toho přímo vychází.", + "Dodávané teplo při tání rozrušuje vazby, nezvedá teplotu.", + "lt je měrné skupenské teplo tání. U ledu asi 334 kJ/kg.", + "Druhá věta omezuje, kolik tepla lze proměnit v práci.", + }; + + return fyz_mcq_page(1, 2, "fyz2unit3", "fyz2_ex3_title", "fyz2_quiz3_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz2_unit4_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Model", "Ideální plyn", + "Tip: stavová rovnice je p·V = n·R·T.", + { + "Ideální plyn má zanedbatelný vlastní objem částic a srážky bez ztráty kinetické energie.", + "Stavová rovnice je p·V = n·R·T.", + "R je molární plynová konstanta, asi 8,31 J/(mol·K).", + "T v této rovnici musí být v kelvinech.", + NULL, + }, + }, + { + "2 / 3 • Děje", "Izochorický, izobarický, izotermický", + "Tip: izotermický děj má stálou teplotu, proto p·V je konstantní.", + { + "Izochorický děj má stálý objem. Platí p / T = konst.", + "Izobarický děj má stálý tlak. Platí V / T = konst.", + "Izotermický děj má stálou teplotu. Platí p·V = konst.", + "Adiabatický děj nevyměňuje teplo s okolím.", + NULL, + }, + }, + { + "3 / 3 • Tlak plynu", "Odkud se bere", + "Tip: vyšší teplota znamená rychlejší částice a větší tlak.", + { + "Tlak plynu vzniká nárazy částic na stěnu.", + "Střední kinetická energie částice je úměrná termodynamické teplotě.", + "Normální podmínky jsou 0 °C a normální atmosférický tlak.", + "Reálný plyn se ideálnímu blíží při nízkém tlaku a vyšší teplotě.", + NULL, + }, + }, + }; + + return fyz_unit_page(1, &fyz2_lessons[3], "fyz2_unit4", "fyz2_unit4_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz2_unit4_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jak zní stavová rovnice ideálního plynu?", + {"p·V = n·R·T", "p / V = T", "Q = m·c·Δt", "F = m·a"}, + 4, 0}, + {"Co je stálé při izotermickém ději?", + {"Objem", "Tlak", "Teplota", "Hmotnost částice"}, + 4, 2}, + {"Co platí při izochorickém ději?", + {"V / T = konst.", "p / T = konst.", "p·V = konst.", "p·T = V"}, + 4, 1}, + {"V jakých jednotkách musí být T ve stavové rovnici?", + {"Ve stupních Celsia", "V kelvinech", "Ve wattech", "V pascalech"}, + 4, 1}, + }; + static const char *hints[] = { + "n je látkové množství, R ≈ 8,31 J/(mol·K).", + "Boylův-Mariottův zákon: p·V = konst. při stálé teplotě.", + "Izochorický znamená stálý objem. Zahřátí zvedne tlak.", + "Dosazení Celsiovy teploty do p·V = n·R·T je chyba.", + }; + + return fyz_mcq_page(1, 3, "fyz2unit4", "fyz2_ex4_title", "fyz2_quiz4_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz2_unit5_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Deformace", "Hookův zákon", + "Tip: v pružné oblasti je prodloužení úměrné síle.", + { + "Deformace je změna tvaru nebo objemu.", + "Pružná deformace zmizí, až síla přestane působit.", + "Tvárná deformace zůstane.", + "Hookův zákon: F = k·Δl. k je tuhost.", + NULL, + }, + }, + { + "2 / 3 • Krystal", "Pevné látky", + "Tip: amorfní látka nemá pravidelnou mřížku v celém objemu.", + { + "Krystal má částice v pravidelné mřížce.", + "Amorfní látka, třeba sklo, pravidelnou mřížku na velkou vzdálenost nemá.", + "Teplotní roztažnost zvětšuje rozměry při zahřátí.", + "Dilatace mostů a kolejí počítá s mezerami.", + NULL, + }, + }, + { + "3 / 3 • Povrch", "Kapaliny", + "Tip: kapka je kulatá, protože povrchová vrstva se stahuje.", + { + "Povrchová vrstva kapaliny se chová jako tenká blána.", + "Povrchové napětí stahuje povrch na co nejmenší obsah.", + "Kapilární elevace zvedá smáčivou kapalinu v úzké trubici.", + "Kapilární deprese stahuje nesmáčivou kapalinu, třeba rtuť ve skle.", + NULL, + }, + }, + }; + + return fyz_unit_page(1, &fyz2_lessons[4], "fyz2_unit5", "fyz2_unit5_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz2_unit5_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Co říká Hookův zákon v pružné oblasti?", + {"Síla je úměrná prodloužení", + "Síla je úměrná druhé mocnině prodloužení vždy", + "Deformace je vždy trvalá", + "Tlak nezávisí na síle"}, + 4, 0}, + {"Čím se krystal liší od amorfní látky?", + {"Nemá částice", + "Má pravidelnou mřížku", + "Nemá teplotní roztažnost", + "Je vždy plyn"}, + 4, 1}, + {"Proč má kapka kulový tvar?", + {"Protože povrchová vrstva stahuje povrch", + "Protože v kapalině není tlak", + "Protože kapka je krystal", + "Protože Hookův zákon platí jen pro koule"}, + 4, 0}, + {"Co je kapilární elevace?", + {"Pokles nesmáčivé kapaliny", + "Vzestup smáčivé kapaliny v úzké trubici", + "Var při nízkém tlaku", + "Trvalá deformace"}, + 4, 1}, + }; + static const char *hints[] = { + "Za mezí pružnosti už závislost není přímá a těleso se nevrátí.", + "Sklo je typická amorfní látka. Sůl je krystal.", + "Koule má při daném objemu nejmenší povrch.", + "Deprese je pokles, třeba rtuti. Elevace je vzestup, třeba vody.", + }; + + return fyz_mcq_page(1, 4, "fyz2unit5", "fyz2_ex5_title", "fyz2_quiz5_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz2_unit6_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Tání a tuhnutí", "Pevná látka a kapalina", + "Tip: teplota tání a teplota tuhnutí téže látky jsou stejné.", + { + "Tání mění pevnou látku na kapalinu při teplotě tání.", + "Tuhnutí je opačný děj a u čisté látky probíhá při stejné teplotě.", + "Příměsi teplotu tání obvykle snižují.", + "Led potřebuje skupenské teplo tání, proto směs ledu a vody drží 0 °C.", + NULL, + }, + }, + { + "2 / 3 • Vypařování", "Var a pára", + "Tip: var nastane, když se tlak syté páry rovná vnějšímu tlaku.", + { + "Vypařování probíhá z povrchu při každé teplotě.", + "Var probíhá v celém objemu při teplotě varu.", + "Sytá pára je v rovnováze se svou kapalinou.", + "Nižší vnější tlak znamená nižší teplotu varu.", + NULL, + }, + }, + { + "3 / 3 • Vlhkost", "Pára ve vzduchu", + "Tip: relativní vlhkost 100 % znamená, že pára je sytá.", + { + "Absolutní vlhkost udává hmotnost vodní páry v objemu vzduchu.", + "Relativní vlhkost je podíl tlaku páry a tlaku syté páry při dané teplotě.", + "Rosný bod je teplota, při které se pára ve vzduchu stane sytou.", + "Kondenzace je vznik kapaliny z páry.", + NULL, + }, + }, + }; + + return fyz_unit_page(1, &fyz2_lessons[5], "fyz2_unit6", "fyz2_unit6_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz2_unit6_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Při jaké teplotě tuhne čistá voda za normálního tlaku?", + {"Při 100 °C", "Při 0 °C", "Při −273,15 °C", "Při teplotě varu rtuti"}, + 4, 1}, + {"Čím se var liší od vypařování?", + {"Var probíhá jen na povrchu a při každé teplotě", + "Var probíhá v celém objemu při teplotě varu", + "Var nepotřebuje skupenské teplo", + "Vypařování nastane jen při 100 °C"}, + 4, 1}, + {"Kdy nastane var?", + {"Když je tlak syté páry roven vnějšímu tlaku", + "Když je relativní vlhkost nulová", + "Když klesne teplota k rosnému bodu", + "Když je látka amorfní"}, + 4, 0}, + {"Co je relativní vlhkost 100 %?", + {"Ve vzduchu není žádná pára", + "Pára ve vzduchu je sytá", + "Teplota je 100 °C", + "Tlak je 100 Pa"}, + 4, 1}, + }; + static const char *hints[] = { + "Tání a tuhnutí čisté vody jsou při 0 °C. Var je při 100 °C.", + "Vypařování je povrchové a děje se i pod teplotou varu.", + "Na horách je nižší tlak, proto voda vře při nižší teplotě.", + "Rosný bod je teplota, při níž relativní vlhkost dosáhne 100 %.", + }; + + return fyz_mcq_page(1, 5, "fyz2unit6", "fyz2_ex6_title", "fyz2_quiz6_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz2_unit7_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Oscilátor", "Perioda a frekvence", + "Tip: frekvence je převrácená hodnota periody.", + { + "Kmitání je pohyb, který se po periodě opakuje.", + "Perioda T je doba jednoho kmitu. Frekvence f = 1 / T.", + "Jednotka frekvence je hertz, Hz.", + "Harmonické kmitání popisuje sinus nebo kosinus.", + NULL, + }, + }, + { + "2 / 3 • Dva oscilátory", "Pružina a kyvadlo", + "Tip: perioda kyvadla nezávisí na hmotnosti závaží.", + { + "Pružinový oscilátor má T = 2π·√(m / k).", + "Matematické kyvadlo má pro malé výchylky T = 2π·√(l / g).", + "Delší kyvadlo kmitá pomaleji.", + "Větší g znamená kratší periodu kyvadla.", + NULL, + }, + }, + { + "3 / 3 • Rezonance", "Vlastní a nucené kmitání", + "Tip: rezonance nastane, když budicí frekvence souhlasí s vlastní.", + { + "Vlastní frekvence patří volnému oscilátoru.", + "Nucené kmitání udržuje vnější síla.", + "Při rezonanci amplituda silně vzroste.", + "Tlumení energii kmitů odebírá a amplitudu zmenšuje.", + NULL, + }, + }, + }; + + return fyz_unit_page(1, &fyz2_lessons[6], "fyz2_unit7", "fyz2_unit7_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz2_unit7_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jak spolu souvisí frekvence a perioda?", + {"f = T", "f = 1 / T", "f = T²", "f = 2π·T"}, + 4, 1}, + {"Na čem závisí perioda matematického kyvadla?", + {"Na hmotnosti závaží a na barvě", + "Na délce a na tíhovém zrychlení", + "Jen na amplitudě při velkých úhlech jako jediném údaji", + "Na tlaku vzduchu jako na hlavní veličině"}, + 4, 1}, + {"Jak se změní perioda, když kyvadlo prodloužíme?", + {"Zkrátí se", "Prodlouží se", "Nezmění se", "Stane se nulovou"}, + 4, 1}, + {"Kdy nastane rezonance?", + {"Když je budicí frekvence rovna vlastní frekvenci", + "Když je tlumení nekonečné", + "Když oscilátor stojí", + "Když je perioda rovna vlnové délce"}, + 4, 0}, + }; + static const char *hints[] = { + "10 Hz znamená 10 kmitů za sekundu, perioda je 0,1 s.", + "Vzorec T = 2π·√(l / g) hmotnost neobsahuje.", + "Pod odmocninou je délka. Delší l znamená větší T.", + "Rezonance může stavbu nebo most rozkmitat nebezpečně.", + }; + + return fyz_mcq_page(1, 6, "fyz2unit7", "fyz2_ex7_title", "fyz2_quiz7_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz2_unit8_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Vlna", "Vlnová délka", + "Tip: v = λ·f platí pro každou vlnu.", + { + "Vlnění přenáší energii, ne látku jako celek.", + "Vlnová délka λ je vzdálenost dvou sousedních bodů ve stejné fázi.", + "Rychlost vlny je v = λ·f.", + "Postupné vlnění se šíří prostředím. Stojaté vzniká skládáním.", + NULL, + }, + }, + { + "2 / 3 • Druhy", "Příčné a podélné", + "Tip: zvuk ve vzduchu je podélné vlnění.", + { + "U příčného vlnění kmitají částice kolmo ke směru šíření.", + "U podélného vlnění kmitají ve směru šíření.", + "Na hladině vody je vlnění příčné, zvuk v plynu podélný.", + "Odraz, lom a ohyb patří k vlnění obecně.", + NULL, + }, + }, + { + "3 / 3 • Zvuk", "Výška, hlasitost, barva", + "Tip: zdravé ucho slyší zhruba od 20 Hz do 20 kHz.", + { + "Zvuk je mechanické vlnění, které vnímáme sluchem.", + "Výška tónu odpovídá frekvenci. Hlasitost souvisí s amplitudou.", + "Infrazvuk je pod 20 Hz, ultrazvuk nad 20 kHz.", + "Ve vzduchu při pokojové teplotě je rychlost zvuku asi 340 m/s.", + NULL, + }, + }, + }; + + return fyz_unit_page(1, &fyz2_lessons[7], "fyz2_unit8", "fyz2_unit8_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz2_unit8_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jak se počítá rychlost vlny?", + {"v = λ / f", "v = λ·f", "v = f / λ", "v = T·λ²"}, + 4, 1}, + {"Jaké vlnění je zvuk ve vzduchu?", + {"Příčné elektromagnetické", "Podélné mechanické", + "Stojaté jen na hladině", "Příčné jako světlo"}, + 4, 1}, + {"Které frekvence člověk obvykle slyší?", + {"Od 20 Hz do 20 kHz", "Jen nad 20 kHz", + "Jen pod 20 Hz", "Od 340 Hz do 340 kHz"}, + 4, 0}, + {"Jak rychle se šíří zvuk ve vzduchu při pokojové teplotě?", + {"Asi 340 m/s", "Asi 3·10⁸ m/s", "Asi 9,81 m/s", "Asi 1500 km/s"}, + 4, 0}, + }; + static const char *hints[] = { + "Vyšší frekvence při stejné rychlosti znamená kratší vlnovou délku.", + "Světlo je příčné elektromagnetické. Zvuk potřebuje látkové prostředí.", + "Pod 20 Hz je infrazvuk, nad 20 kHz ultrazvuk.", + "Ve vodě je zvuk rychlejší, zhruba 1500 m/s. Světlo má 3·10⁸ m/s.", + }; + + return fyz_mcq_page(1, 7, "fyz2unit8", "fyz2_ex8_title", "fyz2_quiz8_head", + qs, hints, 4); +} + +void add_fyz2_pages(GtkStack *stack) { + typedef GtkWidget *(*FyzBuilder)(void); + static const struct { + const char *unit_name; + const char *ex_name; + FyzBuilder build_unit; + FyzBuilder build_ex; + } pages[] = { + {"fyz2unit1", "fyz2ex1", build_fyz2_unit1_page, build_fyz2_unit1_exercise_page}, + {"fyz2unit2", "fyz2ex2", build_fyz2_unit2_page, build_fyz2_unit2_exercise_page}, + {"fyz2unit3", "fyz2ex3", build_fyz2_unit3_page, build_fyz2_unit3_exercise_page}, + {"fyz2unit4", "fyz2ex4", build_fyz2_unit4_page, build_fyz2_unit4_exercise_page}, + {"fyz2unit5", "fyz2ex5", build_fyz2_unit5_page, build_fyz2_unit5_exercise_page}, + {"fyz2unit6", "fyz2ex6", build_fyz2_unit6_page, build_fyz2_unit6_exercise_page}, + {"fyz2unit7", "fyz2ex7", build_fyz2_unit7_page, build_fyz2_unit7_exercise_page}, + {"fyz2unit8", "fyz2ex8", build_fyz2_unit8_page, build_fyz2_unit8_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); + } +} diff --git a/src/fyz_year3.c b/src/fyz_year3.c new file mode 100644 index 0000000..cb3b45e --- /dev/null +++ b/src/fyz_year3.c @@ -0,0 +1,618 @@ +#include "graduately.h" + +/* Year 3: electricity and magnetism. Charge, current, circuits, + * matter in electric field, magnetic field, induction, alternating + * current and electromagnetic waves. */ + +NetLesson fyz3_lessons[FYZ_N] = { + { .n_slides = 3, .unit_page = "fyz3unit1", .ex_page = "fyz3ex1" }, + { .n_slides = 3, .unit_page = "fyz3unit2", .ex_page = "fyz3ex2" }, + { .n_slides = 3, .unit_page = "fyz3unit3", .ex_page = "fyz3ex3" }, + { .n_slides = 3, .unit_page = "fyz3unit4", .ex_page = "fyz3ex4" }, + { .n_slides = 3, .unit_page = "fyz3unit5", .ex_page = "fyz3ex5" }, + { .n_slides = 3, .unit_page = "fyz3unit6", .ex_page = "fyz3ex6" }, + { .n_slides = 3, .unit_page = "fyz3unit7", .ex_page = "fyz3ex7" }, + { .n_slides = 3, .unit_page = "fyz3unit8", .ex_page = "fyz3ex8" }, +}; + +static GtkWidget *build_fyz3_unit1_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Náboj", "Dva druhy", + "Tip: souhlasné náboje se odpuzují, nesouhlasné přitahují.", + { + "Elektrický náboj je kladný nebo záporný.", + "Elementární náboj je e ≈ 1,6·10⁻¹⁹ C.", + "Náboj se zachovává a je celistvým násobkem e.", + "Třením se náboje rozdělují, nevznikají z ničeho.", + NULL, + }, + }, + { + "2 / 3 • Coulomb", "Síla mezi náboji", + "Tip: síla klesá s druhou mocninou vzdálenosti.", + { + "Coulombův zákon: Fe = k·|Q₁·Q₂| / r².", + "k je asi 9·10⁹ N·m²/C².", + "Vodič má volné náboje, izolant je nemá.", + "Elektrické pole je kolem každého náboje.", + NULL, + }, + }, + { + "3 / 3 • Pole", "Intenzita a napětí", + "Tip: napětí je práce na jednotkový náboj.", + { + "Intenzita E = F / q ukazuje sílu na kladný jednotkový náboj.", + "Homogenní pole má všude stejnou intenzitu, třeba mezi deskami.", + "Napětí U = W / Q. Jednotka je volt.", + "Ekvipotenciální hladina je množina bodů se stejným potenciálem.", + NULL, + }, + }, + }; + + return fyz_unit_page(2, &fyz3_lessons[0], "fyz3_unit1", "fyz3_unit1_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz3_unit1_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jak na sebe působí souhlasné náboje?", + {"Přitahují se", "Odpuzují se", "Nepůsobí", "Jen při stejné hmotnosti"}, + 4, 1}, + {"Jak velký je elementární náboj?", + {"Asi 1,6·10⁻¹⁹ C", "Asi 9·10⁹ C", "1 C přesně jako proton", "Asi 6,67·10⁻¹¹ C"}, + 4, 0}, + {"Jak závisí Coulombova síla na vzdálenosti?", + {"Přímo úměrně", "Nepřímo úměrně druhé mocnině vzdálenosti", + "Nezávisí na ní", "Přímo úměrně druhé mocnině"}, + 4, 1}, + {"Co je elektrické napětí?", + {"Síla na jednotkovou hmotnost", + "Práce na jednotkový náboj", + "Náboj za čas", + "Odpor krát plocha"}, + 4, 1}, + }; + static const char *hints[] = { + "Nesouhlasné náboje se přitahují. Neutrální těleso má vyrovnané náboje.", + "Proton má +e, elektron −e. Coulomb je velmi velký náboj.", + "Stejný tvar má i gravitační zákon, jen konstanta je jiná.", + "1 V = 1 J/C. Intenzita se měří v N/C nebo V/m.", + }; + + return fyz_mcq_page(2, 0, "fyz3unit1", "fyz3_ex1_title", "fyz3_quiz1_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz3_unit2_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Proud", "Náboj v pohybu", + "Tip: dohodnutý směr proudu je směr pohybu kladného náboje.", + { + "Elektrický proud je usměrněný pohyb nabitých částic.", + "Proud I = Q / t. Jednotka je ampér.", + "V kovech proud nesou volné elektrony.", + "Dohodnutý směr proudu je opačný než směr pohybu elektronů.", + NULL, + }, + }, + { + "2 / 3 • Ohm", "Odpor", + "Tip: větší délka vodiče znamená větší odpor, větší průřez menší.", + { + "Ohmův zákon pro část obvodu: U = R·I.", + "Odpor R závisí na materiálu, délce a průřezu.", + "R = ρ·l / S. ρ je měrný elektrický odpor.", + "S rostoucí teplotou odpor kovů obvykle roste.", + NULL, + }, + }, + { + "3 / 3 • Zdroj", "Svorkové napětí", + "Tip: vnitřní odpor zdroje snižuje napětí při odběru proudu.", + { + "Zdroj má elektromotorické napětí a vnitřní odpor.", + "Svorkové napětí je menší než elektromotorické, když obvodem teče proud.", + "Zkrat je spojení s velmi malým odporem. Proud je nebezpečně velký.", + "Ampérmetr se řadí sériově, voltmetr paralelně.", + NULL, + }, + }, + }; + + return fyz_unit_page(2, &fyz3_lessons[1], "fyz3_unit2", "fyz3_unit2_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz3_unit2_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jak se počítá proud?", + {"I = Q·t", "I = Q / t", "I = U·R", "I = R / U"}, + 4, 1}, + {"Jak zní Ohmův zákon pro část obvodu?", + {"U = R / I", "U = R·I", "U = I / R", "R = U·I"}, + 4, 1}, + {"Kdy má kovový vodič větší odpor?", + {"Když je kratší a tlustší", + "Když je delší a tenčí", + "Když má větší průřez při stejné délce", + "Když je z látky s nulovým měrným odporem"}, + 4, 1}, + {"Jak se zapojuje ampérmetr?", + {"Paralelně ke spotřebiči", "Sériově do obvodu", + "Jen místo voltmetru paralelně", "Mimo obvod"}, + 4, 1}, + }; + static const char *hints[] = { + "Ampér je coulomb za sekundu.", + "Odpor je U/I. Větší odpor při stejném napětí znamená menší proud.", + "R = ρ·l / S. Měrný odpor ρ je vlastnost látky.", + "Voltmetr má mít velký odpor a měří napětí vedle spotřebiče.", + }; + + return fyz_mcq_page(2, 1, "fyz3unit2", "fyz3_ex2_title", "fyz3_quiz2_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz3_unit3_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Spojení", "Sériově a paralelně", + "Tip: sériově se odpory sčítají, proud je všude stejný.", + { + "Při sériovém spojení je proud stejný a napětí se sčítají.", + "Výsledný odpor sériově je R = R₁ + R₂ + …", + "Při paralelním spojení je napětí stejné a proudy se sčítají.", + "Pro dva paralelní odpory je 1/R = 1/R₁ + 1/R₂.", + NULL, + }, + }, + { + "2 / 3 • Kirchhoff", "Uzly a smyčky", + "Tip: v uzlu je součet přitékajících proudů roven součtu odtékajících.", + { + "První Kirchhoffův zákon je o proudu v uzlu.", + "Druhý Kirchhoffův zákon: součet napětí ve smyčce je nulový.", + "Zdroj ve smyčce dodává napětí, spotřebiče ho ubírají.", + "Zákony stačí na jednoduché obvody se dvěma smyčkami.", + NULL, + }, + }, + { + "3 / 3 • Výkon", "Práce proudu", + "Tip: příkon je P = U·I.", + { + "Práce elektrického proudu je W = U·I·t.", + "Příkon je P = U·I = R·I² = U² / R.", + "Kilowatthodina je jednotka energie, ne výkonu.", + "Jouleovo teplo ve vodiči je Q = R·I²·t.", + NULL, + }, + }, + }; + + return fyz_unit_page(2, &fyz3_lessons[2], "fyz3_unit3", "fyz3_unit3_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz3_unit3_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jaký je výsledný odpor dvou sériově spojených rezistorů?", + {"R = R₁ + R₂", "1/R = 1/R₁ + 1/R₂", "R = R₁·R₂", "R = R₁ / R₂"}, + 4, 0}, + {"Co je stejné na paralelně spojených spotřebičích?", + {"Proud každým z nich", "Napětí", + "Odpor každého z nich", "Jouleovo teplo bez ohledu na odpor"}, + 4, 1}, + {"Co říká první Kirchhoffův zákon?", + {"Součet napětí ve smyčce je nula", + "Součet proudů v uzlu je nula", + "Odpor roste s teplotou", + "Výkon je U děleno I"}, + 4, 1}, + {"Jak se počítá elektrická práce?", + {"W = F·s", "W = U·I·t", "W = R / I", "W = Q·c·Δt"}, + 4, 1}, + }; + static const char *hints[] = { + "Paralelní spojení výsledný odpor zmenší. Dva stejné paralelně dají polovinu.", + "Větší odpor při stejném napětí bere menší proud.", + "Druhý zákon se týká napětí ve smyčce.", + "1 kWh = 3,6·10⁶ J. Příkon ve wattech je U·I.", + }; + + return fyz_mcq_page(2, 2, "fyz3unit3", "fyz3_ex3_title", "fyz3_quiz3_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz3_unit4_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Polovodič", "Vlastní a příměsová vodivost", + "Tip: dioda vede proud hlavně v propustném směru.", + { + "Polovodič má vodivost mezi vodičem a izolantem.", + "S teplotou jeho odpor obvykle klesá, na rozdíl od kovu.", + "Typ N má příměs s volnými elektrony, typ P s dírami.", + "Přechod PN je základ diody. V závěrném směru téměř nevede.", + NULL, + }, + }, + { + "2 / 3 • Kapaliny", "Elektrolyt", + "Tip: v elektrolytu nesou proud ionty, ne volné elektrony jako v kovu.", + { + "Elektrolyt je roztok nebo tavenina, která vede proud ionty.", + "Kationty putují ke katodě, anionty k anodě.", + "Elektrolýza vylučuje látku na elektrodách.", + "Hmotnost vyloučené látky je úměrná náboji, který prošel.", + NULL, + }, + }, + { + "3 / 3 • Plyny", "Výboj", + "Tip: plyn za běžných podmínek nevede, dokud se neionizuje.", + { + "Samostatný výboj udržuje ionizaci sám, nesamostatný potřebuje vnější ionizátor.", + "Jiskra, oblouk a doutnavý výboj jsou druhy výboje.", + "Blesk je jiskrový výboj v atmosféře.", + "Výbojka a zářivka využívají záření výboje v plynu.", + NULL, + }, + }, + }; + + return fyz_unit_page(2, &fyz3_lessons[3], "fyz3_unit4", "fyz3_unit4_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz3_unit4_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jak se s teplotou obvykle mění odpor polovodiče?", + {"Roste jako u kovu", "Klesá", "Nemění se", "Skokem se stane nulovým vždy"}, + 4, 1}, + {"Co je polovodič typu N?", + {"Má převahu děr", "Má převahu volných elektronů z příměsi", + "Je izolant bez náboje", "Vede jen ionty"}, + 4, 1}, + {"Čím je nesen proud v elektrolytu?", + {"Jen volnými elektrony jako v mědi", "Ionty", + "Fotony", "Neutrony"}, + 4, 1}, + {"Kdy plyn začne výrazně vést proud?", + {"Až když je ionizovaný", "Vždy stejně jako kov", + "Jen pod absolutní nulou", "Jen když je to vakuum bez částic"}, + 4, 0}, + }; + static const char *hints[] = { + "Termistor s klesajícím odporem při zahřátí je typické užití.", + "Typ P má díry. Dioda je jeden přechod PN.", + "Katoda přitahuje kationty. Faradayův zákon váže hmotnost a náboj.", + "Nesamostatný výboj zanikne, když zmizí vnější ionizátor.", + }; + + return fyz_mcq_page(2, 3, "fyz3unit4", "fyz3_ex4_title", "fyz3_quiz4_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz3_unit5_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Magnet", "Pole magnetu", + "Tip: souhlasné póly se odpuzují.", + { + "Souhlasné magnetické póly se odpuzují, nesouhlasné přitahují.", + "Severní pól střelky ukazuje k jižnímu magnetickému pólu Země.", + "Magnetické indukční čáry jsou uzavřené.", + "Zemské pole odklání část nabitých částic z vesmíru.", + NULL, + }, + }, + { + "2 / 3 • Proud", "Magnetické pole vodiče", + "Tip: cívka s proudem se chová jako magnet.", + { + "Kolem přímého vodiče s proudem jsou indukční čáry kružnice.", + "Cívka, solenoid, má uvnitř téměř homogenní pole.", + "Na vodič s proudem v magnetickém poli působí síla F = B·I·l·sin α.", + "B je magnetická indukce. Jednotka je tesla, T.", + NULL, + }, + }, + { + "3 / 3 • Užití", "Motor a měřidlo", + "Tip: elektromotor mění elektrickou energii na pohybovou.", + { + "Stejnosměrný motor využívá sílu na závit s proudem v magnetickém poli.", + "Komutátor mění směr proudu v závitu, aby se točil dál.", + "Částice s nábojem v magnetickém poli zatáčí, pokud má složku rychlosti kolmou k poli.", + "Magnetický záznam a elektromagnet jsou běžné užití pole.", + NULL, + }, + }, + }; + + return fyz_unit_page(2, &fyz3_lessons[4], "fyz3_unit5", "fyz3_unit5_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz3_unit5_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jaký tvar mají magnetické indukční čáry?", + {"Začínají na severním pólu a končí v nekonečnu jako přímky", + "Jsou uzavřené", + "Jsou vždy rovnoběžné s vodičem", + "Existují jen ve vakuu"}, + 4, 1}, + {"Jak se počítá síla na přímý vodič s proudem v magnetickém poli?", + {"F = B·I·l·sin α", "F = k·Q₁·Q₂ / r²", "F = m·g", "F = U / R"}, + 4, 0}, + {"Jaká je jednotka magnetické indukce?", + {"Volt", "Tesla", "Henry jen jako indukce B", "Ampér"}, + 4, 1}, + {"K čemu je v stejnosměrném motoru komutátor?", + {"Aby zvýšil odpor na nekonečno", + "Aby měnil směr proudu v závitu a motor se točil dál", + "Aby zrušil magnetické pole", + "Aby měřil napětí"}, + 4, 1}, + }; + static const char *hints[] = { + "Elektrické siločáry začínají a končí na nábojích. Magnetické jsou uzavřené.", + "Když je vodič rovnoběžný s polem, sin α je 0 a síla je nulová.", + "Tesla je N/(A·m). Henry je jednotka indukčnosti.", + "Bez komutátoru by se závit po půl otáčce zastavil.", + }; + + return fyz_mcq_page(2, 4, "fyz3unit5", "fyz3_ex5_title", "fyz3_quiz5_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz3_unit6_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Faraday", "Indukované napětí", + "Tip: napětí vzniká, jen když se magnetický tok mění.", + { + "Elektromagnetická indukce vzniká při změně magnetického toku plochou smyčky.", + "Magnetický tok je Φ = B·S·cos α.", + "Indukované napětí je tím větší, čím rychleji se tok mění.", + "Stálé pole a klidná smyčka napětí neindukují.", + NULL, + }, + }, + { + "2 / 3 • Lenz", "Směr proudu", + "Tip: indukovaný proud působí proti změně, která ho vyvolala.", + { + "Lenzův zákon určuje směr indukovaného proudu.", + "Proud vytváří pole, které brání změně toku.", + "Proto se magnet padající měděnou trubkou brzdí.", + "Vlastní indukce brání změně proudu v cívce.", + NULL, + }, + }, + { + "3 / 3 • Transformátor", "Změna napětí", + "Tip: poměr napětí je poměr počtů závitů.", + { + "Transformátor má dvě cívky na společném jádře.", + "U₁ / U₂ = N₁ / N₂.", + "Zvyšovací transformátor má na sekundáru víc závitů.", + "U ideálního transformátoru je příkon roven výkonu, proud se mění opačně než napětí.", + NULL, + }, + }, + }; + + return fyz_unit_page(2, &fyz3_lessons[5], "fyz3_unit6", "fyz3_unit6_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz3_unit6_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Kdy se ve smyčce indukuje napětí?", + {"Když je magnetický tok stálý", + "Když se magnetický tok plochou smyčky mění", + "Když smyčkou neteče žádný náboj a tok je nulový trvale", + "Jen při stejnosměrném proudu v klidném poli"}, + 4, 1}, + {"Co říká Lenzův zákon?", + {"Indukovaný proud podporuje změnu toku", + "Indukovaný proud působí proti změně, která ho vyvolala", + "Napětí je vždy rovno proudu", + "Tok je B děleno S"}, + 4, 1}, + {"Jaký je poměr napětí na ideálním transformátoru?", + {"U₁ / U₂ = N₂ / N₁", "U₁ / U₂ = N₁ / N₂", + "U₁ / U₂ = I₁ / I₂ vždy při stejném počtu závitů jen", + "Napětí nezávisí na závitech"}, + 4, 1}, + {"Co platí pro ideální transformátor?", + {"Příkon je roven výkonu", + "Sekundární proud je vždy nulový", + "Funguje i se stejnosměrným stálým proudem", + "Napětí na obou cívkách je stejné bez ohledu na závity"}, + 4, 0}, + }; + static const char *hints[] = { + "Pohyb magnetu u cívky, nebo změna proudu v primáru, tok mění.", + "Zákon zachování energie: indukce nemůže změnu sama zesilovat donekonečna.", + "Více závitů na sekundáru znamená vyšší sekundární napětí.", + "Stejnosměrný stálý proud tok nemění, proto se netransformuje.", + }; + + return fyz_mcq_page(2, 5, "fyz3unit6", "fyz3_ex6_title", "fyz3_quiz6_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz3_unit7_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Střídání", "Perioda a frekvence", + "Tip: síť v Česku má frekvenci 50 Hz.", + { + "Střídavý proud periodicky mění velikost i směr.", + "Harmonické napětí popisuje sinus.", + "Frekvence sítě je 50 Hz, perioda je 0,02 s.", + "Okamžitá hodnota se stále mění. Měří se efektivní hodnota.", + NULL, + }, + }, + { + "2 / 3 • Efektivní", "Co ukáže voltmetr", + "Tip: efektivní hodnota je maximální dělená odmocninou ze dvou.", + { + "Efektivní proud vydá na rezistoru stejný výkon jako stejný stejnosměrný proud.", + "Pro sinusový průběh je Uef = Umax / √2.", + "Síťové napětí 230 V je efektivní hodnota.", + "Amplituda je tedy asi 325 V.", + NULL, + }, + }, + { + "3 / 3 • Obvod", "Odpor, cívka, kondenzátor", + "Tip: na rezistoru jsou napětí a proud ve fázi.", + { + "Rezistor proud a napětí neposouvá.", + "Cívka zpožďuje proud za napětím, kondenzátor ho předbíhá.", + "Indukčnost a kapacita závisejí na frekvenci.", + "Výkon na rezistoru je P = Uef·Ief.", + NULL, + }, + }, + }; + + return fyz_unit_page(2, &fyz3_lessons[6], "fyz3_unit7", "fyz3_unit7_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz3_unit7_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jaká je frekvence elektrické sítě v Česku?", + {"50 Hz", "230 Hz", "1 Hz", "60 kHz"}, + 4, 0}, + {"Co znamená síťové napětí 230 V?", + {"Maximální hodnotu", "Efektivní hodnotu", + "Napětí za jednu periodu jako součet", "Vnitřní odpor"}, + 4, 1}, + {"Jak spolu souvisí efektivní a maximální hodnota u sinusového napětí?", + {"Uef = Umax·√2", "Uef = Umax / √2", "Uef = Umax", "Uef = Umax / 2"}, + 4, 1}, + {"Jak je to s fází na rezistoru?", + {"Proud se zpožďuje o čtvrt periodu", + "Napětí a proud jsou ve fázi", + "Proud předbíhá o půl periodu", + "Fáze závisí jen na barvě rezistoru"}, + 4, 1}, + }; + static const char *hints[] = { + "Perioda 50 Hz je 20 ms. V USA je síť 60 Hz.", + "Maximální napětí sítě je 230·√2, zhruba 325 V.", + "√2 je asi 1,414. Efektivní hodnota je menší než amplituda.", + "Fázový posun dělá cívka a kondenzátor, ne samotný rezistor.", + }; + + return fyz_mcq_page(2, 6, "fyz3unit7", "fyz3_ex7_title", "fyz3_quiz7_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz3_unit8_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Vznik", "Elektromagnetická vlna", + "Tip: ve vakuu se vlna šíří rychlostí světla.", + { + "Kmitající náboj vyzařuje elektromagnetickou vlnu.", + "Elektrická a magnetická složka jsou navzájem kolmé a kolmé ke směru šíření.", + "Ve vakuu je rychlost c = 3·10⁸ m/s.", + "Vlna přenáší energii a nepotřebuje látkové prostředí.", + NULL, + }, + }, + { + "2 / 3 • Spektrum", "Od rádia po záření gama", + "Tip: viditelné světlo je jen úzká část spektra.", + { + "Spektrum tvoří rádiové vlny, mikrovlny, infračervené záření, světlo, ultrafialové, rentgenové a gama.", + "Větší frekvence znamená kratší vlnovou délku, protože c = λ·f.", + "Viditelné světlo je zhruba 400 až 750 nm.", + "Za fialovou je ultrafialové záření, před červenou infračervené.", + NULL, + }, + }, + { + "3 / 3 • Užití", "Spoje a ohřev", + "Tip: mikrovlny rozkmitávají molekuly vody.", + { + "Rádiové vlny přenášejí rozhlas, televizi i mobilní signál.", + "Mikrovlny se používají v radarové technice a v troubě.", + "Rentgenové záření prochází měkkými tkáněmi víc než kostmi.", + "Všechny tyto vlny jsou totéž elektromagnetické vlnění, liší se frekvencí.", + NULL, + }, + }, + }; + + return fyz_unit_page(2, &fyz3_lessons[7], "fyz3_unit8", "fyz3_unit8_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz3_unit8_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jak rychle se šíří elektromagnetická vlna ve vakuu?", + {"Asi 340 m/s", "Asi 3·10⁸ m/s", "Asi 9,81 m/s", "Závisí na hlasitosti"}, + 4, 1}, + {"Co platí pro vlnovou délku a frekvenci ve vakuu?", + {"c = λ / f", "c = λ·f", "c = f / λ", "λ nezávisí na f"}, + 4, 1}, + {"Kde ve spektru leží viditelné světlo?", + {"Mezi rádiovými vlnami a mikrovlnami", + "Mezi infračerveným a ultrafialovým zářením", + "Za zářením gama", + "Jen jako zvuk"}, + 4, 1}, + {"Čím se od sebe liší rádiová vlna a rentgenové záření?", + {"Rentgen není elektromagnetická vlna", + "Mají jinou frekvenci", + "Rentgen potřebuje vzduch, rádio vakuum", + "Rádiová vlna má vždy vyšší frekvenci"}, + 4, 1}, + }; + static const char *hints[] = { + "340 m/s je zvuk. Světlo je skoro milionkrát rychlejší.", + "Stejný vztah jako u mechanické vlny, rychlost je c.", + "Fialová má kratší vlnovou délku než červená.", + "Gama a rentgen mají vysokou frekvenci a krátkou vlnovou délku.", + }; + + return fyz_mcq_page(2, 7, "fyz3unit8", "fyz3_ex8_title", "fyz3_quiz8_head", + qs, hints, 4); +} + +void add_fyz3_pages(GtkStack *stack) { + typedef GtkWidget *(*FyzBuilder)(void); + static const struct { + const char *unit_name; + const char *ex_name; + FyzBuilder build_unit; + FyzBuilder build_ex; + } pages[] = { + {"fyz3unit1", "fyz3ex1", build_fyz3_unit1_page, build_fyz3_unit1_exercise_page}, + {"fyz3unit2", "fyz3ex2", build_fyz3_unit2_page, build_fyz3_unit2_exercise_page}, + {"fyz3unit3", "fyz3ex3", build_fyz3_unit3_page, build_fyz3_unit3_exercise_page}, + {"fyz3unit4", "fyz3ex4", build_fyz3_unit4_page, build_fyz3_unit4_exercise_page}, + {"fyz3unit5", "fyz3ex5", build_fyz3_unit5_page, build_fyz3_unit5_exercise_page}, + {"fyz3unit6", "fyz3ex6", build_fyz3_unit6_page, build_fyz3_unit6_exercise_page}, + {"fyz3unit7", "fyz3ex7", build_fyz3_unit7_page, build_fyz3_unit7_exercise_page}, + {"fyz3unit8", "fyz3ex8", build_fyz3_unit8_page, build_fyz3_unit8_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); + } +} diff --git a/src/fyz_year4.c b/src/fyz_year4.c new file mode 100644 index 0000000..8965050 --- /dev/null +++ b/src/fyz_year4.c @@ -0,0 +1,635 @@ +#include "graduately.h" + +/* Year 4: wave and geometric optics, special relativity, quantum + * physics, the atom, the nucleus, nuclear reactions and astrophysics. */ + +NetLesson fyz4_lessons[FYZ_N] = { + { .n_slides = 3, .unit_page = "fyz4unit1", .ex_page = "fyz4ex1" }, + { .n_slides = 3, .unit_page = "fyz4unit2", .ex_page = "fyz4ex2" }, + { .n_slides = 3, .unit_page = "fyz4unit3", .ex_page = "fyz4ex3" }, + { .n_slides = 3, .unit_page = "fyz4unit4", .ex_page = "fyz4ex4" }, + { .n_slides = 3, .unit_page = "fyz4unit5", .ex_page = "fyz4ex5" }, + { .n_slides = 3, .unit_page = "fyz4unit6", .ex_page = "fyz4ex6" }, + { .n_slides = 3, .unit_page = "fyz4unit7", .ex_page = "fyz4ex7" }, + { .n_slides = 3, .unit_page = "fyz4unit8", .ex_page = "fyz4ex8" }, +}; + +static GtkWidget *build_fyz4_unit1_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Paprsek", "Odraz a lom", + "Tip: úhel odrazu se rovná úhlu dopadu.", + { + "Světlo se v homogenním prostředí šíří přímočaře.", + "Úhel odrazu se rovná úhlu dopadu. Oba se měří od kolmice.", + "Index lomu je n = c / v. Ve skle je větší než 1.", + "Snellův zákon: n₁·sin α = n₂·sin β.", + NULL, + }, + }, + { + "2 / 3 • Mez", "Úplný odraz", + "Tip: úplný odraz nastane při přechodu do opticky řidšího prostředí.", + { + "Opticky hustší prostředí má větší index lomu.", + "Při přechodu do řidšího prostředí se paprsek láme od kolmice.", + "Od mezního úhlu se už neláme a nastane úplný odraz.", + "Optická vlákna vedou světlo opakovaným úplným odrazem.", + NULL, + }, + }, + { + "3 / 3 • Vlna", "Interference a ohyb", + "Tip: světlo je příčné elektromagnetické vlnění.", + { + "Interference je skládání vln. Světlý pruh vzniká při dráhovém rozdílu k·λ.", + "Tmavý pruh vzniká při rozdílu liché poloviny vlnové délky.", + "Ohyb je patrný, když je překážka srovnatelná s vlnovou délkou.", + "Polarizace dokazuje, že světlo je příčné vlnění.", + NULL, + }, + }, + }; + + return fyz_unit_page(3, &fyz4_lessons[0], "fyz4_unit1", "fyz4_unit1_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz4_unit1_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jak zní zákon odrazu?", + {"Úhel odrazu je dvojnásobek úhlu dopadu", + "Úhel odrazu se rovná úhlu dopadu", + "Úhel odrazu je vždy 90°", + "Odraz nezávisí na kolmici"}, + 4, 1}, + {"Co je index lomu?", + {"n = v / c", "n = c / v", "n = λ·f v látce dělené c²", "n = sin α"}, + 4, 1}, + {"Kdy nastane úplný odraz?", + {"Při přechodu do opticky řidšího prostředí nad mezním úhlem", + "Při každém dopadu na zrcadlo", + "Jen ve vakuu", + "Při přechodu do hustšího prostředí"}, + 4, 0}, + {"Kdy vznikne interferencí světlý pruh?", + {"Při dráhovém rozdílu k·λ", + "Při dráhovém rozdílu vždy λ/2", + "Když se vlny nepotkají", + "Jen u podélného zvuku"}, + 4, 0}, + }; + static const char *hints[] = { + "Úhly se měří od kolmice dopadu, ne od plochy.", + "V látce je světlo pomalejší než ve vakuu, proto n > 1.", + "Mezní úhel splňuje sin αm = n₂ / n₁ pro n₁ > n₂.", + "Podmínka pro minimum je (2k+1)·λ/2.", + }; + + return fyz_mcq_page(3, 0, "fyz4unit1", "fyz4_ex1_title", "fyz4_quiz1_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz4_unit2_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Zrcadla", "Dutá a vypuklá", + "Tip: zobrazovací rovnice je 1/a + 1/a' = 1/f.", + { + "Rovinné zrcadlo dává vzpřímený obraz stejné velikosti, zdánlivě za zrcadlem.", + "Duté zrcadlo může dát skutečný převrácený obraz.", + "Vypuklé zrcadlo dává zmenšený vzpřímený zdánlivý obraz.", + "Ohnisková vzdálenost kulového zrcadla je f = r / 2.", + NULL, + }, + }, + { + "2 / 3 • Čočky", "Spojka a rozptylka", + "Tip: spojka soustředí rovnoběžné paprsky do ohniska.", + { + "Spojka je uprostřed tlustší a má kladnou ohniskovou vzdálenost.", + "Rozptylka je uprostřed tenčí a paprsky rozbíhá.", + "Zobrazovací rovnice je 1/a + 1/a' = 1/f.", + "a je vzdálenost předmětu, a' vzdálenost obrazu.", + NULL, + }, + }, + { + "3 / 3 • Oko", "Vady a přístroje", + "Tip: krátkozrakost se koriguje rozptylkou.", + { + "Oko zobrazuje spojnou soustavou na sítnici.", + "Krátkozraké oko zobrazuje před sítnici. Pomůže rozptylka.", + "Dalekozraké oko zobrazuje za sítnici. Pomůže spojka.", + "Lupa je spojka a předmět je mezi čočkou a ohniskem.", + NULL, + }, + }, + }; + + return fyz_unit_page(3, &fyz4_lessons[1], "fyz4_unit2", "fyz4_unit2_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz4_unit2_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jaká je ohnisková vzdálenost kulového zrcadla?", + {"f = r", "f = r / 2", "f = 2·r", "f = 1 / r²"}, + 4, 1}, + {"Jak zní zobrazovací rovnice čočky?", + {"a + a' = f", "1/a + 1/a' = 1/f", "a·a' = f", "1/a − 1/f = a'"}, + 4, 1}, + {"Čím se koriguje krátkozrakost?", + {"Spojkou", "Rozptylkou", "Dutým zrcadlem v oku", "Clonou bez čočky"}, + 4, 1}, + {"Jaký obraz dává lupa?", + {"Skutečný převrácený, předmět je za ohniskem", + "Zvětšený vzpřímený zdánlivý, předmět je mezi čočkou a ohniskem", + "Zmenšený obraz v nekonečnu vždy", + "Obraz stejný jako rovinné zrcadlo"}, + 4, 1}, + }; + static const char *hints[] = { + "Poloměr křivosti je dvojnásobek ohniskové vzdálenosti.", + "Když je a = 2f, je i a' = 2f a obraz je stejně velký.", + "Dalekozrakost koriguje spojka. Obraz pak padne na sítnici.", + "Zdánlivý obraz spojky vzniká, když je předmět uvnitř ohniska.", + }; + + return fyz_mcq_page(3, 1, "fyz4unit2", "fyz4_ex2_title", "fyz4_quiz2_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz4_unit3_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Postuláty", "Dva principy", + "Tip: rychlost světla ve vakuu je ve všech inerciálních soustavách stejná.", + { + "Fyzikální zákony mají ve všech inerciálních soustavách stejný tvar.", + "Rychlost světla ve vakuu je ve všech inerciálních soustavách c.", + "c je mezní rychlost pro přenos informace a pro částice s hmotností.", + "Klasické skládání rychlostí u rychlostí blízkých c selhává.", + NULL, + }, + }, + { + "2 / 3 • Čas a délka", "Dilatace a kontrakce", + "Tip: pohybující se hodiny jdou z hlediska klidné soustavy pomaleji.", + { + "Dilatace času: pohyblivé hodiny jdou pomaleji.", + "Kontrakce délky zkracuje rozměr ve směru pohybu.", + "Rozměry kolmé ke směru pohybu se nemění.", + "Efekty jsou patrné až při rychlostech srovnatelných s c.", + NULL, + }, + }, + { + "3 / 3 • Energie", "E = m·c²", + "Tip: klidová energie je energie tělesa v soustavě, kde je v klidu.", + { + "Klidová energie je E₀ = m·c².", + "Celková energie roste s rychlostí.", + "Hmotnost a energie jsou dvě stránky téže veličiny.", + "Částice s nenulovou klidovou hmotností nikdy nedosáhne rychlosti c.", + NULL, + }, + }, + }; + + return fyz_unit_page(3, &fyz4_lessons[2], "fyz4_unit3", "fyz4_unit3_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz4_unit3_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Co platí o rychlosti světla ve vakuu?", + {"Závisí na rychlosti zdroje klasickým součtem", + "Je ve všech inerciálních soustavách stejná", + "Je 340 m/s", + "Je různá podle barvy jako hlavní postulát"}, + 4, 1}, + {"Co je dilatace času?", + {"Pohyblivé hodiny jdou z hlediska klidného pozorovatele pomaleji", + "Pohyblivé hodiny jdou rychleji", + "Čas se zastaví při každé rychlosti", + "Délka se prodlužuje"}, + 4, 0}, + {"Který rozměr se při kontrakci zkracuje?", + {"Rozměr kolmý ke směru pohybu", + "Rozměr ve směru pohybu", + "Všechny rozměry stejně", + "Žádný, zkracuje se jen hmotnost"}, + 4, 1}, + {"Jak se značí klidová energie?", + {"E₀ = m·v² / 2", "E₀ = m·c²", "E₀ = m·g·h", "E₀ = h·f"}, + 4, 1}, + }; + static const char *hints[] = { + "Druhý postulát nahrazuje klasické skládání rychlostí světla.", + "Vzorec obsahuje √(1 − v²/c²) ve jmenovateli vlastního času.", + "Tyč kolmá k pohybu má stejnou délku v obou soustavách.", + "c² je obrovské číslo, proto i malá hmotnost znamená velkou energii.", + }; + + return fyz_mcq_page(3, 2, "fyz4unit3", "fyz4_ex3_title", "fyz4_quiz3_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz4_unit4_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Foton", "Energie kvanta", + "Tip: E = h·f. h je Planckova konstanta.", + { + "Záření si vyměňuje energii po kvantech, fotonech.", + "Energie fotonu je E = h·f.", + "h je asi 6,63·10⁻³⁴ J·s.", + "Větší frekvence znamená energičtější foton.", + NULL, + }, + }, + { + "2 / 3 • Fotoefekt", "Světlo vyráží elektrony", + "Tip: pod mezní frekvencí elektrony nevyletí, i když je světlo silné.", + { + "Fotoelektrický jev: foton předá energii elektronu v kovu.", + "Výstupní práce Wv je energie potřebná k opuštění kovu.", + "Einsteinova rovnice: h·f = Wv + Ek.", + "Počet elektronů roste s intenzitou, jejich maximální energie s frekvencí.", + NULL, + }, + }, + { + "3 / 3 • Dualita", "Vlna i částice", + "Tip: de Broglieho vlnová délka je λ = h / p.", + { + "Světlo se v interferenci chová jako vlna a ve fotoefektu jako částice.", + "I elektrony mají vlnové vlastnosti.", + "De Broglie: λ = h / p, kde p je hybnost.", + "Čím větší hybnost, tím kratší vlnová délka.", + NULL, + }, + }, + }; + + return fyz_unit_page(3, &fyz4_lessons[3], "fyz4_unit4", "fyz4_unit4_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz4_unit4_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Jak se počítá energie fotonu?", + {"E = m·c² jen pro foton v klidu", "E = h·f", "E = h / f", "E = f / h"}, + 4, 1}, + {"Co rozhoduje o tom, zda fotoefekt nastane?", + {"Jen intenzita světla", + "Frekvence musí být aspoň mezní", + "Jen barva kovu bez frekvence", + "Teplota laboratoře jako jediná podmínka"}, + 4, 1}, + {"Jak zní Einsteinova rovnice fotoefektu?", + {"h·f = Wv + Ek", "h·f = Wv − Ek vždy záporně", + "E = m·g·h", "h·f = R·I"}, + 4, 0}, + {"Co říká de Broglieho vztah?", + {"λ = h·p", "λ = h / p", "λ = p / h", "λ = c·p"}, + 4, 1}, + }; + static const char *hints[] = { + "Foton nemá klidovou hmotnost. Jeho energie je h·f, hybnost h/λ.", + "Silnější světlo stejné nízké frekvence elektrony neuvolní.", + "Zbytek energie fotonu po výstupní práci je kinetická energie elektronu.", + "Makroskopická tělesa mají obrovskou hybnost, proto nepatrnou vlnovou délku.", + }; + + return fyz_mcq_page(3, 3, "fyz4unit4", "fyz4_ex4_title", "fyz4_quiz4_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz4_unit5_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Modely", "Od Rutherforda k Bohrovi", + "Tip: Rutherfordův pokus ukázal malé kladné jádro.", + { + "Thomsonův model měl náboj rozptýlený v celém atomu.", + "Rutherford: atom má malé kladné jádro a obal z elektronů.", + "Většina objemu atomu je prázdná.", + "Bohr doplnil, že elektron je jen na určitých drahách bez vyzařování.", + NULL, + }, + }, + { + "2 / 3 • Hladiny", "Emise a absorpce", + "Tip: atom vyzáří foton, když elektron klesne na nižší hladinu.", + { + "Energie elektronu v atomu je kvantovaná.", + "Při přeskoku dolů atom vyzáří foton, při přeskoku nahoru ho pohltí.", + "Rozdíl hladin je E = h·f.", + "Spektrum plynu je čárové, spektrum pevné látky je spojité.", + NULL, + }, + }, + { + "3 / 3 • Obal", "Slupky", + "Tip: slupky se značí K, L, M a odpovídají hlavnímu kvantovému číslu.", + { + "Hlavní kvantové číslo n určuje slupku a energii ve vodíku.", + "Slupka K je n = 1, L je n = 2, M je n = 3.", + "Pauliho princip: v atomu nejsou dva elektrony se všemi kvantovými čísly stejnými.", + "Chemická vazba vzniká hlavně elektrony ve vnější slupce.", + NULL, + }, + }, + }; + + return fyz_unit_page(3, &fyz4_lessons[4], "fyz4_unit5", "fyz4_unit5_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz4_unit5_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Co ukázal Rutherfordův pokus?", + {"Že atom nemá jádro", + "Že kladný náboj je soustředěn v malém jádře", + "Že elektron má vlnovou délku nula", + "Že spektrum pevné látky je čárové"}, + 4, 1}, + {"Kdy atom vyzáří foton?", + {"Když elektron přejde na nižší hladinu", + "Když elektron přejde na vyšší hladinu", + "Když se jádro nepohne a elektron stojí", + "Při každém oběhu podle klasické fyziky trvale"}, + 4, 0}, + {"Jaká je energie fotonu při přeskoku?", + {"Součet obou hladin", "Rozdíl hladin", + "Vždy h·c bez rozdílu", "Klidová energie elektronu"}, + 4, 1}, + {"Která slupka je nejblíž jádru?", + {"M", "L", "K", "Libovolná se stejným n"}, + 4, 2}, + }; + static const char *hints[] = { + "Odraz mála částic alfa o velký úhel nešel vysvětlit rozptýleným nábojem.", + "Přeskok nahoru je absorpce. Elektron energii fotonu přijme.", + "E₂ − E₁ = h·f. Větší skok znamená větší frekvenci.", + "n = 1 je K. Vyšší n je dál od jádra a má vyšší energii.", + }; + + return fyz_mcq_page(3, 4, "fyz4unit5", "fyz4_ex5_title", "fyz4_quiz5_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz4_unit6_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Jádro", "Protonové a nukleonové číslo", + "Tip: nukleonové číslo A je počet protonů a neutronů.", + { + "Jádro tvoří protony a neutrony, souhrnně nukleony.", + "Protonové číslo Z je počet protonů a určuje prvek.", + "Nukleonové číslo A je počet nukleonů. Neutronů je A − Z.", + "Izotopy mají stejné Z a různé A.", + NULL, + }, + }, + { + "2 / 3 • Vazba", "Vazebná energie", + "Tip: jádro je lehčí než součet volných nukleonů.", + { + "Jaderné síly drží nukleony pohromadě a působí na krátkou vzdálenost.", + "Vazebná energie je energie, kterou je třeba dodat na rozbití jádra na nukleony.", + "Hmotnostní úbytek odpovídá vazebné energii podle E = m·c².", + "Nejstabilnější jsou jádra kolem železa.", + NULL, + }, + }, + { + "3 / 3 • Rozpad", "Alfa, beta, gama", + "Tip: poločas je doba, za kterou se rozpadne polovina jader.", + { + "Záření alfa je jádro helia, má malý dolet a silné ionizační účinky.", + "Záření beta jsou elektrony nebo pozitrony z jádra.", + "Záření gama je elektromagnetické a proniká nejvíc.", + "Poločas rozpadu je pro daný nuklid stálý a nezávisí na okolí.", + NULL, + }, + }, + }; + + return fyz_unit_page(3, &fyz4_lessons[5], "fyz4_unit6", "fyz4_unit6_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz4_unit6_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Co je nukleonové číslo A?", + {"Počet protonů", "Počet protonů a neutronů", + "Počet elektronů v obalu", "Počet slupek"}, + 4, 1}, + {"Co jsou izotopy?", + {"Jádra se stejným Z a různým A", + "Jádra se stejným A a různým Z", + "Jádra bez neutronů", + "Atomy bez elektronů"}, + 4, 0}, + {"Co je záření alfa?", + {"Elektromagnetická vlna", "Jádro helia", + "Volný neutron", "Foton z obalu"}, + 4, 1}, + {"Co je poločas rozpadu?", + {"Doba, za kterou se rozpadne polovina jader", + "Doba jednoho oběhu elektronu", + "Doba, za kterou vznikne jádro železa", + "Doba života jednoho neutronu mimo každou látku jako jediný údaj"}, + 4, 0}, + }; + static const char *hints[] = { + "Zapiš uhlík 14 jako A = 14, Z = 6, neutronů 8.", + "Stejné protonové číslo znamená stejný prvek.", + "Beta je elektron nebo pozitron. Gama je foton.", + "Po dvou poločasech zbývá čtvrtina původních jader.", + }; + + return fyz_mcq_page(3, 5, "fyz4unit6", "fyz4_ex6_title", "fyz4_quiz6_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz4_unit7_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Štěpení", "Těžká jádra", + "Tip: štěpení uranu uvolní neutrony, které mohou štěpit dál.", + { + "Štěpení rozdělí těžké jádro na dvě středně těžká.", + "Uvolní se energie, protože produkty mají větší vazebnou energii na nukleon.", + "Při štěpení uranu 235 vznikají i neutrony.", + "Řetězová reakce pokračuje, když každý krok uvolní aspoň jeden další neutron.", + NULL, + }, + }, + { + "2 / 3 • Reaktor", "Řízená reakce", + "Tip: moderátor neutrony zpomaluje, regulační tyče je pohlcují.", + { + "V reaktoru je řetězová reakce řízená.", + "Moderátor, třeba voda, zpomaluje neutrony.", + "Regulační tyče neutrony zachycují a výkon tím klesá.", + "Teplo z reaktoru ohřívá vodu a pohání turbínu.", + NULL, + }, + }, + { + "3 / 3 • Syntéza", "Slučování lehkých jader", + "Tip: fúze pohání Slunce. Potřebuje vysokou teplotu.", + { + "Jaderná syntéza slučuje lehká jádra, třeba vodík na helium.", + "Uvolní víc energie na nukleon než štěpení.", + "K překonání odpudivé síly je potřeba velmi vysoká teplota.", + "Ochrana před zářením stojí na čase, vzdálenosti a stínění.", + NULL, + }, + }, + }; + + return fyz_unit_page(3, &fyz4_lessons[6], "fyz4_unit7", "fyz4_unit7_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz4_unit7_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Co je řetězová reakce?", + {"Jedno štěpení, které se dál nešíří", + "Štěpení udržované neutrony z předchozích štěpení", + "Slučování elektronů", + "Rozpad alfa bez neutronů"}, + 4, 1}, + {"K čemu je v reaktoru moderátor?", + {"Aby neutrony zpomalil", + "Aby všechna jádra okamžitě rozbil", + "Aby pohltil všechno teplo bez užitku", + "Aby zvýšil protonové číslo paliva"}, + 4, 0}, + {"Co dělají regulační tyče?", + {"Neutrony zachycují a reakci brzdí", + "Neutrony jen vyrábějí", + "Slouží jako palivo místo uranu", + "Zvyšují teplotu fúze"}, + 4, 0}, + {"Proč fúze potřebuje vysokou teplotu?", + {"Aby jádra překonala elektrické odpuzování", + "Aby se jádra ochladila", + "Aby vznikl moderátor", + "Aby poločas klesl k nule"}, + 4, 0}, + }; + static const char *hints[] = { + "Kritické množství znamená, že neutrony nestačí utéct a reakce běží.", + "Pomalý neutron štěpí uran 235 ochotněji než rychlý.", + "Zasunutí tyčí výkon sníží. Vysunutí ho zvýší.", + "Ve Slunci fúzi pomáhá i obrovský tlak. Záření se stíní olovem a betonem.", + }; + + return fyz_mcq_page(3, 6, "fyz4unit7", "fyz4_ex7_title", "fyz4_quiz7_head", + qs, hints, 4); +} + +static GtkWidget *build_fyz4_unit8_page(void) { + static const NetSlide slides[] = { + { + "1 / 3 • Slunce", "Hvězda", + "Tip: Slunce získává energii fúzí vodíku na helium.", + { + "Slunce je hvězda. Svítí díky jaderné syntéze v jádře.", + "Světlo ze Slunce letí na Zemi asi 8 minut.", + "Svítivost a teplota povrchu řadí hvězdy do Hertzsprungova–Russellova diagramu.", + "Většina hvězd leží na hlavní posloupnosti.", + NULL, + }, + }, + { + "2 / 3 • Vývoj", "Od oblaku k závěru", + "Tip: hmotnější hvězda žije kratší dobu.", + { + "Hvězda vzniká smršťováním oblaku plynu a prachu.", + "Po vyčerpání vodíku se mění stavba hvězdy.", + "Hvězda podobná Slunci skončí jako bílý trpaslík.", + "Velmi hmotná hvězda může skončit výbuchem supernovy a zbyde neutronová hvězda nebo černá díra.", + NULL, + }, + }, + { + "3 / 3 • Vesmír", "Galaxie a rozpínání", + "Tip: světelný rok je vzdálenost, ne čas.", + { + "Galaxie je soustava hvězd, plynu a prachu. Slunce je v Mléčné dráze.", + "Světelný rok je dráha, kterou světlo urazí za rok.", + "Vzdálené galaxie mají spektrum posunuté k červené. To znamená, že se vzdalují.", + "Rudý posuv podporuje obraz rozpínajícího se vesmíru.", + NULL, + }, + }, + }; + + return fyz_unit_page(3, &fyz4_lessons[7], "fyz4_unit8", "fyz4_unit8_sub", + slides, G_N_ELEMENTS(slides)); +} + +static GtkWidget *build_fyz4_unit8_exercise_page(void) { + static const ChoiceQ qs[] = { + {"Odkud bere Slunce energii?", + {"Z hoření uhlí", "Z fúze vodíku na helium", + "Z štěpení uranu v kůře", "Z odrazu světla planet"}, + 4, 1}, + {"Co je hlavní posloupnost?", + {"Pás, na kterém leží většina hvězd v HR diagramu", + "Dráha Země kolem Slunce", + "Řada planet podle velikosti", + "Spektrum jen bílých trpaslíků"}, + 4, 0}, + {"Co je světelný rok?", + {"Doba oběhu Země", "Vzdálenost, kterou světlo urazí za rok", + "Rychlost světla", "Stáří Slunce"}, + 4, 1}, + {"Co znamená rudý posuv vzdálených galaxií?", + {"Galaxie se přibližují", + "Galaxie se vzdalují", + "Galaxie přestaly svítit", + "Světlo změnilo rychlost ve vakuu"}, + 4, 1}, + }; + static const char *hints[] = { + "Fúze v jádře Slunce mění hmotnost na energii podle E = m·c².", + "Obři a bílí trpaslíci leží mimo hlavní posloupnost.", + "Rok je tu doba letu světla, výsledek je délka.", + "Posuv k delší vlnové délce je Dopplerův jev rozpínání.", + }; + + return fyz_mcq_page(3, 7, "fyz4unit8", "fyz4_ex8_title", "fyz4_quiz8_head", + qs, hints, 4); +} + +void add_fyz4_pages(GtkStack *stack) { + typedef GtkWidget *(*FyzBuilder)(void); + static const struct { + const char *unit_name; + const char *ex_name; + FyzBuilder build_unit; + FyzBuilder build_ex; + } pages[] = { + {"fyz4unit1", "fyz4ex1", build_fyz4_unit1_page, build_fyz4_unit1_exercise_page}, + {"fyz4unit2", "fyz4ex2", build_fyz4_unit2_page, build_fyz4_unit2_exercise_page}, + {"fyz4unit3", "fyz4ex3", build_fyz4_unit3_page, build_fyz4_unit3_exercise_page}, + {"fyz4unit4", "fyz4ex4", build_fyz4_unit4_page, build_fyz4_unit4_exercise_page}, + {"fyz4unit5", "fyz4ex5", build_fyz4_unit5_page, build_fyz4_unit5_exercise_page}, + {"fyz4unit6", "fyz4ex6", build_fyz4_unit6_page, build_fyz4_unit6_exercise_page}, + {"fyz4unit7", "fyz4ex7", build_fyz4_unit7_page, build_fyz4_unit7_exercise_page}, + {"fyz4unit8", "fyz4ex8", build_fyz4_unit8_page, build_fyz4_unit8_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); + } +} diff --git a/src/fyzika.c b/src/fyzika.c new file mode 100644 index 0000000..05da2d4 --- /dev/null +++ b/src/fyzika.c @@ -0,0 +1,720 @@ +#include "graduately.h" + +/* Physics across four secondary-school years. Each year is a list of + * lessons, then slides and a quiz. The syllabus follows the gymnasium + * outline: mechanics, thermal physics and waves, electromagnetism, + * then optics, relativity, the micro-world and astrophysics. */ + +typedef struct { + const char *map_page; + const char *back_page; + const char *title_key; + const char *sub_key; + char **progress_path; + NetLesson *lessons; + const char *unit_keys[FYZ_N]; + const char *sub_keys[FYZ_N]; + GtkWidget *nodes[FYZ_N]; + int note_last_w; +} FyzTrack; + +static FyzTrack fyz_tracks[4]; +static gboolean fyz_ready; + +static void fyz_ensure(void) { + static const char *keys0[] = { + "fyz_unit1", "fyz_unit2", "fyz_unit3", "fyz_unit4", + "fyz_unit5", "fyz_unit6", "fyz_unit7", "fyz_unit8", + }; + static const char *subs0[] = { + "fyz_unit1_sub", "fyz_unit2_sub", "fyz_unit3_sub", "fyz_unit4_sub", + "fyz_unit5_sub", "fyz_unit6_sub", "fyz_unit7_sub", "fyz_unit8_sub", + }; + static const char *keys1[] = { + "fyz2_unit1", "fyz2_unit2", "fyz2_unit3", "fyz2_unit4", + "fyz2_unit5", "fyz2_unit6", "fyz2_unit7", "fyz2_unit8", + }; + static const char *subs1[] = { + "fyz2_unit1_sub", "fyz2_unit2_sub", "fyz2_unit3_sub", "fyz2_unit4_sub", + "fyz2_unit5_sub", "fyz2_unit6_sub", "fyz2_unit7_sub", "fyz2_unit8_sub", + }; + static const char *keys2[] = { + "fyz3_unit1", "fyz3_unit2", "fyz3_unit3", "fyz3_unit4", + "fyz3_unit5", "fyz3_unit6", "fyz3_unit7", "fyz3_unit8", + }; + static const char *subs2[] = { + "fyz3_unit1_sub", "fyz3_unit2_sub", "fyz3_unit3_sub", "fyz3_unit4_sub", + "fyz3_unit5_sub", "fyz3_unit6_sub", "fyz3_unit7_sub", "fyz3_unit8_sub", + }; + static const char *keys3[] = { + "fyz4_unit1", "fyz4_unit2", "fyz4_unit3", "fyz4_unit4", + "fyz4_unit5", "fyz4_unit6", "fyz4_unit7", "fyz4_unit8", + }; + static const char *subs3[] = { + "fyz4_unit1_sub", "fyz4_unit2_sub", "fyz4_unit3_sub", "fyz4_unit4_sub", + "fyz4_unit5_sub", "fyz4_unit6_sub", "fyz4_unit7_sub", "fyz4_unit8_sub", + }; + static const char *const *all_keys[4] = {keys0, keys1, keys2, keys3}; + static const char *const *all_subs[4] = {subs0, subs1, subs2, subs3}; + static const char *maps[] = {"fyzmap", "fyz2map", "fyz3map", "fyz4map"}; + static const char *titles[] = { + "fyz_year1", "fyz_year2", "fyz_year3", "fyz_year4", + }; + static const char *subs[] = {"fyz_sub", "fyz2_sub", "fyz3_sub", "fyz4_sub"}; + NetLesson *lessons[4] = { + fyz_lessons, fyz2_lessons, fyz3_lessons, fyz4_lessons, + }; + char **paths[4] = { + &app_progress_fyz, &app_progress_fyz2, + &app_progress_fyz3, &app_progress_fyz4, + }; + + if (fyz_ready) + return; + for (int y = 0; y < 4; y++) { + FyzTrack *T = &fyz_tracks[y]; + + T->map_page = maps[y]; + T->back_page = "fyzyears"; + T->title_key = titles[y]; + T->sub_key = subs[y]; + T->progress_path = paths[y]; + T->lessons = lessons[y]; + T->note_last_w = -1; + for (int i = 0; i < FYZ_N; i++) { + T->unit_keys[i] = all_keys[y][i]; + T->sub_keys[i] = all_subs[y][i]; + } + } + fyz_ready = TRUE; +} + +static void fyz_save(FyzTrack *T) { + GKeyFile *kf; + char *path; + gchar *data; + + if (!T->progress_path || !*T->progress_path) + return; + path = *T->progress_path; + kf = g_key_file_new(); + for (int i = 0; i < FYZ_N; i++) { + gchar key[8]; + + g_snprintf(key, sizeof(key), "%d", i + 1); + g_key_file_set_boolean(kf, "done", key, T->lessons[i].done); + } + if (g_mkdir_with_parents(PROGRESS_DIR, 0755) != 0) { + g_key_file_free(kf); + return; + } + data = g_key_file_to_data(kf, NULL, NULL); + g_key_file_free(kf); + if (data) { + GError *err = NULL; + + g_file_set_contents(path, data, -1, &err); + if (err) + g_error_free(err); + g_free(data); + } +} + +static void fyz_load_one(FyzTrack *T) { + GKeyFile *kf; + GError *err = NULL; + const char *path; + + if (!T->progress_path || !*T->progress_path) + return; + path = *T->progress_path; + kf = g_key_file_new(); + if (!g_key_file_load_from_file(kf, path, G_KEY_FILE_NONE, &err)) { + if (err) + g_error_free(err); + g_key_file_free(kf); + return; + } + for (int i = 0; i < FYZ_N; i++) { + gchar key[8]; + + g_snprintf(key, sizeof(key), "%d", i + 1); + T->lessons[i].done = g_key_file_get_boolean(kf, "done", key, NULL); + } + g_key_file_free(kf); +} + +void fyz_load_progress(void) { + fyz_ensure(); + for (int y = 0; y < 4; y++) + fyz_load_one(&fyz_tracks[y]); +} + +void fyz_rail_theme_reset(void) { +} + +static void fyz_refresh(FyzTrack *T) { + for (int i = 0; i < FYZ_N; i++) { + GtkWidget *node = T->nodes[i]; + GtkWidget *icon = T->lessons[i].done_icon; + + if (!node) + continue; + if (T->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); + } + } +} + +static void fyz_mark_done(FyzTrack *T, int lesson_id) { + if (lesson_id < 0 || lesson_id >= FYZ_N) + return; + if (T->lessons[lesson_id].done) + return; + T->lessons[lesson_id].done = TRUE; + fyz_save(T); + fyz_refresh(T); + refresh_stats_ui(); +} + +void progress_for_fyz(ProgressSum *out) { + if (!out) + return; + fyz_ensure(); + for (int y = 0; y < 4; y++) { + FyzTrack *T = &fyz_tracks[y]; + + out->total_ex += FYZ_N; + out->open_units += FYZ_N; + for (int i = 0; i < FYZ_N; i++) { + if (T->lessons[i].done) { + out->done_ex++; + out->done_units++; + } + } + } +} + +static void fyz_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), "s%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 GtkWidget *fyz_lesson_button(FyzTrack *T, int index) { + GtkWidget *btn; + GtkWidget *row; + GtkWidget *num; + GtkWidget *texts; + GtkWidget *title; + GtkWidget *sub; + GtkWidget *icon; + char *num_text; + + btn = gtk_button_new(); + gtk_widget_add_css_class(btn, "unit-node"); + gtk_widget_add_css_class(btn, "current"); + gtk_widget_set_can_focus(btn, FALSE); + gtk_widget_set_hexpand(btn, TRUE); + gtk_widget_set_size_request(btn, -1, 84); + + row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 14); + gtk_widget_set_halign(row, GTK_ALIGN_START); + gtk_widget_set_valign(row, GTK_ALIGN_CENTER); + gtk_widget_set_margin_start(row, 18); + gtk_widget_set_margin_end(row, 18); + gtk_button_set_child(GTK_BUTTON(btn), row); + + num_text = g_strdup_printf("%d", index + 1); + num = gtk_label_new(num_text); + g_free(num_text); + gtk_widget_add_css_class(num, "unit-number"); + gtk_widget_set_valign(num, GTK_ALIGN_CENTER); + gtk_box_append(GTK_BOX(row), num); + + texts = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2); + gtk_widget_set_halign(texts, GTK_ALIGN_START); + gtk_widget_set_hexpand(texts, TRUE); + gtk_box_append(GTK_BOX(row), texts); + + title = gtk_label_new(NULL); + i18n_bind(title, T->unit_keys[index], 0); + gtk_widget_set_halign(title, GTK_ALIGN_START); + gtk_widget_add_css_class(title, "unit-name"); + gtk_box_append(GTK_BOX(texts), title); + + sub = gtk_label_new(NULL); + i18n_bind(sub, T->sub_keys[index], 0); + gtk_widget_set_halign(sub, GTK_ALIGN_START); + gtk_label_set_wrap(GTK_LABEL(sub), TRUE); + gtk_label_set_xalign(GTK_LABEL(sub), 0.0); + gtk_widget_add_css_class(sub, "hint"); + gtk_box_append(GTK_BOX(texts), sub); + + icon = icon_area_new(draw_check_icon, 0.1176, 0.1176, 0.1804, 18); + gtk_widget_set_visible(icon, FALSE); + gtk_widget_set_valign(icon, GTK_ALIGN_CENTER); + gtk_box_append(GTK_BOX(row), icon); + T->lessons[index].done_icon = icon; + T->nodes[index] = btn; + g_signal_connect(btn, "clicked", G_CALLBACK(fyz_open_unit), + &T->lessons[index]); + return btn; +} + +static GtkWidget *fyz_build_list(FyzTrack *T) { + GtkWidget *page; + GtkWidget *scroll; + GtkWidget *list; + + fyz_ensure(); + 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(T->back_page, T->title_key, T->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, 18); + gtk_box_append(GTK_BOX(page), scroll); + + list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12); + gtk_widget_set_halign(list, GTK_ALIGN_CENTER); + gtk_widget_set_valign(list, GTK_ALIGN_START); + gtk_widget_set_size_request(list, 520, -1); + gtk_widget_set_margin_bottom(list, 12); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), list); + + for (int i = 0; i < FYZ_N; i++) + gtk_box_append(GTK_BOX(list), fyz_lesson_button(T, i)); + fyz_refresh(T); + return page; +} + +GtkWidget *build_fyzmap_page(void) { return fyz_build_list(&fyz_tracks[0]); } +GtkWidget *build_fyz2map_page(void) { return fyz_build_list(&fyz_tracks[1]); } +GtkWidget *build_fyz3map_page(void) { return fyz_build_list(&fyz_tracks[2]); } +GtkWidget *build_fyz4map_page(void) { return fyz_build_list(&fyz_tracks[3]); } + +static GtkWidget *fyz_year_button(int year, const char *target, + const char *title_key, const char *sub_key) { + GtkWidget *btn; + GtkWidget *row; + GtkWidget *num; + GtkWidget *texts; + GtkWidget *title; + GtkWidget *sub; + char *num_text; + + btn = gtk_button_new(); + gtk_widget_add_css_class(btn, "unit-node"); + gtk_widget_add_css_class(btn, "current"); + gtk_widget_set_can_focus(btn, FALSE); + gtk_widget_set_hexpand(btn, TRUE); + gtk_widget_set_size_request(btn, -1, 84); + + row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 14); + gtk_widget_set_halign(row, GTK_ALIGN_START); + gtk_widget_set_valign(row, GTK_ALIGN_CENTER); + gtk_widget_set_margin_start(row, 18); + gtk_widget_set_margin_end(row, 18); + gtk_button_set_child(GTK_BUTTON(btn), row); + + num_text = g_strdup_printf("%d", year); + num = gtk_label_new(num_text); + g_free(num_text); + gtk_widget_add_css_class(num, "unit-number"); + gtk_widget_set_valign(num, GTK_ALIGN_CENTER); + gtk_box_append(GTK_BOX(row), num); + + texts = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2); + gtk_widget_set_halign(texts, GTK_ALIGN_START); + gtk_widget_set_hexpand(texts, TRUE); + gtk_box_append(GTK_BOX(row), texts); + + title = gtk_label_new(NULL); + i18n_bind(title, title_key, 0); + gtk_widget_set_halign(title, GTK_ALIGN_START); + gtk_widget_add_css_class(title, "unit-name"); + gtk_box_append(GTK_BOX(texts), title); + + sub = gtk_label_new(NULL); + i18n_bind(sub, sub_key, 0); + gtk_widget_set_halign(sub, GTK_ALIGN_START); + gtk_label_set_wrap(GTK_LABEL(sub), TRUE); + gtk_label_set_xalign(GTK_LABEL(sub), 0.0); + gtk_widget_add_css_class(sub, "hint"); + gtk_box_append(GTK_BOX(texts), sub); + + g_object_set_data_full(G_OBJECT(btn), "target", g_strdup(target), g_free); + g_signal_connect(btn, "clicked", G_CALLBACK(on_nav_clicked), NULL); + return btn; +} + +GtkWidget *build_fyzyears_page(void) { + GtkWidget *page; + GtkWidget *scroll; + GtkWidget *list; + static const char *targets[] = {"fyzmap", "fyz2map", "fyz3map", "fyz4map"}; + static const char *titles[] = { + "fyz_year1", "fyz_year2", "fyz_year3", "fyz_year4", + }; + static const char *subs[] = {"fyz_sub", "fyz2_sub", "fyz3_sub", "fyz4_sub"}; + + 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("subjects", "Fyzika", "fyz_years_sub")); + + 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, 18); + gtk_box_append(GTK_BOX(page), scroll); + + list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12); + gtk_widget_set_halign(list, GTK_ALIGN_CENTER); + gtk_widget_set_valign(list, GTK_ALIGN_CENTER); + gtk_widget_set_size_request(list, 480, -1); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), list); + + for (int y = 0; y < 4; y++) + gtk_box_append(GTK_BOX(list), + fyz_year_button(y + 1, targets[y], titles[y], subs[y])); + return page; +} + +static void fyz_slide_apply(NetLesson *L) { + char name[16]; + + if (!L || !L->stack) + return; + g_snprintf(name, sizeof(name), "s%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 fyz_slide_prev(GtkButton *button, gpointer data) { + NetLesson *L = data; + + (void)button; + if (!L || L->idx == 0) + return; + L->idx--; + fyz_slide_apply(L); +} + +static void fyz_slide_next(GtkButton *button, gpointer data) { + NetLesson *L = data; + + (void)button; + if (!L) + return; + if (L->idx + 1 < L->n_slides) { + L->idx++; + fyz_slide_apply(L); + } else { + gtk_stack_set_visible_child_name(main_stack, L->ex_page); + } +} + +static void fyz_rescale(FyzTrack *T, 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 < FYZ_N; i++) + net_rescale_lesson_notes(&T->lessons[i], body, head, kick); +} + +static gboolean fyz_note_tick(GtkWidget *w, GdkFrameClock *clock, + gpointer data) { + FyzTrack *T = data; + int width = gtk_widget_get_allocated_width(w); + + (void)clock; + if (width != T->note_last_w) { + T->note_last_w = width; + fyz_rescale(T, width); + } + return G_SOURCE_CONTINUE; +} + +GtkWidget *fyz_unit_page(int year, NetLesson *L, const char *title_key, + const char *sub_key, const NetSlide *slides, + guint n_slides) { + FyzTrack *T; + GtkWidget *page; + GtkWidget *scroll; + GtkWidget *nav; + + fyz_ensure(); + if (year < 0 || year > 3) + year = 0; + T = &fyz_tracks[year]; + + 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(T->map_page, 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), "s%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, fyz_note_tick, T, NULL); + T->note_last_w = -1; + fyz_rescale(T, 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(fyz_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(fyz_slide_next), L); + gtk_box_append(GTK_BOX(nav), L->next_btn); + + L->idx = 0; + fyz_slide_apply(L); + return page; +} + +void fyz_lessons_apply_lang(void) { + fyz_ensure(); + for (int y = 0; y < 4; y++) { + for (int i = 0; i < FYZ_N; i++) + fyz_slide_apply(&fyz_tracks[y].lessons[i]); + } +} + +typedef struct { + const ChoiceQ *qs; + int n; + int n_opts; + FyzTrack *track; + int lesson_id; + GtkWidget *feedback; + GtkToggleButton **toggles; + GtkWidget **hints; +} FyzMcqCtx; + +static void fyz_mcq_check(GtkButton *button, gpointer data) { + FyzMcqCtx *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")); + fyz_mark_done(ctx->track, ctx->lesson_id); + } else { + set_feedback(ctx->feedback, FALSE, tr("feedback_retry_short")); + } +} + +GtkWidget *fyz_mcq_page(int year, 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; + FyzMcqCtx *ctx = g_new0(FyzMcqCtx, 1); + int n_opts = qs[0].n_options; + + fyz_ensure(); + if (year < 0 || year > 3) + year = 0; + + 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->track = &fyz_tracks[year]; + 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(fyz_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; +} diff --git a/src/graduately.h b/src/graduately.h index 405a8ac..314e7d4 100644 --- a/src/graduately.h +++ b/src/graduately.h @@ -20,6 +20,7 @@ #define HW_SUBJ 2 /* index of "Technické vybavení" */ #define CZ_SUBJ 3 /* index of "Český jazyk a literatura" */ #define ON_SUBJ 4 /* index of "Občanská nauka" */ +#define PHY_SUBJ 7 /* index of "Fyzika" */ #define SCI_SUBJ 8 /* index of "Základy Přírodopisných věd" */ #define CSS_FILE "style.css" #define CHANGELOG_FILE "changelog.txt" @@ -62,6 +63,10 @@ extern char *app_progress_lit3; extern char *app_progress_lit4; extern char *app_progress_chem; extern char *app_progress_bio; +extern char *app_progress_fyz; +extern char *app_progress_fyz2; +extern char *app_progress_fyz3; +extern char *app_progress_fyz4; #define PROGRESS_DIR app_progress_dir #define PROGRESS_U1 app_progress_u1 #define PROGRESS_U2 app_progress_u2 @@ -140,6 +145,7 @@ extern char *app_progress_bio; #define ON4_LESSONS 16 #define LIT_N 8 #define SCI_N 8 +#define FYZ_N 8 #define HW_SLIDES 3 #define HW2_SLIDES 6 #define HW3_SLIDES 2 @@ -950,6 +956,29 @@ void add_chem_pages(GtkStack *stack); void add_bio_pages(GtkStack *stack); extern NetLesson chem_lessons[SCI_N]; extern NetLesson bio_lessons[SCI_N]; +void fyz_load_progress(void); +void fyz_rail_theme_reset(void); +void fyz_lessons_apply_lang(void); +void progress_for_fyz(ProgressSum *out); +GtkWidget *fyz_unit_page(int year, NetLesson *L, const char *title_key, + const char *sub_key, const NetSlide *slides, + guint n_slides); +GtkWidget *fyz_mcq_page(int year, 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_fyzyears_page(void); +GtkWidget *build_fyzmap_page(void); +GtkWidget *build_fyz2map_page(void); +GtkWidget *build_fyz3map_page(void); +GtkWidget *build_fyz4map_page(void); +void add_fyz_pages(GtkStack *stack); +void add_fyz2_pages(GtkStack *stack); +void add_fyz3_pages(GtkStack *stack); +void add_fyz4_pages(GtkStack *stack); +extern NetLesson fyz_lessons[FYZ_N]; +extern NetLesson fyz2_lessons[FYZ_N]; +extern NetLesson fyz3_lessons[FYZ_N]; +extern NetLesson fyz4_lessons[FYZ_N]; void hw_lessons_apply_lang(void); GtkWidget *build_hwyears_page(void); GtkWidget *build_hwmap_page(void); @@ -1039,6 +1068,8 @@ void draw_civics_icon(GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer data); void draw_science_icon(GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer data); +void draw_physics_icon(GtkDrawingArea *area, cairo_t *cr, + int width, int height, gpointer data); void draw_czech_flag(GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer data); void draw_book_icon(GtkDrawingArea *area, cairo_t *cr, diff --git a/src/i18n.c b/src/i18n.c index 82e16a1..da44ef8 100644 --- a/src/i18n.c +++ b/src/i18n.c @@ -90,12 +90,12 @@ const TrEntry tr_ui[] = { "Cvičení, nápovědy a vokabeltraining. Aplikace si pamatuje, kde jste skončili.", "Exercises, hints and vocabulary drills. The app remembers where you left off."}, {"welcome_feat1_link", "Začít procvičovat", "Start practicing"}, - {"welcome_feat2_title", "Šest otevřených předmětů", "Six open subjects"}, + {"welcome_feat2_title", "Sedm otevřených předmětů", "Seven open subjects"}, {"welcome_feat2_body", - "Deutsch, sítě, hardware, čeština, občanská nauka a přírodní vědy. Ostatní předměty čekají na svůj obsah.", - "German, networks, hardware, Czech, civics and natural sciences. The other subjects are still locked."}, + "Deutsch, sítě, hardware, čeština, občanská nauka, fyzika a přírodní vědy. Ostatní předměty čekají na svůj obsah.", + "German, networks, hardware, Czech, civics, physics and natural sciences. The other subjects are still locked."}, {"welcome_feat2_link", "Jaké předměty jsou otevřené?", "Which subjects are open?"}, - {"welcome_stat_value", "6", "6"}, + {"welcome_stat_value", "7", "7"}, {"welcome_stat_label", "otevřené\npředměty", "open\nsubjects"}, {"continue", "Pokračuj", "Continue"}, {"roadmap_title", "Učební plán", "Learning path"}, @@ -1396,6 +1396,183 @@ const TrEntry tr_ui[] = { {"bio_ex8_title", "Kvíz: ekologie", "Quiz: ecology"}, {"bio_quiz8_head", "Ověřte si vztahy v ekosystému", "Check relationships in an ecosystem"}, + {"fyz_years_sub", "Fyzika podle osnov středních škol. Vyberte ročník.", + "Physics from the secondary-school syllabus. Choose a year."}, + {"fyz_year1", "1. ročník", "Year 1"}, + {"fyz_year2", "2. ročník", "Year 2"}, + {"fyz_year3", "3. ročník", "Year 3"}, + {"fyz_year4", "4. ročník", "Year 4"}, + {"fyz_sub", "Mechanika: veličiny, pohyb, síly a energie.", + "Mechanics: quantities, motion, forces and energy."}, + {"fyz2_sub", "Molekulová fyzika, teplo, kmity a vlny.", + "Molecular physics, heat, oscillations and waves."}, + {"fyz3_sub", "Elektřina a magnetismus.", + "Electricity and magnetism."}, + {"fyz4_sub", "Optika, relativita, mikročástice a vesmír.", + "Optics, relativity, the micro-world and the universe."}, + {"fyz_unit1", "Fyzikální veličiny", "Physical quantities"}, + {"fyz_unit1_sub", "SI, skalár a vektor, chyba měření.", + "SI, scalar and vector, measurement error."}, + {"fyz_ex1_title", "Kvíz: veličiny", "Quiz: quantities"}, + {"fyz_quiz1_head", "Ověřte si jednotky a chyby", "Check units and errors"}, + {"fyz_unit2", "Kinematika", "Kinematics"}, + {"fyz_unit2_sub", "Dráha, rychlost a zrychlení.", + "Distance, speed and acceleration."}, + {"fyz_ex2_title", "Kvíz: kinematika", "Quiz: kinematics"}, + {"fyz_quiz2_head", "Ověřte si pohyb", "Check motion"}, + {"fyz_unit3", "Newtonovy zákony", "Newton's laws"}, + {"fyz_unit3_sub", "Setrvačnost, síla a hybnost.", + "Inertia, force and momentum."}, + {"fyz_ex3_title", "Kvíz: Newton", "Quiz: Newton"}, + {"fyz_quiz3_head", "Ověřte si zákony síly", "Check the laws of force"}, + {"fyz_unit4", "Síly v běžných situacích", "Everyday forces"}, + {"fyz_unit4_sub", "Tíha, tření, tlak a výslednice.", + "Weight, friction, pressure and the resultant."}, + {"fyz_ex4_title", "Kvíz: síly", "Quiz: forces"}, + {"fyz_quiz4_head", "Ověřte si tíhu a tlak", "Check weight and pressure"}, + {"fyz_unit5", "Práce, energie a výkon", "Work, energy and power"}, + {"fyz_unit5_sub", "Mechanická práce, energie a účinnost.", + "Mechanical work, energy and efficiency."}, + {"fyz_ex5_title", "Kvíz: energie", "Quiz: energy"}, + {"fyz_quiz5_head", "Ověřte si práci a výkon", "Check work and power"}, + {"fyz_unit6", "Gravitační pole", "Gravitational field"}, + {"fyz_unit6_sub", "Newtonův gravitační zákon a kosmické rychlosti.", + "Newton's law of gravitation and cosmic speeds."}, + {"fyz_ex6_title", "Kvíz: gravitace", "Quiz: gravity"}, + {"fyz_quiz6_head", "Ověřte si gravitační pole", "Check the gravitational field"}, + {"fyz_unit7", "Tuhé těleso", "Rigid body"}, + {"fyz_unit7_sub", "Moment síly, páka a těžiště.", + "Moment of a force, the lever and the centre of gravity."}, + {"fyz_ex7_title", "Kvíz: tuhé těleso", "Quiz: the rigid body"}, + {"fyz_quiz7_head", "Ověřte si otáčení", "Check rotation"}, + {"fyz_unit8", "Kapaliny a plyny", "Liquids and gases"}, + {"fyz_unit8_sub", "Hydrostatika, Archimédés a Bernoulli.", + "Hydrostatics, Archimedes and Bernoulli."}, + {"fyz_ex8_title", "Kvíz: tekutiny", "Quiz: fluids"}, + {"fyz_quiz8_head", "Ověřte si tlak v kapalině", "Check pressure in a liquid"}, + {"fyz2_unit1", "Částicová stavba", "Particle structure"}, + {"fyz2_unit1_sub", "Brownův pohyb, skupenství a vnitřní energie.", + "Brownian motion, states of matter and internal energy."}, + {"fyz2_ex1_title", "Kvíz: částice", "Quiz: particles"}, + {"fyz2_quiz1_head", "Ověřte si stavbu látky", "Check the structure of matter"}, + {"fyz2_unit2", "Teplota a teplo", "Temperature and heat"}, + {"fyz2_unit2_sub", "Kelvin, měrná tepelná kapacita a šíření tepla.", + "The kelvin, specific heat capacity and heat transfer."}, + {"fyz2_ex2_title", "Kvíz: teplo", "Quiz: heat"}, + {"fyz2_quiz2_head", "Ověřte si teplotu a teplo", "Check temperature and heat"}, + {"fyz2_unit3", "Kalorimetrie", "Calorimetry"}, + {"fyz2_unit3_sub", "Směšování, skupenské teplo a první zákon.", + "Mixing, latent heat and the first law."}, + {"fyz2_ex3_title", "Kvíz: kalorimetrie", "Quiz: calorimetry"}, + {"fyz2_quiz3_head", "Ověřte si tepelnou výměnu", "Check heat exchange"}, + {"fyz2_unit4", "Ideální plyn", "Ideal gas"}, + {"fyz2_unit4_sub", "Stavová rovnice a děje.", + "The equation of state and processes."}, + {"fyz2_ex4_title", "Kvíz: plyn", "Quiz: gases"}, + {"fyz2_quiz4_head", "Ověřte si stavovou rovnici", "Check the equation of state"}, + {"fyz2_unit5", "Pevné látky a kapaliny", "Solids and liquids"}, + {"fyz2_unit5_sub", "Hooke, roztažnost a povrchové napětí.", + "Hooke, expansion and surface tension."}, + {"fyz2_ex5_title", "Kvíz: látky", "Quiz: materials"}, + {"fyz2_quiz5_head", "Ověřte si deformaci a povrch", "Check deformation and surfaces"}, + {"fyz2_unit6", "Změny skupenství", "Changes of state"}, + {"fyz2_unit6_sub", "Tání, var a vlhkost vzduchu.", + "Melting, boiling and humidity."}, + {"fyz2_ex6_title", "Kvíz: skupenství", "Quiz: states of matter"}, + {"fyz2_quiz6_head", "Ověřte si tání a var", "Check melting and boiling"}, + {"fyz2_unit7", "Mechanické kmitání", "Mechanical oscillations"}, + {"fyz2_unit7_sub", "Pružina, kyvadlo a rezonance.", + "The spring, the pendulum and resonance."}, + {"fyz2_ex7_title", "Kvíz: kmity", "Quiz: oscillations"}, + {"fyz2_quiz7_head", "Ověřte si oscilátor", "Check the oscillator"}, + {"fyz2_unit8", "Vlnění a zvuk", "Waves and sound"}, + {"fyz2_unit8_sub", "Vlnová délka, frekvence a slyšitelný zvuk.", + "Wavelength, frequency and audible sound."}, + {"fyz2_ex8_title", "Kvíz: zvuk", "Quiz: sound"}, + {"fyz2_quiz8_head", "Ověřte si vlnění", "Check waves"}, + {"fyz3_unit1", "Elektrický náboj a pole", "Electric charge and field"}, + {"fyz3_unit1_sub", "Coulombův zákon a napětí.", + "Coulomb's law and voltage."}, + {"fyz3_ex1_title", "Kvíz: náboj", "Quiz: charge"}, + {"fyz3_quiz1_head", "Ověřte si elektrické pole", "Check the electric field"}, + {"fyz3_unit2", "Proud v kovech", "Current in metals"}, + {"fyz3_unit2_sub", "Ohmův zákon a odpor.", + "Ohm's law and resistance."}, + {"fyz3_ex2_title", "Kvíz: proud", "Quiz: current"}, + {"fyz3_quiz2_head", "Ověřte si Ohmův zákon", "Check Ohm's law"}, + {"fyz3_unit3", "Elektrické obvody", "Electric circuits"}, + {"fyz3_unit3_sub", "Sériové a paralelní spojení, výkon.", + "Series and parallel connection, power."}, + {"fyz3_ex3_title", "Kvíz: obvody", "Quiz: circuits"}, + {"fyz3_quiz3_head", "Ověřte si zapojení", "Check the connections"}, + {"fyz3_unit4", "Polovodiče, kapaliny a plyny", + "Semiconductors, liquids and gases"}, + {"fyz3_unit4_sub", "Dioda, elektrolýza a výboj.", + "The diode, electrolysis and discharge."}, + {"fyz3_ex4_title", "Kvíz: vodivost", "Quiz: conduction"}, + {"fyz3_quiz4_head", "Ověřte si polovodiče a ionty", + "Check semiconductors and ions"}, + {"fyz3_unit5", "Magnetické pole", "Magnetic field"}, + {"fyz3_unit5_sub", "Indukční čáry a síla na vodič.", + "Field lines and the force on a wire."}, + {"fyz3_ex5_title", "Kvíz: magnetismus", "Quiz: magnetism"}, + {"fyz3_quiz5_head", "Ověřte si magnetické pole", "Check the magnetic field"}, + {"fyz3_unit6", "Elektromagnetická indukce", "Electromagnetic induction"}, + {"fyz3_unit6_sub", "Faraday, Lenz a transformátor.", + "Faraday, Lenz and the transformer."}, + {"fyz3_ex6_title", "Kvíz: indukce", "Quiz: induction"}, + {"fyz3_quiz6_head", "Ověřte si indukci", "Check induction"}, + {"fyz3_unit7", "Střídavý proud", "Alternating current"}, + {"fyz3_unit7_sub", "Efektivní hodnota a fáze.", + "The rms value and phase."}, + {"fyz3_ex7_title", "Kvíz: střídavý proud", "Quiz: alternating current"}, + {"fyz3_quiz7_head", "Ověřte si síť 230 V", "Check the 230 V grid"}, + {"fyz3_unit8", "Elektromagnetické vlnění", "Electromagnetic waves"}, + {"fyz3_unit8_sub", "Rychlost světla a spektrum.", + "The speed of light and the spectrum."}, + {"fyz3_ex8_title", "Kvíz: spektrum", "Quiz: the spectrum"}, + {"fyz3_quiz8_head", "Ověřte si elektromagnetickou vlnu", + "Check the electromagnetic wave"}, + {"fyz4_unit1", "Vlnová optika", "Wave optics"}, + {"fyz4_unit1_sub", "Odraz, lom, interference a ohyb.", + "Reflection, refraction, interference and diffraction."}, + {"fyz4_ex1_title", "Kvíz: optika", "Quiz: optics"}, + {"fyz4_quiz1_head", "Ověřte si lom a odraz", "Check refraction and reflection"}, + {"fyz4_unit2", "Zobrazování", "Image formation"}, + {"fyz4_unit2_sub", "Zrcadla, čočky a oko.", + "Mirrors, lenses and the eye."}, + {"fyz4_ex2_title", "Kvíz: čočky", "Quiz: lenses"}, + {"fyz4_quiz2_head", "Ověřte si zobrazování", "Check image formation"}, + {"fyz4_unit3", "Speciální relativita", "Special relativity"}, + {"fyz4_unit3_sub", "Postuláty, dilatace a klidová energie.", + "The postulates, dilation and rest energy."}, + {"fyz4_ex3_title", "Kvíz: relativita", "Quiz: relativity"}, + {"fyz4_quiz3_head", "Ověřte si dva postuláty", "Check the two postulates"}, + {"fyz4_unit4", "Kvantové jevy", "Quantum phenomena"}, + {"fyz4_unit4_sub", "Foton, fotoefekt a de Broglie.", + "The photon, the photoelectric effect and de Broglie."}, + {"fyz4_ex4_title", "Kvíz: kvanta", "Quiz: quanta"}, + {"fyz4_quiz4_head", "Ověřte si foton", "Check the photon"}, + {"fyz4_unit5", "Stavba atomu", "Structure of the atom"}, + {"fyz4_unit5_sub", "Rutherford, Bohr a spektra.", + "Rutherford, Bohr and spectra."}, + {"fyz4_ex5_title", "Kvíz: atom", "Quiz: the atom"}, + {"fyz4_quiz5_head", "Ověřte si hladiny", "Check the energy levels"}, + {"fyz4_unit6", "Atomové jádro", "The atomic nucleus"}, + {"fyz4_unit6_sub", "Nukleony, vazebná energie a radioaktivita.", + "Nucleons, binding energy and radioactivity."}, + {"fyz4_ex6_title", "Kvíz: jádro", "Quiz: the nucleus"}, + {"fyz4_quiz6_head", "Ověřte si rozpad", "Check radioactive decay"}, + {"fyz4_unit7", "Jaderné reakce", "Nuclear reactions"}, + {"fyz4_unit7_sub", "Štěpení, reaktor a fúze.", + "Fission, the reactor and fusion."}, + {"fyz4_ex7_title", "Kvíz: reakce", "Quiz: reactions"}, + {"fyz4_quiz7_head", "Ověřte si štěpení a fúzi", "Check fission and fusion"}, + {"fyz4_unit8", "Astrofyzika", "Astrophysics"}, + {"fyz4_unit8_sub", "Hvězdy, galaxie a rozpínání vesmíru.", + "Stars, galaxies and the expanding universe."}, + {"fyz4_ex8_title", "Kvíz: vesmír", "Quiz: the universe"}, + {"fyz4_quiz8_head", "Ověřte si hvězdy a galaxie", "Check stars and galaxies"}, {"czech_sub", "Vyberte oblast a začněte procvičovat.", "Pick an area and start practicing."}, {"Literatura", "Literatura", "Literature"}, @@ -2425,6 +2602,7 @@ void on3_lessons_apply_lang(void); void on4_lessons_apply_lang(void); void lit_lessons_apply_lang(void); void sci_lessons_apply_lang(void); +void fyz_lessons_apply_lang(void); void net_rail_theme_reset(void); void apply_language(void) { @@ -2445,5 +2623,6 @@ void apply_language(void) { on4_lessons_apply_lang(); lit_lessons_apply_lang(); sci_lessons_apply_lang(); + fyz_lessons_apply_lang(); search_apply_lang(); } diff --git a/src/icons.c b/src/icons.c index b14c376..7db09e1 100644 --- a/src/icons.c +++ b/src/icons.c @@ -228,6 +228,39 @@ void draw_science_icon(GtkDrawingArea *area, cairo_t *cr, cairo_stroke(cr); } +/* Atom glyph for Fyzika: nucleus and two orbits. */ +void draw_physics_icon(GtkDrawingArea *area, cairo_t *cr, + int width, int height, gpointer data) { + const double s = MIN(width, height); + const double cx = width / 2.0; + const double cy = height / 2.0; + const Rgb c = mix_rgb(color_from_hex(app_theme.on_accent), + color_from_hex(app_theme.accent2), 0.22); + + (void)area; + (void)data; + + cairo_set_source_rgb(cr, c.r, c.g, c.b); + cairo_set_line_width(cr, MAX(1.6, s * 0.06)); + cairo_arc(cr, cx, cy, s * 0.10, 0, 2 * G_PI); + cairo_fill(cr); + + cairo_save(cr); + cairo_translate(cr, cx, cy); + cairo_scale(cr, 1.0, 0.38); + cairo_arc(cr, 0, 0, s * 0.34, 0, 2 * G_PI); + cairo_restore(cr); + cairo_stroke(cr); + + cairo_save(cr); + cairo_translate(cr, cx, cy); + cairo_rotate(cr, 1.05); + cairo_scale(cr, 1.0, 0.38); + cairo_arc(cr, 0, 0, s * 0.34, 0, 2 * G_PI); + cairo_restore(cr); + cairo_stroke(cr); +} + /* Open book glyph, painted in the given colour. */ static void book_paint(cairo_t *cr, int width, int height, Rgb c) { const double w = width; diff --git a/src/main.c b/src/main.c index 16df5c2..bdc81d9 100644 --- a/src/main.c +++ b/src/main.c @@ -390,6 +390,10 @@ static gboolean run_smoke_test(gpointer data) { "czechmap", "lityears", "litmap", "lit2map", "lit3map", "lit4map", "scimap", "chemmap", "biomap", + "fyzyears", "fyzmap", "fyz2map", "fyz3map", "fyz4map", + "fyzunit1", "fyzex1", "fyzunit8", "fyzex8", + "fyz2unit1", "fyz2ex1", "fyz3unit1", "fyz4unit1", + "fyz4unit8", "fyz4ex8", "chemunit1", "chemex1", "chemunit8", "chemex8", "biounit1", "bioex1", "biounit8", "bioex8", "litunit1", "litex1", "litunit8", "litex8", @@ -528,6 +532,15 @@ void activate(GtkApplication *app, gpointer user_data) { add_on3_pages(main_stack); gtk_stack_add_named(main_stack, build_on4map_page(), "on4map"); add_on4_pages(main_stack); + gtk_stack_add_named(main_stack, build_fyzyears_page(), "fyzyears"); + gtk_stack_add_named(main_stack, build_fyzmap_page(), "fyzmap"); + add_fyz_pages(main_stack); + gtk_stack_add_named(main_stack, build_fyz2map_page(), "fyz2map"); + add_fyz2_pages(main_stack); + gtk_stack_add_named(main_stack, build_fyz3map_page(), "fyz3map"); + add_fyz3_pages(main_stack); + gtk_stack_add_named(main_stack, build_fyz4map_page(), "fyz4map"); + add_fyz4_pages(main_stack); gtk_stack_add_named(main_stack, build_scimap_page(), "scimap"); gtk_stack_add_named(main_stack, build_chemmap_page(), "chemmap"); add_chem_pages(main_stack); diff --git a/src/progress.c b/src/progress.c index 508b0ed..f597155 100644 --- a/src/progress.c +++ b/src/progress.c @@ -60,6 +60,7 @@ void load_progress(void) { on4_load_progress(); lit_load_progress(); sci_load_progress(); + fyz_load_progress(); } /* Redraw the exercise rails of every unit that already has a page. */ diff --git a/src/search.c b/src/search.c index 1a47354..b5045ab 100644 --- a/src/search.c +++ b/src/search.c @@ -23,7 +23,7 @@ static GPtrArray *search_items; static gboolean subject_open(int i) { return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ - || i == ON_SUBJ || i == SCI_SUBJ; + || i == ON_SUBJ || i == PHY_SUBJ || i == SCI_SUBJ; } static const char *subject_page(int i) { @@ -37,6 +37,8 @@ static const char *subject_page(int i) { return "czechmap"; if (i == ON_SUBJ) return "onyears"; + if (i == PHY_SUBJ) + return "fyzyears"; if (i == SCI_SUBJ) return "scimap"; return "subjects"; @@ -273,6 +275,38 @@ static void catalog_rebuild(void) { g_snprintf(page, sizeof(page), "lit4ex%d", i); catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz"); } + catalog_add_key("fyz_year1", "search_lesson", "fyzmap", FALSE, 1, "fyzika rocnik"); + catalog_add_key("fyz_year2", "search_lesson", "fyz2map", FALSE, 1, "fyzika rocnik"); + catalog_add_key("fyz_year3", "search_lesson", "fyz3map", FALSE, 1, "fyzika rocnik"); + catalog_add_key("fyz_year4", "search_lesson", "fyz4map", FALSE, 1, "fyzika rocnik"); + for (i = 1; i <= FYZ_N; i++) { + g_snprintf(key, sizeof(key), "fyz_unit%d", i); + g_snprintf(page, sizeof(page), "fyzunit%d", i); + catalog_add_key(key, "search_lesson", page, FALSE, 2, "fyzika"); + g_snprintf(page, sizeof(page), "fyzex%d", i); + catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz"); + } + for (i = 1; i <= FYZ_N; i++) { + g_snprintf(key, sizeof(key), "fyz2_unit%d", i); + g_snprintf(page, sizeof(page), "fyz2unit%d", i); + catalog_add_key(key, "search_lesson", page, FALSE, 2, "fyzika"); + g_snprintf(page, sizeof(page), "fyz2ex%d", i); + catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz"); + } + for (i = 1; i <= FYZ_N; i++) { + g_snprintf(key, sizeof(key), "fyz3_unit%d", i); + g_snprintf(page, sizeof(page), "fyz3unit%d", i); + catalog_add_key(key, "search_lesson", page, FALSE, 2, "fyzika"); + g_snprintf(page, sizeof(page), "fyz3ex%d", i); + catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz"); + } + for (i = 1; i <= FYZ_N; i++) { + g_snprintf(key, sizeof(key), "fyz4_unit%d", i); + g_snprintf(page, sizeof(page), "fyz4unit%d", i); + catalog_add_key(key, "search_lesson", page, FALSE, 2, "fyzika"); + g_snprintf(page, sizeof(page), "fyz4ex%d", i); + catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz"); + } catalog_add_key("Chemie", "search_lesson", "chemmap", FALSE, 1, "chemie"); catalog_add_key("Biologie", "search_lesson", "biomap", FALSE, 1, "biologie"); for (i = 1; i <= SCI_N; i++) { diff --git a/src/stats.c b/src/stats.c index 8a5acf9..8dba401 100644 --- a/src/stats.c +++ b/src/stats.c @@ -77,6 +77,8 @@ void refresh_stats_ui(void) { } else if (s == SCI_SUBJ) progress_for_sci(&all); + else if (s == PHY_SUBJ) + progress_for_fyz(&all); else progress_for_units(sub_unit_start[s], sub_unit_count[s], &all); } @@ -107,7 +109,8 @@ void refresh_stats_ui(void) { ProgressSum sp = {0}; gboolean has_content = (s == NET_SUBJ) || (s == HW_SUBJ) || (s == ON_SUBJ) || (s == CZ_SUBJ) - || (s == SCI_SUBJ) || sub_unit_count[s] > 0; + || (s == SCI_SUBJ) || (s == PHY_SUBJ) + || sub_unit_count[s] > 0; if (!count || !bar) continue; @@ -123,6 +126,8 @@ void refresh_stats_ui(void) { } else if (s == SCI_SUBJ) progress_for_sci(&sp); + else if (s == PHY_SUBJ) + progress_for_fyz(&sp); else progress_for_units(sub_unit_start[s], sub_unit_count[s], &sp); if (has_content) { @@ -393,7 +398,8 @@ GtkWidget *build_stats_page(void) { GtkWidget *bar; gboolean has_units = (s == NET_SUBJ) || (s == HW_SUBJ) || (s == CZ_SUBJ) || (s == ON_SUBJ) - || (s == SCI_SUBJ) || sub_unit_count[s] > 0; + || (s == SCI_SUBJ) || (s == PHY_SUBJ) + || sub_unit_count[s] > 0; gboolean has_deutsch_units = sub_unit_count[s] > 0; subject = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); diff --git a/src/subjects.c b/src/subjects.c index ab50e7d..b324f19 100644 --- a/src/subjects.c +++ b/src/subjects.c @@ -5,7 +5,8 @@ /* ------------------------------------------------------------------ */ /* Index 0 (Deutsch), NET_SUBJ (networks), HW_SUBJ (hardware), CZ_SUBJ - * (Czech), ON_SUBJ (civics) and SCI_SUBJ (natural sciences) are unlocked. */ + * (Czech), ON_SUBJ (civics), PHY_SUBJ (physics) and SCI_SUBJ + * (natural sciences) are unlocked. */ const char *sub_keys[NUM_SUBJECTS] = { "Deutsch", "Správa počítačových sítí", @@ -154,7 +155,7 @@ void draw_czech_flag(GtkDrawingArea *area, cairo_t *cr, static gboolean subject_is_open(int i) { return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ - || i == ON_SUBJ || i == SCI_SUBJ; + || i == ON_SUBJ || i == PHY_SUBJ || i == SCI_SUBJ; } static GtkWidget *subject_icon(int i) { @@ -177,6 +178,9 @@ static GtkWidget *subject_icon(int i) { else if (i == ON_SUBJ) gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area), draw_civics_icon, NULL, NULL); + else if (i == PHY_SUBJ) + gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area), + draw_physics_icon, NULL, NULL); else if (i == SCI_SUBJ) gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area), draw_science_icon, NULL, NULL); @@ -196,6 +200,8 @@ static const char *subject_target(int i) { return "czechmap"; if (i == ON_SUBJ) return "onyears"; + if (i == PHY_SUBJ) + return "fyzyears"; if (i == SCI_SUBJ) return "scimap"; return NULL; diff --git a/src/theme.c b/src/theme.c index 0ee8901..7b59b7a 100644 --- a/src/theme.c +++ b/src/theme.c @@ -433,5 +433,6 @@ void apply_theme(void) { czech_rail_theme_reset(); lit_rail_theme_reset(); sci_rail_theme_reset(); + fyz_rail_theme_reset(); book_rail_theme_reset(); } diff --git a/src/util.c b/src/util.c index b248635..2f7ea24 100644 --- a/src/util.c +++ b/src/util.c @@ -32,6 +32,10 @@ char *app_progress_lit3; char *app_progress_lit4; char *app_progress_chem; char *app_progress_bio; +char *app_progress_fyz; +char *app_progress_fyz2; +char *app_progress_fyz3; +char *app_progress_fyz4; static char *dup_dirname(const char *path) { char *tmp = g_strdup(path); @@ -180,6 +184,10 @@ static void set_progress_paths(const char *write_root) { g_free(app_progress_lit4); g_free(app_progress_chem); g_free(app_progress_bio); + g_free(app_progress_fyz); + g_free(app_progress_fyz2); + g_free(app_progress_fyz3); + g_free(app_progress_fyz4); app_progress_dir = g_build_filename(write_root, PROGRESS_DIR_NAME, NULL); app_progress_u1 = g_build_filename(app_progress_dir, "unit1.conf", NULL); @@ -200,6 +208,10 @@ static void set_progress_paths(const char *write_root) { app_progress_lit4 = g_build_filename(app_progress_dir, "lit4.conf", NULL); app_progress_chem = g_build_filename(app_progress_dir, "chem.conf", NULL); app_progress_bio = g_build_filename(app_progress_dir, "bio.conf", NULL); + app_progress_fyz = g_build_filename(app_progress_dir, "fyz.conf", NULL); + app_progress_fyz2 = g_build_filename(app_progress_dir, "fyz2.conf", NULL); + app_progress_fyz3 = g_build_filename(app_progress_dir, "fyz3.conf", NULL); + app_progress_fyz4 = g_build_filename(app_progress_dir, "fyz4.conf", NULL); } /* Resolve read-only asset root, chdir there, and point progress at a writable