1#![doc = "メッセージダイアログ及び確認ダイアログ"]
2use crate::i18n::get_locale_language;
3use iced::{window, Task};
4use native_dialog::{MessageAlert, MessageLevel};
5use std::fmt::Debug;
6use tracing_unwrap::ResultExt;
7
8pub trait UnwrapOrErrorAlert<T> {
10 fn unwrap_or_alert(self) -> T;
12}
13
14mod builder {
15 use crate::i18n::i18n;
16 use iced::wgpu::rwh::HasWindowHandle;
17 use native_dialog::{MessageAlert, MessageConfirm, MessageDialogBuilder, MessageLevel};
18
19 fn msg_dialog_builder(
20 owner: &Option<&dyn HasWindowHandle>,
21 (title, msg): (impl ToString, impl ToString),
22 level: MessageLevel,
23 ) -> MessageDialogBuilder {
24 let v = MessageDialogBuilder::default()
25 .set_level(level)
26 .set_title(title)
27 .set_text(msg);
28 match owner {
29 None => v,
30 Some(w) => v.set_owner(w),
31 }
32 }
33
34 fn msg_dialog_builder_i18n(
35 owner: &Option<&dyn HasWindowHandle>,
36 (title, msg): (&'static str, &'static str),
37 level: MessageLevel,
38 ) -> MessageDialogBuilder {
39 msg_dialog_builder(owner, (i18n(title), i18n(msg)), level)
40 }
41
42 pub fn alert_(
43 owner: &Option<&dyn HasWindowHandle>,
44 content: (impl ToString, impl ToString),
45 level: MessageLevel,
46 ) -> MessageAlert {
47 msg_dialog_builder(owner, content, level).alert()
48 }
49
50 pub fn alert_i18n_(
51 owner: &Option<&dyn HasWindowHandle>,
52 content: (&'static str, &'static str),
53 level: MessageLevel,
54 ) -> MessageAlert {
55 msg_dialog_builder_i18n(owner, content, level).alert()
56 }
57
58 pub fn confirm_(
59 owner: &Option<&dyn HasWindowHandle>,
60 content: (impl ToString, impl ToString),
61 level: MessageLevel,
62 ) -> MessageConfirm {
63 msg_dialog_builder(owner, content, level).confirm()
64 }
65
66 pub fn confirm_i18n_(
67 owner: &Option<&dyn HasWindowHandle>,
68 content: (&'static str, &'static str),
69 level: MessageLevel,
70 ) -> MessageConfirm {
71 msg_dialog_builder_i18n(owner, content, level).confirm()
72 }
73}
74
75trait DialogResult {
76 fn result(self) -> bool;
77}
78
79impl DialogResult for () {
80 fn result(self) -> bool {
81 false
82 }
83}
84
85impl DialogResult for bool {
86 fn result(self) -> bool {
87 self
88 }
89}
90
91macro_rules! dialog_spawner {
92 ($dialog_body:ident) => {
93 Task::future(async {
94 $dialog_body
95 .spawn()
96 .await
97 .map(DialogResult::result)
98 .ok_or_log()
99 .unwrap_or(false)
100 })
101 };
102}
103
104macro_rules! show_dialog {
105 ($owner_window_id:ident, $content:ident, $level:ident, $builder_method:path) => {
106 match $owner_window_id {
107 None => {
108 let v = $builder_method(&None, $content, $level);
109 dialog_spawner!(v)
110 }
111 Some(owner_window_id) => window::run(owner_window_id, move |w| {
112 $builder_method(&Some(w), $content, $level)
113 })
114 .then(|v| dialog_spawner!(v)),
115 }
116 };
117}
118
119#[tracing::instrument]
120pub fn alert<T>(
122 owner_window_id: Option<window::Id>,
123 (title, msg): (impl ToString + Debug, impl ToString + Debug),
124 level: MessageLevel,
125) -> Task<T>
126where
127 T: 'static + Send,
128{
129 let content = (title.to_string(), msg.to_string());
130 show_dialog!(owner_window_id, content, level, builder::alert_).discard()
131}
132
133#[tracing::instrument]
134pub fn alert_i18n<T>(
136 owner_window_id: Option<window::Id>,
137 content: (&'static str, &'static str),
138 level: MessageLevel,
139) -> Task<T>
140where
141 T: 'static + Send,
142{
143 show_dialog!(owner_window_id, content, level, builder::alert_i18n_).discard()
144}
145
146#[tracing::instrument]
147pub fn alert_show(
149 (title, msg): (impl ToString + Debug, impl ToString + Debug),
150 level: MessageLevel,
151) {
152 let content = (title.to_string(), msg.to_string());
153 builder::alert_(&None, content, level).show().ok_or_log();
154}
155
156#[tracing::instrument]
157pub async fn alert_spawn(
159 (title, msg): (impl ToString + Debug, impl ToString + Debug),
160 level: MessageLevel,
161) {
162 let content = (title.to_string(), msg.to_string());
163 builder::alert_(&None, content, level)
164 .spawn()
165 .await
166 .ok_or_log();
167}
168
169#[tracing::instrument]
170pub fn alert_i18n_show(content: (&'static str, &'static str), level: MessageLevel) {
172 builder::alert_i18n_(&None, content, level)
173 .show()
174 .ok_or_log();
175}
176
177#[tracing::instrument]
178pub async fn alert_i18n_spawn(content: (&'static str, &'static str), level: MessageLevel) {
180 builder::alert_i18n_(&None, content, level)
181 .spawn()
182 .await
183 .ok_or_log();
184}
185
186#[tracing::instrument]
187pub fn confirm(
189 owner_window_id: Option<window::Id>,
190 (title, msg): (impl ToString + Debug, impl ToString + Debug),
191 level: MessageLevel,
192) -> Task<bool> {
193 let content = (title.to_string(), msg.to_string());
194 show_dialog!(owner_window_id, content, level, builder::confirm_)
195}
196
197#[tracing::instrument]
198pub fn confirm_i18n(
200 owner_window_id: Option<window::Id>,
201 content: (&'static str, &'static str),
202 level: MessageLevel,
203) -> Task<bool> {
204 show_dialog!(owner_window_id, content, level, builder::confirm_i18n_)
205}
206
207#[tracing::instrument]
208pub fn confirm_show(
210 (title, msg): (impl ToString + Debug, impl ToString + Debug),
211 level: MessageLevel,
212) {
213 let content = (title.to_string(), msg.to_string());
214 builder::confirm_(&None, content, level).show().ok_or_log();
215}
216
217#[tracing::instrument]
218pub async fn confirm_spawn(
220 (title, msg): (impl ToString + Debug, impl ToString + Debug),
221 level: MessageLevel,
222) {
223 let content = (title.to_string(), msg.to_string());
224 builder::confirm_(&None, content, level)
225 .spawn()
226 .await
227 .ok_or_log();
228}
229
230#[tracing::instrument]
231pub fn confirm_i18n_show(content: (&'static str, &'static str), level: MessageLevel) {
233 builder::confirm_i18n_(&None, content, level)
234 .show()
235 .ok_or_log();
236}
237
238#[tracing::instrument]
239pub async fn confirm_i18n_spawn(content: (&'static str, &'static str), level: MessageLevel) {
241 builder::confirm_i18n_(&None, content, level)
242 .spawn()
243 .await
244 .ok_or_log();
245}
246
247const FATAL_JA: &str = "致命的なエラー";
248const FATAL_EN: &str = "Fatal error";
249
250const FATAL_INIT_DB_ERROR_MESSAGE_JA: &str = "アプリ設定用のデータベースが作成できません。
251アプリ用ディレクトリが決定できませんでした。
252PREHNITE_GLOBAL_DIR_PATH 環境変数を以下のように指定してください。
253例(実行時のディレクトリに作成): PREHNITE_GLOBAL_DIR_PATH = \".\"";
254const FATAL_INIT_DB_ERROR_MESSAGE_EN: &str = "Unable to create database for app settings.
255Could not determine directory for app.
256Specify the PREHNITE_GLOBAL_DIR_PATH environment variable as follows:
257Example (created in the runtime directory): PREHNITE_GLOBAL_DIR_PATH = \".\"";
258
259pub fn fatal_init_db_error() -> MessageAlert {
261 builder::alert_(
262 &None,
263 match get_locale_language().as_str() {
264 "ja" => (FATAL_JA, FATAL_INIT_DB_ERROR_MESSAGE_JA),
265 &_ => (FATAL_EN, FATAL_INIT_DB_ERROR_MESSAGE_EN),
266 },
267 MessageLevel::Error,
268 )
269}
270
271const FATAL_INIT_APP_ERROR_MESSAGE_JA: &str = "アプリケーションの初期化に失敗しました。";
272const FATAL_INIT_APP_ERROR_MESSAGE_EN: &str = "Application initialization failed.";
273pub fn fatal_initialize_app_error_db(e: impl Debug) -> MessageAlert {
275 let (title, err_msg) = match get_locale_language().as_str() {
276 "ja" => (FATAL_JA, FATAL_INIT_APP_ERROR_MESSAGE_JA),
277 &_ => (FATAL_EN, FATAL_INIT_APP_ERROR_MESSAGE_EN),
278 };
279 builder::alert_(
280 &None,
281 (title, format!("{err_msg}\nError:\n{:#?}", e).as_str()),
282 MessageLevel::Error,
283 )
284}
285
286const FATAL_INIT_SETTING_REGISTRY_ERROR_MESSAGE_JA: &str =
287 "設定レジストリの読み込みに失敗しました。";
288const FATAL_INIT_SETTING_REGISTRY_ERROR_MESSAGE_EN: &str = "Failed to load settings registry.";
289pub fn fatal_initialize_setting_registry_error() -> MessageAlert {
291 let (title, err_msg) = match get_locale_language().as_str() {
292 "ja" => (FATAL_JA, FATAL_INIT_SETTING_REGISTRY_ERROR_MESSAGE_JA),
293 &_ => (FATAL_EN, FATAL_INIT_SETTING_REGISTRY_ERROR_MESSAGE_EN),
294 };
295 builder::alert_(&None, (title, err_msg), MessageLevel::Error)
296}