Mercurial > minori
changeset 348:6b0768158dcd
text: redesign almost every widget
i.e. Paragraph is now a QLabel, etc etc, some things will probably
break, idc
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Tue, 25 Jun 2024 11:19:54 -0400 |
parents | a0aa8c8c4307 |
children | 7e97c566cce4 daa03aa2262d |
files | include/core/anime_season.h include/core/strings.h include/gui/pages/now_playing.h include/gui/pages/seasons.h include/gui/pages/statistics.h include/gui/widgets/anime_button.h include/gui/widgets/elided_label.h include/gui/widgets/poster.h include/gui/widgets/text.h src/core/anime_season.cc src/gui/dialog/information.cc src/gui/pages/now_playing.cc src/gui/pages/seasons.cc src/gui/pages/statistics.cc src/gui/widgets/anime_button.cc src/gui/widgets/anime_info.cc src/gui/widgets/elided_label.cc src/gui/widgets/poster.cc src/gui/widgets/text.cc |
diffstat | 19 files changed, 458 insertions(+), 560 deletions(-) [+] |
line wrap: on
line diff
--- a/include/core/anime_season.h Sun Jun 23 10:32:09 2024 -0400 +++ b/include/core/anime_season.h Tue Jun 25 11:19:54 2024 -0400 @@ -36,6 +36,9 @@ bool operator<=(const Season& o) const; bool operator>=(const Season& o) const; + Season& operator++(); + Season& operator--(); + Name season = Name::Unknown; Date::Year year = 0; };
--- a/include/core/strings.h Sun Jun 23 10:32:09 2024 -0400 +++ b/include/core/strings.h Tue Jun 25 11:19:54 2024 -0400 @@ -53,10 +53,10 @@ return def; } -template<typename T, std::enable_if_t<std::is_integral<T>::value && !std::is_same<T, bool>::value, bool> = true> +template<typename T, std::enable_if_t<std::is_arithmetic<T>::value && !std::is_same<T, bool>::value, bool> = true> std::string ToUtf8String(T i) { std::ostringstream s; - s << i; + s << std::noboolalpha << i; return s.str(); }
--- a/include/gui/pages/now_playing.h Sun Jun 23 10:32:09 2024 -0400 +++ b/include/gui/pages/now_playing.h Tue Jun 25 11:19:54 2024 -0400 @@ -3,13 +3,15 @@ #include "gui/widgets/anime_info.h" #include "gui/widgets/poster.h" +#include "gui/widgets/sidebar.h" #include "gui/widgets/text.h" #include <QFrame> +#include <QStackedWidget> + #include <memory> #include <unordered_map> -class QStackedWidget; namespace Anime { class Anime; @@ -19,6 +21,9 @@ class Elements; } +/* -------------------------------------------------------------- */ +/* separate pages */ + namespace NowPlayingPages { class Default : public QWidget { @@ -26,6 +31,9 @@ public: Default(QWidget* parent = nullptr); + +private: + TextWidgets::Title title_; }; class Playing : public QWidget { @@ -39,15 +47,20 @@ private: int _id = 0; int _episode = 0; - std::unique_ptr<QWidget> _main = nullptr; - std::unique_ptr<TextWidgets::Title> _title = nullptr; - std::unique_ptr<AnimeInfoWidget> _info = nullptr; - std::unique_ptr<QWidget> _sidebar = nullptr; - std::unique_ptr<Poster> _poster = nullptr; + + QWidget _main; + TextWidgets::Title _title; + AnimeInfoWidget _info; + + QWidget _sidebar; + Poster _poster; }; } // namespace NowPlayingPages +/* -------------------------------------------------------------- */ +/* the full page */ + class NowPlayingPage final : public QFrame { Q_OBJECT @@ -58,7 +71,15 @@ int GetPlayingId(); private: - QStackedWidget* stack; + enum class Subpages { + Default = 0, + Playing = 1, + }; + + QStackedWidget stack_; + + NowPlayingPages::Default default_; + NowPlayingPages::Playing playing_; }; #endif // MINORI_GUI_PAGES_NOW_PLAYING_H_
--- a/include/gui/pages/seasons.h Sun Jun 23 10:32:09 2024 -0400 +++ b/include/gui/pages/seasons.h Tue Jun 25 11:19:54 2024 -0400 @@ -3,6 +3,7 @@ #include <QFrame> #include <QThread> +#include <QToolButton> #include "core/anime.h" #include "core/date.h" @@ -43,7 +44,7 @@ protected: QListWidget* buttons = nullptr; - QToolButton* season_button = nullptr; + QToolButton season_button; Anime::Season season_; };
--- a/include/gui/pages/statistics.h Sun Jun 23 10:32:09 2024 -0400 +++ b/include/gui/pages/statistics.h Tue Jun 25 11:19:54 2024 -0400 @@ -4,12 +4,8 @@ #include <QFrame> #include <QWidget> -template<typename T> -class Graph; - -namespace TextWidgets { -class LabelledSection; -} +#include "gui/widgets/graph.h" +#include "gui/widgets/text.h" class StatisticsPage final : public QFrame { Q_OBJECT @@ -22,9 +18,10 @@ void showEvent(QShowEvent*) override; private: - std::shared_ptr<TextWidgets::LabelledSection> _anime_list; - std::shared_ptr<Graph<int>> _score_distribution_graph; - std::shared_ptr<TextWidgets::LabelledSection> _application; + TextWidgets::LabelledSection _anime_list; + TextWidgets::LabelledSection _application; + + Graph<int> _score_distribution_graph; }; #endif // MINORI_GUI_PAGES_STATISTICS_H_ \ No newline at end of file
--- a/include/gui/widgets/anime_button.h Sun Jun 23 10:32:09 2024 -0400 +++ b/include/gui/widgets/anime_button.h Tue Jun 25 11:19:54 2024 -0400 @@ -14,6 +14,8 @@ } class AnimeButton : public QFrame { + Q_OBJECT + public: AnimeButton(QWidget* parent = nullptr); AnimeButton(const Anime::Anime& anime, QWidget* parent = nullptr); @@ -23,7 +25,7 @@ Poster _poster; QLabel _title; TextWidgets::LabelledParagraph _info; - ElidedLabel _synopsis; + TextWidgets::Paragraph _synopsis; }; #endif // MINORI_GUI_WIDGETS_ANIME_BUTTON_H_
--- a/include/gui/widgets/elided_label.h Sun Jun 23 10:32:09 2024 -0400 +++ b/include/gui/widgets/elided_label.h Tue Jun 25 11:19:54 2024 -0400 @@ -7,15 +7,4 @@ class QPaintEvent; class QWidget; -class ElidedLabel : public QFrame { -public: - ElidedLabel(const QString& text, QWidget* parent = nullptr); - void SetText(const QString& text); - -protected: - QString content; - - void paintEvent(QPaintEvent* event) override; -}; - #endif // MINORI_GUI_WIDGETS_ELIDED_LABEL_H_
--- a/include/gui/widgets/poster.h Sun Jun 23 10:32:09 2024 -0400 +++ b/include/gui/widgets/poster.h Tue Jun 25 11:19:54 2024 -0400 @@ -20,6 +20,9 @@ void SetAnime(const Anime::Anime& anime); void SetClickable(bool clickable); + bool hasHeightForWidth(void) const override; + int heightForWidth(int w) const override; + protected: void showEvent(QShowEvent*) override; void resizeEvent(QResizeEvent*) override; @@ -27,6 +30,9 @@ void RenderToLabel(); void DownloadPoster(); + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + private: /* stored as a pointer to prevent blocking */ HTTP::RequestThread* get_thread_;
--- a/include/gui/widgets/text.h Sun Jun 23 10:32:09 2024 -0400 +++ b/include/gui/widgets/text.h Tue Jun 25 11:19:54 2024 -0400 @@ -4,131 +4,136 @@ #include <QLineEdit> #include <QPlainTextEdit> #include <QSize> -#include <QString> #include <QWidget> +#include <QLabel> +#include <QVBoxLayout> +#include <QPointer> -class QFrame; - -#include <QLabel> +#include <memory> +#include <unordered_map> namespace TextWidgets { +/* These used to have getter methods to get the real Qt widgets; + * don't make those anymore. Instead, add new methods that are + * wrappers around the Qt methods instead. */ + class Header : public QWidget { Q_OBJECT public: - Header(const QString& title, QWidget* parent = nullptr); - void SetText(const QString& title); + Header(QWidget* parent = nullptr); + void SetText(const std::string& title); -private: - QLabel static_text_title; - QFrame static_text_line; +protected: + QPointer<QLabel> title_; + QPointer<QFrame> separator_; }; +/* This is a nice clean wrapper around Label suitable for our needs. */ class Paragraph : public QWidget { Q_OBJECT public: - Paragraph(const QString& text, QWidget* parent = nullptr); - void SetText(const QString& text); - QPlainTextEdit* GetLabel(); + Paragraph(QWidget* parent = nullptr); + void SetText(const std::string& text); + void SetSelectable(bool enable); + void SetWordWrap(bool enable); + void SetElidingMode(bool enable); protected: - bool hasHeightForWidth() const override; - int heightForWidth(int w) const override; - -private: - QPlainTextEdit text_edit; + QPointer<QLabel> label_; }; +/* This aligns data with labels */ class LabelledParagraph final : public QWidget { Q_OBJECT public: - LabelledParagraph(const QString& label, const QString& data, QWidget* parent = nullptr); - QLabel* GetLabels(); - QLabel* GetData(); + enum Style { + BoldedLabels = (1 << 1), + ElidedData = (1 << 2), /* does nothing for now */ + }; + + LabelledParagraph(QWidget* parent = nullptr); + ~LabelledParagraph(); + void SetData(const std::vector<std::pair<std::string, std::string>>& data); + void SetStyle(int style); /* bit-flags from Style enum */ + void Clear(void); + +protected: + QPointer<QWidget> contents_; + QPointer<QGridLayout> contents_layout_; - /* synonymous with GetData(), kept for compatibility. don't use in new code!!! */ - QLabel* GetParagraph(); + std::vector<std::pair<QSharedPointer<QLabel>, QSharedPointer<QLabel>>> data_; +}; -private: - QLabel labels_; - QLabel data_; +/* this is just a generic QLabel with a specific font and foreground role, + * which is why it's defined inline */ +class Title final : public Paragraph { +public: + Title(QWidget* parent = nullptr) : Paragraph(parent) { + QFont fnt(label_->font()); + fnt.setPixelSize(16); + label_->setFont(fnt); + + label_->setForegroundRole(QPalette::Highlight); + } }; -class Line : public QWidget { - Q_OBJECT +/* ----------------------------------------------------------------------- */ +/* Generic "Section" widget */ +template<typename T> +class Section final : public QWidget { public: - Line(QWidget* parent = nullptr); - Line(const QString& text, QWidget* parent = nullptr); - void SetText(const QString& text); + Section(QWidget* parent = nullptr) : QWidget(parent) { + header_ = new Header(this); -protected: - QLineEdit line_edit_; -}; + content_container_ = new QWidget(this); + content_ = new T(content_container_); + + QVBoxLayout* content_layout = new QVBoxLayout(content_container_); -class Title final : public Line { - Q_OBJECT + content_layout->addWidget(content_); + content_layout->setSpacing(0); + content_layout->setContentsMargins(12, 0, 0, 0); -public: - Title(const QString& title, QWidget* parent = nullptr); -}; + content_container_->setContentsMargins(0, 0, 0, 0); + + content_container_->setLayout(content_layout); + + QVBoxLayout* layout = new QVBoxLayout(this); -class Section final : public QWidget { - Q_OBJECT + layout->addWidget(header_); + layout->addWidget(content_container_); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); -public: - Section(const QString& title, const QString& data, QWidget* parent = nullptr); - Header* GetHeader(); - Paragraph* GetParagraph(); + setLayout(layout); + } + + Header& GetHeader() { return *header_; } + T& GetContent() { return *content_; } private: - Header* header; - Paragraph* paragraph; -}; - -class LabelledSection final : public QWidget { - Q_OBJECT + Header* header_; + T* content_; -public: - LabelledSection(const QString& title, const QString& label, const QString& data, QWidget* parent = nullptr); - Header* GetHeader(); - QLabel* GetLabels(); - QLabel* GetData(); - QLabel* GetParagraph(); - -private: - Header* header; - LabelledParagraph* content; + /* I don't think making a separate container + * widget is necessary anymore, but I'm paranoid */ + QWidget* content_container_; }; -class SelectableSection final : public QWidget { - Q_OBJECT - -public: - SelectableSection(const QString& title, const QString& data, QWidget* parent = nullptr); - Header* GetHeader(); - Paragraph* GetParagraph(); - -private: - Header* header; - Paragraph* paragraph; -}; - -class OneLineSection final : public QWidget { - Q_OBJECT - -public: - OneLineSection(const QString& title, const QString& data, QWidget* parent = nullptr); - Header* GetHeader(); - Line* GetLine(); - -private: - Header* header; - Line* line; -}; +/* Old aliases used when the sections weren't templateized. + * + * These are kept to keep old code working and can largely + * be ignored for anything new. + * + * SelectableSection is actually just a generic "long text" */ +using LabelledSection = Section<LabelledParagraph>; +using SelectableSection = Section<Paragraph>; +using OneLineSection = Section<Paragraph>; } // namespace TextWidgets
--- a/src/core/anime_season.cc Sun Jun 23 10:32:09 2024 -0400 +++ b/src/core/anime_season.cc Tue Jun 25 11:19:54 2024 -0400 @@ -79,4 +79,28 @@ return !(*this < o); } +Season& Season::operator++() { + switch (season) { + case Season::Name::Winter: season = Season::Name::Spring; break; + case Season::Name::Spring: season = Season::Name::Summer; break; + case Season::Name::Summer: season = Season::Name::Autumn; break; + case Season::Name::Autumn: season = Season::Name::Winter; year++; break; + default: season = Season::Name::Unknown; break; + } + + return *this; } + +Season& Season::operator--() { + switch (season) { + case Season::Name::Winter: season = Season::Name::Autumn; year--; break; + case Season::Name::Spring: season = Season::Name::Winter; break; + case Season::Name::Summer: season = Season::Name::Spring; break; + case Season::Name::Autumn: season = Season::Name::Summer; break; + default: season = Season::Name::Unknown; break; + } + + return *this; +} + +}
--- a/src/gui/dialog/information.cc Sun Jun 23 10:32:09 2024 -0400 +++ b/src/gui/dialog/information.cc Tue Jun 25 11:19:54 2024 -0400 @@ -46,7 +46,7 @@ } InformationDialog::InformationDialog(Anime::Anime* anime, std::function<void(Anime::Anime*)> accept, enum Pages page, - QWidget* parent) + QWidget* parent) : QDialog(parent) { /* ack. lots of brackets here, but MUCH, MUCH MUCH better than what it used to be */ setFixedSize(842, 613); @@ -89,8 +89,8 @@ { /* Anime title */ - TextWidgets::Title* anime_title = - new TextWidgets::Title(Strings::ToQString(anime->GetUserPreferredTitle()), main_widget); + TextWidgets::Title* anime_title = new TextWidgets::Title(main_widget); + anime_title->SetText(anime->GetUserPreferredTitle()); main_layout->addWidget(anime_title); } @@ -111,7 +111,10 @@ settings_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); QVBoxLayout* settings_layout = new QVBoxLayout(settings_widget); - settings_layout->addWidget(new TextWidgets::Header(tr("Anime list"), settings_widget)); + + TextWidgets::Header* header = new TextWidgets::Header(settings_widget); + header->SetText(Strings::Translate("Anime list")); + settings_layout->addWidget(header); { /* Anime List */ @@ -143,7 +146,7 @@ QSpinBox* spin_box = new QSpinBox(section); connect(spin_box, QOverload<int>::of(&QSpinBox::valueChanged), this, - [this](int i) { _progress = i; }); + [this](int i) { _progress = i; }); spin_box->setRange(0, anime->GetEpisodes()); spin_box->setSingleStep(1); spin_box->setValue(_progress = anime->GetUserProgress()); @@ -155,9 +158,9 @@ /* Rewatching? */ QCheckBox* checkbox = new QCheckBox(tr("Rewatching")); connect(checkbox, QOverload<int>::of(&QCheckBox::stateChanged), this, - [this](int state) { _rewatching = (state == Qt::Checked); }); + [this](int state) { _rewatching = (state == Qt::Checked); }); checkbox->setCheckState((_rewatching = anime->GetUserIsRewatching()) ? Qt::Checked - : Qt::Unchecked); + : Qt::Unchecked); checkbox->setFixedWidth(LAYOUT_ITEM_WIDTH); layout->addWidget(checkbox, 1, 1); } @@ -174,15 +177,15 @@ _status = anime->GetUserStatus(); for (unsigned int i = 0; i < Anime::ListStatuses.size(); i++) { combo_box->addItem(Strings::ToQString(Translate::ToLocalString(Anime::ListStatuses[i])), - static_cast<int>(Anime::ListStatuses[i])); + static_cast<int>(Anime::ListStatuses[i])); if (Anime::ListStatuses[i] == _status) combo_box->setCurrentIndex(i); } connect(combo_box, QOverload<int>::of(&QComboBox::currentIndexChanged), this, - [this, combo_box](int) { - _status = static_cast<Anime::ListStatus>(combo_box->currentData().toInt()); - }); + [this, combo_box](int) { + _status = static_cast<Anime::ListStatus>(combo_box->currentData().toInt()); + }); /* this should NEVER, EVER, be NOT_IN_LIST */ combo_box->setFixedWidth(LAYOUT_ITEM_WIDTH); @@ -195,7 +198,7 @@ QSpinBox* spin_box = new QSpinBox(section); connect(spin_box, QOverload<int>::of(&QSpinBox::valueChanged), this, - [this](int i) { _score = i; }); + [this](int i) { _score = i; }); spin_box->setRange(0, 100); spin_box->setSingleStep(5); spin_box->setValue(_score = anime->GetUserScore()); @@ -225,7 +228,7 @@ OptionalDate* date = new OptionalDate(true, section); connect(date, &OptionalDate::DataChanged, this, - [this](bool enabled, Date date) { _started = enabled ? date : Date(); }); + [this](bool enabled, Date date) { _started = enabled ? date : Date(); }); date->setFixedWidth(LAYOUT_ITEM_WIDTH); _started = anime->GetUserDateStarted(); if (!_started.IsValid()) { @@ -242,7 +245,7 @@ OptionalDate* date = new OptionalDate(true, section); connect(date, &OptionalDate::DataChanged, this, - [this](bool enabled, Date date) { _completed = enabled ? date : Date(); }); + [this](bool enabled, Date date) { _completed = enabled ? date : Date(); }); date->setFixedWidth(LAYOUT_ITEM_WIDTH); _completed = anime->GetUserDateCompleted(); if (!_completed.IsValid()) { @@ -260,28 +263,28 @@ /* { - // commenting this out until it actually gets implemented :) + // commenting this out until it actually gets implemented :) - settings_layout->addWidget(new TextWidgets::Header(tr("Local settings"), settings_widget)); + settings_layout->addWidget(new TextWidgets::Header(tr("Local settings"), settings_widget)); - QWidget* sg_local_content = new QWidget(settings_widget); - QVBoxLayout* sg_local_layout = new QVBoxLayout(sg_local_content); - sg_local_layout->setSpacing(5); - sg_local_layout->setContentsMargins(12, 0, 0, 0); + QWidget* sg_local_content = new QWidget(settings_widget); + QVBoxLayout* sg_local_layout = new QVBoxLayout(sg_local_content); + sg_local_layout->setSpacing(5); + sg_local_layout->setContentsMargins(12, 0, 0, 0); - CREATE_SECTION(sg_local_content, [this, &anime](QWidget* section, QGridLayout* layout){ - layout->addWidget(new QLabel(tr("Alternative titles:"), section), 0, 0); + CREATE_SECTION(sg_local_content, [this, &anime](QWidget* section, QGridLayout* layout){ + layout->addWidget(new QLabel(tr("Alternative titles:"), section), 0, 0); - QLineEdit* line_edit = new QLineEdit("", section); - line_edit->setPlaceholderText( - tr("Enter alternative titles here, separated by a semicolon (i.e. Title 1; Title 2)")); - layout->addWidget(line_edit, 1, 0); + QLineEdit* line_edit = new QLineEdit("", section); + line_edit->setPlaceholderText( + tr("Enter alternative titles here, separated by a semicolon (i.e. Title 1; Title 2)")); + layout->addWidget(line_edit, 1, 0); - QCheckBox* checkbox = new QCheckBox(tr("Use the first alternative title to search for + QCheckBox* checkbox = new QCheckBox(tr("Use the first alternative title to search for torrents")); layout->addWidget(checkbox, 2, 0); - }); + }); - settings_layout->addWidget(sg_local_content); + settings_layout->addWidget(sg_local_content); } */
--- a/src/gui/pages/now_playing.cc Sun Jun 23 10:32:09 2024 -0400 +++ b/src/gui/pages/now_playing.cc Tue Jun 25 11:19:54 2024 -0400 @@ -19,8 +19,8 @@ QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); - TextWidgets::Title* title = new TextWidgets::Title(tr("Now Playing"), this); - layout->addWidget(title); + title_.SetText(Strings::Translate("Now Playing")); + layout->addWidget(&title_); layout->addStretch(); } @@ -28,32 +28,27 @@ Playing::Playing(QWidget* parent) : QWidget(parent) { QHBoxLayout* layout = new QHBoxLayout(this); - _main.reset(new QWidget(this)); - _main->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + _main.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - QVBoxLayout* main_layout = new QVBoxLayout(_main.get()); + QVBoxLayout* main_layout = new QVBoxLayout(&_main); main_layout->setContentsMargins(0, 0, 0, 0); - _title.reset(new TextWidgets::Title("", _main.get())); - main_layout->addWidget(_title.get()); + main_layout->addWidget(&_title); - _info.reset(new AnimeInfoWidget(_main.get())); - _info->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - _info->layout()->setContentsMargins(0, 0, 0, 0); - main_layout->addWidget(_info.get()); + _info.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + _info.layout()->setContentsMargins(0, 0, 0, 0); + main_layout->addWidget(&_info); /* "sidebar", includes... just the anime image :) */ - _sidebar.reset(new QWidget(this)); - QVBoxLayout* sidebar_layout = new QVBoxLayout(_sidebar.get()); + QVBoxLayout* sidebar_layout = new QVBoxLayout(&_sidebar); sidebar_layout->setContentsMargins(0, 0, 0, 0); - _poster.reset(new Poster(_sidebar.get())); - sidebar_layout->addWidget(_poster.get()); + sidebar_layout->addWidget(&_poster); sidebar_layout->addStretch(); - layout->addWidget(_sidebar.get()); - layout->addWidget(_main.get()); + layout->addWidget(&_sidebar); + layout->addWidget(&_main); layout->setSpacing(10); layout->setContentsMargins(0, 0, 0, 0); } @@ -68,9 +63,9 @@ return; _id = anime.GetId(); _episode = Strings::ToInt(Strings::ToUtf8String(info.get(anitomy::kElementEpisodeNumber))); - _title->SetText(Strings::ToQString(anime.GetUserPreferredTitle())); - _info->SetAnime(anime); - _poster->SetAnime(anime); + _title.SetText(anime.GetUserPreferredTitle()); + _info.SetAnime(anime); + _poster.SetAnime(anime); updateGeometry(); } @@ -85,24 +80,23 @@ setFrameShadow(QFrame::Sunken); setAutoFillBackground(true); - stack = new QStackedWidget(this); - stack->addWidget(new NowPlayingPages::Default(stack)); - stack->addWidget(new NowPlayingPages::Playing(stack)); - layout->addWidget(stack); + stack_.addWidget(&default_); + stack_.addWidget(&playing_); + layout->addWidget(&stack_); SetDefault(); } void NowPlayingPage::SetDefault() { - stack->setCurrentIndex(0); + stack_.setCurrentIndex(static_cast<int>(Subpages::Default)); } int NowPlayingPage::GetPlayingId() { - return reinterpret_cast<NowPlayingPages::Playing*>(stack->widget(1))->GetPlayingAnime(); + return playing_.GetPlayingAnime(); } void NowPlayingPage::SetPlaying(const Anime::Anime& anime, const anitomy::Elements& info) { - reinterpret_cast<NowPlayingPages::Playing*>(stack->widget(1))->SetPlayingAnime(anime, info); - stack->setCurrentIndex(1); + playing_.SetPlayingAnime(anime, info); + stack_.setCurrentIndex(static_cast<int>(Subpages::Playing)); updateGeometry(); }
--- a/src/gui/pages/seasons.cc Sun Jun 23 10:32:09 2024 -0400 +++ b/src/gui/pages/seasons.cc Tue Jun 25 11:19:54 2024 -0400 @@ -15,6 +15,8 @@ #include <QToolButton> #include <QVBoxLayout> +#include <iostream> + SeasonsPageSearchThread::SeasonsPageSearchThread(QObject* parent) : QThread(parent) { } @@ -56,7 +58,7 @@ void SeasonsPage::Refresh() { setUpdatesEnabled(false); - if (!buttons || !season_button) + if (!buttons) return; buttons->clear(); @@ -70,7 +72,7 @@ buttons->setItemWidget(item, button); } - season_button->setText(Strings::ToQString(Translate::ToLocalString(season_))); + season_button.setText(Strings::ToQString(Translate::ToLocalString(season_))); setUpdatesEnabled(true); } @@ -96,47 +98,50 @@ toolbar->setMovable(false); { - /* currently this is VERY hardcoded to en_US */ - static constexpr Date::Year last_year = 1960; + /* XXX this last year probably shouldn't be hardcoded */ + static const Anime::Season last_season(Anime::Season::Name::Winter, 1960); + Anime::Season current_season(Date(QDate::currentDate())); + const Date::Year year_before_collapse = GetClosestDecade(current_season.year) - 10; - auto create_year_menu = [this](QWidget* parent, QMenu* parent_menu, Date::Year year){ - const QString year_s = QString::number(year); + /* year -> menu for that year */ + std::map<Date::Year, QMenu*> menu_map; - QMenu* menu = new QMenu(year_s, parent); - for (const auto& season : Anime::Season::Names) { - QAction* action = menu->addAction(Strings::ToQString(Translate::ToLocalString(Anime::Season(season, year)))); - connect(action, &QAction::triggered, this, [this, season, year] { - SetSeason({season, year}); - }); - } - parent_menu->addMenu(menu); + auto create_season_menu = [&](QWidget* parent, Anime::Season season){ + QMenu*& menu = menu_map[season.year]; + if (!menu) + menu = new QMenu(QString::number(season.year), parent); + + QAction* action = menu->addAction(Strings::ToQString(Translate::ToLocalString(season))); + connect(action, &QAction::triggered, this, [this, season] { + SetSeason(season); + }); }; - auto create_decade_menu = [create_year_menu](QWidget* parent, QMenu* parent_menu, Date::Year decade) { - QMenu* menu = new QMenu(QString::number(decade) + "s", parent); - for (int i = 9; i >= 0; i--) - create_year_menu(parent, menu, decade + i); - parent_menu->addMenu(menu); - }; + for (Anime::Season s = current_season; s >= last_season; --s) + create_season_menu(&season_button, s); + + /* ------------------------------------------------------- */ + /* now actually generate the full menu */ - /* we'll be extinct by the time this code breaks, so I guess it's fine :) */ - const Date::Year current_year = static_cast<Date::Year>(QDate::currentDate().year()); - const Date::Year year_before_collapse = GetClosestDecade(current_year) - 10; - season_button = new QToolButton(toolbar); - QMenu* full_season_menu = new QMenu(season_button); + QMenu* full_menu = new QMenu(&season_button); + + for (Date::Year c = current_season.year; c >= year_before_collapse; c--) + full_menu->addMenu(menu_map[c]); + + full_menu->addSeparator(); - for (Date::Year c = current_year; c >= year_before_collapse; c--) - create_year_menu(season_button, full_season_menu, c); - - full_season_menu->addSeparator(); + /* collapse each menu into a decade */ + for (Date::Year c = year_before_collapse - 10; c >= last_season.year; c -= 10) { + QMenu* decade_menu = new QMenu(tr("%1s").arg(QString::number(c)), parent); + for (Date::Year i = c + 9; i >= c; i--) + decade_menu->addMenu(menu_map[i]); + full_menu->addMenu(decade_menu); + } - for (Date::Year c = year_before_collapse - 10; c >= last_year; c -= 10) - create_decade_menu(season_button, full_season_menu, c); + season_button.setMenu(full_menu); + season_button.setPopupMode(QToolButton::InstantPopup); - season_button->setMenu(full_season_menu); - season_button->setPopupMode(QToolButton::InstantPopup); - - toolbar->addWidget(season_button); + toolbar->addWidget(&season_button); } toolbar->addSeparator();
--- a/src/gui/pages/statistics.cc Sun Jun 23 10:32:09 2024 -0400 +++ b/src/gui/pages/statistics.cc Tue Jun 25 11:19:54 2024 -0400 @@ -17,7 +17,8 @@ #include <cmath> #include <sstream> -StatisticsPage::StatisticsPage(QWidget* parent) : QFrame(parent) { +StatisticsPage::StatisticsPage(QWidget* parent) + : QFrame(parent) { setBackgroundRole(QPalette::Base); QVBoxLayout* layout = new QVBoxLayout(this); @@ -27,25 +28,34 @@ setAutoFillBackground(true); - _anime_list.reset(new TextWidgets::LabelledSection( - tr("Anime list"), - tr("Anime count:\nEpisode count:\nTime spent watching:\nTime to complete:\nAverage score:\nScore deviation:"), - "", this)); - layout->addWidget(_anime_list.get()); + const std::vector<std::pair<std::string, std::string>> al_data_template = { + {Strings::Translate("Anime count:"), ""}, + {Strings::Translate("Episode count:"), ""}, + {Strings::Translate("Time spent watching:"), ""}, + {Strings::Translate("Time to complete:"), ""}, + {Strings::Translate("Average score:"), ""}, + {Strings::Translate("Score deviation:"), ""}, + }; + + _anime_list.GetHeader().SetText(Strings::Translate("Anime List")); + _anime_list.GetContent().SetData(al_data_template); + + layout->addWidget(&_anime_list); { QWidget* score_dist_widget = new QWidget(this); QVBoxLayout* score_dist_layout = new QVBoxLayout(score_dist_widget); - score_dist_layout->addWidget(new TextWidgets::Header(tr("Score distribution"), score_dist_widget)); + TextWidgets::Header* hdr = new TextWidgets::Header(score_dist_widget); + hdr->SetText(Strings::Translate("Score distribution")); + score_dist_layout->addWidget(hdr); /* Ew */ { QWidget* score_graph_parent = new QWidget(score_dist_widget); QVBoxLayout* score_parent_layout = new QVBoxLayout(score_graph_parent); - _score_distribution_graph.reset(new Graph<int>(score_graph_parent)); - score_parent_layout->addWidget(_score_distribution_graph.get()); + score_parent_layout->addWidget(&_score_distribution_graph); score_parent_layout->setSpacing(0); score_parent_layout->setContentsMargins(12, 0, 0, 0); @@ -58,8 +68,15 @@ layout->addWidget(score_dist_widget); } - _application.reset(new TextWidgets::LabelledSection(tr("Minori"), tr("Uptime:\nRequests made:"), "\n\n", this)); - layout->addWidget(_application.get()); + const std::vector<std::pair<std::string, std::string>> app_data_template = { + {Strings::Translate("Uptime:"), ""}, + {Strings::Translate("Requests made:"), ""}, + }; + + _application.GetHeader().SetText(Strings::Translate("Minori")); + _application.GetContent().SetData(app_data_template); + + layout->addWidget(&_application); layout->addStretch(); @@ -84,25 +101,26 @@ } void StatisticsPage::UpdateStatistics() { - /* Anime list */ - QString string = ""; - QTextStream ts(&string); - ts << Anime::db.GetTotalAnimeAmount() << '\n'; - ts << Anime::db.GetTotalEpisodeAmount() << '\n'; - ts << Strings::ToQString(Time::GetSecondsAsAbsoluteString(Time::Units::Minutes, Anime::db.GetTotalWatchedAmount(), 60.0)) << '\n'; - ts << Strings::ToQString(Time::GetSecondsAsAbsoluteString(Time::Units::Minutes, Anime::db.GetTotalPlannedAmount(), 60.0)) << '\n'; - ts << Anime::db.GetAverageScore() << '\n'; - ts << Anime::db.GetScoreDeviation(); - _anime_list->GetData()->setText(string); + const std::vector<std::pair<std::string, std::string>> al_data = { + {Strings::Translate("Anime count:"), Strings::ToUtf8String(Anime::db.GetTotalAnimeAmount())}, + {Strings::Translate("Episode count:"), Strings::ToUtf8String(Anime::db.GetTotalEpisodeAmount())}, + {Strings::Translate("Time spent watching:"), Time::GetSecondsAsAbsoluteString(Time::Units::Minutes, Anime::db.GetTotalWatchedAmount(), 60.0)}, + {Strings::Translate("Time to complete:"), Time::GetSecondsAsAbsoluteString(Time::Units::Minutes, Anime::db.GetTotalPlannedAmount(), 60.0)}, + {Strings::Translate("Average score:"), Strings::ToUtf8String(Anime::db.GetAverageScore())}, + {Strings::Translate("Score deviation:"), Strings::ToUtf8String(Anime::db.GetScoreDeviation())}, + }; + + _anime_list.GetContent().SetData(al_data); - _score_distribution_graph->Clear(); + _score_distribution_graph.Clear(); for (int i = 10; i <= 100; i += 10) - _score_distribution_graph->AddItem(i, GetTotalWithScore(i)); + _score_distribution_graph.AddItem(i, GetTotalWithScore(i)); - string = ""; - ts << Strings::ToQString(Time::GetSecondsAsAbsoluteString(Time::Units::Seconds, session.uptime() / 1000)) << '\n'; - ts << session.GetRequests(); /* Application */ - // UiUtils::SetPlainTextEditData(application_data, QString::number(session.uptime() / 1000)); - _application->GetData()->setText(string); + const std::vector<std::pair<std::string, std::string>> app_data_template = { + {Strings::Translate("Uptime:"), Time::GetSecondsAsAbsoluteString(Time::Units::Seconds, session.uptime() / 1000)}, + {Strings::Translate("Requests made:"), Strings::ToUtf8String(session.GetRequests())}, + }; + + _application.GetContent().SetData(app_data_template); }
--- a/src/gui/widgets/anime_button.cc Sun Jun 23 10:32:09 2024 -0400 +++ b/src/gui/widgets/anime_button.cc Tue Jun 25 11:19:54 2024 -0400 @@ -12,6 +12,8 @@ #include <QVBoxLayout> #include <QWidget> +#include <iostream> + /* This widget is only used on the Seasons page. */ /***********************************\ @@ -26,19 +28,27 @@ *|_________| Synopsis * \***********************************/ -AnimeButton::AnimeButton(QWidget* parent) - : QFrame(parent) - , _info(tr("Aired:\nEpisodes:\nGenres:\nProducers:\nScore:\nPopularity:"), "\n\n\n\n\n", nullptr) - , _synopsis("", nullptr) { +AnimeButton::AnimeButton(QWidget* parent) : QFrame(parent) { setFrameShadow(QFrame::Plain); setFrameShape(QFrame::Box); + QHBoxLayout* ly = new QHBoxLayout(this); - /* XXX does Qt have a "fixed ratio"? */ + _poster.SetClickable(false); _poster.setFixedSize(120, 170); - _poster.SetClickable(false); ly->addWidget(&_poster, 0, Qt::AlignTop); + const std::vector<std::pair<std::string, std::string>> imap = { + {Strings::Translate("Aired:"), ""}, + {Strings::Translate("Episodes:"), ""}, + {Strings::Translate("Genres:"), ""}, + {Strings::Translate("Producers:"), ""}, + {Strings::Translate("Score:"), ""}, + {Strings::Translate("Popularity:"), ""}, + }; + + _info.SetData(imap); + { QWidget* misc_section = new QWidget(this); misc_section->setFixedSize(354, 180); @@ -56,11 +66,7 @@ } misc_layout->addWidget(&_title); - { - QFont fnt(_info.GetLabels()->font()); - fnt.setWeight(QFont::Bold); - _info.GetLabels()->setFont(fnt); - } + _info.SetStyle(TextWidgets::LabelledParagraph::Style::BoldedLabels); _info.setContentsMargins(4, 0, 4, 0); _info.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); @@ -74,6 +80,7 @@ dummy_layout->setContentsMargins(0, 0, 0, 0); _synopsis.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + _synopsis.SetSelectable(false); dummy_layout->addWidget(&_synopsis); misc_layout->addWidget(dummy); } @@ -90,13 +97,18 @@ _poster.SetAnime(anime); _title.setText(Strings::ToQString(anime.GetUserPreferredTitle())); - { - const QLocale& locale = session.config.locale.GetLocale(); - _info.GetData()->setText(locale.toString(anime.GetStartedDate().GetAsQDate(), "dd MMM yyyy") + "\n" + - QString::number(anime.GetEpisodes()) + "\n" + - Strings::ToQString(Strings::Implode(anime.GetGenres(), ", ")) + "\n" + "...\n" + - QString::number(anime.GetAudienceScore()) + "%\n" + "..."); - } + const QLocale& locale = session.config.locale.GetLocale(); - _synopsis.SetText(Strings::ToQString(anime.GetSynopsis())); + const std::vector<std::pair<std::string, std::string>> imap = { + {Strings::Translate("Aired:"), Strings::ToUtf8String(locale.toString(anime.GetStartedDate().GetAsQDate(), "dd MMM yyyy"))}, + {Strings::Translate("Episodes:"), Strings::ToUtf8String(anime.GetEpisodes())}, + {Strings::Translate("Genres:"), Strings::Implode(anime.GetGenres(), ", ")}, + {Strings::Translate("Producers:"), "..."}, + {Strings::Translate("Score:"), Strings::ToUtf8String(anime.GetAudienceScore()) + "%"}, + {Strings::Translate("Popularity:"), "..."}, + }; + + _info.SetData(imap); + + _synopsis.SetText(anime.GetSynopsis()); }
--- a/src/gui/widgets/anime_info.cc Sun Jun 23 10:32:09 2024 -0400 +++ b/src/gui/widgets/anime_info.cc Tue Jun 25 11:19:54 2024 -0400 @@ -36,17 +36,33 @@ /* all widgets share this thread */ static AnimeInfoWidgetGetMetadataThread get_metadata_thread; -AnimeInfoWidget::AnimeInfoWidget(QWidget* parent) - : QWidget(parent) - , _title(tr("Alternative titles"), "") - , _details(tr("Details"), tr("Type:\nEpisodes:\nStatus:\nSeason:\nGenres:\nProducers:\nScore:"), "") - , _synopsis(tr("Synopsis"), "") { +AnimeInfoWidget::AnimeInfoWidget(QWidget* parent) : QWidget(parent) { QVBoxLayout* layout = new QVBoxLayout(this); + _title.GetHeader().SetText(Strings::Translate("Alternative titles")); layout->addWidget(&_title); + + _details.GetHeader().SetText(Strings::Translate("Details")); + + const std::vector<std::pair<std::string, std::string>> items = { + {Strings::Translate("Type:"), ""}, + {Strings::Translate("Episodes:"), ""}, + {Strings::Translate("Status:"), ""}, + {Strings::Translate("Season:"), ""}, + {Strings::Translate("Genres:"), ""}, + {Strings::Translate("Producers:"), ""}, + {Strings::Translate("Score:"), ""}, + }; + + _details.GetContent().SetData(items); + layout->addWidget(&_details); + + _synopsis.GetHeader().SetText(Strings::Translate("Synopsis")); layout->addWidget(&_synopsis); + layout->addStretch(); + /* ... */ connect(&get_metadata_thread, &AnimeInfoWidgetGetMetadataThread::NeedRefresh, this, [this](int id) { setUpdatesEnabled(false); @@ -72,11 +88,11 @@ get_metadata_thread.start(); /* alt titles */ - _title.GetLine()->SetText(Strings::ToQString(Strings::Implode(anime.GetTitleSynonyms(), ", "))); + _title.GetContent().SetText(Strings::Implode(anime.GetTitleSynonyms(), ", ")); RefreshGenres(anime); - _synopsis.GetParagraph()->SetText(Strings::ToQString(anime.GetSynopsis())); + _synopsis.GetContent().SetText(anime.GetSynopsis()); setUpdatesEnabled(true); @@ -84,23 +100,21 @@ } void AnimeInfoWidget::RefreshGenres(const Anime::Anime& anime) { - /* details */ - QString details_data; - QTextStream details_data_s(&details_data); - /* we have to convert ALL of these strings to * QString because QTextStream sucks and assumes * Latin-1 (on Windows?) */ const auto genres = anime.GetGenres(); const auto producers = anime.GetProducers(); - details_data_s << Strings::ToQString(Translate::ToLocalString(anime.GetFormat())) << "\n" - << anime.GetEpisodes() << "\n" - << Strings::ToQString(Translate::ToLocalString(anime.GetAiringStatus())) << "\n" - << Strings::ToQString(Translate::ToLocalString(anime.GetSeason())) << "\n" - << Strings::ToQString((genres.size() > 1) ? Strings::Implode(genres, ", ") : "-") << "\n" - << Strings::ToQString((producers.size() > 1) ? Strings::Implode(producers, ", ") : "-") << "\n" - << anime.GetAudienceScore() << "%"; + const std::vector<std::pair<std::string, std::string>> items = { + {Strings::Translate("Type:"), Translate::ToLocalString(anime.GetFormat())}, + {Strings::Translate("Episodes:"), Strings::ToUtf8String(anime.GetEpisodes())}, + {Strings::Translate("Status:"), Translate::ToLocalString(anime.GetAiringStatus())}, + {Strings::Translate("Season:"), Translate::ToLocalString(anime.GetSeason())}, + {Strings::Translate("Genres:"), (genres.size() > 1) ? Strings::Implode(genres, ", ") : "-"}, + {Strings::Translate("Producers:"), (producers.size() > 1) ? Strings::Implode(producers, ", ") : "-"}, + {Strings::Translate("Score:"), Strings::ToUtf8String(anime.GetAudienceScore()) + "%"}, + }; - _details.GetData()->setText(details_data); + _details.GetContent().SetData(items); }
--- a/src/gui/widgets/elided_label.cc Sun Jun 23 10:32:09 2024 -0400 +++ b/src/gui/widgets/elided_label.cc Tue Jun 25 11:19:54 2024 -0400 @@ -1,78 +1,7 @@ -/* - * Copyright (C) 2016 The Qt Company Ltd. - * Contact: https://www.qt.io/licensing/ - * - * This file is part of the QtCore module of the Qt Toolkit. - * - * "Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * * Neither the name of The Qt Company Ltd nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." - */ + #include "gui/widgets/elided_label.h" #include <QPainter> #include <QSizePolicy> #include <QTextLayout> - -ElidedLabel::ElidedLabel(const QString& text, QWidget* parent) : QFrame(parent), content(text) { - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); -} - -void ElidedLabel::SetText(const QString& text) { - content = text; - update(); -} - -void ElidedLabel::paintEvent(QPaintEvent* event) { - QFrame::paintEvent(event); - - QPainter painter(this); - QFontMetrics metrics = painter.fontMetrics(); - - const int line_spacing = metrics.lineSpacing(); - int y = 0; - - QTextLayout text_layout(content, painter.font()); - text_layout.beginLayout(); - for (;;) { - QTextLine line = text_layout.createLine(); - if (!line.isValid()) - break; - - line.setLineWidth(width()); - - if (height() >= y + (2 * line_spacing)) { - line.draw(&painter, QPoint(0, y)); - y += line_spacing; - } else { - QString last_line = content.mid(line.textStart()); - QString elided_last_line = metrics.elidedText(last_line, Qt::ElideRight, width()); - painter.drawText(QPoint(0, y + metrics.ascent()), elided_last_line); - line = text_layout.createLine(); - break; - } - } - text_layout.endLayout(); -}
--- a/src/gui/widgets/poster.cc Sun Jun 23 10:32:09 2024 -0400 +++ b/src/gui/widgets/poster.cc Tue Jun 25 11:19:54 2024 -0400 @@ -16,12 +16,14 @@ #include <QThread> #include <QUrl> +#include <iostream> + Poster::Poster(QWidget* parent) : QFrame(parent) { QHBoxLayout* layout = new QHBoxLayout(this); layout->setContentsMargins(1, 1, 1, 1); setCursor(Qt::PointingHandCursor); - setFixedSize(150, 225); + setFixedSize(150, 225); // FIXME need to kill this setFrameShape(QFrame::Box); setFrameShadow(QFrame::Plain); @@ -101,6 +103,22 @@ label_.setPixmap(pixmap.scaled(label_.size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); } +bool Poster::hasHeightForWidth(void) const { + return true; +} + +int Poster::heightForWidth(int w) const { + return static_cast<int>(static_cast<double>(w) * 225 / 150); +} + void Poster::resizeEvent(QResizeEvent*) { RenderToLabel(); } + +QSize Poster::minimumSizeHint() const { + return QSize(120, heightForWidth(120)); +} + +QSize Poster::sizeHint() const { + return QSize(150, heightForWidth(150)); +}
--- a/src/gui/widgets/text.cc Sun Jun 23 10:32:09 2024 -0400 +++ b/src/gui/widgets/text.cc Tue Jun 25 11:19:54 2024 -0400 @@ -1,5 +1,6 @@ #include "gui/widgets/text.h" #include "core/session.h" +#include "core/strings.h" #include <QDebug> #include <QFrame> @@ -9,272 +10,128 @@ #include <QScrollArea> #include <QDebug> -/* WARNING: GARBAGE CODE FOLLOWS - * - * This file is filled with spaghetti to make this - * stupid text render how I want it to. - * - * many cases of hacking with setSizePolicy() are seen - * all around this file. Edit it only if really - * necessary, please. -*/ - namespace TextWidgets { -Header::Header(const QString& title, QWidget* parent) +/* Generic header meant to be used in conjunction with Section<T> */ + +Header::Header(QWidget* parent) : QWidget(parent) - , static_text_title(title) { + , title_(new QLabel(this)) + , separator_(new QFrame(this)) { QVBoxLayout* layout = new QVBoxLayout(this); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - static_text_title.setTextFormat(Qt::PlainText); + title_->setTextFormat(Qt::PlainText); { - QFont font = static_text_title.font(); + QFont font = title_->font(); font.setWeight(QFont::Bold); - static_text_title.setFont(font); + title_->setFont(font); } - static_text_line.setFrameShape(QFrame::HLine); - static_text_line.setFrameShadow(QFrame::Sunken); - static_text_line.setFixedHeight(2); + separator_->setFrameShape(QFrame::HLine); + separator_->setFrameShadow(QFrame::Sunken); + separator_->setFixedHeight(2); - layout->addWidget(&static_text_title); - layout->addWidget(&static_text_line); + layout->addWidget(title_.data()); + layout->addWidget(separator_.data()); layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); } -void Header::SetText(const QString& text) { - static_text_title.setText(text); +void Header::SetText(const std::string& text) { + title_->setText(Strings::ToQString(text)); updateGeometry(); } -Paragraph::Paragraph(const QString& text, QWidget* parent) : QWidget(parent) { - /* meh */ - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - - QVBoxLayout* layout = new QVBoxLayout(this); - layout->setSpacing(0); - layout->setContentsMargins(0, 0, 0, 0); - - text_edit.setTextInteractionFlags(Qt::TextBrowserInteraction); - text_edit.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - text_edit.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - text_edit.setStyleSheet("background: transparent;"); - text_edit.setFrameStyle(QFrame::NoFrame); - - text_edit.document()->setDocumentMargin(0); - - SetText(text); - - layout->addWidget(&text_edit); -} - -void Paragraph::SetText(const QString& text) { - text_edit.document()->setPlainText(text); +/* ---------------------------------------------------------------------------------- */ +/* "Paragraph" widgets, as in widgets meant to hold a bunch of text. */ - /* return the view to the start */ - QTextCursor cursor = text_edit.textCursor(); - cursor.setPosition(0); - text_edit.setTextCursor(cursor); -} - -bool Paragraph::hasHeightForWidth() const { - return true; -} - -int Paragraph::heightForWidth(int w) const { - QTextDocument* doc = text_edit.document(); - doc->setTextWidth(w); - - return doc->size().toSize().height(); -} - -QPlainTextEdit* Paragraph::GetLabel() { - return &text_edit; -} - -Line::Line(QWidget* parent) : QWidget(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); +Paragraph::Paragraph(QWidget *parent) : QWidget(parent), label_(new QLabel) { + QVBoxLayout *layout = new QVBoxLayout(this); layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); - line_edit_.setReadOnly(true); - line_edit_.setFrame(false); - line_edit_.setStyleSheet("background: transparent;"); + label_->setTextInteractionFlags(Qt::TextBrowserInteraction); - layout->addWidget(&line_edit_); -} + /* defaults */ + SetWordWrap(true); + SetSelectable(true); -Line::Line(const QString& text, QWidget* parent) : Line(parent) { - SetText(text); + layout->addWidget(label_.data()); } -void Line::SetText(const QString& text) { - line_edit_.setText(text); - line_edit_.setCursorPosition(0); +void Paragraph::SetText(const std::string& text) { + label_->setText(Strings::ToQString(text)); } -Title::Title(const QString& title, QWidget* parent) : Line(title, parent) { - QFont fnt(line_edit_.font()); - fnt.setPixelSize(16); - line_edit_.setFont(fnt); - - line_edit_.setForegroundRole(QPalette::Highlight); +void Paragraph::SetSelectable(bool enable) { + label_->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents, !enable); + label_->setCursor(enable ? Qt::IBeamCursor : Qt::ArrowCursor); } -Section::Section(const QString& title, const QString& data, QWidget* parent) - : QWidget(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); - - header = new Header(title, this); - - QWidget* content = new QWidget(this); - QHBoxLayout* content_layout = new QHBoxLayout(content); - - paragraph = new Paragraph(data, this); - paragraph->GetLabel()->setTextInteractionFlags(Qt::NoTextInteraction); - paragraph->GetLabel()->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents); - paragraph->GetLabel()->setWordWrapMode(QTextOption::NoWrap); - - content_layout->addWidget(paragraph); - content_layout->setSpacing(0); - content_layout->setContentsMargins(0, 0, 0, 0); - content->setContentsMargins(12, 0, 0, 0); - - layout->addWidget(header); - layout->addWidget(paragraph); - layout->setSpacing(0); - layout->setContentsMargins(0, 0, 0, 0); +void Paragraph::SetWordWrap(bool enable) { + label_->setWordWrap(enable); } -Header* Section::GetHeader() { - return header; -} +/* LabelledParagraph implementation */ -Paragraph* Section::GetParagraph() { - return paragraph; -} - -/* despite being named a "labelled paragraph" this uses QLabels for simplicity */ -LabelledParagraph::LabelledParagraph(const QString& label, const QString& data, QWidget* parent) +LabelledParagraph::LabelledParagraph(QWidget* parent) : QWidget(parent) - , labels_(label) - , data_(data) { + , contents_(new QWidget) + , contents_layout_(new QGridLayout) { QHBoxLayout* ly = new QHBoxLayout(this); - labels_.setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - data_.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + contents_layout_->setVerticalSpacing(1); + contents_layout_->setHorizontalSpacing(20); + contents_layout_->setContentsMargins(0, 0, 0, 0); + contents_layout_->setColumnStretch(1, 0); - ly->addWidget(&labels_, 0, Qt::AlignTop); - ly->addWidget(&data_, 0, Qt::AlignTop); - ly->setSpacing(20); + contents_->setLayout(contents_layout_.data()); + + ly->addWidget(contents_.data()); ly->setContentsMargins(0, 0, 0, 0); } -QLabel* LabelledParagraph::GetLabels() { - return &labels_; -} - -QLabel* LabelledParagraph::GetData() { - return &data_; -} - -QLabel* LabelledParagraph::GetParagraph() { - return GetData(); +LabelledParagraph::~LabelledParagraph() { + data_.clear(); } -LabelledSection::LabelledSection(const QString& title, const QString& label, const QString& data, QWidget* parent) - : QWidget(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); - - header = new Header(title, this); - - // this is not accessible from the object because there's really - // no reason to make it accessible... - content = new LabelledParagraph(label, data, this); - content->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - content->setContentsMargins(12, 0, 0, 0); +void LabelledParagraph::Clear(void) { + for (auto& [label, data] : data_) { + contents_layout_->removeWidget(label.data()); + contents_layout_->removeWidget(data.data()); + } - layout->addWidget(header); - layout->addWidget(content); - layout->setSpacing(0); - layout->setContentsMargins(0, 0, 0, 0); -} - -Header* LabelledSection::GetHeader() { - return header; -} - -QLabel* LabelledSection::GetLabels() { - return content->GetLabels(); -} - -QLabel* LabelledSection::GetData() { - return content->GetData(); -} - -QLabel* LabelledSection::GetParagraph() { - return content->GetParagraph(); + data_.clear(); } -SelectableSection::SelectableSection(const QString& title, const QString& data, QWidget* parent) : QWidget(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); - - header = new Header(title, this); +void LabelledParagraph::SetData(const std::vector<std::pair<std::string, std::string>>& data) { + Clear(); - QWidget* content = new QWidget(this); - QHBoxLayout* content_layout = new QHBoxLayout(content); - - paragraph = new Paragraph(data, content); + data_.reserve(data.size()); + for (std::size_t i = 0; i < data.size(); i++) { + QSharedPointer<QLabel> first(new QLabel); + QSharedPointer<QLabel> second(new QLabel); - content_layout->addWidget(paragraph); - content_layout->setSpacing(0); - content_layout->setContentsMargins(12, 0, 0, 0); - content->setContentsMargins(0, 0, 0, 0); + first->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + + first->setText(Strings::ToQString(data[i].first)); + second->setText(Strings::ToQString(data[i].second)); - layout->addWidget(header); - layout->addWidget(content); - layout->setSpacing(0); - layout->setContentsMargins(0, 0, 0, 0); -} + data_.push_back({first, second}); -Header* SelectableSection::GetHeader() { - return header; + contents_layout_->addWidget(first.data(), i, 0); + contents_layout_->addWidget(second.data(), i, 1); + } } -Paragraph* SelectableSection::GetParagraph() { - return paragraph; -} - -OneLineSection::OneLineSection(const QString& title, const QString& text, QWidget* parent) : QWidget(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); - - header = new Header(title, this); - - QWidget* content = new QWidget(this); - QHBoxLayout* content_layout = new QHBoxLayout(content); - - line = new Line(text, content); +void LabelledParagraph::SetStyle(int style) { + const QString style_sheet = (style & LabelledParagraph::BoldedLabels) ? "font-weight: bold;" : ""; + for (auto& [label, data] : data_) + label->setStyleSheet(style_sheet); - content_layout->addWidget(line); - content_layout->setSpacing(0); - content_layout->setContentsMargins(0, 0, 0, 0); - content->setContentsMargins(12, 0, 0, 0); - - layout->addWidget(header); - layout->addWidget(content); - layout->setSpacing(0); - layout->setContentsMargins(0, 0, 0, 0); -} - -Header* OneLineSection::GetHeader() { - return header; -} - -Line* OneLineSection::GetLine() { - return line; + // TODO ElidedData } } // namespace TextWidgets