Skip to main content

cadmus_core/view/settings_editor/kinds/
reader.rs

1//! Setting kinds for the Reader category.
2
3use super::{SettingData, SettingIdentity, SettingKind, WidgetKind};
4use crate::fl;
5use crate::geom::Rectangle;
6use crate::i18n::I18nDisplay;
7use crate::settings::{FileExtension, FinishedAction, RefreshRatePair, Settings};
8use crate::view::{Bus, EntryId, EntryKind, Event, ViewId};
9
10/// Reader finished action setting
11pub struct FinishedActionSetting;
12
13impl SettingKind for FinishedActionSetting {
14    fn identity(&self) -> SettingIdentity {
15        SettingIdentity::FinishedAction
16    }
17
18    fn label(&self, _settings: &Settings) -> String {
19        fl!("settings-reader-end-of-book-action")
20    }
21
22    fn fetch(&self, settings: &Settings) -> SettingData {
23        let current = settings.reader.finished;
24
25        let entries = vec![
26            EntryKind::RadioButton(
27                FinishedAction::Notify.to_i18n_string(),
28                EntryId::SetFinishedAction(FinishedAction::Notify),
29                current == FinishedAction::Notify,
30            ),
31            EntryKind::RadioButton(
32                FinishedAction::Close.to_i18n_string(),
33                EntryId::SetFinishedAction(FinishedAction::Close),
34                current == FinishedAction::Close,
35            ),
36            EntryKind::RadioButton(
37                FinishedAction::GoToNext.to_i18n_string(),
38                EntryId::SetFinishedAction(FinishedAction::GoToNext),
39                current == FinishedAction::GoToNext,
40            ),
41        ];
42
43        SettingData {
44            value: current.to_i18n_string(),
45            widget: WidgetKind::SubMenu(entries),
46        }
47    }
48
49    fn handle(
50        &self,
51        evt: &Event,
52        settings: &mut Settings,
53        _bus: &mut Bus,
54    ) -> (Option<String>, bool) {
55        if let Event::Select(EntryId::SetFinishedAction(action)) = evt {
56            settings.reader.finished = *action;
57            return (Some(action.to_i18n_string()), true);
58        }
59        (None, false)
60    }
61}
62
63/// Shows global refresh rate and opens the RefreshRateByKindEditor on tap.
64pub struct RefreshRateInfo;
65
66impl SettingKind for RefreshRateInfo {
67    fn identity(&self) -> SettingIdentity {
68        SettingIdentity::RefreshRate
69    }
70
71    fn label(&self, _settings: &Settings) -> String {
72        fl!("settings-reader-refresh-rate")
73    }
74
75    fn fetch(&self, settings: &Settings) -> SettingData {
76        let global = &settings.reader.refresh_rate.global;
77        let value = format!("{} / {}", global.regular, global.inverted);
78
79        SettingData {
80            value,
81            widget: WidgetKind::ActionLabel(Event::OpenRefreshRateEditor),
82        }
83    }
84
85    /// Updates the summary label when either global input is submitted.
86    ///
87    /// Settings are intentionally not written here. Each input (`RefreshRateRegularInput`,
88    /// `RefreshRateInvertedInput`) has its own [`SettingKind`] row (`RefreshRateRegularSetting`,
89    /// `RefreshRateInvertedSetting`) that owns the write.
90    fn handle(
91        &self,
92        evt: &Event,
93        settings: &mut Settings,
94        _bus: &mut Bus,
95    ) -> (Option<String>, bool) {
96        let global = &settings.reader.refresh_rate.global;
97        match evt {
98            Event::Submit(ViewId::RefreshRateRegularInput, text) => {
99                let regular = text.parse::<u8>().unwrap_or(global.regular);
100                (
101                    Some(fl!(
102                        "settings-reader-refresh-rate-summary",
103                        regular = regular,
104                        inverted = global.inverted
105                    )),
106                    false,
107                )
108            }
109            Event::Submit(ViewId::RefreshRateInvertedInput, text) => {
110                let inverted = text.parse::<u8>().unwrap_or(global.inverted);
111                (
112                    Some(fl!(
113                        "settings-reader-refresh-rate-summary",
114                        regular = global.regular,
115                        inverted = inverted
116                    )),
117                    false,
118                )
119            }
120            _ => (None, false),
121        }
122    }
123}
124
125/// Shows the refresh rate pair for a specific file extension.
126pub struct RefreshRateByKindInfo(pub FileExtension);
127
128impl SettingKind for RefreshRateByKindInfo {
129    fn identity(&self) -> SettingIdentity {
130        SettingIdentity::RefreshRateByKind(self.0.as_str().to_string())
131    }
132
133    fn label(&self, _settings: &Settings) -> String {
134        self.0.to_string().to_uppercase()
135    }
136
137    fn fetch(&self, settings: &Settings) -> SettingData {
138        let pair = settings
139            .reader
140            .refresh_rate
141            .by_kind
142            .get(self.0.as_str())
143            .cloned()
144            .unwrap_or(RefreshRatePair {
145                regular: 0,
146                inverted: 0,
147            });
148
149        let value = format!("{} / {}", pair.regular, pair.inverted);
150
151        SettingData {
152            value,
153            widget: WidgetKind::ActionLabel(Event::Select(EntryId::EditRefreshRateByKind(self.0))),
154        }
155    }
156
157    fn hold_event(&self, rect: Rectangle) -> Option<Event> {
158        let entries = vec![EntryKind::Command(
159            fl!("delete"),
160            EntryId::DeleteRefreshRateByKind(self.0),
161        )];
162
163        Some(Event::SubMenu(rect, entries))
164    }
165}
166
167/// The "regular" field of the global refresh rate pair.
168pub struct RefreshRateRegularSetting;
169
170impl SettingKind for RefreshRateRegularSetting {
171    fn identity(&self) -> SettingIdentity {
172        SettingIdentity::RefreshRateRegular
173    }
174
175    fn label(&self, _settings: &Settings) -> String {
176        fl!("settings-reader-refresh-rate-regular")
177    }
178
179    fn fetch(&self, settings: &Settings) -> SettingData {
180        let value = settings.reader.refresh_rate.global.regular.to_string();
181
182        SettingData {
183            value,
184            widget: WidgetKind::ActionLabel(Event::OpenNamedInput {
185                view_id: crate::view::ViewId::RefreshRateRegularInput,
186                label: fl!("settings-reader-refresh-rate-regular-input"),
187                max_chars: 3,
188                initial_text: settings.reader.refresh_rate.global.regular.to_string(),
189            }),
190        }
191    }
192
193    fn handle(
194        &self,
195        evt: &Event,
196        settings: &mut Settings,
197        _bus: &mut Bus,
198    ) -> (Option<String>, bool) {
199        if let Event::Submit(crate::view::ViewId::RefreshRateRegularInput, ref text) = evt {
200            if let Ok(v) = text.parse::<u8>() {
201                settings.reader.refresh_rate.global.regular = v;
202                return (Some(v.to_string()), true);
203            }
204        }
205
206        (None, false)
207    }
208}
209
210/// The "inverted" field of the global refresh rate pair.
211pub struct RefreshRateInvertedSetting;
212
213impl SettingKind for RefreshRateInvertedSetting {
214    fn identity(&self) -> SettingIdentity {
215        SettingIdentity::RefreshRateInverted
216    }
217
218    fn label(&self, _settings: &Settings) -> String {
219        fl!("settings-reader-refresh-rate-inverted")
220    }
221
222    fn fetch(&self, settings: &Settings) -> SettingData {
223        let value = settings.reader.refresh_rate.global.inverted.to_string();
224
225        SettingData {
226            value,
227            widget: WidgetKind::ActionLabel(Event::OpenNamedInput {
228                view_id: crate::view::ViewId::RefreshRateInvertedInput,
229                label: fl!("settings-reader-refresh-rate-inverted-input"),
230                max_chars: 3,
231                initial_text: settings.reader.refresh_rate.global.inverted.to_string(),
232            }),
233        }
234    }
235
236    fn handle(
237        &self,
238        evt: &Event,
239        settings: &mut Settings,
240        _bus: &mut Bus,
241    ) -> (Option<String>, bool) {
242        if let Event::Submit(crate::view::ViewId::RefreshRateInvertedInput, ref text) = evt {
243            if let Ok(v) = text.parse::<u8>() {
244                settings.reader.refresh_rate.global.inverted = v;
245                return (Some(v.to_string()), true);
246            }
247        }
248
249        (None, false)
250    }
251}
252
253/// The "regular" field of a per-kind refresh rate pair.
254pub struct RefreshRateByKindRegular(pub FileExtension);
255
256impl SettingKind for RefreshRateByKindRegular {
257    fn identity(&self) -> SettingIdentity {
258        SettingIdentity::RefreshRateByKindRegular(self.0.as_str().to_string())
259    }
260
261    fn label(&self, _settings: &Settings) -> String {
262        fl!("settings-reader-refresh-rate-regular")
263    }
264
265    fn fetch(&self, settings: &Settings) -> SettingData {
266        let regular = settings
267            .reader
268            .refresh_rate
269            .by_kind
270            .get(self.0.as_str())
271            .map(|p| p.regular)
272            .unwrap_or(0);
273
274        SettingData {
275            value: regular.to_string(),
276            widget: WidgetKind::ActionLabel(Event::OpenNamedInput {
277                view_id: crate::view::ViewId::RefreshRateByKindRegularInput,
278                label: fl!(
279                    "settings-reader-refresh-rate-by-kind-regular-input",
280                    ext = self.0.as_str()
281                ),
282                max_chars: 3,
283                initial_text: regular.to_string(),
284            }),
285        }
286    }
287
288    fn handle(
289        &self,
290        evt: &Event,
291        settings: &mut Settings,
292        _bus: &mut Bus,
293    ) -> (Option<String>, bool) {
294        if let Event::Submit(crate::view::ViewId::RefreshRateByKindRegularInput, ref text) = evt {
295            if let Ok(v) = text.parse::<u8>() {
296                let pair = settings
297                    .reader
298                    .refresh_rate
299                    .by_kind
300                    .entry(self.0.as_str().to_string())
301                    .or_insert(RefreshRatePair {
302                        regular: 0,
303                        inverted: 0,
304                    });
305                pair.regular = v;
306                return (Some(v.to_string()), true);
307            }
308        }
309
310        (None, false)
311    }
312}
313
314/// The "inverted" field of a per-kind refresh rate pair.
315pub struct RefreshRateByKindInverted(pub FileExtension);
316
317impl SettingKind for RefreshRateByKindInverted {
318    fn identity(&self) -> SettingIdentity {
319        SettingIdentity::RefreshRateByKindInverted(self.0.as_str().to_string())
320    }
321
322    fn label(&self, _settings: &Settings) -> String {
323        fl!("settings-reader-refresh-rate-inverted")
324    }
325
326    fn fetch(&self, settings: &Settings) -> SettingData {
327        let inverted = settings
328            .reader
329            .refresh_rate
330            .by_kind
331            .get(self.0.as_str())
332            .map(|p| p.inverted)
333            .unwrap_or(0);
334
335        SettingData {
336            value: inverted.to_string(),
337            widget: WidgetKind::ActionLabel(Event::OpenNamedInput {
338                view_id: crate::view::ViewId::RefreshRateByKindInvertedInput,
339                label: fl!(
340                    "settings-reader-refresh-rate-by-kind-inverted-input",
341                    ext = self.0.as_str()
342                ),
343                max_chars: 3,
344                initial_text: inverted.to_string(),
345            }),
346        }
347    }
348
349    fn handle(
350        &self,
351        evt: &Event,
352        settings: &mut Settings,
353        _bus: &mut Bus,
354    ) -> (Option<String>, bool) {
355        if let Event::Submit(crate::view::ViewId::RefreshRateByKindInvertedInput, ref text) = evt {
356            if let Ok(v) = text.parse::<u8>() {
357                let pair = settings
358                    .reader
359                    .refresh_rate
360                    .by_kind
361                    .entry(self.0.as_str().to_string())
362                    .or_insert(RefreshRatePair {
363                        regular: 0,
364                        inverted: 0,
365                    });
366                pair.inverted = v;
367                return (Some(v.to_string()), true);
368            }
369        }
370
371        (None, false)
372    }
373}
374
375#[cfg(test)]
376mod tests {
377    use super::*;
378    use crate::settings::{FinishedAction, Settings};
379    use crate::view::{Bus, EntryId, Event};
380    use std::collections::VecDeque;
381
382    mod finished_action_setting {
383        use super::*;
384
385        #[test]
386        fn handle_set_action_updates_settings() {
387            let setting = FinishedActionSetting;
388            let mut settings = Settings::default();
389            settings.reader.finished = FinishedAction::Close;
390            let mut bus: Bus = VecDeque::new();
391            let event = Event::Select(EntryId::SetFinishedAction(FinishedAction::GoToNext));
392
393            let result = setting.handle(&event, &mut settings, &mut bus);
394
395            assert!(result.0.is_some());
396            assert_eq!(settings.reader.finished, FinishedAction::GoToNext);
397        }
398
399        #[test]
400        fn handle_can_set_all_actions() {
401            let setting = FinishedActionSetting;
402            let mut settings = Settings::default();
403            let mut bus: Bus = VecDeque::new();
404
405            for action in [
406                FinishedAction::Notify,
407                FinishedAction::Close,
408                FinishedAction::GoToNext,
409            ] {
410                let event = Event::Select(EntryId::SetFinishedAction(action));
411                setting.handle(&event, &mut settings, &mut bus);
412                assert_eq!(settings.reader.finished, action);
413            }
414        }
415
416        #[test]
417        fn handle_returns_none_for_wrong_event() {
418            let setting = FinishedActionSetting;
419            let mut settings = Settings::default();
420            let mut bus: Bus = VecDeque::new();
421
422            let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
423
424            assert!(result.0.is_none());
425        }
426
427        #[test]
428        fn handle_returns_none_for_per_library_entry_id() {
429            let setting = FinishedActionSetting;
430            let mut settings = Settings::default();
431            let mut bus: Bus = VecDeque::new();
432            let event = Event::Select(EntryId::SetLibraryFinishedAction(0, FinishedAction::Notify));
433
434            let result = setting.handle(&event, &mut settings, &mut bus);
435
436            assert!(result.0.is_none());
437        }
438    }
439
440    mod refresh_rate_info {
441        use super::*;
442
443        #[test]
444        fn handle_regular_submit_updates_display_without_writing_settings() {
445            let setting = RefreshRateInfo;
446            let mut settings = Settings::default();
447            settings.reader.refresh_rate.global.regular = 5;
448            settings.reader.refresh_rate.global.inverted = 10;
449            let mut bus: Bus = VecDeque::new();
450
451            let event = Event::Submit(ViewId::RefreshRateRegularInput, "3".to_string());
452            let (display, handled) = setting.handle(&event, &mut settings, &mut bus);
453
454            assert_eq!(
455                display.as_deref(),
456                Some(
457                    fl!(
458                        "settings-reader-refresh-rate-summary",
459                        regular = 3u8,
460                        inverted = 10u8
461                    )
462                    .as_str()
463                )
464            );
465            assert!(!handled);
466            assert_eq!(settings.reader.refresh_rate.global.regular, 5);
467        }
468
469        #[test]
470        fn handle_inverted_submit_updates_display_without_writing_settings() {
471            let setting = RefreshRateInfo;
472            let mut settings = Settings::default();
473            settings.reader.refresh_rate.global.regular = 5;
474            settings.reader.refresh_rate.global.inverted = 10;
475            let mut bus: Bus = VecDeque::new();
476
477            let event = Event::Submit(ViewId::RefreshRateInvertedInput, "7".to_string());
478            let (display, handled) = setting.handle(&event, &mut settings, &mut bus);
479
480            assert_eq!(
481                display.as_deref(),
482                Some(
483                    fl!(
484                        "settings-reader-refresh-rate-summary",
485                        regular = 5u8,
486                        inverted = 7u8
487                    )
488                    .as_str()
489                )
490            );
491            assert!(!handled);
492            assert_eq!(settings.reader.refresh_rate.global.inverted, 10);
493        }
494
495        #[test]
496        fn handle_invalid_text_falls_back_to_current_value() {
497            let setting = RefreshRateInfo;
498            let mut settings = Settings::default();
499            settings.reader.refresh_rate.global.regular = 5;
500            settings.reader.refresh_rate.global.inverted = 10;
501            let mut bus: Bus = VecDeque::new();
502
503            let event = Event::Submit(ViewId::RefreshRateRegularInput, "bad".to_string());
504            let (display, _) = setting.handle(&event, &mut settings, &mut bus);
505
506            assert_eq!(
507                display.as_deref(),
508                Some(
509                    fl!(
510                        "settings-reader-refresh-rate-summary",
511                        regular = 5u8,
512                        inverted = 10u8
513                    )
514                    .as_str()
515                )
516            );
517        }
518
519        #[test]
520        fn handle_unrelated_event_returns_none() {
521            let setting = RefreshRateInfo;
522            let mut settings = Settings::default();
523            let mut bus: Bus = VecDeque::new();
524
525            let (display, handled) =
526                setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
527
528            assert!(display.is_none());
529            assert!(!handled);
530        }
531    }
532}