1use super::{SettingData, SettingIdentity, SettingKind, WidgetKind};
4use crate::fl;
5use crate::i18n::I18nDisplay;
6use crate::settings::{IntermKind, IntermissionDisplay, Settings};
7use crate::view::{Bus, EntryId, EntryKind, Event};
8
9fn fetch_intermission(kind: IntermKind, settings: &Settings) -> SettingData {
11 let display = &settings.intermissions[kind];
12 let value = intermission_display_name(display);
13 let selected = selected_builtin_intermission(kind, display);
14 let is_selected = |candidate| selected.as_ref() == Some(&candidate);
15
16 let mut entries = vec![
17 EntryKind::RadioButton(
18 IntermissionDisplay::Logo.to_i18n_string(),
19 EntryId::SetIntermission(kind, IntermissionDisplay::Logo),
20 is_selected(IntermissionDisplay::Logo),
21 ),
22 EntryKind::RadioButton(
23 IntermissionDisplay::Cover.to_i18n_string(),
24 EntryId::SetIntermission(kind, IntermissionDisplay::Cover),
25 is_selected(IntermissionDisplay::Cover),
26 ),
27 EntryKind::RadioButton(
28 IntermissionDisplay::Blank.to_i18n_string(),
29 EntryId::SetIntermission(kind, IntermissionDisplay::Blank),
30 is_selected(IntermissionDisplay::Blank),
31 ),
32 EntryKind::RadioButton(
33 IntermissionDisplay::BlankInverted.to_i18n_string(),
34 EntryId::SetIntermission(kind, IntermissionDisplay::BlankInverted),
35 is_selected(IntermissionDisplay::BlankInverted),
36 ),
37 ];
38
39 if kind.supports_calendar() {
40 entries.push(EntryKind::RadioButton(
41 IntermissionDisplay::Calendar.to_i18n_string(),
42 EntryId::SetIntermission(kind, IntermissionDisplay::Calendar),
43 is_selected(IntermissionDisplay::Calendar),
44 ));
45 }
46
47 entries.push(EntryKind::Command(
48 fl!("settings-intermission-custom-image"),
49 EntryId::EditIntermissionImage(kind),
50 ));
51
52 SettingData {
53 value,
54 widget: WidgetKind::SubMenu(entries),
55 }
56}
57
58fn intermission_display_name(display: &IntermissionDisplay) -> String {
64 let i18n_display = fl!("settings-intermission-custom");
65 match display {
66 IntermissionDisplay::Image(path) => path
67 .file_name()
68 .and_then(|n| n.to_str())
69 .unwrap_or(i18n_display.as_str())
70 .to_string(),
71 _ => display.to_i18n_string(),
72 }
73}
74
75fn selected_builtin_intermission(
76 kind: IntermKind,
77 display: &IntermissionDisplay,
78) -> Option<IntermissionDisplay> {
79 match display {
80 IntermissionDisplay::Image(_) => None,
81 IntermissionDisplay::Calendar if !kind.supports_calendar() => {
82 Some(IntermissionDisplay::Logo)
83 }
84 _ => Some(display.clone()),
85 }
86}
87
88pub struct IntermissionSuspend;
90
91impl SettingKind for IntermissionSuspend {
92 fn identity(&self) -> SettingIdentity {
93 SettingIdentity::IntermissionSuspend
94 }
95
96 fn label(&self, _settings: &Settings) -> String {
97 fl!("settings-intermission-suspend-screen")
98 }
99
100 fn fetch(&self, settings: &Settings) -> SettingData {
101 fetch_intermission(IntermKind::Suspend, settings)
102 }
103
104 fn handle(
105 &self,
106 evt: &Event,
107 settings: &mut Settings,
108 _bus: &mut Bus,
109 ) -> (Option<String>, bool) {
110 if let Event::Select(EntryId::SetIntermission(IntermKind::Suspend, ref display)) = evt {
111 if !settings
112 .intermissions
113 .set_display(IntermKind::Suspend, display.clone())
114 {
115 return (None, true);
116 }
117
118 return (Some(intermission_display_name(display)), true);
119 }
120
121 if let Event::FileChooserClosed(Some(ref path)) = evt {
122 let display = IntermissionDisplay::Image(path.clone());
123 settings.intermissions[IntermKind::Suspend] = display.clone();
124 return (Some(intermission_display_name(&display)), true);
125 }
126
127 (None, false)
128 }
129
130 fn file_chooser_entry_id(&self) -> Option<EntryId> {
131 Some(EntryId::EditIntermissionImage(IntermKind::Suspend))
132 }
133}
134
135pub struct IntermissionPowerOff;
137
138impl SettingKind for IntermissionPowerOff {
139 fn identity(&self) -> SettingIdentity {
140 SettingIdentity::IntermissionPowerOff
141 }
142
143 fn label(&self, _settings: &Settings) -> String {
144 fl!("settings-intermission-power-off-screen")
145 }
146
147 fn fetch(&self, settings: &Settings) -> SettingData {
148 fetch_intermission(IntermKind::PowerOff, settings)
149 }
150
151 fn handle(
152 &self,
153 evt: &Event,
154 settings: &mut Settings,
155 _bus: &mut Bus,
156 ) -> (Option<String>, bool) {
157 if let Event::Select(EntryId::SetIntermission(IntermKind::PowerOff, ref display)) = evt {
158 if !settings
159 .intermissions
160 .set_display(IntermKind::PowerOff, display.clone())
161 {
162 return (None, true);
163 }
164
165 return (Some(intermission_display_name(display)), true);
166 }
167
168 if let Event::FileChooserClosed(Some(ref path)) = evt {
169 let display = IntermissionDisplay::Image(path.clone());
170 settings.intermissions[IntermKind::PowerOff] = display.clone();
171 return (Some(intermission_display_name(&display)), true);
172 }
173
174 (None, false)
175 }
176
177 fn file_chooser_entry_id(&self) -> Option<EntryId> {
178 Some(EntryId::EditIntermissionImage(IntermKind::PowerOff))
179 }
180}
181
182pub struct IntermissionShare;
184
185impl SettingKind for IntermissionShare {
186 fn identity(&self) -> SettingIdentity {
187 SettingIdentity::IntermissionShare
188 }
189
190 fn label(&self, _settings: &Settings) -> String {
191 fl!("settings-intermission-share-screen")
192 }
193
194 fn fetch(&self, settings: &Settings) -> SettingData {
195 fetch_intermission(IntermKind::Share, settings)
196 }
197
198 fn handle(
199 &self,
200 evt: &Event,
201 settings: &mut Settings,
202 _bus: &mut Bus,
203 ) -> (Option<String>, bool) {
204 if let Event::Select(EntryId::SetIntermission(IntermKind::Share, ref display)) = evt {
205 if !settings
206 .intermissions
207 .set_display(IntermKind::Share, display.clone())
208 {
209 return (None, true);
210 }
211
212 return (Some(intermission_display_name(display)), true);
213 }
214
215 if let Event::FileChooserClosed(Some(ref path)) = evt {
216 let display = IntermissionDisplay::Image(path.clone());
217 settings.intermissions[IntermKind::Share] = display.clone();
218 return (Some(intermission_display_name(&display)), true);
219 }
220
221 (None, false)
222 }
223
224 fn file_chooser_entry_id(&self) -> Option<EntryId> {
225 Some(EntryId::EditIntermissionImage(IntermKind::Share))
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232 use crate::settings::{IntermissionDisplay, Settings};
233 use crate::view::{Bus, EntryId, Event};
234 use std::collections::VecDeque;
235 use std::path::PathBuf;
236
237 mod intermission_suspend {
238 use super::*;
239 use crate::view::EntryKind;
240
241 #[test]
242 fn handle_set_intermission_updates_settings() {
243 let setting = IntermissionSuspend;
244 let mut settings = Settings::default();
245 let mut bus: Bus = VecDeque::new();
246 let event = Event::Select(EntryId::SetIntermission(
247 IntermKind::Suspend,
248 IntermissionDisplay::Cover,
249 ));
250
251 let result = setting.handle(&event, &mut settings, &mut bus);
252
253 assert!(result.0.is_some());
254 assert_eq!(
255 settings.intermissions[IntermKind::Suspend],
256 IntermissionDisplay::Cover
257 );
258 }
259
260 #[test]
261 fn handle_file_chooser_closed_updates_settings() {
262 let setting = IntermissionSuspend;
263 let mut settings = Settings::default();
264 let mut bus: Bus = VecDeque::new();
265 let path = PathBuf::from("/selected/image.jpg");
266 let event = Event::FileChooserClosed(Some(path));
267
268 let result = setting.handle(&event, &mut settings, &mut bus);
269
270 assert!(result.0.is_some());
271 assert_eq!(
272 settings.intermissions[IntermKind::Suspend],
273 IntermissionDisplay::Image(PathBuf::from("/selected/image.jpg"))
274 );
275 }
276
277 #[test]
278 fn handle_returns_none_for_wrong_kind() {
279 let setting = IntermissionSuspend;
280 let mut settings = Settings::default();
281 let mut bus: Bus = VecDeque::new();
282 let event = Event::Select(EntryId::SetIntermission(
283 IntermKind::PowerOff,
284 IntermissionDisplay::Cover,
285 ));
286
287 let result = setting.handle(&event, &mut settings, &mut bus);
288
289 assert!(result.0.is_none());
290 }
291
292 #[test]
293 fn handle_returns_none_for_cancelled_file_chooser() {
294 let setting = IntermissionSuspend;
295 let mut settings = Settings::default();
296 let mut bus: Bus = VecDeque::new();
297
298 let result = setting.handle(&Event::FileChooserClosed(None), &mut settings, &mut bus);
299
300 assert!(result.0.is_none());
301 }
302
303 #[test]
304 fn fetch_includes_calendar_option() {
305 let setting = IntermissionSuspend;
306 let settings = Settings::default();
307 let data = setting.fetch(&settings);
308
309 let WidgetKind::SubMenu(entries) = data.widget else {
310 panic!("expected submenu widget");
311 };
312
313 assert!(entries.iter().any(|entry| {
314 matches!(
315 entry,
316 EntryKind::RadioButton(
317 _,
318 EntryId::SetIntermission(
319 IntermKind::Suspend,
320 IntermissionDisplay::Calendar
321 ),
322 _
323 )
324 )
325 }));
326 }
327
328 #[test]
329 fn fetch_includes_blank_options() {
330 let setting = IntermissionSuspend;
331 let settings = Settings::default();
332 let data = setting.fetch(&settings);
333
334 let WidgetKind::SubMenu(entries) = data.widget else {
335 panic!("expected submenu widget");
336 };
337
338 assert!(entries.iter().any(|entry| {
339 matches!(
340 entry,
341 EntryKind::RadioButton(
342 _,
343 EntryId::SetIntermission(IntermKind::Suspend, IntermissionDisplay::Blank),
344 _
345 )
346 )
347 }));
348
349 assert!(entries.iter().any(|entry| {
350 matches!(
351 entry,
352 EntryKind::RadioButton(
353 _,
354 EntryId::SetIntermission(
355 IntermKind::Suspend,
356 IntermissionDisplay::BlankInverted
357 ),
358 _
359 )
360 )
361 }));
362 }
363 }
364
365 mod intermission_power_off {
366 use super::*;
367 use crate::view::EntryKind;
368
369 #[test]
370 fn handle_set_intermission_updates_settings() {
371 let setting = IntermissionPowerOff;
372 let mut settings = Settings::default();
373 let mut bus: Bus = VecDeque::new();
374 let event = Event::Select(EntryId::SetIntermission(
375 IntermKind::PowerOff,
376 IntermissionDisplay::Cover,
377 ));
378
379 let result = setting.handle(&event, &mut settings, &mut bus);
380
381 assert!(result.0.is_some());
382 assert_eq!(
383 settings.intermissions[IntermKind::PowerOff],
384 IntermissionDisplay::Cover
385 );
386 }
387
388 #[test]
389 fn handle_file_chooser_closed_updates_settings() {
390 let setting = IntermissionPowerOff;
391 let mut settings = Settings::default();
392 let mut bus: Bus = VecDeque::new();
393 let path = PathBuf::from("/selected/poweroff.png");
394 let event = Event::FileChooserClosed(Some(path));
395
396 let result = setting.handle(&event, &mut settings, &mut bus);
397
398 assert!(result.0.is_some());
399 assert_eq!(
400 settings.intermissions[IntermKind::PowerOff],
401 IntermissionDisplay::Image(PathBuf::from("/selected/poweroff.png"))
402 );
403 }
404
405 #[test]
406 fn handle_returns_none_for_wrong_kind() {
407 let setting = IntermissionPowerOff;
408 let mut settings = Settings::default();
409 let mut bus: Bus = VecDeque::new();
410 let event = Event::Select(EntryId::SetIntermission(
411 IntermKind::Suspend,
412 IntermissionDisplay::Logo,
413 ));
414
415 let result = setting.handle(&event, &mut settings, &mut bus);
416
417 assert!(result.0.is_none());
418 }
419
420 #[test]
421 fn handle_returns_none_for_cancelled_file_chooser() {
422 let setting = IntermissionPowerOff;
423 let mut settings = Settings::default();
424 let mut bus: Bus = VecDeque::new();
425
426 let result = setting.handle(&Event::FileChooserClosed(None), &mut settings, &mut bus);
427
428 assert!(result.0.is_none());
429 }
430
431 #[test]
432 fn handle_rejects_calendar_selection() {
433 let setting = IntermissionPowerOff;
434 let mut settings = Settings::default();
435 let mut bus: Bus = VecDeque::new();
436 let event = Event::Select(EntryId::SetIntermission(
437 IntermKind::PowerOff,
438 IntermissionDisplay::Calendar,
439 ));
440
441 let result = setting.handle(&event, &mut settings, &mut bus);
442
443 assert_eq!(result, (None, true));
444 assert_eq!(
445 settings.intermissions[IntermKind::PowerOff],
446 IntermissionDisplay::Logo
447 );
448 }
449
450 #[test]
451 fn handle_accepts_blank_selection() {
452 let setting = IntermissionPowerOff;
453 let mut settings = Settings::default();
454 let mut bus: Bus = VecDeque::new();
455 let event = Event::Select(EntryId::SetIntermission(
456 IntermKind::PowerOff,
457 IntermissionDisplay::Blank,
458 ));
459
460 let result = setting.handle(&event, &mut settings, &mut bus);
461
462 assert_eq!(result, (Some(fl!("settings-intermission-blank")), true));
463 assert_eq!(
464 settings.intermissions[IntermKind::PowerOff],
465 IntermissionDisplay::Blank
466 );
467 }
468
469 #[test]
470 fn fetch_excludes_calendar_option() {
471 let setting = IntermissionPowerOff;
472 let settings = Settings::default();
473 let data = setting.fetch(&settings);
474
475 let WidgetKind::SubMenu(entries) = data.widget else {
476 panic!("expected submenu widget");
477 };
478
479 assert!(!entries.iter().any(|entry| {
480 matches!(
481 entry,
482 EntryKind::RadioButton(
483 _,
484 EntryId::SetIntermission(
485 IntermKind::PowerOff,
486 IntermissionDisplay::Calendar
487 ),
488 _
489 )
490 )
491 }));
492 }
493 }
494
495 mod intermission_share {
496 use super::*;
497 use crate::view::EntryKind;
498
499 #[test]
500 fn handle_set_intermission_updates_settings() {
501 let setting = IntermissionShare;
502 let mut settings = Settings::default();
503 let mut bus: Bus = VecDeque::new();
504 let event = Event::Select(EntryId::SetIntermission(
505 IntermKind::Share,
506 IntermissionDisplay::Cover,
507 ));
508
509 let result = setting.handle(&event, &mut settings, &mut bus);
510
511 assert!(result.0.is_some());
512 assert_eq!(
513 settings.intermissions[IntermKind::Share],
514 IntermissionDisplay::Cover
515 );
516 }
517
518 #[test]
519 fn handle_file_chooser_closed_updates_settings() {
520 let setting = IntermissionShare;
521 let mut settings = Settings::default();
522 let mut bus: Bus = VecDeque::new();
523 let path = PathBuf::from("/selected/share.jpg");
524 let event = Event::FileChooserClosed(Some(path));
525
526 let result = setting.handle(&event, &mut settings, &mut bus);
527
528 assert!(result.0.is_some());
529 assert_eq!(
530 settings.intermissions[IntermKind::Share],
531 IntermissionDisplay::Image(PathBuf::from("/selected/share.jpg"))
532 );
533 }
534
535 #[test]
536 fn handle_returns_none_for_wrong_kind() {
537 let setting = IntermissionShare;
538 let mut settings = Settings::default();
539 let mut bus: Bus = VecDeque::new();
540 let event = Event::Select(EntryId::SetIntermission(
541 IntermKind::PowerOff,
542 IntermissionDisplay::Cover,
543 ));
544
545 let result = setting.handle(&event, &mut settings, &mut bus);
546
547 assert!(result.0.is_none());
548 }
549
550 #[test]
551 fn handle_returns_none_for_cancelled_file_chooser() {
552 let setting = IntermissionShare;
553 let mut settings = Settings::default();
554 let mut bus: Bus = VecDeque::new();
555
556 let result = setting.handle(&Event::FileChooserClosed(None), &mut settings, &mut bus);
557
558 assert!(result.0.is_none());
559 }
560
561 #[test]
562 fn handle_rejects_calendar_selection() {
563 let setting = IntermissionShare;
564 let mut settings = Settings::default();
565 let mut bus: Bus = VecDeque::new();
566 let event = Event::Select(EntryId::SetIntermission(
567 IntermKind::Share,
568 IntermissionDisplay::Calendar,
569 ));
570
571 let result = setting.handle(&event, &mut settings, &mut bus);
572
573 assert_eq!(result, (None, true));
574 assert_eq!(
575 settings.intermissions[IntermKind::Share],
576 IntermissionDisplay::Logo
577 );
578 }
579
580 #[test]
581 fn handle_accepts_blank_inverted_selection() {
582 let setting = IntermissionShare;
583 let mut settings = Settings::default();
584 let mut bus: Bus = VecDeque::new();
585 let event = Event::Select(EntryId::SetIntermission(
586 IntermKind::Share,
587 IntermissionDisplay::BlankInverted,
588 ));
589
590 let result = setting.handle(&event, &mut settings, &mut bus);
591
592 assert_eq!(
593 result,
594 (Some(fl!("settings-intermission-blank-inverted")), true)
595 );
596 assert_eq!(
597 settings.intermissions[IntermKind::Share],
598 IntermissionDisplay::BlankInverted
599 );
600 }
601
602 #[test]
603 fn fetch_excludes_calendar_option() {
604 let setting = IntermissionShare;
605 let settings = Settings::default();
606 let data = setting.fetch(&settings);
607
608 let WidgetKind::SubMenu(entries) = data.widget else {
609 panic!("expected submenu widget");
610 };
611
612 assert!(!entries.iter().any(|entry| {
613 matches!(
614 entry,
615 EntryKind::RadioButton(
616 _,
617 EntryId::SetIntermission(IntermKind::Share, IntermissionDisplay::Calendar),
618 _
619 )
620 )
621 }));
622 }
623 }
624}