Add chemistry and biology lessons for natural sciences.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-10-06 09:09:07 +02:00
commit bd7ab09b1d
30 changed files with 2519 additions and 13 deletions

View file

@ -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 - **Český jazyk a literatura** — literatura (4 ročníky, 32 lekcí podle osnov
středních škol), mluvnice (20 cvičení) a maturitní četba středních škol), mluvnice (20 cvičení) a maturitní četba
(82 knih: zápisky, kvíz, sestavení děje) (82 knih: zápisky, kvíz, sestavení děje)
- **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í); - **Občanská nauka** — 1. ročník, 17 lekcí (člověk v lidském společenství);
2. ročník, 16 lekcí (člověk jako občan); 3. ročník, 16 lekcí (člověk a právo); 2. ročník, 16 lekcí (člověk jako občan); 3. ročník, 16 lekcí (člověk a právo);
4. ročník, 16 lekcí (hospodářství, svět a filozofie) 4. ročník, 16 lekcí (hospodářství, svět a filozofie)
@ -78,7 +80,7 @@ Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#ses
### Použití ### Použití
1. **Pokračuj** otevře mapu předmětů. 1. **Pokračuj** otevře mapu předmětů.
2. Otevřený předmět (vlajka, Wi‑Fi, čip, česká vlajka, váhy) vede na učební cestu. 2. Otevřený předmět (vlajka, Wi‑Fi, čip, česká vlajka, váhy, baňka) vede na učební cestu.
3. Uzly cvičení / lekcí otevřou obsah; zamčené nic nedělají. 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. 4. **Hledat** (lupa), **Statistiky** (graf) a **Nastavení** (ozubené kolo) jsou vpravo nahoře.
@ -266,6 +268,8 @@ Intel Mac, another architecture, or hacking on the code →
- **Czech language** — literature (4 years, 32 lessons from the secondary-school - **Czech language** — literature (4 years, 32 lessons from the secondary-school
syllabus), grammar (20 exercises) and required reading syllabus), grammar (20 exercises) and required reading
(82 books: notes, quiz, plot ordering) (82 books: notes, quiz, plot ordering)
- **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); - **Civics** — year 1, 17 lessons (the individual and society);
year 2, 16 lessons (the citizen in a democratic state); year 2, 16 lessons (the citizen in a democratic state);
year 3, 16 lessons (the individual and the law); year 3, 16 lessons (the individual and the law);
@ -276,7 +280,7 @@ Intel Mac, another architecture, or hacking on the code →
### Usage ### Usage
1. **Continue** opens the subject map. 1. **Continue** opens the subject map.
2. An open subject (flag, Wi‑Fi, chip, Czech flag, scales) opens its path. 2. An open subject (flag, Wi‑Fi, chip, Czech flag, scales, flask) opens its path.
3. Exercise / lesson nodes open content; locked nodes do nothing. 3. Exercise / lesson nodes open content; locked nodes do nothing.
4. **Search** (magnifier), **Statistics** (chart) and **Settings** (gear) sit in the top-right. 4. **Search** (magnifier), **Statistics** (chart) and **Settings** (gear) sit in the top-right.

View file

@ -166,6 +166,16 @@ class AppViewModel(app: Application) : AndroidViewModel(app) {
refresh() refresh()
} }
fun markChem(id: Int) {
progress.markChem(id)
refresh()
}
fun markBio(id: Int) {
progress.markBio(id)
refresh()
}
fun markMluv(n: Int) { fun markMluv(n: Int) {
progress.markMluv(n) progress.markMluv(n)
refresh() refresh()

View file

@ -35,6 +35,8 @@ import org.maturita.maturita.ui.LessonListScreen
import org.maturita.maturita.ui.LitYearsScreen import org.maturita.maturita.ui.LitYearsScreen
import org.maturita.maturita.ui.LessonRow import org.maturita.maturita.ui.LessonRow
import org.maturita.maturita.ui.LitQuizScreen import org.maturita.maturita.ui.LitQuizScreen
import org.maturita.maturita.ui.SciMapScreen
import org.maturita.maturita.ui.SciQuizScreen
import org.maturita.maturita.ui.MluvExercise import org.maturita.maturita.ui.MluvExercise
import org.maturita.maturita.ui.NetQuizScreen import org.maturita.maturita.ui.NetQuizScreen
import org.maturita.maturita.ui.NetYearsScreen import org.maturita.maturita.ui.NetYearsScreen
@ -346,6 +348,35 @@ private fun MaturitaAppBody(vm: AppViewModel) {
} }
} }
is Route.Lit4Ex -> LitQuizScreen(vm, 4, r.id) is Route.Lit4Ex -> LitQuizScreen(vm, 4, r.id)
Route.SciMap -> SciMapScreen(vm)
Route.ChemMap -> LessonListScreen(
vm, vm.tr("Chemie"), vm.tr("chem_sub"),
vm.content.chem.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.chemDone(id), Route.ChemLesson(id))
},
)
is Route.ChemLesson -> {
val l = vm.content.chemLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.ChemEx(r.id))
}
}
is Route.ChemEx -> SciQuizScreen(vm, "chem", r.id)
Route.BioMap -> LessonListScreen(
vm, vm.tr("Biologie"), vm.tr("bio_sub"),
vm.content.bio.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.bioDone(id), Route.BioLesson(id))
},
)
is Route.BioLesson -> {
val l = vm.content.bioLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.BioEx(r.id))
}
}
is Route.BioEx -> SciQuizScreen(vm, "bio", r.id)
Route.CzechMap -> CzechMapScreen(vm) Route.CzechMap -> CzechMapScreen(vm)
Route.Mluvnice -> LessonListScreen( Route.Mluvnice -> LessonListScreen(
vm, vm.tr("Mluvnice"), vm.tr("mluv_sub"), vm, vm.tr("Mluvnice"), vm.tr("mluv_sub"),

View file

@ -43,6 +43,13 @@ sealed class Route(val page: String) {
data object Lit4Map : Route("lit4map") data object Lit4Map : Route("lit4map")
data class Lit4Lesson(val id: Int) : Route("lit4unit$id") data class Lit4Lesson(val id: Int) : Route("lit4unit$id")
data class Lit4Ex(val id: Int) : Route("lit4ex$id") data class Lit4Ex(val id: Int) : Route("lit4ex$id")
data object SciMap : Route("scimap")
data object ChemMap : Route("chemmap")
data class ChemLesson(val id: Int) : Route("chemunit$id")
data class ChemEx(val id: Int) : Route("chemex$id")
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 CzechMap : Route("czechmap") data object CzechMap : Route("czechmap")
data object Mluvnice : Route("mluvnice") data object Mluvnice : Route("mluvnice")
data class MluvEx(val n: Int) : Route("mluve$n") data class MluvEx(val n: Int) : Route("mluve$n")
@ -60,7 +67,8 @@ sealed class Route(val page: String) {
this is OnEx || this is On2Ex || this is On3Ex || this is On4Ex || this is MluvEx || this is BookQuiz || this is BookPlot || this is OnEx || this is On2Ex || this is On3Ex || this is On4Ex || this is MluvEx || this is BookQuiz || this is BookPlot ||
this is NetLesson || this is HwLesson || this is OnLesson || this is On2Lesson || this is On3Lesson || this is On4Lesson || 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 LitLesson || this is LitEx || this is Lit2Lesson || this is Lit2Ex ||
this is Lit3Lesson || this is Lit3Ex || this is Lit4Lesson || this is Lit4Ex this is Lit3Lesson || this is Lit3Ex || this is Lit4Lesson || this is Lit4Ex ||
this is ChemLesson || this is ChemEx || this is BioLesson || this is BioEx
/** Full stack from the practice root to this screen, so back always goes up one level. */ /** Full stack from the practice root to this screen, so back always goes up one level. */
fun stack(): List<Route> = when (this) { fun stack(): List<Route> = when (this) {
@ -105,6 +113,13 @@ sealed class Route(val page: String) {
Lit4Map -> listOf(Subjects, CzechMap, LitYears, Lit4Map) Lit4Map -> listOf(Subjects, CzechMap, LitYears, Lit4Map)
is Lit4Lesson -> listOf(Subjects, CzechMap, LitYears, Lit4Map, this) is Lit4Lesson -> listOf(Subjects, CzechMap, LitYears, Lit4Map, this)
is Lit4Ex -> listOf(Subjects, CzechMap, LitYears, Lit4Map, Lit4Lesson(id), this) is Lit4Ex -> listOf(Subjects, CzechMap, LitYears, Lit4Map, Lit4Lesson(id), this)
SciMap -> listOf(Subjects, SciMap)
ChemMap -> listOf(Subjects, SciMap, ChemMap)
is ChemLesson -> listOf(Subjects, SciMap, ChemMap, this)
is ChemEx -> listOf(Subjects, SciMap, ChemMap, ChemLesson(id), this)
BioMap -> listOf(Subjects, SciMap, BioMap)
is BioLesson -> listOf(Subjects, SciMap, BioMap, this)
is BioEx -> listOf(Subjects, SciMap, BioMap, BioLesson(id), this)
CzechMap -> listOf(Subjects, CzechMap) CzechMap -> listOf(Subjects, CzechMap)
Mluvnice -> listOf(Subjects, CzechMap, Mluvnice) Mluvnice -> listOf(Subjects, CzechMap, Mluvnice)
is MluvEx -> listOf(Subjects, CzechMap, Mluvnice, this) is MluvEx -> listOf(Subjects, CzechMap, Mluvnice, this)
@ -135,6 +150,9 @@ fun routeFromPage(page: String): Route? = when (page) {
"lit2map" -> Route.Lit2Map "lit2map" -> Route.Lit2Map
"lit3map" -> Route.Lit3Map "lit3map" -> Route.Lit3Map
"lit4map" -> Route.Lit4Map "lit4map" -> Route.Lit4Map
"scimap" -> Route.SciMap
"chemmap" -> Route.ChemMap
"biomap" -> Route.BioMap
"czechmap" -> Route.CzechMap "czechmap" -> Route.CzechMap
"mluvnice" -> Route.Mluvnice "mluvnice" -> Route.Mluvnice
"readinglist" -> Route.ReadingList "readinglist" -> Route.ReadingList
@ -166,6 +184,18 @@ fun routeFromPage(page: String): Route? = when (page) {
Regex("""hwex(\d+)""").matchEntire(page)?.let { Regex("""hwex(\d+)""").matchEntire(page)?.let {
return Route.HwEx(it.groupValues[1].toInt()) return Route.HwEx(it.groupValues[1].toInt())
} }
Regex("""chemunit(\d+)""").matchEntire(page)?.let {
return Route.ChemLesson(it.groupValues[1].toInt())
}
Regex("""chemex(\d+)""").matchEntire(page)?.let {
return Route.ChemEx(it.groupValues[1].toInt())
}
Regex("""biounit(\d+)""").matchEntire(page)?.let {
return Route.BioLesson(it.groupValues[1].toInt())
}
Regex("""bioex(\d+)""").matchEntire(page)?.let {
return Route.BioEx(it.groupValues[1].toInt())
}
Regex("""lit4unit(\d+)""").matchEntire(page)?.let { Regex("""lit4unit(\d+)""").matchEntire(page)?.let {
return Route.Lit4Lesson(it.groupValues[1].toInt()) return Route.Lit4Lesson(it.groupValues[1].toInt())
} }

View file

@ -37,6 +37,8 @@ class Content(root: JSONObject) {
val lit2 = raw.arr("lit2") val lit2 = raw.arr("lit2")
val lit3 = raw.arr("lit3") val lit3 = raw.arr("lit3")
val lit4 = raw.arr("lit4") val lit4 = raw.arr("lit4")
val chem = raw.arr("chem")
val bio = raw.arr("bio")
val mluvnice = raw.arr("mluvnice") val mluvnice = raw.arr("mluvnice")
val books = raw.arr("books") val books = raw.arr("books")
val changelog = raw.arr("changelog") val changelog = raw.arr("changelog")
@ -52,6 +54,8 @@ class Content(root: JSONObject) {
fun lit2Lesson(id: Int) = lit2.firstOrNull { it.int("id") == id } fun lit2Lesson(id: Int) = lit2.firstOrNull { it.int("id") == id }
fun lit3Lesson(id: Int) = lit3.firstOrNull { it.int("id") == id } fun lit3Lesson(id: Int) = lit3.firstOrNull { it.int("id") == id }
fun lit4Lesson(id: Int) = lit4.firstOrNull { it.int("id") == id } 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 mluv(n: Int) = mluvnice.firstOrNull { it.int("id") == n } fun mluv(n: Int) = mluvnice.firstOrNull { it.int("id") == n }
fun book(id: String) = books.firstOrNull { it.str("id") == id } fun book(id: String) = books.firstOrNull { it.str("id") == id }

View file

@ -53,6 +53,11 @@ class ProgressStore(context: Context) {
fun lit4Done(id: Int) = prefs.getBoolean("lit4.$id", false) fun lit4Done(id: Int) = prefs.getBoolean("lit4.$id", false)
fun markLit4(id: Int) { prefs.edit().putBoolean("lit4.$id", true).apply() } fun markLit4(id: Int) { prefs.edit().putBoolean("lit4.$id", true).apply() }
fun chemDone(id: Int) = prefs.getBoolean("chem.$id", false)
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 mluvDone(n: Int) = prefs.getBoolean("mluv.$n", false) fun mluvDone(n: Int) = prefs.getBoolean("mluv.$n", false)
fun markMluv(n: Int) { prefs.edit().putBoolean("mluv.$n", true).apply() } fun markMluv(n: Int) { prefs.edit().putBoolean("mluv.$n", true).apply() }
@ -155,6 +160,22 @@ fun summarize(content: Content, p: ProgressStore): ProgressSum {
sum.doneUnits += 1 sum.doneUnits += 1
} }
} }
content.chem.forEach { l ->
sum.totalEx += 1
sum.openUnits += 1
if (p.chemDone(l.int("id"))) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
content.bio.forEach { l ->
sum.totalEx += 1
sum.openUnits += 1
if (p.bioDone(l.int("id"))) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
sum.totalEx += content.mluvnice.size sum.totalEx += content.mluvnice.size
sum.openUnits += 1 sum.openUnits += 1
val md = content.mluvnice.count { p.mluvDone(it.int("id")) } val md = content.mluvnice.count { p.mluvDone(it.int("id")) }

View file

@ -849,6 +849,20 @@ fun NetQuizScreen(vm: AppViewModel, id: Int) {
DispatchExercise(vm, spec, vm.tr(lesson.str("exTitleKey"))) { vm.markNet(id) } DispatchExercise(vm, spec, vm.tr(lesson.str("exTitleKey"))) { vm.markNet(id) }
} }
@Composable
fun SciQuizScreen(vm: AppViewModel, area: String, id: Int) {
val lesson = (if (area == "bio") vm.content.bioLesson(id) else vm.content.chemLesson(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"))) {
if (area == "bio") vm.markBio(id) else vm.markChem(id)
}
}
@Composable @Composable
fun LitQuizScreen(vm: AppViewModel, year: Int, id: Int) { fun LitQuizScreen(vm: AppViewModel, year: Int, id: Int) {
val lesson = when (year) { val lesson = when (year) {

View file

@ -32,6 +32,7 @@ import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
import androidx.compose.material.icons.filled.Groups import androidx.compose.material.icons.filled.Groups
import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.Quiz import androidx.compose.material.icons.filled.Quiz
import androidx.compose.material.icons.filled.Science
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.outlined.Lightbulb import androidx.compose.material.icons.outlined.Lightbulb
import androidx.compose.material3.Card import androidx.compose.material3.Card
@ -220,6 +221,7 @@ private fun SubjectIcon(icon: String, open: Boolean) {
"chip" -> ChipIcon(tint, 26.dp) "chip" -> ChipIcon(tint, 26.dp)
"people" -> Icon(Icons.Filled.Groups, null, tint = tint, modifier = Modifier.size(26.dp)) "people" -> Icon(Icons.Filled.Groups, null, tint = tint, modifier = Modifier.size(26.dp))
"cz" -> CzechFlag(28.dp) "cz" -> CzechFlag(28.dp)
"flask" -> Icon(Icons.Filled.Science, null, tint = tint, modifier = Modifier.size(26.dp))
else -> Icon(Icons.Filled.Lock, null, tint = tint) else -> Icon(Icons.Filled.Lock, null, tint = tint)
} }
} }
@ -307,6 +309,11 @@ private fun subjectSum(vm: AppViewModel, s: J): ProgressSum {
vm.content.on3.count { vm.progress.on3Done(it.int("id")) } + vm.content.on3.count { vm.progress.on3Done(it.int("id")) } +
vm.content.on4.count { vm.progress.on4Done(it.int("id")) } vm.content.on4.count { vm.progress.on4Done(it.int("id")) }
} }
"scimap" -> {
sum.totalEx = vm.content.chem.size + vm.content.bio.size
sum.doneEx = vm.content.chem.count { vm.progress.chemDone(it.int("id")) } +
vm.content.bio.count { vm.progress.bioDone(it.int("id")) }
}
"czechmap" -> { "czechmap" -> {
val litDone = vm.content.lit.count { vm.progress.litDone(it.int("id")) } + val litDone = vm.content.lit.count { vm.progress.litDone(it.int("id")) } +
vm.content.lit2.count { vm.progress.lit2Done(it.int("id")) } + vm.content.lit2.count { vm.progress.lit2Done(it.int("id")) } +
@ -540,6 +547,16 @@ private fun buildSearch(vm: AppViewModel): List<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_lesson"), "lit4unit${l.int("id")}", extra = "literatura")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "lit4ex${l.int("id")}") add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "lit4ex${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 ->
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "chemunit${l.int("id")}", extra = "chemie")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "chemex${l.int("id")}")
}
vm.content.bio.forEach { l ->
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "biounit${l.int("id")}", extra = "biologie")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "bioex${l.int("id")}")
}
add(vm.tr("Mluvnice"), vm.tr("search_lesson"), "mluvnice") add(vm.tr("Mluvnice"), vm.tr("search_lesson"), "mluvnice")
vm.content.mluvnice.forEach { m -> add(m.str("name"), vm.tr("Mluvnice"), "mluve${m.int("id")}") } vm.content.mluvnice.forEach { m -> add(m.str("name"), vm.tr("Mluvnice"), "mluve${m.int("id")}") }
add(vm.tr("Maturitní četba"), vm.tr("search_book"), "readinglist") add(vm.tr("Maturitní četba"), vm.tr("search_book"), "readinglist")
@ -666,6 +683,24 @@ fun LitYearsScreen(vm: AppViewModel) {
} }
} }
@Composable
fun SciMapScreen(vm: AppViewModel) {
DetailScaffold(vm, vm.tr("Základy Přírodopisných věd"), vm.tr("sci_sub")) { inner ->
LazyColumn(contentPadding = PaddingValues(top = inner.calculateTopPadding(), bottom = inner.calculateBottomPadding() + 16.dp)) {
item {
NavRow(
title = vm.tr("Chemie"),
subtitle = vm.tr("chem_sub"),
leading = { Icon(Icons.Filled.Science, null, tint = MaterialTheme.colorScheme.primary) },
) { vm.go(Route.ChemMap) }
}
item {
NavRow(title = vm.tr("Biologie"), subtitle = vm.tr("bio_sub")) { vm.go(Route.BioMap) }
}
}
}
}
@Composable @Composable
fun CzechMapScreen(vm: AppViewModel) { fun CzechMapScreen(vm: AppViewModel) {
DetailScaffold(vm, vm.tr("Český jazyk a literatura"), vm.tr("czech_sub")) { inner -> DetailScaffold(vm, vm.tr("Český jazyk a literatura"), vm.tr("czech_sub")) { inner ->

View file

@ -7,6 +7,10 @@
# en: bullet # en: bullet
--- 2026-10-06 --- 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
en: Natural sciences: chemistry and biology, eight lessons each, from the syllabus
en: Every lesson has an overview and a four-question quiz
cs: Český jazyk: literatura ve čtyřech ročnících podle osnov středních škol cs: Český jazyk: literatura ve čtyřech ročnících podle osnov středních škol
cs: 32 lekcí od starověku po současnost, každá s výkladem a kvízem cs: 32 lekcí od starověku po současnost, každá s výkladem a kvízem
en: Czech: literature across four years, following the secondary-school syllabus en: Czech: literature across four years, following the secondary-school syllabus

View file

@ -168,6 +168,16 @@ final class AppModel: ObservableObject {
refresh() refresh()
} }
func markChem(_ id: Int) {
progress.markChem(id)
refresh()
}
func markBio(_ id: Int) {
progress.markBio(id)
refresh()
}
func markMluv(_ n: Int) { func markMluv(_ n: Int) {
progress.markMluv(n) progress.markMluv(n)
refresh() refresh()

View file

@ -39,6 +39,8 @@ final class Content {
let lit2: [J] let lit2: [J]
let lit3: [J] let lit3: [J]
let lit4: [J] let lit4: [J]
let chem: [J]
let bio: [J]
let mluvnice: [J] let mluvnice: [J]
let books: [J] let books: [J]
let changelog: [J] let changelog: [J]
@ -78,6 +80,8 @@ final class Content {
lit2 = raw.arr("lit2") lit2 = raw.arr("lit2")
lit3 = raw.arr("lit3") lit3 = raw.arr("lit3")
lit4 = raw.arr("lit4") lit4 = raw.arr("lit4")
chem = raw.arr("chem")
bio = raw.arr("bio")
mluvnice = raw.arr("mluvnice") mluvnice = raw.arr("mluvnice")
books = raw.arr("books") books = raw.arr("books")
changelog = raw.arr("changelog") changelog = raw.arr("changelog")
@ -94,6 +98,8 @@ final class Content {
func lit2Lesson(_ id: Int) -> J? { lit2.first { $0.int("id") == id } } func lit2Lesson(_ id: Int) -> J? { lit2.first { $0.int("id") == id } }
func lit3Lesson(_ id: Int) -> J? { lit3.first { $0.int("id") == id } } func lit3Lesson(_ id: Int) -> J? { lit3.first { $0.int("id") == id } }
func lit4Lesson(_ id: Int) -> J? { lit4.first { $0.int("id") == id } } 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 mluv(_ n: Int) -> J? { mluvnice.first { $0.int("id") == n } } func mluv(_ n: Int) -> J? { mluvnice.first { $0.int("id") == n } }
func book(_ id: String) -> J? { books.first { $0.str("id") == id } } func book(_ id: String) -> J? { books.first { $0.str("id") == id } }

View file

@ -55,6 +55,11 @@ final class ProgressStore {
func lit4Done(_ id: Int) -> Bool { defaults.bool(forKey: "lit4.\(id)") } func lit4Done(_ id: Int) -> Bool { defaults.bool(forKey: "lit4.\(id)") }
func markLit4(_ id: Int) { defaults.set(true, forKey: "lit4.\(id)") } func markLit4(_ id: Int) { defaults.set(true, forKey: "lit4.\(id)") }
func chemDone(_ id: Int) -> Bool { defaults.bool(forKey: "chem.\(id)") }
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 mluvDone(_ n: Int) -> Bool { defaults.bool(forKey: "mluv.\(n)") } func mluvDone(_ n: Int) -> Bool { defaults.bool(forKey: "mluv.\(n)") }
func markMluv(_ n: Int) { defaults.set(true, forKey: "mluv.\(n)") } func markMluv(_ n: Int) { defaults.set(true, forKey: "mluv.\(n)") }
@ -162,6 +167,22 @@ func summarize(_ content: Content, _ p: ProgressStore) -> ProgressSum {
sum.doneUnits += 1 sum.doneUnits += 1
} }
} }
for l in content.chem {
sum.totalEx += 1
sum.openUnits += 1
if p.chemDone(l.int("id")) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
for l in content.bio {
sum.totalEx += 1
sum.openUnits += 1
if p.bioDone(l.int("id")) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
sum.totalEx += content.mluvnice.count sum.totalEx += content.mluvnice.count
sum.openUnits += 1 sum.openUnits += 1
let md = content.mluvnice.filter { p.mluvDone($0.int("id")) }.count let md = content.mluvnice.filter { p.mluvDone($0.int("id")) }.count

View file

@ -49,6 +49,13 @@ enum Route: Hashable {
case lit4Map case lit4Map
case lit4Lesson(Int) case lit4Lesson(Int)
case lit4Ex(Int) case lit4Ex(Int)
case sciMap
case chemMap
case chemLesson(Int)
case chemEx(Int)
case bioMap
case bioLesson(Int)
case bioEx(Int)
case czechMap case czechMap
case mluvnice case mluvnice
case mluvEx(Int) case mluvEx(Int)
@ -100,6 +107,13 @@ enum Route: Hashable {
case .lit4Map: return "lit4map" case .lit4Map: return "lit4map"
case .lit4Lesson(let id): return "lit4unit\(id)" case .lit4Lesson(let id): return "lit4unit\(id)"
case .lit4Ex(let id): return "lit4ex\(id)" case .lit4Ex(let id): return "lit4ex\(id)"
case .sciMap: return "scimap"
case .chemMap: return "chemmap"
case .chemLesson(let id): return "chemunit\(id)"
case .chemEx(let id): return "chemex\(id)"
case .bioMap: return "biomap"
case .bioLesson(let id): return "biounit\(id)"
case .bioEx(let id): return "bioex\(id)"
case .czechMap: return "czechmap" case .czechMap: return "czechmap"
case .mluvnice: return "mluvnice" case .mluvnice: return "mluvnice"
case .mluvEx(let n): return "mluve\(n)" case .mluvEx(let n): return "mluve\(n)"
@ -200,6 +214,20 @@ enum Route: Hashable {
return [.czechMap, .litYears, .lit4Map, .lit4Lesson(id)] return [.czechMap, .litYears, .lit4Map, .lit4Lesson(id)]
case .lit4Ex(let id): case .lit4Ex(let id):
return [.czechMap, .litYears, .lit4Map, .lit4Lesson(id), .lit4Ex(id)] return [.czechMap, .litYears, .lit4Map, .lit4Lesson(id), .lit4Ex(id)]
case .sciMap:
return [.sciMap]
case .chemMap:
return [.sciMap, .chemMap]
case .chemLesson(let id):
return [.sciMap, .chemMap, .chemLesson(id)]
case .chemEx(let id):
return [.sciMap, .chemMap, .chemLesson(id), .chemEx(id)]
case .bioMap:
return [.sciMap, .bioMap]
case .bioLesson(let id):
return [.sciMap, .bioMap, .bioLesson(id)]
case .bioEx(let id):
return [.sciMap, .bioMap, .bioLesson(id), .bioEx(id)]
case .czechMap: case .czechMap:
return [.czechMap] return [.czechMap]
case .mluvnice: case .mluvnice:
@ -238,6 +266,9 @@ func routeFromPage(_ page: String) -> Route? {
case "lit2map": return .lit2Map case "lit2map": return .lit2Map
case "lit3map": return .lit3Map case "lit3map": return .lit3Map
case "lit4map": return .lit4Map case "lit4map": return .lit4Map
case "scimap": return .sciMap
case "chemmap": return .chemMap
case "biomap": return .bioMap
case "czechmap": return .czechMap case "czechmap": return .czechMap
case "mluvnice": return .mluvnice case "mluvnice": return .mluvnice
case "readinglist": return .readingList case "readinglist": return .readingList
@ -281,6 +312,18 @@ func routeFromPage(_ page: String) -> Route? {
if let m = page.wholeMatch(of: /hwex(\d+)/), let n = Int(m.1) { if let m = page.wholeMatch(of: /hwex(\d+)/), let n = Int(m.1) {
return .hwEx(n) return .hwEx(n)
} }
if let m = page.wholeMatch(of: /chemunit(\d+)/), let n = Int(m.1) {
return .chemLesson(n)
}
if let m = page.wholeMatch(of: /chemex(\d+)/), let n = Int(m.1) {
return .chemEx(n)
}
if let m = page.wholeMatch(of: /biounit(\d+)/), let n = Int(m.1) {
return .bioLesson(n)
}
if let m = page.wholeMatch(of: /bioex(\d+)/), let n = Int(m.1) {
return .bioEx(n)
}
if let m = page.wholeMatch(of: /lit4unit(\d+)/), let n = Int(m.1) { if let m = page.wholeMatch(of: /lit4unit(\d+)/), let n = Int(m.1) {
return .lit4Lesson(n) return .lit4Lesson(n)
} }

View file

@ -607,6 +607,7 @@ func subjectSymbol(_ icon: String) -> String {
case "chip": return "cpu" case "chip": return "cpu"
case "cz": return "text.book.closed.fill" case "cz": return "text.book.closed.fill"
case "people": return "person.2.fill" case "people": return "person.2.fill"
case "flask": return "flask.fill"
default: return "lock.fill" default: return "lock.fill"
} }
} }

View file

@ -625,6 +625,27 @@ struct NetQuizScreen: View {
} }
} }
struct SciQuizScreen: View {
@ObservedObject var vm: AppModel
let area: String
let id: Int
var body: some View {
let lesson = area == "bio" ? vm.content.bioLesson(id) : vm.content.chemLesson(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"))) {
if area == "bio" { vm.markBio(id) } else { vm.markChem(id) }
}
}
}
}
struct LitYearQuizScreen: View { struct LitYearQuizScreen: View {
@ObservedObject var vm: AppModel @ObservedObject var vm: AppModel
let year: Int let year: Int

View file

@ -695,6 +695,10 @@ private func subjectSum(_ vm: AppModel, _ s: J) -> ProgressSum {
+ vm.content.on2.filter { vm.progress.on2Done($0.int("id")) }.count + vm.content.on2.filter { vm.progress.on2Done($0.int("id")) }.count
+ vm.content.on3.filter { vm.progress.on3Done($0.int("id")) }.count + vm.content.on3.filter { vm.progress.on3Done($0.int("id")) }.count
+ vm.content.on4.filter { vm.progress.on4Done($0.int("id")) }.count + vm.content.on4.filter { vm.progress.on4Done($0.int("id")) }.count
case "scimap":
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 "czechmap": case "czechmap":
let litCount = vm.content.lit.count + vm.content.lit2.count + vm.content.lit3.count + vm.content.lit4.count 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 let litDone = vm.content.lit.filter { vm.progress.litDone($0.int("id")) }.count
@ -925,6 +929,16 @@ 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_lesson"), "lit4unit\(l.int("id"))", extra: "literatura")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "lit4ex\(l.int("id"))") add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "lit4ex\(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 {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "chemunit\(l.int("id"))", extra: "chemie")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "chemex\(l.int("id"))")
}
for l in vm.content.bio {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "biounit\(l.int("id"))", extra: "biologie")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "bioex\(l.int("id"))")
}
add(vm.tr("Mluvnice"), vm.tr("search_lesson"), "mluvnice") add(vm.tr("Mluvnice"), vm.tr("search_lesson"), "mluvnice")
add(vm.tr("Maturitní četba"), vm.tr("search_book"), "readinglist") add(vm.tr("Maturitní četba"), vm.tr("search_book"), "readinglist")
for b in vm.content.books { for b in vm.content.books {
@ -1471,6 +1485,50 @@ struct LitYearsScreen: View {
} }
} }
struct SciMapScreen: View {
@ObservedObject var vm: AppModel
var body: some View {
#if os(macOS)
MacFillGrid(count: 2, minWidth: 260, minHeight: 180) { i in
if i == 0 {
NavigationLink(value: Route.chemMap) {
MacLinkCard(title: vm.tr("Chemie"), subtitle: vm.tr("chem_sub"), systemImage: "flask.fill")
}
.buttonStyle(.plain)
} else {
NavigationLink(value: Route.bioMap) {
MacLinkCard(title: vm.tr("Biologie"), subtitle: vm.tr("bio_sub"), systemImage: "leaf.fill")
}
.buttonStyle(.plain)
}
}
.navigationTitle(vm.tr("Základy Přírodopisných věd"))
.navigationSubtitle(vm.tr("sci_sub"))
#else
List {
NavigationLink(value: Route.chemMap) {
Label {
ListRowLabel(title: vm.tr("Chemie"), subtitle: vm.tr("chem_sub"))
} icon: {
Image(systemName: "flask.fill")
}
}
NavigationLink(value: Route.bioMap) {
Label {
ListRowLabel(title: vm.tr("Biologie"), subtitle: vm.tr("bio_sub"))
} icon: {
Image(systemName: "leaf.fill")
}
}
}
.navigationTitle(vm.tr("Základy Přírodopisných věd"))
.navigationSubtitle(vm.tr("sci_sub"))
.appListStyle()
#endif
}
}
struct CzechMapScreen: View { struct CzechMapScreen: View {
@ObservedObject var vm: AppModel @ObservedObject var vm: AppModel
@ -2151,6 +2209,50 @@ struct RouteDestination: View {
} }
case .lit4Ex(let id): case .lit4Ex(let id):
LitYearQuizScreen(vm: vm, year: 4, id: id) LitYearQuizScreen(vm: vm, year: 4, id: id)
case .sciMap:
SciMapScreen(vm: vm)
case .chemMap:
LessonListScreen(
vm: vm,
title: vm.tr("Chemie"),
subtitle: vm.tr("chem_sub"),
rows: vm.content.chem.map { l in
let id = l.int("id")
return LessonRow(
id: "c\(id)",
title: vm.tr(l.str("titleKey")),
done: vm.progress.chemDone(id),
destination: .chemLesson(id)
)
}
)
case .chemLesson(let id):
if let l = vm.content.chemLesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .chemEx(id))
}
case .chemEx(let id):
SciQuizScreen(vm: vm, area: "chem", id: id)
case .bioMap:
LessonListScreen(
vm: vm,
title: vm.tr("Biologie"),
subtitle: vm.tr("bio_sub"),
rows: vm.content.bio.map { l in
let id = l.int("id")
return LessonRow(
id: "b\(id)",
title: vm.tr(l.str("titleKey")),
done: vm.progress.bioDone(id),
destination: .bioLesson(id)
)
}
)
case .bioLesson(let id):
if let l = vm.content.bioLesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .bioEx(id))
}
case .bioEx(let id):
SciQuizScreen(vm: vm, area: "bio", id: id)
case .mluvnice: case .mluvnice:
LessonListScreen( LessonListScreen(
vm: vm, vm: vm,

View file

@ -1109,6 +1109,8 @@ def main() -> int:
lit2_lessons = lit_pack("lit2") lit2_lessons = lit_pack("lit2")
lit3_lessons = lit_pack("lit3") lit3_lessons = lit_pack("lit3")
lit4_lessons = lit_pack("lit4") lit4_lessons = lit_pack("lit4")
chem_lessons = lit_pack("chem")
bio_lessons = lit_pack("bio")
on_lessons = [] on_lessons = []
for i in range(1, 18): for i in range(1, 18):
@ -1559,7 +1561,7 @@ def main() -> int:
{"key": "English", "open": False, "target": None, "icon": "lock"}, {"key": "English", "open": False, "target": None, "icon": "lock"},
{"key": "Matematika", "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": False, "target": None, "icon": "lock"},
{"key": "Základy Přírodopisných věd", "open": False, "target": None, "icon": "lock"}, {"key": "Základy Přírodopisných věd", "open": True, "target": "scimap", "icon": "flask"},
{"key": "Technická grafika", "open": False, "target": None, "icon": "lock"}, {"key": "Technická grafika", "open": False, "target": None, "icon": "lock"},
{"key": "Prezentační grafika", "open": False, "target": None, "icon": "lock"}, {"key": "Prezentační grafika", "open": False, "target": None, "icon": "lock"},
{"key": "Programování", "open": False, "target": None, "icon": "lock"}, {"key": "Programování", "open": False, "target": None, "icon": "lock"},
@ -1581,6 +1583,8 @@ def main() -> int:
"lit2": lit2_lessons, "lit2": lit2_lessons,
"lit3": lit3_lessons, "lit3": lit3_lessons,
"lit4": lit4_lessons, "lit4": lit4_lessons,
"chem": chem_lessons,
"bio": bio_lessons,
"on2": on2_lessons, "on2": on2_lessons,
"on3": on3_lessons, "on3": on3_lessons,
"on4": on4_lessons, "on4": on4_lessons,
@ -1620,6 +1624,8 @@ def main() -> int:
print(f" hw lessons: {sum(1 for L in hw_lessons if L['slides'])}") print(f" hw lessons: {sum(1 for L in hw_lessons if L['slides'])}")
print(f" on lessons: {sum(1 for L in on_lessons if L['slides'])}") print(f" on lessons: {sum(1 for L in on_lessons if L['slides'])}")
print(f" lit lessons: {sum(1 for L in lit_lessons + lit2_lessons + lit3_lessons + lit4_lessons if L['slides'])}") 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" on2 lessons: {sum(1 for L in on2_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" 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'])}") print(f" on4 lessons: {sum(1 for L in on4_lessons if L['slides'])}")
@ -1679,6 +1685,7 @@ def main() -> int:
for label, block in ( for label, block in (
("lit", lit_lessons), ("lit2", lit2_lessons), ("lit", lit_lessons), ("lit2", lit2_lessons),
("lit3", lit3_lessons), ("lit4", lit4_lessons), ("lit3", lit3_lessons), ("lit4", lit4_lessons),
("chem", chem_lessons), ("bio", bio_lessons),
): ):
for L in block: for L in block:
if not L["slides"]: if not L["slides"]:

617
src/biologie.c Normal file
View file

@ -0,0 +1,617 @@
#include "graduately.h"
/* Biology for Základy přírodopisných věd.
* Follows the secondary-school outline: general biology, a survey of
* organisms, human biology, genetics and ecology. */
NetLesson bio_lessons[SCI_N] = {
{ .n_slides = 3, .unit_page = "biounit1", .ex_page = "bioex1" },
{ .n_slides = 3, .unit_page = "biounit2", .ex_page = "bioex2" },
{ .n_slides = 3, .unit_page = "biounit3", .ex_page = "bioex3" },
{ .n_slides = 3, .unit_page = "biounit4", .ex_page = "bioex4" },
{ .n_slides = 3, .unit_page = "biounit5", .ex_page = "bioex5" },
{ .n_slides = 3, .unit_page = "biounit6", .ex_page = "bioex6" },
{ .n_slides = 3, .unit_page = "biounit7", .ex_page = "bioex7" },
{ .n_slides = 3, .unit_page = "biounit8", .ex_page = "bioex8" },
};
static GtkWidget *build_bio_unit1_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Život", "Čím se živé liší",
"Tip: metabolismus je přeměna látek a energie, ne jen dýchání.",
{
"Živé soustavy mají metabolismus, dráždivost, růst a vývin.",
"Rozmnožují se a předávají dědičnou informaci.",
"Udržují stálé vnitřní prostředí, homeostázu.",
"Bez příjmu energie a látek z okolí život nepokračuje.",
NULL,
},
},
{
"2 / 3 • Řada", "Od buňky k ekosystému",
"Tip: populace je jeden druh, společenstvo je víc druhů pohromadě.",
{
"Buňka je základní stavební a funkční jednotka živého.",
"Tkáně tvoří orgány, orgány soustavy a soustavy organismus.",
"Populace jsou jedinci téhož druhu na jednom místě.",
"Společenstvo s neživým prostředím tvoří ekosystém.",
NULL,
},
},
{
"3 / 3 • Výživa", "Autotrof a heterotrof",
"Tip: zelená rostlina je autotrof, živočich heterotrof.",
{
"Autotrof tvoří organické látky z anorganických, hlavně fotosyntézou.",
"Heterotrof přijímá hotové organické látky.",
"Život na Zemi je vázaný na vodu a na zdroj energie.",
"Buňka vznikla dřív než mnohobuněčné organismy.",
NULL,
},
},
};
return sci_unit_page(1, &bio_lessons[0], "bio_unit1", "bio_unit1_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_bio_unit1_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co je základní jednotka živých organismů?",
{"Atom", "Buňka", "Ekosystém", "Orgán"},
4, 1},
{"Co je metabolismus?",
{"Jen pohyb živočicha",
"Přeměna látek a energie v organismu",
"Počet chromozomů",
"Rozmnožování viru mimo buňku"},
4, 1},
{"Co je populace?",
{"Jedinci jednoho druhu na určitém místě",
"Všechny druhy v ekosystému",
"Jedna buňka",
"Neživé složky prostředí"},
4, 0},
{"Čím se autotrof liší od heterotrofa?",
{"Nemá buňky",
"Sám tvoří organické látky z anorganických",
"Nepotřebuje energii",
"Žije jen bez vody"},
4, 1},
};
static const char *hints[] = {
"Buněčná teorie: nová buňka vzniká z buňky.",
"Metabolismus zahrnuje výstavbu i rozklad látek.",
"Společenstvo už sdružuje populace různých druhů.",
"Rostliny fotosyntetizují. Živočichové a houby jsou heterotrofní.",
};
return sci_mcq_page(1, 0, "biounit1", "bio_ex1_title", "bio_quiz1_head",
qs, hints, 4);
}
static GtkWidget *build_bio_unit2_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Dva typy", "Prokaryota a eukaryota",
"Tip: bakterie nemají jádro, živočišná a rostlinná buňka ano.",
{
"Prokaryotní buňka nemá pravé jádro. Patří sem bakterie.",
"Eukaryotní buňka má jádro oddělené membránou a má organely.",
"Plazmatická membrána ohraničuje každou buňku.",
"Ribozomy vyrábějí bílkoviny v obou typech buněk.",
NULL,
},
},
{
"2 / 3 • Organely", "Co je v eukaryotní buňce",
"Tip: chloroplast mají rostliny, mitochondrii mají i živočichové.",
{
"Jádro obsahuje DNA a řídí buňku.",
"Mitochondrie uvolňují energii buněčným dýcháním.",
"Chloroplasty provádějí fotosyntézu a jsou v rostlinách.",
"Rostlinná buňka má navíc buněčnou stěnu a vakuolu.",
NULL,
},
},
{
"3 / 3 • Dělení", "Mitóza a meióza",
"Tip: člověk má 46 chromozomů, pohlavní buňka 23.",
{
"Mitóza dává dvě tělní buňky se stejnou genetickou informací.",
"Slouží k růstu, obnově a nepohlavnímu rozmnožování.",
"Meióza tvoří pohlavní buňky s polovičním počtem chromozomů.",
"Oplozením se počet chromozomů vrací na původní.",
NULL,
},
},
};
return sci_unit_page(1, &bio_lessons[1], "bio_unit2", "bio_unit2_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_bio_unit2_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Která buňka je prokaryotní?",
{"Bakterie", "Rostlinná buňka", "Živočišná buňka", "Buňka houby s jádrem"},
4, 0},
{"Kde probíhá fotosyntéza v rostlinné buňce?",
{"V mitochondrii", "V chloroplastu", "V ribozomu", "Jen v jádře"},
4, 1},
{"K čemu slouží mitóza?",
{"Ke vzniku pohlavních buněk s poloviční sadou",
"Ke vzniku dvou geneticky stejných tělních buněk",
"K tvorbě viru",
"K odstranění jádra"},
4, 1},
{"Kolik chromozomů má lidská pohlavní buňka?",
{"46", "23", "92", "2"},
4, 1},
};
static const char *hints[] = {
"Prokaryota nemají pravé jádro. Eukaryota ano.",
"Chloroplast je zelený a je v rostlinách a některých protistech.",
"Meióza, ne mitóza, snižuje počet chromozomů na polovinu.",
"Tělní buňka má 46, spermie a vajíčko po 23.",
};
return sci_mcq_page(1, 1, "biounit2", "bio_ex2_title", "bio_quiz2_head",
qs, hints, 4);
}
static GtkWidget *build_bio_unit3_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Virus", "Na hranici živého",
"Tip: virus není buňka a sám se mimo hostitele nemnoží.",
{
"Virus má nukleovou kyselinu v bílkovinném obalu.",
"Nemá vlastní metabolismus a množí se jen v hostitelské buňce.",
"Způsobuje třeba chřipku. Antibiotika na virus nepůsobí.",
"Očkování učí imunitu poznat virus dřív, než nemoc propukne.",
NULL,
},
},
{
"2 / 3 • Bakterie a houby", "Rozkladači a původci",
"Tip: houba není rostlina, živí se hotovými organickými látkami.",
{
"Bakterie jsou prokaryota. Některé jsou symbionti, jiné patogeny.",
"V průmyslu kvasí jogurt, v přírodě rozkládají organické zbytky.",
"Houby mají buněčnou stěnu z chitinu a nemají chlorofyl.",
"Kvasinky, plísně i kloboukaté houby patří mezi houby.",
NULL,
},
},
{
"3 / 3 • Rostliny", "Kořen, stonek, list",
"Tip: fotosyntéza běží hlavně v listech, v chloroplastech.",
{
"Rostliny jsou autotrofní a mají buněčnou stěnu z celulózy.",
"Kořen přijímá vodu a minerály a upevňuje rostlinu.",
"Stonek nese listy a vede látky, list je hlavní místo fotosyntézy.",
"Květ slouží rozmnožování, semeno obsahuje zárodek.",
NULL,
},
},
};
return sci_unit_page(1, &bio_lessons[2], "bio_unit3", "bio_unit3_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_bio_unit3_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Proč se virus množí jen v hostiteli?",
{"Má vlastní chloroplast, ale bez světla",
"Nemá buněčný metabolismus",
"Je to prokaryotní buňka",
"Má jádro a mitochondrie"},
4, 1},
{"Čím se houby liší od rostlin?",
{"Nemají buňky",
"Jsou heterotrofní a nemají chlorofyl",
"Mají chloroplasty navíc",
"Jsou vždy jednobuněčné prokaryota"},
4, 1},
{"Kde v rostlině probíhá většina fotosyntézy?",
{"V kořeni", "V listu", "Jen v květu", "Ve dřevu bez chloroplastů"},
4, 1},
{"Z čeho je buněčná stěna rostlin?",
{"Z chitinu", "Z celulózy", "Z cholesterolu", "Z peptidoglykanu jako u všech hub"},
4, 1},
};
static const char *hints[] = {
"Virus je nukleová kyselina v obalu. Buňku si půjčuje.",
"Chitin je stěna hub. Rostliny mají celulózu a chlorofyl.",
"List má nejvíc chloroplastů. Kořen je většinou pod zemí.",
"Rostliny: celulóza. Houby: chitin. Bakterie: peptidoglykan.",
};
return sci_mcq_page(1, 2, "biounit3", "bio_ex3_title", "bio_quiz3_head",
qs, hints, 4);
}
static GtkWidget *build_bio_unit4_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Bezobratlí", "Hmyz a další",
"Tip: hmyz má tři páry nohou a tělo ze tří částí.",
{
"Bezobratlí nemají vnitřní kostru z obratlů.",
"Kroužkovci, třeba žížala, mají článkované tělo.",
"Měkkýši mají měkké tělo, často schránku. Patří sem hlemýžď.",
"Hmyz má hlavu, hruď a zadeček a šest nohou.",
NULL,
},
},
{
"2 / 3 • Obratlovci", "Pět tříd, které škola probírá",
"Tip: ptáci i savci mají stálou tělesnou teplotu.",
{
"Ryby dýchají žábrami a žijí ve vodě.",
"Obojživelníci mají larvu ve vodě a vlhkou kůži, třeba žába.",
"Plazi mají suchou kůži se šupinami a vejce s obalem.",
"Ptáci mají peří, savci srst a mláďata krmí mlékem.",
NULL,
},
},
{
"3 / 3 • Vývoj", "Jak číst příbuznost",
"Tip: vývoj není žebřík od horšího k dokonalému člověku.",
{
"Druhy se mění v čase. Společný předek vysvětluje podobnost.",
"Obratlovci jsou jedna větev, ne vrchol všech živočichů.",
"Hmyz je druhově nejbohatší skupina živočichů.",
"Přizpůsobení prostředí se týká stavby těla i chování.",
NULL,
},
},
};
return sci_unit_page(1, &bio_lessons[3], "bio_unit4", "bio_unit4_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_bio_unit4_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Kolik nohou má hmyz?",
{"Čtyři", "Šest", "Osm", "Deset"},
4, 1},
{"Čím dýchají ryby?",
{"Plicemi jako savci", "Žábrami", "Jen kůží bez cév", "Vzdušnicemi jako hmyz"},
4, 1},
{"Který znak patří ptákům?",
{"Mléčné žlázy", "Peří", "Žábry u dospělce", "Tělo bez kostry"},
4, 1},
{"Čím savci živí mláďata?",
{"Vždy jen žloutkem mimo tělo", "Mlékem",
"Fotosyntézou mláděte", "Filtrací vody žábrami"},
4, 1},
};
static const char *hints[] = {
"Tři páry nohou jsou šest. Pavouci mají osm a nejsou hmyz.",
"Žábry berou kyslík z vody. Dospělá žába už dýchá plícemi a kůží.",
"Peří je ptačí znak. Srst a mléko patří savcům.",
"Mléčné žlázy jsou znak savců. Ptáci vejce zahřívají.",
};
return sci_mcq_page(1, 3, "biounit4", "bio_ex4_title", "bio_quiz4_head",
qs, hints, 4);
}
static GtkWidget *build_bio_unit5_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Pohyb", "Kostra a svaly",
"Tip: kost je živá tkáň, ne jen minerál.",
{
"Kostra dává oporu, chrání orgány a je zásobárna vápníku.",
"Sval se upíná na kost šlachou a pohyb vzniká jeho stahem.",
"Kloub spojuje kosti pohyblivě.",
"Páteř chrání míchu.",
NULL,
},
},
{
"2 / 3 • Oběh", "Srdce a krev",
"Tip: malý oběh vede krev do plic, velký do těla.",
{
"Srdce člověka má dvě síně a dvě komory.",
"Pravá komora žene krev do plic, levá do těla.",
"Tepny vedou krev ze srdce, žíly do srdce.",
"V plicních sklípcích se do krve dostává kyslík a odchází oxid uhličitý.",
NULL,
},
},
{
"3 / 3 • Trávení", "Od úst k ledvinám",
"Tip: živiny se vstřebávají hlavně v tenkém střevě.",
{
"V ústech začíná mechanické a chemické zpracování potravy.",
"Žaludek kyselinou a enzymy pokračuje v trávení bílkovin.",
"Tenké střevo živiny vstřebá, tlusté hlavně vodu.",
"Ledviny filtrují krev a tvoří moč, játra zpracovávají živiny.",
NULL,
},
},
};
return sci_unit_page(1, &bio_lessons[4], "bio_unit5", "bio_unit5_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_bio_unit5_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Kolik dutin má srdce člověka?",
{"Dvě", "Tři", "Čtyři", "Jednu"},
4, 2},
{"Kam vede malý krevní oběh?",
{"Z levé komory do celého těla", "Z pravé komory do plic",
"Z jater do žaludku", "Z ledvin do střeva"},
4, 1},
{"Kde se vstřebává většina živin?",
{"V jícnu", "V tenkém střevě", "V tlustém střevě jako cukry", "Ve slezině"},
4, 1},
{"Co tvoří ledviny?",
{"Žluč", "Moč", "Insulin", "Slinu"},
4, 1},
};
static const char *hints[] = {
"Dvě síně a dvě komory. Mezi pravou a levou polovinou je přepážka.",
"Malý oběh okysličuje krev. Velký ji rozvádí tělem.",
"Klky tenkého střeva zvětšují plochu. Tlusté střevo hlavně zahušťuje.",
"Játra tvoří žluč. Slinivka insulin. Ledviny moč.",
};
return sci_mcq_page(1, 4, "biounit5", "bio_ex5_title", "bio_quiz5_head",
qs, hints, 4);
}
static GtkWidget *build_bio_unit6_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Nervy", "Řízení organismu",
"Tip: mozek a mícha jsou ústřední nervová soustava.",
{
"Nervová soustava vede vzruchy rychle a cíleně.",
"Mozek a mícha tvoří ústřední část, nervy obvodovou.",
"Reflex je odpověď na podnět, třeba ucuknutí od horka.",
"Smysly převádějí podněty z okolí na vzruch.",
NULL,
},
},
{
"2 / 3 • Hormony", "Pomalejší řízení",
"Tip: insulin ze slinivky snižuje hladinu glukózy v krvi.",
{
"Hormony jsou signální látky přenášené krví.",
"Štítná žláza ovlivňuje metabolismus, nadledviny stresovou odpověď.",
"Insulin snižuje glykemii, glukagon ji zvyšuje.",
"Pohlavní hormony řídí dospívání a rozmnožování.",
NULL,
},
},
{
"3 / 3 • Obrana", "Rozmnožování a imunita",
"Tip: očkování připraví imunitu, aniž by člověk nemoc prodělal.",
{
"Spermie a vajíčko mají po 23 chromozomech, zygota 46.",
"Oplození spojí genetickou informaci obou rodičů.",
"Bílé krvinky a protilátky patří k imunitě.",
"Imunita rozliší vlastní buňky od cizích a od patogenů.",
NULL,
},
},
};
return sci_unit_page(1, &bio_lessons[5], "bio_unit6", "bio_unit6_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_bio_unit6_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co tvoří ústřední nervovou soustavu?",
{"Jen obvodové nervy", "Mozek a mícha",
"Jen štítná žláza", "Srdce a plíce"},
4, 1},
{"Co dělá insulin?",
{"Zvyšuje množství glukózy v krvi",
"Snižuje hladinu glukózy v krvi",
"Tvoří moč",
"Nahrazuje červené krvinky"},
4, 1},
{"Kolik chromozomů má lidská zygota?",
{"23", "46", "92 v každé běžné tělní buňce navíc", "2"},
4, 1},
{"K čemu je očkování?",
{"Aby virus získal buňku",
"Aby se imunita naučila poznat patogen předem",
"Aby nahradilo všechny bílé krvinky natrvalo",
"Aby zastavilo mitózu"},
4, 1},
};
static const char *hints[] = {
"Obvodové nervy spojují ústředí se smysly a svaly.",
"Insulin vyrábí slinivka. Jeho nedostatek je podstatou cukrovky 1. typu.",
"23 + 23 = 46. Pohlavní buňky jsou haploidní, zygota diploidní.",
"Vakcína je antigen, který imunitní paměť připraví.",
};
return sci_mcq_page(1, 5, "biounit6", "bio_ex6_title", "bio_quiz6_head",
qs, hints, 4);
}
static GtkWidget *build_bio_unit7_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • DNA", "Gen a alela",
"Tip: gen je úsek DNA, alela je jeho konkrétní forma.",
{
"DNA nese dědičnou informaci a je v chromozomech.",
"Gen je úsek DNA, který ovlivňuje určitý znak.",
"Alely jsou varianty téhož genu.",
"Dominantní alela se projeví i v jedné kopii, recesivní až ve dvou.",
NULL,
},
},
{
"2 / 3 • Mendel", "Genotyp a fenotyp",
"Tip: fenotyp je to, co vidíme, genotyp je zápis alel.",
{
"Johann Gregor Mendel sledoval dědičnost na hrachu.",
"Při křížení se alely rozcházejí do pohlavních buněk.",
"Genotyp je sestava alel, fenotyp je jejich projev.",
"Ne každý znak řídí jediný gen a prostředí projev také mění.",
NULL,
},
},
{
"3 / 3 • Změna", "Mutace a pohlaví",
"Tip: u člověka je typická žena XX a typický muž XY.",
{
"Mutace je změna DNA. Může být neutrální, škodlivá, nebo vzácně užitečná.",
"Mutace v pohlavní buňce se může přenést na potomka.",
"Pohlavní chromozomy člověka jsou X a Y.",
"Kombinace XX odpovídá ženě, XY muži.",
NULL,
},
},
};
return sci_unit_page(1, &bio_lessons[6], "bio_unit7", "bio_unit7_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_bio_unit7_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co je gen?",
{"Celá buňka", "Úsek DNA ovlivňující znak",
"Druh bílkoviny v potravě", "Hormon štítné žlázy"},
4, 1},
{"Co je fenotyp?",
{"Sestava alel v buňce", "Pozorovatelný projev znaku",
"Počet ribozomů", "Vzorec glukózy"},
4, 1},
{"Kdy se projeví recesivní alela?",
{"Vždycky, i když je v páru s dominantní",
"Až když jsou recesivní obě alely",
"Jen u bakterií",
"Jen jako mutace viru"},
4, 1},
{"Která sestava chromozomů odpovídá muži?",
{"XX", "XY", "YY jako jediná možnost u savců", "Jen autozomy bez X a Y"},
4, 1},
};
static const char *hints[] = {
"Alela je varianta genu. Chromozom nese mnoho genů.",
"Genotyp je zápis, fenotyp je výsledek genů a prostředí.",
"Heterozygot s dominantní alelou má dominantní fenotyp.",
"XX je žena, XY muž. Y nese gen pro vývoj varlat.",
};
return sci_mcq_page(1, 6, "biounit7", "bio_ex7_title", "bio_quiz7_head",
qs, hints, 4);
}
static GtkWidget *build_bio_unit8_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Úrovně", "Od jedince k biosféře",
"Tip: ekosystém zahrnuje živé i neživé.",
{
"Ekologie zkoumá vztahy organismů mezi sebou a k prostředí.",
"Populace, společenstvo a ekosystém jsou tři navazující úrovně.",
"Biosféra je všechny ekosystémy Země.",
"Abiotické faktory jsou světlo, voda, teplota, vzduch a půda.",
NULL,
},
},
{
"2 / 3 • Potravní vztahy", "Kdo koho živí",
"Tip: na každý další článek řetězce zbývá méně energie.",
{
"Producenti, hlavně rostliny, vážou energii fotosyntézou.",
"Konzumenti se živí jinými organismy.",
"Rozkladači, bakterie a houby, vracejí látky do koloběhu.",
"V potravním řetězci se většina energie ztrácí jako teplo.",
NULL,
},
},
{
"3 / 3 • Člověk", "Prostředí a ochrana",
"Tip: fotosyntéza váže CO2, dýchání a spalování ho vracejí.",
{
"Člověk mění krajinu, klima i biodiverzitu.",
"Znečištění, kácení a nadměrný lov snižují rozmanitost druhů.",
"Koloběh uhlíku spojuje fotosyntézu, dýchání a spalování.",
"Ochrana přírody drží životní podmínky i pro člověka.",
NULL,
},
},
};
return sci_unit_page(1, &bio_lessons[7], "bio_unit8", "bio_unit8_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_bio_unit8_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co je ekosystém?",
{"Jen jedinci jednoho druhu",
"Společenstvo spolu s neživým prostředím",
"Jedna buňka",
"Jen atmosféra bez organismů"},
4, 1},
{"Kdo je v ekosystému producent?",
{"Dravec", "Zelená rostlina", "Houba rozkladač", "Virus"},
4, 1},
{"Co dělají rozkladači?",
{"Vyrábějí kyslík fotosyntézou jako hlavní producenti",
"Rozkládají organické zbytky a vracejí látky do koloběhu",
"Jsou vždy obratlovci",
"Nahrazují producenty v moři i na souši jako rostliny"},
4, 1},
{"Proč je na vrcholu potravního řetězce málo energie?",
{"Protože se na každém článku část energie ztratí",
"Protože producenti energii nevytvářejí",
"Protože rozkladači energii zdvojnásobí",
"Protože slunce ekosystém neovlivňuje"},
4, 0},
};
static const char *hints[] = {
"Společenstvo jsou živé složky. Ekosystém k nim přidává neživé.",
"Producent je autotrof. Konzument je heterotrof, který někoho žere.",
"Bakterie a houby mineralizují zbytky. Rostliny je znovu využijí.",
"Škola často uvádí, že na další hladinu přejde jen malá část energie.",
};
return sci_mcq_page(1, 7, "biounit8", "bio_ex8_title", "bio_quiz8_head",
qs, hints, 4);
}
void add_bio_pages(GtkStack *stack) {
typedef GtkWidget *(*SciBuilder)(void);
static const struct {
const char *unit_name;
const char *ex_name;
SciBuilder build_unit;
SciBuilder build_ex;
} pages[] = {
{"biounit1", "bioex1", build_bio_unit1_page, build_bio_unit1_exercise_page},
{"biounit2", "bioex2", build_bio_unit2_page, build_bio_unit2_exercise_page},
{"biounit3", "bioex3", build_bio_unit3_page, build_bio_unit3_exercise_page},
{"biounit4", "bioex4", build_bio_unit4_page, build_bio_unit4_exercise_page},
{"biounit5", "bioex5", build_bio_unit5_page, build_bio_unit5_exercise_page},
{"biounit6", "bioex6", build_bio_unit6_page, build_bio_unit6_exercise_page},
{"biounit7", "bioex7", build_bio_unit7_page, build_bio_unit7_exercise_page},
{"biounit8", "bioex8", build_bio_unit8_page, build_bio_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);
}
}

604
src/chemie.c Normal file
View file

@ -0,0 +1,604 @@
#include "graduately.h"
/* Chemistry for Základy přírodopisných věd.
* Follows the secondary-school outline (RVP for vocational schools,
* přírodovědné vzdělávání, and the gymnasium general-chemistry track):
* general chemistry, inorganic chemistry, organic chemistry, biochemistry. */
NetLesson chem_lessons[SCI_N] = {
{ .n_slides = 3, .unit_page = "chemunit1", .ex_page = "chemex1" },
{ .n_slides = 3, .unit_page = "chemunit2", .ex_page = "chemex2" },
{ .n_slides = 3, .unit_page = "chemunit3", .ex_page = "chemex3" },
{ .n_slides = 3, .unit_page = "chemunit4", .ex_page = "chemex4" },
{ .n_slides = 3, .unit_page = "chemunit5", .ex_page = "chemex5" },
{ .n_slides = 3, .unit_page = "chemunit6", .ex_page = "chemex6" },
{ .n_slides = 3, .unit_page = "chemunit7", .ex_page = "chemex7" },
{ .n_slides = 3, .unit_page = "chemunit8", .ex_page = "chemex8" },
};
static GtkWidget *build_chem_unit1_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Atom", "Z čeho je látka",
"Tip: protonové číslo je počet protonů, ne elektronů.",
{
"Atom má jádro z protonů a neutronů a obal z elektronů.",
"Proton má kladný náboj, elektron záporný, neutron je bez náboje.",
"Protonové číslo Z udává počet protonů a určuje prvek.",
"Nukleonové číslo A je součet protonů a neutronů.",
NULL,
},
},
{
"2 / 3 • Tabulka", "Periody a skupiny",
"Tip: perioda je řádek, skupina je sloupec.",
{
"Periodická soustava řadí prvky podle rostoucího protonového čísla.",
"Prvky v jedné skupině mají podobné chemické vlastnosti.",
"Číslo periody souvisí s počtem obsazených slupek.",
"Vlevo jsou kovy, vpravo nekovy, mezi nimi polokovy.",
NULL,
},
},
{
"3 / 3 • Částice", "Atom, molekula, ion",
"Tip: kation vzniká ztrátou elektronů, anion jejich přijetím.",
{
"Prvek tvoří atomy se stejným protonovým číslem.",
"Molekula je částice ze dvou nebo více atomů spojených vazbou.",
"Ion je nabitá částice: kation je kladný, anion záporný.",
"Izotopy jednoho prvku se liší počtem neutronů, ne protonů.",
NULL,
},
},
};
return sci_unit_page(0, &chem_lessons[0], "chem_unit1", "chem_unit1_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_chem_unit1_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co udává protonové číslo?",
{"Počet protonů v jádře", "Počet neutronů v jádře",
"Součet protonů a elektronů v molekule", "Hmotnost atomu v gramech"},
4, 0},
{"Jaký náboj má elektron?",
{"Kladný", "Záporný", "Žádný", "Stejný jako neutron"},
4, 1},
{"Co mají společného prvky v jedné skupině?",
{"Stejné nukleonové číslo", "Podobné chemické vlastnosti",
"Stejný počet neutronů", "Stejné skupenství za pokojové teploty"},
4, 1},
{"Co je v periodické soustavě perioda?",
{"Sloupec prvků", "Řádek prvků",
"Jen skupina vzácných plynů", "Rozdíl protonů a neutronů"},
4, 1},
};
static const char *hints[] = {
"Z je počet protonů a zároveň pořadí prvku v tabulce.",
"Proton je kladný, elektron záporný, neutron neutrální.",
"Skupina je sloupec. Podobné vlastnosti plynou z podobného obalu.",
"Perioda je řádek. Číslo periody souvisí s obsazenými slupkami.",
};
return sci_mcq_page(0, 0, "chemunit1", "chem_ex1_title", "chem_quiz1_head",
qs, hints, 4);
}
static GtkWidget *build_chem_unit2_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Ionty", "Iontová vazba",
"Tip: NaCl je typická iontová sloučenina.",
{
"Iontová vazba vzniká přenosem elektronů mezi atomy.",
"Kov odevzdá elektrony a stane se kationtem, nekov je přijme jako anion.",
"Opačné náboje se přitahují a drží krystal pohromadě.",
"Iontové látky mají vysoké teploty tání a v tavenině vedou proud.",
NULL,
},
},
{
"2 / 3 • Sdílení", "Kovalentní vazba",
"Tip: ve vodě atomy elektrony sdílejí, nepředávají si je natrvalo.",
{
"Kovalentní vazba vzniká sdílením elektronového páru.",
"Jednoduchá vazba je jeden pár, dvojná dva, trojná tři.",
"Nepolární vazba spojuje atomy se stejnou elektronegativitou, třeba H2.",
"Polární vazba vzniká mezi atomy s různou elektronegativitou, třeba v H2O.",
NULL,
},
},
{
"3 / 3 • Kovy", "Kovová vazba",
"Tip: volné elektrony vysvětlují lesk i vodivost kovů.",
{
"V kovu jsou kationty obklopené sdílenými elektrony.",
"Elektrony se mohou posouvat, proto kov vede proud a teplo.",
"Elektronegativita je schopnost atomu přitahovat elektrony vazby.",
"Velký rozdíl elektronegativit vede spíš k iontové vazbě.",
NULL,
},
},
};
return sci_unit_page(0, &chem_lessons[1], "chem_unit2", "chem_unit2_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_chem_unit2_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jaká vazba drží krystal chloridu sodného?",
{"Kovalentní nepolární", "Iontová", "Kovová", "Vodíkový můstek jako jediná"},
4, 1},
{"Co je kovalentní vazba?",
{"Přenos elektronu z kovu na nekov",
"Sdílení elektronového páru",
"Pohyb volných elektronů mezi kationty kovu",
"Přitažlivost jen mezi molekulami vody"},
4, 1},
{"Proč kovy vedou elektrický proud?",
{"Mají pevné jádro bez elektronů",
"Obsahují pohyblivé elektrony kovové vazby",
"Jsou vždy kapalné",
"Nemají protony"},
4, 1},
{"Co je elektronegativita?",
{"Počet neutronů v jádře",
"Schopnost atomu přitahovat elektrony vazby",
"Hmotnost jednoho molu",
"Teplota varu kovu"},
4, 1},
};
static const char *hints[] = {
"Sodík předá elektron chloru. Vznikne Na+ a Cl−.",
"Kovalentní vazba je společný elektronový pár, ne trvalý přenos.",
"Kovová vazba má volné elektrony, které přenášejí náboj.",
"Čím větší rozdíl elektronegativit, tím polárnější nebo iontovější vazba.",
};
return sci_mcq_page(0, 1, "chemunit2", "chem_ex2_title", "chem_quiz2_head",
qs, hints, 4);
}
static GtkWidget *build_chem_unit3_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Rovnice", "Co se při reakci zachová",
"Tip: vyčíslit znamená srovnat počty atomů, ne měnit vzorce.",
{
"Při chemické reakci se atomy přeskupují, ale neztrácejí.",
"Zákon zachování hmotnosti: hmotnost reaktantů se rovná hmotnosti produktů.",
"V rovnici musí být na obou stranách stejný počet atomů každého prvku.",
"Koeficient před vzorcem násobí celou částici.",
NULL,
},
},
{
"2 / 3 • Mol", "Kolik je částic",
"Tip: molární hmotnost vody je přibližně 18 g/mol.",
{
"Mol je jednotka látkového množství.",
"Jeden mol obsahuje 6,022 × 10^23 částic, to je Avogadrova konstanta.",
"Molární hmotnost M je hmotnost jednoho molu v g/mol.",
"Látkové množství se počítá jako n = m / M.",
NULL,
},
},
{
"3 / 3 • Výpočet", "Od hmotnosti k molům",
"Tip: nejdřív sečti relativní atomové hmotnosti ve vzorci.",
{
"Relativní atomová hmotnost vodíku je 1, kyslíku 16, uhlíku 12.",
"Voda H2O má molární hmotnost 18 g/mol, oxid uhličitý CO2 má 44 g/mol.",
"18 g vody je 1 mol, tedy 6,022 × 10^23 molekul.",
"Stechiometrie porovnává moly podle koeficientů v rovnici.",
NULL,
},
},
};
return sci_unit_page(0, &chem_lessons[2], "chem_unit3", "chem_unit3_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_chem_unit3_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co říká zákon zachování hmotnosti?",
{"Hmotnost produktů je vždy menší",
"Hmotnost reaktantů se rovná hmotnosti produktů",
"Atomy se při reakci ničí",
"Molární hmotnost se během reakce mění"},
4, 1},
{"Kolik částic je v jednom molu?",
{"18", "44", "6,022 × 10^23", "1000"},
4, 2},
{"Jak se spočítá látkové množství?",
{"n = m × M", "n = m / M", "n = M / m", "n = Z + A"},
4, 1},
{"Jaká je přibližně molární hmotnost vody?",
{"2 g/mol", "16 g/mol", "18 g/mol", "44 g/mol"},
4, 2},
};
static const char *hints[] = {
"Atomy se přeskupují. Jejich celková hmotnost zůstává.",
"Avogadrova konstanta je počet částic v jednom molu.",
"Hmotnost dělíme molární hmotností. Výsledek je v molech.",
"H2O: 1 + 1 + 16 = 18. CO2 je 12 + 16 + 16 = 44.",
};
return sci_mcq_page(0, 2, "chemunit3", "chem_ex3_title", "chem_quiz3_head",
qs, hints, 4);
}
static GtkWidget *build_chem_unit4_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Směsi", "Čistá látka a směs",
"Tip: roztok je homogenní směs, suspenze heterogenní.",
{
"Čistá látka má stálé složení a stálé vlastnosti.",
"Směs obsahuje dvě nebo více látek, které nejsou chemicky vázané.",
"Homogenní směs, třeba roztok, vypadá v celém objemu stejně.",
"Heterogenní směs má rozlišitelné složky, třeba suspenze nebo emulze.",
NULL,
},
},
{
"2 / 3 • Roztok", "Složení roztoku",
"Tip: hmotnostní zlomek je podíl hmotnosti složky a hmotnosti směsi.",
{
"Rozpouštědlo je látka, v níž se rozpouští rozpuštěná látka.",
"Hmotnostní zlomek w = hmotnost složky / hmotnost celé směsi.",
"Nasycený roztok už při dané teplotě další látku nerozpustí.",
"Rozpustnost většiny pevných látek s teplotou roste.",
NULL,
},
},
{
"3 / 3 • pH", "Kyseliny a zásady",
"Tip: neutralizace kyseliny hydroxidem dává sůl a vodu.",
{
"Kyselina ve vodě zvyšuje množství H+, zásada množství OH−.",
"pH menší než 7 je kyselé, 7 neutrální, větší než 7 zásadité.",
"Silné kyseliny jsou třeba HCl a H2SO4, silná zásada je NaOH.",
"Neutralizace: kyselina + hydroxid → sůl + voda.",
NULL,
},
},
};
return sci_unit_page(0, &chem_lessons[3], "chem_unit4", "chem_unit4_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_chem_unit4_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co je roztok?",
{"Heterogenní směs dvou kovů", "Homogenní směs",
"Čistý prvek", "Jen sraženina na dně"},
4, 1},
{"Jaké pH má kyselý roztok?",
{"Přesně 7", "Menší než 7", "Větší než 7", "Vždy 14"},
4, 1},
{"Co vzniká neutralizací kyseliny hydroxidem?",
{"Jen vodík a kyslík jako prvky", "Sůl a voda",
"Uhlovodík", "Vzácný plyn"},
4, 1},
{"Co je hmotnostní zlomek?",
{"Podíl hmotnosti složky a hmotnosti směsi",
"Počet molů v litru",
"Rozdíl pH a 7",
"Hmotnost jednoho protonu"},
4, 0},
};
static const char *hints[] = {
"Roztok je stejnorodý. Suspenze a emulze jsou různorodé.",
"Neutrální pH je 7. Pod ním je kyselina, nad ním zásada.",
"H+ z kyseliny a OH− ze zásady dají vodu, zbytek iontů sůl.",
"w = m(složky) / m(směsi). Nemá jednotku, nebo se uvádí v procentech.",
};
return sci_mcq_page(0, 3, "chemunit4", "chem_ex4_title", "chem_quiz4_head",
qs, hints, 4);
}
static GtkWidget *build_chem_unit5_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Názvy", "Čtyři skupiny sloučenin",
"Tip: oxid uhličitý je CO2, oxid uhelnatý je CO.",
{
"Oxidy jsou sloučeniny prvku s kyslíkem.",
"Kyseliny ve vzorci obsahují vodík, který mohou odštěpit jako H+.",
"Hydroxidy obsahují skupinu OH, soli vznikají z kyseliny náhradou vodíku.",
"NaCl je chlorid sodný, NaOH hydroxid sodný, HCl kyselina chlorovodíková.",
NULL,
},
},
{
"2 / 3 • Nekovy", "Halogeny, kyslík, dusík",
"Tip: halogeny jsou ve 17. skupině a jsou velmi reaktivní.",
{
"Kyslík O2 tvoří asi pětinu vzduchu a je nutný k dýchání i hoření.",
"Halogeny fluor, chlor, brom a jod tvoří soli, halogenidy.",
"Dusík N2 tvoří většinu vzduchu a je v bílkovinách i v hnojivech.",
"Síra je v kyselině sírové H2SO4, důležité průmyslové kyselině.",
NULL,
},
},
{
"3 / 3 • Kovy", "Sodík, vápník, železo",
"Tip: alkalické kovy reagují s vodou a musí se uchovávat pod petrolejem.",
{
"Alkalické kovy lithium, sodík a draslík jsou měkké a velmi reaktivní.",
"Vápník a hořčík patří ke kovům alkalických zemin a jsou v kostech i chlorofylu.",
"Železo je přechodný kov, základ oceli, a v těle je v hemoglobinu.",
"Ušlechtilé kovy, třeba zlato, vzdorují korozi víc než železo.",
NULL,
},
},
};
return sci_unit_page(0, &chem_lessons[4], "chem_unit5", "chem_unit5_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_chem_unit5_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Který vzorec patří oxidu uhličitému?",
{"CO", "CO2", "H2CO3", "CaCO3"},
4, 1},
{"Co je hydroxid sodný?",
{"NaCl", "NaOH", "HCl", "H2SO4"},
4, 1},
{"Které prvky jsou halogeny?",
{"Li, Na, K", "F, Cl, Br, I", "Fe, Cu, Zn", "He, Ne, Ar"},
4, 1},
{"Kam patří sodík a draslík?",
{"Mezi vzácné plyny", "Mezi alkalické kovy",
"Mezi halogeny", "Mezi polokovy"},
4, 1},
};
static const char *hints[] = {
"CO je oxid uhelnatý, CO2 oxid uhličitý. H2CO3 je kyselina uhličitá.",
"Hydroxidy končí na OH. NaOH je silná zásada.",
"Halogeny jsou 17. skupina. Alkalické kovy jsou 1. skupina.",
"Li, Na a K jsou alkalické kovy. S vodou reagují bouřlivě.",
};
return sci_mcq_page(0, 4, "chemunit5", "chem_ex5_title", "chem_quiz5_head",
qs, hints, 4);
}
static GtkWidget *build_chem_unit6_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Uhlík", "Proč existuje organická chemie",
"Tip: organické sloučeniny staví řetězce a kruhy z uhlíku.",
{
"Organická chemie zkoumá sloučeniny uhlíku, kromě oxidů a uhličitanů.",
"Uhlík tvoří čtyři vazby a může se řetězit.",
"Uhlovodíky obsahují jen uhlík a vodík.",
"Nasycené mají jen jednoduché vazby, nenasycené i dvojné nebo trojné.",
NULL,
},
},
{
"2 / 3 • Řady", "Alkany, alkeny a alkyny",
"Tip: obecný vzorec alkanů je CnH2n+2.",
{
"Alkany jsou nasycené. Methan CH4 je nejjednodušší.",
"Alkeny mají dvojnou vazbu. Ethen C2H4 je surovina pro plasty.",
"Alkyny mají trojnou vazbu. Ethyn, acetylen, se používá ke svařování.",
"Hořením uhlovodíků vzniká oxid uhličitý a voda.",
NULL,
},
},
{
"3 / 3 • Areny", "Benzen",
"Tip: benzen není alken se třemi obyčejnými dvojnými vazbami.",
{
"Areny jsou aromatické uhlovodíky s zvláštně stálým kruhem.",
"Benzen má vzorec C6H6.",
"Aromatický kruh je stálejší než obyčejné střídání dvojných vazeb.",
"Z arenů se vyrábějí plasty, léčiva i barviva.",
NULL,
},
},
};
return sci_unit_page(0, &chem_lessons[5], "chem_unit6", "chem_unit6_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_chem_unit6_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Který uhlovodík je alkan?",
{"Ethen", "Ethyn", "Methan", "Benzen"},
4, 2},
{"Jaký je obecný vzorec alkanů?",
{"CnH2n", "CnH2n+2", "CnH2n−2", "CnH2n−6"},
4, 1},
{"Čím se ethen liší od ethanu?",
{"Nemá uhlík", "Má dvojnou vazbu",
"Je to kov", "Má trojnou vazbu"},
4, 1},
{"Jaký vzorec má benzen?",
{"CH4", "C2H4", "C2H2", "C6H6"},
4, 3},
};
static const char *hints[] = {
"Methan a ethan jsou alkany. Ethen je alken, ethyn alkyn, benzen aren.",
"Alkany CnH2n+2, alkeny CnH2n, alkyny CnH2n−2.",
"Ethan je nasycený. Ethen má jednu dvojnou vazbu.",
"Benzen C6H6 je základní aren. Methan je CH4.",
};
return sci_mcq_page(0, 5, "chemunit6", "chem_ex6_title", "chem_quiz6_head",
qs, hints, 4);
}
static GtkWidget *build_chem_unit7_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Skupiny", "Deriváty uhlovodíků",
"Tip: funkční skupina rozhoduje o vlastnostech víc než délka řetězce.",
{
"Derivát vzniká náhradou vodíku jinou skupinou atomů.",
"Alkoholy mají hydroxyl −OH. Ethanol je v alkoholických nápojích.",
"Karboxylové kyseliny mají −COOH. Kyselina octová je v octu.",
"Aldehydy a ketony obsahují karbonylovou skupinu C=O.",
NULL,
},
},
{
"2 / 3 • Cukry", "Sacharidy",
"Tip: glukóza je monosacharid, škrob a celulóza jsou její polymery.",
{
"Sacharidy jsou přírodní látky z uhlíku, vodíku a kyslíku.",
"Glukóza C6H12O6 je základní cukr a palivo buněk.",
"Sacharosa je řepný a třtinový cukr, škrob je zásoba rostlin.",
"Celulóza tvoří buněčné stěny rostlin a člověk ji netráví.",
NULL,
},
},
{
"3 / 3 • Tuky a bílkoviny", "Z čeho je tělo",
"Tip: bílkovina je řetězec aminokyselin, tuk je ester glycerolu.",
{
"Lipidy jsou tuky a oleje, estery glycerolu a mastných kyselin.",
"Tuky jsou zásoba energie a součást membrán.",
"Bílkoviny vznikají spojením aminokyselin peptidovou vazbou.",
"Pořadí aminokyselin určuje tvar a funkci bílkoviny.",
NULL,
},
},
};
return sci_unit_page(0, &chem_lessons[6], "chem_unit7", "chem_unit7_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_chem_unit7_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Kterou skupinu mají alkoholy?",
{"−COOH", "−OH", "−NH2 jako jedinou v alkanech", "Jen dvojnou vazbu uhlík–uhlík"},
4, 1},
{"Co je kyselina octová?",
{"Alkan", "Karboxylová kyselina", "Halogen", "Hydroxid"},
4, 1},
{"Která látka je sacharid?",
{"Škrob", "Chlorid sodný", "Ethyn", "Oxid vápenatý"},
4, 0},
{"Z čeho jsou bílkoviny?",
{"Z glycerolu a nic jiného", "Z aminokyselin",
"Jen z glukózy", "Z vzácných plynů"},
4, 1},
};
static const char *hints[] = {
"Hydroxyl −OH je alkohol. Karboxyl −COOH je kyselina.",
"Octová kyselina CH3COOH má karboxyl. V octu je její zředěný roztok.",
"Glukóza, sacharosa, škrob a celulóza jsou sacharidy.",
"Aminokyseliny se pojí peptidovou vazbou do bílkoviny.",
};
return sci_mcq_page(0, 6, "chemunit7", "chem_ex7_title", "chem_quiz7_head",
qs, hints, 4);
}
static GtkWidget *build_chem_unit8_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Enzymy", "Biokatalyzátory",
"Tip: enzym je bílkovina a urychluje jednu určitou reakci.",
{
"Biochemie zkoumá chemické děje v živých organismech.",
"Enzym je bílkovinný katalyzátor. Snižuje aktivační energii.",
"Každý enzym působí na určitý substrát, jako klíč do zámku.",
"Teplo a nevhodné pH enzym denaturují a reakce se zpomalí.",
NULL,
},
},
{
"2 / 3 • Energie", "Fotosyntéza a dýchání",
"Tip: fotosyntéza váže energii, buněčné dýchání ji uvolňuje.",
{
"Fotosyntéza: 6 CO2 + 6 H2O → C6H12O6 + 6 O2, potřebuje světlo.",
"Probíhá v chloroplastech a je zdrojem kyslíku i organických látek.",
"Buněčné dýchání glukózu oxiduje a uvolňuje energii pro buňku.",
"Zjednodušeně je dýchání opačný směr než fotosyntéza.",
NULL,
},
},
{
"3 / 3 • DNA", "Chemický zápis dědičnosti",
"Tip: v RNA je uracil tam, kde má DNA thymin.",
{
"Nukleové kyseliny jsou DNA a RNA.",
"DNA nese genetickou informaci. Báze jsou adenin, thymin, guanin a cytosin.",
"Adenin se páruje s thyminem, guanin s cytosinem.",
"RNA má místo thyminu uracil a v buňce pomáhá číst geny.",
NULL,
},
},
};
return sci_unit_page(0, &chem_lessons[7], "chem_unit8", "chem_unit8_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_chem_unit8_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co je enzym?",
{"Anorganická sůl", "Bílkovinný biokatalyzátor",
"Vzácný plyn", "Nasycený uhlovodík"},
4, 1},
{"Co při fotosyntéze vzniká spolu s glukózou?",
{"Dusík", "Kyslík", "Chlorid sodný", "Železo"},
4, 1},
{"Která báze je v RNA místo thyminu?",
{"Adenin", "Uracil", "Guanin navíc bez cytosinu", "Sodík"},
4, 1},
{"Co dělá buněčné dýchání s glukózou?",
{"Ukládá ji beze změny do jádra",
"Oxiduje ji a uvolňuje energii",
"Mění ji na vzácný plyn",
"Zastavuje všechny enzymy"},
4, 1},
};
static const char *hints[] = {
"Enzym je bílkovina. Katalyzátor reakci urychlí a sám se nespotřebuje.",
"Zjednodušená rovnice končí glukózou a kyslíkem.",
"DNA: A, T, G, C. RNA má U místo T.",
"Dýchání je opak fotosyntézy v tom smyslu, že energii z glukózy uvolní.",
};
return sci_mcq_page(0, 7, "chemunit8", "chem_ex8_title", "chem_quiz8_head",
qs, hints, 4);
}
void add_chem_pages(GtkStack *stack) {
typedef GtkWidget *(*SciBuilder)(void);
static const struct {
const char *unit_name;
const char *ex_name;
SciBuilder build_unit;
SciBuilder build_ex;
} pages[] = {
{"chemunit1", "chemex1", build_chem_unit1_page, build_chem_unit1_exercise_page},
{"chemunit2", "chemex2", build_chem_unit2_page, build_chem_unit2_exercise_page},
{"chemunit3", "chemex3", build_chem_unit3_page, build_chem_unit3_exercise_page},
{"chemunit4", "chemex4", build_chem_unit4_page, build_chem_unit4_exercise_page},
{"chemunit5", "chemex5", build_chem_unit5_page, build_chem_unit5_exercise_page},
{"chemunit6", "chemex6", build_chem_unit6_page, build_chem_unit6_exercise_page},
{"chemunit7", "chemex7", build_chem_unit7_page, build_chem_unit7_exercise_page},
{"chemunit8", "chemex8", build_chem_unit8_page, build_chem_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);
}
}

View file

@ -20,6 +20,7 @@
#define HW_SUBJ 2 /* index of "Technické vybavení" */ #define HW_SUBJ 2 /* index of "Technické vybavení" */
#define CZ_SUBJ 3 /* index of "Český jazyk a literatura" */ #define CZ_SUBJ 3 /* index of "Český jazyk a literatura" */
#define ON_SUBJ 4 /* index of "Občanská nauka" */ #define ON_SUBJ 4 /* index of "Občanská nauka" */
#define SCI_SUBJ 8 /* index of "Základy Přírodopisných věd" */
#define CSS_FILE "style.css" #define CSS_FILE "style.css"
#define CHANGELOG_FILE "changelog.txt" #define CHANGELOG_FILE "changelog.txt"
#define APP_ID "org.maturita.Maturita" #define APP_ID "org.maturita.Maturita"
@ -59,6 +60,8 @@ extern char *app_progress_lit;
extern char *app_progress_lit2; extern char *app_progress_lit2;
extern char *app_progress_lit3; extern char *app_progress_lit3;
extern char *app_progress_lit4; extern char *app_progress_lit4;
extern char *app_progress_chem;
extern char *app_progress_bio;
#define PROGRESS_DIR app_progress_dir #define PROGRESS_DIR app_progress_dir
#define PROGRESS_U1 app_progress_u1 #define PROGRESS_U1 app_progress_u1
#define PROGRESS_U2 app_progress_u2 #define PROGRESS_U2 app_progress_u2
@ -136,6 +139,7 @@ extern char *app_progress_lit4;
#define ON4_UNITS 16 #define ON4_UNITS 16
#define ON4_LESSONS 16 #define ON4_LESSONS 16
#define LIT_N 8 #define LIT_N 8
#define SCI_N 8
#define HW_SLIDES 3 #define HW_SLIDES 3
#define HW2_SLIDES 6 #define HW2_SLIDES 6
#define HW3_SLIDES 2 #define HW3_SLIDES 2
@ -929,6 +933,23 @@ extern NetLesson lit_lessons[LIT_N];
extern NetLesson lit2_lessons[LIT_N]; extern NetLesson lit2_lessons[LIT_N];
extern NetLesson lit3_lessons[LIT_N]; extern NetLesson lit3_lessons[LIT_N];
extern NetLesson lit4_lessons[LIT_N]; extern NetLesson lit4_lessons[LIT_N];
void sci_load_progress(void);
void sci_rail_theme_reset(void);
void sci_lessons_apply_lang(void);
void progress_for_sci(ProgressSum *out);
GtkWidget *sci_unit_page(int area, NetLesson *L, const char *title_key,
const char *sub_key, const NetSlide *slides,
guint n_slides);
GtkWidget *sci_mcq_page(int area, 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_scimap_page(void);
GtkWidget *build_chemmap_page(void);
GtkWidget *build_biomap_page(void);
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 hw_lessons_apply_lang(void); void hw_lessons_apply_lang(void);
GtkWidget *build_hwyears_page(void); GtkWidget *build_hwyears_page(void);
GtkWidget *build_hwmap_page(void); GtkWidget *build_hwmap_page(void);
@ -1016,6 +1037,8 @@ void draw_chip_icon(GtkDrawingArea *area, cairo_t *cr,
int width, int height, gpointer data); int width, int height, gpointer data);
void draw_civics_icon(GtkDrawingArea *area, cairo_t *cr, void draw_civics_icon(GtkDrawingArea *area, cairo_t *cr,
int width, int height, gpointer data); int width, int height, gpointer data);
void draw_science_icon(GtkDrawingArea *area, cairo_t *cr,
int width, int height, gpointer data);
void draw_czech_flag(GtkDrawingArea *area, cairo_t *cr, void draw_czech_flag(GtkDrawingArea *area, cairo_t *cr,
int width, int height, gpointer data); int width, int height, gpointer data);
void draw_book_icon(GtkDrawingArea *area, cairo_t *cr, void draw_book_icon(GtkDrawingArea *area, cairo_t *cr,

View file

@ -90,12 +90,12 @@ const TrEntry tr_ui[] = {
"Cvičení, nápovědy a vokabeltraining. Aplikace si pamatuje, kde jste skončili.", "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."}, "Exercises, hints and vocabulary drills. The app remembers where you left off."},
{"welcome_feat1_link", "Začít procvičovat", "Start practicing"}, {"welcome_feat1_link", "Začít procvičovat", "Start practicing"},
{"welcome_feat2_title", "Čtyři otevřené předměty", "Four open subjects"}, {"welcome_feat2_title", "Šest otevřených předmětů", "Six open subjects"},
{"welcome_feat2_body", {"welcome_feat2_body",
"Deutsch, sítě, hardware a čeština. Ostatní předměty čekají na svůj obsah.", "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 and Czech. The other subjects are still locked."}, "German, networks, hardware, Czech, civics and natural sciences. The other subjects are still locked."},
{"welcome_feat2_link", "Jaké předměty jsou otevřené?", "Which subjects are open?"}, {"welcome_feat2_link", "Jaké předměty jsou otevřené?", "Which subjects are open?"},
{"welcome_stat_value", "4", "4"}, {"welcome_stat_value", "6", "6"},
{"welcome_stat_label", "otevřené\npředměty", "open\nsubjects"}, {"welcome_stat_label", "otevřené\npředměty", "open\nsubjects"},
{"continue", "Pokračuj", "Continue"}, {"continue", "Pokračuj", "Continue"},
{"roadmap_title", "Učební plán", "Learning path"}, {"roadmap_title", "Učební plán", "Learning path"},
@ -1304,6 +1304,98 @@ const TrEntry tr_ui[] = {
{"lit4_ex8_title", "Kvíz: interpretace", "Quiz: interpretation"}, {"lit4_ex8_title", "Kvíz: interpretace", "Quiz: interpretation"},
{"lit4_quiz8_head", "Ověřte si postup rozboru textu", {"lit4_quiz8_head", "Ověřte si postup rozboru textu",
"Check how to read a text"}, "Check how to read a text"},
{"sci_sub", "Vyberte chemii nebo biologii.", "Pick chemistry or biology."},
{"Chemie", "Chemie", "Chemistry"},
{"Biologie", "Biologie", "Biology"},
{"chem_sub", "Osm lekcí od atomu po biochemii.",
"Eight lessons from the atom to biochemistry."},
{"bio_sub", "Osm lekcí od buňky po ekologii.",
"Eight lessons from the cell to ecology."},
{"chem_unit1", "Částice a periodická soustava", "Particles and the periodic table"},
{"chem_unit1_sub", "Atom, protonové číslo, periody a skupiny.",
"The atom, atomic number, periods and groups."},
{"chem_ex1_title", "Kvíz: částice", "Quiz: particles"},
{"chem_quiz1_head", "Ověřte si stavbu atomu", "Check the structure of the atom"},
{"chem_unit2", "Chemická vazba", "Chemical bonding"},
{"chem_unit2_sub", "Iontová, kovalentní a kovová vazba.",
"Ionic, covalent and metallic bonds."},
{"chem_ex2_title", "Kvíz: vazba", "Quiz: bonding"},
{"chem_quiz2_head", "Ověřte si chemickou vazbu", "Check chemical bonding"},
{"chem_unit3", "Reakce a výpočty", "Reactions and calculations"},
{"chem_unit3_sub", "Zákon zachování hmotnosti, mol a molární hmotnost.",
"Conservation of mass, the mole and molar mass."},
{"chem_ex3_title", "Kvíz: výpočty", "Quiz: calculations"},
{"chem_quiz3_head", "Ověřte si mol a reakce", "Check the mole and reactions"},
{"chem_unit4", "Směsi, roztoky a pH", "Mixtures, solutions and pH"},
{"chem_unit4_sub", "Homogenní a heterogenní směsi, neutralizace.",
"Homogeneous and heterogeneous mixtures, neutralization."},
{"chem_ex4_title", "Kvíz: roztoky", "Quiz: solutions"},
{"chem_quiz4_head", "Ověřte si směsi a pH", "Check mixtures and pH"},
{"chem_unit5", "Anorganické látky", "Inorganic substances"},
{"chem_unit5_sub", "Oxidy, kyseliny, hydroxidy, soli a vybrané prvky.",
"Oxides, acids, hydroxides, salts and selected elements."},
{"chem_ex5_title", "Kvíz: anorganika", "Quiz: inorganic chemistry"},
{"chem_quiz5_head", "Ověřte si anorganické látky", "Check inorganic substances"},
{"chem_unit6", "Uhlovodíky", "Hydrocarbons"},
{"chem_unit6_sub", "Alkany, alkeny, alkyny a benzen.",
"Alkanes, alkenes, alkynes and benzene."},
{"chem_ex6_title", "Kvíz: uhlovodíky", "Quiz: hydrocarbons"},
{"chem_quiz6_head", "Ověřte si uhlovodíky", "Check hydrocarbons"},
{"chem_unit7", "Deriváty a přírodní látky", "Derivatives and natural substances"},
{"chem_unit7_sub", "Alkoholy, kyseliny, sacharidy a bílkoviny.",
"Alcohols, acids, carbohydrates and proteins."},
{"chem_ex7_title", "Kvíz: deriváty", "Quiz: derivatives"},
{"chem_quiz7_head", "Ověřte si deriváty a přírodní látky",
"Check derivatives and natural substances"},
{"chem_unit8", "Biochemie", "Biochemistry"},
{"chem_unit8_sub", "Enzymy, fotosyntéza, dýchání a nukleové kyseliny.",
"Enzymes, photosynthesis, respiration and nucleic acids."},
{"chem_ex8_title", "Kvíz: biochemie", "Quiz: biochemistry"},
{"chem_quiz8_head", "Ověřte si biochemii", "Check biochemistry"},
{"bio_unit1", "Živé soustavy", "Living systems"},
{"bio_unit1_sub", "Metabolismus a organizace od buňky k ekosystému.",
"Metabolism and organisation from the cell to the ecosystem."},
{"bio_ex1_title", "Kvíz: živé soustavy", "Quiz: living systems"},
{"bio_quiz1_head", "Ověřte si znaky života", "Check the features of life"},
{"bio_unit2", "Buňka", "The cell"},
{"bio_unit2_sub", "Prokaryota, eukaryota, organely a dělení.",
"Prokaryotes, eukaryotes, organelles and division."},
{"bio_ex2_title", "Kvíz: buňka", "Quiz: the cell"},
{"bio_quiz2_head", "Ověřte si stavbu buňky", "Check the structure of the cell"},
{"bio_unit3", "Viry, bakterie, houby a rostliny",
"Viruses, bacteria, fungi and plants"},
{"bio_unit3_sub", "Nebuněčné viry, prokaryota a autotrofní rostliny.",
"Acellular viruses, prokaryotes and autotrophic plants."},
{"bio_ex3_title", "Kvíz: organismy", "Quiz: organisms"},
{"bio_quiz3_head", "Ověřte si viry, houby a rostliny",
"Check viruses, fungi and plants"},
{"bio_unit4", "Živočichové", "Animals"},
{"bio_unit4_sub", "Bezobratlí a pět tříd obratlovců.",
"Invertebrates and five classes of vertebrates."},
{"bio_ex4_title", "Kvíz: živočichové", "Quiz: animals"},
{"bio_quiz4_head", "Ověřte si skupiny živočichů", "Check the animal groups"},
{"bio_unit5", "Soustavy člověka", "Human body systems"},
{"bio_unit5_sub", "Kostra, oběh, dýchání a trávení.",
"Skeleton, circulation, breathing and digestion."},
{"bio_ex5_title", "Kvíz: člověk", "Quiz: the human body"},
{"bio_quiz5_head", "Ověřte si soustavy člověka", "Check the human body systems"},
{"bio_unit6", "Řízení, rozmnožování a imunita",
"Control, reproduction and immunity"},
{"bio_unit6_sub", "Nervy, hormony, oplození a obrana organismu.",
"Nerves, hormones, fertilisation and the body's defence."},
{"bio_ex6_title", "Kvíz: řízení", "Quiz: control"},
{"bio_quiz6_head", "Ověřte si řízení a imunitu", "Check control and immunity"},
{"bio_unit7", "Genetika", "Genetics"},
{"bio_unit7_sub", "Gen, alela, Mendel a pohlavní chromozomy.",
"The gene, the allele, Mendel and the sex chromosomes."},
{"bio_ex7_title", "Kvíz: genetika", "Quiz: genetics"},
{"bio_quiz7_head", "Ověřte si dědičnost", "Check inheritance"},
{"bio_unit8", "Ekologie", "Ecology"},
{"bio_unit8_sub", "Ekosystém, potravní vztahy a člověk v prostředí.",
"The ecosystem, feeding relationships and humans in the environment."},
{"bio_ex8_title", "Kvíz: ekologie", "Quiz: ecology"},
{"bio_quiz8_head", "Ověřte si vztahy v ekosystému",
"Check relationships in an ecosystem"},
{"czech_sub", "Vyberte oblast a začněte procvičovat.", {"czech_sub", "Vyberte oblast a začněte procvičovat.",
"Pick an area and start practicing."}, "Pick an area and start practicing."},
{"Literatura", "Literatura", "Literature"}, {"Literatura", "Literatura", "Literature"},
@ -2332,6 +2424,7 @@ void on2_lessons_apply_lang(void);
void on3_lessons_apply_lang(void); void on3_lessons_apply_lang(void);
void on4_lessons_apply_lang(void); void on4_lessons_apply_lang(void);
void lit_lessons_apply_lang(void); void lit_lessons_apply_lang(void);
void sci_lessons_apply_lang(void);
void net_rail_theme_reset(void); void net_rail_theme_reset(void);
void apply_language(void) { void apply_language(void) {
@ -2351,5 +2444,6 @@ void apply_language(void) {
on3_lessons_apply_lang(); on3_lessons_apply_lang();
on4_lessons_apply_lang(); on4_lessons_apply_lang();
lit_lessons_apply_lang(); lit_lessons_apply_lang();
sci_lessons_apply_lang();
search_apply_lang(); search_apply_lang();
} }

View file

@ -188,6 +188,46 @@ void draw_civics_icon(GtkDrawingArea *area, cairo_t *cr,
cairo_stroke(cr); cairo_stroke(cr);
} }
/* Round flask for Základy přírodopisných věd. */
void draw_science_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 top = height / 2.0 - s * 0.38;
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_cap(cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
cairo_set_line_width(cr, MAX(1.6, s * 0.07));
cairo_move_to(cr, cx - s * 0.08, top);
cairo_line_to(cr, cx - s * 0.08, top + s * 0.22);
cairo_line_to(cr, cx - s * 0.28, top + s * 0.58);
cairo_curve_to(cr, cx - s * 0.34, top + s * 0.78,
cx - s * 0.16, top + s * 0.82,
cx, top + s * 0.82);
cairo_curve_to(cr, cx + s * 0.16, top + s * 0.82,
cx + s * 0.34, top + s * 0.78,
cx + s * 0.28, top + s * 0.58);
cairo_line_to(cr, cx + s * 0.08, top + s * 0.22);
cairo_line_to(cr, cx + s * 0.08, top);
cairo_stroke(cr);
cairo_move_to(cr, cx - s * 0.14, top);
cairo_line_to(cr, cx + s * 0.14, top);
cairo_stroke(cr);
cairo_set_line_width(cr, MAX(1.4, s * 0.055));
cairo_move_to(cr, cx - s * 0.16, top + s * 0.62);
cairo_line_to(cr, cx + s * 0.16, top + s * 0.62);
cairo_stroke(cr);
}
/* Open book glyph, painted in the given colour. */ /* Open book glyph, painted in the given colour. */
static void book_paint(cairo_t *cr, int width, int height, Rgb c) { static void book_paint(cairo_t *cr, int width, int height, Rgb c) {
const double w = width; const double w = width;

View file

@ -389,6 +389,9 @@ static gboolean run_smoke_test(gpointer data) {
"on3map", "on4map", "on3map", "on4map",
"czechmap", "czechmap",
"lityears", "litmap", "lit2map", "lit3map", "lit4map", "lityears", "litmap", "lit2map", "lit3map", "lit4map",
"scimap", "chemmap", "biomap",
"chemunit1", "chemex1", "chemunit8", "chemex8",
"biounit1", "bioex1", "biounit8", "bioex8",
"litunit1", "litex1", "litunit8", "litex8", "litunit1", "litex1", "litunit8", "litex8",
"lit2unit1", "lit2ex1", "lit3unit1", "lit3ex1", "lit2unit1", "lit2ex1", "lit3unit1", "lit3ex1",
"lit4unit1", "lit4ex1", "lit4unit8", "lit4ex8", "lit4unit1", "lit4ex1", "lit4unit8", "lit4ex8",
@ -525,6 +528,11 @@ void activate(GtkApplication *app, gpointer user_data) {
add_on3_pages(main_stack); add_on3_pages(main_stack);
gtk_stack_add_named(main_stack, build_on4map_page(), "on4map"); gtk_stack_add_named(main_stack, build_on4map_page(), "on4map");
add_on4_pages(main_stack); add_on4_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);
gtk_stack_add_named(main_stack, build_biomap_page(), "biomap");
add_bio_pages(main_stack);
gtk_stack_add_named(main_stack, build_czechmap_page(), "czechmap"); gtk_stack_add_named(main_stack, build_czechmap_page(), "czechmap");
gtk_stack_add_named(main_stack, build_lityears_page(), "lityears"); gtk_stack_add_named(main_stack, build_lityears_page(), "lityears");
gtk_stack_add_named(main_stack, build_litmap_page(), "litmap"); gtk_stack_add_named(main_stack, build_litmap_page(), "litmap");

720
src/priroda.c Normal file
View file

@ -0,0 +1,720 @@
#include "graduately.h"
/* Základy přírodopisných věd: chemistry and biology share one subject.
* Each area is a vertical list of lessons, then slides and a quiz. */
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[SCI_N];
const char *sub_keys[SCI_N];
GtkWidget *nodes[SCI_N];
int note_last_w;
} SciTrack;
static SciTrack sci_tracks[2];
static GtkWidget *sci_rail;
static gboolean sci_ready;
static void sci_ensure(void) {
static const char *chem_units[] = {
"chem_unit1", "chem_unit2", "chem_unit3", "chem_unit4",
"chem_unit5", "chem_unit6", "chem_unit7", "chem_unit8",
};
static const char *chem_subs[] = {
"chem_unit1_sub", "chem_unit2_sub", "chem_unit3_sub", "chem_unit4_sub",
"chem_unit5_sub", "chem_unit6_sub", "chem_unit7_sub", "chem_unit8_sub",
};
static const char *bio_units[] = {
"bio_unit1", "bio_unit2", "bio_unit3", "bio_unit4",
"bio_unit5", "bio_unit6", "bio_unit7", "bio_unit8",
};
static const char *bio_subs[] = {
"bio_unit1_sub", "bio_unit2_sub", "bio_unit3_sub", "bio_unit4_sub",
"bio_unit5_sub", "bio_unit6_sub", "bio_unit7_sub", "bio_unit8_sub",
};
if (sci_ready)
return;
sci_tracks[0].map_page = "chemmap";
sci_tracks[0].back_page = "scimap";
sci_tracks[0].title_key = "Chemie";
sci_tracks[0].sub_key = "chem_sub";
sci_tracks[0].progress_path = &app_progress_chem;
sci_tracks[0].lessons = chem_lessons;
sci_tracks[0].note_last_w = -1;
sci_tracks[1].map_page = "biomap";
sci_tracks[1].back_page = "scimap";
sci_tracks[1].title_key = "Biologie";
sci_tracks[1].sub_key = "bio_sub";
sci_tracks[1].progress_path = &app_progress_bio;
sci_tracks[1].lessons = bio_lessons;
sci_tracks[1].note_last_w = -1;
for (int i = 0; i < SCI_N; i++) {
sci_tracks[0].unit_keys[i] = chem_units[i];
sci_tracks[0].sub_keys[i] = chem_subs[i];
sci_tracks[1].unit_keys[i] = bio_units[i];
sci_tracks[1].sub_keys[i] = bio_subs[i];
}
sci_ready = TRUE;
}
static void sci_save(SciTrack *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 < SCI_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 sci_load_one(SciTrack *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 < SCI_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 sci_load_progress(void) {
sci_ensure();
sci_load_one(&sci_tracks[0]);
sci_load_one(&sci_tracks[1]);
}
static void sci_refresh(SciTrack *T) {
for (int i = 0; i < SCI_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 sci_mark_done(SciTrack *T, int lesson_id) {
if (lesson_id < 0 || lesson_id >= SCI_N)
return;
if (T->lessons[lesson_id].done)
return;
T->lessons[lesson_id].done = TRUE;
sci_save(T);
sci_refresh(T);
refresh_stats_ui();
}
void progress_for_sci(ProgressSum *out) {
if (!out)
return;
sci_ensure();
for (int a = 0; a < 2; a++) {
SciTrack *T = &sci_tracks[a];
out->total_ex += SCI_N;
out->open_units += SCI_N;
for (int i = 0; i < SCI_N; i++) {
if (T->lessons[i].done) {
out->done_ex++;
out->done_units++;
}
}
}
}
static void sci_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 *sci_lesson_button(SciTrack *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(sci_open_unit),
&T->lessons[index]);
return btn;
}
static GtkWidget *sci_build_list(SciTrack *T) {
GtkWidget *page;
GtkWidget *scroll;
GtkWidget *list;
sci_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 < SCI_N; i++)
gtk_box_append(GTK_BOX(list), sci_lesson_button(T, i));
sci_refresh(T);
return page;
}
GtkWidget *build_chemmap_page(void) { return sci_build_list(&sci_tracks[0]); }
GtkWidget *build_biomap_page(void) { return sci_build_list(&sci_tracks[1]); }
#define SCI_BUBBLE SUB_BUBBLE
#define SCI_LABEL_W 180.0
#define SCI_CW 560
#define SCI_CH 280
static double sci_cx[2];
static double sci_cy[2];
static void draw_sci_rail(GtkDrawingArea *area, cairo_t *cr,
int width, int height, gpointer data) {
const Rgb rail = color_from_hex(app_theme.rail);
const Rgb slate = color_from_hex(app_theme.surface1);
(void)area;
(void)width;
(void)height;
(void)data;
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_paint(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
cairo_move_to(cr, sci_cx[0], sci_cy[0]);
cairo_line_to(cr, sci_cx[1], sci_cy[1]);
cairo_set_line_width(cr, 14);
cairo_set_source_rgb(cr, rail.r, rail.g, rail.b);
cairo_stroke(cr);
cairo_move_to(cr, sci_cx[0], sci_cy[0]);
cairo_line_to(cr, sci_cx[1], sci_cy[1]);
cairo_set_line_width(cr, 9);
cairo_set_source_rgb(cr, slate.r, slate.g, slate.b);
cairo_stroke(cr);
}
void sci_rail_theme_reset(void) {
if (sci_rail)
gtk_widget_queue_draw(sci_rail);
}
GtkWidget *build_scimap_page(void) {
GtkWidget *page;
GtkWidget *scroll;
GtkWidget *wrap;
GtkWidget *fixed;
GtkWidget *rail;
static const char *keys[] = {"Chemie", "Biologie"};
static const char *initials[] = {"C", "B"};
static const char *targets[] = {"chemmap", "biomap"};
sci_cx[0] = 160.0;
sci_cy[0] = 110.0;
sci_cx[1] = 400.0;
sci_cy[1] = 110.0;
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_hexpand(page, TRUE);
gtk_widget_set_vexpand(page, TRUE);
gtk_widget_set_margin_start(page, 32);
gtk_widget_set_margin_end(page, 32);
gtk_widget_set_margin_top(page, 24);
gtk_widget_set_margin_bottom(page, 24);
gtk_box_append(GTK_BOX(page),
top_bar("subjects", "Základy Přírodopisných věd", "sci_sub"));
scroll = gtk_scrolled_window_new();
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_widget_set_vexpand(scroll, TRUE);
gtk_widget_set_margin_top(scroll, 14);
gtk_box_append(GTK_BOX(page), scroll);
wrap = gtk_center_box_new();
gtk_widget_set_vexpand(wrap, TRUE);
gtk_widget_set_hexpand(wrap, TRUE);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), wrap);
fixed = gtk_fixed_new();
gtk_widget_set_halign(fixed, GTK_ALIGN_CENTER);
gtk_widget_set_valign(fixed, GTK_ALIGN_CENTER);
gtk_widget_set_size_request(fixed, SCI_CW, SCI_CH);
gtk_center_box_set_center_widget(GTK_CENTER_BOX(wrap), fixed);
rail = gtk_drawing_area_new();
gtk_widget_set_size_request(rail, SCI_CW, SCI_CH);
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(rail), draw_sci_rail,
NULL, NULL);
gtk_fixed_put(GTK_FIXED(fixed), rail, 0, 0);
sci_rail = rail;
for (int i = 0; i < 2; i++) {
GtkWidget *btn = gtk_button_new();
GtkWidget *letter = gtk_label_new(initials[i]);
GtkWidget *name = gtk_label_new(NULL);
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_size_request(btn, (int)SCI_BUBBLE, (int)SCI_BUBBLE);
gtk_widget_add_css_class(letter, "unit-number");
gtk_button_set_child(GTK_BUTTON(btn), letter);
g_object_set_data_full(G_OBJECT(btn), "target",
g_strdup(targets[i]), g_free);
g_signal_connect(btn, "clicked", G_CALLBACK(on_nav_clicked), NULL);
i18n_bind(name, keys[i], 0);
gtk_widget_set_size_request(name, (int)SCI_LABEL_W, -1);
gtk_widget_set_halign(name, GTK_ALIGN_CENTER);
gtk_label_set_justify(GTK_LABEL(name), GTK_JUSTIFY_CENTER);
gtk_label_set_wrap(GTK_LABEL(name), TRUE);
gtk_widget_add_css_class(name, "unit-name");
gtk_fixed_put(GTK_FIXED(fixed), btn, 0, 0);
gtk_fixed_put(GTK_FIXED(fixed), name, 0, 0);
gtk_fixed_move(GTK_FIXED(fixed), btn,
(int)(sci_cx[i] - SCI_BUBBLE / 2.0),
(int)(sci_cy[i] - SCI_BUBBLE / 2.0));
gtk_fixed_move(GTK_FIXED(fixed), name,
(int)(sci_cx[i] - SCI_LABEL_W / 2.0),
(int)(sci_cy[i] + SCI_BUBBLE / 2.0 + 10.0));
}
return page;
}
static void sci_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 sci_slide_prev(GtkButton *button, gpointer data) {
NetLesson *L = data;
(void)button;
if (!L || L->idx == 0)
return;
L->idx--;
sci_slide_apply(L);
}
static void sci_slide_next(GtkButton *button, gpointer data) {
NetLesson *L = data;
(void)button;
if (!L)
return;
if (L->idx + 1 < L->n_slides) {
L->idx++;
sci_slide_apply(L);
} else {
gtk_stack_set_visible_child_name(main_stack, L->ex_page);
}
}
static void sci_rescale(SciTrack *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 < SCI_N; i++)
net_rescale_lesson_notes(&T->lessons[i], body, head, kick);
}
static gboolean sci_note_tick(GtkWidget *w, GdkFrameClock *clock,
gpointer data) {
SciTrack *T = data;
int width = gtk_widget_get_allocated_width(w);
(void)clock;
if (width != T->note_last_w) {
T->note_last_w = width;
sci_rescale(T, width);
}
return G_SOURCE_CONTINUE;
}
GtkWidget *sci_unit_page(int area, NetLesson *L, const char *title_key,
const char *sub_key, const NetSlide *slides,
guint n_slides) {
SciTrack *T;
GtkWidget *page;
GtkWidget *scroll;
GtkWidget *nav;
sci_ensure();
if (area < 0 || area > 1)
area = 0;
T = &sci_tracks[area];
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, sci_note_tick, T, NULL);
T->note_last_w = -1;
sci_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(sci_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(sci_slide_next), L);
gtk_box_append(GTK_BOX(nav), L->next_btn);
L->idx = 0;
sci_slide_apply(L);
return page;
}
void sci_lessons_apply_lang(void) {
sci_ensure();
for (int a = 0; a < 2; a++) {
for (int i = 0; i < SCI_N; i++)
sci_slide_apply(&sci_tracks[a].lessons[i]);
}
}
typedef struct {
const ChoiceQ *qs;
int n;
int n_opts;
SciTrack *track;
int lesson_id;
GtkWidget *feedback;
GtkToggleButton **toggles;
GtkWidget **hints;
} SciMcqCtx;
static void sci_mcq_check(GtkButton *button, gpointer data) {
SciMcqCtx *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"));
sci_mark_done(ctx->track, ctx->lesson_id);
} else {
set_feedback(ctx->feedback, FALSE, tr("feedback_retry_short"));
}
}
GtkWidget *sci_mcq_page(int area, 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;
SciMcqCtx *ctx = g_new0(SciMcqCtx, 1);
int n_opts = qs[0].n_options;
sci_ensure();
if (area < 0 || area > 1)
area = 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 = &sci_tracks[area];
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(sci_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;
}

View file

@ -59,6 +59,7 @@ void load_progress(void) {
on3_load_progress(); on3_load_progress();
on4_load_progress(); on4_load_progress();
lit_load_progress(); lit_load_progress();
sci_load_progress();
} }
/* Redraw the exercise rails of every unit that already has a page. */ /* Redraw the exercise rails of every unit that already has a page. */

View file

@ -23,7 +23,7 @@ static GPtrArray *search_items;
static gboolean subject_open(int i) { static gboolean subject_open(int i) {
return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ
|| i == ON_SUBJ; || i == ON_SUBJ || i == SCI_SUBJ;
} }
static const char *subject_page(int i) { static const char *subject_page(int i) {
@ -37,6 +37,8 @@ static const char *subject_page(int i) {
return "czechmap"; return "czechmap";
if (i == ON_SUBJ) if (i == ON_SUBJ)
return "onyears"; return "onyears";
if (i == SCI_SUBJ)
return "scimap";
return "subjects"; return "subjects";
} }
@ -271,6 +273,22 @@ static void catalog_rebuild(void) {
g_snprintf(page, sizeof(page), "lit4ex%d", i); g_snprintf(page, sizeof(page), "lit4ex%d", i);
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz"); 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++) {
g_snprintf(key, sizeof(key), "chem_unit%d", i);
g_snprintf(page, sizeof(page), "chemunit%d", i);
catalog_add_key(key, "search_lesson", page, FALSE, 2, "chemie");
g_snprintf(page, sizeof(page), "chemex%d", i);
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz");
}
for (i = 1; i <= SCI_N; i++) {
g_snprintf(key, sizeof(key), "bio_unit%d", i);
g_snprintf(page, sizeof(page), "biounit%d", i);
catalog_add_key(key, "search_lesson", page, FALSE, 2, "biologie");
g_snprintf(page, sizeof(page), "bioex%d", i);
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz");
}
catalog_add_key("Mluvnice", "search_lesson", "mluvnice", FALSE, 1, catalog_add_key("Mluvnice", "search_lesson", "mluvnice", FALSE, 1,
"gramatika grammar"); "gramatika grammar");
catalog_add_key("Maturitní četba", "search_book", "readinglist", FALSE, 1, catalog_add_key("Maturitní četba", "search_book", "readinglist", FALSE, 1,

View file

@ -75,6 +75,8 @@ void refresh_stats_ui(void) {
progress_for_mluvnice(&all); progress_for_mluvnice(&all);
progress_for_lit(&all); progress_for_lit(&all);
} }
else if (s == SCI_SUBJ)
progress_for_sci(&all);
else else
progress_for_units(sub_unit_start[s], sub_unit_count[s], &all); progress_for_units(sub_unit_start[s], sub_unit_count[s], &all);
} }
@ -105,7 +107,7 @@ void refresh_stats_ui(void) {
ProgressSum sp = {0}; ProgressSum sp = {0};
gboolean has_content = (s == NET_SUBJ) || (s == HW_SUBJ) gboolean has_content = (s == NET_SUBJ) || (s == HW_SUBJ)
|| (s == ON_SUBJ) || (s == CZ_SUBJ) || (s == ON_SUBJ) || (s == CZ_SUBJ)
|| sub_unit_count[s] > 0; || (s == SCI_SUBJ) || sub_unit_count[s] > 0;
if (!count || !bar) if (!count || !bar)
continue; continue;
@ -119,6 +121,8 @@ void refresh_stats_ui(void) {
progress_for_mluvnice(&sp); progress_for_mluvnice(&sp);
progress_for_lit(&sp); progress_for_lit(&sp);
} }
else if (s == SCI_SUBJ)
progress_for_sci(&sp);
else else
progress_for_units(sub_unit_start[s], sub_unit_count[s], &sp); progress_for_units(sub_unit_start[s], sub_unit_count[s], &sp);
if (has_content) { if (has_content) {
@ -388,7 +392,8 @@ GtkWidget *build_stats_page(void) {
GtkWidget *count; GtkWidget *count;
GtkWidget *bar; GtkWidget *bar;
gboolean has_units = (s == NET_SUBJ) || (s == HW_SUBJ) gboolean has_units = (s == NET_SUBJ) || (s == HW_SUBJ)
|| (s == CZ_SUBJ) || sub_unit_count[s] > 0; || (s == CZ_SUBJ) || (s == ON_SUBJ)
|| (s == SCI_SUBJ) || sub_unit_count[s] > 0;
gboolean has_deutsch_units = sub_unit_count[s] > 0; gboolean has_deutsch_units = sub_unit_count[s] > 0;
subject = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); subject = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);

View file

@ -5,7 +5,7 @@
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Index 0 (Deutsch), NET_SUBJ (networks), HW_SUBJ (hardware), CZ_SUBJ /* Index 0 (Deutsch), NET_SUBJ (networks), HW_SUBJ (hardware), CZ_SUBJ
* (Czech) and ON_SUBJ (civics) are unlocked. */ * (Czech), ON_SUBJ (civics) and SCI_SUBJ (natural sciences) are unlocked. */
const char *sub_keys[NUM_SUBJECTS] = { const char *sub_keys[NUM_SUBJECTS] = {
"Deutsch", "Deutsch",
"Správa počítačových sítí", "Správa počítačových sítí",
@ -154,7 +154,7 @@ void draw_czech_flag(GtkDrawingArea *area, cairo_t *cr,
static gboolean subject_is_open(int i) { static gboolean subject_is_open(int i) {
return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ
|| i == ON_SUBJ; || i == ON_SUBJ || i == SCI_SUBJ;
} }
static GtkWidget *subject_icon(int i) { static GtkWidget *subject_icon(int i) {
@ -177,6 +177,9 @@ static GtkWidget *subject_icon(int i) {
else if (i == ON_SUBJ) else if (i == ON_SUBJ)
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area), gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area),
draw_civics_icon, NULL, NULL); draw_civics_icon, NULL, NULL);
else if (i == SCI_SUBJ)
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area),
draw_science_icon, NULL, NULL);
else else
return icon_area_new(draw_lock_icon, 0.3451, 0.3569, 0.4392, 22); return icon_area_new(draw_lock_icon, 0.3451, 0.3569, 0.4392, 22);
return area; return area;
@ -193,6 +196,8 @@ static const char *subject_target(int i) {
return "czechmap"; return "czechmap";
if (i == ON_SUBJ) if (i == ON_SUBJ)
return "onyears"; return "onyears";
if (i == SCI_SUBJ)
return "scimap";
return NULL; return NULL;
} }

View file

@ -432,5 +432,6 @@ void apply_theme(void) {
on4_rail_theme_reset(); on4_rail_theme_reset();
czech_rail_theme_reset(); czech_rail_theme_reset();
lit_rail_theme_reset(); lit_rail_theme_reset();
sci_rail_theme_reset();
book_rail_theme_reset(); book_rail_theme_reset();
} }

View file

@ -30,6 +30,8 @@ char *app_progress_lit;
char *app_progress_lit2; char *app_progress_lit2;
char *app_progress_lit3; char *app_progress_lit3;
char *app_progress_lit4; char *app_progress_lit4;
char *app_progress_chem;
char *app_progress_bio;
static char *dup_dirname(const char *path) { static char *dup_dirname(const char *path) {
char *tmp = g_strdup(path); char *tmp = g_strdup(path);
@ -176,6 +178,8 @@ static void set_progress_paths(const char *write_root) {
g_free(app_progress_lit2); g_free(app_progress_lit2);
g_free(app_progress_lit3); g_free(app_progress_lit3);
g_free(app_progress_lit4); g_free(app_progress_lit4);
g_free(app_progress_chem);
g_free(app_progress_bio);
app_progress_dir = g_build_filename(write_root, PROGRESS_DIR_NAME, NULL); app_progress_dir = g_build_filename(write_root, PROGRESS_DIR_NAME, NULL);
app_progress_u1 = g_build_filename(app_progress_dir, "unit1.conf", NULL); app_progress_u1 = g_build_filename(app_progress_dir, "unit1.conf", NULL);
@ -194,6 +198,8 @@ static void set_progress_paths(const char *write_root) {
app_progress_lit2 = g_build_filename(app_progress_dir, "lit2.conf", NULL); app_progress_lit2 = g_build_filename(app_progress_dir, "lit2.conf", NULL);
app_progress_lit3 = g_build_filename(app_progress_dir, "lit3.conf", NULL); app_progress_lit3 = g_build_filename(app_progress_dir, "lit3.conf", NULL);
app_progress_lit4 = g_build_filename(app_progress_dir, "lit4.conf", NULL); 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);
} }
/* Resolve read-only asset root, chdir there, and point progress at a writable /* Resolve read-only asset root, chdir there, and point progress at a writable