Mercurial > minori
changeset 10:4b198a111713
Update
things actually compile now btw
qttest wants to fuck over the model but that might be my fault so /shrug
author | Paper <mrpapersonic@gmail.com> |
---|---|
date | Sat, 16 Sep 2023 02:06:01 -0400 |
parents | 5c0397762b53 |
children | fc1bf97c528b |
files | CMakeLists.txt include/core/anime.h include/core/anime_db.h include/core/session.h include/core/strings.h include/gui/pages/anime_list.h include/gui/sidebar.h include/gui/translate/anime.h include/services/anilist.h include/services/services.h src/core/anime.cpp src/core/anime_db.cpp src/core/date.cpp src/gui/dialog/information.cpp src/gui/dialog/settings/application.cpp src/gui/dialog/settings/services.cpp src/gui/pages/anime_list.cpp src/gui/pages/statistics.cpp src/gui/translate/anime.cpp src/gui/window.cpp src/main.cpp src/services/anilist.cpp src/services/services.cpp |
diffstat | 23 files changed, 1237 insertions(+), 1164 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Sun Sep 10 03:59:16 2023 -0400 +++ b/CMakeLists.txt Sat Sep 16 02:06:01 2023 -0400 @@ -33,7 +33,11 @@ src/gui/pages/now_playing.cpp src/gui/pages/statistics.cpp + # Translate + src/gui/translate/anime.cpp + # Services (only AniList for now) + src/services/services.cpp src/services/anilist.cpp # Qt resources @@ -55,11 +59,12 @@ set_property(TARGET weeaboo PROPERTY AUTOMOC ON) set_property(TARGET weeaboo PROPERTY AUTORCC ON) -find_package(Qt5 COMPONENTS Widgets REQUIRED) +find_package(Qt5 COMPONENTS Widgets Test REQUIRED) find_package(CURL REQUIRED) set(LIBRARIES ${Qt5Widgets_LIBRARIES} + ${Qt5Test_LIBRARIES} ${CURL_LIBRARIES} anitomy ) @@ -69,7 +74,7 @@ list(APPEND LIBRARIES ${COCOA_LIBRARY}) endif() -target_include_directories(weeaboo PUBLIC ${Qt5Widgets_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS} PRIVATE include) +target_include_directories(weeaboo PUBLIC ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Test_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS} PRIVATE include) target_compile_options(weeaboo PRIVATE -Wall -Wextra -Wsuggest-override) if(APPLE) target_compile_definitions(weeaboo PUBLIC MACOSX)
--- a/include/core/anime.h Sun Sep 10 03:59:16 2023 -0400 +++ b/include/core/anime.h Sat Sep 16 02:06:01 2023 -0400 @@ -111,6 +111,7 @@ std::string GetUserNotes() const; void SetUserStatus(ListStatus status); + void SetUserScore(int score); void SetUserProgress(int progress); void SetUserDateStarted(Date const& started); void SetUserDateCompleted(Date const& completed); @@ -163,7 +164,7 @@ private: SeriesInformation info_; - std::unique_ptr<struct ListInformation> list_info_; + std::shared_ptr<struct ListInformation> list_info_; }; } // namespace Anime
--- a/include/core/anime_db.h Sun Sep 10 03:59:16 2023 -0400 +++ b/include/core/anime_db.h Sat Sep 16 02:06:01 2023 -0400 @@ -1,23 +1,23 @@ -#ifndef __core__anime_db_h -#define __core__anime_db_h -#include <unordered_map> - -namespace Anime { - -class Anime; - -class Database { - public: - std::unordered_map<int, Anime> items; - int GetTotalAnimeAmount(); - int GetTotalEpisodeAmount(); - int GetTotalWatchedAmount(); - int GetTotalPlannedAmount(); - double GetAverageScore(); - double GetScoreDeviation(); -}; - -inline Database db; - -} // namespace Anime -#endif // __core__anime_db_h +#ifndef __core__anime_db_h +#define __core__anime_db_h +#include "core/anime.h" +#include <unordered_map> + +namespace Anime { + +class Database { + public: + std::unordered_map<int, Anime> items; + int GetTotalAnimeAmount(); + int GetTotalEpisodeAmount(); + int GetTotalWatchedAmount(); + int GetTotalPlannedAmount(); + double GetAverageScore(); + double GetScoreDeviation(); + int GetListsAnimeAmount(ListStatus status); +}; + +inline Database db; + +} // namespace Anime +#endif // __core__anime_db_h
--- a/include/core/session.h Sun Sep 10 03:59:16 2023 -0400 +++ b/include/core/session.h Sat Sep 16 02:06:01 2023 -0400 @@ -1,17 +1,17 @@ -#ifndef __core__session_h -#define __core__session_h -#include "core/config.h" -#include <QElapsedTimer> - -struct Session { - Config config; - Session() { timer.start(); } - int uptime() { return timer.elapsed(); } - - private: - QElapsedTimer timer; -}; - -extern Session session; - +#ifndef __core__session_h +#define __core__session_h +#include "core/config.h" +#include <QElapsedTimer> + +struct Session { + Config config; + Session() { timer.start(); } + int uptime() { return timer.elapsed(); } + + private: + QElapsedTimer timer; +}; + +extern Session session; + #endif // __core__session_h \ No newline at end of file
--- a/include/core/strings.h Sun Sep 10 03:59:16 2023 -0400 +++ b/include/core/strings.h Sat Sep 16 02:06:01 2023 -0400 @@ -2,7 +2,7 @@ #define __core__strings_h #include <string> #include <vector> -namespace StringUtils { +namespace Strings { /* Implode function: takes a vector of strings and turns it into a string, separated by delimiters. */ std::string Implode(const std::vector<std::string>& vector, const std::string& delimiter);
--- a/include/gui/pages/anime_list.h Sun Sep 10 03:59:16 2023 -0400 +++ b/include/gui/pages/anime_list.h Sat Sep 16 02:06:01 2023 -0400 @@ -1,92 +1,98 @@ -#ifndef __gui__pages__anime_list_h -#define __gui__pages__anime_list_h -#include "core/anime.h" -#include <QAbstractListModel> -#include <QSortFilterProxyModel> -#include <QStyledItemDelegate> -#include <QTreeView> -#include <QWidget> -#include <vector> - -class AnimeListWidgetDelegate : public QStyledItemDelegate { - Q_OBJECT - - public: - explicit AnimeListWidgetDelegate(QObject* parent); - - QWidget* createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const override; - void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; -}; - -class AnimeListWidgetSortFilter : public QSortFilterProxyModel { - Q_OBJECT - - public: - AnimeListWidgetSortFilter(QObject* parent = nullptr); - - protected: - bool lessThan(const QModelIndex& l, const QModelIndex& r) const override; -}; - -class AnimeListWidgetModel : public QAbstractListModel { - Q_OBJECT - - public: - enum columns { - AL_TITLE, - AL_PROGRESS, - AL_EPISODES, - AL_SCORE, - AL_AVG_SCORE, - AL_TYPE, - AL_SEASON, - AL_STARTED, - AL_COMPLETED, - AL_UPDATED, - AL_NOTES, - AL_ID, /* Note: This is only used in Qt::UserRole to make my life easier */ - - NB_COLUMNS - }; - - AnimeListWidgetModel(QWidget* parent); - ~AnimeListWidgetModel() override = default; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex& index, int role) const override; - QVariant headerData(const int section, const Qt::Orientation orientation, const int role) const override; - void UpdateAnime(int id); -}; - -/* todo: rename these to "page" or something more - sensible than "widget" */ -class AnimeListWidget : public QWidget { - Q_OBJECT - - public: - AnimeListWidget(QWidget* parent); - void UpdateAnimeList(); - void Reset(); - - protected: - void paintEvent(QPaintEvent*) override; - void InitStyle(QStyleOptionTabWidgetFrame* option) const; - void InitBasicStyle(QStyleOptionTabWidgetFrame* option) const; - void SetupLayout(); - void showEvent(QShowEvent*) override; - void resizeEvent(QResizeEvent* e) override; - - private slots: - void DisplayColumnHeaderMenu(); - void DisplayListMenu(); - void ItemDoubleClicked(); - void SetColumnDefaults(); - int VisibleColumnsCount() const; - - private: - QTabBar* tab_bar; - QTreeView* tree_view; - QRect panelRect; - AnimeListWidgetSortFilter* sort_models[5]; -}; +#ifndef __gui__pages__anime_list_h +#define __gui__pages__anime_list_h +#include "core/anime.h" +#include <QAbstractListModel> +#include <QSortFilterProxyModel> +#include <QStyledItemDelegate> +#include <QTreeView> +#include <QWidget> +#include <vector> + +class AnimeListWidgetDelegate : public QStyledItemDelegate { + Q_OBJECT + + public: + explicit AnimeListWidgetDelegate(QObject* parent); + + QWidget* createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const override; + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; +}; + +class AnimeListWidgetSortFilter : public QSortFilterProxyModel { + Q_OBJECT + + public: + AnimeListWidgetSortFilter(QObject* parent = nullptr); + + protected: + bool lessThan(const QModelIndex& l, const QModelIndex& r) const override; +}; + +class AnimeListWidgetModel : public QAbstractListModel { + Q_OBJECT + + public: + enum columns { + AL_TITLE, + AL_PROGRESS, + AL_EPISODES, + AL_SCORE, + AL_AVG_SCORE, + AL_TYPE, + AL_SEASON, + AL_STARTED, + AL_COMPLETED, + AL_UPDATED, + AL_NOTES, + AL_ID, /* Note: This is only used in Qt::UserRole to make my life easier */ + + NB_COLUMNS + }; + + AnimeListWidgetModel(QWidget* parent, Anime::ListStatus _status); + ~AnimeListWidgetModel() override = default; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role) const override; + QVariant headerData(const int section, const Qt::Orientation orientation, const int role) const override; + void UpdateAnime(int id); + void RefreshList(); + Anime::Anime* GetAnimeFromIndex(QModelIndex index); + + private: + Anime::ListStatus status; + std::vector<Anime::Anime> list; +}; + +/* todo: rename these to "page" or something more + sensible than "widget" */ +class AnimeListWidget : public QWidget { + Q_OBJECT + + public: + AnimeListWidget(QWidget* parent); + void RefreshList(); + void Reset(); + + protected: + void paintEvent(QPaintEvent*) override; + void InitStyle(QStyleOptionTabWidgetFrame* option) const; + void InitBasicStyle(QStyleOptionTabWidgetFrame* option) const; + void SetupLayout(); + void showEvent(QShowEvent*) override; + void resizeEvent(QResizeEvent* e) override; + + private slots: + void DisplayColumnHeaderMenu(); + void DisplayListMenu(); + void ItemDoubleClicked(); + void SetColumnDefaults(); + int VisibleColumnsCount() const; + + private: + QTabBar* tab_bar; + QTreeView* tree_view; + QRect panelRect; + AnimeListWidgetSortFilter* sort_models[5]; +}; #endif // __gui__pages__anime_list_h \ No newline at end of file
--- a/include/gui/sidebar.h Sun Sep 10 03:59:16 2023 -0400 +++ b/include/gui/sidebar.h Sat Sep 16 02:06:01 2023 -0400 @@ -1,25 +1,25 @@ -#ifndef __gui__sidebar_h -#define __gui__sidebar_h -#include <QItemSelectionModel> -#include <QListWidget> -#include <QListWidgetItem> -class SideBar : public QListWidget { - Q_OBJECT - - public: - SideBar(QWidget* parent = nullptr); - QListWidgetItem* AddItem(QString name, QIcon icon = QIcon()); - QListWidgetItem* AddSeparator(); - bool IndexIsSeparator(QModelIndex index) const; - static QIcon CreateIcon(const char* file); - - signals: - void CurrentItemChanged(int index); - - protected: - virtual void mouseMoveEvent(QMouseEvent* event) override; - QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex& index, - const QEvent* event) const override; - int RemoveSeparatorsFromIndex(int index); -}; -#endif // __gui__sidebar_h +#ifndef __gui__sidebar_h +#define __gui__sidebar_h +#include <QItemSelectionModel> +#include <QListWidget> +#include <QListWidgetItem> +class SideBar : public QListWidget { + Q_OBJECT + + public: + SideBar(QWidget* parent = nullptr); + QListWidgetItem* AddItem(QString name, QIcon icon = QIcon()); + QListWidgetItem* AddSeparator(); + bool IndexIsSeparator(QModelIndex index) const; + static QIcon CreateIcon(const char* file); + + signals: + void CurrentItemChanged(int index); + + protected: + virtual void mouseMoveEvent(QMouseEvent* event) override; + QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex& index, + const QEvent* event) const override; + int RemoveSeparatorsFromIndex(int index); +}; +#endif // __gui__sidebar_h
--- a/include/gui/translate/anime.h Sun Sep 10 03:59:16 2023 -0400 +++ b/include/gui/translate/anime.h Sat Sep 16 02:06:01 2023 -0400 @@ -1,10 +1,10 @@ -#include "core/anime.h" - -namespace Translate { - -std::string TranslateListStatus(const Anime::ListStatus status); -std::string TranslateSeriesFormat(const Anime::SeriesFormat format); -std::string TranslateSeriesSeason(const Anime::SeriesSeason season); -std::string TranslateSeriesStatus(const Anime::SeriesStatus status); - -} // namespace Translate +#include "core/anime.h" + +namespace Translate { + +std::string TranslateListStatus(const Anime::ListStatus status); +std::string TranslateSeriesFormat(const Anime::SeriesFormat format); +std::string TranslateSeriesSeason(const Anime::SeriesSeason season); +std::string TranslateSeriesStatus(const Anime::SeriesStatus status); + +} // namespace Translate
--- a/include/services/anilist.h Sun Sep 10 03:59:16 2023 -0400 +++ b/include/services/anilist.h Sat Sep 16 02:06:01 2023 -0400 @@ -3,8 +3,8 @@ #include "core/anime.h" #include "core/json.h" #include <curl/curl.h> -namespace AniList { -int Authorize(); +namespace Services::AniList { +int AuthorizeUser(); /* Read queries */ int GetAnimeList();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/include/services/services.h Sat Sep 16 02:06:01 2023 -0400 @@ -0,0 +1,11 @@ +#ifndef __services__services_h +#define __services__services_h + +namespace Services { + +void Synchronize(); +void Authorize(); + +}; + +#endif // __services__services_h \ No newline at end of file
--- a/src/core/anime.cpp Sun Sep 10 03:59:16 2023 -0400 +++ b/src/core/anime.cpp Sat Sep 16 02:06:01 2023 -0400 @@ -21,14 +21,10 @@ } void Anime::AddToUserList() { - if (!list_info_.get()) - return; list_info_.reset(new ListInformation); } void Anime::RemoveFromUserList() { - if (list_info_.get()) - return; list_info_.reset(); } @@ -87,6 +83,11 @@ list_info_->status = status; } +void Anime::SetUserScore(int score) { + assert(list_info_.get()); + list_info_->score = score; +} + void Anime::SetUserProgress(int progress) { assert(list_info_.get()); list_info_->progress = progress;
--- a/src/core/anime_db.cpp Sun Sep 10 03:59:16 2023 -0400 +++ b/src/core/anime_db.cpp Sat Sep 16 02:06:01 2023 -0400 @@ -1,78 +1,89 @@ -#include "core/anime_db.h" -#include "core/anime.h" - -namespace Anime { - -int Database::GetTotalAnimeAmount() { - int total = 0; - for (const auto& [id, anime] : items) { - if (anime.IsInUserList()) - total++; - } - return total; -} - -int Database::GetTotalEpisodeAmount() { - int total = 0; - for (const auto& [id, anime] : items) { - if (anime.IsInUserList()) { - total += anime.GetUserRewatchedTimes() * anime.GetEpisodes(); - total += anime.GetUserProgress(); - } - } - return total; -} - -/* Returns the total watched amount in minutes. */ -int Database::GetTotalWatchedAmount() { - int total = 0; - for (const auto& [id, anime] : items) { - if (anime.IsInUserList()) { - total += anime.GetDuration() * anime.GetUserProgress(); - total += anime.GetEpisodes() * anime.GetDuration() * anime.GetUserRewatchedTimes(); - } - } - return total; -} - -/* Returns the total planned amount in minutes. - Note that we should probably limit progress to the - amount of episodes, as AniList will let you - set episode counts up to 32768. But that should - rather be handled elsewhere. */ -int Database::GetTotalPlannedAmount() { - int total = 0; - for (const auto& [id, anime] : items) { - if (anime.IsInUserList()) - total += anime.GetDuration() * (anime.GetEpisodes() - anime.GetUserProgress()); - } - return total; -} - -/* I'm sure many will appreciate this being called an - "average" instead of a "mean" */ -double Database::GetAverageScore() { - double avg = 0; - int amt = 0; - for (const auto& [id, anime] : items) { - if (anime.IsInUserList() && anime.GetUserScore()) { - avg += anime.GetUserScore(); - amt++; - } - } - return avg / amt; -} - -double Database::GetScoreDeviation() { - double squares_sum = 0, avg = GetAverageScore(); - int amt = 0; - for (const auto& [id, anime] : items) { - if (anime.GetUserScore()) { - squares_sum += std::pow((double)anime.GetUserScore() - avg, 2); - amt++; - } - } - return (amt > 0) ? std::sqrt(squares_sum / amt) : 0; -} - +#include "core/anime_db.h" +#include "core/anime.h" + +namespace Anime { + +int Database::GetTotalAnimeAmount() { + int total = 0; + for (const auto& [id, anime] : items) { + if (anime.IsInUserList()) + total++; + } + return total; +} + +int Database::GetListsAnimeAmount(ListStatus status) { + if (status == ListStatus::NOT_IN_LIST) + return 0; + int total = 0; + for (const auto& [id, anime] : items) { + if (anime.IsInUserList() && anime.GetUserStatus() == status) + total++; + } + return total; +} + +int Database::GetTotalEpisodeAmount() { + int total = 0; + for (const auto& [id, anime] : items) { + if (anime.IsInUserList()) { + total += anime.GetUserRewatchedTimes() * anime.GetEpisodes(); + total += anime.GetUserProgress(); + } + } + return total; +} + +/* Returns the total watched amount in minutes. */ +int Database::GetTotalWatchedAmount() { + int total = 0; + for (const auto& [id, anime] : items) { + if (anime.IsInUserList()) { + total += anime.GetDuration() * anime.GetUserProgress(); + total += anime.GetEpisodes() * anime.GetDuration() * anime.GetUserRewatchedTimes(); + } + } + return total; +} + +/* Returns the total planned amount in minutes. + Note that we should probably limit progress to the + amount of episodes, as AniList will let you + set episode counts up to 32768. But that should + rather be handled elsewhere. */ +int Database::GetTotalPlannedAmount() { + int total = 0; + for (const auto& [id, anime] : items) { + if (anime.IsInUserList()) + total += anime.GetDuration() * (anime.GetEpisodes() - anime.GetUserProgress()); + } + return total; +} + +/* I'm sure many will appreciate this being called an + "average" instead of a "mean" */ +double Database::GetAverageScore() { + double avg = 0; + int amt = 0; + for (const auto& [id, anime] : items) { + if (anime.IsInUserList() && anime.GetUserScore()) { + avg += anime.GetUserScore(); + amt++; + } + } + return avg / amt; +} + +double Database::GetScoreDeviation() { + double squares_sum = 0, avg = GetAverageScore(); + int amt = 0; + for (const auto& [id, anime] : items) { + if (anime.GetUserScore()) { + squares_sum += std::pow((double)anime.GetUserScore() - avg, 2); + amt++; + } + } + return (amt > 0) ? std::sqrt(squares_sum / amt) : 0; +} + } // namespace Anime \ No newline at end of file
--- a/src/core/date.cpp Sun Sep 10 03:59:16 2023 -0400 +++ b/src/core/date.cpp Sat Sep 16 02:06:01 2023 -0400 @@ -103,7 +103,10 @@ } QDate Date::GetAsQDate() const { - return QDate(*year, *month, *day); + /* QDates don't (yet) support "missing" values */ + if (year.get() && month.get() && day.get()) + return QDate(*year, *month, *day); + else return QDate(); } nlohmann::json Date::GetAsAniListJson() const {
--- a/src/gui/dialog/information.cpp Sun Sep 10 03:59:16 2023 -0400 +++ b/src/gui/dialog/information.cpp Sat Sep 16 02:06:01 2023 -0400 @@ -53,7 +53,7 @@ /* alt titles */ main_information_widget->layout()->addWidget(new UiUtils::SelectableTextParagraph( - "Alternative titles", QString::fromUtf8(StringUtils::Implode(anime.GetTitleSynonyms(), ", ").c_str()), + "Alternative titles", QString::fromUtf8(Strings::Implode(anime.GetTitleSynonyms(), ", ").c_str()), main_information_widget)); /* details */ @@ -64,7 +64,7 @@ << Translate::TranslateListStatus(anime.GetUserStatus()).c_str() << "\n" << Translate::TranslateSeriesSeason(anime.GetSeason()).c_str() << " " << anime.GetAirDate().GetYear() << "\n" - << StringUtils::Implode(anime.GetGenres(), ", ").c_str() << "\n" + << Strings::Implode(anime.GetGenres(), ", ").c_str() << "\n" << anime.GetAudienceScore() << "%"; main_information_widget->layout()->addWidget(new UiUtils::LabelledTextParagraph( "Details", "Type:\nEpisodes:\nStatus:\nSeason:\nGenres:\nScore:", details_data, main_information_widget));
--- a/src/gui/dialog/settings/application.cpp Sun Sep 10 03:59:16 2023 -0400 +++ b/src/gui/dialog/settings/application.cpp Sat Sep 16 02:06:01 2023 -0400 @@ -1,125 +1,125 @@ -#include "core/session.h" -#include "gui/dialog/settings.h" -#include <QCheckBox> -#include <QComboBox> -#include <QGroupBox> -#include <QPushButton> -#include <QSizePolicy> - -QWidget* SettingsPageApplication::CreateAnimeListWidget() { - QWidget* result = new QWidget(this); - result->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); - - QGroupBox* actions_group_box = new QGroupBox(tr("Actions"), result); - actions_group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); - - /* Actions/Double click */ - QWidget* double_click_widget = new QWidget(actions_group_box); - QLabel* dc_combo_box_label = new QLabel(tr("Double click:"), double_click_widget); - QComboBox* dc_combo_box = new QComboBox(double_click_widget); - dc_combo_box->addItem(tr("View anime info")); - - QVBoxLayout* double_click_layout = new QVBoxLayout; - double_click_layout->addWidget(dc_combo_box_label); - double_click_layout->addWidget(dc_combo_box); - double_click_layout->setMargin(0); - double_click_widget->setLayout(double_click_layout); - - /* Actions/Middle click */ - QWidget* middle_click_widget = new QWidget(actions_group_box); - QLabel* mc_combo_box_label = new QLabel(tr("Middle click:"), middle_click_widget); - QComboBox* mc_combo_box = new QComboBox(middle_click_widget); - mc_combo_box->addItem(tr("Play next episode")); - - QVBoxLayout* middle_click_layout = new QVBoxLayout; - middle_click_layout->addWidget(mc_combo_box_label); - middle_click_layout->addWidget(mc_combo_box); - middle_click_layout->setMargin(0); - middle_click_widget->setLayout(middle_click_layout); - - /* Actions */ - QHBoxLayout* actions_layout = new QHBoxLayout; - actions_layout->addWidget(double_click_widget); - actions_layout->addWidget(middle_click_widget); - actions_group_box->setLayout(actions_layout); - - QGroupBox* appearance_group_box = new QGroupBox(tr("Appearance"), result); - appearance_group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); - - QLabel* lang_combo_box_label = new QLabel(tr("Title language preference:"), appearance_group_box); - QComboBox* lang_combo_box = new QComboBox(appearance_group_box); - lang_combo_box->addItem(tr("Romaji")); - lang_combo_box->addItem(tr("Native")); - lang_combo_box->addItem(tr("English")); - connect(lang_combo_box, QOverload<int>::of(&QComboBox::currentIndexChanged), this, - [this](int index) { language = static_cast<Anime::TitleLanguage>(index); }); - lang_combo_box->setCurrentIndex(static_cast<int>(language)); - - QCheckBox* hl_anime_box = - new QCheckBox(tr("Highlight anime if next episode is available in library folders"), appearance_group_box); - QCheckBox* hl_above_anime_box = new QCheckBox(tr("Display highlighted anime above others"), appearance_group_box); - connect(hl_anime_box, &QCheckBox::stateChanged, this, [this, hl_above_anime_box](int state) { - highlight_anime_if_available = (state == Qt::Unchecked) ? false : true; - hl_above_anime_box->setEnabled(state); - }); - connect(hl_above_anime_box, &QCheckBox::stateChanged, this, - [this](int state) { highlight_anime_if_available = (state == Qt::Unchecked) ? false : true; }); - hl_anime_box->setCheckState(highlight_anime_if_available ? Qt::Checked : Qt::Unchecked); - hl_above_anime_box->setCheckState(highlighted_anime_above_others ? Qt::Checked : Qt::Unchecked); - hl_above_anime_box->setEnabled(hl_anime_box->checkState() != Qt::Unchecked); - hl_above_anime_box->setStyleSheet("margin-left: 10px;"); - - /* Appearance */ - QVBoxLayout* appearance_layout = new QVBoxLayout; - appearance_layout->addWidget(lang_combo_box_label); - appearance_layout->addWidget(lang_combo_box); - appearance_layout->addWidget(hl_anime_box); - appearance_layout->addWidget(hl_above_anime_box); - appearance_group_box->setLayout(appearance_layout); - - QGroupBox* progress_group_box = new QGroupBox(tr("Progress"), result); - progress_group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); - - QCheckBox* progress_display_aired_episodes = - new QCheckBox(tr("Display aired episodes (estimated)"), progress_group_box); - connect(progress_display_aired_episodes, &QCheckBox::stateChanged, this, - [this](int state) { display_aired_episodes = (state == Qt::Unchecked) ? false : true; }); - progress_display_aired_episodes->setCheckState(display_aired_episodes ? Qt::Checked : Qt::Unchecked); - - QCheckBox* progress_display_available_episodes = - new QCheckBox(tr("Display available episodes in library folders"), progress_group_box); - connect(progress_display_available_episodes, &QCheckBox::stateChanged, this, - [this](int state) { display_available_episodes = (state == Qt::Unchecked) ? false : true; }); - progress_display_available_episodes->setCheckState(display_available_episodes ? Qt::Checked : Qt::Unchecked); - - QVBoxLayout* progress_layout = new QVBoxLayout; - progress_layout->addWidget(progress_display_aired_episodes); - progress_layout->addWidget(progress_display_available_episodes); - progress_group_box->setLayout(progress_layout); - - QVBoxLayout* full_layout = new QVBoxLayout; - full_layout->addWidget(actions_group_box); - full_layout->addWidget(appearance_group_box); - full_layout->addWidget(progress_group_box); - full_layout->setSpacing(10); - full_layout->addStretch(); - result->setLayout(full_layout); - return result; -} - -void SettingsPageApplication::SaveInfo() { - session.config.anime_list.language = language; - session.config.anime_list.highlighted_anime_above_others = highlighted_anime_above_others; - session.config.anime_list.highlight_anime_if_available = highlight_anime_if_available; - session.config.anime_list.display_aired_episodes = display_aired_episodes; - session.config.anime_list.display_available_episodes = display_available_episodes; -} - -SettingsPageApplication::SettingsPageApplication(QWidget* parent) : SettingsPage(parent, tr("Application")) { - language = session.config.anime_list.language; - highlighted_anime_above_others = session.config.anime_list.highlighted_anime_above_others; - highlight_anime_if_available = session.config.anime_list.highlight_anime_if_available; - display_aired_episodes = session.config.anime_list.display_aired_episodes; - display_available_episodes = session.config.anime_list.display_available_episodes; - AddTab(CreateAnimeListWidget(), tr("Anime list")); -} +#include "core/session.h" +#include "gui/dialog/settings.h" +#include <QCheckBox> +#include <QComboBox> +#include <QGroupBox> +#include <QPushButton> +#include <QSizePolicy> + +QWidget* SettingsPageApplication::CreateAnimeListWidget() { + QWidget* result = new QWidget(this); + result->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + QGroupBox* actions_group_box = new QGroupBox(tr("Actions"), result); + actions_group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + /* Actions/Double click */ + QWidget* double_click_widget = new QWidget(actions_group_box); + QLabel* dc_combo_box_label = new QLabel(tr("Double click:"), double_click_widget); + QComboBox* dc_combo_box = new QComboBox(double_click_widget); + dc_combo_box->addItem(tr("View anime info")); + + QVBoxLayout* double_click_layout = new QVBoxLayout; + double_click_layout->addWidget(dc_combo_box_label); + double_click_layout->addWidget(dc_combo_box); + double_click_layout->setMargin(0); + double_click_widget->setLayout(double_click_layout); + + /* Actions/Middle click */ + QWidget* middle_click_widget = new QWidget(actions_group_box); + QLabel* mc_combo_box_label = new QLabel(tr("Middle click:"), middle_click_widget); + QComboBox* mc_combo_box = new QComboBox(middle_click_widget); + mc_combo_box->addItem(tr("Play next episode")); + + QVBoxLayout* middle_click_layout = new QVBoxLayout; + middle_click_layout->addWidget(mc_combo_box_label); + middle_click_layout->addWidget(mc_combo_box); + middle_click_layout->setMargin(0); + middle_click_widget->setLayout(middle_click_layout); + + /* Actions */ + QHBoxLayout* actions_layout = new QHBoxLayout; + actions_layout->addWidget(double_click_widget); + actions_layout->addWidget(middle_click_widget); + actions_group_box->setLayout(actions_layout); + + QGroupBox* appearance_group_box = new QGroupBox(tr("Appearance"), result); + appearance_group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + QLabel* lang_combo_box_label = new QLabel(tr("Title language preference:"), appearance_group_box); + QComboBox* lang_combo_box = new QComboBox(appearance_group_box); + lang_combo_box->addItem(tr("Romaji")); + lang_combo_box->addItem(tr("Native")); + lang_combo_box->addItem(tr("English")); + connect(lang_combo_box, QOverload<int>::of(&QComboBox::currentIndexChanged), this, + [this](int index) { language = static_cast<Anime::TitleLanguage>(index); }); + lang_combo_box->setCurrentIndex(static_cast<int>(language)); + + QCheckBox* hl_anime_box = + new QCheckBox(tr("Highlight anime if next episode is available in library folders"), appearance_group_box); + QCheckBox* hl_above_anime_box = new QCheckBox(tr("Display highlighted anime above others"), appearance_group_box); + connect(hl_anime_box, &QCheckBox::stateChanged, this, [this, hl_above_anime_box](int state) { + highlight_anime_if_available = (state == Qt::Unchecked) ? false : true; + hl_above_anime_box->setEnabled(state); + }); + connect(hl_above_anime_box, &QCheckBox::stateChanged, this, + [this](int state) { highlight_anime_if_available = (state == Qt::Unchecked) ? false : true; }); + hl_anime_box->setCheckState(highlight_anime_if_available ? Qt::Checked : Qt::Unchecked); + hl_above_anime_box->setCheckState(highlighted_anime_above_others ? Qt::Checked : Qt::Unchecked); + hl_above_anime_box->setEnabled(hl_anime_box->checkState() != Qt::Unchecked); + hl_above_anime_box->setStyleSheet("margin-left: 10px;"); + + /* Appearance */ + QVBoxLayout* appearance_layout = new QVBoxLayout; + appearance_layout->addWidget(lang_combo_box_label); + appearance_layout->addWidget(lang_combo_box); + appearance_layout->addWidget(hl_anime_box); + appearance_layout->addWidget(hl_above_anime_box); + appearance_group_box->setLayout(appearance_layout); + + QGroupBox* progress_group_box = new QGroupBox(tr("Progress"), result); + progress_group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + QCheckBox* progress_display_aired_episodes = + new QCheckBox(tr("Display aired episodes (estimated)"), progress_group_box); + connect(progress_display_aired_episodes, &QCheckBox::stateChanged, this, + [this](int state) { display_aired_episodes = (state == Qt::Unchecked) ? false : true; }); + progress_display_aired_episodes->setCheckState(display_aired_episodes ? Qt::Checked : Qt::Unchecked); + + QCheckBox* progress_display_available_episodes = + new QCheckBox(tr("Display available episodes in library folders"), progress_group_box); + connect(progress_display_available_episodes, &QCheckBox::stateChanged, this, + [this](int state) { display_available_episodes = (state == Qt::Unchecked) ? false : true; }); + progress_display_available_episodes->setCheckState(display_available_episodes ? Qt::Checked : Qt::Unchecked); + + QVBoxLayout* progress_layout = new QVBoxLayout; + progress_layout->addWidget(progress_display_aired_episodes); + progress_layout->addWidget(progress_display_available_episodes); + progress_group_box->setLayout(progress_layout); + + QVBoxLayout* full_layout = new QVBoxLayout; + full_layout->addWidget(actions_group_box); + full_layout->addWidget(appearance_group_box); + full_layout->addWidget(progress_group_box); + full_layout->setSpacing(10); + full_layout->addStretch(); + result->setLayout(full_layout); + return result; +} + +void SettingsPageApplication::SaveInfo() { + session.config.anime_list.language = language; + session.config.anime_list.highlighted_anime_above_others = highlighted_anime_above_others; + session.config.anime_list.highlight_anime_if_available = highlight_anime_if_available; + session.config.anime_list.display_aired_episodes = display_aired_episodes; + session.config.anime_list.display_available_episodes = display_available_episodes; +} + +SettingsPageApplication::SettingsPageApplication(QWidget* parent) : SettingsPage(parent, tr("Application")) { + language = session.config.anime_list.language; + highlighted_anime_above_others = session.config.anime_list.highlighted_anime_above_others; + highlight_anime_if_available = session.config.anime_list.highlight_anime_if_available; + display_aired_episodes = session.config.anime_list.display_aired_episodes; + display_available_episodes = session.config.anime_list.display_available_episodes; + AddTab(CreateAnimeListWidget(), tr("Anime list")); +}
--- a/src/gui/dialog/settings/services.cpp Sun Sep 10 03:59:16 2023 -0400 +++ b/src/gui/dialog/settings/services.cpp Sat Sep 16 02:06:01 2023 -0400 @@ -1,94 +1,94 @@ -#include "core/anime.h" -#include "core/session.h" -#include "gui/dialog/settings.h" -#include "services/anilist.h" -#include <QComboBox> -#include <QGroupBox> -#include <QPushButton> -#include <QSizePolicy> - -QWidget* SettingsPageServices::CreateMainPage() { - QWidget* result = new QWidget(this); - result->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); - - QGroupBox* sync_group_box = new QGroupBox(tr("Synchronization"), result); - sync_group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); - - QLabel* sync_combo_box_label = new QLabel(tr("Active service and metadata provider:"), sync_group_box); - - QComboBox* sync_combo_box = new QComboBox(sync_group_box); - sync_combo_box->addItem(tr("AniList")); - connect(sync_combo_box, QOverload<int>::of(&QComboBox::currentIndexChanged), this, - [this](int index) { service = static_cast<Anime::Services>(index + 1); }); - sync_combo_box->setCurrentIndex(static_cast<int>(service) - 1); - - QLabel* sync_note_label = - new QLabel(tr("Note: Weeaboo is unable to synchronize multiple services at the same time."), sync_group_box); - - QVBoxLayout* sync_layout = new QVBoxLayout; - sync_layout->addWidget(sync_combo_box_label); - sync_layout->addWidget(sync_combo_box); - sync_layout->addWidget(sync_note_label); - sync_group_box->setLayout(sync_layout); - - QVBoxLayout* full_layout = new QVBoxLayout; - full_layout->addWidget(sync_group_box); - full_layout->setSpacing(10); - full_layout->addStretch(); - result->setLayout(full_layout); - return result; -} - -QWidget* SettingsPageServices::CreateAniListPage() { - QWidget* result = new QWidget(this); - result->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - - QGroupBox* group_box = new QGroupBox(tr("Account"), result); - group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); - - QLabel* username_entry_label = new QLabel(tr("Username: (not your email address)"), group_box); - - QWidget* auth_widget = new QWidget(group_box); - QLineEdit* username_entry = new QLineEdit(username, auth_widget); - connect(username_entry, &QLineEdit::editingFinished, this, - [this, username_entry] { username = username_entry->text(); }); - - QPushButton* auth_button = new QPushButton(auth_widget); - connect(auth_button, &QPushButton::clicked, this, [] { AniList::Authorize(); }); - auth_button->setText(session.config.anilist.auth_token.empty() ? tr("Authorize...") : tr("Re-authorize...")); - - QHBoxLayout* auth_layout = new QHBoxLayout; - auth_layout->addWidget(username_entry); - auth_layout->addWidget(auth_button); - auth_widget->setLayout(auth_layout); - - QLabel* note_label = new QLabel(tr("<a href=\"http://anilist.co/\">Create a new AniList account</a>"), group_box); - note_label->setTextFormat(Qt::RichText); - note_label->setTextInteractionFlags(Qt::TextBrowserInteraction); - note_label->setOpenExternalLinks(true); - - QVBoxLayout* layout = new QVBoxLayout; - layout->addWidget(username_entry_label); - layout->addWidget(auth_widget); - layout->addWidget(note_label); - group_box->setLayout(layout); - - QVBoxLayout* full_layout = new QVBoxLayout; - full_layout->addWidget(group_box); - full_layout->setSpacing(10); - full_layout->addStretch(); - result->setLayout(full_layout); - return result; -} - -void SettingsPageServices::SaveInfo() { - session.config.anilist.username = username.toStdString(); - session.config.service = service; -} - -SettingsPageServices::SettingsPageServices(QWidget* parent) : SettingsPage(parent, tr("Services")) { - username = QString::fromUtf8(session.config.anilist.username.c_str()); - service = session.config.service; - AddTab(CreateMainPage(), tr("Main")); - AddTab(CreateAniListPage(), tr("AniList")); -} +#include "core/anime.h" +#include "core/session.h" +#include "gui/dialog/settings.h" +#include "services/anilist.h" +#include <QComboBox> +#include <QGroupBox> +#include <QPushButton> +#include <QSizePolicy> + +QWidget* SettingsPageServices::CreateMainPage() { + QWidget* result = new QWidget(this); + result->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + QGroupBox* sync_group_box = new QGroupBox(tr("Synchronization"), result); + sync_group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + QLabel* sync_combo_box_label = new QLabel(tr("Active service and metadata provider:"), sync_group_box); + + QComboBox* sync_combo_box = new QComboBox(sync_group_box); + sync_combo_box->addItem(tr("AniList")); + connect(sync_combo_box, QOverload<int>::of(&QComboBox::currentIndexChanged), this, + [this](int index) { service = static_cast<Anime::Services>(index + 1); }); + sync_combo_box->setCurrentIndex(static_cast<int>(service) - 1); + + QLabel* sync_note_label = + new QLabel(tr("Note: Weeaboo is unable to synchronize multiple services at the same time."), sync_group_box); + + QVBoxLayout* sync_layout = new QVBoxLayout; + sync_layout->addWidget(sync_combo_box_label); + sync_layout->addWidget(sync_combo_box); + sync_layout->addWidget(sync_note_label); + sync_group_box->setLayout(sync_layout); + + QVBoxLayout* full_layout = new QVBoxLayout; + full_layout->addWidget(sync_group_box); + full_layout->setSpacing(10); + full_layout->addStretch(); + result->setLayout(full_layout); + return result; +} + +QWidget* SettingsPageServices::CreateAniListPage() { + QWidget* result = new QWidget(this); + result->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + + QGroupBox* group_box = new QGroupBox(tr("Account"), result); + group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + QLabel* username_entry_label = new QLabel(tr("Username: (not your email address)"), group_box); + + QWidget* auth_widget = new QWidget(group_box); + QLineEdit* username_entry = new QLineEdit(username, auth_widget); + connect(username_entry, &QLineEdit::editingFinished, this, + [this, username_entry] { username = username_entry->text(); }); + + QPushButton* auth_button = new QPushButton(auth_widget); + connect(auth_button, &QPushButton::clicked, this, [] { Services::AniList::AuthorizeUser(); }); + auth_button->setText(session.config.anilist.auth_token.empty() ? tr("Authorize...") : tr("Re-authorize...")); + + QHBoxLayout* auth_layout = new QHBoxLayout; + auth_layout->addWidget(username_entry); + auth_layout->addWidget(auth_button); + auth_widget->setLayout(auth_layout); + + QLabel* note_label = new QLabel(tr("<a href=\"http://anilist.co/\">Create a new AniList account</a>"), group_box); + note_label->setTextFormat(Qt::RichText); + note_label->setTextInteractionFlags(Qt::TextBrowserInteraction); + note_label->setOpenExternalLinks(true); + + QVBoxLayout* layout = new QVBoxLayout; + layout->addWidget(username_entry_label); + layout->addWidget(auth_widget); + layout->addWidget(note_label); + group_box->setLayout(layout); + + QVBoxLayout* full_layout = new QVBoxLayout; + full_layout->addWidget(group_box); + full_layout->setSpacing(10); + full_layout->addStretch(); + result->setLayout(full_layout); + return result; +} + +void SettingsPageServices::SaveInfo() { + session.config.anilist.username = username.toStdString(); + session.config.service = service; +} + +SettingsPageServices::SettingsPageServices(QWidget* parent) : SettingsPage(parent, tr("Services")) { + username = QString::fromUtf8(session.config.anilist.username.c_str()); + service = session.config.service; + AddTab(CreateMainPage(), tr("Main")); + AddTab(CreateAniListPage(), tr("AniList")); +}
--- a/src/gui/pages/anime_list.cpp Sun Sep 10 03:59:16 2023 -0400 +++ b/src/gui/pages/anime_list.cpp Sat Sep 16 02:06:01 2023 -0400 @@ -1,468 +1,479 @@ -/** - * anime_list.cpp: defines the anime list page - * and widgets. - * - * much of this file is based around - * Qt's original QTabWidget implementation, because - * I needed a somewhat native way to create a tabbed - * widget with only one subwidget that worked exactly - * like a native tabbed widget. - **/ -#include "gui/pages/anime_list.h" -#include "core/anime.h" -#include "core/anime_db.h" -#include "core/session.h" -#include "core/time.h" -#include "gui/dialog/information.h" -#include "gui/translate/anime.h" -#include "services/anilist.h" -#include <QHBoxLayout> -#include <QHeaderView> -#include <QMenu> -#include <QProgressBar> -#include <QShortcut> -#include <QStylePainter> -#include <QStyledItemDelegate> -#include <cmath> - -#if 0 -AnimeListWidgetDelegate::AnimeListWidgetDelegate(QObject* parent) : QStyledItemDelegate(parent) { -} - -QWidget* AnimeListWidgetDelegate::createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const { - // no edit 4 u - return nullptr; -} - -void AnimeListWidgetDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const { - switch (index.column()) { -/* - case AnimeListWidgetModel::AL_PROGRESS: { - const int progress = static_cast<int>(index.data(Qt::UserRole).toReal()); - const int episodes = - static_cast<int>(index.siblingAtColumn(AnimeListWidgetModel::AL_EPISODES).data(Qt::UserRole).toReal()); - - int text_width = 59; - QRectF text_rect(option.rect.x() + text_width, option.rect.y(), text_width, option.decorationSize.height()); - painter->save(); - painter->drawText(text_rect, "/", QTextOption(Qt::AlignCenter | Qt::AlignVCenter)); - // drawText(const QRectF &rectangle, const QString &text, const QTextOption &option = QTextOption()) - painter->drawText(QRectF(text_rect.x(), text_rect.y(), text_width / 2 - 2, text_rect.height()), - QString::number(progress), QTextOption(Qt::AlignRight | Qt::AlignVCenter)); - painter->drawText( - QRectF(text_rect.x() + text_width / 2 + 2, text_rect.y(), text_width / 2 - 2, text_rect.height()), - QString::number(episodes), QTextOption(Qt::AlignLeft | Qt::AlignVCenter)); - painter->restore(); - QStyledItemDelegate::paint(painter, option, index); - break; - } -*/ - default: QStyledItemDelegate::paint(painter, option, index); break; - } -} - -AnimeListWidgetSortFilter::AnimeListWidgetSortFilter(QObject* parent) : QSortFilterProxyModel(parent) { -} - -bool AnimeListWidgetSortFilter::lessThan(const QModelIndex& l, const QModelIndex& r) const { - QVariant left = sourceModel()->data(l, sortRole()); - QVariant right = sourceModel()->data(r, sortRole()); - - switch (left.userType()) { - case QMetaType::Int: - case QMetaType::UInt: - case QMetaType::LongLong: - case QMetaType::ULongLong: return left.toInt() < right.toInt(); - case QMetaType::QDate: return left.toDate() < right.toDate(); - case QMetaType::QString: - default: return QString::compare(left.toString(), right.toString(), Qt::CaseInsensitive) < 0; - } -} - -AnimeListWidgetModel::AnimeListWidgetModel(QWidget* parent) : QAbstractListModel(parent) { - return; -} - -int AnimeListWidgetModel::rowCount(const QModelIndex& parent) const { - int count = 0; - for (const auto& [id, anime] : Anime::db.items) { - if (anime.IsInUserList()) - count++; - } - return count; - (void)(parent); -} - -int AnimeListWidgetModel::columnCount(const QModelIndex& parent) const { - return NB_COLUMNS; - (void)(parent); -} - -QVariant AnimeListWidgetModel::headerData(const int section, const Qt::Orientation orientation, const int role) const { - if (role == Qt::DisplayRole) { - switch (section) { - case AL_TITLE: return tr("Anime title"); - case AL_PROGRESS: return tr("Progress"); - case AL_EPISODES: return tr("Episodes"); - case AL_TYPE: return tr("Type"); - case AL_SCORE: return tr("Score"); - case AL_SEASON: return tr("Season"); - case AL_STARTED: return tr("Date started"); - case AL_COMPLETED: return tr("Date completed"); - case AL_NOTES: return tr("Notes"); - case AL_AVG_SCORE: return tr("Average score"); - case AL_UPDATED: return tr("Last updated"); - default: return {}; - } - } else if (role == Qt::TextAlignmentRole) { - switch (section) { - case AL_TITLE: - case AL_NOTES: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); - case AL_PROGRESS: - case AL_EPISODES: - case AL_TYPE: - case AL_SCORE: - case AL_AVG_SCORE: return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - case AL_SEASON: - case AL_STARTED: - case AL_COMPLETED: - case AL_UPDATED: return QVariant(Qt::AlignRight | Qt::AlignVCenter); - default: return QAbstractListModel::headerData(section, orientation, role); - } - } - return QAbstractListModel::headerData(section, orientation, role); -} - -QVariant AnimeListWidgetModel::data(const QModelIndex& index, int role) const { - if (!index.isValid()) - return QVariant(); - switch (role) { - case Qt::DisplayRole: - switch (index.column()) { - case AL_TITLE: return QString::fromUtf8(list[index.row()].GetUserPreferredTitle().c_str()); - case AL_PROGRESS: - return QString::number(list[index.row()].progress) + "/" + QString::number(list[index.row()].episodes); - case AL_EPISODES: return list[index.row()].episodes; - case AL_SCORE: return list[index.row()].score; - case AL_TYPE: return QString::fromStdString(Translate::TranslateSeriesFormat(list[index.row()].type)); - case AL_SEASON: - return QString::fromStdString(Translate::TranslateSeriesSeason(list[index.row()].season)) + " " + - QString::number(list[index.row()].air_date.GetYear()); - case AL_AVG_SCORE: return QString::number(list[index.row()].audience_score) + "%"; - case AL_STARTED: return list[index.row()].started.GetAsQDate(); - case AL_COMPLETED: return list[index.row()].completed.GetAsQDate(); - case AL_UPDATED: { - if (list[index.row()].updated == 0) - return QString("-"); - Time::Duration duration(Time::GetSystemTime() - list[index.row()].updated); - return QString::fromUtf8(duration.AsRelativeString().c_str()); - } - case AL_NOTES: return QString::fromUtf8(list[index.row()].notes.c_str()); - default: return ""; - } - break; - case Qt::UserRole: - switch (index.column()) { - case AL_ID: return - case AL_PROGRESS: return list[index.row()].progress; - case AL_TYPE: return list[index.row()].type; - case AL_SEASON: return list[index.row()].air_date.GetAsQDate(); - case AL_AVG_SCORE: return list[index.row()].audience_score; - case AL_UPDATED: return list[index.row()].updated; - default: return data(index, Qt::DisplayRole); - } - break; - case Qt::TextAlignmentRole: - switch (index.column()) { - case AL_TITLE: - case AL_NOTES: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); - case AL_PROGRESS: - case AL_EPISODES: - case AL_TYPE: - case AL_SCORE: - case AL_AVG_SCORE: return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - case AL_SEASON: - case AL_STARTED: - case AL_COMPLETED: - case AL_UPDATED: return QVariant(Qt::AlignRight | Qt::AlignVCenter); - default: break; - } - break; - } - return QVariant(); -} - -void AnimeListWidgetModel::UpdateAnime(int id) { - /* meh... it might be better to just redraw the entire list */ - int i = 0; - for (const auto& [a_id, anime] : Anime:db.items) { - if (anime.IsInUserList() && a_id == id && anime.GetUserStatus() == Anime::ListStatus::WATCHING) { - emit dataChanged(index(i), index(i)); - } - i++; - } -} -#endif - -int AnimeListWidget::VisibleColumnsCount() const { - int count = 0; - - for (int i = 0, end = tree_view->header()->count(); i < end; i++) { - if (!tree_view->isColumnHidden(i)) - count++; - } - - return count; -} - -void AnimeListWidget::SetColumnDefaults() { - tree_view->setColumnHidden(AnimeListWidgetModel::AL_SEASON, false); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_TYPE, false); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_UPDATED, false); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_PROGRESS, false); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_SCORE, false); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_TITLE, false); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_EPISODES, true); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_AVG_SCORE, true); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_STARTED, true); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_COMPLETED, true); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_UPDATED, true); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_NOTES, true); -} - -void AnimeListWidget::DisplayColumnHeaderMenu() { - QMenu* menu = new QMenu(this); - menu->setAttribute(Qt::WA_DeleteOnClose); - menu->setTitle(tr("Column visibility")); - menu->setToolTipsVisible(true); - - for (int i = 0; i < AnimeListWidgetModel::NB_COLUMNS; i++) { - if (i == AnimeListWidgetModel::AL_TITLE) - continue; - const auto column_name = - sort_models[tab_bar->currentIndex()]->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); - QAction* action = menu->addAction(column_name, this, [this, i](const bool checked) { - if (!checked && (VisibleColumnsCount() <= 1)) - return; - - tree_view->setColumnHidden(i, !checked); - - if (checked && (tree_view->columnWidth(i) <= 5)) - tree_view->resizeColumnToContents(i); - - // SaveSettings(); - }); - action->setCheckable(true); - action->setChecked(!tree_view->isColumnHidden(i)); - } - - menu->addSeparator(); - QAction* resetAction = menu->addAction(tr("Reset to defaults"), this, [this]() { - for (int i = 0, count = tree_view->header()->count(); i < count; ++i) { - SetColumnDefaults(); - } - // SaveSettings(); - }); - menu->popup(QCursor::pos()); - (void)(resetAction); -} - -void AnimeListWidget::DisplayListMenu() { - QMenu* menu = new QMenu(this); - menu->setAttribute(Qt::WA_DeleteOnClose); - menu->setTitle(tr("Column visibility")); - menu->setToolTipsVisible(true); - - const QItemSelection selection = sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); - if (!selection.indexes().first().isValid()) { - return; - } - -/* - QAction* action = menu->addAction("Information", [this, selection] { - const QModelIndex index = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel()) - ->index(selection.indexes().first().row()); - Anime::Anime* anime = - ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->GetAnimeFromIndex(index); - if (!anime) { - return; - } - - InformationDialog* dialog = new InformationDialog( - *anime, - [this, anime] { - ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->UpdateAnime(*anime); - }, - this); - - dialog->show(); - dialog->raise(); - dialog->activateWindow(); - }); -*/ - menu->popup(QCursor::pos()); -} - -void AnimeListWidget::ItemDoubleClicked() { - /* throw out any other garbage */ - const QItemSelection selection = - sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); - if (!selection.indexes().first().isValid()) { - return; - } - -/* - const QModelIndex index = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel()) - ->index(selection.indexes().first().row()); - Anime::Anime* anime = - ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->GetAnimeFromIndex(index); - if (!anime) { - return; - } - - InformationDialog* dialog = new InformationDialog(*anime, [this, anime] { - ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->UpdateAnime(*anime); - }, this); - - dialog->show(); - dialog->raise(); - dialog->activateWindow(); -*/ -} - -void AnimeListWidget::paintEvent(QPaintEvent*) { - QStylePainter p(this); - - QStyleOptionTabWidgetFrame opt; - InitStyle(&opt); - opt.rect = panelRect; - p.drawPrimitive(QStyle::PE_FrameTabWidget, opt); -} - -void AnimeListWidget::resizeEvent(QResizeEvent* e) { - QWidget::resizeEvent(e); - SetupLayout(); -} - -void AnimeListWidget::showEvent(QShowEvent*) { - SetupLayout(); -} - -void AnimeListWidget::InitBasicStyle(QStyleOptionTabWidgetFrame* option) const { - if (!option) - return; - - option->initFrom(this); - option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, this); - option->shape = QTabBar::RoundedNorth; - option->tabBarRect = tab_bar->geometry(); -} - -void AnimeListWidget::InitStyle(QStyleOptionTabWidgetFrame* option) const { - if (!option) - return; - - InitBasicStyle(option); - - // int exth = style()->pixelMetric(QStyle::PM_TabBarBaseHeight, nullptr, this); - QSize t(0, tree_view->frameWidth()); - if (tab_bar->isVisibleTo(this)) { - t = tab_bar->sizeHint(); - t.setWidth(width()); - } - option->tabBarSize = t; - - QRect selected_tab_rect = tab_bar->tabRect(tab_bar->currentIndex()); - selected_tab_rect.moveTopLeft(selected_tab_rect.topLeft() + option->tabBarRect.topLeft()); - option->selectedTabRect = selected_tab_rect; - - option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, this); -} - -void AnimeListWidget::SetupLayout() { - QStyleOptionTabWidgetFrame option; - InitStyle(&option); - - QRect tabRect = style()->subElementRect(QStyle::SE_TabWidgetTabBar, &option, this); - tabRect.setLeft(tabRect.left() + 1); - panelRect = style()->subElementRect(QStyle::SE_TabWidgetTabPane, &option, this); - QRect contentsRect = style()->subElementRect(QStyle::SE_TabWidgetTabContents, &option, this); - - tab_bar->setGeometry(tabRect); - tree_view->parentWidget()->setGeometry(contentsRect); -} - -AnimeListWidget::AnimeListWidget(QWidget* parent) : QWidget(parent) { - /* Tab bar */ - tab_bar = new QTabBar(this); - tab_bar->setExpanding(false); - tab_bar->setDrawBase(false); - for (int i = 0; i < ARRAYSIZE(sort_models); i++) { - tab_bar->addTab(QString::fromStdString(Translate::TranslateListStatus(Anime::ListStatuses[i]))); - - /* Tree view... */ - QWidget* tree_widget = new QWidget(this); - tree_view = new QTreeView(tree_widget); - tree_view->setItemDelegate(new AnimeListWidgetDelegate(tree_view)); - tree_view->setUniformRowHeights(true); - tree_view->setAllColumnsShowFocus(false); - tree_view->setAlternatingRowColors(true); - tree_view->setSortingEnabled(true); - tree_view->setSelectionMode(QAbstractItemView::ExtendedSelection); - tree_view->setItemsExpandable(false); - tree_view->setRootIsDecorated(false); - tree_view->setContextMenuPolicy(Qt::CustomContextMenu); - tree_view->setFrameShape(QFrame::NoFrame); - - QHBoxLayout* layout = new QHBoxLayout; - layout->addWidget(tree_view); - layout->setMargin(0); - tree_widget->setLayout(layout); - - /* Double click stuff */ - connect(tree_view, &QAbstractItemView::doubleClicked, this, &AnimeListWidget::ItemDoubleClicked); - connect(tree_view, &QWidget::customContextMenuRequested, this, &AnimeListWidget::DisplayListMenu); - - /* Enter & return keys */ - connect(new QShortcut(Qt::Key_Return, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, - this, &AnimeListWidget::ItemDoubleClicked); - - connect(new QShortcut(Qt::Key_Enter, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, - this, &AnimeListWidget::ItemDoubleClicked); - - tree_view->header()->setStretchLastSection(false); - tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu); - connect(tree_view->header(), &QWidget::customContextMenuRequested, this, - &AnimeListWidget::DisplayColumnHeaderMenu); - - connect(tab_bar, &QTabBar::currentChanged, this, [this](int index) { - if (sort_models[index]) - tree_view->setModel(sort_models[index]); - }); - - setFocusPolicy(Qt::TabFocus); - setFocusProxy(tab_bar); - } - - void AnimeListWidget::UpdateAnimeList() { - for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++) { - sort_models[i] = new AnimeListWidgetSortFilter(tree_view); - sort_models[i]->setSourceModel(new AnimeListWidgetModel(this, &anime_lists[i])); - sort_models[i]->setSortRole(Qt::UserRole); - sort_models[i]->setSortCaseSensitivity(Qt::CaseInsensitive); - } - if (ARRAYSIZE(sort_models) > 0) - tree_view->setModel(sort_models[0]); - SetColumnDefaults(); - SetupLayout(); - } - - void AnimeListWidget::Reset() { - while (tab_bar->count()) - tab_bar->removeTab(0); - for (int i = 0; i < ARRAYSIZE(sort_models); i++) - delete sort_models[i]; - } - -#include "gui/pages/moc_anime_list.cpp" +/** + * anime_list.cpp: defines the anime list page + * and widgets. + * + * much of this file is based around + * Qt's original QTabWidget implementation, because + * I needed a somewhat native way to create a tabbed + * widget with only one subwidget that worked exactly + * like a native tabbed widget. + **/ +#include "gui/pages/anime_list.h" +#include "core/anime.h" +#include "core/anime_db.h" +#include "core/session.h" +#include "core/time.h" +#include "gui/dialog/information.h" +#include "gui/translate/anime.h" +#include "services/anilist.h" +#include <QHBoxLayout> +#include <QHeaderView> +#include <QMenu> +#include <QProgressBar> +#include <QDebug> +#include <QShortcut> +#include <QStylePainter> +#include <QStyledItemDelegate> +#include <QAbstractItemModelTester> +#include <cmath> + +AnimeListWidgetDelegate::AnimeListWidgetDelegate(QObject* parent) : QStyledItemDelegate(parent) { +} + +QWidget* AnimeListWidgetDelegate::createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const { + // no edit 4 u + return nullptr; +} + +void AnimeListWidgetDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const { + switch (index.column()) { +/* + case AnimeListWidgetModel::AL_PROGRESS: { + const int progress = static_cast<int>(index.data(Qt::UserRole).toReal()); + const int episodes = + static_cast<int>(index.siblingAtColumn(AnimeListWidgetModel::AL_EPISODES).data(Qt::UserRole).toReal()); + + int text_width = 59; + QRectF text_rect(option.rect.x() + text_width, option.rect.y(), text_width, option.decorationSize.height()); + painter->save(); + painter->drawText(text_rect, "/", QTextOption(Qt::AlignCenter | Qt::AlignVCenter)); + // drawText(const QRectF &rectangle, const QString &text, const QTextOption &option = QTextOption()) + painter->drawText(QRectF(text_rect.x(), text_rect.y(), text_width / 2 - 2, text_rect.height()), + QString::number(progress), QTextOption(Qt::AlignRight | Qt::AlignVCenter)); + painter->drawText( + QRectF(text_rect.x() + text_width / 2 + 2, text_rect.y(), text_width / 2 - 2, text_rect.height()), + QString::number(episodes), QTextOption(Qt::AlignLeft | Qt::AlignVCenter)); + painter->restore(); + QStyledItemDelegate::paint(painter, option, index); + break; + } +*/ + default: QStyledItemDelegate::paint(painter, option, index); break; + } +} + +AnimeListWidgetSortFilter::AnimeListWidgetSortFilter(QObject* parent) : QSortFilterProxyModel(parent) { +} + +bool AnimeListWidgetSortFilter::lessThan(const QModelIndex& l, const QModelIndex& r) const { + QVariant left = sourceModel()->data(l, sortRole()); + QVariant right = sourceModel()->data(r, sortRole()); + + switch (left.userType()) { + case QMetaType::Int: + case QMetaType::UInt: + case QMetaType::LongLong: + case QMetaType::ULongLong: return left.toInt() < right.toInt(); + case QMetaType::QDate: return left.toDate() < right.toDate(); + case QMetaType::QString: + default: return QString::compare(left.toString(), right.toString(), Qt::CaseInsensitive) < 0; + } +} + +AnimeListWidgetModel::AnimeListWidgetModel(QWidget* parent, Anime::ListStatus _status) : QAbstractListModel(parent) { + status = _status; + return; +} + +int AnimeListWidgetModel::rowCount(const QModelIndex& parent) const { + return list.size(); + (void)(parent); +} + +int AnimeListWidgetModel::columnCount(const QModelIndex& parent) const { + return NB_COLUMNS; + (void)(parent); +} + +QVariant AnimeListWidgetModel::headerData(const int section, const Qt::Orientation orientation, const int role) const { + if (role == Qt::DisplayRole) { + switch (section) { + case AL_TITLE: return tr("Anime title"); + case AL_PROGRESS: return tr("Progress"); + case AL_EPISODES: return tr("Episodes"); + case AL_TYPE: return tr("Type"); + case AL_SCORE: return tr("Score"); + case AL_SEASON: return tr("Season"); + case AL_STARTED: return tr("Date started"); + case AL_COMPLETED: return tr("Date completed"); + case AL_NOTES: return tr("Notes"); + case AL_AVG_SCORE: return tr("Average score"); + case AL_UPDATED: return tr("Last updated"); + default: return {}; + } + } else if (role == Qt::TextAlignmentRole) { + switch (section) { + case AL_TITLE: + case AL_NOTES: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + case AL_PROGRESS: + case AL_EPISODES: + case AL_TYPE: + case AL_SCORE: + case AL_AVG_SCORE: return QVariant(Qt::AlignCenter | Qt::AlignVCenter); + case AL_SEASON: + case AL_STARTED: + case AL_COMPLETED: + case AL_UPDATED: return QVariant(Qt::AlignRight | Qt::AlignVCenter); + default: return QAbstractListModel::headerData(section, orientation, role); + } + } + return QAbstractListModel::headerData(section, orientation, role); +} + +QVariant AnimeListWidgetModel::data(const QModelIndex& index, int role) const { + if (!index.isValid()) + return QVariant(); + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case AL_TITLE: return QString::fromUtf8(list[index.row()].GetUserPreferredTitle().c_str()); + case AL_PROGRESS: + return QString::number(list[index.row()].GetUserProgress()) + "/" + QString::number(list[index.row()].GetEpisodes()); + case AL_EPISODES: return list[index.row()].GetEpisodes(); + case AL_SCORE: return list[index.row()].GetUserScore(); + case AL_TYPE: return QString::fromStdString(Translate::TranslateSeriesFormat(list[index.row()].GetFormat())); + case AL_SEASON: + return QString::fromStdString(Translate::TranslateSeriesSeason(list[index.row()].GetSeason())) + " " + + QString::number(list[index.row()].GetAirDate().GetYear()); + case AL_AVG_SCORE: return QString::number(list[index.row()].GetAudienceScore()) + "%"; + case AL_STARTED: return list[index.row()].GetUserDateStarted().GetAsQDate(); + case AL_COMPLETED: return list[index.row()].GetUserDateCompleted().GetAsQDate(); + case AL_UPDATED: { + if (list[index.row()].GetUserTimeUpdated() == 0) + return QString("-"); + Time::Duration duration(Time::GetSystemTime() - list[index.row()].GetUserTimeUpdated()); + return QString::fromUtf8(duration.AsRelativeString().c_str()); + } + case AL_NOTES: return QString::fromUtf8(list[index.row()].GetUserNotes().c_str()); + default: return ""; + } + break; + case Qt::UserRole: + switch (index.column()) { + case AL_ID: return list[index.row()].GetId(); + case AL_PROGRESS: return list[index.row()].GetUserProgress(); + case AL_TYPE: return static_cast<int>(list[index.row()].GetFormat()); + case AL_SEASON: return list[index.row()].GetAirDate().GetAsQDate(); + case AL_AVG_SCORE: return list[index.row()].GetAudienceScore(); + case AL_UPDATED: return list[index.row()].GetUserTimeUpdated(); + default: return data(index, Qt::DisplayRole); + } + break; + case Qt::TextAlignmentRole: + switch (index.column()) { + case AL_TITLE: + case AL_NOTES: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + case AL_PROGRESS: + case AL_EPISODES: + case AL_TYPE: + case AL_SCORE: + case AL_AVG_SCORE: return QVariant(Qt::AlignCenter | Qt::AlignVCenter); + case AL_SEASON: + case AL_STARTED: + case AL_COMPLETED: + case AL_UPDATED: return QVariant(Qt::AlignRight | Qt::AlignVCenter); + default: break; + } + break; + } + return QVariant(); +} + +void AnimeListWidgetModel::UpdateAnime(int id) { + /* meh... it might be better to just reinit the entire list */ + int i = 0; + for (const auto& [a_id, anime] : Anime::db.items) { + if (anime.IsInUserList() && a_id == id && anime.GetUserStatus() == status) { + emit dataChanged(index(i), index(i)); + } + i++; + } +} + +Anime::Anime* AnimeListWidgetModel::GetAnimeFromIndex(QModelIndex index) { + return &list.at(index.row()); +} + +void AnimeListWidgetModel::RefreshList() { + bool has_children = !!rowCount(index(0)); + if (has_children) beginResetModel(); + list.clear(); + + for (const auto& [id, anime] : Anime::db.items) { + if (anime.IsInUserList() && anime.GetUserStatus() == status) { + list.push_back(anime); + } + } + if (has_children) endResetModel(); +} + +int AnimeListWidget::VisibleColumnsCount() const { + int count = 0; + + for (int i = 0, end = tree_view->header()->count(); i < end; i++) { + if (!tree_view->isColumnHidden(i)) + count++; + } + + return count; +} + +void AnimeListWidget::SetColumnDefaults() { + tree_view->setColumnHidden(AnimeListWidgetModel::AL_SEASON, false); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_TYPE, false); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_UPDATED, false); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_PROGRESS, false); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_SCORE, false); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_TITLE, false); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_EPISODES, true); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_AVG_SCORE, true); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_STARTED, true); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_COMPLETED, true); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_UPDATED, true); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_NOTES, true); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_ID, true); +} + +void AnimeListWidget::DisplayColumnHeaderMenu() { + QMenu* menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->setTitle(tr("Column visibility")); + menu->setToolTipsVisible(true); + + for (int i = 0; i < AnimeListWidgetModel::NB_COLUMNS; i++) { + if (i == AnimeListWidgetModel::AL_TITLE || i == AnimeListWidgetModel::AL_ID) + continue; + const auto column_name = + sort_models[tab_bar->currentIndex()]->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); + QAction* action = menu->addAction(column_name, this, [this, i](const bool checked) { + if (!checked && (VisibleColumnsCount() <= 1)) + return; + + tree_view->setColumnHidden(i, !checked); + + if (checked && (tree_view->columnWidth(i) <= 5)) + tree_view->resizeColumnToContents(i); + + // SaveSettings(); + }); + action->setCheckable(true); + action->setChecked(!tree_view->isColumnHidden(i)); + } + + menu->addSeparator(); + QAction* resetAction = menu->addAction(tr("Reset to defaults"), this, [this]() { + for (int i = 0, count = tree_view->header()->count(); i < count; ++i) { + SetColumnDefaults(); + } + // SaveSettings(); + }); + menu->popup(QCursor::pos()); + (void)(resetAction); +} + +void AnimeListWidget::DisplayListMenu() { + QMenu* menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->setTitle(tr("Column visibility")); + menu->setToolTipsVisible(true); + + const QItemSelection selection = sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); + if (!selection.indexes().first().isValid()) { + return; + } + + QAction* action = menu->addAction("Information", [this, selection] { + const QModelIndex index = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel()) + ->index(selection.indexes().first().row()); + Anime::Anime* anime = + ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->GetAnimeFromIndex(index); + if (!anime) { + return; + } + + InformationDialog* dialog = new InformationDialog( + *anime, + [this, anime] { + ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->UpdateAnime(anime->GetId()); + }, + this); + + dialog->show(); + dialog->raise(); + dialog->activateWindow(); + }); + menu->popup(QCursor::pos()); +} + +void AnimeListWidget::ItemDoubleClicked() { + /* throw out any other garbage */ + const QItemSelection selection = + sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); + if (!selection.indexes().first().isValid()) { + return; + } + + const QModelIndex index = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel()) + ->index(selection.indexes().first().row()); + Anime::Anime* anime = + ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->GetAnimeFromIndex(index); + + InformationDialog* dialog = new InformationDialog( + *anime, + [this, anime] { + ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->UpdateAnime(anime->GetId()); + }, + this); + + dialog->show(); + dialog->raise(); + dialog->activateWindow(); +} + +void AnimeListWidget::paintEvent(QPaintEvent*) { + QStylePainter p(this); + + QStyleOptionTabWidgetFrame opt; + InitStyle(&opt); + opt.rect = panelRect; + p.drawPrimitive(QStyle::PE_FrameTabWidget, opt); +} + +void AnimeListWidget::resizeEvent(QResizeEvent* e) { + QWidget::resizeEvent(e); + SetupLayout(); +} + +void AnimeListWidget::showEvent(QShowEvent*) { + SetupLayout(); +} + +void AnimeListWidget::InitBasicStyle(QStyleOptionTabWidgetFrame* option) const { + if (!option) + return; + + option->initFrom(this); + option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, this); + option->shape = QTabBar::RoundedNorth; + option->tabBarRect = tab_bar->geometry(); +} + +void AnimeListWidget::InitStyle(QStyleOptionTabWidgetFrame* option) const { + if (!option) + return; + + InitBasicStyle(option); + + // int exth = style()->pixelMetric(QStyle::PM_TabBarBaseHeight, nullptr, this); + QSize t(0, tree_view->frameWidth()); + if (tab_bar->isVisibleTo(this)) { + t = tab_bar->sizeHint(); + t.setWidth(width()); + } + option->tabBarSize = t; + + QRect selected_tab_rect = tab_bar->tabRect(tab_bar->currentIndex()); + selected_tab_rect.moveTopLeft(selected_tab_rect.topLeft() + option->tabBarRect.topLeft()); + option->selectedTabRect = selected_tab_rect; + + option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, this); +} + +void AnimeListWidget::SetupLayout() { + QStyleOptionTabWidgetFrame option; + InitStyle(&option); + + QRect tabRect = style()->subElementRect(QStyle::SE_TabWidgetTabBar, &option, this); + tabRect.setLeft(tabRect.left() + 1); + panelRect = style()->subElementRect(QStyle::SE_TabWidgetTabPane, &option, this); + QRect contentsRect = style()->subElementRect(QStyle::SE_TabWidgetTabContents, &option, this); + + tab_bar->setGeometry(tabRect); + tree_view->parentWidget()->setGeometry(contentsRect); +} + +AnimeListWidget::AnimeListWidget(QWidget* parent) : QWidget(parent) { + /* Tab bar */ + tab_bar = new QTabBar(this); + tab_bar->setExpanding(false); + tab_bar->setDrawBase(false); + + /* Tree view... */ + QWidget* tree_widget = new QWidget(this); + tree_view = new QTreeView(tree_widget); + tree_view->setItemDelegate(new AnimeListWidgetDelegate(tree_view)); + tree_view->setUniformRowHeights(true); + tree_view->setAllColumnsShowFocus(false); + tree_view->setAlternatingRowColors(true); + tree_view->setSortingEnabled(true); + tree_view->setSelectionMode(QAbstractItemView::ExtendedSelection); + tree_view->setItemsExpandable(false); + tree_view->setRootIsDecorated(false); + tree_view->setContextMenuPolicy(Qt::CustomContextMenu); + tree_view->setFrameShape(QFrame::NoFrame); + + for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++) { + tab_bar->addTab(QString::fromStdString(Translate::TranslateListStatus(Anime::ListStatuses[i])) + " (" + QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); + sort_models[i] = new AnimeListWidgetSortFilter(tree_view); + AnimeListWidgetModel* model = new AnimeListWidgetModel(this, Anime::ListStatuses[i]); + new QAbstractItemModelTester(model, QAbstractItemModelTester::FailureReportingMode::Fatal, this); + sort_models[i]->setSourceModel(model); + sort_models[i]->setSortRole(Qt::UserRole); + sort_models[i]->setSortCaseSensitivity(Qt::CaseInsensitive); + } + + QHBoxLayout* layout = new QHBoxLayout; + layout->addWidget(tree_view); + layout->setMargin(0); + tree_widget->setLayout(layout); + + /* Double click stuff */ + connect(tree_view, &QAbstractItemView::doubleClicked, this, &AnimeListWidget::ItemDoubleClicked); + connect(tree_view, &QWidget::customContextMenuRequested, this, &AnimeListWidget::DisplayListMenu); + + /* Enter & return keys */ + connect(new QShortcut(Qt::Key_Return, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, + this, &AnimeListWidget::ItemDoubleClicked); + + connect(new QShortcut(Qt::Key_Enter, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, + this, &AnimeListWidget::ItemDoubleClicked); + + tree_view->header()->setStretchLastSection(false); + tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(tree_view->header(), &QWidget::customContextMenuRequested, this, + &AnimeListWidget::DisplayColumnHeaderMenu); + + connect(tab_bar, &QTabBar::currentChanged, this, [this](int index) { + if (sort_models[index]) + tree_view->setModel(sort_models[index]); + }); + + setFocusPolicy(Qt::TabFocus); + setFocusProxy(tab_bar); +} + +void AnimeListWidget::RefreshList() { + for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++) { + ((AnimeListWidgetModel*)sort_models[i]->sourceModel())->RefreshList(); + } +} + +void AnimeListWidget::Reset() { + while (tab_bar->count()) + tab_bar->removeTab(0); + for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++) + delete sort_models[i]; +} + +#include "gui/pages/moc_anime_list.cpp"
--- a/src/gui/pages/statistics.cpp Sun Sep 10 03:59:16 2023 -0400 +++ b/src/gui/pages/statistics.cpp Sat Sep 16 02:06:01 2023 -0400 @@ -1,7 +1,8 @@ #include "gui/pages/statistics.h" #include "gui/pages/anime_list.h" #include "gui/ui_utils.h" -#include "session.h" +#include "core/session.h" +#include "core/anime_db.h" #include <QString> #include <QTextDocument> #include <QTextStream> @@ -48,9 +49,9 @@ } /* me abusing macros :) */ -#define ADD_TIME_SEGMENT(r, x, s, p) \ +#define ADD_TIME_SEGMENT(r, x, s, p) { \ if (x > 0) \ - r << x << ((x == 1) ? s : p) + r << x << ((x == 1) ? s : p); } std::string StatisticsWidget::MinutesToDateString(int minutes) { /* ew */ int years = (minutes * (1 / 525949.2F)); @@ -68,25 +69,22 @@ return return_stream.str(); } -std::string StatisticsWidget::SecondsToDateString(int seconds) { +std::string StatisticsWidget::SecondsToDateString(int sec) { /* this is all fairly unnecessary, but works:tm: */ - std::chrono::duration<int, std::ratio<1>> int_total_mins(seconds); - auto int_years = std::chrono::duration_cast<std::chrono::years>(int_total_mins); - auto int_months = std::chrono::duration_cast<std::chrono::months>(int_total_mins - int_years); - auto int_days = std::chrono::duration_cast<std::chrono::days>(int_total_mins - int_years - int_months); - auto int_hours = std::chrono::duration_cast<std::chrono::hours>(int_total_mins - int_years - int_months - int_days); - auto int_minutes = std::chrono::duration_cast<std::chrono::minutes>(int_total_mins - int_years - int_months - - int_days - int_hours); - auto int_seconds = std::chrono::duration_cast<std::chrono::seconds>(int_total_mins - int_years - int_months - - int_days - int_hours - int_minutes); + int years = sec * (1 / 31556952.0F); + int months = sec * (1 / 2629746.0F) - (years * 12); + int days = sec * (1 / 86400.0F) - (years * 365.2425F) - (months * 30.436875F); + int hours = sec * (1 / 3600.0F) - (years * 8765.82F) - (months * 730.485F) - (days * 24); + int minutes = (sec) * (1 / 60.0F) - (years * 525949.2F) - (months * 43829.1F) - (days * 1440.0F) - (hours * 60.0F); + int seconds = sec - (years * 31556952.0F) - (months * 2629746.0F) - (days * 86400.0F) - (hours * 3600.0F) - (minutes * 60.0F); std::ostringstream return_stream; - ADD_TIME_SEGMENT(return_stream, int_years, " year ", " years "); - ADD_TIME_SEGMENT(return_stream, int_months, " month ", " months "); - ADD_TIME_SEGMENT(return_stream, int_days, " day ", " days "); - ADD_TIME_SEGMENT(return_stream, int_hours, " hour ", " hours "); - ADD_TIME_SEGMENT(return_stream, int_minutes, " minute ", " minutes "); - if (int_seconds.count() > 0 || return_stream.str().size() == 0) - return_stream << int_seconds.count() << ((int_seconds.count() == 1) ? " second" : " seconds"); + ADD_TIME_SEGMENT(return_stream, years, " year ", " years "); + ADD_TIME_SEGMENT(return_stream, months, " month ", " months "); + ADD_TIME_SEGMENT(return_stream, days, " day ", " days "); + ADD_TIME_SEGMENT(return_stream, hours, " hour ", " hours "); + ADD_TIME_SEGMENT(return_stream, minutes, " minute ", " minutes "); + if (seconds > 0 || return_stream.str().size() == 0) + return_stream << seconds << ((seconds == 1) ? " second" : " seconds"); return return_stream.str(); } #undef ADD_TIME_SEGMENT @@ -95,12 +93,12 @@ /* Anime list */ QString string = ""; QTextStream ts(&string); - ts << Anime::db->GetTotalAnimeAmount() << '\n'; - ts << Anime::db->GetTotalEpisodeAmount() << '\n'; - ts << MinutesToDateString(Anime::db->GetTotalWatchedAmount()).c_str() << '\n'; - ts << MinutesToDateString(Anime::db->GetTotalPlannedAmount()).c_str() << '\n'; - ts << Anime::db->GetAverageScore() << '\n'; - ts << Anime::db->GetScoreDeviation(); + ts << Anime::db.GetTotalAnimeAmount() << '\n'; + ts << Anime::db.GetTotalEpisodeAmount() << '\n'; + ts << MinutesToDateString(Anime::db.GetTotalWatchedAmount()).c_str() << '\n'; + ts << MinutesToDateString(Anime::db.GetTotalPlannedAmount()).c_str() << '\n'; + ts << Anime::db.GetAverageScore() << '\n'; + ts << Anime::db.GetScoreDeviation(); UiUtils::SetPlainTextEditData(anime_list_data, string); /* Application */ @@ -108,4 +106,4 @@ UiUtils::SetPlainTextEditData(application_data, QString(SecondsToDateString(session.uptime() / 1000).c_str())); } -#include "gui/pages/moc_statistics.h" +#include "gui/pages/moc_statistics.cpp"
--- a/src/gui/translate/anime.cpp Sun Sep 10 03:59:16 2023 -0400 +++ b/src/gui/translate/anime.cpp Sat Sep 16 02:06:01 2023 -0400 @@ -1,54 +1,54 @@ -#include "core/anime.h" - -namespace Translate { - -std::string TranslateListStatus(const Anime::ListStatus status) { - switch (status) { - case Anime::ListStatus::NOT_IN_LIST: return "Not in list"; - case Anime::ListStatus::CURRENT: return "Currently watching"; - case Anime::ListStatus::PLANNING: return "Plan to watch"; - case Anime::ListStatus::COMPLETED: return "Completed"; - case Anime::ListStatus::DROPPED: return "Dropped"; - case Anime::ListStatus::PAUSED: return "On hold"; - default: return ""; - } -} - -std::string TranslateSeriesFormat(const Anime::SeriesFormat format) { - switch (format) { - case Anime::SeriesFormat::UNKNOWN: return "Unknown"; - case Anime::SeriesFormat::TV: return "TV"; - case Anime::SeriesFormat::TV_SHORT: return "TV short"; - case Anime::SeriesFormat::OVA: return "OVA"; - case Anime::SeriesFormat::MOVIE: return "Movie"; - case Anime::SeriesFormat::SPECIAL: return "Special"; - case Anime::SeriesFormat::ONA: return "ONA"; - case Anime::SeriesFormat::MUSIC: return "Music"; - default: return ""; - } -} - -std::string TranslateSeriesSeason(const Anime::SeriesSeason season) { - switch (season) { - case Anime::SeriesSeason::UNKNOWN: return "Unknown"; - case Anime::SeriesSeason::WINTER: return "Winter"; - case Anime::SeriesSeason::SUMMER: return "Summer"; - case Anime::SeriesSeason::FALL: return "Fall"; - case Anime::SeriesSeason::SPRING: return "Spring"; - default: return ""; - } -} - -std::string TranslateSeriesStatus(const Anime::SeriesStatus status) { - switch (status) { - case Anime::SeriesStatus::UNKNOWN: return "Unknown"; - case Anime::SeriesStatus::RELEASING: return "Currently airing"; - case Anime::SeriesStatus::FINISHED: return "Finished airing"; - case Anime::SeriesStatus::NOT_YET_RELEASED: return "Not yet aired"; - case Anime::SeriesStatus::CANCELLED: return "Cancelled"; - case Anime::SeriesStatus::HIATUS: return "On hiatus"; - default: return ""; - } -} - +#include "core/anime.h" + +namespace Translate { + +std::string TranslateListStatus(const Anime::ListStatus status) { + switch (status) { + case Anime::ListStatus::NOT_IN_LIST: return "Not in list"; + case Anime::ListStatus::CURRENT: return "Currently watching"; + case Anime::ListStatus::PLANNING: return "Plan to watch"; + case Anime::ListStatus::COMPLETED: return "Completed"; + case Anime::ListStatus::DROPPED: return "Dropped"; + case Anime::ListStatus::PAUSED: return "On hold"; + default: return ""; + } +} + +std::string TranslateSeriesFormat(const Anime::SeriesFormat format) { + switch (format) { + case Anime::SeriesFormat::UNKNOWN: return "Unknown"; + case Anime::SeriesFormat::TV: return "TV"; + case Anime::SeriesFormat::TV_SHORT: return "TV short"; + case Anime::SeriesFormat::OVA: return "OVA"; + case Anime::SeriesFormat::MOVIE: return "Movie"; + case Anime::SeriesFormat::SPECIAL: return "Special"; + case Anime::SeriesFormat::ONA: return "ONA"; + case Anime::SeriesFormat::MUSIC: return "Music"; + default: return ""; + } +} + +std::string TranslateSeriesSeason(const Anime::SeriesSeason season) { + switch (season) { + case Anime::SeriesSeason::UNKNOWN: return "Unknown"; + case Anime::SeriesSeason::WINTER: return "Winter"; + case Anime::SeriesSeason::SUMMER: return "Summer"; + case Anime::SeriesSeason::FALL: return "Fall"; + case Anime::SeriesSeason::SPRING: return "Spring"; + default: return ""; + } +} + +std::string TranslateSeriesStatus(const Anime::SeriesStatus status) { + switch (status) { + case Anime::SeriesStatus::UNKNOWN: return "Unknown"; + case Anime::SeriesStatus::RELEASING: return "Currently airing"; + case Anime::SeriesStatus::FINISHED: return "Finished airing"; + case Anime::SeriesStatus::NOT_YET_RELEASED: return "Not yet aired"; + case Anime::SeriesStatus::CANCELLED: return "Cancelled"; + case Anime::SeriesStatus::HIATUS: return "On hiatus"; + default: return ""; + } +} + } // namespace Translate \ No newline at end of file
--- a/src/gui/window.cpp Sun Sep 10 03:59:16 2023 -0400 +++ b/src/gui/window.cpp Sat Sep 16 02:06:01 2023 -0400 @@ -1,6 +1,7 @@ #include "gui/window.h" #include "core/config.h" #include "core/session.h" +#include "services/services.h" #include "gui/dialog/settings.h" #include "gui/pages/anime_list.h" #include "gui/pages/now_playing.h" @@ -24,17 +25,56 @@ wxWidgets, but I thought the API was a little meh, so I switched to Qt. */ +enum class Pages { + NOW_PLAYING, + + ANIME_LIST, + HISTORY, + STATISTICS, + + SEARCH, + SEASONS, + TORRENTS +}; + MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { main_widget = new QWidget(parent); + + SideBar* sidebar = new SideBar(main_widget); + sidebar->AddItem("Now Playing", SideBar::CreateIcon(":/icons/16x16/film.png")); + sidebar->AddSeparator(); + sidebar->AddItem("Anime List", SideBar::CreateIcon(":/icons/16x16/document-list.png")); + sidebar->AddItem("History", SideBar::CreateIcon(":/icons/16x16/clock-history-frame.png")); + sidebar->AddItem("Statistics", SideBar::CreateIcon(":/icons/16x16/chart.png")); + sidebar->AddSeparator(); + sidebar->AddItem("Search", SideBar::CreateIcon(":/icons/16x16/magnifier.png")); + sidebar->AddItem("Seasons", SideBar::CreateIcon(":/icons/16x16/calendar.png")); + sidebar->AddItem("Torrents", SideBar::CreateIcon(":/icons/16x16/feed.png")); + sidebar->setFixedWidth(128); + sidebar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + + QStackedWidget* stack = new QStackedWidget(main_widget); + stack->addWidget(new NowPlayingWidget(main_widget)); + stack->addWidget(new AnimeListWidget(main_widget)); + stack->addWidget(new StatisticsWidget(main_widget)); + + connect(sidebar, &SideBar::CurrentItemChanged, stack, [stack](int index) { + switch (index) { + case 0: + case 1: stack->setCurrentIndex(index); break; + case 3: stack->setCurrentIndex(2); break; + default: break; + } + }); + sidebar->setCurrentRow(2); + /* Menu Bar */ QAction* action; QMenuBar* menubar = new QMenuBar(parent); QMenu* menu = menubar->addMenu("&File"); QMenu* submenu = menu->addMenu("&Library folders"); - action = new QAction("&Add new folder..."); - submenu->addAction(action); - action = new QAction("&Scan available episodes"); - menu->addAction(action); + action = submenu->addAction("&Add new folder..."); + action = menu->addAction("&Scan available episodes"); menu->addSeparator(); @@ -44,7 +84,10 @@ action = menu->addAction("E&xit", qApp, &QApplication::quit); menu = menubar->addMenu("&Services"); - action = new QAction("Synchronize &list"); + action = menu->addAction("Synchronize &list", [this, stack] { + Services::Synchronize(); + ((AnimeListWidget*)stack->widget((int)Pages::ANIME_LIST))->RefreshList(); + }); menu->addSeparator(); @@ -85,34 +128,6 @@ setMenuBar(menubar); - SideBar* sidebar = new SideBar(main_widget); - sidebar->AddItem("Now Playing", SideBar::CreateIcon(":/icons/16x16/film.png")); - sidebar->AddSeparator(); - sidebar->AddItem("Anime List", SideBar::CreateIcon(":/icons/16x16/document-list.png")); - sidebar->AddItem("History", SideBar::CreateIcon(":/icons/16x16/clock-history-frame.png")); - sidebar->AddItem("Statistics", SideBar::CreateIcon(":/icons/16x16/chart.png")); - sidebar->AddSeparator(); - sidebar->AddItem("Search", SideBar::CreateIcon(":/icons/16x16/magnifier.png")); - sidebar->AddItem("Seasons", SideBar::CreateIcon(":/icons/16x16/calendar.png")); - sidebar->AddItem("Torrents", SideBar::CreateIcon(":/icons/16x16/feed.png")); - sidebar->setFixedWidth(128); - sidebar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); - - QStackedWidget* stack = new QStackedWidget(main_widget); - stack->addWidget(new NowPlayingWidget(parent)); - stack->addWidget(new AnimeListWidget(parent)); - stack->addWidget(new StatisticsWidget(parent)); - - connect(sidebar, &SideBar::CurrentItemChanged, stack, [stack](int index) { - switch (index) { - case 0: - case 1: stack->setCurrentIndex(index); break; - case 3: stack->setCurrentIndex(2); break; - default: break; - } - }); - sidebar->setCurrentRow(2); - QHBoxLayout* layout = new QHBoxLayout(main_widget); layout->addWidget(sidebar, 0, Qt::AlignLeft | Qt::AlignTop); layout->addWidget(stack);
--- a/src/main.cpp Sun Sep 10 03:59:16 2023 -0400 +++ b/src/main.cpp Sat Sep 16 02:06:01 2023 -0400 @@ -1,19 +1,19 @@ -#include "core/session.h" -#include "gui/window.h" -#include <QApplication> - -Session session; - -int main(int argc, char** argv) { - QApplication app(argc, argv); - - session.config.Load(); - - MainWindow window; - - window.resize(941, 750); - window.setWindowTitle("Weeaboo"); - window.show(); - - return app.exec(); +#include "core/session.h" +#include "gui/window.h" +#include <QApplication> + +Session session; + +int main(int argc, char** argv) { + QApplication app(argc, argv); + + session.config.Load(); + + MainWindow window; + + window.resize(941, 750); + window.setWindowTitle("Weeaboo"); + window.show(); + + return app.exec(); } \ No newline at end of file
--- a/src/services/anilist.cpp Sun Sep 10 03:59:16 2023 -0400 +++ b/src/services/anilist.cpp Sat Sep 16 02:06:01 2023 -0400 @@ -1,5 +1,6 @@ #include "services/anilist.h" #include "core/anime.h" +#include "core/anime_db.h" #include "core/config.h" #include "core/json.h" #include "core/session.h" @@ -8,6 +9,7 @@ #include <QInputDialog> #include <QLineEdit> #include <QMessageBox> +#include <QUrl> #include <chrono> #include <curl/curl.h> #include <exception> @@ -18,17 +20,17 @@ class Account { public: - std::string Username() const { return session.anilist.username; } - void SetUsername(std::string const& username) { session.anilist.username = username; } + std::string Username() const { return session.config.anilist.username; } + void SetUsername(std::string const& username) { session.config.anilist.username = username; } - int UserId() const { return session.anilist.user_id; } - void SetUserId(const int id) { session.anilist.user_id = id; } + int UserId() const { return session.config.anilist.user_id; } + void SetUserId(const int id) { session.config.anilist.user_id = id; } - std::string AuthToken() const { return session.anilist.auth_token; } - void SetAuthToken(std::string const& auth_token) { session.anilist.auth_token = auth_token; } + std::string AuthToken() const { return session.config.anilist.auth_token; } + void SetAuthToken(std::string const& auth_token) { session.config.anilist.auth_token = auth_token; } bool Authenticated() const { return !AuthToken().empty(); } -} +}; static Account account; @@ -68,55 +70,55 @@ return ""; } -/* Maps to convert string forms to our internal enums */ +/* TODO: Move to Translate */ -std::map<std::string, enum AnimeWatchingStatus> StringToAnimeWatchingMap = { - {"CURRENT", CURRENT }, - {"PLANNING", PLANNING }, - {"COMPLETED", COMPLETED}, - {"DROPPED", DROPPED }, - {"PAUSED", PAUSED }, - {"REPEATING", REPEATING} +std::map<std::string, Anime::ListStatus> AniListStringToAnimeWatchingMap = { + {"CURRENT", Anime::ListStatus::CURRENT }, + {"PLANNING", Anime::ListStatus::PLANNING }, + {"COMPLETED", Anime::ListStatus::COMPLETED}, + {"DROPPED", Anime::ListStatus::DROPPED }, + {"PAUSED", Anime::ListStatus::PAUSED }, + {"REPEATING", Anime::ListStatus::CURRENT} }; -std::map<enum AnimeWatchingStatus, std::string> AnimeWatchingToStringMap = { - {CURRENT, "CURRENT" }, - {PLANNING, "PLANNING" }, - {COMPLETED, "COMPLETED"}, - {DROPPED, "DROPPED" }, - {PAUSED, "PAUSED" }, - {REPEATING, "REPEATING"} +std::map<Anime::ListStatus, std::string> AniListAnimeWatchingToStringMap = { + {Anime::ListStatus::CURRENT, "CURRENT" }, + {Anime::ListStatus::PLANNING, "PLANNING" }, + {Anime::ListStatus::COMPLETED, "COMPLETED"}, + {Anime::ListStatus::DROPPED, "DROPPED" }, + {Anime::ListStatus::PAUSED, "PAUSED" } }; -std::map<std::string, enum AnimeAiringStatus> StringToAnimeAiringMap = { - {"FINISHED", FINISHED }, - {"RELEASING", RELEASING }, - {"NOT_YET_RELEASED", NOT_YET_RELEASED}, - {"CANCELLED", CANCELLED }, - {"HIATUS", HIATUS } +std::map<std::string, Anime::SeriesStatus> AniListStringToAnimeAiringMap = { + {"FINISHED", Anime::SeriesStatus::FINISHED }, + {"RELEASING", Anime::SeriesStatus::RELEASING }, + {"NOT_YET_RELEASED", Anime::SeriesStatus::NOT_YET_RELEASED}, + {"CANCELLED", Anime::SeriesStatus::CANCELLED }, + {"HIATUS", Anime::SeriesStatus::HIATUS } }; -std::map<std::string, enum AnimeSeason> StringToAnimeSeasonMap = { - {"WINTER", WINTER}, - {"SPRING", SPRING}, - {"SUMMER", SUMMER}, - {"FALL", FALL } +std::map<std::string, Anime::SeriesSeason> AniListStringToAnimeSeasonMap = { + {"WINTER", Anime::SeriesSeason::WINTER}, + {"SPRING", Anime::SeriesSeason::SPRING}, + {"SUMMER", Anime::SeriesSeason::SUMMER}, + {"FALL", Anime::SeriesSeason::FALL } }; -std::map<std::string, enum AnimeFormat> StringToAnimeFormatMap = { - {"TV", TV }, - {"TV_SHORT", TV_SHORT}, - {"MOVIE", MOVIE }, - {"SPECIAL", SPECIAL }, - {"OVA", OVA }, - {"ONA", ONA }, - {"MUSIC", MUSIC }, - {"MANGA", MANGA }, - {"NOVEL", NOVEL }, - {"ONE_SHOT", ONE_SHOT} +std::map<std::string, enum Anime::SeriesFormat> AniListStringToAnimeFormatMap = { + {"TV", Anime::SeriesFormat::TV }, + {"TV_SHORT", Anime::SeriesFormat::TV_SHORT}, + {"MOVIE", Anime::SeriesFormat::MOVIE }, + {"SPECIAL", Anime::SeriesFormat::SPECIAL }, + {"OVA", Anime::SeriesFormat::OVA }, + {"ONA", Anime::SeriesFormat::ONA }, + {"MUSIC", Anime::SeriesFormat::MUSIC }, + {"MANGA", Anime::SeriesFormat::MANGA }, + {"NOVEL", Anime::SeriesFormat::NOVEL }, + {"ONE_SHOT", Anime::SeriesFormat::ONE_SHOT} }; -void ParseDate(const nlohmann::json& json, Date& date) { +Date ParseDate(const nlohmann::json& json) { + Date date; if (json.contains("/year"_json_pointer) && json["/year"_json_pointer].is_number()) date.SetYear(JSON::GetInt(json, "/year"_json_pointer)); else @@ -131,6 +133,7 @@ date.SetDay(JSON::GetInt(json, "/day"_json_pointer)); else date.VoidDay(); + return date; } void ParseTitle(const nlohmann::json& json, Anime::Anime& anime) { @@ -151,43 +154,50 @@ anime.SetEpisodes(JSON::GetInt(json, "/episodes"_json_pointer)); anime.SetFormat(AniListStringToAnimeFormatMap[JSON::GetString(json, "/format"_json_pointer)]); - anime.SetListStatus(AniListStringToAnimeAiringMap[JSON::GetString(json, "/status"_json_pointer)]); + anime.SetAiringStatus(AniListStringToAnimeAiringMap[JSON::GetString(json, "/status"_json_pointer)]); - ParseDate(json["/startDate"_json_pointer], anime.air_date); + anime.SetAirDate(ParseDate(json["/startDate"_json_pointer])); anime.SetAudienceScore(JSON::GetInt(json, "/averageScore"_json_pointer)); anime.SetSeason(AniListStringToAnimeSeasonMap[JSON::GetString(json, "/season"_json_pointer)]); anime.SetDuration(JSON::GetInt(json, "/duration"_json_pointer)); - anime.SetSynopsis(StringUtils::TextifySynopsis(JSON::GetString(json, "/description"_json_pointer))); + anime.SetSynopsis(Strings::TextifySynopsis(JSON::GetString(json, "/description"_json_pointer))); if (json.contains("/genres"_json_pointer) && json["/genres"_json_pointer].is_array()) anime.SetGenres(json["/genres"_json_pointer].get<std::vector<std::string>>()); if (json.contains("/synonyms"_json_pointer) && json["/synonyms"_json_pointer].is_array()) - anime.SetSynonyms(json["/synonyms"_json_pointer].get<std::vector<std::string>>()); - return 1; + anime.SetTitleSynonyms(json["/synonyms"_json_pointer].get<std::vector<std::string>>()); + return id; } -int ParseListItem(const nlohmann::json& json, Anime::Anime& anime) { - anime.SetScore(JSON::GetInt(entry.value(), "/score"_json_pointer)); - anime.SetProgress(JSON::GetInt(entry.value(), "/progress"_json_pointer)); - anime.SetStatus(AniListStringToAnimeWatchingMap[JSON::GetString(entry.value(), "/status"_json_pointer)]); - anime.SetNotes(JSON::GetString(entry.value(), "/notes"_json_pointer)); +int ParseListItem(const nlohmann::json& json) { + int id = ParseMediaJson(json["media"]); + + Anime::Anime& anime = Anime::db.items[id]; + + anime.AddToUserList(); - ParseDate(json["/startedAt"_json_pointer], anime.started); - ParseDate(json["/completedAt"_json_pointer], anime.completed); + anime.SetUserScore(JSON::GetInt(json, "/score"_json_pointer)); + anime.SetUserProgress(JSON::GetInt(json, "/progress"_json_pointer)); + anime.SetUserStatus(AniListStringToAnimeWatchingMap[JSON::GetString(json, "/status"_json_pointer)]); + anime.SetUserNotes(JSON::GetString(json, "/notes"_json_pointer)); - anime.SetUpdated(JSON::GetInt(entry.value(), "/updatedAt"_json_pointer)); + anime.SetUserDateStarted(ParseDate(json["/startedAt"_json_pointer])); + anime.SetUserDateCompleted(ParseDate(json["/completedAt"_json_pointer])); - return ParseMediaJson(json["media"], anime); + anime.SetUserTimeUpdated(JSON::GetInt(json, "/updatedAt"_json_pointer)); + + return id; } int ParseList(const nlohmann::json& json) { for (const auto& entry : json["entries"].items()) { ParseListItem(entry.value()); } + return 1; } -int GetAnimeList(int id) { +int GetAnimeList() { /* NOTE: these should be in the qrc file */ const std::string query = "query ($id: Int) {\n" " MediaListCollection (userId: $id, type: ANIME) {\n" @@ -196,6 +206,7 @@ " entries {\n" " score\n" " notes\n" + " status\n" " progress\n" " startedAt {\n" " year\n" @@ -238,7 +249,7 @@ nlohmann::json json = { {"query", query}, {"variables", { - {"id", id} + {"id", account.UserId()} }} }; // clang-format on @@ -248,13 +259,12 @@ /* TODO: make sure that we actually need the wstring converter and see if we can just get wide strings back from nlohmann::json */ for (const auto& list : res["data"]["MediaListCollection"]["lists"].items()) { - - ParseList(list.entry()); + ParseList(list.value()); } return 1; } -int UpdateAnimeEntry(const Anime& anime) { +int UpdateAnimeEntry(const Anime::Anime& anime) { /** * possible values: * @@ -285,11 +295,11 @@ nlohmann::json json = { {"query", query}, {"variables", { - {"media_id", anime.id}, - {"progress", anime.progress}, - {"status", AnimeWatchingToStringMap[anime.status]}, - {"score", anime.score}, - {"notes", anime.notes} + {"media_id", anime.GetId()}, + {"progress", anime.GetUserProgress()}, + {"status", AniListAnimeWatchingToStringMap[anime.GetUserStatus()]}, + {"score", anime.GetUserScore()}, + {"notes", anime.GetUserNotes()} }} }; // clang-format on @@ -300,7 +310,7 @@ int ParseUser(const nlohmann::json& json) { account.SetUsername(JSON::GetString(json, "/name"_json_pointer)); account.SetUserId(JSON::GetInt(json, "/id"_json_pointer)); - account.SetAuthenticated(true); + return account.UserId(); } int AuthorizeUser() { @@ -314,7 +324,6 @@ if (ok && !token.isEmpty()) account.SetAuthToken(token.toStdString()); else { // fail - account.SetAuthenticated(false); return 0; } const std::string query = "query {\n" @@ -330,7 +339,7 @@ {"query", query} }; auto ret = nlohmann::json::parse(SendRequest(json.dump())); - ParseUser(json["Viewer"]) account.SetAuthenticated(true); + ParseUser(json["Viewer"]); return 1; }
--- a/src/services/services.cpp Sun Sep 10 03:59:16 2023 -0400 +++ b/src/services/services.cpp Sat Sep 16 02:06:01 2023 -0400 @@ -1,19 +1,21 @@ -#include "session.h" - -namespace Services { - -void Synchronize() { - switch (session.config.service) { - case ANILIST: AniList::GetAnimeList(); break; - default: break; - } -} - -void Authorize() { - switch (session.config.service) { - case ANILIST: AniList::AuthorizeUser(); break; - default: break; - } -} - -} // namespace Services \ No newline at end of file +#include "core/session.h" +#include "services/anilist.h" +#include "services/services.h" + +namespace Services { + +void Synchronize() { + switch (session.config.service) { + case Anime::Services::ANILIST: AniList::GetAnimeList(); break; + default: break; + } +} + +void Authorize() { + switch (session.config.service) { + case Anime::Services::ANILIST: AniList::AuthorizeUser(); break; + default: break; + } +} + +}; // namespace Services \ No newline at end of file