From 199b1cbf953bb7584d6b4406c96ff47d33b88c5f Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 20 Mar 2017 20:58:50 +0100 Subject: [PATCH] screen_utils: convert screen_get_yesno() to an awaitable --- src/FileBrowserPage.cxx | 6 +-- src/dialogs/YesNoDialog.cxx | 84 +++++++++++++++++++++++++++++++++++++ src/dialogs/YesNoDialog.hxx | 84 +++++++++++++++++++++++++++++++++++++ src/dialogs/meson.build | 2 + src/save_playlist.cxx | 12 +++++- src/screen_utils.cxx | 19 --------- src/screen_utils.hxx | 10 ----- 7 files changed, 183 insertions(+), 34 deletions(-) create mode 100644 src/dialogs/YesNoDialog.cxx create mode 100644 src/dialogs/YesNoDialog.hxx diff --git a/src/FileBrowserPage.cxx b/src/FileBrowserPage.cxx index 501524a7..2e95c4e0 100644 --- a/src/FileBrowserPage.cxx +++ b/src/FileBrowserPage.cxx @@ -12,10 +12,10 @@ #include "charset.hxx" #include "mpdclient.hxx" #include "filelist.hxx" -#include "screen_utils.hxx" #include "screen_client.hxx" #include "Command.hxx" #include "Options.hxx" +#include "dialogs/YesNoDialog.hxx" #include "ui/Bell.hxx" #include "lib/fmt/ToSpan.hxx" #include "util/UriUtil.hxx" @@ -243,8 +243,8 @@ FileBrowserPage::HandleDelete(struct mpdclient &c) snprintf(prompt, sizeof(prompt), _("Delete playlist %s?"), Utf8ToLocaleZ{GetUriFilename(mpd_playlist_get_path(playlist))}.c_str()); - bool confirmed = screen_get_yesno(screen, prompt, false); - if (!confirmed) + + if (co_await YesNoDialog{screen, prompt} != YesNoResult::YES) co_return; auto *connection = c.GetConnection(); diff --git a/src/dialogs/YesNoDialog.cxx b/src/dialogs/YesNoDialog.cxx new file mode 100644 index 00000000..9eba0bb6 --- /dev/null +++ b/src/dialogs/YesNoDialog.cxx @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright The Music Player Daemon Project + +#include "YesNoDialog.hxx" +#include "ui/Bell.hxx" +#include "ui/Keys.hxx" +#include "ui/Options.hxx" +#include "ui/Window.hxx" +#include "Styles.hxx" +#include "i18n.h" + +#include + +using std::string_view_literals::operator""sv; + +void +YesNoDialog::OnLeave(const Window window) noexcept +{ + noecho(); + curs_set(0); + + if (ui_options.enable_colors) + window.SetBackgroundStyle(Style::STATUS); +} + +void +YesNoDialog::OnCancel() noexcept +{ + SetResult(YesNoResult::CANCEL); +} + +void +YesNoDialog::Paint(const Window window) const noexcept +{ + if (ui_options.enable_colors) + window.SetBackgroundStyle(Style::INPUT); + + SelectStyle(window, Style::STATUS_ALERT); + window.String({0, 0}, prompt); + window.String(" ["sv); + window.String(YES_TRANSLATION); + window.Char('/'); + window.String(NO_TRANSLATION); + window.String("] "sv); + + SelectStyle(window, Style::INPUT); + window.ClearToEol(); + + echo(); + curs_set(1); +} + +static constexpr bool +IsCancelKey(int key) noexcept +{ + return key == KEY_CANCEL || key == KEY_SCANCEL || + key == KEY_CLOSE || + key == KEY_UNDO || + key == KEY_CTL('C') || + key == KEY_CTL('G') || + key == KEY_ESCAPE; +} + +bool +YesNoDialog::OnKey(Window, int key) +{ + /* NOTE: if one day a translator decides to use a multi-byte character + for one of the yes/no keys, we'll have to parse it properly */ + + key = tolower(key); + if (key == YES_TRANSLATION[0]) { + Hide(); + SetResult(YesNoResult::YES); + } else if (key == NO_TRANSLATION[0]) { + Hide(); + SetResult(YesNoResult::NO); + } else if (IsCancelKey(key)) { + Cancel(); + } else { + Bell(); + } + + return true; +} diff --git a/src/dialogs/YesNoDialog.hxx b/src/dialogs/YesNoDialog.hxx new file mode 100644 index 00000000..dbdd461a --- /dev/null +++ b/src/dialogs/YesNoDialog.hxx @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright The Music Player Daemon Project + +#pragma once + +#include "ModalDialog.hxx" +#include "co/AwaitableHelper.hxx" + +#include +#include + +enum class YesNoResult : int8_t { + CANCEL = -1, + NO = false, + YES = true, +}; + +/** + * A #ModalDialog that asks the user a yes/no question. + * + * This dialog is supposed to be awaited from a coroutine using + * co_await. It suspends the caller while waiting for user input. + */ +class YesNoDialog final : public ModalDialog { + const std::string_view prompt; + + std::coroutine_handle<> continuation; + + bool ready = false; + YesNoResult result; + + using Awaitable = Co::AwaitableHelper; + friend Awaitable; + +public: + /** + * @param _prompt the human-readable prompt to be displayed + * (including question mark if desired); the pointed-by memory + * is owned by the caller and must remain valid during the + * lifetime of this dialog + */ + YesNoDialog(ModalDock &_dock, std::string_view _prompt) noexcept + :ModalDialog(_dock), prompt(_prompt) + { + Show(); + } + + ~YesNoDialog() noexcept { + Hide(); + } + + /** + * Await completion of this dialog. + * + * @return a YesNoResult + */ + Awaitable operator co_await() noexcept { + return *this; + } + +private: + void SetResult(YesNoResult _result) noexcept { + result = _result; + ready = true; + + if (continuation) + continuation.resume(); + } + + bool IsReady() const noexcept { + return ready; + } + + YesNoResult TakeValue() noexcept { + return result; + } + +public: + /* virtual methodds from Modal */ + void OnLeave(Window window) noexcept override; + void OnCancel() noexcept override; + void Paint(Window window) const noexcept override; + bool OnKey(Window window, int key) override; +}; diff --git a/src/dialogs/meson.build b/src/dialogs/meson.build index 4181a30c..12358a4d 100644 --- a/src/dialogs/meson.build +++ b/src/dialogs/meson.build @@ -1,9 +1,11 @@ dialogs = static_library( 'dialogs', 'ModalDialog.cxx', + 'YesNoDialog.cxx', include_directories: inc, dependencies: [ ui_dep, + intl_dep, ], ) diff --git a/src/save_playlist.cxx b/src/save_playlist.cxx index f751801a..8bee80c7 100644 --- a/src/save_playlist.cxx +++ b/src/save_playlist.cxx @@ -9,7 +9,9 @@ #include "charset.hxx" #include "mpdclient.hxx" #include "Completion.hxx" +#include "screen.hxx" #include "screen_utils.hxx" +#include "dialogs/YesNoDialog.hxx" #include "co/InvokeTask.hxx" #include @@ -89,8 +91,14 @@ playlist_save(ScreenManager &screen, struct mpdclient &c, char prompt[256]; snprintf(prompt, sizeof(prompt), _("Replace %s?"), filename.c_str()); - bool replace = screen_get_yesno(screen, prompt, false); - if (!replace) + + if (co_await YesNoDialog{screen, prompt} != YesNoResult::YES) + co_return; + + /* obtain a new connection pointer after + resuming this coroutine */ + connection = c.GetConnection(); + if (connection == nullptr) co_return; if (!mpd_run_rm(connection, filename_utf8.c_str()) || diff --git a/src/screen_utils.cxx b/src/screen_utils.cxx index 8a83f389..cee33bec 100644 --- a/src/screen_utils.cxx +++ b/src/screen_utils.cxx @@ -75,25 +75,6 @@ screen_getch(ScreenManager &screen, const char *prompt) noexcept return key; } -bool -screen_get_yesno(ScreenManager &screen, const char *_prompt, bool def) noexcept -{ - /* NOTE: if one day a translator decides to use a multi-byte character - for one of the yes/no keys, we'll have to parse it properly */ - - char prompt[256]; - snprintf(prompt, sizeof(prompt), - "%s [%s/%s] ", _prompt, - YES_TRANSLATION, NO_TRANSLATION); - int key = tolower(screen_getch(screen, prompt)); - if (key == YES_TRANSLATION[0]) - return true; - else if (key == NO_TRANSLATION[0]) - return false; - else - return def; -} - std::string screen_readln(ScreenManager &screen, const char *prompt, const char *value, diff --git a/src/screen_utils.hxx b/src/screen_utils.hxx index 30895206..64aaab4c 100644 --- a/src/screen_utils.hxx +++ b/src/screen_utils.hxx @@ -13,16 +13,6 @@ class ScreenManager; int screen_getch(ScreenManager &screen, const char *prompt) noexcept; -/** - * display a prompt, wait for the user to press a key, and compare it with - * the default keys for "yes" and "no" (and their upper-case pendants). - * - * @returns true, if the user pressed the key for "yes"; false, if the user - * pressed the key for "no"; def otherwise - */ -bool -screen_get_yesno(ScreenManager &screen, const char *prompt, bool def) noexcept; - std::string screen_read_password(ScreenManager &screen, const char *prompt) noexcept;