Skip to main content

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