# HG changeset patch # User Paper # Date 1698077247 14400 # Node ID 9b2b41f83a5e4057b1bead56f215d514bf619c82 # Parent 825506f0e221e68ea7f519ddd3b4605e779d41ca boring: mass rename to cc because this is a very unix-y project, it makes more sense to use the 'cc' extension diff -r 825506f0e221 -r 9b2b41f83a5e CMakeLists.txt --- a/CMakeLists.txt Fri Oct 13 13:15:19 2023 -0400 +++ b/CMakeLists.txt Mon Oct 23 12:07:27 2023 -0400 @@ -48,58 +48,58 @@ set(SRC_FILES # Main entrypoint - src/main.cpp + src/main.cc # Core files and datatype declarations... - src/core/anime.cpp - src/core/anime_db.cpp - src/core/config.cpp - src/core/date.cpp - src/core/filesystem.cpp - src/core/http.cpp - src/core/json.cpp - src/core/strings.cpp - src/core/time.cpp + src/core/anime.cc + src/core/anime_db.cc + src/core/config.cc + src/core/date.cc + src/core/filesystem.cc + src/core/http.cc + src/core/json.cc + src/core/strings.cc + src/core/time.cc # Main window - src/gui/window.cpp - src/gui/dark_theme.cpp + src/gui/window.cc + src/gui/dark_theme.cc # Main window pages - src/gui/pages/anime_list.cpp - src/gui/pages/now_playing.cpp - src/gui/pages/statistics.cpp - src/gui/pages/search.cpp - src/gui/pages/seasons.cpp - src/gui/pages/torrents.cpp - src/gui/pages/history.cpp + src/gui/pages/anime_list.cc + src/gui/pages/now_playing.cc + src/gui/pages/statistics.cc + src/gui/pages/search.cc + src/gui/pages/seasons.cc + src/gui/pages/torrents.cc + src/gui/pages/history.cc # Custom widgets - src/gui/widgets/anime_info.cpp - src/gui/widgets/poster.cpp - src/gui/widgets/clickable_label.cpp - src/gui/widgets/sidebar.cpp - src/gui/widgets/text.cpp - src/gui/widgets/optional_date.cpp + src/gui/widgets/anime_info.cc + src/gui/widgets/poster.cc + src/gui/widgets/clickable_label.cc + src/gui/widgets/sidebar.cc + src/gui/widgets/text.cc + src/gui/widgets/optional_date.cc # Dialogs - src/gui/dialog/about.cpp - src/gui/dialog/information.cpp - src/gui/dialog/settings.cpp - src/gui/dialog/settings/application.cpp - src/gui/dialog/settings/services.cpp + src/gui/dialog/about.cc + src/gui/dialog/information.cc + src/gui/dialog/settings.cc + src/gui/dialog/settings/application.cc + src/gui/dialog/settings/services.cc # Translate - src/gui/translate/anime.cpp - src/gui/translate/anilist.cpp + src/gui/translate/anime.cc + src/gui/translate/anilist.cc # Services (only AniList for now) - src/services/services.cpp - src/services/anilist.cpp + src/services/services.cc + src/services/anilist.cc # Tracking - src/track/media.cpp + src/track/media.cc # Qt resources rc/icons.qrc @@ -112,7 +112,7 @@ src/sys/osx/filesystem.mm ) elseif(WIN32) # Windows - list(APPEND SRC_FILES src/sys/win32/dark_theme.cpp) + list(APPEND SRC_FILES src/sys/win32/dark_theme.cc) endif() add_executable(minori ${SRC_FILES}) diff -r 825506f0e221 -r 9b2b41f83a5e src/core/anime.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core/anime.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,290 @@ +/* + * anime.cpp: defining of custom anime-related + * datatypes & variables + */ +#include "core/anime.h" +#include "core/date.h" +#include "core/session.h" +#include +#include +#include +#include +#include + +namespace Anime { + +/* User list data */ +bool Anime::IsInUserList() const { + if (list_info_.get()) + return true; + return false; +} + +void Anime::AddToUserList() { + list_info_.reset(new ListInformation); +} + +void Anime::RemoveFromUserList() { + list_info_.reset(); +} + +ListStatus Anime::GetUserStatus() const { + assert(list_info_.get()); + return list_info_->status; +} + +int Anime::GetUserProgress() const { + assert(list_info_.get()); + return list_info_->progress; +} + +int Anime::GetUserScore() const { + assert(list_info_.get()); + return list_info_->score; +} + +Date Anime::GetUserDateStarted() const { + assert(list_info_.get()); + return list_info_->started; +} + +Date Anime::GetUserDateCompleted() const { + assert(list_info_.get()); + return list_info_->completed; +} + +bool Anime::GetUserIsPrivate() const { + assert(list_info_.get()); + return list_info_->is_private; +} + +unsigned int Anime::GetUserRewatchedTimes() const { + assert(list_info_.get()); + return list_info_->rewatched_times; +} + +bool Anime::GetUserIsRewatching() const { + assert(list_info_.get()); + return list_info_->rewatching; +} + +uint64_t Anime::GetUserTimeUpdated() const { + assert(list_info_.get()); + return list_info_->updated; +} + +std::string Anime::GetUserNotes() const { + assert(list_info_.get()); + return list_info_->notes; +} + +void Anime::SetUserStatus(ListStatus status) { + assert(list_info_.get()); + 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; +} + +void Anime::SetUserDateStarted(Date const& started) { + assert(list_info_.get()); + list_info_->started = started; +} + +void Anime::SetUserDateCompleted(Date const& completed) { + assert(list_info_.get()); + list_info_->completed = completed; +} + +void Anime::SetUserIsPrivate(bool is_private) { + assert(list_info_.get()); + list_info_->is_private = is_private; +} + +void Anime::SetUserRewatchedTimes(int rewatched) { + assert(list_info_.get()); + list_info_->rewatched_times = rewatched; +} + +void Anime::SetUserIsRewatching(bool rewatching) { + assert(list_info_.get()); + list_info_->rewatching = rewatching; +} + +void Anime::SetUserTimeUpdated(uint64_t updated) { + assert(list_info_.get()); + list_info_->updated = updated; +} + +void Anime::SetUserNotes(std::string const& notes) { + assert(list_info_.get()); + list_info_->notes = notes; +} + +/* Series data */ +int Anime::GetId() const { + return info_.id; +} + +std::string Anime::GetRomajiTitle() const { + return info_.title.romaji; +} + +std::string Anime::GetEnglishTitle() const { + return info_.title.english; +} + +std::string Anime::GetNativeTitle() const { + return info_.title.native; +} + +std::vector Anime::GetTitleSynonyms() const { + std::vector result; +#define IN_VECTOR(v, k) (std::count(v.begin(), v.end(), k)) +#define ADD_TO_SYNONYMS(v, k) \ + if (!k.empty() && !IN_VECTOR(v, k) && k != GetUserPreferredTitle()) \ + v.push_back(k) + ADD_TO_SYNONYMS(result, info_.title.english); + ADD_TO_SYNONYMS(result, info_.title.romaji); + ADD_TO_SYNONYMS(result, info_.title.native); + for (auto& synonym : info_.synonyms) { + ADD_TO_SYNONYMS(result, synonym); + } +#undef ADD_TO_SYNONYMS +#undef IN_VECTOR + return result; +} + +int Anime::GetEpisodes() const { + return info_.episodes; +} + +SeriesStatus Anime::GetAiringStatus() const { + return info_.status; +} + +Date Anime::GetAirDate() const { + return info_.air_date; +} + +std::vector Anime::GetGenres() const { + return info_.genres; +} + +std::vector Anime::GetProducers() const { + return info_.producers; +} + +SeriesFormat Anime::GetFormat() const { + return info_.format; +} + +SeriesSeason Anime::GetSeason() const { + return info_.season; +} + +int Anime::GetAudienceScore() const { + return info_.audience_score; +} + +std::string Anime::GetSynopsis() const { + return info_.synopsis; +} + +int Anime::GetDuration() const { + return info_.duration; +} + +std::string Anime::GetPosterUrl() const { + return info_.poster_url; +} + +std::string Anime::GetServiceUrl() const { + return "https://anilist.co/anime/" + std::to_string(GetId()); +} + +void Anime::SetId(int id) { + info_.id = id; +} + +void Anime::SetRomajiTitle(std::string const& title) { + info_.title.romaji = title; +} + +void Anime::SetEnglishTitle(std::string const& title) { + info_.title.english = title; +} + +void Anime::SetNativeTitle(std::string const& title) { + info_.title.native = title; +} + +void Anime::SetTitleSynonyms(std::vector const& synonyms) { + info_.synonyms = synonyms; +} + +void Anime::AddTitleSynonym(std::string const& synonym) { + info_.synonyms.push_back(synonym); +} + +void Anime::SetEpisodes(int episodes) { + info_.episodes = episodes; +} + +void Anime::SetAiringStatus(SeriesStatus status) { + info_.status = status; +} + +void Anime::SetAirDate(Date const& date) { + info_.air_date = date; +} + +void Anime::SetGenres(std::vector const& genres) { + info_.genres = genres; +} + +void Anime::SetProducers(std::vector const& producers) { + info_.producers = producers; +} + +void Anime::SetFormat(SeriesFormat format) { + info_.format = format; +} + +void Anime::SetSeason(SeriesSeason season) { + info_.season = season; +} + +void Anime::SetAudienceScore(int audience_score) { + info_.audience_score = audience_score; +} + +void Anime::SetSynopsis(std::string synopsis) { + info_.synopsis = synopsis; +} + +void Anime::SetDuration(int duration) { + info_.duration = duration; +} + +void Anime::SetPosterUrl(std::string url) { + info_.poster_url = url; +} + +std::string Anime::GetUserPreferredTitle() const { + switch (session.config.anime_list.language) { + case TitleLanguage::NATIVE: return (GetNativeTitle().empty()) ? GetRomajiTitle() : GetNativeTitle(); + case TitleLanguage::ENGLISH: return (GetEnglishTitle().empty()) ? GetRomajiTitle() : GetEnglishTitle(); + default: break; + } + return GetRomajiTitle(); +} + +} // namespace Anime diff -r 825506f0e221 -r 9b2b41f83a5e src/core/anime.cpp --- a/src/core/anime.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,290 +0,0 @@ -/* - * anime.cpp: defining of custom anime-related - * datatypes & variables - */ -#include "core/anime.h" -#include "core/date.h" -#include "core/session.h" -#include -#include -#include -#include -#include - -namespace Anime { - -/* User list data */ -bool Anime::IsInUserList() const { - if (list_info_.get()) - return true; - return false; -} - -void Anime::AddToUserList() { - list_info_.reset(new ListInformation); -} - -void Anime::RemoveFromUserList() { - list_info_.reset(); -} - -ListStatus Anime::GetUserStatus() const { - assert(list_info_.get()); - return list_info_->status; -} - -int Anime::GetUserProgress() const { - assert(list_info_.get()); - return list_info_->progress; -} - -int Anime::GetUserScore() const { - assert(list_info_.get()); - return list_info_->score; -} - -Date Anime::GetUserDateStarted() const { - assert(list_info_.get()); - return list_info_->started; -} - -Date Anime::GetUserDateCompleted() const { - assert(list_info_.get()); - return list_info_->completed; -} - -bool Anime::GetUserIsPrivate() const { - assert(list_info_.get()); - return list_info_->is_private; -} - -unsigned int Anime::GetUserRewatchedTimes() const { - assert(list_info_.get()); - return list_info_->rewatched_times; -} - -bool Anime::GetUserIsRewatching() const { - assert(list_info_.get()); - return list_info_->rewatching; -} - -uint64_t Anime::GetUserTimeUpdated() const { - assert(list_info_.get()); - return list_info_->updated; -} - -std::string Anime::GetUserNotes() const { - assert(list_info_.get()); - return list_info_->notes; -} - -void Anime::SetUserStatus(ListStatus status) { - assert(list_info_.get()); - 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; -} - -void Anime::SetUserDateStarted(Date const& started) { - assert(list_info_.get()); - list_info_->started = started; -} - -void Anime::SetUserDateCompleted(Date const& completed) { - assert(list_info_.get()); - list_info_->completed = completed; -} - -void Anime::SetUserIsPrivate(bool is_private) { - assert(list_info_.get()); - list_info_->is_private = is_private; -} - -void Anime::SetUserRewatchedTimes(int rewatched) { - assert(list_info_.get()); - list_info_->rewatched_times = rewatched; -} - -void Anime::SetUserIsRewatching(bool rewatching) { - assert(list_info_.get()); - list_info_->rewatching = rewatching; -} - -void Anime::SetUserTimeUpdated(uint64_t updated) { - assert(list_info_.get()); - list_info_->updated = updated; -} - -void Anime::SetUserNotes(std::string const& notes) { - assert(list_info_.get()); - list_info_->notes = notes; -} - -/* Series data */ -int Anime::GetId() const { - return info_.id; -} - -std::string Anime::GetRomajiTitle() const { - return info_.title.romaji; -} - -std::string Anime::GetEnglishTitle() const { - return info_.title.english; -} - -std::string Anime::GetNativeTitle() const { - return info_.title.native; -} - -std::vector Anime::GetTitleSynonyms() const { - std::vector result; -#define IN_VECTOR(v, k) (std::count(v.begin(), v.end(), k)) -#define ADD_TO_SYNONYMS(v, k) \ - if (!k.empty() && !IN_VECTOR(v, k) && k != GetUserPreferredTitle()) \ - v.push_back(k) - ADD_TO_SYNONYMS(result, info_.title.english); - ADD_TO_SYNONYMS(result, info_.title.romaji); - ADD_TO_SYNONYMS(result, info_.title.native); - for (auto& synonym : info_.synonyms) { - ADD_TO_SYNONYMS(result, synonym); - } -#undef ADD_TO_SYNONYMS -#undef IN_VECTOR - return result; -} - -int Anime::GetEpisodes() const { - return info_.episodes; -} - -SeriesStatus Anime::GetAiringStatus() const { - return info_.status; -} - -Date Anime::GetAirDate() const { - return info_.air_date; -} - -std::vector Anime::GetGenres() const { - return info_.genres; -} - -std::vector Anime::GetProducers() const { - return info_.producers; -} - -SeriesFormat Anime::GetFormat() const { - return info_.format; -} - -SeriesSeason Anime::GetSeason() const { - return info_.season; -} - -int Anime::GetAudienceScore() const { - return info_.audience_score; -} - -std::string Anime::GetSynopsis() const { - return info_.synopsis; -} - -int Anime::GetDuration() const { - return info_.duration; -} - -std::string Anime::GetPosterUrl() const { - return info_.poster_url; -} - -std::string Anime::GetServiceUrl() const { - return "https://anilist.co/anime/" + std::to_string(GetId()); -} - -void Anime::SetId(int id) { - info_.id = id; -} - -void Anime::SetRomajiTitle(std::string const& title) { - info_.title.romaji = title; -} - -void Anime::SetEnglishTitle(std::string const& title) { - info_.title.english = title; -} - -void Anime::SetNativeTitle(std::string const& title) { - info_.title.native = title; -} - -void Anime::SetTitleSynonyms(std::vector const& synonyms) { - info_.synonyms = synonyms; -} - -void Anime::AddTitleSynonym(std::string const& synonym) { - info_.synonyms.push_back(synonym); -} - -void Anime::SetEpisodes(int episodes) { - info_.episodes = episodes; -} - -void Anime::SetAiringStatus(SeriesStatus status) { - info_.status = status; -} - -void Anime::SetAirDate(Date const& date) { - info_.air_date = date; -} - -void Anime::SetGenres(std::vector const& genres) { - info_.genres = genres; -} - -void Anime::SetProducers(std::vector const& producers) { - info_.producers = producers; -} - -void Anime::SetFormat(SeriesFormat format) { - info_.format = format; -} - -void Anime::SetSeason(SeriesSeason season) { - info_.season = season; -} - -void Anime::SetAudienceScore(int audience_score) { - info_.audience_score = audience_score; -} - -void Anime::SetSynopsis(std::string synopsis) { - info_.synopsis = synopsis; -} - -void Anime::SetDuration(int duration) { - info_.duration = duration; -} - -void Anime::SetPosterUrl(std::string url) { - info_.poster_url = url; -} - -std::string Anime::GetUserPreferredTitle() const { - switch (session.config.anime_list.language) { - case TitleLanguage::NATIVE: return (GetNativeTitle().empty()) ? GetRomajiTitle() : GetNativeTitle(); - case TitleLanguage::ENGLISH: return (GetEnglishTitle().empty()) ? GetRomajiTitle() : GetEnglishTitle(); - default: break; - } - return GetRomajiTitle(); -} - -} // namespace Anime diff -r 825506f0e221 -r 9b2b41f83a5e src/core/anime_db.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core/anime_db.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,108 @@ +#include "core/anime_db.h" +#include "core/anime.h" +#include "core/strings.h" +#include + +namespace Anime { + +int Database::GetTotalAnimeAmount() { + int total = 0; + for (const auto& a : items) { + if (a.second.IsInUserList()) + total++; + } + return total; +} + +int Database::GetListsAnimeAmount(ListStatus status) { + if (status == ListStatus::NOT_IN_LIST) + return 0; + int total = 0; + for (const auto& a : items) { + if (a.second.IsInUserList() && a.second.GetUserStatus() == status) + total++; + } + return total; +} + +int Database::GetTotalEpisodeAmount() { + int total = 0; + for (const auto& a : items) { + if (a.second.IsInUserList()) { + total += a.second.GetUserRewatchedTimes() * a.second.GetEpisodes(); + total += a.second.GetUserProgress(); + } + } + return total; +} + +/* Returns the total watched amount in minutes. */ +int Database::GetTotalWatchedAmount() { + int total = 0; + for (const auto& a : items) { + if (a.second.IsInUserList()) { + total += a.second.GetDuration() * a.second.GetUserProgress(); + total += a.second.GetEpisodes() * a.second.GetDuration() * a.second.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& a : items) { + if (a.second.IsInUserList()) + total += a.second.GetDuration() * (a.second.GetEpisodes() - a.second.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& a : items) { + if (a.second.IsInUserList() && a.second.GetUserScore()) { + avg += a.second.GetUserScore(); + amt++; + } + } + return avg / amt; +} + +double Database::GetScoreDeviation() { + double squares_sum = 0, avg = GetAverageScore(); + int amt = 0; + for (const auto& a : items) { + if (a.second.IsInUserList() && a.second.GetUserScore()) { + squares_sum += std::pow(static_cast(a.second.GetUserScore()) - avg, 2); + amt++; + } + } + return (amt > 0) ? std::sqrt(squares_sum / amt) : 0; +} + +int Database::GetAnimeFromTitle(std::string title) { + if (title.empty()) + return 0; + for (const auto& a : items) { + if (a.second.GetUserPreferredTitle().find(title) != std::string::npos) + return a.second.GetId(); + for (const auto& t : a.second.GetTitleSynonyms()) { + if (t.find(title) != std::string::npos) { + return a.second.GetId(); + } + } + } + return 0; +} + +Database db; + +} // namespace Anime \ No newline at end of file diff -r 825506f0e221 -r 9b2b41f83a5e src/core/anime_db.cpp --- a/src/core/anime_db.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,108 +0,0 @@ -#include "core/anime_db.h" -#include "core/anime.h" -#include "core/strings.h" -#include - -namespace Anime { - -int Database::GetTotalAnimeAmount() { - int total = 0; - for (const auto& a : items) { - if (a.second.IsInUserList()) - total++; - } - return total; -} - -int Database::GetListsAnimeAmount(ListStatus status) { - if (status == ListStatus::NOT_IN_LIST) - return 0; - int total = 0; - for (const auto& a : items) { - if (a.second.IsInUserList() && a.second.GetUserStatus() == status) - total++; - } - return total; -} - -int Database::GetTotalEpisodeAmount() { - int total = 0; - for (const auto& a : items) { - if (a.second.IsInUserList()) { - total += a.second.GetUserRewatchedTimes() * a.second.GetEpisodes(); - total += a.second.GetUserProgress(); - } - } - return total; -} - -/* Returns the total watched amount in minutes. */ -int Database::GetTotalWatchedAmount() { - int total = 0; - for (const auto& a : items) { - if (a.second.IsInUserList()) { - total += a.second.GetDuration() * a.second.GetUserProgress(); - total += a.second.GetEpisodes() * a.second.GetDuration() * a.second.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& a : items) { - if (a.second.IsInUserList()) - total += a.second.GetDuration() * (a.second.GetEpisodes() - a.second.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& a : items) { - if (a.second.IsInUserList() && a.second.GetUserScore()) { - avg += a.second.GetUserScore(); - amt++; - } - } - return avg / amt; -} - -double Database::GetScoreDeviation() { - double squares_sum = 0, avg = GetAverageScore(); - int amt = 0; - for (const auto& a : items) { - if (a.second.IsInUserList() && a.second.GetUserScore()) { - squares_sum += std::pow(static_cast(a.second.GetUserScore()) - avg, 2); - amt++; - } - } - return (amt > 0) ? std::sqrt(squares_sum / amt) : 0; -} - -int Database::GetAnimeFromTitle(std::string title) { - if (title.empty()) - return 0; - for (const auto& a : items) { - if (a.second.GetUserPreferredTitle().find(title) != std::string::npos) - return a.second.GetId(); - for (const auto& t : a.second.GetTitleSynonyms()) { - if (t.find(title) != std::string::npos) { - return a.second.GetId(); - } - } - } - return 0; -} - -Database db; - -} // namespace Anime \ No newline at end of file diff -r 825506f0e221 -r 9b2b41f83a5e src/core/config.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core/config.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,106 @@ +/** + * config.cpp: + * parses the config... lol + **/ +#include "core/config.h" +#include "core/anime.h" +#include "core/filesystem.h" +#include "core/json.h" +#include +#include +#include +#include +#include + +std::map StringToTheme = { + {"Default", Themes::OS }, + {"Light", Themes::LIGHT}, + {"Dark", Themes::DARK } +}; + +std::map ThemeToString = { + {Themes::OS, "Default"}, + {Themes::LIGHT, "Light" }, + {Themes::DARK, "Dark" } +}; + +std::map ServiceToString{ + {Anime::Services::NONE, "None" }, + {Anime::Services::ANILIST, "AniList"} +}; + +std::map StringToService{ + {"None", Anime::Services::NONE }, + {"AniList", Anime::Services::ANILIST} +}; + +std::map AnimeTitleToStringMap = { + {Anime::TitleLanguage::ROMAJI, "Romaji" }, + {Anime::TitleLanguage::NATIVE, "Native" }, + {Anime::TitleLanguage::ENGLISH, "English"} +}; + +std::map StringToAnimeTitleMap = { + {"Romaji", Anime::TitleLanguage::ROMAJI }, + {"Native", Anime::TitleLanguage::NATIVE }, + {"English", Anime::TitleLanguage::ENGLISH} +}; + +int Config::Load() { + Filesystem::Path cfg_path = Filesystem::GetConfigPath(); + if (!cfg_path.Exists()) + return 0; + std::ifstream config(cfg_path.GetPath(), std::ifstream::in); + auto config_js = nlohmann::json::parse(config); + service = StringToService[JSON::GetString(config_js, "/General/Service"_json_pointer)]; + anime_list.language = + StringToAnimeTitleMap[JSON::GetString(config_js, "/Anime List/Title language"_json_pointer, "Romaji")]; + anime_list.display_aired_episodes = + JSON::GetBoolean(config_js, "/Anime List/Display only aired episodes"_json_pointer, true); + anime_list.display_available_episodes = + JSON::GetBoolean(config_js, "/Anime List/Display only available episodes in library"_json_pointer, true); + anime_list.highlight_anime_if_available = + JSON::GetBoolean(config_js, "/Anime List/Highlight anime if available"_json_pointer, true); + anime_list.highlighted_anime_above_others = + JSON::GetBoolean(config_js, "/Anime List/Display highlighted anime above others"_json_pointer); + anilist.auth_token = JSON::GetString(config_js, "/Authorization/AniList/Auth Token"_json_pointer); + anilist.username = JSON::GetString(config_js, "/Authorization/AniList/Username"_json_pointer); + anilist.user_id = JSON::GetInt(config_js, "/Authorization/AniList/User ID"_json_pointer); + theme = StringToTheme[JSON::GetString(config_js, "/Appearance/Theme"_json_pointer)]; + config.close(); + return 0; +} + +int Config::Save() { + Filesystem::Path cfg_path = Filesystem::GetConfigPath(); + if (!cfg_path.GetParent().Exists()) + cfg_path.GetParent().CreateDirectories(); + std::ofstream config(cfg_path.GetPath(), std::ofstream::out | std::ofstream::trunc); + /* clang-format off */ + nlohmann::json config_js = { + {"General", { + {"Service", ServiceToString[service]} + }}, + {"Anime List", { + {"Title language", AnimeTitleToStringMap[anime_list.language]}, + {"Display only aired episodes", anime_list.display_aired_episodes}, + {"Display only available episodes in library", anime_list.display_available_episodes}, + {"Highlight anime if available", anime_list.highlight_anime_if_available}, + {"Display highlighted anime above others", anime_list.highlighted_anime_above_others} + }}, + {"Authorization", { + {"AniList", { + {"Auth Token", anilist.auth_token}, + {"Username", anilist.username}, + {"User ID", anilist.user_id} + }} + }}, + {"Appearance", { + {"Theme", ThemeToString[theme]} + }} + }; + /* clang-format on */ + config << std::setw(4) << config_js << std::endl; + config.close(); + return 0; +} diff -r 825506f0e221 -r 9b2b41f83a5e src/core/config.cpp --- a/src/core/config.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,106 +0,0 @@ -/** - * config.cpp: - * parses the config... lol - **/ -#include "core/config.h" -#include "core/anime.h" -#include "core/filesystem.h" -#include "core/json.h" -#include -#include -#include -#include -#include - -std::map StringToTheme = { - {"Default", Themes::OS }, - {"Light", Themes::LIGHT}, - {"Dark", Themes::DARK } -}; - -std::map ThemeToString = { - {Themes::OS, "Default"}, - {Themes::LIGHT, "Light" }, - {Themes::DARK, "Dark" } -}; - -std::map ServiceToString{ - {Anime::Services::NONE, "None" }, - {Anime::Services::ANILIST, "AniList"} -}; - -std::map StringToService{ - {"None", Anime::Services::NONE }, - {"AniList", Anime::Services::ANILIST} -}; - -std::map AnimeTitleToStringMap = { - {Anime::TitleLanguage::ROMAJI, "Romaji" }, - {Anime::TitleLanguage::NATIVE, "Native" }, - {Anime::TitleLanguage::ENGLISH, "English"} -}; - -std::map StringToAnimeTitleMap = { - {"Romaji", Anime::TitleLanguage::ROMAJI }, - {"Native", Anime::TitleLanguage::NATIVE }, - {"English", Anime::TitleLanguage::ENGLISH} -}; - -int Config::Load() { - Filesystem::Path cfg_path = Filesystem::GetConfigPath(); - if (!cfg_path.Exists()) - return 0; - std::ifstream config(cfg_path.GetPath(), std::ifstream::in); - auto config_js = nlohmann::json::parse(config); - service = StringToService[JSON::GetString(config_js, "/General/Service"_json_pointer)]; - anime_list.language = - StringToAnimeTitleMap[JSON::GetString(config_js, "/Anime List/Title language"_json_pointer, "Romaji")]; - anime_list.display_aired_episodes = - JSON::GetBoolean(config_js, "/Anime List/Display only aired episodes"_json_pointer, true); - anime_list.display_available_episodes = - JSON::GetBoolean(config_js, "/Anime List/Display only available episodes in library"_json_pointer, true); - anime_list.highlight_anime_if_available = - JSON::GetBoolean(config_js, "/Anime List/Highlight anime if available"_json_pointer, true); - anime_list.highlighted_anime_above_others = - JSON::GetBoolean(config_js, "/Anime List/Display highlighted anime above others"_json_pointer); - anilist.auth_token = JSON::GetString(config_js, "/Authorization/AniList/Auth Token"_json_pointer); - anilist.username = JSON::GetString(config_js, "/Authorization/AniList/Username"_json_pointer); - anilist.user_id = JSON::GetInt(config_js, "/Authorization/AniList/User ID"_json_pointer); - theme = StringToTheme[JSON::GetString(config_js, "/Appearance/Theme"_json_pointer)]; - config.close(); - return 0; -} - -int Config::Save() { - Filesystem::Path cfg_path = Filesystem::GetConfigPath(); - if (!cfg_path.GetParent().Exists()) - cfg_path.GetParent().CreateDirectories(); - std::ofstream config(cfg_path.GetPath(), std::ofstream::out | std::ofstream::trunc); - /* clang-format off */ - nlohmann::json config_js = { - {"General", { - {"Service", ServiceToString[service]} - }}, - {"Anime List", { - {"Title language", AnimeTitleToStringMap[anime_list.language]}, - {"Display only aired episodes", anime_list.display_aired_episodes}, - {"Display only available episodes in library", anime_list.display_available_episodes}, - {"Highlight anime if available", anime_list.highlight_anime_if_available}, - {"Display highlighted anime above others", anime_list.highlighted_anime_above_others} - }}, - {"Authorization", { - {"AniList", { - {"Auth Token", anilist.auth_token}, - {"Username", anilist.username}, - {"User ID", anilist.user_id} - }} - }}, - {"Appearance", { - {"Theme", ThemeToString[theme]} - }} - }; - /* clang-format on */ - config << std::setw(4) << config_js << std::endl; - config.close(); - return 0; -} diff -r 825506f0e221 -r 9b2b41f83a5e src/core/date.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core/date.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,122 @@ +#include "core/date.h" +#include "core/json.h" +#include +#include +#include +#include +#include + +/* An implementation of AniList's "fuzzy date" */ + +#define CLAMP(x, low, high) (std::max(low, std::min(high, x))) + +Date::Date() { +} + +Date::Date(unsigned int y) { + SetYear(y); +} + +Date::Date(unsigned int y, unsigned int m, unsigned int d) { + SetYear(y); + SetMonth(m); + SetDay(d); +} + +Date::Date(const QDate& date) { + SetYear(date.year()); + SetMonth(date.month()); + SetDay(date.day()); +} + +void Date::VoidYear() { + year.reset(); +} + +void Date::VoidMonth() { + month.reset(); +} + +void Date::VoidDay() { + day.reset(); +} + +void Date::SetYear(unsigned int y) { + year.reset(new unsigned int(y)); +} + +void Date::SetMonth(unsigned int m) { + month.reset(new unsigned int(CLAMP(m, 1U, 12U))); +} + +void Date::SetDay(unsigned int d) { + day.reset(new unsigned int(CLAMP(d, 1U, 31U))); +} + +unsigned int Date::GetYear() const { + unsigned int* ptr = year.get(); + if (ptr != nullptr) + return *year; + return -1; +} + +unsigned int Date::GetMonth() const { + unsigned int* ptr = month.get(); + if (ptr != nullptr) + return *month; + return -1; +} + +unsigned int Date::GetDay() const { + unsigned int* ptr = day.get(); + if (ptr != nullptr) + return *day; + return -1; +} + +bool Date::IsValid() const { + return year.get() && month.get() && day.get(); +} + +bool Date::operator<(const Date& other) const { + unsigned int y = GetYear(), m = GetMonth(), d = GetDay(); + unsigned int o_y = other.GetYear(), o_m = other.GetMonth(), o_d = other.GetDay(); + return std::tie(y, m, d) < std::tie(o_y, o_m, o_d); +} + +bool Date::operator>(const Date& other) const { + return other < (*this); +} + +bool Date::operator<=(const Date& other) const { + return !((*this) > other); +} + +bool Date::operator>=(const Date& other) const { + return !((*this) < other); +} + +QDate Date::GetAsQDate() const { + /* QDates don't support "missing" values, for good reason. */ + if (IsValid()) + return QDate(*year, *month, *day); + else + return QDate(); +} + +nlohmann::json Date::GetAsAniListJson() const { + nlohmann::json result = {}; + if (year.get()) + result["year"] = *year; + else + result["year"] = nullptr; + if (month.get()) + result["month"] = *month; + else + result["month"] = nullptr; + if (day.get()) + result["day"] = *day; + else + result["day"] = nullptr; + return result; +} diff -r 825506f0e221 -r 9b2b41f83a5e src/core/date.cpp --- a/src/core/date.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,122 +0,0 @@ -#include "core/date.h" -#include "core/json.h" -#include -#include -#include -#include -#include - -/* An implementation of AniList's "fuzzy date" */ - -#define CLAMP(x, low, high) (std::max(low, std::min(high, x))) - -Date::Date() { -} - -Date::Date(unsigned int y) { - SetYear(y); -} - -Date::Date(unsigned int y, unsigned int m, unsigned int d) { - SetYear(y); - SetMonth(m); - SetDay(d); -} - -Date::Date(const QDate& date) { - SetYear(date.year()); - SetMonth(date.month()); - SetDay(date.day()); -} - -void Date::VoidYear() { - year.reset(); -} - -void Date::VoidMonth() { - month.reset(); -} - -void Date::VoidDay() { - day.reset(); -} - -void Date::SetYear(unsigned int y) { - year.reset(new unsigned int(y)); -} - -void Date::SetMonth(unsigned int m) { - month.reset(new unsigned int(CLAMP(m, 1U, 12U))); -} - -void Date::SetDay(unsigned int d) { - day.reset(new unsigned int(CLAMP(d, 1U, 31U))); -} - -unsigned int Date::GetYear() const { - unsigned int* ptr = year.get(); - if (ptr != nullptr) - return *year; - return -1; -} - -unsigned int Date::GetMonth() const { - unsigned int* ptr = month.get(); - if (ptr != nullptr) - return *month; - return -1; -} - -unsigned int Date::GetDay() const { - unsigned int* ptr = day.get(); - if (ptr != nullptr) - return *day; - return -1; -} - -bool Date::IsValid() const { - return year.get() && month.get() && day.get(); -} - -bool Date::operator<(const Date& other) const { - unsigned int y = GetYear(), m = GetMonth(), d = GetDay(); - unsigned int o_y = other.GetYear(), o_m = other.GetMonth(), o_d = other.GetDay(); - return std::tie(y, m, d) < std::tie(o_y, o_m, o_d); -} - -bool Date::operator>(const Date& other) const { - return other < (*this); -} - -bool Date::operator<=(const Date& other) const { - return !((*this) > other); -} - -bool Date::operator>=(const Date& other) const { - return !((*this) < other); -} - -QDate Date::GetAsQDate() const { - /* QDates don't support "missing" values, for good reason. */ - if (IsValid()) - return QDate(*year, *month, *day); - else - return QDate(); -} - -nlohmann::json Date::GetAsAniListJson() const { - nlohmann::json result = {}; - if (year.get()) - result["year"] = *year; - else - result["year"] = nullptr; - if (month.get()) - result["month"] = *month; - else - result["month"] = nullptr; - if (day.get()) - result["day"] = *day; - else - result["day"] = nullptr; - return result; -} diff -r 825506f0e221 -r 9b2b41f83a5e src/core/filesystem.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core/filesystem.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,139 @@ +#ifdef WIN32 +# include +#elif defined(MACOSX) +# include "sys/osx/filesystem.h" +#elif defined(__linux__) +# include +# include +#endif + +#ifdef WIN32 +# define DELIM "\\" +#else +# define DELIM "/" +# include +# include +# include +#endif + +#include "core/filesystem.h" +#include "core/config.h" +#include "core/strings.h" +#include + +namespace Filesystem { + +Path::Path() { + _path = ""; +} +Path::Path(const std::string& path) { + _path = path; +} +Path::Path(const Path& path) { + _path = path.GetPath(); +} + +bool Path::CreateDirectories() const { + std::string temp = ""; + size_t start; + size_t end = 0; + temp.append(_path.substr(0, _path.find_first_not_of(DELIM, end))); + + while ((start = _path.find_first_not_of(DELIM, end)) != std::string::npos) { + end = _path.find(DELIM, start); + temp.append(_path.substr(start, end - start)); +#ifdef WIN32 + if (!CreateDirectoryW(Strings::ToWstring(temp).c_str(), NULL) && GetLastError() == ERROR_PATH_NOT_FOUND) + /* ERROR_PATH_NOT_FOUND should NOT happen here */ + return false; +#else + struct stat st; + if (stat(temp.c_str(), &st) == -1) + mkdir(temp.c_str(), 0755); +#endif + temp.append(DELIM); + } + return true; +} + +bool Path::Exists() const { +#if WIN32 + std::wstring buf = Strings::ToWstring(_path); + return GetFileAttributesW(buf.c_str()) != INVALID_FILE_ATTRIBUTES; +#else + struct stat st; + return stat(_path.c_str(), &st) == 0; +#endif +} + +std::string Path::Basename() const { + unsigned long long pos = _path.find_last_of(DELIM); + return pos != std::string::npos ? _path.substr(pos + 1, _path.length()) : ""; +} + +std::string Path::Stem() const { + std::string basename = Basename(); + unsigned long long pos = basename.find_last_of("."); + return pos != std::string::npos ? basename.substr(0, pos) : ""; +} + +std::string Path::Extension() const { + std::string basename = Basename(); + unsigned long long pos = basename.find_last_of("."); + return pos != std::string::npos ? basename.substr(pos + 1, basename.length()) : ""; +} + +Path Path::GetParent() const { + return _path.substr(0, _path.find_last_of(DELIM)); +} + +void Path::SetPath(const std::string& path) { + _path = path; +} + +std::string Path::GetPath() const { + return _path; +} + +Path GetDotPath(void) { + std::string ret = ""; +#ifdef WIN32 + std::wstring buf(MAX_PATH, '\0'); + if (SHGetFolderPathAndSubDirW(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, CONFIG_WDIR, &buf.front()) == + S_OK) { + buf.resize(buf.find('\0')); + ret += Strings::ToUtf8String(buf); + } +#elif defined(MACOSX) + ret += osx::GetApplicationSupportDirectory(); + ret += DELIM CONFIG_DIR; +#else // just assume POSIX + if (getenv("HOME") != NULL) + ret += getenv("HOME"); +# ifdef __linux__ + else + ret += getpwuid(getuid())->pw_dir; +# endif // __linux__ + if (!ret.empty()) + ret += DELIM ".config" DELIM CONFIG_DIR; +#endif // !WIN32 && !MACOSX + return ret; +} + +Path GetConfigPath(void) { + std::string ret = ""; + ret += GetDotPath().GetPath(); + if (!ret.empty()) + ret += DELIM CONFIG_NAME; + return ret; +} + +Path GetAnimeDBPath(void) { + std::string ret = ""; + ret += GetDotPath().GetPath(); + if (!ret.empty()) + ret += DELIM "anime" DELIM "db.json"; + return ret; +} + +} // namespace Filesystem diff -r 825506f0e221 -r 9b2b41f83a5e src/core/filesystem.cpp --- a/src/core/filesystem.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,139 +0,0 @@ -#ifdef WIN32 -# include -#elif defined(MACOSX) -# include "sys/osx/filesystem.h" -#elif defined(__linux__) -# include -# include -#endif - -#ifdef WIN32 -# define DELIM "\\" -#else -# define DELIM "/" -# include -# include -# include -#endif - -#include "core/filesystem.h" -#include "core/config.h" -#include "core/strings.h" -#include - -namespace Filesystem { - -Path::Path() { - _path = ""; -} -Path::Path(const std::string& path) { - _path = path; -} -Path::Path(const Path& path) { - _path = path.GetPath(); -} - -bool Path::CreateDirectories() const { - std::string temp = ""; - size_t start; - size_t end = 0; - temp.append(_path.substr(0, _path.find_first_not_of(DELIM, end))); - - while ((start = _path.find_first_not_of(DELIM, end)) != std::string::npos) { - end = _path.find(DELIM, start); - temp.append(_path.substr(start, end - start)); -#ifdef WIN32 - if (!CreateDirectoryW(Strings::ToWstring(temp).c_str(), NULL) && GetLastError() == ERROR_PATH_NOT_FOUND) - /* ERROR_PATH_NOT_FOUND should NOT happen here */ - return false; -#else - struct stat st; - if (stat(temp.c_str(), &st) == -1) - mkdir(temp.c_str(), 0755); -#endif - temp.append(DELIM); - } - return true; -} - -bool Path::Exists() const { -#if WIN32 - std::wstring buf = Strings::ToWstring(_path); - return GetFileAttributesW(buf.c_str()) != INVALID_FILE_ATTRIBUTES; -#else - struct stat st; - return stat(_path.c_str(), &st) == 0; -#endif -} - -std::string Path::Basename() const { - unsigned long long pos = _path.find_last_of(DELIM); - return pos != std::string::npos ? _path.substr(pos + 1, _path.length()) : ""; -} - -std::string Path::Stem() const { - std::string basename = Basename(); - unsigned long long pos = basename.find_last_of("."); - return pos != std::string::npos ? basename.substr(0, pos) : ""; -} - -std::string Path::Extension() const { - std::string basename = Basename(); - unsigned long long pos = basename.find_last_of("."); - return pos != std::string::npos ? basename.substr(pos + 1, basename.length()) : ""; -} - -Path Path::GetParent() const { - return _path.substr(0, _path.find_last_of(DELIM)); -} - -void Path::SetPath(const std::string& path) { - _path = path; -} - -std::string Path::GetPath() const { - return _path; -} - -Path GetDotPath(void) { - std::string ret = ""; -#ifdef WIN32 - std::wstring buf(MAX_PATH, '\0'); - if (SHGetFolderPathAndSubDirW(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, CONFIG_WDIR, &buf.front()) == - S_OK) { - buf.resize(buf.find('\0')); - ret += Strings::ToUtf8String(buf); - } -#elif defined(MACOSX) - ret += osx::GetApplicationSupportDirectory(); - ret += DELIM CONFIG_DIR; -#else // just assume POSIX - if (getenv("HOME") != NULL) - ret += getenv("HOME"); -# ifdef __linux__ - else - ret += getpwuid(getuid())->pw_dir; -# endif // __linux__ - if (!ret.empty()) - ret += DELIM ".config" DELIM CONFIG_DIR; -#endif // !WIN32 && !MACOSX - return ret; -} - -Path GetConfigPath(void) { - std::string ret = ""; - ret += GetDotPath().GetPath(); - if (!ret.empty()) - ret += DELIM CONFIG_NAME; - return ret; -} - -Path GetAnimeDBPath(void) { - std::string ret = ""; - ret += GetDotPath().GetPath(); - if (!ret.empty()) - ret += DELIM "anime" DELIM "db.json"; - return ret; -} - -} // namespace Filesystem diff -r 825506f0e221 -r 9b2b41f83a5e src/core/http.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core/http.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,71 @@ +#include "core/http.h" +#include "core/session.h" +#include +#include +#include +#include +#include + +namespace HTTP { + +static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userdata) { + reinterpret_cast(userdata)->append(reinterpret_cast(contents), size * nmemb); + return size * nmemb; +} + +QByteArray Get(std::string url, std::vector headers) { + struct curl_slist* list = NULL; + QByteArray userdata; + + CURL* curl = curl_easy_init(); + if (curl) { + for (const std::string& h : headers) { + list = curl_slist_append(list, h.c_str()); + } + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &userdata); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + /* Use system certs... useful on Windows. */ + curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); + CURLcode res = curl_easy_perform(curl); + session.IncrementRequests(); + curl_easy_cleanup(curl); + if (res != CURLE_OK) { + QMessageBox box(QMessageBox::Icon::Critical, "", + QString("curl_easy_perform(curl) failed!: ") + QString(curl_easy_strerror(res))); + box.exec(); + } + } + return userdata; +} + +QByteArray Post(std::string url, std::string data, std::vector headers) { + struct curl_slist* list = NULL; + QByteArray userdata; + + CURL* curl = curl_easy_init(); + if (curl) { + for (const std::string& h : headers) { + list = curl_slist_append(list, h.c_str()); + } + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &userdata); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + /* Use system certs... useful on Windows. */ + curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); + CURLcode res = curl_easy_perform(curl); + session.IncrementRequests(); + curl_easy_cleanup(curl); + if (res != CURLE_OK) { + QMessageBox box(QMessageBox::Icon::Critical, "", + QString("curl_easy_perform(curl) failed!: ") + QString(curl_easy_strerror(res))); + box.exec(); + } + } + return userdata; +} + +} // namespace HTTP diff -r 825506f0e221 -r 9b2b41f83a5e src/core/http.cpp --- a/src/core/http.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -#include "core/http.h" -#include "core/session.h" -#include -#include -#include -#include -#include - -namespace HTTP { - -static size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userdata) { - reinterpret_cast(userdata)->append(reinterpret_cast(contents), size * nmemb); - return size * nmemb; -} - -QByteArray Get(std::string url, std::vector headers) { - struct curl_slist* list = NULL; - QByteArray userdata; - - CURL* curl = curl_easy_init(); - if (curl) { - for (const std::string& h : headers) { - list = curl_slist_append(list, h.c_str()); - } - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &userdata); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); - /* Use system certs... useful on Windows. */ - curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); - CURLcode res = curl_easy_perform(curl); - session.IncrementRequests(); - curl_easy_cleanup(curl); - if (res != CURLE_OK) { - QMessageBox box(QMessageBox::Icon::Critical, "", - QString("curl_easy_perform(curl) failed!: ") + QString(curl_easy_strerror(res))); - box.exec(); - } - } - return userdata; -} - -QByteArray Post(std::string url, std::string data, std::vector headers) { - struct curl_slist* list = NULL; - QByteArray userdata; - - CURL* curl = curl_easy_init(); - if (curl) { - for (const std::string& h : headers) { - list = curl_slist_append(list, h.c_str()); - } - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &userdata); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); - /* Use system certs... useful on Windows. */ - curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); - CURLcode res = curl_easy_perform(curl); - session.IncrementRequests(); - curl_easy_cleanup(curl); - if (res != CURLE_OK) { - QMessageBox box(QMessageBox::Icon::Critical, "", - QString("curl_easy_perform(curl) failed!: ") + QString(curl_easy_strerror(res))); - box.exec(); - } - } - return userdata; -} - -} // namespace HTTP diff -r 825506f0e221 -r 9b2b41f83a5e src/core/json.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core/json.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,33 @@ +#include "core/json.h" + +namespace JSON { + +std::string GetString(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, std::string def) { + if (json.contains(ptr) && json[ptr].is_string()) + return json[ptr].get(); + else + return def; +} + +int GetInt(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, int def) { + if (json.contains(ptr) && json[ptr].is_number()) + return json[ptr].get(); + else + return def; +} + +bool GetBoolean(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, bool def) { + if (json.contains(ptr) && json[ptr].is_boolean()) + return json[ptr].get(); + else + return def; +} + +double GetDouble(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, double def) { + if (json.contains(ptr) && json[ptr].is_number()) + return json[ptr].get(); + else + return def; +} + +} // namespace JSON diff -r 825506f0e221 -r 9b2b41f83a5e src/core/json.cpp --- a/src/core/json.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -#include "core/json.h" - -namespace JSON { - -std::string GetString(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, std::string def) { - if (json.contains(ptr) && json[ptr].is_string()) - return json[ptr].get(); - else - return def; -} - -int GetInt(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, int def) { - if (json.contains(ptr) && json[ptr].is_number()) - return json[ptr].get(); - else - return def; -} - -bool GetBoolean(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, bool def) { - if (json.contains(ptr) && json[ptr].is_boolean()) - return json[ptr].get(); - else - return def; -} - -double GetDouble(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, double def) { - if (json.contains(ptr) && json[ptr].is_number()) - return json[ptr].get(); - else - return def; -} - -} // namespace JSON diff -r 825506f0e221 -r 9b2b41f83a5e src/core/strings.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core/strings.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,114 @@ +/** + * strings.cpp: Useful functions for manipulating strings + **/ +#include "core/strings.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Strings { + +std::string Implode(const std::vector& vector, const std::string& delimiter) { + if (vector.size() < 1) + return "-"; + std::string out = ""; + for (unsigned long long i = 0; i < vector.size(); i++) { + out.append(vector.at(i)); + if (i < vector.size() - 1) + out.append(delimiter); + } + return out; +} + +std::string ReplaceAll(const std::string& string, const std::string& find, const std::string& replace) { + std::string result; + size_t pos, find_len = find.size(), from = 0; + while ((pos = string.find(find, from)) != std::string::npos) { + result.append(string, from, pos - from); + result.append(replace); + from = pos + find_len; + } + result.append(string, from, std::string::npos); + return result; +} + +/* this function probably fucks your RAM but whatevs */ +std::string SanitizeLineEndings(const std::string& string) { + std::string result(string); + result = ReplaceAll(result, "\r\n", "\n"); + result = ReplaceAll(result, "
", "\n"); + result = ReplaceAll(result, "\n\n\n", "\n\n"); + return result; +} + +std::string RemoveHtmlTags(const std::string& string) { + std::string html(string); + while (html.find("<") != std::string::npos) { + auto startpos = html.find("<"); + auto endpos = html.find(">") + 1; + + if (endpos != std::string::npos) { + html.erase(startpos, endpos - startpos); + } + } + return html; +} + +std::string TextifySynopsis(const std::string& string) { + return RemoveHtmlTags(SanitizeLineEndings(string)); +} + +/* these functions suck for i18n!... + but we only use them with JSON + stuff anyway */ +std::string ToUpper(const std::string& string) { + std::string result(string); + std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return std::toupper(c); }); + return result; +} + +std::string ToLower(const std::string& string) { + std::string result(string); + std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return std::tolower(c); }); + return result; +} + +std::wstring ToWstring(const std::string& string) { + std::wstring_convert, wchar_t> converter; + return converter.from_bytes(string); +} + +std::wstring ToWstring(const QString& string) { + std::wstring arr(string.size(), L'\0'); + string.toWCharArray(&arr.front()); + return arr; +} + +std::string ToUtf8String(const std::wstring& wstring) { + std::wstring_convert, wchar_t> converter; + return converter.to_bytes(wstring); +} + +std::string ToUtf8String(const QString& string) { + QByteArray ba = string.toUtf8(); + return std::string(ba.constData(), ba.size()); +} + +std::string ToUtf8String(const QByteArray& ba) { + return std::string(ba.constData(), ba.size()); +} + +QString ToQString(const std::string& string) { + return QString::fromUtf8(string.c_str(), string.length()); +} + +QString ToQString(const std::wstring& wstring) { + return QString::fromWCharArray(wstring.c_str(), wstring.length()); +} + +} // namespace Strings diff -r 825506f0e221 -r 9b2b41f83a5e src/core/strings.cpp --- a/src/core/strings.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,114 +0,0 @@ -/** - * strings.cpp: Useful functions for manipulating strings - **/ -#include "core/strings.h" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Strings { - -std::string Implode(const std::vector& vector, const std::string& delimiter) { - if (vector.size() < 1) - return "-"; - std::string out = ""; - for (unsigned long long i = 0; i < vector.size(); i++) { - out.append(vector.at(i)); - if (i < vector.size() - 1) - out.append(delimiter); - } - return out; -} - -std::string ReplaceAll(const std::string& string, const std::string& find, const std::string& replace) { - std::string result; - size_t pos, find_len = find.size(), from = 0; - while ((pos = string.find(find, from)) != std::string::npos) { - result.append(string, from, pos - from); - result.append(replace); - from = pos + find_len; - } - result.append(string, from, std::string::npos); - return result; -} - -/* this function probably fucks your RAM but whatevs */ -std::string SanitizeLineEndings(const std::string& string) { - std::string result(string); - result = ReplaceAll(result, "\r\n", "\n"); - result = ReplaceAll(result, "
", "\n"); - result = ReplaceAll(result, "\n\n\n", "\n\n"); - return result; -} - -std::string RemoveHtmlTags(const std::string& string) { - std::string html(string); - while (html.find("<") != std::string::npos) { - auto startpos = html.find("<"); - auto endpos = html.find(">") + 1; - - if (endpos != std::string::npos) { - html.erase(startpos, endpos - startpos); - } - } - return html; -} - -std::string TextifySynopsis(const std::string& string) { - return RemoveHtmlTags(SanitizeLineEndings(string)); -} - -/* these functions suck for i18n!... - but we only use them with JSON - stuff anyway */ -std::string ToUpper(const std::string& string) { - std::string result(string); - std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return std::toupper(c); }); - return result; -} - -std::string ToLower(const std::string& string) { - std::string result(string); - std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return std::tolower(c); }); - return result; -} - -std::wstring ToWstring(const std::string& string) { - std::wstring_convert, wchar_t> converter; - return converter.from_bytes(string); -} - -std::wstring ToWstring(const QString& string) { - std::wstring arr(string.size(), L'\0'); - string.toWCharArray(&arr.front()); - return arr; -} - -std::string ToUtf8String(const std::wstring& wstring) { - std::wstring_convert, wchar_t> converter; - return converter.to_bytes(wstring); -} - -std::string ToUtf8String(const QString& string) { - QByteArray ba = string.toUtf8(); - return std::string(ba.constData(), ba.size()); -} - -std::string ToUtf8String(const QByteArray& ba) { - return std::string(ba.constData(), ba.size()); -} - -QString ToQString(const std::string& string) { - return QString::fromUtf8(string.c_str(), string.length()); -} - -QString ToQString(const std::wstring& wstring) { - return QString::fromWCharArray(wstring.c_str(), wstring.length()); -} - -} // namespace Strings diff -r 825506f0e221 -r 9b2b41f83a5e src/core/time.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/core/time.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,64 @@ +#include "core/time.h" +#include +#include +#include +#include +#include + +namespace Time { + +Duration::Duration(int64_t l) { + length = l; +} + +std::string Duration::AsRelativeString() { + std::string result; + + auto get = [](int64_t val, const std::string& s, const std::string& p) { + return std::to_string(val) + " " + (val == 1 ? s : p); + }; + + if (InSeconds() < 60) + result = get(InSeconds(), "second", "seconds"); + else if (InMinutes() < 60) + result = get(InMinutes(), "minute", "minutes"); + else if (InHours() < 24) + result = get(InHours(), "hour", "hours"); + else if (InDays() < 28) + result = get(InDays(), "day", "days"); + else if (InDays() < 365) + result = get(InDays() / 30, "month", "months"); + else + result = get(InDays() / 365, "year", "years"); + + if (length < 0) + result = "In " + result; + else + result += " ago"; + + return result; +} + +int64_t Duration::InSeconds() { + return length; +} + +int64_t Duration::InMinutes() { + return std::llround(static_cast(length) / 60.0); +} + +int64_t Duration::InHours() { + return std::llround(static_cast(length) / 3600.0); +} + +int64_t Duration::InDays() { + return std::llround(static_cast(length) / 86400.0); +} + +int64_t GetSystemTime() { + assert(sizeof(int64_t) >= sizeof(time_t)); + time_t t = std::time(nullptr); + return *reinterpret_cast(&t); +} + +} // namespace Time \ No newline at end of file diff -r 825506f0e221 -r 9b2b41f83a5e src/core/time.cpp --- a/src/core/time.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -#include "core/time.h" -#include -#include -#include -#include -#include - -namespace Time { - -Duration::Duration(int64_t l) { - length = l; -} - -std::string Duration::AsRelativeString() { - std::string result; - - auto get = [](int64_t val, const std::string& s, const std::string& p) { - return std::to_string(val) + " " + (val == 1 ? s : p); - }; - - if (InSeconds() < 60) - result = get(InSeconds(), "second", "seconds"); - else if (InMinutes() < 60) - result = get(InMinutes(), "minute", "minutes"); - else if (InHours() < 24) - result = get(InHours(), "hour", "hours"); - else if (InDays() < 28) - result = get(InDays(), "day", "days"); - else if (InDays() < 365) - result = get(InDays() / 30, "month", "months"); - else - result = get(InDays() / 365, "year", "years"); - - if (length < 0) - result = "In " + result; - else - result += " ago"; - - return result; -} - -int64_t Duration::InSeconds() { - return length; -} - -int64_t Duration::InMinutes() { - return std::llround(static_cast(length) / 60.0); -} - -int64_t Duration::InHours() { - return std::llround(static_cast(length) / 3600.0); -} - -int64_t Duration::InDays() { - return std::llround(static_cast(length) / 86400.0); -} - -int64_t GetSystemTime() { - assert(sizeof(int64_t) >= sizeof(time_t)); - time_t t = std::time(nullptr); - return *reinterpret_cast(&t); -} - -} // namespace Time \ No newline at end of file diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/dark_theme.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/dark_theme.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,100 @@ +#include "core/config.h" +#include "core/session.h" +#include +#include +#include +#ifdef MACOSX +# include "sys/osx/dark_theme.h" +#else +# include "sys/win32/dark_theme.h" +#endif + +namespace DarkTheme { + +bool IsInDarkMode() { + if (session.config.theme != Themes::OS) + return (session.config.theme == Themes::DARK); +#ifdef MACOSX + if (osx::DarkThemeAvailable()) { + if (osx::IsInDarkTheme()) { + return true; + } else { + return false; + } + } +#elif defined(WIN32) + if (win32::DarkThemeAvailable()) { + if (win32::IsInDarkTheme()) { + return true; + } else { + return false; + } + } +#endif + return (session.config.theme == Themes::DARK); +} + +/* this function is private, and should stay that way */ +void SetStyleSheet(enum Themes theme) { + switch (theme) { + case Themes::DARK: { + QFile f(":qdarkstyle/dark/darkstyle.qss"); + if (!f.exists()) + return; // fail + f.open(QFile::ReadOnly | QFile::Text); + QTextStream ts(&f); + qApp->setStyleSheet(ts.readAll()); + break; + } + default: qApp->setStyleSheet(""); break; + } +} + +void SetToDarkTheme() { + /* macOS >= 10.14 has its own global dark theme, + use it :) */ +#if MACOSX + if (osx::DarkThemeAvailable()) + osx::SetToDarkTheme(); + else +#endif + SetStyleSheet(Themes::DARK); +} + +void SetToLightTheme() { +#if MACOSX + if (osx::DarkThemeAvailable()) + osx::SetToLightTheme(); + else +#endif + SetStyleSheet(Themes::LIGHT); +} + +enum Themes GetCurrentOSTheme() { +#if MACOSX + if (osx::DarkThemeAvailable()) + return osx::IsInDarkTheme() ? Themes::DARK : Themes::LIGHT; +#elif defined(WIN32) + if (win32::DarkThemeAvailable()) + return win32::IsInDarkTheme() ? Themes::DARK : Themes::LIGHT; +#endif + /* Currently OS detection only supports Windows and macOS. + Please don't be shy if you're willing to port it to other OSes + (or desktop environments, or window managers) */ + return Themes::LIGHT; +} + +void SetTheme(enum Themes theme) { + switch (theme) { + case Themes::LIGHT: SetToLightTheme(); break; + case Themes::DARK: SetToDarkTheme(); break; + case Themes::OS: + if (GetCurrentOSTheme() == Themes::LIGHT) + SetToLightTheme(); + else + SetToDarkTheme(); + break; + } +} + +} // namespace DarkTheme diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/dark_theme.cpp --- a/src/gui/dark_theme.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,100 +0,0 @@ -#include "core/config.h" -#include "core/session.h" -#include -#include -#include -#ifdef MACOSX -# include "sys/osx/dark_theme.h" -#else -# include "sys/win32/dark_theme.h" -#endif - -namespace DarkTheme { - -bool IsInDarkMode() { - if (session.config.theme != Themes::OS) - return (session.config.theme == Themes::DARK); -#ifdef MACOSX - if (osx::DarkThemeAvailable()) { - if (osx::IsInDarkTheme()) { - return true; - } else { - return false; - } - } -#elif defined(WIN32) - if (win32::DarkThemeAvailable()) { - if (win32::IsInDarkTheme()) { - return true; - } else { - return false; - } - } -#endif - return (session.config.theme == Themes::DARK); -} - -/* this function is private, and should stay that way */ -void SetStyleSheet(enum Themes theme) { - switch (theme) { - case Themes::DARK: { - QFile f(":qdarkstyle/dark/darkstyle.qss"); - if (!f.exists()) - return; // fail - f.open(QFile::ReadOnly | QFile::Text); - QTextStream ts(&f); - qApp->setStyleSheet(ts.readAll()); - break; - } - default: qApp->setStyleSheet(""); break; - } -} - -void SetToDarkTheme() { - /* macOS >= 10.14 has its own global dark theme, - use it :) */ -#if MACOSX - if (osx::DarkThemeAvailable()) - osx::SetToDarkTheme(); - else -#endif - SetStyleSheet(Themes::DARK); -} - -void SetToLightTheme() { -#if MACOSX - if (osx::DarkThemeAvailable()) - osx::SetToLightTheme(); - else -#endif - SetStyleSheet(Themes::LIGHT); -} - -enum Themes GetCurrentOSTheme() { -#if MACOSX - if (osx::DarkThemeAvailable()) - return osx::IsInDarkTheme() ? Themes::DARK : Themes::LIGHT; -#elif defined(WIN32) - if (win32::DarkThemeAvailable()) - return win32::IsInDarkTheme() ? Themes::DARK : Themes::LIGHT; -#endif - /* Currently OS detection only supports Windows and macOS. - Please don't be shy if you're willing to port it to other OSes - (or desktop environments, or window managers) */ - return Themes::LIGHT; -} - -void SetTheme(enum Themes theme) { - switch (theme) { - case Themes::LIGHT: SetToLightTheme(); break; - case Themes::DARK: SetToDarkTheme(); break; - case Themes::OS: - if (GetCurrentOSTheme() == Themes::LIGHT) - SetToLightTheme(); - else - SetToDarkTheme(); - break; - } -} - -} // namespace DarkTheme diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/dialog/about.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/dialog/about.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,137 @@ +#include "gui/dialog/about.h" +#include "core/json.h" +#include "core/version.h" +#include "gui/widgets/text.h" +#include "pugixml.hpp" +#include +#include +#include +#include +#include +#include + +#define CONCAT_VERSION_NX(major, minor, patch) ("v" #major "." #minor "." #patch) + +#define CONCAT_VERSION(major, minor, patch) CONCAT_VERSION_NX(major, minor, patch) + +#define SET_TITLE_FONT(font, format, cursor) \ + { \ + QFont fnt; \ + fnt.setPixelSize(16); \ + format.setFont(fnt); \ + cursor.setCharFormat(format); \ + } + +#define SET_PARAGRAPH_FONT(font, format, cursor) \ + { \ + QFont fnt; \ + fnt.setPixelSize(12); \ + format.setFont(fnt); \ + cursor.setCharFormat(format); \ + } + +#define SET_FONT_BOLD(font, format, cursor) \ + { \ + font = cursor.charFormat().font(); \ + font.setBold(true); \ + format.setFont(font); \ + cursor.setCharFormat(format); \ + } + +#define UNSET_FONT_BOLD(font, format, cursor) \ + { \ + font = cursor.charFormat().font(); \ + font.setBold(false); \ + format.setFont(font); \ + cursor.setCharFormat(format); \ + } + +#define SET_FORMAT_HYPERLINK(format, cursor, link) \ + { \ + font = cursor.charFormat().font(); \ + font.setUnderline(true); \ + format.setFont(font); \ + format.setAnchor(true); \ + format.setAnchorHref(link); \ + cursor.setCharFormat(format); \ + } +#define UNSET_FORMAT_HYPERLINK(format, cursor) \ + { \ + font = cursor.charFormat().font(); \ + font.setUnderline(false); \ + format.setFont(font); \ + format.setAnchor(false); \ + format.setAnchorHref(""); \ + cursor.setCharFormat(format); \ + } + +AboutWindow::AboutWindow(QWidget* parent) : QDialog(parent) { + setWindowTitle(tr("About Minori")); + setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint); + QHBoxLayout* layout = new QHBoxLayout(this); + + QPalette pal = QPalette(); + pal.setColor(QPalette::Window, pal.color(QPalette::Base)); + setPalette(pal); + setAutoFillBackground(true); + + QFont font; + QTextCharFormat format; + QTextBrowser* paragraph = new QTextBrowser(this); + paragraph->setOpenExternalLinks(true); + paragraph->setFrameShape(QFrame::NoFrame); + paragraph->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + paragraph->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + QTextCursor cursor = paragraph->textCursor(); + SET_TITLE_FONT(font, format, cursor); + SET_FONT_BOLD(font, format, cursor); + cursor.insertText("Minori"); + UNSET_FONT_BOLD(font, format, cursor); + cursor.insertText(" " MINORI_VERSION); + SET_PARAGRAPH_FONT(font, format, cursor); + cursor.insertBlock(); + cursor.insertBlock(); + SET_FONT_BOLD(font, format, cursor); + cursor.insertText(tr("Author:")); + UNSET_FONT_BOLD(font, format, cursor); + cursor.insertBlock(); + cursor.insertText(tr("Paper")); + cursor.insertBlock(); + cursor.insertBlock(); + SET_FONT_BOLD(font, format, cursor); + cursor.insertText(tr("Third party components:")); + UNSET_FONT_BOLD(font, format, cursor); + cursor.insertBlock(); + SET_FORMAT_HYPERLINK(format, cursor, "https://github.com/nlohmann/json"); + cursor.insertText(tr("JSON for Modern C++ ") + CONCAT_VERSION(NLOHMANN_JSON_VERSION_MAJOR, + NLOHMANN_JSON_VERSION_MINOR, + NLOHMANN_JSON_VERSION_PATCH)); + UNSET_FORMAT_HYPERLINK(format, cursor); + cursor.insertText(", "); + { + curl_version_info_data* data = curl_version_info(CURLVERSION_NOW); + SET_FORMAT_HYPERLINK(format, cursor, "https://curl.se/"); + cursor.insertText(tr("libcurl v") + data->version); + UNSET_FORMAT_HYPERLINK(format, cursor); + cursor.insertText(", "); + } + SET_FORMAT_HYPERLINK(format, cursor, "https://p.yusukekamiyamane.com/"); + cursor.insertText(tr("Fugue Icons ") + CONCAT_VERSION(3, 5, 6)); + UNSET_FORMAT_HYPERLINK(format, cursor); + cursor.insertText(", "); + SET_FORMAT_HYPERLINK(format, cursor, "https://pugixml.org/"); + cursor.insertText(tr("pugixml v") + QString::number(PUGIXML_VERSION / 1000) + "." + + QString::number(PUGIXML_VERSION / 10 % 100) + "." + QString::number(PUGIXML_VERSION % 10)); + UNSET_FORMAT_HYPERLINK(format, cursor); + cursor.insertText(", "); + SET_FORMAT_HYPERLINK(format, cursor, "https://github.com/erengy/anitomy"); + cursor.insertText(tr("Anitomy")); + UNSET_FORMAT_HYPERLINK(format, cursor); + cursor.insertBlock(); + cursor.insertBlock(); + SET_FONT_BOLD(font, format, cursor); + cursor.insertText(tr("Links:")); + UNSET_FONT_BOLD(font, format, cursor); + cursor.insertBlock(); + layout->addWidget(paragraph); +} diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/dialog/about.cpp --- a/src/gui/dialog/about.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,137 +0,0 @@ -#include "gui/dialog/about.h" -#include "core/json.h" -#include "core/version.h" -#include "gui/widgets/text.h" -#include "pugixml.hpp" -#include -#include -#include -#include -#include -#include - -#define CONCAT_VERSION_NX(major, minor, patch) ("v" #major "." #minor "." #patch) - -#define CONCAT_VERSION(major, minor, patch) CONCAT_VERSION_NX(major, minor, patch) - -#define SET_TITLE_FONT(font, format, cursor) \ - { \ - QFont fnt; \ - fnt.setPixelSize(16); \ - format.setFont(fnt); \ - cursor.setCharFormat(format); \ - } - -#define SET_PARAGRAPH_FONT(font, format, cursor) \ - { \ - QFont fnt; \ - fnt.setPixelSize(12); \ - format.setFont(fnt); \ - cursor.setCharFormat(format); \ - } - -#define SET_FONT_BOLD(font, format, cursor) \ - { \ - font = cursor.charFormat().font(); \ - font.setBold(true); \ - format.setFont(font); \ - cursor.setCharFormat(format); \ - } - -#define UNSET_FONT_BOLD(font, format, cursor) \ - { \ - font = cursor.charFormat().font(); \ - font.setBold(false); \ - format.setFont(font); \ - cursor.setCharFormat(format); \ - } - -#define SET_FORMAT_HYPERLINK(format, cursor, link) \ - { \ - font = cursor.charFormat().font(); \ - font.setUnderline(true); \ - format.setFont(font); \ - format.setAnchor(true); \ - format.setAnchorHref(link); \ - cursor.setCharFormat(format); \ - } -#define UNSET_FORMAT_HYPERLINK(format, cursor) \ - { \ - font = cursor.charFormat().font(); \ - font.setUnderline(false); \ - format.setFont(font); \ - format.setAnchor(false); \ - format.setAnchorHref(""); \ - cursor.setCharFormat(format); \ - } - -AboutWindow::AboutWindow(QWidget* parent) : QDialog(parent) { - setWindowTitle(tr("About Minori")); - setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint); - QHBoxLayout* layout = new QHBoxLayout(this); - - QPalette pal = QPalette(); - pal.setColor(QPalette::Window, pal.color(QPalette::Base)); - setPalette(pal); - setAutoFillBackground(true); - - QFont font; - QTextCharFormat format; - QTextBrowser* paragraph = new QTextBrowser(this); - paragraph->setOpenExternalLinks(true); - paragraph->setFrameShape(QFrame::NoFrame); - paragraph->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - paragraph->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - QTextCursor cursor = paragraph->textCursor(); - SET_TITLE_FONT(font, format, cursor); - SET_FONT_BOLD(font, format, cursor); - cursor.insertText("Minori"); - UNSET_FONT_BOLD(font, format, cursor); - cursor.insertText(" " MINORI_VERSION); - SET_PARAGRAPH_FONT(font, format, cursor); - cursor.insertBlock(); - cursor.insertBlock(); - SET_FONT_BOLD(font, format, cursor); - cursor.insertText(tr("Author:")); - UNSET_FONT_BOLD(font, format, cursor); - cursor.insertBlock(); - cursor.insertText(tr("Paper")); - cursor.insertBlock(); - cursor.insertBlock(); - SET_FONT_BOLD(font, format, cursor); - cursor.insertText(tr("Third party components:")); - UNSET_FONT_BOLD(font, format, cursor); - cursor.insertBlock(); - SET_FORMAT_HYPERLINK(format, cursor, "https://github.com/nlohmann/json"); - cursor.insertText(tr("JSON for Modern C++ ") + CONCAT_VERSION(NLOHMANN_JSON_VERSION_MAJOR, - NLOHMANN_JSON_VERSION_MINOR, - NLOHMANN_JSON_VERSION_PATCH)); - UNSET_FORMAT_HYPERLINK(format, cursor); - cursor.insertText(", "); - { - curl_version_info_data* data = curl_version_info(CURLVERSION_NOW); - SET_FORMAT_HYPERLINK(format, cursor, "https://curl.se/"); - cursor.insertText(tr("libcurl v") + data->version); - UNSET_FORMAT_HYPERLINK(format, cursor); - cursor.insertText(", "); - } - SET_FORMAT_HYPERLINK(format, cursor, "https://p.yusukekamiyamane.com/"); - cursor.insertText(tr("Fugue Icons ") + CONCAT_VERSION(3, 5, 6)); - UNSET_FORMAT_HYPERLINK(format, cursor); - cursor.insertText(", "); - SET_FORMAT_HYPERLINK(format, cursor, "https://pugixml.org/"); - cursor.insertText(tr("pugixml v") + QString::number(PUGIXML_VERSION / 1000) + "." + - QString::number(PUGIXML_VERSION / 10 % 100) + "." + QString::number(PUGIXML_VERSION % 10)); - UNSET_FORMAT_HYPERLINK(format, cursor); - cursor.insertText(", "); - SET_FORMAT_HYPERLINK(format, cursor, "https://github.com/erengy/anitomy"); - cursor.insertText(tr("Anitomy")); - UNSET_FORMAT_HYPERLINK(format, cursor); - cursor.insertBlock(); - cursor.insertBlock(); - SET_FONT_BOLD(font, format, cursor); - cursor.insertText(tr("Links:")); - UNSET_FONT_BOLD(font, format, cursor); - cursor.insertBlock(); - layout->addWidget(paragraph); -} diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/dialog/information.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/dialog/information.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,281 @@ +#include "gui/dialog/information.h" +#include "core/anime.h" +#include "core/anime_db.h" +#include "core/array.h" +#include "core/strings.h" +#include "gui/pages/anime_list.h" +#include "gui/translate/anime.h" +#include "gui/widgets/anime_info.h" +#include "gui/widgets/optional_date.h" +#include "gui/widgets/poster.h" +#include "gui/widgets/text.h" +#include "gui/window.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* TODO: Taiga disables rendering of the tab widget entirely when the anime is not part of a list, + which sucks. Think of a better way to implement this later. */ +void InformationDialog::SaveData() { + Anime::Anime& anime = Anime::db.items[id]; + anime.SetUserProgress(progress); + anime.SetUserScore(score); + anime.SetUserIsRewatching(rewatching); + anime.SetUserStatus(status); + anime.SetUserNotes(notes); + anime.SetUserDateStarted(started); + anime.SetUserDateCompleted(completed); +} + +InformationDialog::InformationDialog(const Anime::Anime& anime, std::function accept, QWidget* parent) + : QDialog(parent) { + setFixedSize(842, 613); + setWindowTitle(tr("Anime Information")); + setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint); + + { + QPalette pal(palette()); + pal.setColor(QPalette::Window, pal.color(QPalette::Base)); + setPalette(pal); + } + + QWidget* widget = new QWidget(this); + + /* "sidebar", includes... just the anime image :) */ + QWidget* sidebar = new QWidget(widget); + QVBoxLayout* sidebar_layout = new QVBoxLayout(sidebar); + Poster* poster = new Poster(anime.GetId(), sidebar); + sidebar_layout->addWidget(poster); + sidebar_layout->setContentsMargins(0, 0, 0, 0); + sidebar_layout->addStretch(); + + /* main widget */ + QWidget* main_widget = new QWidget(widget); + + main_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + id = anime.GetId(); + /* anime title header text */ + TextWidgets::Title* anime_title = + new TextWidgets::Title(Strings::ToQString(anime.GetUserPreferredTitle()), main_widget); + + /* tabbed widget */ + QTabWidget* tabbed_widget = new QTabWidget(main_widget); + tabbed_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + + /* main info tab */ + AnimeInfoWidget* main_information_widget = new AnimeInfoWidget(anime, tabbed_widget); + + { + QPalette pal(main_information_widget->palette()); + pal.setColor(QPalette::Base, pal.color(QPalette::Window)); + main_information_widget->setPalette(pal); + } + + QWidget* settings_widget = new QWidget(tabbed_widget); + settings_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + + QVBoxLayout* settings_layout = new QVBoxLayout(settings_widget); + settings_layout->addWidget(new TextWidgets::Header(tr("Anime list"), settings_widget)); + + QWidget* sg_anime_list_content = new QWidget(settings_widget); + + QVBoxLayout* al_layout = new QVBoxLayout(sg_anime_list_content); + al_layout->setSpacing(5); + al_layout->setContentsMargins(12, 0, 0, 0); + +#define LAYOUT_HORIZ_SPACING 25 +#define LAYOUT_VERT_SPACING 5 +#define LAYOUT_ITEM_WIDTH 175 +/* Creates a subsection that takes up whatever space is necessary */ +#define CREATE_FULL_WIDTH_SUBSECTION(x) \ + { \ + QWidget* subsection = new QWidget(section); \ + subsection->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); \ + QVBoxLayout* subsection_layout = new QVBoxLayout(subsection); \ + subsection_layout->setSpacing(LAYOUT_VERT_SPACING); \ + subsection_layout->setContentsMargins(0, 0, 0, 0); \ + x; \ + layout->addWidget(subsection, 0, Qt::AlignBottom); \ + } + +/* Creates a section in the parent `a` */ +#define CREATE_FULL_WIDTH_SECTION(a, x) \ + { \ + QWidget* section = new QWidget(a); \ + QHBoxLayout* layout = new QHBoxLayout(section); \ + layout->setSpacing(LAYOUT_HORIZ_SPACING); \ + layout->setContentsMargins(0, 0, 0, 0); \ + x; \ + a->layout()->addWidget(section); \ + } + +/* Creates a subsection with a width of 175 */ +#define CREATE_SUBSECTION(x) CREATE_FULL_WIDTH_SUBSECTION(x subsection->setFixedWidth(LAYOUT_ITEM_WIDTH);) +/* Creates a section in the parent `a` */ +#define CREATE_SECTION(a, x) CREATE_FULL_WIDTH_SECTION(a, x layout->addStretch();) + + CREATE_SECTION(sg_anime_list_content, { + /* Episodes watched section */ + CREATE_SUBSECTION({ + subsection_layout->addWidget(new QLabel(tr("Episodes watched:"), subsection)); + + QSpinBox* spin_box = new QSpinBox(subsection); + connect(spin_box, QOverload::of(&QSpinBox::valueChanged), this, [this](int i) { progress = i; }); + spin_box->setRange(0, anime.GetEpisodes()); + spin_box->setSingleStep(1); + spin_box->setValue(progress = anime.GetUserProgress()); + subsection_layout->addWidget(spin_box); + }); + CREATE_SUBSECTION({ + subsection_layout->addWidget(new QLabel(tr(" "), subsection)); + + QCheckBox* checkbox = new QCheckBox(tr("Rewatching")); + connect(checkbox, QOverload::of(&QCheckBox::stateChanged), this, + [this](int state) { rewatching = (state == Qt::Checked); }); + checkbox->setCheckState(anime.GetUserIsRewatching() ? Qt::Checked : Qt::Unchecked); + subsection_layout->addWidget(checkbox); + }); + }); + CREATE_SECTION(sg_anime_list_content, { + /* Status & score section */ + CREATE_SUBSECTION({ + subsection_layout->addWidget(new QLabel(tr("Status:"), subsection)); + + QStringList string_list; + for (unsigned int i = 0; i < ARRAYSIZE(Anime::ListStatuses); i++) + string_list.append(Strings::ToQString(Translate::ToString(Anime::ListStatuses[i]))); + + QComboBox* combo_box = new QComboBox(subsection); + combo_box->addItems(string_list); + connect(combo_box, QOverload::of(&QComboBox::currentIndexChanged), this, + [this](int i) { status = Anime::ListStatuses[i]; }); + combo_box->setCurrentIndex(static_cast(status = anime.GetUserStatus()) - 1); + subsection_layout->addWidget(combo_box); + }); + CREATE_SUBSECTION({ + subsection_layout->addWidget(new QLabel(tr("Score:"), subsection)); + + QSpinBox* spin_box = new QSpinBox(subsection); + connect(spin_box, QOverload::of(&QSpinBox::valueChanged), this, [this](int i) { score = i; }); + spin_box->setRange(0, 100); + spin_box->setSingleStep(5); + spin_box->setValue(score = anime.GetUserScore()); + subsection_layout->addWidget(spin_box); + }); + }); + CREATE_FULL_WIDTH_SECTION(sg_anime_list_content, { + /* Notes section */ + CREATE_FULL_WIDTH_SUBSECTION({ + subsection_layout->addWidget(new QLabel(tr("Notes:"), subsection)); + + QLineEdit* line_edit = new QLineEdit(subsection); + connect(line_edit, &QLineEdit::textChanged, this, [this](const QString& text) { + /* this sucks but I don't really want to implement anything smarter :) */ + notes = Strings::ToUtf8String(text); + }); + line_edit->setText(Strings::ToQString(notes = anime.GetUserNotes())); + line_edit->setPlaceholderText(tr("Enter your notes about this anime")); + subsection_layout->addWidget(line_edit); + }); + }); + CREATE_SECTION(sg_anime_list_content, { + /* Dates section */ + CREATE_SUBSECTION({ + subsection_layout->addWidget(new QLabel(tr("Date started:"), subsection)); + + OptionalDate* date = new OptionalDate(true, subsection); + connect(date, &OptionalDate::DataChanged, this, + [this](bool enabled, Date date) { started = (enabled) ? date : Date(); }); + started = anime.GetUserDateStarted(); + if (!started.IsValid()) { + date->SetEnabled(false); + started = anime.GetAirDate(); + } + date->SetDate(started); + subsection_layout->addWidget(date); + }); + CREATE_SUBSECTION({ + subsection_layout->addWidget(new QLabel(tr("Date completed:"), subsection)); + + OptionalDate* date = new OptionalDate(true, subsection); + connect(date, &OptionalDate::DataChanged, this, + [this](bool enabled, Date date) { completed = (enabled) ? date : Date(); }); + completed = anime.GetUserDateCompleted(); + if (!completed.IsValid()) { + date->SetEnabled(false); + completed = anime.GetAirDate(); + } + date->SetDate(completed); + subsection_layout->addWidget(date); + }); + }); + + settings_layout->addWidget(sg_anime_list_content); + + settings_layout->addWidget(new TextWidgets::Header(tr("Local settings"), settings_widget)); + + QWidget* sg_local_content = new QWidget(settings_widget); + QVBoxLayout* sg_local_layout = new QVBoxLayout(sg_local_content); + sg_local_layout->setSpacing(5); + sg_local_layout->setContentsMargins(12, 0, 0, 0); + + CREATE_FULL_WIDTH_SECTION(sg_local_content, { + /* Alternative titles */ + CREATE_FULL_WIDTH_SUBSECTION({ + subsection_layout->addWidget(new QLabel(tr("Alternative titles:"), subsection)); + + QLineEdit* line_edit = new QLineEdit("", subsection); + line_edit->setPlaceholderText( + tr("Enter alternative titles here, separated by a semicolon (i.e. Title 1; Title 2)")); + subsection_layout->addWidget(line_edit); + + QCheckBox* checkbox = new QCheckBox(tr("Use the first alternative title to search for torrents")); + subsection_layout->addWidget(checkbox); + }); + }); +#undef CREATE_SECTION +#undef CREATE_SUBSECTION +#undef CREATE_FULL_WIDTH_SECTION +#undef CREATE_FULL_WIDTH_SUBSECTION + + settings_layout->addWidget(sg_local_content); + settings_layout->addStretch(); + + tabbed_widget->addTab(main_information_widget, tr("Main information")); + tabbed_widget->addTab(settings_widget, tr("My list and settings")); + + QVBoxLayout* main_layout = new QVBoxLayout(main_widget); + main_layout->addWidget(anime_title); + main_layout->addWidget(tabbed_widget); + main_layout->setContentsMargins(0, 0, 0, 0); + + QHBoxLayout* layout = new QHBoxLayout(widget); + layout->addWidget(sidebar); + layout->addWidget(main_widget); + layout->setSpacing(12); + + QDialogButtonBox* button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + connect(button_box, &QDialogButtonBox::accepted, this, [this, accept] { + SaveData(); + accept(); + QDialog::accept(); + }); + connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + + QVBoxLayout* buttons_layout = new QVBoxLayout(this); + buttons_layout->addWidget(widget); + buttons_layout->addWidget(button_box, 0, Qt::AlignBottom); +} + +#include "gui/dialog/moc_information.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/dialog/information.cpp --- a/src/gui/dialog/information.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,281 +0,0 @@ -#include "gui/dialog/information.h" -#include "core/anime.h" -#include "core/anime_db.h" -#include "core/array.h" -#include "core/strings.h" -#include "gui/pages/anime_list.h" -#include "gui/translate/anime.h" -#include "gui/widgets/anime_info.h" -#include "gui/widgets/optional_date.h" -#include "gui/widgets/poster.h" -#include "gui/widgets/text.h" -#include "gui/window.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* TODO: Taiga disables rendering of the tab widget entirely when the anime is not part of a list, - which sucks. Think of a better way to implement this later. */ -void InformationDialog::SaveData() { - Anime::Anime& anime = Anime::db.items[id]; - anime.SetUserProgress(progress); - anime.SetUserScore(score); - anime.SetUserIsRewatching(rewatching); - anime.SetUserStatus(status); - anime.SetUserNotes(notes); - anime.SetUserDateStarted(started); - anime.SetUserDateCompleted(completed); -} - -InformationDialog::InformationDialog(const Anime::Anime& anime, std::function accept, QWidget* parent) - : QDialog(parent) { - setFixedSize(842, 613); - setWindowTitle(tr("Anime Information")); - setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint); - - { - QPalette pal(palette()); - pal.setColor(QPalette::Window, pal.color(QPalette::Base)); - setPalette(pal); - } - - QWidget* widget = new QWidget(this); - - /* "sidebar", includes... just the anime image :) */ - QWidget* sidebar = new QWidget(widget); - QVBoxLayout* sidebar_layout = new QVBoxLayout(sidebar); - Poster* poster = new Poster(anime.GetId(), sidebar); - sidebar_layout->addWidget(poster); - sidebar_layout->setContentsMargins(0, 0, 0, 0); - sidebar_layout->addStretch(); - - /* main widget */ - QWidget* main_widget = new QWidget(widget); - - main_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - - id = anime.GetId(); - /* anime title header text */ - TextWidgets::Title* anime_title = - new TextWidgets::Title(Strings::ToQString(anime.GetUserPreferredTitle()), main_widget); - - /* tabbed widget */ - QTabWidget* tabbed_widget = new QTabWidget(main_widget); - tabbed_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - - /* main info tab */ - AnimeInfoWidget* main_information_widget = new AnimeInfoWidget(anime, tabbed_widget); - - { - QPalette pal(main_information_widget->palette()); - pal.setColor(QPalette::Base, pal.color(QPalette::Window)); - main_information_widget->setPalette(pal); - } - - QWidget* settings_widget = new QWidget(tabbed_widget); - settings_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - - QVBoxLayout* settings_layout = new QVBoxLayout(settings_widget); - settings_layout->addWidget(new TextWidgets::Header(tr("Anime list"), settings_widget)); - - QWidget* sg_anime_list_content = new QWidget(settings_widget); - - QVBoxLayout* al_layout = new QVBoxLayout(sg_anime_list_content); - al_layout->setSpacing(5); - al_layout->setContentsMargins(12, 0, 0, 0); - -#define LAYOUT_HORIZ_SPACING 25 -#define LAYOUT_VERT_SPACING 5 -#define LAYOUT_ITEM_WIDTH 175 -/* Creates a subsection that takes up whatever space is necessary */ -#define CREATE_FULL_WIDTH_SUBSECTION(x) \ - { \ - QWidget* subsection = new QWidget(section); \ - subsection->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); \ - QVBoxLayout* subsection_layout = new QVBoxLayout(subsection); \ - subsection_layout->setSpacing(LAYOUT_VERT_SPACING); \ - subsection_layout->setContentsMargins(0, 0, 0, 0); \ - x; \ - layout->addWidget(subsection, 0, Qt::AlignBottom); \ - } - -/* Creates a section in the parent `a` */ -#define CREATE_FULL_WIDTH_SECTION(a, x) \ - { \ - QWidget* section = new QWidget(a); \ - QHBoxLayout* layout = new QHBoxLayout(section); \ - layout->setSpacing(LAYOUT_HORIZ_SPACING); \ - layout->setContentsMargins(0, 0, 0, 0); \ - x; \ - a->layout()->addWidget(section); \ - } - -/* Creates a subsection with a width of 175 */ -#define CREATE_SUBSECTION(x) CREATE_FULL_WIDTH_SUBSECTION(x subsection->setFixedWidth(LAYOUT_ITEM_WIDTH);) -/* Creates a section in the parent `a` */ -#define CREATE_SECTION(a, x) CREATE_FULL_WIDTH_SECTION(a, x layout->addStretch();) - - CREATE_SECTION(sg_anime_list_content, { - /* Episodes watched section */ - CREATE_SUBSECTION({ - subsection_layout->addWidget(new QLabel(tr("Episodes watched:"), subsection)); - - QSpinBox* spin_box = new QSpinBox(subsection); - connect(spin_box, QOverload::of(&QSpinBox::valueChanged), this, [this](int i) { progress = i; }); - spin_box->setRange(0, anime.GetEpisodes()); - spin_box->setSingleStep(1); - spin_box->setValue(progress = anime.GetUserProgress()); - subsection_layout->addWidget(spin_box); - }); - CREATE_SUBSECTION({ - subsection_layout->addWidget(new QLabel(tr(" "), subsection)); - - QCheckBox* checkbox = new QCheckBox(tr("Rewatching")); - connect(checkbox, QOverload::of(&QCheckBox::stateChanged), this, - [this](int state) { rewatching = (state == Qt::Checked); }); - checkbox->setCheckState(anime.GetUserIsRewatching() ? Qt::Checked : Qt::Unchecked); - subsection_layout->addWidget(checkbox); - }); - }); - CREATE_SECTION(sg_anime_list_content, { - /* Status & score section */ - CREATE_SUBSECTION({ - subsection_layout->addWidget(new QLabel(tr("Status:"), subsection)); - - QStringList string_list; - for (unsigned int i = 0; i < ARRAYSIZE(Anime::ListStatuses); i++) - string_list.append(Strings::ToQString(Translate::ToString(Anime::ListStatuses[i]))); - - QComboBox* combo_box = new QComboBox(subsection); - combo_box->addItems(string_list); - connect(combo_box, QOverload::of(&QComboBox::currentIndexChanged), this, - [this](int i) { status = Anime::ListStatuses[i]; }); - combo_box->setCurrentIndex(static_cast(status = anime.GetUserStatus()) - 1); - subsection_layout->addWidget(combo_box); - }); - CREATE_SUBSECTION({ - subsection_layout->addWidget(new QLabel(tr("Score:"), subsection)); - - QSpinBox* spin_box = new QSpinBox(subsection); - connect(spin_box, QOverload::of(&QSpinBox::valueChanged), this, [this](int i) { score = i; }); - spin_box->setRange(0, 100); - spin_box->setSingleStep(5); - spin_box->setValue(score = anime.GetUserScore()); - subsection_layout->addWidget(spin_box); - }); - }); - CREATE_FULL_WIDTH_SECTION(sg_anime_list_content, { - /* Notes section */ - CREATE_FULL_WIDTH_SUBSECTION({ - subsection_layout->addWidget(new QLabel(tr("Notes:"), subsection)); - - QLineEdit* line_edit = new QLineEdit(subsection); - connect(line_edit, &QLineEdit::textChanged, this, [this](const QString& text) { - /* this sucks but I don't really want to implement anything smarter :) */ - notes = Strings::ToUtf8String(text); - }); - line_edit->setText(Strings::ToQString(notes = anime.GetUserNotes())); - line_edit->setPlaceholderText(tr("Enter your notes about this anime")); - subsection_layout->addWidget(line_edit); - }); - }); - CREATE_SECTION(sg_anime_list_content, { - /* Dates section */ - CREATE_SUBSECTION({ - subsection_layout->addWidget(new QLabel(tr("Date started:"), subsection)); - - OptionalDate* date = new OptionalDate(true, subsection); - connect(date, &OptionalDate::DataChanged, this, - [this](bool enabled, Date date) { started = (enabled) ? date : Date(); }); - started = anime.GetUserDateStarted(); - if (!started.IsValid()) { - date->SetEnabled(false); - started = anime.GetAirDate(); - } - date->SetDate(started); - subsection_layout->addWidget(date); - }); - CREATE_SUBSECTION({ - subsection_layout->addWidget(new QLabel(tr("Date completed:"), subsection)); - - OptionalDate* date = new OptionalDate(true, subsection); - connect(date, &OptionalDate::DataChanged, this, - [this](bool enabled, Date date) { completed = (enabled) ? date : Date(); }); - completed = anime.GetUserDateCompleted(); - if (!completed.IsValid()) { - date->SetEnabled(false); - completed = anime.GetAirDate(); - } - date->SetDate(completed); - subsection_layout->addWidget(date); - }); - }); - - settings_layout->addWidget(sg_anime_list_content); - - settings_layout->addWidget(new TextWidgets::Header(tr("Local settings"), settings_widget)); - - QWidget* sg_local_content = new QWidget(settings_widget); - QVBoxLayout* sg_local_layout = new QVBoxLayout(sg_local_content); - sg_local_layout->setSpacing(5); - sg_local_layout->setContentsMargins(12, 0, 0, 0); - - CREATE_FULL_WIDTH_SECTION(sg_local_content, { - /* Alternative titles */ - CREATE_FULL_WIDTH_SUBSECTION({ - subsection_layout->addWidget(new QLabel(tr("Alternative titles:"), subsection)); - - QLineEdit* line_edit = new QLineEdit("", subsection); - line_edit->setPlaceholderText( - tr("Enter alternative titles here, separated by a semicolon (i.e. Title 1; Title 2)")); - subsection_layout->addWidget(line_edit); - - QCheckBox* checkbox = new QCheckBox(tr("Use the first alternative title to search for torrents")); - subsection_layout->addWidget(checkbox); - }); - }); -#undef CREATE_SECTION -#undef CREATE_SUBSECTION -#undef CREATE_FULL_WIDTH_SECTION -#undef CREATE_FULL_WIDTH_SUBSECTION - - settings_layout->addWidget(sg_local_content); - settings_layout->addStretch(); - - tabbed_widget->addTab(main_information_widget, tr("Main information")); - tabbed_widget->addTab(settings_widget, tr("My list and settings")); - - QVBoxLayout* main_layout = new QVBoxLayout(main_widget); - main_layout->addWidget(anime_title); - main_layout->addWidget(tabbed_widget); - main_layout->setContentsMargins(0, 0, 0, 0); - - QHBoxLayout* layout = new QHBoxLayout(widget); - layout->addWidget(sidebar); - layout->addWidget(main_widget); - layout->setSpacing(12); - - QDialogButtonBox* button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - connect(button_box, &QDialogButtonBox::accepted, this, [this, accept] { - SaveData(); - accept(); - QDialog::accept(); - }); - connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); - - QVBoxLayout* buttons_layout = new QVBoxLayout(this); - buttons_layout->addWidget(widget); - buttons_layout->addWidget(button_box, 0, Qt::AlignBottom); -} - -#include "gui/dialog/moc_information.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/dialog/settings.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/dialog/settings.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,105 @@ +#include "gui/dialog/settings.h" +#include "gui/widgets/sidebar.h" +#include "gui/widgets/text.h" +#include +#include +#include +#include +#include +#include + +SettingsPage::SettingsPage(QWidget* parent, QString title) : QWidget(parent) { + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + page_title = new QLabel(title, this); + page_title->setWordWrap(false); + page_title->setFrameShape(QFrame::Panel); + page_title->setFrameShadow(QFrame::Sunken); + + QFont font(page_title->font()); + font.setPixelSize(12); + font.setWeight(QFont::Bold); + page_title->setFont(font); + + QPalette pal = page_title->palette(); + pal.setColor(QPalette::Window, QColor(0xAB, 0xAB, 0xAB)); + pal.setColor(QPalette::WindowText, Qt::white); + page_title->setPalette(pal); + page_title->setAutoFillBackground(true); + + page_title->setFixedHeight(23); + page_title->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); + page_title->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + + tab_widget = new QTabWidget(this); + tab_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(page_title); + layout->addWidget(tab_widget); +} + +void SettingsPage::SetTitle(QString title) { + page_title->setText(title); +} + +void SettingsPage::AddTab(QWidget* tab, QString title) { + tab_widget->addTab(tab, title); +} + +void SettingsPage::SaveInfo() { + // no-op... child classes will implement this +} + +void SettingsDialog::OnOK() { + for (int i = 0; i < stacked->count(); i++) { + reinterpret_cast(stacked->widget(i))->SaveInfo(); + } + QDialog::accept(); +} + +SettingsDialog::SettingsDialog(QWidget* parent) : QDialog(parent) { + setFixedSize(755, 566); + setWindowTitle(tr("Settings")); + setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint); + QWidget* widget = new QWidget(this); + widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + sidebar = new SideBar(widget); + sidebar->setCurrentItem(sidebar->AddItem(tr("Services"), SideBar::CreateIcon(":/icons/24x24/globe.png"))); + // sidebar->AddItem(tr("Library"), SideBar::CreateIcon(":/icons/24x24/inbox-film.png")); + sidebar->AddItem(tr("Application"), SideBar::CreateIcon(":/icons/24x24/application-sidebar-list.png")); + // sidebar->AddItem(tr("Recognition"), SideBar::CreateIcon(":/icons/24x24/question.png")); + // sidebar->AddItem(tr("Sharing"), SideBar::CreateIcon(":/icons/24x24/megaphone.png")); + // sidebar->AddItem(tr("Torrents"), SideBar::CreateIcon(":/icons/24x24/feed.png")); + // sidebar->AddItem(tr("Advanced"), SideBar::CreateIcon(":/icons/24x24/gear.png")); + sidebar->setIconSize(QSize(24, 24)); + sidebar->setFrameShape(QFrame::Box); + + QPalette pal(sidebar->palette()); + sidebar->SetBackgroundColor(pal.color(QPalette::Base)); + + sidebar->setFixedWidth(158); + sidebar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + + stacked = new QStackedWidget(this); + stacked->addWidget(new SettingsPageServices(stacked)); + stacked->addWidget(new SettingsPageApplication(stacked)); + stacked->setCurrentIndex(0); + + connect(sidebar, &QListWidget::currentRowChanged, stacked, &QStackedWidget::setCurrentIndex); + + QHBoxLayout* layout = new QHBoxLayout(widget); + layout->addWidget(sidebar); + layout->addWidget(stacked); + layout->setContentsMargins(0, 0, 0, 0); + + QDialogButtonBox* button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + connect(button_box, &QDialogButtonBox::accepted, this, &SettingsDialog::OnOK); + connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + + QVBoxLayout* buttons_layout = new QVBoxLayout(this); + buttons_layout->addWidget(widget); + buttons_layout->addWidget(button_box); +} + +#include "gui/dialog/moc_settings.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/dialog/settings.cpp --- a/src/gui/dialog/settings.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +0,0 @@ -#include "gui/dialog/settings.h" -#include "gui/widgets/sidebar.h" -#include "gui/widgets/text.h" -#include -#include -#include -#include -#include -#include - -SettingsPage::SettingsPage(QWidget* parent, QString title) : QWidget(parent) { - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - page_title = new QLabel(title, this); - page_title->setWordWrap(false); - page_title->setFrameShape(QFrame::Panel); - page_title->setFrameShadow(QFrame::Sunken); - - QFont font(page_title->font()); - font.setPixelSize(12); - font.setWeight(QFont::Bold); - page_title->setFont(font); - - QPalette pal = page_title->palette(); - pal.setColor(QPalette::Window, QColor(0xAB, 0xAB, 0xAB)); - pal.setColor(QPalette::WindowText, Qt::white); - page_title->setPalette(pal); - page_title->setAutoFillBackground(true); - - page_title->setFixedHeight(23); - page_title->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); - page_title->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - - tab_widget = new QTabWidget(this); - tab_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - - QVBoxLayout* layout = new QVBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - layout->addWidget(page_title); - layout->addWidget(tab_widget); -} - -void SettingsPage::SetTitle(QString title) { - page_title->setText(title); -} - -void SettingsPage::AddTab(QWidget* tab, QString title) { - tab_widget->addTab(tab, title); -} - -void SettingsPage::SaveInfo() { - // no-op... child classes will implement this -} - -void SettingsDialog::OnOK() { - for (int i = 0; i < stacked->count(); i++) { - reinterpret_cast(stacked->widget(i))->SaveInfo(); - } - QDialog::accept(); -} - -SettingsDialog::SettingsDialog(QWidget* parent) : QDialog(parent) { - setFixedSize(755, 566); - setWindowTitle(tr("Settings")); - setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint); - QWidget* widget = new QWidget(this); - widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - sidebar = new SideBar(widget); - sidebar->setCurrentItem(sidebar->AddItem(tr("Services"), SideBar::CreateIcon(":/icons/24x24/globe.png"))); - // sidebar->AddItem(tr("Library"), SideBar::CreateIcon(":/icons/24x24/inbox-film.png")); - sidebar->AddItem(tr("Application"), SideBar::CreateIcon(":/icons/24x24/application-sidebar-list.png")); - // sidebar->AddItem(tr("Recognition"), SideBar::CreateIcon(":/icons/24x24/question.png")); - // sidebar->AddItem(tr("Sharing"), SideBar::CreateIcon(":/icons/24x24/megaphone.png")); - // sidebar->AddItem(tr("Torrents"), SideBar::CreateIcon(":/icons/24x24/feed.png")); - // sidebar->AddItem(tr("Advanced"), SideBar::CreateIcon(":/icons/24x24/gear.png")); - sidebar->setIconSize(QSize(24, 24)); - sidebar->setFrameShape(QFrame::Box); - - QPalette pal(sidebar->palette()); - sidebar->SetBackgroundColor(pal.color(QPalette::Base)); - - sidebar->setFixedWidth(158); - sidebar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); - - stacked = new QStackedWidget(this); - stacked->addWidget(new SettingsPageServices(stacked)); - stacked->addWidget(new SettingsPageApplication(stacked)); - stacked->setCurrentIndex(0); - - connect(sidebar, &QListWidget::currentRowChanged, stacked, &QStackedWidget::setCurrentIndex); - - QHBoxLayout* layout = new QHBoxLayout(widget); - layout->addWidget(sidebar); - layout->addWidget(stacked); - layout->setContentsMargins(0, 0, 0, 0); - - QDialogButtonBox* button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); - connect(button_box, &QDialogButtonBox::accepted, this, &SettingsDialog::OnOK); - connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); - - QVBoxLayout* buttons_layout = new QVBoxLayout(this); - buttons_layout->addWidget(widget); - buttons_layout->addWidget(button_box); -} - -#include "gui/dialog/moc_settings.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/dialog/settings/application.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/dialog/settings/application.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,123 @@ +#include "core/session.h" +#include "gui/dialog/settings.h" +#include +#include +#include +#include +#include +#include +#include +#include + +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_widget); + double_click_layout->addWidget(dc_combo_box_label); + double_click_layout->addWidget(dc_combo_box); + double_click_layout->setContentsMargins(0, 0, 0, 0); + + /* 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_widget); + middle_click_layout->addWidget(mc_combo_box_label); + middle_click_layout->addWidget(mc_combo_box); + middle_click_layout->setContentsMargins(0, 0, 0, 0); + + /* Actions */ + QHBoxLayout* actions_layout = new QHBoxLayout(actions_group_box); + actions_layout->addWidget(double_click_widget); + actions_layout->addWidget(middle_click_widget); + + 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::of(&QComboBox::currentIndexChanged), this, + [this](int index) { language = static_cast(index); }); + lang_combo_box->setCurrentIndex(static_cast(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); + hl_above_anime_box->setEnabled(state); + }); + connect(hl_above_anime_box, &QCheckBox::stateChanged, this, + [this](int state) { highlight_anime_if_available = !(state == Qt::Unchecked); }); + 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->setContentsMargins(10, 0, 0, 0); + + /* Appearance */ + QVBoxLayout* appearance_layout = new QVBoxLayout(appearance_group_box); + 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); + + 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); }); + 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); }); + progress_display_available_episodes->setCheckState(display_available_episodes ? Qt::Checked : Qt::Unchecked); + + QVBoxLayout* progress_layout = new QVBoxLayout(progress_group_box); + progress_layout->addWidget(progress_display_aired_episodes); + progress_layout->addWidget(progress_display_available_episodes); + + QVBoxLayout* full_layout = new QVBoxLayout(result); + 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(); + + 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")); +} diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/dialog/settings/application.cpp --- a/src/gui/dialog/settings/application.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,123 +0,0 @@ -#include "core/session.h" -#include "gui/dialog/settings.h" -#include -#include -#include -#include -#include -#include -#include -#include - -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_widget); - double_click_layout->addWidget(dc_combo_box_label); - double_click_layout->addWidget(dc_combo_box); - double_click_layout->setContentsMargins(0, 0, 0, 0); - - /* 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_widget); - middle_click_layout->addWidget(mc_combo_box_label); - middle_click_layout->addWidget(mc_combo_box); - middle_click_layout->setContentsMargins(0, 0, 0, 0); - - /* Actions */ - QHBoxLayout* actions_layout = new QHBoxLayout(actions_group_box); - actions_layout->addWidget(double_click_widget); - actions_layout->addWidget(middle_click_widget); - - 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::of(&QComboBox::currentIndexChanged), this, - [this](int index) { language = static_cast(index); }); - lang_combo_box->setCurrentIndex(static_cast(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); - hl_above_anime_box->setEnabled(state); - }); - connect(hl_above_anime_box, &QCheckBox::stateChanged, this, - [this](int state) { highlight_anime_if_available = !(state == Qt::Unchecked); }); - 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->setContentsMargins(10, 0, 0, 0); - - /* Appearance */ - QVBoxLayout* appearance_layout = new QVBoxLayout(appearance_group_box); - 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); - - 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); }); - 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); }); - progress_display_available_episodes->setCheckState(display_available_episodes ? Qt::Checked : Qt::Unchecked); - - QVBoxLayout* progress_layout = new QVBoxLayout(progress_group_box); - progress_layout->addWidget(progress_display_aired_episodes); - progress_layout->addWidget(progress_display_available_episodes); - - QVBoxLayout* full_layout = new QVBoxLayout(result); - 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(); - - 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")); -} diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/dialog/settings/services.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/dialog/settings/services.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,97 @@ +#include "core/anime.h" +#include "core/session.h" +#include "core/strings.h" +#include "gui/dialog/settings.h" +#include "services/anilist.h" +#include +#include +#include +#include +#include +#include +#include + +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::of(&QComboBox::currentIndexChanged), this, + [this](int index) { service = static_cast(index + 1); }); + sync_combo_box->setCurrentIndex(static_cast(service) - 1); + + QLabel* sync_note_label = + new QLabel(tr("Note: Minori is unable to synchronize multiple services at the same time."), sync_group_box); + + QVBoxLayout* sync_layout = new QVBoxLayout(sync_group_box); + sync_layout->addWidget(sync_combo_box_label); + sync_layout->addWidget(sync_combo_box); + sync_layout->addWidget(sync_note_label); + + QVBoxLayout* full_layout = new QVBoxLayout(result); + full_layout->addWidget(sync_group_box); + full_layout->setSpacing(10); + full_layout->addStretch(); + + 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); + + /* this is outdated! usernames are retrieved through a request to AniList now. + although that's a bit... erm... cancerous, maybe this method IS useful. IDK */ + 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_widget); + auth_layout->addWidget(username_entry); + auth_layout->addWidget(auth_button); + + QLabel* note_label = new QLabel(tr("Create a new AniList account"), group_box); + note_label->setTextFormat(Qt::RichText); + note_label->setTextInteractionFlags(Qt::TextBrowserInteraction); + note_label->setOpenExternalLinks(true); + + QVBoxLayout* layout = new QVBoxLayout(group_box); + layout->addWidget(username_entry_label); + layout->addWidget(auth_widget); + layout->addWidget(note_label); + + QVBoxLayout* full_layout = new QVBoxLayout(result); + full_layout->addWidget(group_box); + full_layout->setSpacing(10); + full_layout->addStretch(); + return result; +} + +void SettingsPageServices::SaveInfo() { + // session.config.anilist.username = + Strings::ToUtf8String(username); + 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")); +} diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/dialog/settings/services.cpp --- a/src/gui/dialog/settings/services.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -#include "core/anime.h" -#include "core/session.h" -#include "core/strings.h" -#include "gui/dialog/settings.h" -#include "services/anilist.h" -#include -#include -#include -#include -#include -#include -#include - -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::of(&QComboBox::currentIndexChanged), this, - [this](int index) { service = static_cast(index + 1); }); - sync_combo_box->setCurrentIndex(static_cast(service) - 1); - - QLabel* sync_note_label = - new QLabel(tr("Note: Minori is unable to synchronize multiple services at the same time."), sync_group_box); - - QVBoxLayout* sync_layout = new QVBoxLayout(sync_group_box); - sync_layout->addWidget(sync_combo_box_label); - sync_layout->addWidget(sync_combo_box); - sync_layout->addWidget(sync_note_label); - - QVBoxLayout* full_layout = new QVBoxLayout(result); - full_layout->addWidget(sync_group_box); - full_layout->setSpacing(10); - full_layout->addStretch(); - - 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); - - /* this is outdated! usernames are retrieved through a request to AniList now. - although that's a bit... erm... cancerous, maybe this method IS useful. IDK */ - 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_widget); - auth_layout->addWidget(username_entry); - auth_layout->addWidget(auth_button); - - QLabel* note_label = new QLabel(tr("Create a new AniList account"), group_box); - note_label->setTextFormat(Qt::RichText); - note_label->setTextInteractionFlags(Qt::TextBrowserInteraction); - note_label->setOpenExternalLinks(true); - - QVBoxLayout* layout = new QVBoxLayout(group_box); - layout->addWidget(username_entry_label); - layout->addWidget(auth_widget); - layout->addWidget(note_label); - - QVBoxLayout* full_layout = new QVBoxLayout(result); - full_layout->addWidget(group_box); - full_layout->setSpacing(10); - full_layout->addStretch(); - return result; -} - -void SettingsPageServices::SaveInfo() { - // session.config.anilist.username = - Strings::ToUtf8String(username); - 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")); -} diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/pages/anime_list.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/pages/anime_list.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,504 @@ +/** + * 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/array.h" +#include "core/session.h" +#include "core/strings.h" +#include "core/time.h" +#include "gui/dialog/information.h" +#include "gui/translate/anime.h" +#include "services/services.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +AnimeListPageDelegate::AnimeListPageDelegate(QObject* parent) : QStyledItemDelegate(parent) { +} + +QWidget* AnimeListPageDelegate::createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const { + // no edit 4 u + return nullptr; +} + +void AnimeListPageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const { + switch (index.column()) { +#if 0 + case AnimeListPageModel::AL_PROGRESS: { + const int progress = static_cast(index.data(Qt::UserRole).toReal()); + const int episodes = + static_cast(index.siblingAtColumn(AnimeListPageModel::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, tr("/"), 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; + } +#endif + default: QStyledItemDelegate::paint(painter, option, index); break; + } +} + +AnimeListPageSortFilter::AnimeListPageSortFilter(QObject* parent) : QSortFilterProxyModel(parent) { +} + +bool AnimeListPageSortFilter::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; + } +} + +AnimeListPageModel::AnimeListPageModel(QWidget* parent, Anime::ListStatus _status) : QAbstractListModel(parent) { + status = _status; + return; +} + +int AnimeListPageModel::rowCount(const QModelIndex& parent) const { + return list.size(); + (void)(parent); +} + +int AnimeListPageModel::columnCount(const QModelIndex& parent) const { + return NB_COLUMNS; + (void)(parent); +} + +QVariant AnimeListPageModel::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 AnimeListPageModel::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 Strings::ToQString(Translate::ToString(list[index.row()].GetFormat())); + case AL_SEASON: + return Strings::ToQString(Translate::ToString(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_PROGRESS: return list[index.row()].GetUserProgress(); + case AL_TYPE: return static_cast(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 QVariant::fromValue(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(); +} + +Anime::Anime* AnimeListPageModel::GetAnimeFromIndex(QModelIndex index) { + return &list.at(index.row()); +} + +void AnimeListPageModel::RefreshList() { + bool has_children = !!rowCount(index(0)); + if (!has_children) { + beginInsertRows(QModelIndex(), 0, 0); + endInsertRows(); + } + + beginResetModel(); + + list.clear(); + + for (const auto& a : Anime::db.items) { + if (a.second.IsInUserList() && a.second.GetUserStatus() == status) { + list.push_back(a.second); + } + } + + endResetModel(); +} + +int AnimeListPage::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 AnimeListPage::SetColumnDefaults() { + tree_view->setColumnHidden(AnimeListPageModel::AL_SEASON, false); + tree_view->setColumnHidden(AnimeListPageModel::AL_TYPE, false); + tree_view->setColumnHidden(AnimeListPageModel::AL_UPDATED, false); + tree_view->setColumnHidden(AnimeListPageModel::AL_PROGRESS, false); + tree_view->setColumnHidden(AnimeListPageModel::AL_SCORE, false); + tree_view->setColumnHidden(AnimeListPageModel::AL_TITLE, false); + tree_view->setColumnHidden(AnimeListPageModel::AL_EPISODES, true); + tree_view->setColumnHidden(AnimeListPageModel::AL_AVG_SCORE, true); + tree_view->setColumnHidden(AnimeListPageModel::AL_STARTED, true); + tree_view->setColumnHidden(AnimeListPageModel::AL_COMPLETED, true); + tree_view->setColumnHidden(AnimeListPageModel::AL_UPDATED, true); + tree_view->setColumnHidden(AnimeListPageModel::AL_NOTES, true); +} + +void AnimeListPage::UpdateAnime(int id) { + QThreadPool::globalInstance()->start([this, id] { + Services::UpdateAnimeEntry(id); + Refresh(); + }); +} + +void AnimeListPage::RemoveAnime(int id) { + Anime::Anime& anime = Anime::db.items[id]; + anime.RemoveFromUserList(); + Refresh(); +} + +void AnimeListPage::DisplayColumnHeaderMenu() { + QMenu* menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->setTitle(tr("Column visibility")); + menu->setToolTipsVisible(true); + + for (int i = 0; i < AnimeListPageModel::NB_COLUMNS; i++) { + if (i == AnimeListPageModel::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 AnimeListPage::DisplayListMenu() { + QMenu* menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->setTitle(tr("Column visibility")); + menu->setToolTipsVisible(true); + + AnimeListPageModel* source_model = + reinterpret_cast(sort_models[tab_bar->currentIndex()]->sourceModel()); + const QItemSelection selection = + sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); + + std::set animes; + for (const auto& index : selection.indexes()) { + if (!index.isValid()) + continue; + Anime::Anime* anime = source_model->GetAnimeFromIndex(index); + if (anime) + animes.insert(anime); + } + + QAction* action = menu->addAction(tr("Information"), [this, animes] { + for (auto& anime : animes) { + InformationDialog* dialog = new InformationDialog( + *anime, [this, anime] { UpdateAnime(anime->GetId()); }, this); + + dialog->show(); + dialog->raise(); + dialog->activateWindow(); + } + }); + menu->addSeparator(); + action = menu->addAction(tr("Delete from list..."), [this, animes] { + for (auto& anime : animes) { + RemoveAnime(anime->GetId()); + } + }); + menu->popup(QCursor::pos()); +} + +void AnimeListPage::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; + } + + AnimeListPageModel* source_model = + reinterpret_cast(sort_models[tab_bar->currentIndex()]->sourceModel()); + + const QModelIndex index = source_model->index(selection.indexes().first().row()); + Anime::Anime* anime = source_model->GetAnimeFromIndex(index); + + InformationDialog* dialog = new InformationDialog( + *anime, [this, anime] { UpdateAnime(anime->GetId()); }, this); + + dialog->show(); + dialog->raise(); + dialog->activateWindow(); +} + +void AnimeListPage::paintEvent(QPaintEvent*) { + QStylePainter p(this); + + QStyleOptionTabWidgetFrame opt; + InitStyle(&opt); + opt.rect = panelRect; + p.drawPrimitive(QStyle::PE_FrameTabWidget, opt); +} + +void AnimeListPage::resizeEvent(QResizeEvent* e) { + QWidget::resizeEvent(e); + SetupLayout(); +} + +void AnimeListPage::showEvent(QShowEvent*) { + SetupLayout(); +} + +void AnimeListPage::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 AnimeListPage::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 AnimeListPage::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); +} + +AnimeListPage::AnimeListPage(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 AnimeListPageDelegate(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(Strings::ToQString(Translate::ToString(Anime::ListStatuses[i])) + " (" + + QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); + sort_models[i] = new AnimeListPageSortFilter(tree_view); + sort_models[i]->setSourceModel(new AnimeListPageModel(this, Anime::ListStatuses[i])); + sort_models[i]->setSortRole(Qt::UserRole); + sort_models[i]->setSortCaseSensitivity(Qt::CaseInsensitive); + } + tree_view->setModel(sort_models[0]); + + QHBoxLayout* layout = new QHBoxLayout(tree_widget); + layout->addWidget(tree_view); + layout->setContentsMargins(0, 0, 0, 0); + + /* Double click stuff */ + connect(tree_view, &QAbstractItemView::doubleClicked, this, &AnimeListPage::ItemDoubleClicked); + connect(tree_view, &QWidget::customContextMenuRequested, this, &AnimeListPage::DisplayListMenu); + + /* Enter & return keys */ + connect(new QShortcut(Qt::Key_Return, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this, + &AnimeListPage::ItemDoubleClicked); + + connect(new QShortcut(Qt::Key_Enter, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this, + &AnimeListPage::ItemDoubleClicked); + + tree_view->header()->setStretchLastSection(false); + tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(tree_view->header(), &QWidget::customContextMenuRequested, this, &AnimeListPage::DisplayColumnHeaderMenu); + + connect(tab_bar, &QTabBar::currentChanged, this, [this](int index) { + if (sort_models[index]) + tree_view->setModel(sort_models[index]); + }); + + SetColumnDefaults(); + setFocusPolicy(Qt::TabFocus); + setFocusProxy(tab_bar); +} + +void AnimeListPage::RefreshList() { + for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++) + reinterpret_cast(sort_models[i]->sourceModel())->RefreshList(); +} + +void AnimeListPage::RefreshTabs() { + for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++) + tab_bar->setTabText(i, Strings::ToQString(Translate::ToString(Anime::ListStatuses[i])) + " (" + + QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); +} + +void AnimeListPage::Refresh() { + RefreshList(); + RefreshTabs(); +} + +/* This function, really, really should not be called. + Ever. Why would you ever need to clear the anime list? + Also, this sucks. */ +void AnimeListPage::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" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/pages/anime_list.cpp --- a/src/gui/pages/anime_list.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,504 +0,0 @@ -/** - * 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/array.h" -#include "core/session.h" -#include "core/strings.h" -#include "core/time.h" -#include "gui/dialog/information.h" -#include "gui/translate/anime.h" -#include "services/services.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -AnimeListPageDelegate::AnimeListPageDelegate(QObject* parent) : QStyledItemDelegate(parent) { -} - -QWidget* AnimeListPageDelegate::createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const { - // no edit 4 u - return nullptr; -} - -void AnimeListPageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const { - switch (index.column()) { -#if 0 - case AnimeListPageModel::AL_PROGRESS: { - const int progress = static_cast(index.data(Qt::UserRole).toReal()); - const int episodes = - static_cast(index.siblingAtColumn(AnimeListPageModel::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, tr("/"), 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; - } -#endif - default: QStyledItemDelegate::paint(painter, option, index); break; - } -} - -AnimeListPageSortFilter::AnimeListPageSortFilter(QObject* parent) : QSortFilterProxyModel(parent) { -} - -bool AnimeListPageSortFilter::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; - } -} - -AnimeListPageModel::AnimeListPageModel(QWidget* parent, Anime::ListStatus _status) : QAbstractListModel(parent) { - status = _status; - return; -} - -int AnimeListPageModel::rowCount(const QModelIndex& parent) const { - return list.size(); - (void)(parent); -} - -int AnimeListPageModel::columnCount(const QModelIndex& parent) const { - return NB_COLUMNS; - (void)(parent); -} - -QVariant AnimeListPageModel::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 AnimeListPageModel::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 Strings::ToQString(Translate::ToString(list[index.row()].GetFormat())); - case AL_SEASON: - return Strings::ToQString(Translate::ToString(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_PROGRESS: return list[index.row()].GetUserProgress(); - case AL_TYPE: return static_cast(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 QVariant::fromValue(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(); -} - -Anime::Anime* AnimeListPageModel::GetAnimeFromIndex(QModelIndex index) { - return &list.at(index.row()); -} - -void AnimeListPageModel::RefreshList() { - bool has_children = !!rowCount(index(0)); - if (!has_children) { - beginInsertRows(QModelIndex(), 0, 0); - endInsertRows(); - } - - beginResetModel(); - - list.clear(); - - for (const auto& a : Anime::db.items) { - if (a.second.IsInUserList() && a.second.GetUserStatus() == status) { - list.push_back(a.second); - } - } - - endResetModel(); -} - -int AnimeListPage::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 AnimeListPage::SetColumnDefaults() { - tree_view->setColumnHidden(AnimeListPageModel::AL_SEASON, false); - tree_view->setColumnHidden(AnimeListPageModel::AL_TYPE, false); - tree_view->setColumnHidden(AnimeListPageModel::AL_UPDATED, false); - tree_view->setColumnHidden(AnimeListPageModel::AL_PROGRESS, false); - tree_view->setColumnHidden(AnimeListPageModel::AL_SCORE, false); - tree_view->setColumnHidden(AnimeListPageModel::AL_TITLE, false); - tree_view->setColumnHidden(AnimeListPageModel::AL_EPISODES, true); - tree_view->setColumnHidden(AnimeListPageModel::AL_AVG_SCORE, true); - tree_view->setColumnHidden(AnimeListPageModel::AL_STARTED, true); - tree_view->setColumnHidden(AnimeListPageModel::AL_COMPLETED, true); - tree_view->setColumnHidden(AnimeListPageModel::AL_UPDATED, true); - tree_view->setColumnHidden(AnimeListPageModel::AL_NOTES, true); -} - -void AnimeListPage::UpdateAnime(int id) { - QThreadPool::globalInstance()->start([this, id] { - Services::UpdateAnimeEntry(id); - Refresh(); - }); -} - -void AnimeListPage::RemoveAnime(int id) { - Anime::Anime& anime = Anime::db.items[id]; - anime.RemoveFromUserList(); - Refresh(); -} - -void AnimeListPage::DisplayColumnHeaderMenu() { - QMenu* menu = new QMenu(this); - menu->setAttribute(Qt::WA_DeleteOnClose); - menu->setTitle(tr("Column visibility")); - menu->setToolTipsVisible(true); - - for (int i = 0; i < AnimeListPageModel::NB_COLUMNS; i++) { - if (i == AnimeListPageModel::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 AnimeListPage::DisplayListMenu() { - QMenu* menu = new QMenu(this); - menu->setAttribute(Qt::WA_DeleteOnClose); - menu->setTitle(tr("Column visibility")); - menu->setToolTipsVisible(true); - - AnimeListPageModel* source_model = - reinterpret_cast(sort_models[tab_bar->currentIndex()]->sourceModel()); - const QItemSelection selection = - sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); - - std::set animes; - for (const auto& index : selection.indexes()) { - if (!index.isValid()) - continue; - Anime::Anime* anime = source_model->GetAnimeFromIndex(index); - if (anime) - animes.insert(anime); - } - - QAction* action = menu->addAction(tr("Information"), [this, animes] { - for (auto& anime : animes) { - InformationDialog* dialog = new InformationDialog( - *anime, [this, anime] { UpdateAnime(anime->GetId()); }, this); - - dialog->show(); - dialog->raise(); - dialog->activateWindow(); - } - }); - menu->addSeparator(); - action = menu->addAction(tr("Delete from list..."), [this, animes] { - for (auto& anime : animes) { - RemoveAnime(anime->GetId()); - } - }); - menu->popup(QCursor::pos()); -} - -void AnimeListPage::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; - } - - AnimeListPageModel* source_model = - reinterpret_cast(sort_models[tab_bar->currentIndex()]->sourceModel()); - - const QModelIndex index = source_model->index(selection.indexes().first().row()); - Anime::Anime* anime = source_model->GetAnimeFromIndex(index); - - InformationDialog* dialog = new InformationDialog( - *anime, [this, anime] { UpdateAnime(anime->GetId()); }, this); - - dialog->show(); - dialog->raise(); - dialog->activateWindow(); -} - -void AnimeListPage::paintEvent(QPaintEvent*) { - QStylePainter p(this); - - QStyleOptionTabWidgetFrame opt; - InitStyle(&opt); - opt.rect = panelRect; - p.drawPrimitive(QStyle::PE_FrameTabWidget, opt); -} - -void AnimeListPage::resizeEvent(QResizeEvent* e) { - QWidget::resizeEvent(e); - SetupLayout(); -} - -void AnimeListPage::showEvent(QShowEvent*) { - SetupLayout(); -} - -void AnimeListPage::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 AnimeListPage::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 AnimeListPage::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); -} - -AnimeListPage::AnimeListPage(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 AnimeListPageDelegate(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(Strings::ToQString(Translate::ToString(Anime::ListStatuses[i])) + " (" + - QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); - sort_models[i] = new AnimeListPageSortFilter(tree_view); - sort_models[i]->setSourceModel(new AnimeListPageModel(this, Anime::ListStatuses[i])); - sort_models[i]->setSortRole(Qt::UserRole); - sort_models[i]->setSortCaseSensitivity(Qt::CaseInsensitive); - } - tree_view->setModel(sort_models[0]); - - QHBoxLayout* layout = new QHBoxLayout(tree_widget); - layout->addWidget(tree_view); - layout->setContentsMargins(0, 0, 0, 0); - - /* Double click stuff */ - connect(tree_view, &QAbstractItemView::doubleClicked, this, &AnimeListPage::ItemDoubleClicked); - connect(tree_view, &QWidget::customContextMenuRequested, this, &AnimeListPage::DisplayListMenu); - - /* Enter & return keys */ - connect(new QShortcut(Qt::Key_Return, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this, - &AnimeListPage::ItemDoubleClicked); - - connect(new QShortcut(Qt::Key_Enter, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this, - &AnimeListPage::ItemDoubleClicked); - - tree_view->header()->setStretchLastSection(false); - tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu); - connect(tree_view->header(), &QWidget::customContextMenuRequested, this, &AnimeListPage::DisplayColumnHeaderMenu); - - connect(tab_bar, &QTabBar::currentChanged, this, [this](int index) { - if (sort_models[index]) - tree_view->setModel(sort_models[index]); - }); - - SetColumnDefaults(); - setFocusPolicy(Qt::TabFocus); - setFocusProxy(tab_bar); -} - -void AnimeListPage::RefreshList() { - for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++) - reinterpret_cast(sort_models[i]->sourceModel())->RefreshList(); -} - -void AnimeListPage::RefreshTabs() { - for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++) - tab_bar->setTabText(i, Strings::ToQString(Translate::ToString(Anime::ListStatuses[i])) + " (" + - QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); -} - -void AnimeListPage::Refresh() { - RefreshList(); - RefreshTabs(); -} - -/* This function, really, really should not be called. - Ever. Why would you ever need to clear the anime list? - Also, this sucks. */ -void AnimeListPage::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" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/pages/history.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/pages/history.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,6 @@ +#include "gui/pages/history.h" + +HistoryPage::HistoryPage(QWidget* parent) : QWidget(parent) { +} + +#include "gui/pages/moc_history.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/pages/history.cpp --- a/src/gui/pages/history.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -#include "gui/pages/history.h" - -HistoryPage::HistoryPage(QWidget* parent) : QWidget(parent) { -} - -#include "gui/pages/moc_history.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/pages/now_playing.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/pages/now_playing.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,109 @@ +#include "gui/pages/now_playing.h" +#include "core/anime_db.h" +#include "gui/widgets/anime_info.h" +#include "gui/widgets/text.h" +#include +#include +#include +#include + +/* This is here to make it easier to switch between the + "sub-pages", i.e., not playing and playing. + + TODO: find a way to do this more efficiently */ +namespace NowPlayingPages { + +class Default : public QWidget { + Q_OBJECT + + public: + Default(QWidget* parent = nullptr); +}; + +class Playing : public QWidget { + Q_OBJECT + + public: + Playing(QWidget* parent = nullptr); + void SetPlayingAnime(int id, const std::unordered_map& info); + int GetPlayingAnime(); + + private: + int _id = 0; + int _episode = 0; + std::unique_ptr _title = nullptr; + std::unique_ptr _info = nullptr; +}; + +Default::Default(QWidget* parent) : QWidget(parent) { + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + TextWidgets::Title* title = new TextWidgets::Title(tr("Now Playing"), this); + layout->addWidget(title); + + layout->addStretch(); +} + +Playing::Playing(QWidget* parent) : QWidget(parent) { + QHBoxLayout* layout = new QHBoxLayout(this); + + _title.reset(new TextWidgets::Title("\n", this)); + layout->addWidget(_title.get()); + + _info.reset(new AnimeInfoWidget(this)); + layout->addWidget(_info.get()); + + layout->setContentsMargins(0, 0, 0, 0); +} + +int Playing::GetPlayingAnime() { + return _id; +} + +void Playing::SetPlayingAnime(int id, const std::unordered_map& info) { + if (id == _id || id <= 0) + return; + if (Anime::db.items.find(id) != Anime::db.items.end()) { + const Anime::Anime& anime = Anime::db.items[_id = id]; + _title->setText(anime.GetUserPreferredTitle()); + _info->SetAnime(anime); + } +} + +} // namespace NowPlayingPages + +NowPlayingPage::NowPlayingPage(QWidget* parent) : QFrame(parent) { + QVBoxLayout* layout = new QVBoxLayout(this); + + setFrameShape(QFrame::Box); + setFrameShadow(QFrame::Sunken); + + QPalette pal = QPalette(); + pal.setColor(QPalette::Window, pal.color(QPalette::Base)); + setPalette(pal); + setAutoFillBackground(true); + + stack = new QStackedWidget(this); + stack->addWidget(new NowPlayingPages::Default(stack)); + stack->addWidget(new NowPlayingPages::Playing(stack)); + layout->addWidget(stack); + + SetDefault(); +} + +void NowPlayingPage::SetDefault() { + stack->setCurrentIndex(0); +} + +int NowPlayingPage::GetPlayingId() { + return reinterpret_cast(stack->widget(1))->GetPlayingAnime(); +} + +void NowPlayingPage::SetPlaying(int id, const std::unordered_map& info) { + reinterpret_cast(stack->widget(1))->SetPlayingAnime(id, info); + stack->setCurrentIndex(1); +} + +#include "gui/pages/moc_now_playing.cpp" +#include "now_playing.moc" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/pages/now_playing.cpp --- a/src/gui/pages/now_playing.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -#include "gui/pages/now_playing.h" -#include "core/anime_db.h" -#include "gui/widgets/anime_info.h" -#include "gui/widgets/text.h" -#include -#include -#include -#include - -/* This is here to make it easier to switch between the - "sub-pages", i.e., not playing and playing. - - TODO: find a way to do this more efficiently */ -namespace NowPlayingPages { - -class Default : public QWidget { - Q_OBJECT - - public: - Default(QWidget* parent = nullptr); -}; - -class Playing : public QWidget { - Q_OBJECT - - public: - Playing(QWidget* parent = nullptr); - void SetPlayingAnime(int id, const std::unordered_map& info); - int GetPlayingAnime(); - - private: - int _id = 0; - int _episode = 0; - std::unique_ptr _title = nullptr; - std::unique_ptr _info = nullptr; -}; - -Default::Default(QWidget* parent) : QWidget(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - - TextWidgets::Title* title = new TextWidgets::Title(tr("Now Playing"), this); - layout->addWidget(title); - - layout->addStretch(); -} - -Playing::Playing(QWidget* parent) : QWidget(parent) { - QHBoxLayout* layout = new QHBoxLayout(this); - - _title.reset(new TextWidgets::Title("\n", this)); - layout->addWidget(_title.get()); - - _info.reset(new AnimeInfoWidget(this)); - layout->addWidget(_info.get()); - - layout->setContentsMargins(0, 0, 0, 0); -} - -int Playing::GetPlayingAnime() { - return _id; -} - -void Playing::SetPlayingAnime(int id, const std::unordered_map& info) { - if (id == _id || id <= 0) - return; - if (Anime::db.items.find(id) != Anime::db.items.end()) { - const Anime::Anime& anime = Anime::db.items[_id = id]; - _title->setText(anime.GetUserPreferredTitle()); - _info->SetAnime(anime); - } -} - -} // namespace NowPlayingPages - -NowPlayingPage::NowPlayingPage(QWidget* parent) : QFrame(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); - - setFrameShape(QFrame::Box); - setFrameShadow(QFrame::Sunken); - - QPalette pal = QPalette(); - pal.setColor(QPalette::Window, pal.color(QPalette::Base)); - setPalette(pal); - setAutoFillBackground(true); - - stack = new QStackedWidget(this); - stack->addWidget(new NowPlayingPages::Default(stack)); - stack->addWidget(new NowPlayingPages::Playing(stack)); - layout->addWidget(stack); - - SetDefault(); -} - -void NowPlayingPage::SetDefault() { - stack->setCurrentIndex(0); -} - -int NowPlayingPage::GetPlayingId() { - return reinterpret_cast(stack->widget(1))->GetPlayingAnime(); -} - -void NowPlayingPage::SetPlaying(int id, const std::unordered_map& info) { - reinterpret_cast(stack->widget(1))->SetPlayingAnime(id, info); - stack->setCurrentIndex(1); -} - -#include "gui/pages/moc_now_playing.cpp" -#include "now_playing.moc" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/pages/search.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/pages/search.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,6 @@ +#include "gui/pages/search.h" + +SearchPage::SearchPage(QWidget* parent) : QWidget(parent) { +} + +#include "gui/pages/moc_search.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/pages/search.cpp --- a/src/gui/pages/search.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -#include "gui/pages/search.h" - -SearchPage::SearchPage(QWidget* parent) : QWidget(parent) { -} - -#include "gui/pages/moc_search.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/pages/seasons.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/pages/seasons.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,6 @@ +#include "gui/pages/seasons.h" + +SeasonsPage::SeasonsPage(QWidget* parent) : QWidget(parent) { +} + +#include "gui/pages/moc_seasons.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/pages/seasons.cpp --- a/src/gui/pages/seasons.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -#include "gui/pages/seasons.h" - -SeasonsPage::SeasonsPage(QWidget* parent) : QWidget(parent) { -} - -#include "gui/pages/moc_seasons.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/pages/statistics.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/pages/statistics.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,113 @@ +#include "gui/pages/statistics.h" +#include "core/anime_db.h" +#include "core/session.h" +#include "gui/pages/anime_list.h" +#include "gui/widgets/text.h" +#include +#include +#include +#include +#include +#include +#include + +StatisticsPage::StatisticsPage(QWidget* parent) : QFrame(parent) { + QVBoxLayout* layout = new QVBoxLayout(this); + + setFrameShape(QFrame::Box); + setFrameShadow(QFrame::Sunken); + + QPalette pal = QPalette(); + pal.setColor(QPalette::Window, pal.color(QPalette::Base)); + setPalette(pal); + setAutoFillBackground(true); + + TextWidgets::LabelledSection* anime_list_paragraph = new TextWidgets::LabelledSection( + tr("Anime list"), + tr("Anime count:\nEpisode count:\nTime spent watching:\nTime to complete:\nAverage score:\nScore deviation:"), + "\n\n\n\n\n\n", this); + anime_list_data = anime_list_paragraph->GetParagraph(); + + TextWidgets::LabelledSection* application_paragraph = + new TextWidgets::LabelledSection(tr("Minori"), tr("Uptime:\nRequests made:"), "\n\n", this); + application_data = application_paragraph->GetParagraph(); + + layout->addWidget(anime_list_paragraph); + layout->addWidget(application_paragraph); + layout->addStretch(); + + QTimer* timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, [this] { + if (isVisible()) + UpdateStatistics(); + }); + timer->start(1000); // update statistics every second +} + +void StatisticsPage::showEvent(QShowEvent*) { + UpdateStatistics(); +} + +/* me abusing macros :) */ +#define ADD_TIME_SEGMENT(r, x, s, p) \ + if (x > 0) \ + r << x << ((x == 1) ? s : p) +std::string StatisticsPage::MinutesToDateString(int minutes) { + /* ew */ + int years = (minutes * (1 / 525949.2F)); + int months = (minutes * (1 / 43829.1F)) - (years * 12); + int days = (minutes * (1 / 1440.0F)) - (years * 365.2425F) - (months * 30.436875F); + int hours = (minutes * (1 / 60.0F)) - (years * 8765.82F) - (months * 730.485F) - (days * 24); + int rest_minutes = (minutes) - (years * 525949.2F) - (months * 43829.1F) - (days * 1440) - (hours * 60); + std::ostringstream return_stream; + 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 "); + if (rest_minutes > 0 || return_stream.str().size() == 0) + return_stream << rest_minutes << ((rest_minutes == 1) ? " minute" : " minutes"); + return return_stream.str(); +} + +std::string StatisticsPage::SecondsToDateString(int sec) { + /* this is all fairly unnecessary, but works:tm: */ + 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, 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 + +void StatisticsPage::UpdateStatistics() { + /* 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(); + anime_list_data->SetText(string); + + string = ""; + ts << QString::fromUtf8(SecondsToDateString(session.uptime() / 1000).c_str()) << '\n'; + ts << session.GetRequests(); + /* Application */ + // UiUtils::SetPlainTextEditData(application_data, QString::number(session.uptime() / 1000)); + application_data->SetText(string); +} + +#include "gui/pages/moc_statistics.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/pages/statistics.cpp --- a/src/gui/pages/statistics.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -#include "gui/pages/statistics.h" -#include "core/anime_db.h" -#include "core/session.h" -#include "gui/pages/anime_list.h" -#include "gui/widgets/text.h" -#include -#include -#include -#include -#include -#include -#include - -StatisticsPage::StatisticsPage(QWidget* parent) : QFrame(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); - - setFrameShape(QFrame::Box); - setFrameShadow(QFrame::Sunken); - - QPalette pal = QPalette(); - pal.setColor(QPalette::Window, pal.color(QPalette::Base)); - setPalette(pal); - setAutoFillBackground(true); - - TextWidgets::LabelledSection* anime_list_paragraph = new TextWidgets::LabelledSection( - tr("Anime list"), - tr("Anime count:\nEpisode count:\nTime spent watching:\nTime to complete:\nAverage score:\nScore deviation:"), - "\n\n\n\n\n\n", this); - anime_list_data = anime_list_paragraph->GetParagraph(); - - TextWidgets::LabelledSection* application_paragraph = - new TextWidgets::LabelledSection(tr("Minori"), tr("Uptime:\nRequests made:"), "\n\n", this); - application_data = application_paragraph->GetParagraph(); - - layout->addWidget(anime_list_paragraph); - layout->addWidget(application_paragraph); - layout->addStretch(); - - QTimer* timer = new QTimer(this); - connect(timer, &QTimer::timeout, this, [this] { - if (isVisible()) - UpdateStatistics(); - }); - timer->start(1000); // update statistics every second -} - -void StatisticsPage::showEvent(QShowEvent*) { - UpdateStatistics(); -} - -/* me abusing macros :) */ -#define ADD_TIME_SEGMENT(r, x, s, p) \ - if (x > 0) \ - r << x << ((x == 1) ? s : p) -std::string StatisticsPage::MinutesToDateString(int minutes) { - /* ew */ - int years = (minutes * (1 / 525949.2F)); - int months = (minutes * (1 / 43829.1F)) - (years * 12); - int days = (minutes * (1 / 1440.0F)) - (years * 365.2425F) - (months * 30.436875F); - int hours = (minutes * (1 / 60.0F)) - (years * 8765.82F) - (months * 730.485F) - (days * 24); - int rest_minutes = (minutes) - (years * 525949.2F) - (months * 43829.1F) - (days * 1440) - (hours * 60); - std::ostringstream return_stream; - 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 "); - if (rest_minutes > 0 || return_stream.str().size() == 0) - return_stream << rest_minutes << ((rest_minutes == 1) ? " minute" : " minutes"); - return return_stream.str(); -} - -std::string StatisticsPage::SecondsToDateString(int sec) { - /* this is all fairly unnecessary, but works:tm: */ - 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, 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 - -void StatisticsPage::UpdateStatistics() { - /* 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(); - anime_list_data->SetText(string); - - string = ""; - ts << QString::fromUtf8(SecondsToDateString(session.uptime() / 1000).c_str()) << '\n'; - ts << session.GetRequests(); - /* Application */ - // UiUtils::SetPlainTextEditData(application_data, QString::number(session.uptime() / 1000)); - application_data->SetText(string); -} - -#include "gui/pages/moc_statistics.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/pages/torrents.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/pages/torrents.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,6 @@ +#include "gui/pages/torrents.h" + +TorrentsPage::TorrentsPage(QWidget* parent) : QWidget(parent) { +} + +#include "gui/pages/moc_torrents.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/pages/torrents.cpp --- a/src/gui/pages/torrents.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -#include "gui/pages/torrents.h" - -TorrentsPage::TorrentsPage(QWidget* parent) : QWidget(parent) { -} - -#include "gui/pages/moc_torrents.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/translate/anilist.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/translate/anilist.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,53 @@ +#include "gui/translate/anilist.h" + +namespace Translate { +namespace AniList { + +Anime::SeriesStatus ToSeriesStatus(std::string status) { + const std::unordered_map map = { + {"FINISHED", Anime::SeriesStatus::FINISHED }, + {"RELEASING", Anime::SeriesStatus::RELEASING }, + {"NOT_YET_RELEASED", Anime::SeriesStatus::NOT_YET_RELEASED}, + {"CANCELLED", Anime::SeriesStatus::CANCELLED }, + {"HIATUS", Anime::SeriesStatus::HIATUS } + }; + + if (map.find(status) == map.end()) + return Anime::SeriesStatus::UNKNOWN; + return map.at(status); +} + +Anime::SeriesSeason ToSeriesSeason(std::string season) { + const std::unordered_map map = { + {"WINTER", Anime::SeriesSeason::WINTER}, + {"SPRING", Anime::SeriesSeason::SPRING}, + {"SUMMER", Anime::SeriesSeason::SUMMER}, + {"FALL", Anime::SeriesSeason::FALL } + }; + + if (map.find(season) == map.end()) + return Anime::SeriesSeason::UNKNOWN; + return map.at(season); +} + +Anime::SeriesFormat ToSeriesFormat(std::string format) { + const std::unordered_map map = { + {"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} + }; + + if (map.find(format) == map.end()) + return Anime::SeriesFormat::UNKNOWN; + return map.at(format); +} + +} // namespace AniList +} // namespace Translate diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/translate/anilist.cpp --- a/src/gui/translate/anilist.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -#include "gui/translate/anilist.h" - -namespace Translate { -namespace AniList { - -Anime::SeriesStatus ToSeriesStatus(std::string status) { - const std::unordered_map map = { - {"FINISHED", Anime::SeriesStatus::FINISHED }, - {"RELEASING", Anime::SeriesStatus::RELEASING }, - {"NOT_YET_RELEASED", Anime::SeriesStatus::NOT_YET_RELEASED}, - {"CANCELLED", Anime::SeriesStatus::CANCELLED }, - {"HIATUS", Anime::SeriesStatus::HIATUS } - }; - - if (map.find(status) == map.end()) - return Anime::SeriesStatus::UNKNOWN; - return map.at(status); -} - -Anime::SeriesSeason ToSeriesSeason(std::string season) { - const std::unordered_map map = { - {"WINTER", Anime::SeriesSeason::WINTER}, - {"SPRING", Anime::SeriesSeason::SPRING}, - {"SUMMER", Anime::SeriesSeason::SUMMER}, - {"FALL", Anime::SeriesSeason::FALL } - }; - - if (map.find(season) == map.end()) - return Anime::SeriesSeason::UNKNOWN; - return map.at(season); -} - -Anime::SeriesFormat ToSeriesFormat(std::string format) { - const std::unordered_map map = { - {"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} - }; - - if (map.find(format) == map.end()) - return Anime::SeriesFormat::UNKNOWN; - return map.at(format); -} - -} // namespace AniList -} // namespace Translate diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/translate/anime.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/translate/anime.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,57 @@ +#include "core/anime.h" +#include "core/strings.h" +#include "gui/translate/anime.h" +#include + +namespace Translate { + +std::string ToString(const Anime::ListStatus status) { + switch (status) { + case Anime::ListStatus::NOT_IN_LIST: return Strings::ToUtf8String(QCoreApplication::tr("Not in list")); + case Anime::ListStatus::CURRENT: return Strings::ToUtf8String(QCoreApplication::tr("Currently watching")); + case Anime::ListStatus::PLANNING: return Strings::ToUtf8String(QCoreApplication::tr("Plan to watch")); + case Anime::ListStatus::COMPLETED: return Strings::ToUtf8String(QCoreApplication::tr("Completed")); + case Anime::ListStatus::DROPPED: return Strings::ToUtf8String(QCoreApplication::tr("Dropped")); + case Anime::ListStatus::PAUSED: return Strings::ToUtf8String(QCoreApplication::tr("On hold")); + default: return ""; + } +} + +std::string ToString(const Anime::SeriesFormat format) { + switch (format) { + case Anime::SeriesFormat::UNKNOWN: return Strings::ToUtf8String(QCoreApplication::tr("Unknown")); + case Anime::SeriesFormat::TV: return Strings::ToUtf8String(QCoreApplication::tr("TV")); + case Anime::SeriesFormat::TV_SHORT: return Strings::ToUtf8String(QCoreApplication::tr("TV short")); + case Anime::SeriesFormat::OVA: return Strings::ToUtf8String(QCoreApplication::tr("OVA")); + case Anime::SeriesFormat::MOVIE: return Strings::ToUtf8String(QCoreApplication::tr("Movie")); + case Anime::SeriesFormat::SPECIAL: return Strings::ToUtf8String(QCoreApplication::tr("Special")); + case Anime::SeriesFormat::ONA: return Strings::ToUtf8String(QCoreApplication::tr("ONA")); + case Anime::SeriesFormat::MUSIC: return Strings::ToUtf8String(QCoreApplication::tr("Music")); + default: return ""; + } +} + +std::string ToString(const Anime::SeriesSeason season) { + switch (season) { + case Anime::SeriesSeason::UNKNOWN: return Strings::ToUtf8String(QCoreApplication::tr("Unknown")); + case Anime::SeriesSeason::WINTER: return Strings::ToUtf8String(QCoreApplication::tr("Winter")); + case Anime::SeriesSeason::SUMMER: return Strings::ToUtf8String(QCoreApplication::tr("Summer")); + case Anime::SeriesSeason::FALL: return Strings::ToUtf8String(QCoreApplication::tr("Fall")); + case Anime::SeriesSeason::SPRING: return Strings::ToUtf8String(QCoreApplication::tr("Spring")); + default: return ""; + } +} + +std::string ToString(const Anime::SeriesStatus status) { + switch (status) { + case Anime::SeriesStatus::UNKNOWN: return Strings::ToUtf8String(QCoreApplication::tr("Unknown")); + case Anime::SeriesStatus::RELEASING: return Strings::ToUtf8String(QCoreApplication::tr("Currently airing")); + case Anime::SeriesStatus::FINISHED: return Strings::ToUtf8String(QCoreApplication::tr("Finished airing")); + case Anime::SeriesStatus::NOT_YET_RELEASED: return Strings::ToUtf8String(QCoreApplication::tr("Not yet aired")); + case Anime::SeriesStatus::CANCELLED: return Strings::ToUtf8String(QCoreApplication::tr("Cancelled")); + case Anime::SeriesStatus::HIATUS: return Strings::ToUtf8String(QCoreApplication::tr("On hiatus")); + default: return ""; + } +} + +} // namespace Translate diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/translate/anime.cpp --- a/src/gui/translate/anime.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -#include "core/anime.h" -#include "core/strings.h" -#include "gui/translate/anime.h" -#include - -namespace Translate { - -std::string ToString(const Anime::ListStatus status) { - switch (status) { - case Anime::ListStatus::NOT_IN_LIST: return Strings::ToUtf8String(QCoreApplication::tr("Not in list")); - case Anime::ListStatus::CURRENT: return Strings::ToUtf8String(QCoreApplication::tr("Currently watching")); - case Anime::ListStatus::PLANNING: return Strings::ToUtf8String(QCoreApplication::tr("Plan to watch")); - case Anime::ListStatus::COMPLETED: return Strings::ToUtf8String(QCoreApplication::tr("Completed")); - case Anime::ListStatus::DROPPED: return Strings::ToUtf8String(QCoreApplication::tr("Dropped")); - case Anime::ListStatus::PAUSED: return Strings::ToUtf8String(QCoreApplication::tr("On hold")); - default: return ""; - } -} - -std::string ToString(const Anime::SeriesFormat format) { - switch (format) { - case Anime::SeriesFormat::UNKNOWN: return Strings::ToUtf8String(QCoreApplication::tr("Unknown")); - case Anime::SeriesFormat::TV: return Strings::ToUtf8String(QCoreApplication::tr("TV")); - case Anime::SeriesFormat::TV_SHORT: return Strings::ToUtf8String(QCoreApplication::tr("TV short")); - case Anime::SeriesFormat::OVA: return Strings::ToUtf8String(QCoreApplication::tr("OVA")); - case Anime::SeriesFormat::MOVIE: return Strings::ToUtf8String(QCoreApplication::tr("Movie")); - case Anime::SeriesFormat::SPECIAL: return Strings::ToUtf8String(QCoreApplication::tr("Special")); - case Anime::SeriesFormat::ONA: return Strings::ToUtf8String(QCoreApplication::tr("ONA")); - case Anime::SeriesFormat::MUSIC: return Strings::ToUtf8String(QCoreApplication::tr("Music")); - default: return ""; - } -} - -std::string ToString(const Anime::SeriesSeason season) { - switch (season) { - case Anime::SeriesSeason::UNKNOWN: return Strings::ToUtf8String(QCoreApplication::tr("Unknown")); - case Anime::SeriesSeason::WINTER: return Strings::ToUtf8String(QCoreApplication::tr("Winter")); - case Anime::SeriesSeason::SUMMER: return Strings::ToUtf8String(QCoreApplication::tr("Summer")); - case Anime::SeriesSeason::FALL: return Strings::ToUtf8String(QCoreApplication::tr("Fall")); - case Anime::SeriesSeason::SPRING: return Strings::ToUtf8String(QCoreApplication::tr("Spring")); - default: return ""; - } -} - -std::string ToString(const Anime::SeriesStatus status) { - switch (status) { - case Anime::SeriesStatus::UNKNOWN: return Strings::ToUtf8String(QCoreApplication::tr("Unknown")); - case Anime::SeriesStatus::RELEASING: return Strings::ToUtf8String(QCoreApplication::tr("Currently airing")); - case Anime::SeriesStatus::FINISHED: return Strings::ToUtf8String(QCoreApplication::tr("Finished airing")); - case Anime::SeriesStatus::NOT_YET_RELEASED: return Strings::ToUtf8String(QCoreApplication::tr("Not yet aired")); - case Anime::SeriesStatus::CANCELLED: return Strings::ToUtf8String(QCoreApplication::tr("Cancelled")); - case Anime::SeriesStatus::HIATUS: return Strings::ToUtf8String(QCoreApplication::tr("On hiatus")); - default: return ""; - } -} - -} // namespace Translate diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/widgets/anime_info.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/widgets/anime_info.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,48 @@ +#include "gui/widgets/anime_info.h" +#include "core/anime.h" +#include "core/strings.h" +#include "gui/translate/anime.h" +#include "gui/widgets/text.h" +#include +#include + +AnimeInfoWidget::AnimeInfoWidget(QWidget* parent) : QWidget(parent) { + QVBoxLayout* layout = new QVBoxLayout(this); + + _title.reset(new TextWidgets::OneLineSection(tr("Alternative titles"), "", this)); + layout->addWidget(_title.get()); + + _details.reset(new TextWidgets::LabelledSection(tr("Details"), new TextWidgets::LabelledSection( + tr("Details"), tr("Type:\nEpisodes:\nStatus:\nSeason:\nGenres:\nScore:"), "", this), "", this)); + layout->addWidget(_details.get()); + + _synopsis.reset(new TextWidgets::LabelledSection(tr("Synopsis"), "", this)); + _synopsis->GetParagraph()->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + layout->addWidget(_synopsis.get()); +} + +AnimeInfoWidget::AnimeInfoWidget(const Anime::Anime& anime, QWidget* parent) : AnimeInfoWidget(parent) { + SetAnime(anime); +} + +AnimeInfoWidget::SetAnime(const Anime::Anime& anime) { + /* alt titles */ + _title->GetParagraph()->SetText(Strings::ToQString(Strings::Implode(anime.GetTitleSynonyms(), ", "))); + + /* details */ + QString details_data; + QTextStream details_data_s(&details_data); + details_data_s << Translate::ToString(anime.GetFormat()).c_str() << "\n" + << anime.GetEpisodes() << "\n" + << Translate::ToString(anime.GetUserStatus()).c_str() << "\n" + << Translate::ToString(anime.GetSeason()).c_str() << " " << anime.GetAirDate().GetYear() << "\n" + << Strings::Implode(anime.GetGenres(), ", ").c_str() << "\n" + << anime.GetAudienceScore() << "%"; + _details->GetParagraph()->SetText(Strings::ToQString(Strings::Implode(anime.GetTitleSynonyms(), ", "))); + layout->addWidget(); + + _synopsis->GetParagraph()->SetText(Strings::ToQString(Strings::Implode(anime.GetTitleSynonyms(), ", "))); + layout->addWidget(synopsis); +} + +#include "gui/widgets/moc_anime_info.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/widgets/anime_info.cpp --- a/src/gui/widgets/anime_info.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,48 +0,0 @@ -#include "gui/widgets/anime_info.h" -#include "core/anime.h" -#include "core/strings.h" -#include "gui/translate/anime.h" -#include "gui/widgets/text.h" -#include -#include - -AnimeInfoWidget::AnimeInfoWidget(QWidget* parent) : QWidget(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); - - _title.reset(new TextWidgets::OneLineSection(tr("Alternative titles"), "", this)); - layout->addWidget(_title.get()); - - _details.reset(new TextWidgets::LabelledSection(tr("Details"), new TextWidgets::LabelledSection( - tr("Details"), tr("Type:\nEpisodes:\nStatus:\nSeason:\nGenres:\nScore:"), "", this), "", this)); - layout->addWidget(_details.get()); - - _synopsis.reset(new TextWidgets::LabelledSection(tr("Synopsis"), "", this)); - _synopsis->GetParagraph()->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - layout->addWidget(_synopsis.get()); -} - -AnimeInfoWidget::AnimeInfoWidget(const Anime::Anime& anime, QWidget* parent) : AnimeInfoWidget(parent) { - SetAnime(anime); -} - -AnimeInfoWidget::SetAnime(const Anime::Anime& anime) { - /* alt titles */ - _title->GetParagraph()->SetText(Strings::ToQString(Strings::Implode(anime.GetTitleSynonyms(), ", "))); - - /* details */ - QString details_data; - QTextStream details_data_s(&details_data); - details_data_s << Translate::ToString(anime.GetFormat()).c_str() << "\n" - << anime.GetEpisodes() << "\n" - << Translate::ToString(anime.GetUserStatus()).c_str() << "\n" - << Translate::ToString(anime.GetSeason()).c_str() << " " << anime.GetAirDate().GetYear() << "\n" - << Strings::Implode(anime.GetGenres(), ", ").c_str() << "\n" - << anime.GetAudienceScore() << "%"; - _details->GetParagraph()->SetText(Strings::ToQString(Strings::Implode(anime.GetTitleSynonyms(), ", "))); - layout->addWidget(); - - _synopsis->GetParagraph()->SetText(Strings::ToQString(Strings::Implode(anime.GetTitleSynonyms(), ", "))); - layout->addWidget(synopsis); -} - -#include "gui/widgets/moc_anime_info.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/widgets/clickable_label.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/widgets/clickable_label.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,16 @@ +#include "gui/widgets/clickable_label.h" +/* NOTE: this can likely be moved to poster.cpp, as + it's really the only place this will ever be used */ + +ClickableLabel::ClickableLabel(QWidget* parent) : QLabel(parent) { + setCursor(Qt::PointingHandCursor); +} + +ClickableLabel::~ClickableLabel() { +} + +void ClickableLabel::mousePressEvent(QMouseEvent*) { + emit clicked(); +} + +#include "gui/widgets/moc_clickable_label.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/widgets/clickable_label.cpp --- a/src/gui/widgets/clickable_label.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -#include "gui/widgets/clickable_label.h" -/* NOTE: this can likely be moved to poster.cpp, as - it's really the only place this will ever be used */ - -ClickableLabel::ClickableLabel(QWidget* parent) : QLabel(parent) { - setCursor(Qt::PointingHandCursor); -} - -ClickableLabel::~ClickableLabel() { -} - -void ClickableLabel::mousePressEvent(QMouseEvent*) { - emit clicked(); -} - -#include "gui/widgets/moc_clickable_label.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/widgets/optional_date.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/widgets/optional_date.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,66 @@ +#include "gui/widgets/optional_date.h" +#include "core/date.h" +#include +#include +#include + +OptionalDate::OptionalDate(QWidget* parent) { + OptionalDate(false, parent); +} + +OptionalDate::OptionalDate(bool enabled, QWidget* parent) : QWidget(parent) { + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + _checkbox = new QCheckBox(this); + _checkbox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + + layout->addWidget(_checkbox, 0, Qt::AlignVCenter); + + _dateedit = new QDateEdit(this); + _dateedit->setDisplayFormat("yyyy-MM-dd"); + _dateedit->setCalendarPopup(true); + _dateedit->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + + layout->addWidget(_dateedit); + + SetEnabled(enabled); + connect(_checkbox, &QCheckBox::stateChanged, this, [this](int state) { + SetEnabled(state == Qt::Checked); + emit DataChanged(IsEnabled(), GetDate()); + }); + connect(_dateedit, &QDateEdit::dateChanged, this, [this](QDate) { emit DataChanged(IsEnabled(), GetDate()); }); +} + +void OptionalDate::SetEnabled(bool enabled) { + _checkbox->setCheckState(enabled ? Qt::Checked : Qt::Unchecked); + _dateedit->setEnabled(enabled); +} + +bool OptionalDate::IsEnabled() { + return _dateedit->isEnabled(); +} + +void OptionalDate::SetDate(QDate date) { + _dateedit->setDate(date); +} + +void OptionalDate::SetDate(Date date) { + if (!date.IsValid()) + return; + SetDate(date.GetAsQDate()); +} + +Date OptionalDate::GetDate() { + return Date(_dateedit->date()); +} + +QDateEdit* OptionalDate::GetDateEdit() { + return _dateedit; +} + +QCheckBox* OptionalDate::GetCheckBox() { + return _checkbox; +} + +#include "gui/widgets/moc_optional_date.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/widgets/optional_date.cpp --- a/src/gui/widgets/optional_date.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -#include "gui/widgets/optional_date.h" -#include "core/date.h" -#include -#include -#include - -OptionalDate::OptionalDate(QWidget* parent) { - OptionalDate(false, parent); -} - -OptionalDate::OptionalDate(bool enabled, QWidget* parent) : QWidget(parent) { - QHBoxLayout* layout = new QHBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - - _checkbox = new QCheckBox(this); - _checkbox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); - - layout->addWidget(_checkbox, 0, Qt::AlignVCenter); - - _dateedit = new QDateEdit(this); - _dateedit->setDisplayFormat("yyyy-MM-dd"); - _dateedit->setCalendarPopup(true); - _dateedit->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - - layout->addWidget(_dateedit); - - SetEnabled(enabled); - connect(_checkbox, &QCheckBox::stateChanged, this, [this](int state) { - SetEnabled(state == Qt::Checked); - emit DataChanged(IsEnabled(), GetDate()); - }); - connect(_dateedit, &QDateEdit::dateChanged, this, [this](QDate) { emit DataChanged(IsEnabled(), GetDate()); }); -} - -void OptionalDate::SetEnabled(bool enabled) { - _checkbox->setCheckState(enabled ? Qt::Checked : Qt::Unchecked); - _dateedit->setEnabled(enabled); -} - -bool OptionalDate::IsEnabled() { - return _dateedit->isEnabled(); -} - -void OptionalDate::SetDate(QDate date) { - _dateedit->setDate(date); -} - -void OptionalDate::SetDate(Date date) { - if (!date.IsValid()) - return; - SetDate(date.GetAsQDate()); -} - -Date OptionalDate::GetDate() { - return Date(_dateedit->date()); -} - -QDateEdit* OptionalDate::GetDateEdit() { - return _dateedit; -} - -QCheckBox* OptionalDate::GetCheckBox() { - return _checkbox; -} - -#include "gui/widgets/moc_optional_date.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/widgets/poster.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/widgets/poster.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,60 @@ +#include "gui/widgets/poster.h" +#include "core/anime_db.h" +#include "core/http.h" +#include "core/session.h" +#include "core/strings.h" +#include "gui/widgets/clickable_label.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Poster::Poster(int id, QWidget* parent) : QFrame(parent) { + QHBoxLayout* layout = new QHBoxLayout(this); + layout->setContentsMargins(1, 1, 1, 1); + + setCursor(Qt::PointingHandCursor); + setFixedSize(150, 225); + setFrameShape(QFrame::Box); + setFrameShadow(QFrame::Plain); + + const Anime::Anime& anime = Anime::db.items[id]; + + QThreadPool::globalInstance()->start([this, anime] { + QByteArray ba = HTTP::Get(anime.GetPosterUrl(), {}); + ImageDownloadFinished(ba); + }); + + QPixmap pixmap = QPixmap::fromImage(img); + + label = new ClickableLabel(this); + label->setAlignment(Qt::AlignCenter); + connect(label, &ClickableLabel::clicked, this, + [anime] { QDesktopServices::openUrl(Strings::ToQString(anime.GetServiceUrl())); }); + layout->addWidget(label); +} + +void Poster::ImageDownloadFinished(QByteArray arr) { + img.loadFromData(arr); + RenderToLabel(); +} + +void Poster::RenderToLabel() { + QPixmap pixmap = QPixmap::fromImage(img); + if (pixmap.isNull()) + return; + label->setPixmap(pixmap.scaled(label->size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); +} + +void Poster::resizeEvent(QResizeEvent*) { + RenderToLabel(); +} + +#include "gui/widgets/moc_poster.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/widgets/poster.cpp --- a/src/gui/widgets/poster.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -#include "gui/widgets/poster.h" -#include "core/anime_db.h" -#include "core/http.h" -#include "core/session.h" -#include "core/strings.h" -#include "gui/widgets/clickable_label.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -Poster::Poster(int id, QWidget* parent) : QFrame(parent) { - QHBoxLayout* layout = new QHBoxLayout(this); - layout->setContentsMargins(1, 1, 1, 1); - - setCursor(Qt::PointingHandCursor); - setFixedSize(150, 225); - setFrameShape(QFrame::Box); - setFrameShadow(QFrame::Plain); - - const Anime::Anime& anime = Anime::db.items[id]; - - QThreadPool::globalInstance()->start([this, anime] { - QByteArray ba = HTTP::Get(anime.GetPosterUrl(), {}); - ImageDownloadFinished(ba); - }); - - QPixmap pixmap = QPixmap::fromImage(img); - - label = new ClickableLabel(this); - label->setAlignment(Qt::AlignCenter); - connect(label, &ClickableLabel::clicked, this, - [anime] { QDesktopServices::openUrl(Strings::ToQString(anime.GetServiceUrl())); }); - layout->addWidget(label); -} - -void Poster::ImageDownloadFinished(QByteArray arr) { - img.loadFromData(arr); - RenderToLabel(); -} - -void Poster::RenderToLabel() { - QPixmap pixmap = QPixmap::fromImage(img); - if (pixmap.isNull()) - return; - label->setPixmap(pixmap.scaled(label->size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); -} - -void Poster::resizeEvent(QResizeEvent*) { - RenderToLabel(); -} - -#include "gui/widgets/moc_poster.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/widgets/sidebar.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/widgets/sidebar.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,101 @@ +#include "gui/widgets/sidebar.h" +#include +#include +#include +#include + +SideBar::SideBar(QWidget* parent) : QListWidget(parent) { + setFrameShape(QFrame::NoFrame); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setSelectionMode(QAbstractItemView::SingleSelection); + setSelectionBehavior(QAbstractItemView::SelectItems); + setMouseTracking(true); + /* FIXME: is there an easy way to do this with palettes? */ + setStyleSheet("QListWidget::item:disabled { background: transparent }"); + + SetBackgroundColor(Qt::transparent); + + connect(this, &QListWidget::currentRowChanged, this, + [this](int index) { emit CurrentItemChanged(RemoveSeparatorsFromIndex(index)); }); +} + +void SideBar::SetCurrentItem(int index) { + setCurrentRow(AddSeparatorsToIndex(index)); +} + +void SideBar::SetBackgroundColor(QColor color) { + viewport()->setAutoFillBackground(color != Qt::transparent); + QPalette pal(palette()); + pal.setColor(QPalette::Window, color); + setPalette(pal); +} + +QListWidgetItem* SideBar::AddItem(QString name, QIcon icon) { + QListWidgetItem* item = new QListWidgetItem(this); + item->setText(name); + if (!icon.isNull()) + item->setIcon(icon); + return item; +} + +QIcon SideBar::CreateIcon(const char* file) { + QPixmap pixmap(file, "PNG"); + QIcon result; + result.addPixmap(pixmap, QIcon::Normal); + result.addPixmap(pixmap, QIcon::Selected); + return result; +} + +QListWidgetItem* SideBar::AddSeparator() { + QListWidgetItem* item = new QListWidgetItem(this); + QFrame* line = new QFrame(this); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + line->setMouseTracking(true); + line->setEnabled(false); + + setItemWidget(item, line); + item->setFlags(Qt::NoItemFlags); + item->setBackground(QBrush(Qt::transparent)); + return item; +} + +int SideBar::AddSeparatorsToIndex(int index) { + int i, j; + for (i = 0, j = 0; i < index;) { + i++; + if (IndexIsSeparator(indexFromItem(item(i)))) + j++; + } + return i + j; +} + +int SideBar::RemoveSeparatorsFromIndex(int index) { + int i, j; + for (i = 0, j = 0; i < index; i++) { + if (!IndexIsSeparator(indexFromItem(item(i)))) + j++; + } + return j; +} + +bool SideBar::IndexIsSeparator(QModelIndex index) const { + return !(index.isValid() && index.flags() & Qt::ItemIsEnabled); +} + +QItemSelectionModel::SelectionFlags SideBar::selectionCommand(const QModelIndex& index, const QEvent*) const { + if (IndexIsSeparator(index)) + return QItemSelectionModel::NoUpdate; + return QItemSelectionModel::ClearAndSelect; +} + +void SideBar::mouseMoveEvent(QMouseEvent* event) { + if (!IndexIsSeparator(indexAt(event->pos()))) + setCursor(Qt::PointingHandCursor); + else + unsetCursor(); + QListView::mouseMoveEvent(event); +} + +#include "gui/widgets/moc_sidebar.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/widgets/sidebar.cpp --- a/src/gui/widgets/sidebar.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,101 +0,0 @@ -#include "gui/widgets/sidebar.h" -#include -#include -#include -#include - -SideBar::SideBar(QWidget* parent) : QListWidget(parent) { - setFrameShape(QFrame::NoFrame); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setSelectionMode(QAbstractItemView::SingleSelection); - setSelectionBehavior(QAbstractItemView::SelectItems); - setMouseTracking(true); - /* FIXME: is there an easy way to do this with palettes? */ - setStyleSheet("QListWidget::item:disabled { background: transparent }"); - - SetBackgroundColor(Qt::transparent); - - connect(this, &QListWidget::currentRowChanged, this, - [this](int index) { emit CurrentItemChanged(RemoveSeparatorsFromIndex(index)); }); -} - -void SideBar::SetCurrentItem(int index) { - setCurrentRow(AddSeparatorsToIndex(index)); -} - -void SideBar::SetBackgroundColor(QColor color) { - viewport()->setAutoFillBackground(color != Qt::transparent); - QPalette pal(palette()); - pal.setColor(QPalette::Window, color); - setPalette(pal); -} - -QListWidgetItem* SideBar::AddItem(QString name, QIcon icon) { - QListWidgetItem* item = new QListWidgetItem(this); - item->setText(name); - if (!icon.isNull()) - item->setIcon(icon); - return item; -} - -QIcon SideBar::CreateIcon(const char* file) { - QPixmap pixmap(file, "PNG"); - QIcon result; - result.addPixmap(pixmap, QIcon::Normal); - result.addPixmap(pixmap, QIcon::Selected); - return result; -} - -QListWidgetItem* SideBar::AddSeparator() { - QListWidgetItem* item = new QListWidgetItem(this); - QFrame* line = new QFrame(this); - line->setFrameShape(QFrame::HLine); - line->setFrameShadow(QFrame::Sunken); - line->setMouseTracking(true); - line->setEnabled(false); - - setItemWidget(item, line); - item->setFlags(Qt::NoItemFlags); - item->setBackground(QBrush(Qt::transparent)); - return item; -} - -int SideBar::AddSeparatorsToIndex(int index) { - int i, j; - for (i = 0, j = 0; i < index;) { - i++; - if (IndexIsSeparator(indexFromItem(item(i)))) - j++; - } - return i + j; -} - -int SideBar::RemoveSeparatorsFromIndex(int index) { - int i, j; - for (i = 0, j = 0; i < index; i++) { - if (!IndexIsSeparator(indexFromItem(item(i)))) - j++; - } - return j; -} - -bool SideBar::IndexIsSeparator(QModelIndex index) const { - return !(index.isValid() && index.flags() & Qt::ItemIsEnabled); -} - -QItemSelectionModel::SelectionFlags SideBar::selectionCommand(const QModelIndex& index, const QEvent*) const { - if (IndexIsSeparator(index)) - return QItemSelectionModel::NoUpdate; - return QItemSelectionModel::ClearAndSelect; -} - -void SideBar::mouseMoveEvent(QMouseEvent* event) { - if (!IndexIsSeparator(indexAt(event->pos()))) - setCursor(Qt::PointingHandCursor); - else - unsetCursor(); - QListView::mouseMoveEvent(event); -} - -#include "gui/widgets/moc_sidebar.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/widgets/text.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/widgets/text.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,237 @@ +#include "gui/widgets/text.h" +#include "core/session.h" +#include +#include +#include +#include +#include + +namespace TextWidgets { + +Header::Header(QString title, QWidget* parent) : QWidget(parent) { + QVBoxLayout* layout = new QVBoxLayout(this); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + + static_text_title = new QLabel(title, this); + static_text_title->setTextFormat(Qt::PlainText); + QFont font = static_text_title->font(); + font.setWeight(QFont::Bold); + static_text_title->setFont(font); + + static_text_line = new QFrame(this); + static_text_line->setFrameShape(QFrame::HLine); + static_text_line->setFrameShadow(QFrame::Sunken); + static_text_line->setFixedHeight(2); + + layout->addWidget(static_text_title); + layout->addWidget(static_text_line); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); +} + +void Header::SetText(QString text) { + static_text_title->setText(text); +} + +/* inherits QPlainTextEdit and gives a much more reasonable minimum size */ +Paragraph::Paragraph(QString text, QWidget* parent) : QPlainTextEdit(text, parent) { + setTextInteractionFlags(Qt::TextBrowserInteraction); + setFrameShape(QFrame::NoFrame); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + QPalette pal; + pal.setColor(QPalette::Window, Qt::transparent); + setPalette(pal); + + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); +} + +void Paragraph::SetText(QString text) { + QTextDocument* document = new QTextDocument(this); + document->setDocumentLayout(new QPlainTextDocumentLayout(document)); + document->setPlainText(text); + setDocument(document); +} + +/* highly based upon... some stackoverflow answer for PyQt */ +QSize Paragraph::minimumSizeHint() const { + return QSize(0, 0); +} + +QSize Paragraph::sizeHint() const { + QTextDocument* doc = document(); + doc->adjustSize(); + long h = 0; + for (QTextBlock line = doc->begin(); line != doc->end(); line = line.next()) { + h += doc->documentLayout()->blockBoundingRect(line).height(); + } + return QSize(doc->size().width(), h); +} + +/* Equivalent to Paragraph(), but is only capable of showing one line. Only + exists because sometimes with SelectableSection it will let you go + out of bounds */ +Line::Line(QString text, QWidget* parent) : QLineEdit(text, parent) { + setFrame(false); + setReadOnly(true); + setCursorPosition(0); /* displays left text first */ + + QPalette pal; + pal.setColor(QPalette::Window, Qt::transparent); + setPalette(pal); + + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); +} + +Title::Title(QString title, QWidget* parent) : Line(title, parent) { + QFont fnt(font()); + fnt.setPixelSize(16); + setFont(fnt); + + QPalette pal(palette()); + pal.setColor(QPalette::Window, Qt::transparent); + pal.setColor(QPalette::Text, QColor(0x00, 0x33, 0x99)); + setPalette(pal); +} + +Section::Section(QString title, QString data, QWidget* parent) : QWidget(parent) { + QVBoxLayout* layout = new QVBoxLayout(this); + + header = new Header(title, this); + + QWidget* content = new QWidget(this); + QHBoxLayout* content_layout = new QHBoxLayout(content); + + paragraph = new Paragraph(data, this); + paragraph->setTextInteractionFlags(Qt::NoTextInteraction); + paragraph->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents); + paragraph->setWordWrapMode(QTextOption::NoWrap); + + content_layout->addWidget(paragraph); + content_layout->setSpacing(0); + content_layout->setContentsMargins(0, 0, 0, 0); + content->setContentsMargins(12, 0, 0, 0); + + layout->addWidget(header); + layout->addWidget(paragraph); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); +} + +Header* Section::GetHeader() { + return header; +} + +Paragraph* Section::GetParagraph() { + return paragraph; +} + +LabelledSection::LabelledSection(QString title, QString label, QString data, QWidget* parent) : QWidget(parent) { + QVBoxLayout* layout = new QVBoxLayout(this); + + header = new Header(title, this); + + // this is not accessible from the object because there's really + // no reason to make it accessible... + QWidget* content = new QWidget(this); + content->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + + labels = new Paragraph(label, this); + labels->setTextInteractionFlags(Qt::NoTextInteraction); + labels->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents); + labels->setWordWrapMode(QTextOption::NoWrap); + labels->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + + paragraph = new Paragraph(data, this); + paragraph->setTextInteractionFlags(Qt::NoTextInteraction); + paragraph->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents); + paragraph->setWordWrapMode(QTextOption::NoWrap); + + QHBoxLayout* content_layout = new QHBoxLayout(content); + content_layout->addWidget(labels, 0, Qt::AlignTop); + content_layout->addWidget(paragraph, 0, Qt::AlignTop); + content_layout->setSpacing(20); + content_layout->setContentsMargins(0, 0, 0, 0); + + content->setContentsMargins(12, 0, 0, 0); + + layout->addWidget(header); + layout->addWidget(content); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); +} + +Header* LabelledSection::GetHeader() { + return header; +} + +Paragraph* LabelledSection::GetLabels() { + return labels; +} + +Paragraph* LabelledSection::GetParagraph() { + return paragraph; +} + +SelectableSection::SelectableSection(QString title, QString data, QWidget* parent) : QWidget(parent) { + QVBoxLayout* layout = new QVBoxLayout(this); + + header = new Header(title, this); + + QWidget* content = new QWidget(this); + QHBoxLayout* content_layout = new QHBoxLayout(content); + + paragraph = new Paragraph(data, content); + + content_layout->addWidget(paragraph); + content_layout->setSpacing(0); + content_layout->setContentsMargins(0, 0, 0, 0); + content->setContentsMargins(12, 0, 0, 0); + + layout->addWidget(header); + layout->addWidget(content); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); +} + +Header* SelectableSection::GetHeader() { + return header; +} + +Paragraph* SelectableSection::GetParagraph() { + return paragraph; +} + +OneLineSection::OneLineSection(QString title, QString text, QWidget* parent) : QWidget(parent) { + QVBoxLayout* layout = new QVBoxLayout(this); + + header = new Header(title, this); + + QWidget* content = new QWidget(this); + QHBoxLayout* content_layout = new QHBoxLayout(content); + + line = new Line(text, content); + + content_layout->addWidget(line); + content_layout->setSpacing(0); + content_layout->setContentsMargins(0, 0, 0, 0); + content->setContentsMargins(12, 0, 0, 0); + + layout->addWidget(header); + layout->addWidget(content); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); +} + +Header* OneLineSection::GetHeader() { + return header; +} + +Line* OneLineSection::GetLine() { + return line; +} + +} // namespace TextWidgets + +#include "gui/widgets/moc_text.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/widgets/text.cpp --- a/src/gui/widgets/text.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,237 +0,0 @@ -#include "gui/widgets/text.h" -#include "core/session.h" -#include -#include -#include -#include -#include - -namespace TextWidgets { - -Header::Header(QString title, QWidget* parent) : QWidget(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - - static_text_title = new QLabel(title, this); - static_text_title->setTextFormat(Qt::PlainText); - QFont font = static_text_title->font(); - font.setWeight(QFont::Bold); - static_text_title->setFont(font); - - static_text_line = new QFrame(this); - static_text_line->setFrameShape(QFrame::HLine); - static_text_line->setFrameShadow(QFrame::Sunken); - static_text_line->setFixedHeight(2); - - layout->addWidget(static_text_title); - layout->addWidget(static_text_line); - layout->setSpacing(0); - layout->setContentsMargins(0, 0, 0, 0); -} - -void Header::SetText(QString text) { - static_text_title->setText(text); -} - -/* inherits QPlainTextEdit and gives a much more reasonable minimum size */ -Paragraph::Paragraph(QString text, QWidget* parent) : QPlainTextEdit(text, parent) { - setTextInteractionFlags(Qt::TextBrowserInteraction); - setFrameShape(QFrame::NoFrame); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - - QPalette pal; - pal.setColor(QPalette::Window, Qt::transparent); - setPalette(pal); - - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); -} - -void Paragraph::SetText(QString text) { - QTextDocument* document = new QTextDocument(this); - document->setDocumentLayout(new QPlainTextDocumentLayout(document)); - document->setPlainText(text); - setDocument(document); -} - -/* highly based upon... some stackoverflow answer for PyQt */ -QSize Paragraph::minimumSizeHint() const { - return QSize(0, 0); -} - -QSize Paragraph::sizeHint() const { - QTextDocument* doc = document(); - doc->adjustSize(); - long h = 0; - for (QTextBlock line = doc->begin(); line != doc->end(); line = line.next()) { - h += doc->documentLayout()->blockBoundingRect(line).height(); - } - return QSize(doc->size().width(), h); -} - -/* Equivalent to Paragraph(), but is only capable of showing one line. Only - exists because sometimes with SelectableSection it will let you go - out of bounds */ -Line::Line(QString text, QWidget* parent) : QLineEdit(text, parent) { - setFrame(false); - setReadOnly(true); - setCursorPosition(0); /* displays left text first */ - - QPalette pal; - pal.setColor(QPalette::Window, Qt::transparent); - setPalette(pal); - - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); -} - -Title::Title(QString title, QWidget* parent) : Line(title, parent) { - QFont fnt(font()); - fnt.setPixelSize(16); - setFont(fnt); - - QPalette pal(palette()); - pal.setColor(QPalette::Window, Qt::transparent); - pal.setColor(QPalette::Text, QColor(0x00, 0x33, 0x99)); - setPalette(pal); -} - -Section::Section(QString title, QString data, QWidget* parent) : QWidget(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); - - header = new Header(title, this); - - QWidget* content = new QWidget(this); - QHBoxLayout* content_layout = new QHBoxLayout(content); - - paragraph = new Paragraph(data, this); - paragraph->setTextInteractionFlags(Qt::NoTextInteraction); - paragraph->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents); - paragraph->setWordWrapMode(QTextOption::NoWrap); - - content_layout->addWidget(paragraph); - content_layout->setSpacing(0); - content_layout->setContentsMargins(0, 0, 0, 0); - content->setContentsMargins(12, 0, 0, 0); - - layout->addWidget(header); - layout->addWidget(paragraph); - layout->setSpacing(0); - layout->setContentsMargins(0, 0, 0, 0); -} - -Header* Section::GetHeader() { - return header; -} - -Paragraph* Section::GetParagraph() { - return paragraph; -} - -LabelledSection::LabelledSection(QString title, QString label, QString data, QWidget* parent) : QWidget(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); - - header = new Header(title, this); - - // this is not accessible from the object because there's really - // no reason to make it accessible... - QWidget* content = new QWidget(this); - content->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - - labels = new Paragraph(label, this); - labels->setTextInteractionFlags(Qt::NoTextInteraction); - labels->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents); - labels->setWordWrapMode(QTextOption::NoWrap); - labels->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - - paragraph = new Paragraph(data, this); - paragraph->setTextInteractionFlags(Qt::NoTextInteraction); - paragraph->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents); - paragraph->setWordWrapMode(QTextOption::NoWrap); - - QHBoxLayout* content_layout = new QHBoxLayout(content); - content_layout->addWidget(labels, 0, Qt::AlignTop); - content_layout->addWidget(paragraph, 0, Qt::AlignTop); - content_layout->setSpacing(20); - content_layout->setContentsMargins(0, 0, 0, 0); - - content->setContentsMargins(12, 0, 0, 0); - - layout->addWidget(header); - layout->addWidget(content); - layout->setSpacing(0); - layout->setContentsMargins(0, 0, 0, 0); -} - -Header* LabelledSection::GetHeader() { - return header; -} - -Paragraph* LabelledSection::GetLabels() { - return labels; -} - -Paragraph* LabelledSection::GetParagraph() { - return paragraph; -} - -SelectableSection::SelectableSection(QString title, QString data, QWidget* parent) : QWidget(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); - - header = new Header(title, this); - - QWidget* content = new QWidget(this); - QHBoxLayout* content_layout = new QHBoxLayout(content); - - paragraph = new Paragraph(data, content); - - content_layout->addWidget(paragraph); - content_layout->setSpacing(0); - content_layout->setContentsMargins(0, 0, 0, 0); - content->setContentsMargins(12, 0, 0, 0); - - layout->addWidget(header); - layout->addWidget(content); - layout->setSpacing(0); - layout->setContentsMargins(0, 0, 0, 0); -} - -Header* SelectableSection::GetHeader() { - return header; -} - -Paragraph* SelectableSection::GetParagraph() { - return paragraph; -} - -OneLineSection::OneLineSection(QString title, QString text, QWidget* parent) : QWidget(parent) { - QVBoxLayout* layout = new QVBoxLayout(this); - - header = new Header(title, this); - - QWidget* content = new QWidget(this); - QHBoxLayout* content_layout = new QHBoxLayout(content); - - line = new Line(text, content); - - content_layout->addWidget(line); - content_layout->setSpacing(0); - content_layout->setContentsMargins(0, 0, 0, 0); - content->setContentsMargins(12, 0, 0, 0); - - layout->addWidget(header); - layout->addWidget(content); - layout->setSpacing(0); - layout->setContentsMargins(0, 0, 0, 0); -} - -Header* OneLineSection::GetHeader() { - return header; -} - -Line* OneLineSection::GetLine() { - return line; -} - -} // namespace TextWidgets - -#include "gui/widgets/moc_text.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/window.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/window.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,281 @@ +#include "gui/window.h" +#include "core/anime_db.h" +#include "core/config.h" +#include "core/session.h" +#include "core/strings.h" +#include "gui/dark_theme.h" +#include "gui/dialog/about.h" +#include "gui/dialog/settings.h" +#include "gui/pages/anime_list.h" +#include "gui/pages/history.h" +#include "gui/pages/now_playing.h" +#include "gui/pages/search.h" +#include "gui/pages/seasons.h" +#include "gui/pages/statistics.h" +#include "gui/pages/torrents.h" +#include "gui/widgets/sidebar.h" +#include "services/services.h" +#include "track/media.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if MACOSX +# include "sys/osx/dark_theme.h" +#elif defined(WIN32) +# include "sys/win32/dark_theme.h" +#endif + +enum class Pages { + NOW_PLAYING, + + ANIME_LIST, + HISTORY, + STATISTICS, + + SEARCH, + SEASONS, + TORRENTS +}; + +static void AsyncSynchronize(QStackedWidget* stack) { + QThreadPool::globalInstance()->start([stack] { + Services::Synchronize(); + reinterpret_cast(stack->widget(static_cast(Pages::ANIME_LIST)))->Refresh(); + }); +} + +MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { + main_widget = new QWidget(parent); + + sidebar = new SideBar(main_widget); + sidebar->AddItem(tr("Now Playing"), SideBar::CreateIcon(":/icons/16x16/film.png")); + sidebar->AddSeparator(); + sidebar->AddItem(tr("Anime List"), SideBar::CreateIcon(":/icons/16x16/document-list.png")); + sidebar->AddItem(tr("History"), SideBar::CreateIcon(":/icons/16x16/clock-history-frame.png")); + sidebar->AddItem(tr("Statistics"), SideBar::CreateIcon(":/icons/16x16/chart.png")); + sidebar->AddSeparator(); + sidebar->AddItem(tr("Search"), SideBar::CreateIcon(":/icons/16x16/magnifier.png")); + sidebar->AddItem(tr("Seasons"), SideBar::CreateIcon(":/icons/16x16/calendar.png")); + sidebar->AddItem(tr("Torrents"), SideBar::CreateIcon(":/icons/16x16/feed.png")); + sidebar->setFixedWidth(128); + sidebar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + + stack = new QStackedWidget(main_widget); + stack->addWidget(new NowPlayingPage(main_widget)); + stack->addWidget(new AnimeListPage(main_widget)); + stack->addWidget(new HistoryPage(main_widget)); + stack->addWidget(new StatisticsPage(main_widget)); + stack->addWidget(new SearchPage(main_widget)); + stack->addWidget(new SeasonsPage(main_widget)); + stack->addWidget(new TorrentsPage(main_widget)); + + connect(sidebar, &SideBar::CurrentItemChanged, stack, &QStackedWidget::setCurrentIndex); + sidebar->SetCurrentItem(static_cast(Pages::ANIME_LIST)); + + QHBoxLayout* layout = new QHBoxLayout(main_widget); + layout->addWidget(sidebar); + layout->addWidget(stack); + setCentralWidget(main_widget); + + CreateBars(); + + QTimer* timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, [this] { + NowPlayingPage* page = reinterpret_cast(stack->widget(static_cast(Pages::NOW_PLAYING))); + + Filesystem::Path p = Track::Media::GetCurrentPlaying(); + std::unordered_map elements = Track::Media::GetFileElements(p); + int id = Anime::db.GetAnimeFromTitle(elements["title"]); + if (id == 0) { + page->SetDefault(); + return; + } + + page->SetPlaying(id, elements); + }); + timer->start(5000); + + DarkTheme::SetTheme(session.config.theme); +} + +void MainWindow::CreateBars() { + /* Menu Bar */ + QAction* action; + QMenuBar* menubar = new QMenuBar(this); + QMenu* menu = menubar->addMenu(tr("&File")); + + QMenu* submenu = menu->addMenu(tr("&Library folders")); + action = submenu->addAction(tr("&Add new folder...")); + + action = menu->addAction(tr("&Scan available episodes")); + + menu->addSeparator(); + + action = menu->addAction(tr("Play &next episode")); + action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_N)); + action = menu->addAction(tr("Play &random episode")); + action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_R)); + + menu->addSeparator(); + + action = menu->addAction(tr("E&xit"), qApp, &QApplication::quit); + + menu = menubar->addMenu(tr("&Services")); + action = menu->addAction(tr("Synchronize &list"), [this] { AsyncSynchronize(stack); }); + action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_S)); + + menu->addSeparator(); + + submenu = menu->addMenu(tr("&AniList")); + action = submenu->addAction(tr("Go to my &profile")); + action = submenu->addAction(tr("Go to my &stats")); + + submenu = menu->addMenu(tr("&Kitsu")); + action = submenu->addAction(tr("Go to my &feed")); + action = submenu->addAction(tr("Go to my &library")); + action = submenu->addAction(tr("Go to my &profile")); + + submenu = menu->addMenu(tr("&MyAnimeList")); + action = submenu->addAction(tr("Go to my p&anel")); + action = submenu->addAction(tr("Go to my &profile")); + action = submenu->addAction(tr("Go to my &history")); + + menu = menubar->addMenu(tr("&Tools")); + submenu = menu->addMenu(tr("&Export anime list")); + action = submenu->addAction(tr("Export as &Markdown...")); + action = submenu->addAction(tr("Export as MyAnimeList &XML...")); + + menu->addSeparator(); + + action = menu->addAction(tr("Enable anime &recognition")); + action->setCheckable(true); + action = menu->addAction(tr("Enable auto &sharing")); + action->setCheckable(true); + action = menu->addAction(tr("Enable &auto synchronization")); + action->setCheckable(true); + + menu->addSeparator(); + + action = menu->addAction(tr("&Settings"), [this] { + SettingsDialog dialog(this); + dialog.exec(); + }); + action->setMenuRole(QAction::PreferencesRole); + + menu = menubar->addMenu(tr("&View")); + + std::map page_to_index_map = {}; + + QActionGroup* pages_group = new QActionGroup(this); + pages_group->setExclusive(true); + + action = pages_group->addAction(menu->addAction(tr("&Now Playing"))); + action->setCheckable(true); + page_to_index_map[action] = 0; + + action = pages_group->addAction(menu->addAction(tr("&Anime List"))); + page_to_index_map[action] = 1; + action->setCheckable(true); + action->setChecked(true); + + action = pages_group->addAction(menu->addAction(tr("&History"))); + action->setCheckable(true); + page_to_index_map[action] = 2; + + action = pages_group->addAction(menu->addAction(tr("&Statistics"))); + action->setCheckable(true); + page_to_index_map[action] = 3; + + action = pages_group->addAction(menu->addAction(tr("S&earch"))); + action->setCheckable(true); + page_to_index_map[action] = 4; + + action = pages_group->addAction(menu->addAction(tr("Se&asons"))); + action->setCheckable(true); + page_to_index_map[action] = 5; + + action = pages_group->addAction(menu->addAction(tr("&Torrents"))); + action->setCheckable(true); + page_to_index_map[action] = 6; + + connect(sidebar, &SideBar::CurrentItemChanged, this, + [pages_group](int index) { pages_group->actions()[index]->setChecked(true); }); + + connect(pages_group, &QActionGroup::triggered, this, + [this, page_to_index_map](QAction* action) { sidebar->SetCurrentItem(page_to_index_map.at(action)); }); + + menu->addSeparator(); + menu->addAction(tr("Show sidebar")); + + menu = menubar->addMenu(tr("&Help")); + action = menu->addAction(tr("&About Minori"), this, [this] { + AboutWindow dialog(this); + dialog.exec(); + }); + action = menu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); + action->setMenuRole(QAction::AboutQtRole); + + setMenuBar(menubar); + + /* Toolbar */ + /* remove old toolbar(s) */ + QList toolbars = findChildren(); + for (auto& t : toolbars) + removeToolBar(t); + + QToolBar* toolbar = new QToolBar(this); + toolbar->addAction(QIcon(":/icons/24x24/arrow-circle-double-135.png"), tr("&Synchronize"), + [this] { AsyncSynchronize(stack); }); + toolbar->addSeparator(); + + QToolButton* button = new QToolButton(toolbar); + + menu = new QMenu(button); + action = menu->addAction(tr("Add new folder...")); + + button->setMenu(menu); + button->setIcon(QIcon(":/icons/24x24/folder-open.png")); + button->setPopupMode(QToolButton::InstantPopup); + toolbar->addWidget(button); + + button = new QToolButton(toolbar); + + menu = new QMenu(button); + action = menu->addAction(tr("Placeholder")); + + button->setMenu(menu); + button->setIcon(QIcon(":/icons/24x24/application-export.png")); + button->setPopupMode(QToolButton::InstantPopup); + toolbar->addWidget(button); + + toolbar->addSeparator(); + toolbar->addAction(QIcon(":/icons/24x24/gear.png"), tr("S&ettings"), [this] { + SettingsDialog dialog(this); + dialog.exec(); + }); + addToolBar(toolbar); + +} + +void MainWindow::SetActivePage(QWidget* page) { + this->setCentralWidget(page); +} + +void MainWindow::closeEvent(QCloseEvent* event) { + session.config.Save(); + event->accept(); +} + +#include "gui/moc_window.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/gui/window.cpp --- a/src/gui/window.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,281 +0,0 @@ -#include "gui/window.h" -#include "core/anime_db.h" -#include "core/config.h" -#include "core/session.h" -#include "core/strings.h" -#include "gui/dark_theme.h" -#include "gui/dialog/about.h" -#include "gui/dialog/settings.h" -#include "gui/pages/anime_list.h" -#include "gui/pages/history.h" -#include "gui/pages/now_playing.h" -#include "gui/pages/search.h" -#include "gui/pages/seasons.h" -#include "gui/pages/statistics.h" -#include "gui/pages/torrents.h" -#include "gui/widgets/sidebar.h" -#include "services/services.h" -#include "track/media.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if MACOSX -# include "sys/osx/dark_theme.h" -#elif defined(WIN32) -# include "sys/win32/dark_theme.h" -#endif - -enum class Pages { - NOW_PLAYING, - - ANIME_LIST, - HISTORY, - STATISTICS, - - SEARCH, - SEASONS, - TORRENTS -}; - -static void AsyncSynchronize(QStackedWidget* stack) { - QThreadPool::globalInstance()->start([stack] { - Services::Synchronize(); - reinterpret_cast(stack->widget(static_cast(Pages::ANIME_LIST)))->Refresh(); - }); -} - -MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { - main_widget = new QWidget(parent); - - sidebar = new SideBar(main_widget); - sidebar->AddItem(tr("Now Playing"), SideBar::CreateIcon(":/icons/16x16/film.png")); - sidebar->AddSeparator(); - sidebar->AddItem(tr("Anime List"), SideBar::CreateIcon(":/icons/16x16/document-list.png")); - sidebar->AddItem(tr("History"), SideBar::CreateIcon(":/icons/16x16/clock-history-frame.png")); - sidebar->AddItem(tr("Statistics"), SideBar::CreateIcon(":/icons/16x16/chart.png")); - sidebar->AddSeparator(); - sidebar->AddItem(tr("Search"), SideBar::CreateIcon(":/icons/16x16/magnifier.png")); - sidebar->AddItem(tr("Seasons"), SideBar::CreateIcon(":/icons/16x16/calendar.png")); - sidebar->AddItem(tr("Torrents"), SideBar::CreateIcon(":/icons/16x16/feed.png")); - sidebar->setFixedWidth(128); - sidebar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); - - stack = new QStackedWidget(main_widget); - stack->addWidget(new NowPlayingPage(main_widget)); - stack->addWidget(new AnimeListPage(main_widget)); - stack->addWidget(new HistoryPage(main_widget)); - stack->addWidget(new StatisticsPage(main_widget)); - stack->addWidget(new SearchPage(main_widget)); - stack->addWidget(new SeasonsPage(main_widget)); - stack->addWidget(new TorrentsPage(main_widget)); - - connect(sidebar, &SideBar::CurrentItemChanged, stack, &QStackedWidget::setCurrentIndex); - sidebar->SetCurrentItem(static_cast(Pages::ANIME_LIST)); - - QHBoxLayout* layout = new QHBoxLayout(main_widget); - layout->addWidget(sidebar); - layout->addWidget(stack); - setCentralWidget(main_widget); - - CreateBars(); - - QTimer* timer = new QTimer(this); - connect(timer, &QTimer::timeout, this, [this] { - NowPlayingPage* page = reinterpret_cast(stack->widget(static_cast(Pages::NOW_PLAYING))); - - Filesystem::Path p = Track::Media::GetCurrentPlaying(); - std::unordered_map elements = Track::Media::GetFileElements(p); - int id = Anime::db.GetAnimeFromTitle(elements["title"]); - if (id == 0) { - page->SetDefault(); - return; - } - - page->SetPlaying(id, elements); - }); - timer->start(5000); - - DarkTheme::SetTheme(session.config.theme); -} - -void MainWindow::CreateBars() { - /* Menu Bar */ - QAction* action; - QMenuBar* menubar = new QMenuBar(this); - QMenu* menu = menubar->addMenu(tr("&File")); - - QMenu* submenu = menu->addMenu(tr("&Library folders")); - action = submenu->addAction(tr("&Add new folder...")); - - action = menu->addAction(tr("&Scan available episodes")); - - menu->addSeparator(); - - action = menu->addAction(tr("Play &next episode")); - action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_N)); - action = menu->addAction(tr("Play &random episode")); - action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_R)); - - menu->addSeparator(); - - action = menu->addAction(tr("E&xit"), qApp, &QApplication::quit); - - menu = menubar->addMenu(tr("&Services")); - action = menu->addAction(tr("Synchronize &list"), [this] { AsyncSynchronize(stack); }); - action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_S)); - - menu->addSeparator(); - - submenu = menu->addMenu(tr("&AniList")); - action = submenu->addAction(tr("Go to my &profile")); - action = submenu->addAction(tr("Go to my &stats")); - - submenu = menu->addMenu(tr("&Kitsu")); - action = submenu->addAction(tr("Go to my &feed")); - action = submenu->addAction(tr("Go to my &library")); - action = submenu->addAction(tr("Go to my &profile")); - - submenu = menu->addMenu(tr("&MyAnimeList")); - action = submenu->addAction(tr("Go to my p&anel")); - action = submenu->addAction(tr("Go to my &profile")); - action = submenu->addAction(tr("Go to my &history")); - - menu = menubar->addMenu(tr("&Tools")); - submenu = menu->addMenu(tr("&Export anime list")); - action = submenu->addAction(tr("Export as &Markdown...")); - action = submenu->addAction(tr("Export as MyAnimeList &XML...")); - - menu->addSeparator(); - - action = menu->addAction(tr("Enable anime &recognition")); - action->setCheckable(true); - action = menu->addAction(tr("Enable auto &sharing")); - action->setCheckable(true); - action = menu->addAction(tr("Enable &auto synchronization")); - action->setCheckable(true); - - menu->addSeparator(); - - action = menu->addAction(tr("&Settings"), [this] { - SettingsDialog dialog(this); - dialog.exec(); - }); - action->setMenuRole(QAction::PreferencesRole); - - menu = menubar->addMenu(tr("&View")); - - std::map page_to_index_map = {}; - - QActionGroup* pages_group = new QActionGroup(this); - pages_group->setExclusive(true); - - action = pages_group->addAction(menu->addAction(tr("&Now Playing"))); - action->setCheckable(true); - page_to_index_map[action] = 0; - - action = pages_group->addAction(menu->addAction(tr("&Anime List"))); - page_to_index_map[action] = 1; - action->setCheckable(true); - action->setChecked(true); - - action = pages_group->addAction(menu->addAction(tr("&History"))); - action->setCheckable(true); - page_to_index_map[action] = 2; - - action = pages_group->addAction(menu->addAction(tr("&Statistics"))); - action->setCheckable(true); - page_to_index_map[action] = 3; - - action = pages_group->addAction(menu->addAction(tr("S&earch"))); - action->setCheckable(true); - page_to_index_map[action] = 4; - - action = pages_group->addAction(menu->addAction(tr("Se&asons"))); - action->setCheckable(true); - page_to_index_map[action] = 5; - - action = pages_group->addAction(menu->addAction(tr("&Torrents"))); - action->setCheckable(true); - page_to_index_map[action] = 6; - - connect(sidebar, &SideBar::CurrentItemChanged, this, - [pages_group](int index) { pages_group->actions()[index]->setChecked(true); }); - - connect(pages_group, &QActionGroup::triggered, this, - [this, page_to_index_map](QAction* action) { sidebar->SetCurrentItem(page_to_index_map.at(action)); }); - - menu->addSeparator(); - menu->addAction(tr("Show sidebar")); - - menu = menubar->addMenu(tr("&Help")); - action = menu->addAction(tr("&About Minori"), this, [this] { - AboutWindow dialog(this); - dialog.exec(); - }); - action = menu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); - action->setMenuRole(QAction::AboutQtRole); - - setMenuBar(menubar); - - /* Toolbar */ - /* remove old toolbar(s) */ - QList toolbars = findChildren(); - for (auto& t : toolbars) - removeToolBar(t); - - QToolBar* toolbar = new QToolBar(this); - toolbar->addAction(QIcon(":/icons/24x24/arrow-circle-double-135.png"), tr("&Synchronize"), - [this] { AsyncSynchronize(stack); }); - toolbar->addSeparator(); - - QToolButton* button = new QToolButton(toolbar); - - menu = new QMenu(button); - action = menu->addAction(tr("Add new folder...")); - - button->setMenu(menu); - button->setIcon(QIcon(":/icons/24x24/folder-open.png")); - button->setPopupMode(QToolButton::InstantPopup); - toolbar->addWidget(button); - - button = new QToolButton(toolbar); - - menu = new QMenu(button); - action = menu->addAction(tr("Placeholder")); - - button->setMenu(menu); - button->setIcon(QIcon(":/icons/24x24/application-export.png")); - button->setPopupMode(QToolButton::InstantPopup); - toolbar->addWidget(button); - - toolbar->addSeparator(); - toolbar->addAction(QIcon(":/icons/24x24/gear.png"), tr("S&ettings"), [this] { - SettingsDialog dialog(this); - dialog.exec(); - }); - addToolBar(toolbar); - -} - -void MainWindow::SetActivePage(QWidget* page) { - this->setCentralWidget(page); -} - -void MainWindow::closeEvent(QCloseEvent* event) { - session.config.Save(); - event->accept(); -} - -#include "gui/moc_window.cpp" diff -r 825506f0e221 -r 9b2b41f83a5e src/main.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,20 @@ +#include "core/session.h" +#include "gui/window.h" +#include +#include + +Session session; + +int main(int argc, char** argv) { + QApplication app(argc, argv); + + session.config.Load(); + + MainWindow window; + + window.resize(941, 750); + window.setWindowTitle("Minori"); + window.show(); + + return app.exec(); +} diff -r 825506f0e221 -r 9b2b41f83a5e src/main.cpp --- a/src/main.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -#include "core/session.h" -#include "gui/window.h" -#include -#include - -Session session; - -int main(int argc, char** argv) { - QApplication app(argc, argv); - - session.config.Load(); - - MainWindow window; - - window.resize(941, 750); - window.setWindowTitle("Minori"); - window.show(); - - return app.exec(); -} diff -r 825506f0e221 -r 9b2b41f83a5e src/services/anilist.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/services/anilist.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,320 @@ +#include "services/anilist.h" +#include "core/anime.h" +#include "core/anime_db.h" +#include "core/config.h" +#include "core/http.h" +#include "core/json.h" +#include "core/session.h" +#include "core/strings.h" +#include "gui/translate/anilist.h" +#include +#include +#include +#include +#include +#include +#include +#include +#define CLIENT_ID "13706" + +using namespace nlohmann::literals::json_literals; + +namespace Services { +namespace AniList { + +class Account { + public: + 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.config.anilist.user_id; } + void SetUserId(const int id) { session.config.anilist.user_id = id; } + + 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; + +std::string SendRequest(std::string data) { + std::vector headers = {"Authorization: Bearer " + account.AuthToken(), "Accept: application/json", + "Content-Type: application/json"}; + return Strings::ToUtf8String(HTTP::Post("https://graphql.anilist.co", data, headers)); +} + +void ParseListStatus(std::string status, Anime::Anime& anime) { + std::unordered_map map = { + {"CURRENT", Anime::ListStatus::CURRENT }, + {"PLANNING", Anime::ListStatus::PLANNING }, + {"COMPLETED", Anime::ListStatus::COMPLETED}, + {"DROPPED", Anime::ListStatus::DROPPED }, + {"PAUSED", Anime::ListStatus::PAUSED } + }; + + if (status == "REPEATING") { + anime.SetUserIsRewatching(true); + anime.SetUserStatus(Anime::ListStatus::CURRENT); + return; + } + + if (map.find(status) == map.end()) { + anime.SetUserStatus(Anime::ListStatus::NOT_IN_LIST); + return; + } + + anime.SetUserStatus(map[status]); +} + +std::string ListStatusToString(const Anime::Anime& anime) { + if (anime.GetUserIsRewatching()) + return "REWATCHING"; + + switch (anime.GetUserStatus()) { + case Anime::ListStatus::PLANNING: return "PLANNING"; + case Anime::ListStatus::COMPLETED: return "COMPLETED"; + case Anime::ListStatus::DROPPED: return "DROPPED"; + case Anime::ListStatus::PAUSED: return "PAUSED"; + default: break; + } + return "CURRENT"; +} + +Date ParseDate(const nlohmann::json& json) { + Date date; + /* JSON for Modern C++ warns here. I'm not too sure why, this code works when I set the + standard to C++17 :/ */ + if (json.contains("/year"_json_pointer) && json.at("/year"_json_pointer).is_number()) + date.SetYear(JSON::GetInt(json, "/year"_json_pointer)); + else + date.VoidYear(); + + if (json.contains("/month"_json_pointer) && json.at("/month"_json_pointer).is_number()) + date.SetMonth(JSON::GetInt(json, "/month"_json_pointer)); + else + date.VoidMonth(); + + if (json.contains("/day"_json_pointer) && json.at("/day"_json_pointer).is_number()) + date.SetDay(JSON::GetInt(json, "/day"_json_pointer)); + else + date.VoidDay(); + return date; +} + +void ParseTitle(const nlohmann::json& json, Anime::Anime& anime) { + anime.SetNativeTitle(JSON::GetString(json, "/native"_json_pointer)); + anime.SetEnglishTitle(JSON::GetString(json, "/english"_json_pointer)); + anime.SetRomajiTitle(JSON::GetString(json, "/romaji"_json_pointer)); +} + +int ParseMediaJson(const nlohmann::json& json) { + int id = JSON::GetInt(json, "/id"_json_pointer); + if (!id) + return 0; + Anime::Anime& anime = Anime::db.items[id]; + anime.SetId(id); + + ParseTitle(json.at("/title"_json_pointer), anime); + + anime.SetEpisodes(JSON::GetInt(json, "/episodes"_json_pointer)); + anime.SetFormat(Translate::AniList::ToSeriesFormat(JSON::GetString(json, "/format"_json_pointer))); + + anime.SetAiringStatus(Translate::AniList::ToSeriesStatus(JSON::GetString(json, "/status"_json_pointer))); + + anime.SetAirDate(ParseDate(json["/startDate"_json_pointer])); + + anime.SetPosterUrl(JSON::GetString(json, "/coverImage/large"_json_pointer)); + + anime.SetAudienceScore(JSON::GetInt(json, "/averageScore"_json_pointer)); + anime.SetSeason(Translate::AniList::ToSeriesSeason(JSON::GetString(json, "/season"_json_pointer))); + anime.SetDuration(JSON::GetInt(json, "/duration"_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>()); + if (json.contains("/synonyms"_json_pointer) && json["/synonyms"_json_pointer].is_array()) + anime.SetTitleSynonyms(json["/synonyms"_json_pointer].get>()); + return id; +} + +int ParseListItem(const nlohmann::json& json) { + int id = ParseMediaJson(json["media"]); + + Anime::Anime& anime = Anime::db.items[id]; + + anime.AddToUserList(); + + anime.SetUserScore(JSON::GetInt(json, "/score"_json_pointer)); + anime.SetUserProgress(JSON::GetInt(json, "/progress"_json_pointer)); + ParseListStatus(JSON::GetString(json, "/status"_json_pointer), anime); + anime.SetUserNotes(JSON::GetString(json, "/notes"_json_pointer)); + + anime.SetUserDateStarted(ParseDate(json["/startedAt"_json_pointer])); + anime.SetUserDateCompleted(ParseDate(json["/completedAt"_json_pointer])); + + 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() { + /* NOTE: these should be in the qrc file */ + const std::string query = "query ($id: Int) {\n" + " MediaListCollection (userId: $id, type: ANIME) {\n" + " lists {\n" + " name\n" + " entries {\n" + " score\n" + " notes\n" + " status\n" + " progress\n" + " startedAt {\n" + " year\n" + " month\n" + " day\n" + " }\n" + " completedAt {\n" + " year\n" + " month\n" + " day\n" + " }\n" + " updatedAt\n" + " media {\n" + " coverImage {\n" + " large\n" + " }\n" + " id\n" + " title {\n" + " romaji\n" + " english\n" + " native\n" + " }\n" + " format\n" + " status\n" + " averageScore\n" + " season\n" + " startDate {\n" + " year\n" + " month\n" + " day\n" + " }\n" + " genres\n" + " episodes\n" + " duration\n" + " synonyms\n" + " description(asHtml: false)\n" + " }\n" + " }\n" + " }\n" + " }\n" + "}\n"; + // clang-format off + nlohmann::json json = { + {"query", query}, + {"variables", { + {"id", account.UserId()} + }} + }; + // clang-format on + /* TODO: do a try catch here, catch any json errors and then call + Authorize() if needed */ + auto res = nlohmann::json::parse(SendRequest(json.dump())); + /* 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.value()); + } + return 1; +} + +int UpdateAnimeEntry(int id) { + /** + * possible values: + * + * int mediaId, + * MediaListStatus status, + * float score, + * int scoreRaw, + * int progress, + * int progressVolumes, + * int repeat, + * int priority, + * bool private, + * string notes, + * bool hiddenFromStatusLists, + * string[] customLists, + * float[] advancedScores, + * Date startedAt, + * Date completedAt + **/ + Anime::Anime& anime = Anime::db.items[id]; + const std::string query = "mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, " + "$notes: String, $start: FuzzyDateInput, $comp: FuzzyDateInput) {\n" + " SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, " + "scoreRaw: $score, notes: $notes, startedAt: $start, completedAt: $comp) {\n" + " id\n" + " }\n" + "}\n"; + // clang-format off + nlohmann::json json = { + {"query", query}, + {"variables", { + {"media_id", anime.GetId()}, + {"progress", anime.GetUserProgress()}, + {"status", ListStatusToString(anime)}, + {"score", anime.GetUserScore()}, + {"notes", anime.GetUserNotes()}, + {"start", anime.GetUserDateStarted().GetAsAniListJson()}, + {"comp", anime.GetUserDateCompleted().GetAsAniListJson()} + }} + }; + // clang-format on + auto ret = nlohmann::json::parse(SendRequest(json.dump())); + return JSON::GetInt(ret, "/data/SaveMediaListEntry/id"_json_pointer); +} + +int ParseUser(const nlohmann::json& json) { + account.SetUsername(JSON::GetString(json, "/name"_json_pointer)); + account.SetUserId(JSON::GetInt(json, "/id"_json_pointer)); + return account.UserId(); +} + +bool AuthorizeUser() { + /* Prompt for PIN */ + QDesktopServices::openUrl( + QUrl("https://anilist.co/api/v2/oauth/authorize?client_id=" CLIENT_ID "&response_type=token")); + bool ok; + QString token = QInputDialog::getText( + 0, "Credentials needed!", "Please enter the code given to you after logging in to AniList:", QLineEdit::Normal, + "", &ok); + if (ok && !token.isEmpty()) + account.SetAuthToken(Strings::ToUtf8String(token)); + else // fail + return false; + const std::string query = "query {\n" + " Viewer {\n" + " id\n" + " name\n" + " mediaListOptions {\n" + " scoreFormat\n" + " }\n" + " }\n" + "}\n"; + nlohmann::json json = { + {"query", query} + }; + auto ret = nlohmann::json::parse(SendRequest(json.dump())); + ParseUser(ret["data"]["Viewer"]); + return true; +} + +} // namespace AniList +} // namespace Services diff -r 825506f0e221 -r 9b2b41f83a5e src/services/anilist.cpp --- a/src/services/anilist.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,320 +0,0 @@ -#include "services/anilist.h" -#include "core/anime.h" -#include "core/anime_db.h" -#include "core/config.h" -#include "core/http.h" -#include "core/json.h" -#include "core/session.h" -#include "core/strings.h" -#include "gui/translate/anilist.h" -#include -#include -#include -#include -#include -#include -#include -#include -#define CLIENT_ID "13706" - -using namespace nlohmann::literals::json_literals; - -namespace Services { -namespace AniList { - -class Account { - public: - 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.config.anilist.user_id; } - void SetUserId(const int id) { session.config.anilist.user_id = id; } - - 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; - -std::string SendRequest(std::string data) { - std::vector headers = {"Authorization: Bearer " + account.AuthToken(), "Accept: application/json", - "Content-Type: application/json"}; - return Strings::ToUtf8String(HTTP::Post("https://graphql.anilist.co", data, headers)); -} - -void ParseListStatus(std::string status, Anime::Anime& anime) { - std::unordered_map map = { - {"CURRENT", Anime::ListStatus::CURRENT }, - {"PLANNING", Anime::ListStatus::PLANNING }, - {"COMPLETED", Anime::ListStatus::COMPLETED}, - {"DROPPED", Anime::ListStatus::DROPPED }, - {"PAUSED", Anime::ListStatus::PAUSED } - }; - - if (status == "REPEATING") { - anime.SetUserIsRewatching(true); - anime.SetUserStatus(Anime::ListStatus::CURRENT); - return; - } - - if (map.find(status) == map.end()) { - anime.SetUserStatus(Anime::ListStatus::NOT_IN_LIST); - return; - } - - anime.SetUserStatus(map[status]); -} - -std::string ListStatusToString(const Anime::Anime& anime) { - if (anime.GetUserIsRewatching()) - return "REWATCHING"; - - switch (anime.GetUserStatus()) { - case Anime::ListStatus::PLANNING: return "PLANNING"; - case Anime::ListStatus::COMPLETED: return "COMPLETED"; - case Anime::ListStatus::DROPPED: return "DROPPED"; - case Anime::ListStatus::PAUSED: return "PAUSED"; - default: break; - } - return "CURRENT"; -} - -Date ParseDate(const nlohmann::json& json) { - Date date; - /* JSON for Modern C++ warns here. I'm not too sure why, this code works when I set the - standard to C++17 :/ */ - if (json.contains("/year"_json_pointer) && json.at("/year"_json_pointer).is_number()) - date.SetYear(JSON::GetInt(json, "/year"_json_pointer)); - else - date.VoidYear(); - - if (json.contains("/month"_json_pointer) && json.at("/month"_json_pointer).is_number()) - date.SetMonth(JSON::GetInt(json, "/month"_json_pointer)); - else - date.VoidMonth(); - - if (json.contains("/day"_json_pointer) && json.at("/day"_json_pointer).is_number()) - date.SetDay(JSON::GetInt(json, "/day"_json_pointer)); - else - date.VoidDay(); - return date; -} - -void ParseTitle(const nlohmann::json& json, Anime::Anime& anime) { - anime.SetNativeTitle(JSON::GetString(json, "/native"_json_pointer)); - anime.SetEnglishTitle(JSON::GetString(json, "/english"_json_pointer)); - anime.SetRomajiTitle(JSON::GetString(json, "/romaji"_json_pointer)); -} - -int ParseMediaJson(const nlohmann::json& json) { - int id = JSON::GetInt(json, "/id"_json_pointer); - if (!id) - return 0; - Anime::Anime& anime = Anime::db.items[id]; - anime.SetId(id); - - ParseTitle(json.at("/title"_json_pointer), anime); - - anime.SetEpisodes(JSON::GetInt(json, "/episodes"_json_pointer)); - anime.SetFormat(Translate::AniList::ToSeriesFormat(JSON::GetString(json, "/format"_json_pointer))); - - anime.SetAiringStatus(Translate::AniList::ToSeriesStatus(JSON::GetString(json, "/status"_json_pointer))); - - anime.SetAirDate(ParseDate(json["/startDate"_json_pointer])); - - anime.SetPosterUrl(JSON::GetString(json, "/coverImage/large"_json_pointer)); - - anime.SetAudienceScore(JSON::GetInt(json, "/averageScore"_json_pointer)); - anime.SetSeason(Translate::AniList::ToSeriesSeason(JSON::GetString(json, "/season"_json_pointer))); - anime.SetDuration(JSON::GetInt(json, "/duration"_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>()); - if (json.contains("/synonyms"_json_pointer) && json["/synonyms"_json_pointer].is_array()) - anime.SetTitleSynonyms(json["/synonyms"_json_pointer].get>()); - return id; -} - -int ParseListItem(const nlohmann::json& json) { - int id = ParseMediaJson(json["media"]); - - Anime::Anime& anime = Anime::db.items[id]; - - anime.AddToUserList(); - - anime.SetUserScore(JSON::GetInt(json, "/score"_json_pointer)); - anime.SetUserProgress(JSON::GetInt(json, "/progress"_json_pointer)); - ParseListStatus(JSON::GetString(json, "/status"_json_pointer), anime); - anime.SetUserNotes(JSON::GetString(json, "/notes"_json_pointer)); - - anime.SetUserDateStarted(ParseDate(json["/startedAt"_json_pointer])); - anime.SetUserDateCompleted(ParseDate(json["/completedAt"_json_pointer])); - - 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() { - /* NOTE: these should be in the qrc file */ - const std::string query = "query ($id: Int) {\n" - " MediaListCollection (userId: $id, type: ANIME) {\n" - " lists {\n" - " name\n" - " entries {\n" - " score\n" - " notes\n" - " status\n" - " progress\n" - " startedAt {\n" - " year\n" - " month\n" - " day\n" - " }\n" - " completedAt {\n" - " year\n" - " month\n" - " day\n" - " }\n" - " updatedAt\n" - " media {\n" - " coverImage {\n" - " large\n" - " }\n" - " id\n" - " title {\n" - " romaji\n" - " english\n" - " native\n" - " }\n" - " format\n" - " status\n" - " averageScore\n" - " season\n" - " startDate {\n" - " year\n" - " month\n" - " day\n" - " }\n" - " genres\n" - " episodes\n" - " duration\n" - " synonyms\n" - " description(asHtml: false)\n" - " }\n" - " }\n" - " }\n" - " }\n" - "}\n"; - // clang-format off - nlohmann::json json = { - {"query", query}, - {"variables", { - {"id", account.UserId()} - }} - }; - // clang-format on - /* TODO: do a try catch here, catch any json errors and then call - Authorize() if needed */ - auto res = nlohmann::json::parse(SendRequest(json.dump())); - /* 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.value()); - } - return 1; -} - -int UpdateAnimeEntry(int id) { - /** - * possible values: - * - * int mediaId, - * MediaListStatus status, - * float score, - * int scoreRaw, - * int progress, - * int progressVolumes, - * int repeat, - * int priority, - * bool private, - * string notes, - * bool hiddenFromStatusLists, - * string[] customLists, - * float[] advancedScores, - * Date startedAt, - * Date completedAt - **/ - Anime::Anime& anime = Anime::db.items[id]; - const std::string query = "mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, " - "$notes: String, $start: FuzzyDateInput, $comp: FuzzyDateInput) {\n" - " SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, " - "scoreRaw: $score, notes: $notes, startedAt: $start, completedAt: $comp) {\n" - " id\n" - " }\n" - "}\n"; - // clang-format off - nlohmann::json json = { - {"query", query}, - {"variables", { - {"media_id", anime.GetId()}, - {"progress", anime.GetUserProgress()}, - {"status", ListStatusToString(anime)}, - {"score", anime.GetUserScore()}, - {"notes", anime.GetUserNotes()}, - {"start", anime.GetUserDateStarted().GetAsAniListJson()}, - {"comp", anime.GetUserDateCompleted().GetAsAniListJson()} - }} - }; - // clang-format on - auto ret = nlohmann::json::parse(SendRequest(json.dump())); - return JSON::GetInt(ret, "/data/SaveMediaListEntry/id"_json_pointer); -} - -int ParseUser(const nlohmann::json& json) { - account.SetUsername(JSON::GetString(json, "/name"_json_pointer)); - account.SetUserId(JSON::GetInt(json, "/id"_json_pointer)); - return account.UserId(); -} - -bool AuthorizeUser() { - /* Prompt for PIN */ - QDesktopServices::openUrl( - QUrl("https://anilist.co/api/v2/oauth/authorize?client_id=" CLIENT_ID "&response_type=token")); - bool ok; - QString token = QInputDialog::getText( - 0, "Credentials needed!", "Please enter the code given to you after logging in to AniList:", QLineEdit::Normal, - "", &ok); - if (ok && !token.isEmpty()) - account.SetAuthToken(Strings::ToUtf8String(token)); - else // fail - return false; - const std::string query = "query {\n" - " Viewer {\n" - " id\n" - " name\n" - " mediaListOptions {\n" - " scoreFormat\n" - " }\n" - " }\n" - "}\n"; - nlohmann::json json = { - {"query", query} - }; - auto ret = nlohmann::json::parse(SendRequest(json.dump())); - ParseUser(ret["data"]["Viewer"]); - return true; -} - -} // namespace AniList -} // namespace Services diff -r 825506f0e221 -r 9b2b41f83a5e src/services/services.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/services/services.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,42 @@ +#include "services/services.h" +#include "core/session.h" +#include "gui/dialog/settings.h" +#include "services/anilist.h" +#include + +namespace Services { + +void Synchronize() { + switch (session.config.service) { + case Anime::Services::ANILIST: AniList::GetAnimeList(); break; + default: { + QMessageBox msg; + msg.setInformativeText("It seems you haven't yet selected a service to use."); + msg.setText("Would you like to select one now?"); + msg.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msg.setDefaultButton(QMessageBox::Yes); + int ret = msg.exec(); + if (ret == QMessageBox::Yes) { + SettingsDialog dialog; + dialog.exec(); + } + break; + } + } +} + +void UpdateAnimeEntry(int id) { + switch (session.config.service) { + case Anime::Services::ANILIST: AniList::UpdateAnimeEntry(id); break; + default: break; + } +} + +bool Authorize() { + switch (session.config.service) { + case Anime::Services::ANILIST: return AniList::AuthorizeUser(); + default: return true; + } +} + +}; // namespace Services diff -r 825506f0e221 -r 9b2b41f83a5e src/services/services.cpp --- a/src/services/services.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -#include "services/services.h" -#include "core/session.h" -#include "gui/dialog/settings.h" -#include "services/anilist.h" -#include - -namespace Services { - -void Synchronize() { - switch (session.config.service) { - case Anime::Services::ANILIST: AniList::GetAnimeList(); break; - default: { - QMessageBox msg; - msg.setInformativeText("It seems you haven't yet selected a service to use."); - msg.setText("Would you like to select one now?"); - msg.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - msg.setDefaultButton(QMessageBox::Yes); - int ret = msg.exec(); - if (ret == QMessageBox::Yes) { - SettingsDialog dialog; - dialog.exec(); - } - break; - } - } -} - -void UpdateAnimeEntry(int id) { - switch (session.config.service) { - case Anime::Services::ANILIST: AniList::UpdateAnimeEntry(id); break; - default: break; - } -} - -bool Authorize() { - switch (session.config.service) { - case Anime::Services::ANILIST: return AniList::AuthorizeUser(); - default: return true; - } -} - -}; // namespace Services diff -r 825506f0e221 -r 9b2b41f83a5e src/sys/win32/dark_theme.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/sys/win32/dark_theme.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,20 @@ +#include "sys/win32/dark_theme.h" +#include +#include + +namespace win32 { + +bool DarkThemeAvailable() { + // dark mode supported Windows 10 1809 10.0.17763 onward + // https://stackoverflow.com/questions/53501268/win10-dark-theme-how-to-use-in-winapi + const auto& ver = QOperatingSystemVersion::current(); + return (ver.majorVersion() > 10) ? true : (ver.majorVersion() == 10 && ver.microVersion() >= 17763); +} + +bool IsInDarkTheme() { + QSettings settings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", + QSettings::NativeFormat); + return settings.value("AppsUseLightTheme", 1).toInt() == 0; +} + +} // namespace win32 diff -r 825506f0e221 -r 9b2b41f83a5e src/sys/win32/dark_theme.cpp --- a/src/sys/win32/dark_theme.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -#include "sys/win32/dark_theme.h" -#include -#include - -namespace win32 { - -bool DarkThemeAvailable() { - // dark mode supported Windows 10 1809 10.0.17763 onward - // https://stackoverflow.com/questions/53501268/win10-dark-theme-how-to-use-in-winapi - const auto& ver = QOperatingSystemVersion::current(); - return (ver.majorVersion() > 10) ? true : (ver.majorVersion() == 10 && ver.microVersion() >= 17763); -} - -bool IsInDarkTheme() { - QSettings settings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", - QSettings::NativeFormat); - return settings.value("AppsUseLightTheme", 1).toInt() == 0; -} - -} // namespace win32 diff -r 825506f0e221 -r 9b2b41f83a5e src/track/media.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/track/media.cc Mon Oct 23 12:07:27 2023 -0400 @@ -0,0 +1,53 @@ +#include "track/media.h" +#include "animia.h" +#include "anitomy/anitomy.h" +#include "core/filesystem.h" +#include "core/strings.h" +#include +#include +#include +#include + +namespace Track { +namespace Media { + +Filesystem::Path GetCurrentPlaying() { + /* getting all open files */ + std::vector pids = Animia::get_all_pids(); + for (int i : pids) { + if (Animia::get_process_name(i) == "vlc") { + std::vector files = Animia::filter_system_files(Animia::get_open_files(i)); + for (std::string s : files) { + qDebug() << Strings::ToQString(s); + Filesystem::Path p(s); + if (p.Extension() == "mp4") + return p; + } + } + } + return Filesystem::Path(); +} + +std::unordered_map GetMapFromElements(const anitomy::Elements& elements) { + /* there are way more than this in anitomy, but we only need basic information + I also just prefer using maps than using the ".get()" stuff which is why I'm doing this */ + std::unordered_map ret; + + ret["title"] = Strings::ToUtf8String(elements.get(anitomy::kElementAnimeTitle)); + ret["filename"] = Strings::ToUtf8String(elements.get(anitomy::kElementFileName)); + ret["language"] = Strings::ToUtf8String(elements.get(anitomy::kElementLanguage)); + ret["group"] = Strings::ToUtf8String(elements.get(anitomy::kElementReleaseGroup)); + ret["episode"] = Strings::ToUtf8String(elements.get(anitomy::kElementEpisodeNumber)); + + return ret; +} + +std::unordered_map GetFileElements(Filesystem::Path path) { + anitomy::Anitomy anitomy; + anitomy.Parse(Strings::ToWstring(path.Basename())); + + return GetMapFromElements(anitomy.elements()); +} + +} // namespace Media +} // namespace Track diff -r 825506f0e221 -r 9b2b41f83a5e src/track/media.cpp --- a/src/track/media.cpp Fri Oct 13 13:15:19 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -#include "track/media.h" -#include "animia.h" -#include "anitomy/anitomy.h" -#include "core/filesystem.h" -#include "core/strings.h" -#include -#include -#include -#include - -namespace Track { -namespace Media { - -Filesystem::Path GetCurrentPlaying() { - /* getting all open files */ - std::vector pids = Animia::get_all_pids(); - for (int i : pids) { - if (Animia::get_process_name(i) == "vlc") { - std::vector files = Animia::filter_system_files(Animia::get_open_files(i)); - for (std::string s : files) { - qDebug() << Strings::ToQString(s); - Filesystem::Path p(s); - if (p.Extension() == "mp4") - return p; - } - } - } - return Filesystem::Path(); -} - -std::unordered_map GetMapFromElements(const anitomy::Elements& elements) { - /* there are way more than this in anitomy, but we only need basic information - I also just prefer using maps than using the ".get()" stuff which is why I'm doing this */ - std::unordered_map ret; - - ret["title"] = Strings::ToUtf8String(elements.get(anitomy::kElementAnimeTitle)); - ret["filename"] = Strings::ToUtf8String(elements.get(anitomy::kElementFileName)); - ret["language"] = Strings::ToUtf8String(elements.get(anitomy::kElementLanguage)); - ret["group"] = Strings::ToUtf8String(elements.get(anitomy::kElementReleaseGroup)); - ret["episode"] = Strings::ToUtf8String(elements.get(anitomy::kElementEpisodeNumber)); - - return ret; -} - -std::unordered_map GetFileElements(Filesystem::Path path) { - anitomy::Anitomy anitomy; - anitomy.Parse(Strings::ToWstring(path.Basename())); - - return GetMapFromElements(anitomy.elements()); -} - -} // namespace Media -} // namespace Track