cadmus_core/view/settings_editor/kinds/
general.rs

1//! Setting kinds for the General category.
2
3use super::{
4    InputSettingKind, SettingData, SettingIdentity, SettingKind, ToggleSettings, WidgetKind,
5};
6use crate::fl;
7use crate::i18n::I18nDisplay;
8use crate::settings::Settings;
9use crate::view::{Bus, EntryId, EntryKind, Event, ToggleEvent, ViewId};
10use anyhow::Error;
11use std::fs;
12use std::path::Path;
13
14/// Language and locale selection setting
15pub struct Locale;
16
17impl SettingKind for Locale {
18    fn identity(&self) -> SettingIdentity {
19        SettingIdentity::Locale
20    }
21
22    fn label(&self, _settings: &Settings) -> String {
23        fl!("settings-general-language")
24    }
25
26    fn fetch(&self, settings: &Settings) -> SettingData {
27        let current = settings.locale.as_ref().map(|l| l.to_string());
28        let display = current
29            .as_deref()
30            .unwrap_or(crate::i18n::DEFAULT_LOCALE)
31            .to_string();
32
33        let entries = crate::i18n::AVAILABLE_LOCALES
34            .iter()
35            .map(|&tag| {
36                let lang_id: Option<unic_langid::LanguageIdentifier> = tag.parse().ok();
37                EntryKind::RadioButton(
38                    tag.to_string(),
39                    EntryId::SetLocale(lang_id),
40                    current.as_deref() == Some(tag),
41                )
42            })
43            .collect::<Vec<_>>();
44
45        SettingData {
46            value: display,
47            widget: WidgetKind::SubMenu(entries),
48        }
49    }
50
51    fn handle(&self, evt: &Event, settings: &mut Settings, _bus: &mut Bus) -> Option<String> {
52        if let Event::Select(EntryId::SetLocale(ref locale)) = evt {
53            settings.locale = locale.clone();
54            crate::i18n::init(locale.as_ref());
55            let display = locale
56                .as_ref()
57                .map(|l| l.to_string())
58                .unwrap_or_else(|| crate::i18n::DEFAULT_LOCALE.to_string());
59            return Some(display);
60        }
61        None
62    }
63}
64
65/// Keyboard layout selection setting
66pub struct KeyboardLayout;
67
68impl SettingKind for KeyboardLayout {
69    fn identity(&self) -> SettingIdentity {
70        SettingIdentity::KeyboardLayout
71    }
72
73    fn label(&self, _settings: &Settings) -> String {
74        fl!("settings-general-keyboard-layout")
75    }
76
77    fn fetch(&self, settings: &Settings) -> SettingData {
78        let current_layout = settings.keyboard_layout.clone();
79        let available_layouts = get_available_layouts().unwrap_or_default();
80
81        let entries = available_layouts
82            .iter()
83            .map(|layout| {
84                EntryKind::RadioButton(
85                    layout.clone(),
86                    EntryId::SetKeyboardLayout(layout.clone()),
87                    current_layout == *layout,
88                )
89            })
90            .collect::<Vec<_>>();
91
92        SettingData {
93            value: current_layout,
94            widget: WidgetKind::SubMenu(entries),
95        }
96    }
97
98    fn handle(&self, evt: &Event, settings: &mut Settings, _bus: &mut Bus) -> Option<String> {
99        if let Event::Select(EntryId::SetKeyboardLayout(ref layout)) = evt {
100            settings.keyboard_layout = layout.clone();
101            return Some(layout.clone());
102        }
103        None
104    }
105}
106
107/// Auto suspend timeout setting
108pub struct AutoSuspend;
109
110impl SettingKind for AutoSuspend {
111    fn identity(&self) -> SettingIdentity {
112        SettingIdentity::AutoSuspend
113    }
114
115    fn label(&self, _settings: &Settings) -> String {
116        fl!("settings-general-auto-suspend")
117    }
118
119    fn fetch(&self, settings: &Settings) -> SettingData {
120        let value = if settings.auto_suspend == 0.0 {
121            fl!("settings-general-never")
122        } else {
123            format!("{:.1}", settings.auto_suspend)
124        };
125
126        SettingData {
127            value,
128            widget: WidgetKind::ActionLabel(Event::Select(EntryId::EditAutoSuspend)),
129        }
130    }
131
132    fn as_input_kind(&self) -> Option<&dyn InputSettingKind> {
133        Some(self)
134    }
135}
136
137impl InputSettingKind for AutoSuspend {
138    fn submit_view_id(&self) -> ViewId {
139        ViewId::AutoSuspendInput
140    }
141
142    fn open_entry_id(&self) -> EntryId {
143        EntryId::EditAutoSuspend
144    }
145
146    fn input_label(&self) -> String {
147        fl!("settings-general-auto-suspend-input")
148    }
149
150    fn input_max_chars(&self) -> usize {
151        10
152    }
153
154    fn current_text(&self, settings: &Settings) -> String {
155        if settings.auto_suspend == 0.0 {
156            "0".to_string()
157        } else {
158            format!("{:.1}", settings.auto_suspend)
159        }
160    }
161
162    fn apply_text(&self, text: &str, settings: &mut Settings) -> String {
163        if let Ok(value) = text.parse::<f32>() {
164            settings.auto_suspend = value;
165        }
166        if settings.auto_suspend == 0.0 {
167            fl!("settings-general-never")
168        } else {
169            format!("{:.1}", settings.auto_suspend)
170        }
171    }
172}
173
174/// Auto power off timeout setting
175pub struct AutoPowerOff;
176
177impl SettingKind for AutoPowerOff {
178    fn identity(&self) -> SettingIdentity {
179        SettingIdentity::AutoPowerOff
180    }
181
182    fn label(&self, _settings: &Settings) -> String {
183        fl!("settings-general-auto-power-off")
184    }
185
186    fn fetch(&self, settings: &Settings) -> SettingData {
187        let value = if settings.auto_power_off == 0.0 {
188            fl!("settings-general-never")
189        } else {
190            format!("{:.1}", settings.auto_power_off)
191        };
192
193        SettingData {
194            value,
195            widget: WidgetKind::ActionLabel(Event::Select(EntryId::EditAutoPowerOff)),
196        }
197    }
198
199    fn as_input_kind(&self) -> Option<&dyn InputSettingKind> {
200        Some(self)
201    }
202}
203
204impl InputSettingKind for AutoPowerOff {
205    fn submit_view_id(&self) -> ViewId {
206        ViewId::AutoPowerOffInput
207    }
208
209    fn open_entry_id(&self) -> EntryId {
210        EntryId::EditAutoPowerOff
211    }
212
213    fn input_label(&self) -> String {
214        fl!("settings-general-auto-power-off-input")
215    }
216
217    fn input_max_chars(&self) -> usize {
218        10
219    }
220
221    fn current_text(&self, settings: &Settings) -> String {
222        if settings.auto_power_off == 0.0 {
223            "0".to_string()
224        } else {
225            format!("{:.1}", settings.auto_power_off)
226        }
227    }
228
229    fn apply_text(&self, text: &str, settings: &mut Settings) -> String {
230        if let Ok(value) = text.parse::<f32>() {
231            settings.auto_power_off = value;
232        }
233        if settings.auto_power_off == 0.0 {
234            fl!("settings-general-never")
235        } else {
236            format!("{:.1}", settings.auto_power_off)
237        }
238    }
239}
240
241/// Sleep cover enable/disable toggle setting
242pub struct SleepCover;
243
244impl SettingKind for SleepCover {
245    fn identity(&self) -> SettingIdentity {
246        SettingIdentity::SleepCover
247    }
248
249    fn label(&self, _settings: &Settings) -> String {
250        fl!("settings-general-enable-sleep-cover")
251    }
252
253    fn fetch(&self, settings: &Settings) -> SettingData {
254        SettingData {
255            value: settings.sleep_cover.to_string(),
256            widget: WidgetKind::Toggle {
257                left_label: fl!("settings-general-toggle-on"),
258                right_label: fl!("settings-general-toggle-off"),
259                enabled: settings.sleep_cover,
260                tap_event: Event::Toggle(ToggleEvent::Setting(ToggleSettings::SleepCover)),
261            },
262        }
263    }
264
265    fn handle(&self, evt: &Event, settings: &mut Settings, _bus: &mut Bus) -> Option<String> {
266        if let Event::Toggle(ToggleEvent::Setting(ToggleSettings::SleepCover)) = evt {
267            settings.sleep_cover = !settings.sleep_cover;
268            return Some(settings.sleep_cover.to_string());
269        }
270        None
271    }
272}
273
274/// Auto share enable/disable toggle setting
275pub struct AutoShare;
276
277impl SettingKind for AutoShare {
278    fn identity(&self) -> SettingIdentity {
279        SettingIdentity::AutoShare
280    }
281
282    fn label(&self, _settings: &Settings) -> String {
283        fl!("settings-general-enable-auto-share")
284    }
285
286    fn fetch(&self, settings: &Settings) -> SettingData {
287        SettingData {
288            value: settings.auto_share.to_string(),
289            widget: WidgetKind::Toggle {
290                left_label: fl!("settings-general-toggle-on"),
291                right_label: fl!("settings-general-toggle-off"),
292                enabled: settings.auto_share,
293                tap_event: Event::Toggle(ToggleEvent::Setting(ToggleSettings::AutoShare)),
294            },
295        }
296    }
297
298    fn handle(&self, evt: &Event, settings: &mut Settings, _bus: &mut Bus) -> Option<String> {
299        if let Event::Toggle(ToggleEvent::Setting(ToggleSettings::AutoShare)) = evt {
300            settings.auto_share = !settings.auto_share;
301            return Some(settings.auto_share.to_string());
302        }
303        None
304    }
305}
306
307/// Button scheme (natural/inverted) toggle setting
308pub struct ButtonScheme;
309
310impl SettingKind for ButtonScheme {
311    fn identity(&self) -> SettingIdentity {
312        SettingIdentity::ButtonScheme
313    }
314
315    fn label(&self, _settings: &Settings) -> String {
316        fl!("settings-general-button-scheme")
317    }
318
319    fn fetch(&self, settings: &Settings) -> SettingData {
320        let enabled = settings.button_scheme == crate::settings::ButtonScheme::Natural;
321        SettingData {
322            value: settings.button_scheme.to_i18n_string(),
323            widget: WidgetKind::Toggle {
324                left_label: crate::settings::ButtonScheme::Natural.to_i18n_string(),
325                right_label: crate::settings::ButtonScheme::Inverted.to_i18n_string(),
326                enabled,
327                tap_event: Event::Toggle(ToggleEvent::Setting(ToggleSettings::ButtonScheme)),
328            },
329        }
330    }
331
332    fn handle(&self, evt: &Event, settings: &mut Settings, bus: &mut Bus) -> Option<String> {
333        let new_scheme = match evt {
334            Event::Toggle(ToggleEvent::Setting(ToggleSettings::ButtonScheme)) => {
335                match settings.button_scheme {
336                    crate::settings::ButtonScheme::Natural => {
337                        Some(crate::settings::ButtonScheme::Inverted)
338                    }
339                    crate::settings::ButtonScheme::Inverted => {
340                        Some(crate::settings::ButtonScheme::Natural)
341                    }
342                }
343            }
344            Event::Select(EntryId::SetButtonScheme(scheme)) => Some(*scheme),
345            _ => None,
346        };
347
348        if let Some(scheme) = new_scheme {
349            settings.button_scheme = scheme;
350            bus.push_back(Event::Select(EntryId::SetButtonScheme(scheme)));
351            return Some(settings.button_scheme.to_i18n_string());
352        }
353        None
354    }
355}
356
357/// Settings retention count setting
358pub struct SettingsRetention;
359
360impl SettingKind for SettingsRetention {
361    fn identity(&self) -> SettingIdentity {
362        SettingIdentity::SettingsRetention
363    }
364
365    fn label(&self, _settings: &Settings) -> String {
366        fl!("settings-general-settings-retention")
367    }
368
369    fn fetch(&self, settings: &Settings) -> SettingData {
370        SettingData {
371            value: settings.settings_retention.to_string(),
372            widget: WidgetKind::ActionLabel(Event::Select(EntryId::EditSettingsRetention)),
373        }
374    }
375
376    fn as_input_kind(&self) -> Option<&dyn InputSettingKind> {
377        Some(self)
378    }
379}
380
381impl InputSettingKind for SettingsRetention {
382    fn submit_view_id(&self) -> ViewId {
383        ViewId::SettingsRetentionInput
384    }
385
386    fn open_entry_id(&self) -> EntryId {
387        EntryId::EditSettingsRetention
388    }
389
390    fn input_label(&self) -> String {
391        fl!("settings-general-settings-retention")
392    }
393
394    fn input_max_chars(&self) -> usize {
395        3
396    }
397
398    fn current_text(&self, settings: &Settings) -> String {
399        settings.settings_retention.to_string()
400    }
401
402    fn apply_text(&self, text: &str, settings: &mut Settings) -> String {
403        if let Ok(value) = text.parse::<usize>() {
404            settings.settings_retention = value;
405        }
406        settings.settings_retention.to_string()
407    }
408}
409
410/// Scans the keyboard-layouts directory for available keyboard layouts
411fn get_available_layouts() -> Result<Vec<String>, Error> {
412    let layouts_dir = Path::new("keyboard-layouts");
413    let mut layouts = Vec::new();
414
415    if layouts_dir.exists() {
416        for entry in fs::read_dir(layouts_dir)? {
417            let entry = entry?;
418            let path = entry.path();
419
420            if path.extension().and_then(|s| s.to_str()) == Some("json") {
421                if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
422                    let layout_name = stem
423                        .chars()
424                        .enumerate()
425                        .map(|(i, c)| {
426                            if i == 0 {
427                                c.to_uppercase().collect::<String>()
428                            } else {
429                                c.to_string()
430                            }
431                        })
432                        .collect::<String>();
433                    layouts.push(layout_name);
434                }
435            }
436        }
437    }
438
439    layouts.sort();
440    Ok(layouts)
441}
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446    use crate::settings::Settings;
447    use crate::view::settings_editor::kinds::{InputSettingKind, ToggleSettings};
448    use crate::view::{Bus, EntryId, Event, ToggleEvent};
449    use std::collections::VecDeque;
450
451    mod locale {
452        use super::*;
453
454        #[test]
455        fn handle_set_locale_updates_settings() {
456            let setting = Locale;
457            let mut settings = Settings::default();
458            let mut bus: Bus = VecDeque::new();
459            let locale: Option<unic_langid::LanguageIdentifier> = Some("de-DE".parse().unwrap());
460            let event = Event::Select(EntryId::SetLocale(locale.clone()));
461
462            let result = setting.handle(&event, &mut settings, &mut bus);
463
464            assert!(result.is_some());
465            assert_eq!(result.unwrap(), "de-DE");
466            assert_eq!(settings.locale, locale);
467        }
468
469        #[test]
470        fn handle_returns_none_for_wrong_event() {
471            let setting = Locale;
472            let mut settings = Settings::default();
473            let mut bus: Bus = VecDeque::new();
474
475            let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
476
477            assert!(result.is_none());
478        }
479    }
480
481    mod keyboard_layout {
482        use super::*;
483
484        #[test]
485        fn handle_set_layout_updates_settings() {
486            let setting = KeyboardLayout;
487            let mut settings = Settings::default();
488            let mut bus: Bus = VecDeque::new();
489            let event = Event::Select(EntryId::SetKeyboardLayout("German".to_string()));
490
491            let result = setting.handle(&event, &mut settings, &mut bus);
492
493            assert!(result.is_some());
494            assert_eq!(result.unwrap(), "German");
495            assert_eq!(settings.keyboard_layout, "German");
496        }
497
498        #[test]
499        fn handle_returns_none_for_wrong_event() {
500            let setting = KeyboardLayout;
501            let mut settings = Settings::default();
502            let mut bus: Bus = VecDeque::new();
503
504            let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
505
506            assert!(result.is_none());
507        }
508    }
509
510    mod auto_suspend {
511        use super::*;
512
513        #[test]
514        fn apply_text_parses_and_updates() {
515            let setting = AutoSuspend;
516            let mut settings = Settings::default();
517
518            let display = setting.apply_text("60.0", &mut settings);
519
520            assert_eq!(display, "60.0");
521            assert_eq!(settings.auto_suspend, 60.0);
522        }
523
524        #[test]
525        fn apply_text_returns_never_for_zero() {
526            let setting = AutoSuspend;
527            let mut settings = Settings::default();
528
529            let display = setting.apply_text("0", &mut settings);
530
531            assert_eq!(display, "Never");
532            assert_eq!(settings.auto_suspend, 0.0);
533        }
534
535        #[test]
536        fn apply_text_ignores_invalid_input() {
537            let setting = AutoSuspend;
538            let mut settings = Settings {
539                auto_suspend: 30.0,
540                ..Default::default()
541            };
542
543            let display = setting.apply_text("invalid", &mut settings);
544
545            assert_eq!(settings.auto_suspend, 30.0);
546            assert_eq!(display, "30.0");
547        }
548    }
549
550    mod auto_power_off {
551        use super::*;
552
553        #[test]
554        fn apply_text_parses_and_updates() {
555            let setting = AutoPowerOff;
556            let mut settings = Settings::default();
557
558            let display = setting.apply_text("14.0", &mut settings);
559
560            assert_eq!(display, "14.0");
561            assert_eq!(settings.auto_power_off, 14.0);
562        }
563
564        #[test]
565        fn apply_text_returns_never_for_zero() {
566            let setting = AutoPowerOff;
567            let mut settings = Settings::default();
568
569            let display = setting.apply_text("0", &mut settings);
570
571            assert_eq!(display, "Never");
572            assert_eq!(settings.auto_power_off, 0.0);
573        }
574
575        #[test]
576        fn apply_text_ignores_invalid_input() {
577            let setting = AutoPowerOff;
578            let mut settings = Settings {
579                auto_power_off: 7.0,
580                ..Default::default()
581            };
582
583            let display = setting.apply_text("invalid", &mut settings);
584
585            assert_eq!(settings.auto_power_off, 7.0);
586            assert_eq!(display, "7.0");
587        }
588    }
589
590    mod sleep_cover {
591        use super::*;
592
593        #[test]
594        fn handle_toggle_event_toggles_value() {
595            let setting = SleepCover;
596            let mut settings = Settings {
597                sleep_cover: true,
598                ..Default::default()
599            };
600            let mut bus: Bus = VecDeque::new();
601            let event = Event::Toggle(ToggleEvent::Setting(ToggleSettings::SleepCover));
602
603            let result = setting.handle(&event, &mut settings, &mut bus);
604
605            assert!(result.is_some());
606            assert_eq!(result.unwrap(), "false");
607            assert!(!settings.sleep_cover);
608        }
609
610        #[test]
611        fn handle_returns_none_for_wrong_event() {
612            let setting = SleepCover;
613            let mut settings = Settings::default();
614            let mut bus: Bus = VecDeque::new();
615
616            let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
617
618            assert!(result.is_none());
619        }
620    }
621
622    mod auto_share {
623        use super::*;
624
625        #[test]
626        fn handle_toggle_event_toggles_value() {
627            let setting = AutoShare;
628            let mut settings = Settings {
629                auto_share: false,
630                ..Default::default()
631            };
632            let mut bus: Bus = VecDeque::new();
633            let event = Event::Toggle(ToggleEvent::Setting(ToggleSettings::AutoShare));
634
635            let result = setting.handle(&event, &mut settings, &mut bus);
636
637            assert!(result.is_some());
638            assert_eq!(result.unwrap(), "true");
639            assert!(settings.auto_share);
640        }
641
642        #[test]
643        fn handle_returns_none_for_wrong_event() {
644            let setting = AutoShare;
645            let mut settings = Settings::default();
646            let mut bus: Bus = VecDeque::new();
647
648            let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
649
650            assert!(result.is_none());
651        }
652    }
653
654    mod button_scheme {
655        use super::*;
656        use crate::settings::ButtonScheme;
657
658        #[test]
659        fn handle_toggle_event_switches_natural_to_inverted() {
660            let setting = ButtonScheme;
661            let mut settings = Settings {
662                button_scheme: ButtonScheme::Natural,
663                ..Default::default()
664            };
665            let mut bus: Bus = VecDeque::new();
666            let event = Event::Toggle(ToggleEvent::Setting(ToggleSettings::ButtonScheme));
667
668            let result = setting.handle(&event, &mut settings, &mut bus);
669
670            assert_eq!(settings.button_scheme, ButtonScheme::Inverted);
671            assert_eq!(bus.len(), 1);
672            assert!(result.is_some());
673        }
674
675        #[test]
676        fn handle_toggle_event_switches_inverted_to_natural() {
677            let setting = ButtonScheme;
678            let mut settings = Settings {
679                button_scheme: ButtonScheme::Inverted,
680                ..Default::default()
681            };
682            let mut bus: Bus = VecDeque::new();
683            let event = Event::Toggle(ToggleEvent::Setting(ToggleSettings::ButtonScheme));
684
685            let result = setting.handle(&event, &mut settings, &mut bus);
686
687            assert_eq!(settings.button_scheme, ButtonScheme::Natural);
688            assert_eq!(bus.len(), 1);
689            assert!(result.is_some());
690        }
691
692        #[test]
693        fn handle_set_scheme_event_applies_directly() {
694            let setting = ButtonScheme;
695            let mut settings = Settings {
696                button_scheme: ButtonScheme::Natural,
697                ..Default::default()
698            };
699            let mut bus: Bus = VecDeque::new();
700            let event = Event::Select(EntryId::SetButtonScheme(ButtonScheme::Inverted));
701
702            let result = setting.handle(&event, &mut settings, &mut bus);
703
704            assert_eq!(settings.button_scheme, ButtonScheme::Inverted);
705            assert!(result.is_some());
706        }
707
708        #[test]
709        fn handle_returns_none_for_wrong_event() {
710            let setting = ButtonScheme;
711            let mut settings = Settings::default();
712            let mut bus: Bus = VecDeque::new();
713
714            let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
715
716            assert!(result.is_none());
717        }
718    }
719
720    mod settings_retention {
721        use super::*;
722
723        #[test]
724        fn apply_text_parses_and_updates() {
725            let setting = SettingsRetention;
726            let mut settings = Settings::default();
727
728            let display = setting.apply_text("10", &mut settings);
729
730            assert_eq!(display, "10");
731            assert_eq!(settings.settings_retention, 10);
732        }
733
734        #[test]
735        fn apply_text_ignores_invalid_input() {
736            let setting = SettingsRetention;
737            let mut settings = Settings {
738                settings_retention: 3,
739                ..Default::default()
740            };
741
742            let display = setting.apply_text("invalid", &mut settings);
743
744            assert_eq!(settings.settings_retention, 3);
745            assert_eq!(display, "3");
746        }
747    }
748}