pacenotes_installer\wizard\scene/
configuration.rs1use windows_sys::Win32::Foundation::{HWND, LPARAM, WPARAM};
2use windows_sys::Win32::UI::Controls as ctl;
3use windows_sys::Win32::UI::WindowsAndMessaging as wm;
4
5const LISTS: [(u16, u16); 3] = [
7 (
8 crate::wizard::control_ids::IDC_S5_LANG,
9 crate::wizard::control_ids::IDC_S5_LANG_LABEL,
10 ),
11 (
12 crate::wizard::control_ids::IDC_S5_VOICE,
13 crate::wizard::control_ids::IDC_S5_VOICE_LABEL,
14 ),
15 (
16 crate::wizard::control_ids::IDC_S5_STYLE,
17 crate::wizard::control_ids::IDC_S5_STYLE_LABEL,
18 ),
19];
20
21pub unsafe extern "system" fn configuration_page(
23 dialog: HWND,
24 message: u32,
25 wparam: WPARAM,
26 lparam: LPARAM,
27) -> isize {
28 match message {
29 wm::WM_INITDIALOG => {
30 crate::wizard::page::adopt_state(dialog, lparam);
31 for ((_, label), category) in LISTS.iter().zip(pacenotes_shared::CHOICE_CATEGORIES) {
32 crate::ui::set_text(dialog, *label, category_caption(category));
33 }
34 1
35 }
36 wm::WM_COMMAND => {
37 if crate::ui::hiword(wparam) == crate::ui::LBN_SELCHANGE {
38 on_choice_picked(dialog, crate::ui::loword(wparam));
39 }
40 1
41 }
42 wm::WM_NOTIFY => match crate::wizard::page::notify_code(lparam) {
43 Some(ctl::PSN_SETACTIVE) => {
44 crate::wizard::page::set_sheet_caption(dialog);
45 crate::wizard::page::set_header(dialog, crate::i18n::IDS_S5_TITLE);
46 crate::wizard::page::set_buttons(dialog, ctl::PSWIZB_BACK | ctl::PSWIZB_NEXT);
47 crate::wizard::page::set_next_text(dialog, crate::i18n::IDS_BTN_INSTALL);
48 populate_lists(dialog);
49 crate::wizard::page::reply(dialog, 0)
50 }
51 Some(ctl::PSN_WIZNEXT) => on_install(dialog),
52 Some(ctl::PSN_WIZBACK) => on_back(dialog),
53 Some(ctl::PSN_QUERYCANCEL) => crate::wizard::page::query_cancel(dialog),
54 _ => 0,
55 },
56 _ => 0,
57 }
58}
59
60unsafe fn populate_lists(dialog: HWND) {
62 let Some(state) = crate::wizard::page::state(dialog) else {
63 return;
64 };
65 let keep = state.config.selected_keys();
66 let categories = pacenotes_installer::schema::choice_categories(state.game_id());
67 state.config =
68 pacenotes_installer::choices::Configuration::from_tree(&state.tree, &keep, &categories);
69
70 let nothing_ticked = crate::i18n::load_string(crate::i18n::IDS_S5_NONE);
71 for (&(list_control, _), list) in LISTS.iter().zip(&state.config.lists) {
72 crate::ui::list_reset(dialog, list_control);
73 if list.options.is_empty() {
74 crate::ui::list_add(dialog, list_control, ¬hing_ticked);
76 crate::ui::enable(dialog, list_control, false);
77 continue;
78 }
79 crate::ui::enable(dialog, list_control, true);
80 for option in &list.options {
81 crate::ui::list_add(dialog, list_control, &option.label);
82 }
83 crate::ui::list_select(dialog, list_control, list.selected);
84 }
85}
86
87unsafe fn on_choice_picked(dialog: HWND, control: u16) {
88 let Some(index) = LISTS.iter().position(|&(list, _)| list == control) else {
89 return;
90 };
91 let selection = crate::ui::list_selection(dialog, control);
92 if let Some(state) = crate::wizard::page::state(dialog) {
93 state.config.select(index, selection);
94 }
95}
96
97unsafe fn on_back(dialog: HWND) -> isize {
100 let route = crate::wizard::page::state(dialog)
101 .map(|state| state.route)
102 .unwrap_or(crate::wizard::state::Route::Download);
103 let target = match route {
104 crate::wizard::state::Route::Download => crate::wizard::control_ids::IDD_S4_DOWNLOAD,
105 crate::wizard::state::Route::Configure | crate::wizard::state::Route::Restore => {
106 crate::wizard::control_ids::IDD_S3_FOLDER
107 }
108 };
109 crate::wizard::page::reply(dialog, target as isize)
110}
111
112unsafe fn on_install(dialog: HWND) -> isize {
114 let Some(state) = crate::wizard::page::state(dialog) else {
115 return crate::wizard::page::reply(dialog, 0);
116 };
117 let missing = state.config.missing();
118 if missing.is_empty() {
119 return crate::wizard::page::reply(dialog, 0);
120 }
121 let text = crate::i18n::load_string_arg(crate::i18n::IDS_S5_MISSING, &captions(&missing));
122 crate::ui::message_box_warning(dialog, &text);
123 crate::wizard::page::stay(dialog)
124}
125
126pub(super) unsafe fn has_something_to_pick(
130 dialog: HWND,
131 state: &crate::wizard::state::WizardState,
132) -> bool {
133 let choices = pacenotes_installer::schema::choice_categories(state.game_id());
134 let empty = state
135 .tree
136 .categories_not_ready(&choices, &|key| state.cache.holds(key));
137 if empty.is_empty() {
138 return true;
139 }
140 let text = crate::i18n::load_string_arg(crate::i18n::IDS_S5_NOT_READY, &captions(&empty));
141 crate::ui::message_box_warning(dialog, &text);
142 false
143}
144
145fn captions(categories: &[&str]) -> String {
147 let named: Vec<String> = categories
148 .iter()
149 .map(|category| {
150 crate::i18n::load_string(category_caption(category))
151 .trim_end_matches(':')
152 .to_string()
153 })
154 .collect();
155 named.join(", ")
156}
157
158pub(super) fn category_caption(category: &str) -> u16 {
160 match category {
161 pacenotes_shared::CATEGORY_LANGUAGE => crate::i18n::IDS_S5_LANGUAGE,
162 pacenotes_shared::CATEGORY_CODRIVER => crate::i18n::IDS_S5_VOICE,
163 pacenotes_shared::CATEGORY_STYLE => crate::i18n::IDS_S5_STYLE,
164 _ => crate::i18n::IDS_S5_TITLE,
165 }
166}