From 4126042f269b1021e53cae3b9742d87ca946fa2f Mon Sep 17 00:00:00 2001 From: Matthias Meulien Date: Wed, 8 Nov 2023 18:11:56 +0100 Subject: [PATCH] Implement alert viewer Refs: #66 --- NEWS.md | 4 ++ po/cs.po | 4 +- po/fr.po | 4 +- po/pl.po | 4 +- po/taranis.pot | 2 +- src/alerts.cc | 148 +++++++++++++++++++++++++++------------ src/alerts.h | 54 ++++++++++++-- src/app.cc | 2 +- src/events.cc | 4 +- src/events.h | 2 +- src/hourlyforecastbox.cc | 11 +-- src/ui.cc | 26 ++++--- src/ui.h | 4 +- 13 files changed, 184 insertions(+), 85 deletions(-) diff --git a/NEWS.md b/NEWS.md index 7a22dfb..2830428 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,6 +12,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Changed +- Alerts were displayed in a standard dialog. A widget dedicated to + alert viewing has been implemented to improve + readability. [#66](https://github.com/orontee/taranis/issues/66) + ### Removed ## [ 1.6.0 ] - 2023-11-01 diff --git a/po/cs.po b/po/cs.po index 0b7426c..4f5340d 100644 --- a/po/cs.po +++ b/po/cs.po @@ -30,8 +30,8 @@ msgid "Credits" msgstr "Poděkování" #: src/alerts.cc:43 -msgid "Alert" -msgstr "Výstraha" +msgid "ALERT" +msgstr "VÝSTRAHA" #: src/alerts.cc:52 msgid "Start" diff --git a/po/fr.po b/po/fr.po index 313a13e..3e93016 100644 --- a/po/fr.po +++ b/po/fr.po @@ -30,8 +30,8 @@ msgid "Credits" msgstr "Crédits" #: src/alerts.cc:43 -msgid "Alert" -msgstr "Alerte" +msgid "ALERT" +msgstr "ALERTE" #: src/alerts.cc:52 msgid "Start" diff --git a/po/pl.po b/po/pl.po index 0e20d33..174cb1c 100644 --- a/po/pl.po +++ b/po/pl.po @@ -30,8 +30,8 @@ msgid "Credits" msgstr "Kredyty" #: src/alerts.cc:43 -msgid "Alert" -msgstr "Alert" +msgid "ALERT" +msgstr "ALERT" #: src/alerts.cc:52 msgid "Start" diff --git a/po/taranis.pot b/po/taranis.pot index f73ea12..10a198c 100644 --- a/po/taranis.pot +++ b/po/taranis.pot @@ -28,7 +28,7 @@ msgid "Credits" msgstr "" #: src/alerts.cc:43 -msgid "Alert" +msgid "ALERT" msgstr "" #: src/alerts.cc:52 diff --git a/src/alerts.cc b/src/alerts.cc index 7859f3c..555e79c 100644 --- a/src/alerts.cc +++ b/src/alerts.cc @@ -1,72 +1,130 @@ #include "alerts.h" #include +#include #include +#include -#include "events.h" +#include "inkview.h" +#include "model.h" #include "util.h" -#define OK_BUTTON_INDEX 0 -#define NEXT_ALERT_BUTTON_INDEX 1 - namespace taranis { -static size_t alert_index{0}; -static size_t alert_count{0}; - -inline bool is_next_alert_button_at(int button_index) { - return (alert_index + 1 < alert_count) ? button_index == 1 : false; -} +void AlertsButton::on_clicked() { this->viewer->open(); } -void AlertsButton::open_dialog_maybe() { - alert_count = this->model->alerts.size(); +AlertViewer::AlertViewer(std::shared_ptr model, + std::shared_ptr fonts) + : Widget{0, 0, ScreenWidth(), ScreenHeight()}, model{model}, fonts{fonts} {} - if (alert_count == 0) { - return; - } - if (alert_index >= alert_count) { +void AlertViewer::open() { + if (this->alert_count() == 0 or this->alert_index >= this->alert_count()) { + this->hide(); return; } + this->visible = true; + const auto event_handler = GetEventHandler(); + SendEvent(event_handler, EVT_SHOW, 0, 0); +} + +void AlertViewer::hide() { + this->visible = false; + this->alert_index = 0; + + const auto event_handler = GetEventHandler(); + SendEvent(event_handler, EVT_SHOW, 0, 0); +} + +void AlertViewer::do_paint() { + const auto &alert = this->model->alerts.at(this->alert_index); + + this->update_title(); + this->update_description(alert); - const auto &alert = this->model->alerts.at(alert_index); + const auto default_font = this->fonts->get_small_font(); + SetFont(default_font.get(), BLACK); - const auto dialog_title = - not alert.event.empty() ? alert.event : GetLangText("Alert"); - // 😕 Looks like dialog title isn't displayed at all! + const auto content_width = + (this->bounding_box.w - 2 * AlertViewer::horizontal_padding); + const auto title_height = + std::max(TextRectHeight(content_width, this->title.c_str(), + ALIGN_CENTER | TO_UPPER), + 2 * default_font->height); + DrawTextRect(AlertViewer::horizontal_padding, AlertViewer::vertical_padding, + content_width, title_height, this->title.c_str(), + ALIGN_CENTER | TO_UPPER); + + DrawLine(0, title_height, ScreenWidth(), title_height, BLACK); + + const auto alert_title_start_y = title_height + AlertViewer::vertical_padding; + auto description_start_y = alert_title_start_y; - std::stringstream alert_text; if (not alert.event.empty()) { - alert_text << alert.event << std::endl << std::endl; + const auto bold_font = this->fonts->get_small_bold_font(); + SetFont(bold_font.get(), BLACK); + + const auto alert_title = alert.event; + const auto alert_title_height = + TextRectHeight(content_width, alert_title.c_str(), ALIGN_LEFT); + DrawTextRect(AlertViewer::horizontal_padding, alert_title_start_y, + content_width, alert_title_height, alert_title.c_str(), + ALIGN_LEFT); + + SetFont(default_font.get(), BLACK); + description_start_y += alert_title_height + AlertViewer::vertical_padding; } - alert_text << alert.description << std::endl - << std::endl - << GetLangText("Start") << " " - << format_full_date(alert.start_date) << std::endl - << GetLangText("Duration") << " " - << format_duration(alert.start_date, alert.end_date) << std::endl; + const auto description_height = + TextRectHeight(content_width, this->description.c_str(), ALIGN_LEFT); + DrawTextRect(AlertViewer::horizontal_padding, description_start_y, + content_width, description_height, this->description.c_str(), + ALIGN_LEFT); +} + +void AlertViewer::update_title() { + this->title = (std::string{GetLangText("ALERT")} + " " + + std::to_string(this->alert_index + 1) + "/" + + std::to_string(this->alert_count())); +} + +void AlertViewer::update_description(const Alert &alert) { + std::stringstream description_text; + description_text << alert.description << std::endl + << std::endl + << GetLangText("Start") << " " + << format_full_date(alert.start_date) << std::endl + << GetLangText("Duration") << " " + << format_duration(alert.start_date, alert.end_date) + << std::endl; if (not alert.sender.empty()) { - alert_text << GetLangText("Source") << " " << alert.sender; + description_text << GetLangText("Source") << " " << alert.sender; } + this->description = description_text.str(); +} - const auto first_button_text = alert_index + 1 < alert_count - ? GetLangText("Next alert") - : GetLangText("Ok"); - const auto second_button_text = - alert_index + 1 < alert_count ? GetLangText("Ok") : nullptr; - - Dialog(ICON_WARNING, dialog_title.c_str(), alert_text.str().c_str(), - first_button_text, second_button_text, &alert_dialog_handler); +bool AlertViewer::handle_key_press(int key) { + return (key == IV_KEY_PREV) or (key == IV_KEY_NEXT); } -void AlertsButton::alert_dialog_handler(int button_index) { - if (is_next_alert_button_at(button_index)) { - const auto event_handler = GetEventHandler(); - ++alert_index; - SendEvent(event_handler, EVT_CUSTOM, CustomEvent::display_alert, 0); - } else { - alert_index = 0; +bool AlertViewer::handle_key_release(int key) { + if (key == IV_KEY_PREV) { + if (this->alert_index != 0) { + --this->alert_index; + this->paint_and_update_screen(); + } else { + this->hide(); + } + return true; + } else if (key == IV_KEY_NEXT) { + if (this->alert_index + 1 < this->alert_count()) { + ++this->alert_index; + this->paint_and_update_screen(); + } else { + this->hide(); + } + return true; } - CloseDialog(); + return false; } + } // namespace taranis diff --git a/src/alerts.h b/src/alerts.h index 4e361f7..820f6b7 100644 --- a/src/alerts.h +++ b/src/alerts.h @@ -2,6 +2,7 @@ #include #include +#include #include "button.h" #include "fonts.h" @@ -10,24 +11,63 @@ namespace taranis { +class AlertViewer; + class AlertsButton : public Button { public: AlertsButton(int icon_size, std::shared_ptr model, - std::shared_ptr icons, std::shared_ptr fonts) - : Button{icon_size, icons->get("warning")}, model{model}, - font{fonts->get_normal_font()} {} + std::shared_ptr icons, + std::shared_ptr viewer) + : Button{icon_size, icons->get("warning")}, model{model}, viewer{viewer} { + } bool is_visible() const override { return not this->model->alerts.empty(); } - void open_dialog_maybe(); +protected: + void on_clicked() override; + +private: + std::shared_ptr model; + std::shared_ptr viewer; +}; + +class AlertViewer : public Widget { +public: + AlertViewer(std::shared_ptr model, std::shared_ptr fonts); + + void open(); + + void hide(); protected: - void on_clicked() override { this->open_dialog_maybe(); } + bool is_modal() const override { return true; } + + bool is_visible() const override { return this->visible; } + + void do_paint() override; private: + static constexpr int horizontal_padding{25}; + static constexpr int vertical_padding{25}; + std::shared_ptr model; - std::shared_ptr font; + std::shared_ptr fonts; + + bool visible{false}; + + size_t alert_index{0}; + + std::string title; + std::string description; + + inline size_t alert_count() const { return this->model->alerts.size(); } + + void update_title(); + + void update_description(const Alert &alert); + + bool handle_key_press(int key) override; - static void alert_dialog_handler(int button_index); + bool handle_key_release(int key) override; }; } // namespace taranis diff --git a/src/app.cc b/src/app.cc index 00e9c87..4d410fe 100644 --- a/src/app.cc +++ b/src/app.cc @@ -177,7 +177,7 @@ int App::handle_custom_event(int param_one, int param_two) { const std::string location{raw_location->data()}; this->search_location(location); return 1; - } else if (param_one == CustomEvent::display_alert) { + } else if (param_one == CustomEvent::open_alert_viewer) { if (this->ui) { this->ui->display_alert(); return 1; diff --git a/src/events.cc b/src/events.cc index 7f83a44..3c4f3f4 100644 --- a/src/events.cc +++ b/src/events.cc @@ -10,8 +10,8 @@ std::string taranis::format_custom_event_param(int param) { if (param == CustomEvent::change_location) { return "change_location"; } - if (param == CustomEvent::display_alert) { - return "display_alert"; + if (param == CustomEvent::open_alert_viewer) { + return "open_alert_viewer"; } if (param == CustomEvent::open_config_editor) { return "open_config_editor"; diff --git a/src/events.h b/src/events.h index 68903fe..4bb32ad 100644 --- a/src/events.h +++ b/src/events.h @@ -9,7 +9,7 @@ enum CustomEvent { change_daily_forecast_display, search_location, change_location, - display_alert, + open_alert_viewer, open_config_editor, refresh_data, select_location_from_history, diff --git a/src/hourlyforecastbox.cc b/src/hourlyforecastbox.cc index d709222..bf519f1 100644 --- a/src/hourlyforecastbox.cc +++ b/src/hourlyforecastbox.cc @@ -96,7 +96,7 @@ void HourlyForecastBox::increase_forecast_offset() { this->forecast_offset = updated_forecast_offset; BOOST_LOG_TRIVIAL(debug) << "Forecast offset increased to " << this->forecast_offset; - this->draw_and_update(); + this->paint_and_update_screen(); } else { this->request_change_display_forecast_display(); } @@ -115,19 +115,12 @@ void HourlyForecastBox::decrease_forecast_offset() { this->forecast_offset = updated_forecast_offset; BOOST_LOG_TRIVIAL(debug) << "Forecast offset decreased to " << this->forecast_offset; - this->draw_and_update(); + this->paint_and_update_screen(); } else { this->request_change_display_forecast_display(); } } -void HourlyForecastBox::draw_and_update() { - this->paint(); - - PartialUpdate(this->bounding_box.x, this->bounding_box.y, - this->bounding_box.w, this->bounding_box.h); -} - std::tuple HourlyForecastBox::get_date_label_properties(size_t bar_index) const { const int forecast_index = this->forecast_offset + bar_index; diff --git a/src/ui.cc b/src/ui.cc index 176b678..ed58e99 100644 --- a/src/ui.cc +++ b/src/ui.cc @@ -56,22 +56,25 @@ Ui::Ui(std::shared_ptr model) const auto ¤t_condition_bounding_box = current_condition_box->get_bounding_box(); - this->alerts_button = std::make_shared( - Ui::alert_icon_size, model, this->icons, this->fonts); - this->alerts_button->set_pos_x(ScreenWidth() - alerts_button->get_width() - - Ui::button_margin); - this->alerts_button->set_pos_y(current_condition_bounding_box.y + - current_condition_bounding_box.h / 2 - - this->alerts_button->get_height() / 2 - - CurrentConditionBox::bottom_padding); + alert_viewer = std::make_shared(model, this->fonts); + + auto alerts_button = std::make_shared( + Ui::alert_icon_size, model, this->icons, alert_viewer); + alerts_button->set_pos_x(ScreenWidth() - alerts_button->get_width() - + Ui::button_margin); + alerts_button->set_pos_y( + current_condition_bounding_box.y + current_condition_bounding_box.h / 2 - + alerts_button->get_height() / 2 - CurrentConditionBox::bottom_padding); this->location_selector = std::make_shared(50, this->fonts, this->icons); + this->modals.push_back(alert_viewer); + this->children.push_back(location_box); this->children.push_back(menu_button); this->children.push_back(current_condition_box); - this->children.push_back(this->alerts_button); + this->children.push_back(alerts_button); this->children.push_back(status_bar); // A forecast widget will be pushed back by select_forecast_widget() @@ -88,6 +91,7 @@ void Ui::paint() { this->check_modal_visibility(); if (this->visible_modal) { + BOOST_LOG_TRIVIAL(debug) << "Painting visible modal"; this->visible_modal->paint(); } else { this->select_forecast_widget(); @@ -151,8 +155,8 @@ bool Ui::is_on_widget(int pointer_pos_x, int pointer_pos_y, void Ui::check_modal_visibility() { const auto found_visible_modal = - std::find_if(this->modals.begin(), this->modals.end(), - [](const auto &modal) { return modal->is_visible(); }); + std::find_if(this->modals.begin(), this->modals.end(), + [](const auto &modal) { return modal->is_visible(); }); if (found_visible_modal != this->modals.end()) { if (this->visible_modal != *found_visible_modal) { diff --git a/src/ui.h b/src/ui.h index 80a41a1..a36d4dc 100644 --- a/src/ui.h +++ b/src/ui.h @@ -34,7 +34,7 @@ class Ui : public KeyEventDispatcher { int handle_pointer_event(int event_type, int pointer_pos_x, int pointer_pos_y); - void display_alert() { this->alerts_button->open_dialog_maybe(); } + void display_alert() { this->alert_viewer->open(); } void switch_forecast_widget(); @@ -52,7 +52,7 @@ class Ui : public KeyEventDispatcher { SwipeDetector swipe_detector; - std::shared_ptr alerts_button; + std::shared_ptr alert_viewer; std::shared_ptr hourly_forecast_box; std::shared_ptr daily_forecast_box;