prehnite_core/util/
alert.rs

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
8/// 値を取り出すかダイアログを表示します。
9pub trait UnwrapOrErrorAlert<T> {
10    /// 値を取り出します。失敗した場合、ダイアログを表示します。
11    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]
120/// [`Task`]として非同期にメッセージダイアログを表示します。エラーが発生した場合は、ログを出力します。
121pub 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]
134/// [`Task`]として非同期にメッセージダイアログを表示します。`content` をi18nキーで指定します。エラーが発生した場合は、ログを出力します。
135pub 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]
147/// 即座にメッセージダイアログを表示します。エラーが発生した場合は、ログを出力します。
148pub 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]
157/// 非同期にメッセージダイアログ表示します。エラーが発生した場合は、ログを出力します。
158pub 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]
170/// 即座にメッセージダイアログを表示します。`content`をi18nキーで指定します。エラーが発生した場合は、ログを出力します。
171pub 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]
178/// 非同期にメッセージダイアログを表示します。`content`をi18nキーで指定します。エラーが発生した場合は、ログを出力します。
179pub 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]
187/// [`Task`]として非同期に確認ダイアログを表示します。エラーが発生した場合は、ログを出力します。
188pub 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]
198/// [`Task`]として非同期に確認ダイアログを表示します。`content` をi18nキーで指定します。エラーが発生した場合は、ログを出力します。
199pub 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]
208/// 即座に確認ダイアログを表示します。エラーが発生した場合は、ログを出力します。
209pub 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]
218/// 非同期に確認ダイアログを表示します。エラーが発生した場合は、ログを出力します。
219pub 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]
231/// 即座に確認ダイアログを表示します。`content` をi18nキーで指定します。エラーが発生した場合は、ログを出力します。
232pub 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]
239/// 非同期に確認ダイアログを表示します。`content` をi18nキーで指定します。エラーが発生した場合は、ログを出力します。
240pub 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
259/// データベースの初期化エラーを表すダイアログ
260pub 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.";
273/// アプリの致命的な初期化エラーを表すダイアログ
274pub 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.";
289/// 設定レジストリの読み込みエラーを表すダイアログ
290pub 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}