# HG changeset patch # User Paper # Date 1692161357 14400 # Node ID 1d82f6e04d7d892c8d46195b9d6901e944b20646 # Parent 51ae25154b7084c650d0d73f80f30093a6215a94 Update: add first parts to the settings dialog diff -r 51ae25154b70 -r 1d82f6e04d7d CMakeLists.txt --- a/CMakeLists.txt Sat Aug 12 13:10:34 2023 -0400 +++ b/CMakeLists.txt Wed Aug 16 00:49:17 2023 -0400 @@ -10,7 +10,12 @@ src/json.cpp src/date.cpp src/time.cpp + src/sidebar.cpp + src/progress.cpp + src/dialog/settings.cpp src/dialog/information.cpp + src/dialog/settings/services.cpp + src/dialog/settings/application.cpp src/ui_utils.cpp src/string_utils.cpp rc/icons.qrc diff -r 51ae25154b70 -r 1d82f6e04d7d rc/icons.qrc --- a/rc/icons.qrc Sat Aug 12 13:10:34 2023 -0400 +++ b/rc/icons.qrc Wed Aug 16 00:49:17 2023 -0400 @@ -7,8 +7,14 @@ icons/16x16/feed.png icons/16x16/film.png icons/16x16/magnifier.png + icons/24x24/application-sidebar-list.png icons/24x24/arrow-circle-double-135.png + icons/24x24/feed.png icons/24x24/folder-open.png icons/24x24/gear.png + icons/24x24/globe.png + icons/24x24/inbox-film.png + icons/24x24/megaphone.png + icons/24x24/question.png \ No newline at end of file diff -r 51ae25154b70 -r 1d82f6e04d7d rc/icons/24x24/application-sidebar-list.png Binary file rc/icons/24x24/application-sidebar-list.png has changed diff -r 51ae25154b70 -r 1d82f6e04d7d rc/icons/24x24/feed.png Binary file rc/icons/24x24/feed.png has changed diff -r 51ae25154b70 -r 1d82f6e04d7d rc/icons/24x24/globe.png Binary file rc/icons/24x24/globe.png has changed diff -r 51ae25154b70 -r 1d82f6e04d7d rc/icons/24x24/inbox-film.png Binary file rc/icons/24x24/inbox-film.png has changed diff -r 51ae25154b70 -r 1d82f6e04d7d rc/icons/24x24/megaphone.png Binary file rc/icons/24x24/megaphone.png has changed diff -r 51ae25154b70 -r 1d82f6e04d7d rc/icons/24x24/question.png Binary file rc/icons/24x24/question.png has changed diff -r 51ae25154b70 -r 1d82f6e04d7d src/anilist.cpp --- a/src/anilist.cpp Sat Aug 12 13:10:34 2023 -0400 +++ b/src/anilist.cpp Wed Aug 16 00:49:17 2023 -0400 @@ -65,7 +65,7 @@ }} }; auto ret = nlohmann::json::parse(SendRequest(json.dump())); - return ret["data"]["User"]["id"].get(); + return JSON::GetInt(ret, "/data/User/id"_json_pointer); #undef QUERY } @@ -169,42 +169,46 @@ for (const auto& list : res["data"]["MediaListCollection"]["lists"].items()) { /* why are the .key() values strings?? */ AnimeList anime_list; - anime_list.name = StringUtils::Utf8ToWstr(JSON::GetString(list.value(), "name")); + anime_list.name = JSON::GetString(list.value(), "/name"_json_pointer); for (const auto& entry : list.value()["entries"].items()) { Anime anime; - anime.score = JSON::GetInt(entry.value(), "score"); - anime.progress = JSON::GetInt(entry.value(), "progress"); - anime.status = StringToAnimeWatchingMap[JSON::GetString(entry.value(), "status")]; - anime.notes = StringUtils::Utf8ToWstr(JSON::GetString(entry.value(), "notes")); + anime.score = JSON::GetInt(entry.value(), "/score"_json_pointer); + anime.progress = JSON::GetInt(entry.value(), "/progress"_json_pointer); + anime.status = StringToAnimeWatchingMap[JSON::GetString(entry.value(), "/status"_json_pointer)]; + anime.notes = JSON::GetString(entry.value(), "/notes"_json_pointer); - anime.started.SetYear(JSON::GetInt(entry.value()["startedAt"], "year")); - anime.started.SetMonth(JSON::GetInt(entry.value()["startedAt"], "month")); - anime.started.SetDay(JSON::GetInt(entry.value()["startedAt"], "day")); + anime.started.SetYear(JSON::GetInt(entry.value(), "/startedAt/year"_json_pointer)); + anime.started.SetMonth(JSON::GetInt(entry.value(), "/startedAt/month"_json_pointer)); + anime.started.SetDay(JSON::GetInt(entry.value(), "/startedAt/day"_json_pointer)); - anime.completed.SetYear(JSON::GetInt(entry.value()["completedAt"], "year")); - anime.completed.SetMonth(JSON::GetInt(entry.value()["completedAt"], "month")); - anime.completed.SetDay(JSON::GetInt(entry.value()["completedAt"], "day")); + anime.completed.SetYear(JSON::GetInt(entry.value(), "/completedAt/year"_json_pointer)); + anime.completed.SetMonth(JSON::GetInt(entry.value(), "/completedAt/month"_json_pointer)); + anime.completed.SetDay(JSON::GetInt(entry.value(), "/completedAt/day"_json_pointer)); - anime.updated = JSON::GetInt(entry.value(), "updatedAt"); + anime.updated = JSON::GetInt(entry.value(), "/updatedAt"_json_pointer); - anime.title.native = StringUtils::Utf8ToWstr(JSON::GetString(entry.value()["media"]["title"], "native")); - anime.title.english = StringUtils::Utf8ToWstr(JSON::GetString(entry.value()["media"]["title"], "english")); - anime.title.romaji = StringUtils::Utf8ToWstr(JSON::GetString(entry.value()["media"]["title"], "romaji")); - - anime.id = JSON::GetInt(entry.value()["media"], "id"); - anime.episodes = JSON::GetInt(entry.value()["media"], "episodes"); - anime.type = StringToAnimeFormatMap[JSON::GetString(entry.value()["media"], "format")]; + anime.title.native = JSON::GetString(entry.value(), "/media/title/native"_json_pointer); + anime.title.english = JSON::GetString(entry.value(), "/media/title/english"_json_pointer); + anime.title.romaji = JSON::GetString(entry.value(), "/media/title/romaji"_json_pointer); + /* fallback to romaji if english is not available + note that this takes up more space in memory and is stinky */ + if (anime.title.english.empty()) + anime.title.english = anime.title.romaji; - anime.airing = StringToAnimeAiringMap[JSON::GetString(entry.value()["media"], "status")]; + anime.id = JSON::GetInt(entry.value(), "/media/id"_json_pointer); + anime.episodes = JSON::GetInt(entry.value(), "/media/episodes"_json_pointer); + anime.type = StringToAnimeFormatMap[JSON::GetString(entry.value()["media"], "/media/format"_json_pointer)]; + + anime.airing = StringToAnimeAiringMap[JSON::GetString(entry.value()["media"], "/media/status"_json_pointer)]; - anime.air_date.SetYear(JSON::GetInt(entry.value()["media"]["startDate"], "year")); - anime.air_date.SetMonth(JSON::GetInt(entry.value()["media"]["startDate"], "month")); - anime.air_date.SetDay(JSON::GetInt(entry.value()["media"]["startDate"], "day")); + anime.air_date.SetYear(JSON::GetInt(entry.value(), "/media/startDate/year"_json_pointer)); + anime.air_date.SetMonth(JSON::GetInt(entry.value(), "/media/startDate/month"_json_pointer)); + anime.air_date.SetDay(JSON::GetInt(entry.value(), "/media/startDate/day"_json_pointer)); - anime.audience_score = JSON::GetInt(entry.value()["media"], "averageScore"); - anime.season = StringToAnimeSeasonMap[JSON::GetString(entry.value()["media"], "season")]; - anime.duration = JSON::GetInt(entry.value()["media"], "duration"); - anime.synopsis = StringUtils::TextifySynopsis(StringUtils::Utf8ToWstr(JSON::GetString(entry.value()["media"], "duration"))); + anime.audience_score = JSON::GetInt(entry.value(), "/media/averageScore"_json_pointer); + anime.season = StringToAnimeSeasonMap[JSON::GetString(entry.value(), "/media/season"_json_pointer)]; + anime.duration = JSON::GetInt(entry.value(), "/media/duration"_json_pointer); + anime.synopsis = StringUtils::TextifySynopsis(JSON::GetString(entry.value(), "/media/description"_json_pointer)); if (entry.value()["media"]["genres"].is_array()) anime.genres = entry.value()["media"]["genres"].get>(); @@ -217,16 +221,14 @@ } int AniList::Authorize() { - if (session.config.anilist.auth_token.empty()) { - /* 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()) { - session.config.anilist.auth_token = token.toStdString(); - } else { // fail - return 0; - } + /* 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()) { + session.config.anilist.auth_token = token.toStdString(); + } else { // fail + return 0; } return 1; } diff -r 51ae25154b70 -r 1d82f6e04d7d src/anime.cpp --- a/src/anime.cpp Sat Aug 12 13:10:34 2023 -0400 +++ b/src/anime.cpp Wed Aug 16 00:49:17 2023 -0400 @@ -10,6 +10,7 @@ #include "time_utils.h" #include "information.h" #include "ui_utils.h" +#include std::map AnimeWatchingToStringMap = { {CURRENT, "Watching"}, @@ -74,6 +75,12 @@ duration = a.duration; } +std::string Anime::GetUserPreferredTitle() { + if (title.english.empty()) + return title.romaji; + return title.english; +} + void AnimeList::Add(Anime& anime) { if (anime_id_to_anime.contains(anime.id)) return; @@ -156,6 +163,58 @@ /* ------------------------------------------------------------------------- */ +AnimeListWidgetDelegate::AnimeListWidgetDelegate(QObject* parent) + : QStyledItemDelegate (parent) { +} + +QWidget *AnimeListWidgetDelegate::createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const +{ + // LOL + return nullptr; +} + +void AnimeListWidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + switch (index.column()) { + case AnimeListWidgetModel::AL_PROGRESS: { + const int progress = static_cast(index.data(Qt::UserRole).toReal()); + const int episodes = static_cast(index.siblingAtColumn(AnimeListWidgetModel::AL_EPISODES).data(Qt::UserRole).toReal()); + + QStyleOptionViewItem customOption (option); + customOption.state.setFlag(QStyle::State_Enabled, true); + + progress_bar.paint(painter, customOption, index.data().toString(), progress, episodes); + break; + } + default: + QStyledItemDelegate::paint(painter, option, index); + break; + } +} + +AnimeListWidgetSortFilter::AnimeListWidgetSortFilter(QObject *parent) + : QSortFilterProxyModel(parent) { +} + +bool AnimeListWidgetSortFilter::lessThan(const QModelIndex &l, + const QModelIndex &r) const { + QVariant left = sourceModel()->data(l, sortRole()); + QVariant right = sourceModel()->data(r, sortRole()); + + switch (left.userType()) { + case QMetaType::Int: + case QMetaType::UInt: + case QMetaType::LongLong: + case QMetaType::ULongLong: + return left.toInt() < right.toInt(); + case QMetaType::QDate: + return left.toDate() < right.toDate(); + case QMetaType::QString: + default: + return left.toString() < right.toString(); + } +} + /* Thank you qBittorrent for having a great example of a widget model. */ AnimeListWidgetModel::AnimeListWidgetModel (QWidget* parent, AnimeList* alist) @@ -181,6 +240,8 @@ 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: @@ -206,6 +267,7 @@ 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: @@ -229,53 +291,75 @@ QVariant AnimeListWidgetModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); - if (role == Qt::DisplayRole) { - switch (index.column()) { - case AL_TITLE: - return QString::fromWCharArray(list[index.row()].title.english.c_str()); - case AL_PROGRESS: - return list[index.row()].progress; - case AL_SCORE: - return list[index.row()].score; - case AL_TYPE: - return QString::fromStdString(AnimeFormatToStringMap[list[index.row()].type]); - case AL_SEASON: - return QString::fromStdString(AnimeSeasonToStringMap[list[index.row()].season]) + " " + QString::number(list[index.row()].air_date.GetYear()); - case AL_AVG_SCORE: - return list[index.row()].audience_score; - case AL_STARTED: - return list[index.row()].started.GetAsQDate(); - case AL_COMPLETED: - return list[index.row()].completed.GetAsQDate(); - case AL_UPDATED: { - if (list[index.row()].updated == 0) - return QString("-"); - Time::Duration duration(Time::GetSystemTime() - list[index.row()].updated); - return QString::fromStdString(duration.AsRelativeString()); + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case AL_TITLE: + return QString::fromUtf8(list[index.row()].GetUserPreferredTitle().c_str()); + case AL_PROGRESS: + return QString::number(list[index.row()].progress) + "/" + QString::number(list[index.row()].episodes); + case AL_EPISODES: + return list[index.row()].episodes; + case AL_SCORE: + return list[index.row()].score; + case AL_TYPE: + return QString::fromStdString(AnimeFormatToStringMap[list[index.row()].type]); + case AL_SEASON: + return QString::fromStdString(AnimeSeasonToStringMap[list[index.row()].season]) + " " + QString::number(list[index.row()].air_date.GetYear()); + case AL_AVG_SCORE: + return QString::number(list[index.row()].audience_score) + "%"; + case AL_STARTED: + return list[index.row()].started.GetAsQDate(); + case AL_COMPLETED: + return list[index.row()].completed.GetAsQDate(); + case AL_UPDATED: { + if (list[index.row()].updated == 0) + return QString("-"); + Time::Duration duration(Time::GetSystemTime() - list[index.row()].updated); + return QString::fromUtf8(duration.AsRelativeString().c_str()); + } + case AL_NOTES: + return QString::fromUtf8(list[index.row()].notes.c_str()); + default: + return ""; } - case AL_NOTES: - return QString::fromWCharArray(list[index.row()].notes.c_str()); - default: - return ""; - } - } else if (role == Qt::TextAlignmentRole) { - switch (index.column()) { - case AL_TITLE: - case AL_NOTES: - return QVariant(Qt::AlignLeft | Qt::AlignVCenter); - case AL_PROGRESS: - 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; + case Qt::UserRole: + switch (index.column()) { + case AL_PROGRESS: + return list[index.row()].progress; + case AL_TYPE: + return list[index.row()].type; + case AL_SEASON: + return list[index.row()].air_date.GetAsQDate(); + case AL_AVG_SCORE: + return list[index.row()].audience_score; + case AL_UPDATED: + return list[index.row()].updated; + default: + return data(index, Qt::DisplayRole); + } + break; + case Qt::TextAlignmentRole: + switch (index.column()) { + case AL_TITLE: + case AL_NOTES: + return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + case AL_PROGRESS: + case AL_EPISODES: + case AL_TYPE: + case AL_SCORE: + case AL_AVG_SCORE: + return QVariant(Qt::AlignCenter | Qt::AlignVCenter); + case AL_SEASON: + case AL_STARTED: + case AL_COMPLETED: + case AL_UPDATED: + return QVariant(Qt::AlignRight | Qt::AlignVCenter); + default: + break; + } + break; } return QVariant(); } @@ -285,27 +369,6 @@ emit dataChanged(index(i), index(i)); } -/* Most of this stuff is const and/or should be edited in the Information dialog - -bool AnimeListWidgetModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (!index.isValid() || role != Qt::DisplayRole) - return false; - - Anime* const anime = &list[index.row()]; - - switch (index.column()) { - case AL_TITLE: - break; - case AL_CATEGORY: - break; - default: - return false; - } - - return true; -} -*/ - int AnimeListWidget::VisibleColumnsCount() const { int count = 0; @@ -325,6 +388,7 @@ setColumnHidden(AnimeListWidgetModel::AL_PROGRESS, false); setColumnHidden(AnimeListWidgetModel::AL_SCORE, false); setColumnHidden(AnimeListWidgetModel::AL_TITLE, false); + setColumnHidden(AnimeListWidgetModel::AL_EPISODES, true); setColumnHidden(AnimeListWidgetModel::AL_AVG_SCORE, true); setColumnHidden(AnimeListWidgetModel::AL_STARTED, true); setColumnHidden(AnimeListWidgetModel::AL_COMPLETED, true); @@ -370,29 +434,44 @@ } void AnimeListWidget::DisplayListMenu() { - /* throw out any other garbage */ - const QModelIndexList selected_items = selectionModel()->selectedRows(); - if (selected_items.size() != 1 || !selected_items.first().isValid()) { + QMenu *menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->setTitle(tr("Column visibility")); + menu->setToolTipsVisible(true); + + const QItemSelection selection = sort_model->mapSelectionToSource(selectionModel()->selection()); + if (!selection.indexes().first().isValid()) { return; } - const QModelIndex index = model->index(selected_items.first().row()); - Anime* anime = model->GetAnimeFromIndex(index); - if (!anime) { - return; - } + QAction* action = menu->addAction("Information", [this, selection]{ + const QModelIndex index = model->index(selection.indexes().first().row()); + Anime* anime = model->GetAnimeFromIndex(index); + if (!anime) { + return; + } + InformationDialog* dialog = new InformationDialog(*anime, model, this); + + dialog->show(); + dialog->raise(); + dialog->activateWindow(); + }); + menu->popup(QCursor::pos()); } void AnimeListWidget::ItemDoubleClicked() { /* throw out any other garbage */ - const QModelIndexList selected_items = selectionModel()->selectedRows(); - if (selected_items.size() != 1 || !selected_items.first().isValid()) { + const QItemSelection selection = sort_model->mapSelectionToSource(selectionModel()->selection()); + if (!selection.indexes().first().isValid()) { return; } - /* TODO: after we implement our sort model, we have to use mapToSource here... */ - const QModelIndex index = model->index(selected_items.first().row()); + const QModelIndex index = model->index(selection.indexes().first().row()); + const QString title = index.siblingAtColumn(AnimeListWidgetModel::AL_TITLE).data(Qt::UserRole).toString(); + QMessageBox box; + box.setText(QString::number(title.size())); + box.exec(); Anime* anime = model->GetAnimeFromIndex(index); if (!anime) { return; @@ -407,10 +486,14 @@ AnimeListWidget::AnimeListWidget(QWidget* parent, AnimeList* alist) : QTreeView(parent) { + setItemDelegate(new AnimeListWidgetDelegate(this)); model = new AnimeListWidgetModel(parent, alist); - setModel(model); + sort_model = new AnimeListWidgetSortFilter(this); + sort_model->setSourceModel(model); + sort_model->setSortRole(Qt::UserRole); + setModel(sort_model); setObjectName("listwidget"); - setStyleSheet("QTreeView#listwidget{border-top:0px;}"); + setStyleSheet("QTreeView#listwidget{border:0px;}"); setUniformRowHeights(true); setAllColumnsShowFocus(false); setSortingEnabled(true); @@ -438,9 +521,11 @@ AnimeListPage::AnimeListPage(QWidget* parent) : QTabWidget (parent) { setDocumentMode(false); + setObjectName("animepage"); + //setStyleSheet("QTabWidget#animepage{border-bottom:0px;border-left:0px;border-right:0px;}"); SyncAnimeList(); for (AnimeList& list : anime_lists) { - addTab(new AnimeListWidget(this, &list), QString::fromWCharArray(list.name.c_str())); + addTab(new AnimeListWidget(this, &list), QString::fromUtf8(list.name.c_str())); } } @@ -448,7 +533,6 @@ switch (session.config.service) { case ANILIST: { AniList anilist = AniList(); - anilist.Authorize(); session.config.anilist.user_id = anilist.GetUserId(session.config.anilist.username); FreeAnimeList(); anilist.UpdateAnimeList(&anime_lists, session.config.anilist.user_id); @@ -460,14 +544,10 @@ } void AnimeListPage::FreeAnimeList() { - if (anime_lists.size() > 0) { - /* FIXME: we may not need this, but to prevent memleaks - we should keep it until we're sure we don't */ - for (auto& list : anime_lists) { - list.Clear(); - } - anime_lists.clear(); + for (auto& list : anime_lists) { + list.Clear(); } + anime_lists.clear(); } int AnimeListPage::GetTotalAnimeAmount() { diff -r 51ae25154b70 -r 1d82f6e04d7d src/config.cpp --- a/src/config.cpp Sat Aug 12 13:10:34 2023 -0400 +++ b/src/config.cpp Wed Aug 16 00:49:17 2023 -0400 @@ -31,21 +31,27 @@ {DARK, "Dark"} }; +std::map ServiceToString { + {NONE, "None"}, + {ANILIST, "AniList"} +}; + +std::map StringToService { + {"None", NONE}, + {"AniList", ANILIST} +}; + int Config::Load() { std::filesystem::path cfg_path = get_config_path(); if (!std::filesystem::exists(cfg_path)) return 0; std::ifstream config_in(cfg_path.string().c_str(), std::ifstream::in); auto config_js = nlohmann::json::parse(config_in); -/* this macro will make it easier to edit these in the future, if needed */ -#define GET_CONFIG_VALUE(pointer, location, struct, default) \ - struct = (config_js.contains(pointer)) ? (location) : (default) - GET_CONFIG_VALUE("/General/Service"_json_pointer, (enum AnimeListServices)config_js["General"]["Service"].get(), service, NONE); - GET_CONFIG_VALUE("/Authorization/AniList/Auth Token"_json_pointer, config_js["Authorization"]["AniList"]["Auth Token"].get(), anilist.auth_token, ""); - GET_CONFIG_VALUE("/Authorization/AniList/Username"_json_pointer, config_js["Authorization"]["AniList"]["Username"].get(), anilist.username, ""); - GET_CONFIG_VALUE("/Authorization/AniList/User ID"_json_pointer, config_js["Authorization"]["AniList"]["User ID"].get(), anilist.user_id, 0); - GET_CONFIG_VALUE("/Appearance/Theme"_json_pointer, StringToTheme[config_js["Appearance"]["Theme"].get()], theme, OS); -#undef GET_CONFIG_VALUE + service = StringToService[JSON::GetString(config_js, "/General/Service"_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_in.close(); return 0; } @@ -57,7 +63,7 @@ std::ofstream config_out(cfg_path.string().c_str(), std::ofstream::out | std::ofstream::trunc); nlohmann::json config_js = { {"General", { - {"Service", service} + {"Service", ServiceToString[service]} }}, {"Authorization", { {"AniList", { diff -r 51ae25154b70 -r 1d82f6e04d7d src/date.cpp --- a/src/date.cpp Sat Aug 12 13:10:34 2023 -0400 +++ b/src/date.cpp Wed Aug 16 00:49:17 2023 -0400 @@ -1,54 +1,73 @@ -#include "date.h" -#include -#include - -#define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) -#define MAX(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; }) - -#define CLAMP(x, low, high) ({\ - __typeof__(x) __x = (x); \ - __typeof__(low) __low = (low);\ - __typeof__(high) __high = (high);\ - __x > __high ? __high : (__x < __low ? __low : __x);\ - }) - -Date::Date() { -} - -Date::Date(int32_t y) { - year = MAX(0, y); -} - -Date::Date(int32_t y, int8_t m, int8_t d) { - year = MAX(0, y); - month = CLAMP(m, 1, 12); - day = CLAMP(d, 1, 31); -} - -void Date::SetYear(int32_t y) { - year = MAX(0, y); -} - -void Date::SetMonth(int8_t m) { - month = CLAMP(m, 1, 12); -} - -void Date::SetDay(int8_t d) { - day = CLAMP(d, 1, 31); -} - -int32_t Date::GetYear() { - return year; -} - -int8_t Date::GetMonth() { - return month; -} - -int8_t Date::GetDay() { - return day; -} - -QDate Date::GetAsQDate() { - return QDate(year, month, day); -} +#include "date.h" +#include +#include +#include + +#define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) +#define MAX(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; }) + +#define CLAMP(x, low, high) ({\ + __typeof__(x) __x = (x); \ + __typeof__(low) __low = (low);\ + __typeof__(high) __high = (high);\ + __x > __high ? __high : (__x < __low ? __low : __x);\ + }) + +Date::Date() { +} + +Date::Date(int32_t y) { + year = MAX(0, y); +} + +Date::Date(int32_t y, int8_t m, int8_t d) { + year = MAX(0, y); + month = CLAMP(m, 1, 12); + day = CLAMP(d, 1, 31); +} + +void Date::SetYear(int32_t y) { + year = MAX(0, y); +} + +void Date::SetMonth(int8_t m) { + month = CLAMP(m, 1, 12); +} + +void Date::SetDay(int8_t d) { + day = CLAMP(d, 1, 31); +} + +int32_t Date::GetYear() const { + return year; +} + +int8_t Date::GetMonth() const { + return month; +} + +int8_t Date::GetDay() const { + return day; +} + +bool Date::operator< (const Date& other) const { + int o_y = other.GetYear(), o_m = other.GetMonth(), o_d = other.GetDay(); + return std::tie(year, month, day) + < 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() { + return QDate(year, month, day); +} diff -r 51ae25154b70 -r 1d82f6e04d7d src/dialog/information.cpp --- a/src/dialog/information.cpp Sat Aug 12 13:10:34 2023 -0400 +++ b/src/dialog/information.cpp Wed Aug 16 00:49:17 2023 -0400 @@ -25,15 +25,17 @@ widget->move(175, 0); widget->setStyleSheet(UiUtils::IsInDarkMode() ? "" : "background-color: white"); widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - QPlainTextEdit* anime_title = new QPlainTextEdit(QString::fromWCharArray(anime->title.english.c_str()), widget); + QPlainTextEdit* anime_title = new QPlainTextEdit(QString::fromUtf8(anime->title.english.c_str()), widget); anime_title->setReadOnly(true); anime_title->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); anime_title->setWordWrapMode(QTextOption::NoWrap); anime_title->setFrameShape(QFrame::NoFrame); + anime_title->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + anime_title->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + anime_title->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + anime_title->setStyleSheet("font-size: 16px; color: blue; background: transparent;"); anime_title->resize(636, 28); anime_title->move(0, 12); - anime_title->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - anime_title->setStyleSheet("font-size: 16px; color: blue"); QTabWidget* tabbed_widget = new QTabWidget(widget); tabbed_widget->resize(636, 485); tabbed_widget->move(0, 45); @@ -49,15 +51,15 @@ << StringUtils::Implode(anime->genres, ", ").c_str() << "\n" << anime->audience_score << "%\n"; UiUtils::CreateTextParagraphWithLabels(main_information_widget, "Details", "Type:\nEpisodes:\nStatus:\nSeason:\nGenres:\nScore:", details_data, QPoint(6, 62), QSize(636-18, 142)); - UiUtils::CreateSelectableTextParagraph(main_information_widget, "Synopsis", QString::fromWCharArray(anime->synopsis.c_str()), QPoint(6, 202), QSize(636-18, 253)); + UiUtils::CreateSelectableTextParagraph(main_information_widget, "Synopsis", QString::fromUtf8(anime->synopsis.c_str()), QPoint(6, 202), QSize(636-18, 253)); tabbed_widget->addTab(main_information_widget, "Main information"); QWidget* settings_widget = new QWidget(tabbed_widget); tabbed_widget->addTab(settings_widget, "My list and settings"); QDialogButtonBox* button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect(button_box, &QDialogButtonBox::accepted, this, &InformationDialog::OnOK); connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); - QVBoxLayout* buttons_layout = new QVBoxLayout(widget); - buttons_layout->addWidget(widget, 0, Qt::AlignTop); + QVBoxLayout* buttons_layout = new QVBoxLayout(this); + //buttons_layout->addWidget(widget, 0, Qt::AlignTop); buttons_layout->addWidget(button_box, 0, Qt::AlignBottom); // this should probably be win32-only setStyleSheet(UiUtils::IsInDarkMode() ? "" : "QDialog#infodiag{background-color: white;}"); diff -r 51ae25154b70 -r 1d82f6e04d7d src/dialog/settings.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dialog/settings.cpp Wed Aug 16 00:49:17 2023 -0400 @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "settings.h" +#include "sidebar.h" +#include "ui_utils.h" + +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); + page_title->setStyleSheet("QLabel { font-size: 10pt; font-weight: bold; background-color: #ABABAB; color: white; }"); + 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; + layout->setMargin(0); + layout->addWidget(page_title); + layout->addWidget(tab_widget); + setLayout(layout); +} + +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::OnSidebar(QListWidgetItem* item) { + layout->itemAt(1)->widget()->setVisible(false); // old widget + layout->replaceWidget(layout->itemAt(1)->widget(), pages[item->listWidget()->row(item)], Qt::FindDirectChildrenOnly); + pages[item->listWidget()->row(item)]->setVisible(true); // new widget +} + +void SettingsDialog::OnOK() { + for (const auto& page : pages) { + page->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"), UiUtils::CreateSideBarIcon(":/icons/24x24/globe.png"))); + //sidebar->AddItem(tr("Library"), UiUtils::CreateSideBarIcon(":/icons/24x24/inbox-film.png")); + sidebar->AddItem(tr("Application"), UiUtils::CreateSideBarIcon(":/icons/24x24/application-sidebar-list.png")); + //sidebar->AddItem(tr("Recognition"), UiUtils::CreateSideBarIcon(":/icons/24x24/question.png")); + //sidebar->AddItem(tr("Sharing"), UiUtils::CreateSideBarIcon(":/icons/24x24/megaphone.png")); + //sidebar->AddItem(tr("Torrents"), UiUtils::CreateSideBarIcon(":/icons/24x24/feed.png")); + //sidebar->AddItem(tr("Advanced"), UiUtils::CreateSideBarIcon(":/icons/24x24/gear.png")); + sidebar->setIconSize(QSize(24, 24)); + sidebar->setFrameShape(QFrame::Box); + sidebar->setStyleSheet("QListWidget { background-color: white; font-size: 12px; }"); + sidebar->setFixedWidth(158); + sidebar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + connect(sidebar, &QListWidget::itemActivated, this, &SettingsDialog::OnSidebar); + + SettingsPageServices* services_page = new SettingsPageServices(this); + pages.push_back(services_page); + SettingsPageApplication* application_page = new SettingsPageApplication(this); + application_page->setVisible(false); + pages.push_back(application_page); + + layout = new QHBoxLayout; + layout->addWidget(sidebar); + layout->addWidget(services_page); + layout->setMargin(0); + widget->setLayout(layout); + + 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); + setLayout(buttons_layout); +} + +#include "moc_settings.cpp" diff -r 51ae25154b70 -r 1d82f6e04d7d src/dialog/settings/application.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dialog/settings/application.cpp Wed Aug 16 00:49:17 2023 -0400 @@ -0,0 +1,95 @@ +#include "settings.h" +#include "anilist.h" +#include "window.h" +#include +#include +#include +#include +#include + +QWidget* SettingsPageApplication::CreateAnimeListPage() { + QWidget* result = new QWidget(this); + result->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + QGroupBox* actions_group_box = new QGroupBox(tr("Actions"), result); + actions_group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + /* Actions/Double click */ + QWidget* double_click_widget = new QWidget(actions_group_box); + QLabel* dc_combo_box_label = new QLabel(tr("Double click:"), double_click_widget); + QComboBox* dc_combo_box = new QComboBox(double_click_widget); + dc_combo_box->addItem(tr("View anime info")); + + QVBoxLayout* double_click_layout = new QVBoxLayout; + double_click_layout->addWidget(dc_combo_box_label); + double_click_layout->addWidget(dc_combo_box); + double_click_widget->setLayout(double_click_layout); + + /* Actions/Middle click */ + QWidget* middle_click_widget = new QWidget(actions_group_box); + QLabel* mc_combo_box_label = new QLabel(tr("Middle click:"), middle_click_widget); + QComboBox* mc_combo_box = new QComboBox(middle_click_widget); + mc_combo_box->addItem(tr("Play next episode")); + + QVBoxLayout* middle_click_layout = new QVBoxLayout; + middle_click_layout->addWidget(mc_combo_box_label); + middle_click_layout->addWidget(mc_combo_box); + middle_click_widget->setLayout(middle_click_layout); + + /* Actions */ + QHBoxLayout* actions_layout = new QHBoxLayout; + actions_layout->addWidget(double_click_widget); + actions_layout->addWidget(middle_click_widget); + actions_group_box->setLayout(actions_layout); + + QGroupBox* appearance_group_box = new QGroupBox(tr("Appearance"), result); + appearance_group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + QLabel* lang_combo_box_label = new QLabel(tr("Title language preference:"), appearance_group_box); + QComboBox* lang_combo_box = new QComboBox(appearance_group_box); + lang_combo_box->addItem(tr("English")); + 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); + hl_above_anime_box->setEnabled((hl_anime_box->checkState() == Qt::Unchecked) ? 0 : 1); + hl_above_anime_box->setStyleSheet("margin-left: 10px;"); + + connect(hl_anime_box, &QCheckBox::stateChanged, this, [hl_above_anime_box](int state){ + hl_above_anime_box->setEnabled(state); + }); + + /* Appearance */ + QVBoxLayout* appearance_layout = new QVBoxLayout; + appearance_layout->addWidget(lang_combo_box_label); + appearance_layout->addWidget(lang_combo_box); + appearance_layout->addWidget(hl_anime_box); + appearance_layout->addWidget(hl_above_anime_box); + appearance_group_box->setLayout(appearance_layout); + + QGroupBox* progress_group_box = new QGroupBox(tr("Progress"), result); + progress_group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + QCheckBox* display_aired_episodes = new QCheckBox(tr("Display aired episodes (estimated)"), progress_group_box); + QCheckBox* display_available_episodes = new QCheckBox(tr("Display available episodes in library folders"), progress_group_box); + + QVBoxLayout* progress_layout = new QVBoxLayout; + progress_layout->addWidget(display_aired_episodes); + progress_layout->addWidget(display_available_episodes); + progress_group_box->setLayout(progress_layout); + + QVBoxLayout* full_layout = new QVBoxLayout; + full_layout->addWidget(actions_group_box); + full_layout->addWidget(appearance_group_box); + full_layout->addWidget(progress_group_box); + full_layout->addStretch(); + result->setLayout(full_layout); + return result; +} + +void SettingsPageApplication::SaveInfo() { + +} + +SettingsPageApplication::SettingsPageApplication(QWidget* parent) + : SettingsPage(parent, tr("Application")) { + AddTab(CreateAnimeListPage(), tr("Anime list")); +} diff -r 51ae25154b70 -r 1d82f6e04d7d src/dialog/settings/services.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dialog/settings/services.cpp Wed Aug 16 00:49:17 2023 -0400 @@ -0,0 +1,86 @@ +#include "settings.h" +#include "anilist.h" +#include "window.h" +#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); + + sync_combo_box = new QComboBox(sync_group_box); + sync_combo_box->addItem(tr("AniList")); + + QLabel* sync_note_label = new QLabel(tr("Note: Weeaboo is unable to synchronize multiple services at the same time."), sync_group_box); + + QVBoxLayout* sync_layout = new QVBoxLayout; + sync_layout->addWidget(sync_combo_box_label); + sync_layout->addWidget(sync_combo_box); + sync_layout->addWidget(sync_note_label); + sync_group_box->setLayout(sync_layout); + + QVBoxLayout* full_layout = new QVBoxLayout; + full_layout->addWidget(sync_group_box); + full_layout->addStretch(); + result->setLayout(full_layout); + return result; +} + +QWidget* SettingsPageServices::CreateAniListPage() { + QWidget* result = new QWidget(this); + result->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + + QGroupBox* group_box = new QGroupBox(tr("Account"), result); + group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + QLabel* username_entry_label = new QLabel(tr("Username: (not your email address)"), group_box); + + QWidget* auth_widget = new QWidget(group_box); + username_entry = new QLineEdit(QString::fromUtf8(session.config.anilist.username.c_str()), auth_widget); + QPushButton* auth_button = new QPushButton(auth_widget); + connect(auth_button, &QPushButton::clicked, this, [this]{ + AniList a; + a.Authorize(); + }); + auth_button->setText(session.config.anilist.auth_token.empty() ? tr("Authorize...") : tr("Re-authorize...")); + + QHBoxLayout* auth_layout = new QHBoxLayout; + auth_layout->addWidget(username_entry); + auth_layout->addWidget(auth_button); + auth_widget->setLayout(auth_layout); + + QLabel* note_label = new QLabel(tr("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; + layout->addWidget(username_entry_label); + layout->addWidget(auth_widget); + layout->addWidget(note_label); + group_box->setLayout(layout); + + QVBoxLayout* full_layout = new QVBoxLayout; + full_layout->addWidget(group_box); + full_layout->addStretch(); + result->setLayout(full_layout); + return result; +} + +void SettingsPageServices::SaveInfo() { + session.config.anilist.username = username_entry->displayText().toStdString(); + session.config.service = static_cast(sync_combo_box->currentIndex()+1); +} + +SettingsPageServices::SettingsPageServices(QWidget* parent) + : SettingsPage(parent, tr("Services")) { + AddTab(CreateMainPage(), tr("Main")); + AddTab(CreateAniListPage(), tr("AniList")); +} diff -r 51ae25154b70 -r 1d82f6e04d7d src/include/anilist.h --- a/src/include/anilist.h Sat Aug 12 13:10:34 2023 -0400 +++ b/src/include/anilist.h Wed Aug 16 00:49:17 2023 -0400 @@ -5,7 +5,7 @@ #include "json.h" class AniList { public: - int Authorize(); + static int Authorize(); int GetUserId(std::string name); int UpdateAnimeList(std::vector* anime_lists, int id); diff -r 51ae25154b70 -r 1d82f6e04d7d src/include/anime.h --- a/src/include/anime.h Sat Aug 12 13:10:34 2023 -0400 +++ b/src/include/anime.h Wed Aug 16 00:49:17 2023 -0400 @@ -2,8 +2,11 @@ #define __anime_h #include #include +#include +#include #include "date.h" #include "window.h" +#include "progress.h" enum AnimeWatchingStatus { CURRENT, @@ -54,14 +57,14 @@ Date started; Date completed; int updated; /* this should be 64-bit */ - std::wstring notes; + std::string notes; /* Useful information */ int id; struct { - std::wstring romaji; - std::wstring english; - std::wstring native; + std::string romaji; + std::string english; + std::string native; } title; int episodes; enum AnimeAiringStatus airing; @@ -71,8 +74,10 @@ enum AnimeFormat type; enum AnimeSeason season; int audience_score; - std::wstring synopsis; + std::string synopsis; int duration; + + std::string GetUserPreferredTitle(); }; /* This is a simple wrapper on a vector that provides @@ -96,19 +101,44 @@ bool AnimeInList(int id); Anime& operator[](size_t index); const Anime& operator[](size_t index) const; - std::wstring name; + std::string name; - private: + protected: std::vector anime_list; std::map anime_id_to_anime; }; +class AnimeListWidgetDelegate : public QStyledItemDelegate { + Q_OBJECT + + public: + explicit AnimeListWidgetDelegate(QObject *parent); + + QWidget *createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const override; + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + + protected: + AnimeProgressBar progress_bar; +}; + +class AnimeListWidgetSortFilter : public QSortFilterProxyModel +{ + Q_OBJECT + + public: + AnimeListWidgetSortFilter(QObject *parent = nullptr); + + protected: + bool lessThan(const QModelIndex &l, const QModelIndex &r) const override; +}; + class AnimeListWidgetModel : public QAbstractListModel { Q_OBJECT public: enum columns { AL_TITLE, AL_PROGRESS, + AL_EPISODES, AL_SCORE, AL_AVG_SCORE, AL_TYPE, @@ -123,7 +153,6 @@ AnimeListWidgetModel(QWidget* parent, AnimeList* alist); ~AnimeListWidgetModel() override = default; - //QVariant headerData(const int section, const Qt::Orientation orientation, const int role) const; int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role) const override; @@ -150,6 +179,7 @@ private: AnimeListWidgetModel* model = nullptr; + AnimeListWidgetSortFilter* sort_model = nullptr; }; class AnimeListPage : public QTabWidget { diff -r 51ae25154b70 -r 1d82f6e04d7d src/include/config.h --- a/src/include/config.h Sat Aug 12 13:10:34 2023 -0400 +++ b/src/include/config.h Wed Aug 16 00:49:17 2023 -0400 @@ -5,7 +5,8 @@ whatever reason, so I'll just leave it here */ enum AnimeListServices { NONE, - ANILIST + ANILIST, + NB_SERVICES }; /* todo: make this a class enum */ diff -r 51ae25154b70 -r 1d82f6e04d7d src/include/date.h --- a/src/include/date.h Sat Aug 12 13:10:34 2023 -0400 +++ b/src/include/date.h Wed Aug 16 00:49:17 2023 -0400 @@ -1,23 +1,27 @@ -#ifndef __date_h -#define __date_h -#include -#include -class Date { - public: - Date(); - Date(int32_t y); - Date(int32_t y, int8_t m, int8_t d); - void SetYear(int32_t y); - void SetMonth(int8_t m); - void SetDay(int8_t d); - int32_t GetYear(); - int8_t GetMonth(); - int8_t GetDay(); - QDate GetAsQDate(); - - private: - int32_t year = -1; - int8_t month = -1; - int8_t day = -1; -}; -#endif // __date_h \ No newline at end of file +#ifndef __date_h +#define __date_h +#include +#include +class Date { + public: + Date(); + Date(int32_t y); + Date(int32_t y, int8_t m, int8_t d); + void SetYear(int32_t y); + void SetMonth(int8_t m); + void SetDay(int8_t d); + int32_t GetYear() const; + int8_t GetMonth() const; + int8_t GetDay() const; + QDate GetAsQDate(); + bool operator< (const Date& other) const; + bool operator> (const Date& other) const; + bool operator<= (const Date& other) const; + bool operator>= (const Date& other) const; + + private: + int32_t year = -1; + int8_t month = -1; + int8_t day = -1; +}; +#endif // __date_h diff -r 51ae25154b70 -r 1d82f6e04d7d src/include/json.h --- a/src/include/json.h Sat Aug 12 13:10:34 2023 -0400 +++ b/src/include/json.h Wed Aug 16 00:49:17 2023 -0400 @@ -1,9 +1,8 @@ -#include "../../dep/json/json.h" - -namespace JSON { - std::string GetString(nlohmann::json const& json, std::string const& key); - int GetInt(nlohmann::json const& json, std::string const& key); - int64_t GetInt64(nlohmann::json const& json, std::string const& key); - bool GetBoolean(nlohmann::json const& json, std::string const& key); - double GetDouble(nlohmann::json const& json, std::string const& key); -} +#include "../../dep/json/json.h" + +namespace JSON { + std::string GetString(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr); + int GetInt(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr); + bool GetBoolean(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr); + double GetDouble(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr); +} diff -r 51ae25154b70 -r 1d82f6e04d7d src/include/progress.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/include/progress.h Wed Aug 16 00:49:17 2023 -0400 @@ -0,0 +1,15 @@ +#ifndef __progress_h +#define __progress_h +#include +#include +#include +#include +class AnimeProgressBar { + public: + AnimeProgressBar(); + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QString &text, const int progress, const int episodes) const; + + private: + QProgressBar dummy_progress; +}; +#endif // __progress_h diff -r 51ae25154b70 -r 1d82f6e04d7d src/include/settings.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/include/settings.h Wed Aug 16 00:49:17 2023 -0400 @@ -0,0 +1,60 @@ +#ifndef __settings_h +#define __settings_h +#include +#include +#include +#include +#include +#include +#include +#include "sidebar.h" +class SettingsPage : public QWidget { + Q_OBJECT + + public: + SettingsPage(QWidget* parent = nullptr, QString title = ""); + void SetTitle(QString title); + virtual void SaveInfo(); + void AddTab(QWidget* tab, QString title = ""); + + private: + QLabel* page_title; + QTabWidget* tab_widget; +}; + +class SettingsPageServices : public SettingsPage { + public: + SettingsPageServices(QWidget* parent = nullptr); + void SaveInfo() override; + + private: + QWidget* CreateMainPage(); + QWidget* CreateAniListPage(); + QLineEdit* username_entry; + QComboBox* sync_combo_box; +}; + +class SettingsPageApplication : public SettingsPage { + public: + SettingsPageApplication(QWidget* parent = nullptr); + void SaveInfo() override; + + private: + QWidget* CreateAnimeListPage(); +}; + +class SettingsDialog : public QDialog { + Q_OBJECT + + public: + SettingsDialog(QWidget* parent = nullptr); + QWidget* CreateServicesMainPage(QWidget* parent); + void OnSidebar(QListWidgetItem* item); + void OnOK(); + + private: + std::vector pages; + QHBoxLayout* layout; + SideBar* sidebar; +}; +#endif // __settings_h \ No newline at end of file diff -r 51ae25154b70 -r 1d82f6e04d7d src/include/sidebar.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/include/sidebar.h Wed Aug 16 00:49:17 2023 -0400 @@ -0,0 +1,10 @@ +#ifndef __sidebar_h +#define __sidebar_h +#include +class SideBar : public QListWidget { + public: + SideBar(QWidget *parent = nullptr); + QListWidgetItem* AddItem(QString name, QIcon icon = QIcon()); + QListWidgetItem* AddSeparator(); +}; +#endif // __sidebar_h diff -r 51ae25154b70 -r 1d82f6e04d7d src/include/time_utils.h --- a/src/include/time_utils.h Sat Aug 12 13:10:34 2023 -0400 +++ b/src/include/time_utils.h Wed Aug 16 00:49:17 2023 -0400 @@ -1,20 +1,20 @@ -#ifndef __duration_h -#define __duration_h -#include -#include -namespace Time { - class Duration { - public: - Duration(int64_t l); - int64_t InSeconds(); - int64_t InMinutes(); - int64_t InHours(); - int64_t InDays(); - std::string AsRelativeString(); - - private: - int64_t length; - }; - int64_t GetSystemTime(); -}; +#ifndef __duration_h +#define __duration_h +#include +#include +namespace Time { + class Duration { + public: + Duration(int64_t l); + int64_t InSeconds(); + int64_t InMinutes(); + int64_t InHours(); + int64_t InDays(); + std::string AsRelativeString(); + + private: + int64_t length; + }; + int64_t GetSystemTime(); +}; #endif // __duration_h \ No newline at end of file diff -r 51ae25154b70 -r 1d82f6e04d7d src/include/ui_utils.h --- a/src/include/ui_utils.h Sat Aug 12 13:10:34 2023 -0400 +++ b/src/include/ui_utils.h Wed Aug 16 00:49:17 2023 -0400 @@ -5,7 +5,9 @@ #include #include #include +#include namespace UiUtils { + QIcon CreateSideBarIcon(const char* file); bool IsInDarkMode(); std::string GetLengthFromQDateTime(QDateTime stamp); QPlainTextEdit* CreateTextParagraph(QWidget* parent, QString title, QString data, QPoint point, QSize size); diff -r 51ae25154b70 -r 1d82f6e04d7d src/include/window.h --- a/src/include/window.h Sat Aug 12 13:10:34 2023 -0400 +++ b/src/include/window.h Wed Aug 16 00:49:17 2023 -0400 @@ -21,6 +21,7 @@ # include # include # include +# include # include "config.h" //# include "statistics.h" //# include "now_playing.h" @@ -34,6 +35,7 @@ void closeEvent(QCloseEvent* event) override; private: + QWidget* main_widget; QWidget* anime_list_page; }; diff -r 51ae25154b70 -r 1d82f6e04d7d src/json.cpp --- a/src/json.cpp Sat Aug 12 13:10:34 2023 -0400 +++ b/src/json.cpp Wed Aug 16 00:49:17 2023 -0400 @@ -2,31 +2,27 @@ namespace JSON { -std::string GetString(nlohmann::json const& json, std::string const& key) { - auto item = json.find(key); - if (item != json.end() && item->is_string()) - return item->get(); +std::string GetString(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr) { + if (json.contains(ptr) && json[ptr].is_string()) + return json[ptr].get(); else return ""; } -int GetInt(nlohmann::json const& json, std::string const& key) { - auto item = json.find(key); - if (item != json.end() && item->is_number()) - return item->get(); +int GetInt(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr) { + if (json.contains(ptr) && json[ptr].is_number()) + return json[ptr].get(); else return 0; } -bool GetBoolean(nlohmann::json const& json, std::string const& key) { - auto item = json.find(key); - if (item != json.end() && item->is_boolean()) - return item->get(); +bool GetBoolean(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr) { + if (json.contains(ptr) && json[ptr].is_boolean()) + return json[ptr].get(); else return false; } -double GetDouble(nlohmann::json const& json, std::string const& key) { - auto item = json.find(key); - if (item != json.end() && item->is_number()) - return item->get(); +double GetDouble(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr) { + if (json.contains(ptr) && json[ptr].is_number()) + return json[ptr].get(); else return 0; } diff -r 51ae25154b70 -r 1d82f6e04d7d src/main.cpp --- a/src/main.cpp Sat Aug 12 13:10:34 2023 -0400 +++ b/src/main.cpp Wed Aug 16 00:49:17 2023 -0400 @@ -1,6 +1,9 @@ #include "window.h" #include "config.h" #include "anime.h" +#include "sidebar.h" +#include "ui_utils.h" +#include "settings.h" #if MACOSX #include "sys/osx/dark_theme.h" #elif WIN32 @@ -17,6 +20,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { + main_widget = new QWidget(parent); /* Menu Bar */ QAction* action; QMenuBar* menubar = new QMenuBar(parent); @@ -40,8 +44,8 @@ menu->addSeparator(); submenu = menu->addMenu("&AniList"); - action = menu->addAction("Go to my &profile"); - action = menu->addAction("Go to my &stats"); + action = submenu->addAction("Go to my &profile"); + action = submenu->addAction("Go to my &stats"); submenu = menu->addMenu("&Kitsu"); action = submenu->addAction("Go to my &feed"); @@ -69,11 +73,34 @@ menu->addSeparator(); - action = menu->addAction("&Settings"); + action = menu->addAction("&Settings", [this]{ + SettingsDialog dialog(this); + dialog.exec(); + }); setMenuBar(menubar); /* Side toolbar */ + SideBar* sidebar = new SideBar(main_widget); + sidebar->AddItem("Now Playing", UiUtils::CreateSideBarIcon(":/icons/16x16/film.png")); + sidebar->AddSeparator(); + sidebar->AddItem("Anime List", UiUtils::CreateSideBarIcon(":/icons/16x16/document-list.png")); + sidebar->AddItem("History", UiUtils::CreateSideBarIcon(":/icons/16x16/clock-history-frame.png")); + sidebar->AddItem("Statistics", UiUtils::CreateSideBarIcon(":/icons/16x16/chart.png")); + sidebar->AddSeparator(); + sidebar->AddItem("Search", UiUtils::CreateSideBarIcon(":/icons/16x16/magnifier.png")); + sidebar->AddItem("Seasons", UiUtils::CreateSideBarIcon(":/icons/16x16/calendar.png")); + sidebar->AddItem("Torrents", UiUtils::CreateSideBarIcon(":/icons/16x16/feed.png")); + sidebar->setFixedWidth(128); + sidebar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + + anime_list_page = new AnimeListPage(parent); + + QHBoxLayout* layout = new QHBoxLayout(main_widget); + layout->addWidget(sidebar, 0, Qt::AlignLeft | Qt::AlignTop); + layout->addWidget(anime_list_page); + SetActivePage(main_widget); +/* QToolBar* toolbar = new QToolBar(parent); QActionGroup* tb_action_group = new QActionGroup(toolbar); @@ -112,9 +139,14 @@ toolbar->setMovable(false); toolbar->setFloatable(false); + toolbar->setMinimumSize(QSize(140, 0)); + toolbar->setObjectName("sidebar"); + toolbar->setStyleSheet("QToolBar#sidebar{margin: 6px;}"); + //toolbar->setFrameShape(QFrame::NoFrame); + toolbar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Maximum); addToolBar(Qt::LeftToolBarArea, toolbar); - +*/ ThemeChanged(); } @@ -174,6 +206,9 @@ } } #else + /* 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) */ SetStyleSheet(LIGHT); #endif break; diff -r 51ae25154b70 -r 1d82f6e04d7d src/progress.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/progress.cpp Wed Aug 16 00:49:17 2023 -0400 @@ -0,0 +1,39 @@ +#include "progress.h" + +#include +#include +#include +#include +#include +#include + +AnimeProgressBar::AnimeProgressBar() { +#if (defined(WIN32) || defined(MACOSX)) + auto *fusionStyle = new QProxyStyle {"fusion"}; + fusionStyle->setParent(&dummy_progress); + dummy_progress.setStyle(fusionStyle); +#endif +} + +void AnimeProgressBar::paint(QPainter *painter, const QStyleOptionViewItem &option, const QString &text, const int progress, const int episodes) const { + QStyleOptionProgressBar styleOption; + styleOption.initFrom(&dummy_progress); + + styleOption.maximum = episodes; + styleOption.minimum = 0; + styleOption.progress = progress; + styleOption.text = text; + styleOption.textVisible = true; + + styleOption.rect = option.rect; + styleOption.state = option.state; + + const bool enabled = option.state.testFlag(QStyle::State_Enabled); + styleOption.palette.setCurrentColorGroup(enabled ? QPalette::Active : QPalette::Disabled); + + painter->save(); + const QStyle *style = dummy_progress.style(); + style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, option.widget); + style->drawControl(QStyle::CE_ProgressBar, &styleOption, painter, &dummy_progress); + painter->restore(); +} \ No newline at end of file diff -r 51ae25154b70 -r 1d82f6e04d7d src/sidebar.cpp --- a/src/sidebar.cpp Sat Aug 12 13:10:34 2023 -0400 +++ b/src/sidebar.cpp Wed Aug 16 00:49:17 2023 -0400 @@ -0,0 +1,35 @@ +#include +#include +#include +#include "sidebar.h" + +SideBar::SideBar(QWidget *parent) + : QListWidget(parent) +{ + setObjectName("sidebar"); + setFrameShape(QFrame::NoFrame); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + viewport()->setAutoFillBackground(false); + setStyleSheet("font-size: 12px"); +} + +QListWidgetItem* SideBar::AddItem(QString name, QIcon icon) { + QListWidgetItem* item = new QListWidgetItem(this); + item->setText(name); + if (!icon.isNull()) + item->setIcon(icon); + return item; +} + +QListWidgetItem* SideBar::AddSeparator() { + QListWidgetItem* item = new QListWidgetItem(this); + item->setFlags(item->flags() & ~Qt::ItemIsEnabled); + setStyleSheet("QListWidget::item:disabled {background: transparent;}"); + QFrame* line = new QFrame(this); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + line->setEnabled(false); + setItemWidget(item, line); + return item; +} diff -r 51ae25154b70 -r 1d82f6e04d7d src/time.cpp --- a/src/time.cpp Sat Aug 12 13:10:34 2023 -0400 +++ b/src/time.cpp Wed Aug 16 00:49:17 2023 -0400 @@ -1,64 +1,64 @@ -#include "time_utils.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((double)length / 60.0); -} - -int64_t Duration::InHours() { - return std::llround((double)length / 3600.0); -} - -int64_t Duration::InDays() { - return std::llround((double)length / 86400.0); -} - -int64_t GetSystemTime() { - assert(sizeof(int64_t) >= sizeof(time_t)); - time_t t = std::time(nullptr); - return *reinterpret_cast(&t); -} - +#include "time_utils.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((double)length / 60.0); +} + +int64_t Duration::InHours() { + return std::llround((double)length / 3600.0); +} + +int64_t Duration::InDays() { + return std::llround((double)length / 86400.0); +} + +int64_t GetSystemTime() { + assert(sizeof(int64_t) >= sizeof(time_t)); + time_t t = std::time(nullptr); + return *reinterpret_cast(&t); +} + } \ No newline at end of file diff -r 51ae25154b70 -r 1d82f6e04d7d src/ui_utils.cpp --- a/src/ui_utils.cpp Sat Aug 12 13:10:34 2023 -0400 +++ b/src/ui_utils.cpp Wed Aug 16 00:49:17 2023 -0400 @@ -6,6 +6,14 @@ #include "sys/win32/dark_theme.h" #endif +QIcon UiUtils::CreateSideBarIcon(const char* file) { + QPixmap pixmap(file, "PNG"); + QIcon result; + result.addPixmap(pixmap, QIcon::Normal); + result.addPixmap(pixmap, QIcon::Selected); + return result; +} + bool UiUtils::IsInDarkMode() { if (session.config.theme != OS) return (session.config.theme == DARK); @@ -48,8 +56,12 @@ QPlainTextEdit* paragraph = new QPlainTextEdit(data, parent); paragraph->setTextInteractionFlags(Qt::NoTextInteraction); + paragraph->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents); paragraph->setWordWrapMode(QTextOption::NoWrap); paragraph->setFrameShape(QFrame::NoFrame); + paragraph->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + paragraph->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + paragraph->setStyleSheet("background: transparent;"); paragraph->move(point.x()+12, point.y()+32); paragraph->resize(size.width()-12, size.height()-32); return paragraph; @@ -60,15 +72,23 @@ QPlainTextEdit* label_t = new QPlainTextEdit(label, parent); label_t->setTextInteractionFlags(Qt::NoTextInteraction); + label_t->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents); label_t->setWordWrapMode(QTextOption::NoWrap); label_t->setFrameShape(QFrame::NoFrame); + label_t->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + label_t->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + label_t->setStyleSheet("background: transparent;"); label_t->move(point.x()+12, point.y()+32); label_t->resize(90, size.height()-32); QPlainTextEdit* paragraph = new QPlainTextEdit(data, parent); paragraph->setTextInteractionFlags(Qt::NoTextInteraction); + paragraph->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents); paragraph->setWordWrapMode(QTextOption::NoWrap); paragraph->setFrameShape(QFrame::NoFrame); + paragraph->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + paragraph->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + paragraph->setStyleSheet("background: transparent;"); paragraph->move(point.x()+102, point.y()+32); paragraph->resize(size.width()-102, size.height()-32); return paragraph; @@ -82,6 +102,9 @@ QPlainTextEdit* text_edit = new QPlainTextEdit(data, parent); text_edit->setReadOnly(true); text_edit->setFrameShape(QFrame::NoFrame); + text_edit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + text_edit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + text_edit->setStyleSheet("background: transparent;"); text_edit->move(point.x()+12, point.y()+32); text_edit->resize(size.width()-12, size.height()-32); return text_edit; diff -r 51ae25154b70 -r 1d82f6e04d7d src/window.cpp --- a/src/window.cpp Sat Aug 12 13:10:34 2023 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,158 +0,0 @@ -#include "window.h" -#include -#include "page.h" -#include "config.h" -#include "anime.h" -#include "statistics.h" -#include "now_playing.h" -#include "16x16/document-list.png.h" -#include "16x16/film.png.h" -#include "16x16/chart.png.h" -#include "16x16/clock-history-frame.png.h" -#include "16x16/magnifier.png.h" -#include "16x16/calendar.png.h" -#include "16x16/feed.png.h" -#include "24x24/arrow-circle-double-135.png.h" -#include "24x24/folder-open.png.h" -#include "24x24/gear.png.h" - -Config Weeaboo::config = Config(); - -WeeabooFrame::WeeabooFrame(const wxString& title, const wxPoint& pos, const wxSize& size) - : wxFrame(NULL, wxID_ANY, title, pos, size) { - /* ---- Menu Bar ---- */ - wxMenu* library_folders_submenu = new wxMenu; - library_folders_submenu->Append(ID_AddLibraryFolder, "&Add library folder"); - library_folders_submenu->Append(ID_ScanLibraryFolders, "&Rescan library folders"); - - wxMenu* file_menu = new wxMenu; - file_menu->AppendSubMenu(library_folders_submenu, "&Library folders"); - file_menu->Append(ID_SyncAnimeList, "&Sync anime list\tCtrl+S"); - file_menu->AppendSeparator(); - file_menu->Append(ID_PlayNextEpisode, "Play &next episode\tCtrl+N"); - file_menu->Append(ID_PlayRandomEpisode, "Play &random episode\tCtrl+R"); - file_menu->AppendSeparator(); - file_menu->Append(wxID_EXIT); - - wxMenu* help_menu = new wxMenu; - help_menu->Append(wxID_ABOUT); - - wxMenuBar* menu_bar = new wxMenuBar; - menu_bar->Append(file_menu, "&File"); - menu_bar->Append(help_menu, "&Help"); - - SetMenuBar(menu_bar); - - /* Toolbar */ - wxToolBar* top_toolbar = CreateToolBar(); - top_toolbar->SetToolBitmapSize(wxSize(24,24)); - top_toolbar->AddTool(ID_ToolbarSync, wxT("Sync"), wxBITMAP_PNG_FROM_DATA(arrow_circle_double_135)); - top_toolbar->Realize(); - - /* ---- Sidebar ---- */ - /* This first panel is only for the sizer... */ - wxPanel* left_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(140, 600), wxTAB_TRAVERSAL, wxPanelNameStr); - wxPanel* left_panel_inside = new wxPanel(left_panel, wxID_ANY, wxPoint(6, 6), wxSize(128, 588), wxTAB_TRAVERSAL); - wxToolBar* left_toolbar = new wxToolBar(left_panel_inside, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTB_FLAT | wxTB_VERTICAL | wxTB_HORZ_TEXT | wxTB_NODIVIDER); - left_toolbar->SetMargins(6, 6); - left_toolbar->SetToolBitmapSize(wxSize(16,16)); - left_toolbar->AddRadioTool(ID_NowPlaying, "Now Playing", wxBITMAP_PNG_FROM_DATA(film)); - left_toolbar->AddRadioTool(ID_AnimeList, "Anime List", wxBITMAP_PNG_FROM_DATA(document_list)); - left_toolbar->AddRadioTool(ID_History, "History", wxBITMAP_PNG_FROM_DATA(clock_history_frame)); - left_toolbar->AddRadioTool(ID_Statistics, "Statistics", wxBITMAP_PNG_FROM_DATA(chart)); - left_toolbar->AddRadioTool(ID_Search, "Search", wxBITMAP_PNG_FROM_DATA(magnifier)); - left_toolbar->AddRadioTool(ID_Seasons, "Seasons", wxBITMAP_PNG_FROM_DATA(calendar)); - left_toolbar->AddRadioTool(ID_Torrents, "Torrents", wxBITMAP_PNG_FROM_DATA(feed)); - - /* ---- Initialize our pages ---- */ - wxPanel* right_panel = new wxPanel(this, wxID_ANY, wxPoint(140, 0), wxSize(460, 600), wxTAB_TRAVERSAL, wxPanelNameStr); - now_playing = new NowPlaying(&pages[PAGE_NOW_PLAYING], right_panel); - anime_list = new AnimeListPage(&pages[PAGE_ANIME_LIST], right_panel); - anime_list->SyncAnimeList(); - anime_list->LoadAnimeList(this); - statistics = new Statistics(&pages[PAGE_STATISTICS], right_panel); - - status.current_page = PAGE_ANIME_LIST; // The below function depends on this value being set? - set_page(PAGE_ANIME_LIST); - left_toolbar->ToggleTool(ID_AnimeList, true); - left_toolbar->Realize(); - - wxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(left_panel, 0, wxEXPAND, 10); - sizer->Add(right_panel, 1, wxEXPAND, 10); - sizer->SetMinSize(600, 600); - this->SetSizer(sizer); - sizer->SetSizeHints(this); - -} - -bool Weeaboo::OnInit() { - config.Load(); - if (curl_global_init(CURL_GLOBAL_DEFAULT) != 0) { - wxMessageBox("libcurl failed to initialize!", - "Error", wxOK | wxICON_ERROR); - } - wxSystemOptions::SetOption("msw.remap", - wxSystemOptions::HasOption("msw.remap") - ? wxSystemOptions::GetOptionInt("msw.remap") - : wxDisplayDepth() <= 8 ? 1 : 2 - ); - wxImage::AddHandler(new wxPNGHandler); - frame = new WeeabooFrame("Weeaboo", wxPoint(50, 50), wxSize(450, 340)); - frame->Show(true); - return true; -} - -#define TOOLBAR_HANDLER(name, page) \ -void WeeabooFrame::name(wxCommandEvent& event) { \ - set_page(page); \ -} -TOOLBAR_HANDLER(OnNowPlaying, PAGE_NOW_PLAYING) -TOOLBAR_HANDLER(OnAnimeList, PAGE_ANIME_LIST) -TOOLBAR_HANDLER(OnHistory, PAGE_HISTORY) -TOOLBAR_HANDLER(OnStatistics, PAGE_STATISTICS) -TOOLBAR_HANDLER(OnSearch, PAGE_SEARCH) -TOOLBAR_HANDLER(OnSeasons, PAGE_SEASONS) -TOOLBAR_HANDLER(OnTorrents, PAGE_TORRENTS) -#undef TOOLBAR_HANDLER - -void WeeabooFrame::OnClose(wxCloseEvent& event) { - Weeaboo::config.Save(); - curl_global_cleanup(); - delete anime_list; - event.Skip(); -} - -void WeeabooFrame::OnExit(wxCommandEvent& event) { - Close(true); -} - -void WeeabooFrame::OnAbout(wxCommandEvent& event) { - wxMessageBox("To be written", - "About Weeaboo", wxOK | wxICON_INFORMATION); -} - -void WeeabooFrame::OnAddFolder(wxCommandEvent& event) { - wxLogMessage("OnAddFolder"); -} - -void WeeabooFrame::OnScanFolders(wxCommandEvent& event) { - wxLogMessage("OnScanFolders"); -} - -void WeeabooFrame::OnNextEpisode(wxCommandEvent& event) { - wxLogMessage("OnNextEpisode"); -} - -void WeeabooFrame::OnSyncList(wxCommandEvent& event) { - anime_list->SyncAnimeList(); - anime_list->LoadAnimeList(this); -} - -void WeeabooFrame::OnRandomEpisode(wxCommandEvent& event) { - wxLogMessage("OnRandomEpisode"); -} - -AnimeListPage* WeeabooFrame::GetAnimeList() { - return anime_list; -}