1use 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
10pub struct FinishedActionSetting;
12
13pub struct DitheredKindsSetting;
15
16impl SettingKind for DitheredKindsSetting {
17 fn identity(&self) -> SettingIdentity {
18 SettingIdentity::DitheredKinds
19 }
20
21 fn label(&self, _settings: &Settings) -> String {
22 fl!("settings-reader-dithered-kinds")
23 }
24
25 fn fetch(&self, settings: &Settings) -> SettingData {
26 let entries = FileExtension::all()
27 .iter()
28 .copied()
29 .map(|kind| {
30 EntryKind::CheckBox(
31 kind.to_string().to_uppercase(),
32 EntryId::ToggleDitheredKind(kind),
33 settings.reader.dithered_kinds.contains(&kind),
34 )
35 })
36 .collect();
37
38 SettingData {
39 value: kinds_summary(settings.reader.dithered_kinds.len()),
40 widget: WidgetKind::SubMenu(entries),
41 }
42 }
43
44 fn handle(
45 &self,
46 evt: &Event,
47 settings: &mut Settings,
48 _bus: &mut Bus,
49 ) -> (Option<String>, bool) {
50 if let Event::Select(EntryId::ToggleDitheredKind(kind)) = evt {
51 if !settings.reader.dithered_kinds.remove(kind) {
52 settings.reader.dithered_kinds.insert(*kind);
53 }
54
55 return (
56 Some(kinds_summary(settings.reader.dithered_kinds.len())),
57 true,
58 );
59 }
60
61 (None, false)
62 }
63
64 fn keep_menu_open(&self) -> bool {
65 true
66 }
67}
68
69fn kinds_summary(selected: usize) -> String {
70 format!("{selected} / {}", FileExtension::all().len())
71}
72
73impl SettingKind for FinishedActionSetting {
74 fn identity(&self) -> SettingIdentity {
75 SettingIdentity::FinishedAction
76 }
77
78 fn label(&self, _settings: &Settings) -> String {
79 fl!("settings-reader-end-of-book-action")
80 }
81
82 fn fetch(&self, settings: &Settings) -> SettingData {
83 let current = settings.reader.finished;
84
85 let entries = vec![
86 EntryKind::RadioButton(
87 FinishedAction::Notify.to_i18n_string(),
88 EntryId::SetFinishedAction(FinishedAction::Notify),
89 current == FinishedAction::Notify,
90 ),
91 EntryKind::RadioButton(
92 FinishedAction::Close.to_i18n_string(),
93 EntryId::SetFinishedAction(FinishedAction::Close),
94 current == FinishedAction::Close,
95 ),
96 EntryKind::RadioButton(
97 FinishedAction::GoToNext.to_i18n_string(),
98 EntryId::SetFinishedAction(FinishedAction::GoToNext),
99 current == FinishedAction::GoToNext,
100 ),
101 ];
102
103 SettingData {
104 value: current.to_i18n_string(),
105 widget: WidgetKind::SubMenu(entries),
106 }
107 }
108
109 fn handle(
110 &self,
111 evt: &Event,
112 settings: &mut Settings,
113 _bus: &mut Bus,
114 ) -> (Option<String>, bool) {
115 if let Event::Select(EntryId::SetFinishedAction(action)) = evt {
116 settings.reader.finished = *action;
117 return (Some(action.to_i18n_string()), true);
118 }
119 (None, false)
120 }
121}
122
123pub struct RefreshRateInfo;
125
126impl SettingKind for RefreshRateInfo {
127 fn identity(&self) -> SettingIdentity {
128 SettingIdentity::RefreshRate
129 }
130
131 fn label(&self, _settings: &Settings) -> String {
132 fl!("settings-reader-refresh-rate")
133 }
134
135 fn fetch(&self, settings: &Settings) -> SettingData {
136 let global = &settings.reader.refresh_rate.global;
137 let value = format!("{} / {}", global.regular, global.inverted);
138
139 SettingData {
140 value,
141 widget: WidgetKind::ActionLabel(Event::OpenRefreshRateEditor),
142 }
143 }
144
145 fn handle(
151 &self,
152 evt: &Event,
153 settings: &mut Settings,
154 _bus: &mut Bus,
155 ) -> (Option<String>, bool) {
156 let global = &settings.reader.refresh_rate.global;
157 match evt {
158 Event::Submit(ViewId::RefreshRateRegularInput, text) => {
159 let regular = text.parse::<u8>().unwrap_or(global.regular);
160 (
161 Some(fl!(
162 "settings-reader-refresh-rate-summary",
163 regular = regular,
164 inverted = global.inverted
165 )),
166 false,
167 )
168 }
169 Event::Submit(ViewId::RefreshRateInvertedInput, text) => {
170 let inverted = text.parse::<u8>().unwrap_or(global.inverted);
171 (
172 Some(fl!(
173 "settings-reader-refresh-rate-summary",
174 regular = global.regular,
175 inverted = inverted
176 )),
177 false,
178 )
179 }
180 _ => (None, false),
181 }
182 }
183}
184
185pub struct RefreshRateByKindInfo(pub FileExtension);
187
188impl SettingKind for RefreshRateByKindInfo {
189 fn identity(&self) -> SettingIdentity {
190 SettingIdentity::RefreshRateByKind(self.0.as_str().to_string())
191 }
192
193 fn label(&self, _settings: &Settings) -> String {
194 self.0.to_string().to_uppercase()
195 }
196
197 fn fetch(&self, settings: &Settings) -> SettingData {
198 let pair = settings
199 .reader
200 .refresh_rate
201 .by_kind
202 .get(self.0.as_str())
203 .cloned()
204 .unwrap_or(RefreshRatePair {
205 regular: 0,
206 inverted: 0,
207 });
208
209 let value = format!("{} / {}", pair.regular, pair.inverted);
210
211 SettingData {
212 value,
213 widget: WidgetKind::ActionLabel(Event::Select(EntryId::EditRefreshRateByKind(self.0))),
214 }
215 }
216
217 fn hold_event(&self, rect: Rectangle) -> Option<Event> {
218 let entries = vec![EntryKind::Command(
219 fl!("delete"),
220 EntryId::DeleteRefreshRateByKind(self.0),
221 )];
222
223 Some(Event::SubMenu(rect, entries))
224 }
225}
226
227pub struct RefreshRateRegularSetting;
229
230impl SettingKind for RefreshRateRegularSetting {
231 fn identity(&self) -> SettingIdentity {
232 SettingIdentity::RefreshRateRegular
233 }
234
235 fn label(&self, _settings: &Settings) -> String {
236 fl!("settings-reader-refresh-rate-regular")
237 }
238
239 fn fetch(&self, settings: &Settings) -> SettingData {
240 let value = settings.reader.refresh_rate.global.regular.to_string();
241
242 SettingData {
243 value,
244 widget: WidgetKind::ActionLabel(Event::OpenNamedInput {
245 view_id: crate::view::ViewId::RefreshRateRegularInput,
246 label: fl!("settings-reader-refresh-rate-regular-input"),
247 max_chars: 3,
248 initial_text: settings.reader.refresh_rate.global.regular.to_string(),
249 }),
250 }
251 }
252
253 fn handle(
254 &self,
255 evt: &Event,
256 settings: &mut Settings,
257 _bus: &mut Bus,
258 ) -> (Option<String>, bool) {
259 if let Event::Submit(crate::view::ViewId::RefreshRateRegularInput, text) = evt
260 && let Ok(v) = text.parse::<u8>()
261 {
262 settings.reader.refresh_rate.global.regular = v;
263 return (Some(v.to_string()), true);
264 }
265
266 (None, false)
267 }
268}
269
270pub struct RefreshRateInvertedSetting;
272
273impl SettingKind for RefreshRateInvertedSetting {
274 fn identity(&self) -> SettingIdentity {
275 SettingIdentity::RefreshRateInverted
276 }
277
278 fn label(&self, _settings: &Settings) -> String {
279 fl!("settings-reader-refresh-rate-inverted")
280 }
281
282 fn fetch(&self, settings: &Settings) -> SettingData {
283 let value = settings.reader.refresh_rate.global.inverted.to_string();
284
285 SettingData {
286 value,
287 widget: WidgetKind::ActionLabel(Event::OpenNamedInput {
288 view_id: crate::view::ViewId::RefreshRateInvertedInput,
289 label: fl!("settings-reader-refresh-rate-inverted-input"),
290 max_chars: 3,
291 initial_text: settings.reader.refresh_rate.global.inverted.to_string(),
292 }),
293 }
294 }
295
296 fn handle(
297 &self,
298 evt: &Event,
299 settings: &mut Settings,
300 _bus: &mut Bus,
301 ) -> (Option<String>, bool) {
302 if let Event::Submit(crate::view::ViewId::RefreshRateInvertedInput, text) = evt
303 && let Ok(v) = text.parse::<u8>()
304 {
305 settings.reader.refresh_rate.global.inverted = v;
306 return (Some(v.to_string()), true);
307 }
308
309 (None, false)
310 }
311}
312
313pub struct RefreshRateByKindRegular(pub FileExtension);
315
316impl SettingKind for RefreshRateByKindRegular {
317 fn identity(&self) -> SettingIdentity {
318 SettingIdentity::RefreshRateByKindRegular(self.0.as_str().to_string())
319 }
320
321 fn label(&self, _settings: &Settings) -> String {
322 fl!("settings-reader-refresh-rate-regular")
323 }
324
325 fn fetch(&self, settings: &Settings) -> SettingData {
326 let regular = settings
327 .reader
328 .refresh_rate
329 .by_kind
330 .get(self.0.as_str())
331 .map(|p| p.regular)
332 .unwrap_or(0);
333
334 SettingData {
335 value: regular.to_string(),
336 widget: WidgetKind::ActionLabel(Event::OpenNamedInput {
337 view_id: crate::view::ViewId::RefreshRateByKindRegularInput,
338 label: fl!(
339 "settings-reader-refresh-rate-by-kind-regular-input",
340 ext = self.0.as_str()
341 ),
342 max_chars: 3,
343 initial_text: regular.to_string(),
344 }),
345 }
346 }
347
348 fn handle(
349 &self,
350 evt: &Event,
351 settings: &mut Settings,
352 _bus: &mut Bus,
353 ) -> (Option<String>, bool) {
354 if let Event::Submit(crate::view::ViewId::RefreshRateByKindRegularInput, text) = evt
355 && let Ok(v) = text.parse::<u8>()
356 {
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.regular = v;
367 return (Some(v.to_string()), true);
368 }
369
370 (None, false)
371 }
372}
373
374pub struct RefreshRateByKindInverted(pub FileExtension);
376
377impl SettingKind for RefreshRateByKindInverted {
378 fn identity(&self) -> SettingIdentity {
379 SettingIdentity::RefreshRateByKindInverted(self.0.as_str().to_string())
380 }
381
382 fn label(&self, _settings: &Settings) -> String {
383 fl!("settings-reader-refresh-rate-inverted")
384 }
385
386 fn fetch(&self, settings: &Settings) -> SettingData {
387 let inverted = settings
388 .reader
389 .refresh_rate
390 .by_kind
391 .get(self.0.as_str())
392 .map(|p| p.inverted)
393 .unwrap_or(0);
394
395 SettingData {
396 value: inverted.to_string(),
397 widget: WidgetKind::ActionLabel(Event::OpenNamedInput {
398 view_id: crate::view::ViewId::RefreshRateByKindInvertedInput,
399 label: fl!(
400 "settings-reader-refresh-rate-by-kind-inverted-input",
401 ext = self.0.as_str()
402 ),
403 max_chars: 3,
404 initial_text: inverted.to_string(),
405 }),
406 }
407 }
408
409 fn handle(
410 &self,
411 evt: &Event,
412 settings: &mut Settings,
413 _bus: &mut Bus,
414 ) -> (Option<String>, bool) {
415 if let Event::Submit(crate::view::ViewId::RefreshRateByKindInvertedInput, text) = evt
416 && let Ok(v) = text.parse::<u8>()
417 {
418 let pair = settings
419 .reader
420 .refresh_rate
421 .by_kind
422 .entry(self.0.as_str().to_string())
423 .or_insert(RefreshRatePair {
424 regular: 0,
425 inverted: 0,
426 });
427 pair.inverted = v;
428 return (Some(v.to_string()), true);
429 }
430
431 (None, false)
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438 use crate::settings::{FileExtension, FinishedAction, Settings};
439 use crate::view::{Bus, EntryId, Event};
440 use std::collections::VecDeque;
441
442 mod finished_action_setting {
443 use super::*;
444
445 #[test]
446 fn handle_set_action_updates_settings() {
447 let setting = FinishedActionSetting;
448 let mut settings = Settings::default();
449 settings.reader.finished = FinishedAction::Close;
450 let mut bus: Bus = VecDeque::new();
451 let event = Event::Select(EntryId::SetFinishedAction(FinishedAction::GoToNext));
452
453 let result = setting.handle(&event, &mut settings, &mut bus);
454
455 assert!(result.0.is_some());
456 assert_eq!(settings.reader.finished, FinishedAction::GoToNext);
457 }
458
459 #[test]
460 fn handle_can_set_all_actions() {
461 let setting = FinishedActionSetting;
462 let mut settings = Settings::default();
463 let mut bus: Bus = VecDeque::new();
464
465 for action in [
466 FinishedAction::Notify,
467 FinishedAction::Close,
468 FinishedAction::GoToNext,
469 ] {
470 let event = Event::Select(EntryId::SetFinishedAction(action));
471 setting.handle(&event, &mut settings, &mut bus);
472 assert_eq!(settings.reader.finished, action);
473 }
474 }
475
476 mod dithered_kinds_setting {
477 use super::*;
478
479 #[test]
480 fn fetch_builds_checkbox_submenu_for_all_extensions() {
481 let setting = DitheredKindsSetting;
482 let settings = Settings::default();
483
484 let data = setting.fetch(&settings);
485
486 assert_eq!(
487 data.value,
488 kinds_summary(settings.reader.dithered_kinds.len())
489 );
490 let WidgetKind::SubMenu(entries) = data.widget else {
491 panic!("expected submenu widget");
492 };
493 assert_eq!(entries.len(), FileExtension::all().len());
494 assert!(matches!(
495 entries.first(),
496 Some(EntryKind::CheckBox(_, EntryId::ToggleDitheredKind(_), _))
497 ));
498 }
499
500 #[test]
501 fn handle_toggle_adds_and_removes_extensions() {
502 let setting = DitheredKindsSetting;
503 let mut settings = Settings::default();
504 settings.reader.dithered_kinds.remove(&FileExtension::Pdf);
505 let mut bus: Bus = VecDeque::new();
506
507 let add = setting.handle(
508 &Event::Select(EntryId::ToggleDitheredKind(FileExtension::Pdf)),
509 &mut settings,
510 &mut bus,
511 );
512 assert_eq!(
513 add.0,
514 Some(kinds_summary(settings.reader.dithered_kinds.len()))
515 );
516 assert!(settings.reader.dithered_kinds.contains(&FileExtension::Pdf));
517
518 let remove = setting.handle(
519 &Event::Select(EntryId::ToggleDitheredKind(FileExtension::Pdf)),
520 &mut settings,
521 &mut bus,
522 );
523 assert_eq!(
524 remove.0,
525 Some(kinds_summary(settings.reader.dithered_kinds.len()))
526 );
527 assert!(!settings.reader.dithered_kinds.contains(&FileExtension::Pdf));
528 }
529 }
530
531 #[test]
532 fn handle_returns_none_for_wrong_event() {
533 let setting = FinishedActionSetting;
534 let mut settings = Settings::default();
535 let mut bus: Bus = VecDeque::new();
536
537 let result = setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
538
539 assert!(result.0.is_none());
540 }
541
542 #[test]
543 fn handle_returns_none_for_per_library_entry_id() {
544 let setting = FinishedActionSetting;
545 let mut settings = Settings::default();
546 let mut bus: Bus = VecDeque::new();
547 let event = Event::Select(EntryId::SetLibraryFinishedAction(0, FinishedAction::Notify));
548
549 let result = setting.handle(&event, &mut settings, &mut bus);
550
551 assert!(result.0.is_none());
552 }
553 }
554
555 mod refresh_rate_info {
556 use super::*;
557
558 #[test]
559 fn handle_regular_submit_updates_display_without_writing_settings() {
560 let setting = RefreshRateInfo;
561 let mut settings = Settings::default();
562 settings.reader.refresh_rate.global.regular = 5;
563 settings.reader.refresh_rate.global.inverted = 10;
564 let mut bus: Bus = VecDeque::new();
565
566 let event = Event::Submit(ViewId::RefreshRateRegularInput, "3".to_string());
567 let (display, handled) = setting.handle(&event, &mut settings, &mut bus);
568
569 assert_eq!(
570 display.as_deref(),
571 Some(
572 fl!(
573 "settings-reader-refresh-rate-summary",
574 regular = 3u8,
575 inverted = 10u8
576 )
577 .as_str()
578 )
579 );
580 assert!(!handled);
581 assert_eq!(settings.reader.refresh_rate.global.regular, 5);
582 }
583
584 #[test]
585 fn handle_inverted_submit_updates_display_without_writing_settings() {
586 let setting = RefreshRateInfo;
587 let mut settings = Settings::default();
588 settings.reader.refresh_rate.global.regular = 5;
589 settings.reader.refresh_rate.global.inverted = 10;
590 let mut bus: Bus = VecDeque::new();
591
592 let event = Event::Submit(ViewId::RefreshRateInvertedInput, "7".to_string());
593 let (display, handled) = setting.handle(&event, &mut settings, &mut bus);
594
595 assert_eq!(
596 display.as_deref(),
597 Some(
598 fl!(
599 "settings-reader-refresh-rate-summary",
600 regular = 5u8,
601 inverted = 7u8
602 )
603 .as_str()
604 )
605 );
606 assert!(!handled);
607 assert_eq!(settings.reader.refresh_rate.global.inverted, 10);
608 }
609
610 #[test]
611 fn handle_invalid_text_falls_back_to_current_value() {
612 let setting = RefreshRateInfo;
613 let mut settings = Settings::default();
614 settings.reader.refresh_rate.global.regular = 5;
615 settings.reader.refresh_rate.global.inverted = 10;
616 let mut bus: Bus = VecDeque::new();
617
618 let event = Event::Submit(ViewId::RefreshRateRegularInput, "bad".to_string());
619 let (display, _) = setting.handle(&event, &mut settings, &mut bus);
620
621 assert_eq!(
622 display.as_deref(),
623 Some(
624 fl!(
625 "settings-reader-refresh-rate-summary",
626 regular = 5u8,
627 inverted = 10u8
628 )
629 .as_str()
630 )
631 );
632 }
633
634 #[test]
635 fn handle_unrelated_event_returns_none() {
636 let setting = RefreshRateInfo;
637 let mut settings = Settings::default();
638 let mut bus: Bus = VecDeque::new();
639
640 let (display, handled) =
641 setting.handle(&Event::Select(EntryId::About), &mut settings, &mut bus);
642
643 assert!(display.is_none());
644 assert!(!handled);
645 }
646 }
647}