Mercurial > minori
diff src/anime.cpp @ 2:23d0d9319a00
Update
Also converted everything to LF from CRLF
author | Paper <mrpapersonic@gmail.com> |
---|---|
date | Sat, 12 Aug 2023 03:16:26 -0400 |
parents | 1ae666fdf9e2 |
children | 190ded9438c0 |
line wrap: on
line diff
--- a/src/anime.cpp Tue Aug 08 19:49:15 2023 -0400 +++ b/src/anime.cpp Sat Aug 12 03:16:26 2023 -0400 @@ -1,513 +1,539 @@ -#include <chrono> -#include <string> -#include <vector> -#include <cmath> -#include "window.h" -#include "anilist.h" -#include "config.h" -#include "anime.h" -//#include "information.h" - -std::map<enum AnimeWatchingStatus, std::string> AnimeWatchingToStringMap = { - {CURRENT, "Watching"}, - {PLANNING, "Planning"}, - {COMPLETED, "Completed"}, - {DROPPED, "Dropped"}, - {PAUSED, "On hold"}, - {REPEATING, "Rewatching"} -}; - -std::map<enum AnimeAiringStatus, std::string> AnimeAiringToStringMap = { - {FINISHED, "Finished"}, - {RELEASING, "Airing"}, - {NOT_YET_RELEASED, "Not aired yet"}, - {CANCELLED, "Cancelled"}, - {HIATUS, "On hiatus"} -}; - -std::map<enum AnimeSeason, std::string> AnimeSeasonToStringMap = { - {WINTER, "Winter"}, - {SPRING, "Spring"}, - {SUMMER, "Summer"}, - {FALL, "Fall"} -}; - -std::map<enum AnimeFormat, std::string> AnimeFormatToStringMap = { - {TV, "TV"}, - {TV_SHORT, "TV short"}, - {MOVIE, "Movie"}, - {SPECIAL, "Special"}, - {OVA, "OVA"}, - {ONA, "ONA"}, - {MUSIC, "Music video"}, - /* these should NEVER be in the list. naybe we should - remove them? */ - {MANGA, "Manga"}, - {NOVEL, "Novel"}, - {ONE_SHOT, "One-shot"} -}; - -Anime::Anime() {} -Anime::Anime(const Anime& a) { - status = a.status; - progress = a.progress; - score = a.score; - started = a.started; - completed = a.completed; - notes = a.notes; - id = a.id; - title = a.title; - episodes = a.episodes; - airing = a.airing; - air_date = a.air_date; - genres = a.genres; - producers = a.producers; - type = a.type; - season = a.season; - audience_score = a.audience_score; - synopsis = a.synopsis; - duration = a.duration; -} - -void AnimeList::Add(Anime& anime) { - if (anime_id_to_anime.contains(anime.id)) - return; - anime_list.push_back(anime); - anime_id_to_anime.emplace(anime.id, &anime); -} - -void AnimeList::Insert(size_t pos, Anime& anime) { - if (anime_id_to_anime.contains(anime.id)) - return; - anime_list.insert(anime_list.begin()+pos, anime); - anime_id_to_anime.emplace(anime.id, &anime); -} - -void AnimeList::Delete(size_t index) { - anime_list.erase(anime_list.begin()+index); -} - -void AnimeList::Clear() { - anime_list.clear(); -} - -size_t AnimeList::Size() const { - return anime_list.size(); -} - -std::vector<Anime>::iterator AnimeList::begin() noexcept { - return anime_list.begin(); -} - -std::vector<Anime>::iterator AnimeList::end() noexcept { - return anime_list.end(); -} - -std::vector<Anime>::const_iterator AnimeList::cbegin() noexcept { - return anime_list.cbegin(); -} - -std::vector<Anime>::const_iterator AnimeList::cend() noexcept { - return anime_list.cend(); -} - -AnimeList::AnimeList() {} -AnimeList::AnimeList(const AnimeList& l) { - for (int i = 0; i < l.Size(); i++) { - anime_list.push_back(Anime(l[i])); - } - name = l.name; -} - -AnimeList::~AnimeList() { - anime_list.clear(); - anime_list.shrink_to_fit(); -} - -Anime* AnimeList::AnimeById(int id) { - return anime_id_to_anime.contains(id) ? anime_id_to_anime[id] : nullptr; -} - -bool AnimeList::AnimeInList(int id) { - return anime_id_to_anime.contains(id); -} - -Anime& AnimeList::operator[](std::size_t index) { - return anime_list.at(index); -} - -const Anime& AnimeList::operator[](std::size_t index) const { - return anime_list.at(index); -} - -/* ------------------------------------------------------------------------- */ - -/* Thank you qBittorrent for having a great example of a - widget model. */ -AnimeListWidgetModel::AnimeListWidgetModel (QWidget* parent, AnimeList* alist) - : QAbstractListModel(parent) - , list(*alist) { - return; -} - -int AnimeListWidgetModel::rowCount(const QModelIndex& parent) const { - return list.Size(); -} - -int AnimeListWidgetModel::columnCount(const QModelIndex& parent) const { - return NB_COLUMNS; -} - -QVariant AnimeListWidgetModel::headerData(const int section, const Qt::Orientation orientation, const int role) const { - if (role == Qt::DisplayRole) { - switch (section) { - case AL_TITLE: - return tr("Anime title"); - case AL_PROGRESS: - return tr("Progress"); - case AL_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_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); -} - -Anime* AnimeListWidgetModel::GetAnimeFromIndex(const QModelIndex& index) { - return (!index.isValid()) ? &(list[index.row()]) : nullptr; -} - -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.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((int)list[index.row()].air_date.year()); - case AL_AVG_SCORE: - return list[index.row()].audience_score; - case AL_STARTED: - /* why c++20 chrono is stinky: the game */ - return QDate(int(list[index.row()].started.year()), static_cast<int>((unsigned int)list[index.row()].started.month()), static_cast<int>((unsigned int)list[index.row()].started.day())); - case AL_COMPLETED: - return QDate(int(list[index.row()].completed.year()), static_cast<int>((unsigned int)list[index.row()].completed.month()), static_cast<int>((unsigned int)list[index.row()].completed.day())); - 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: - return QVariant(Qt::AlignRight | Qt::AlignVCenter); - default: - break; - } - } - return QVariant(); -} - -/* this should ALWAYS be called if the list is edited */ -void AnimeListWidgetModel::Update() { - -} - -/* 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; - - for (int i = 0, end = header()->count(); i < end; i++) - { - if (!isColumnHidden(i)) - count++; - } - - return count; -} - -void AnimeListWidget::SetColumnDefaults() { - setColumnHidden(AnimeListWidgetModel::AL_SEASON, false); - setColumnHidden(AnimeListWidgetModel::AL_TYPE, false); - setColumnHidden(AnimeListWidgetModel::AL_UPDATED, false); - setColumnHidden(AnimeListWidgetModel::AL_PROGRESS, false); - setColumnHidden(AnimeListWidgetModel::AL_SCORE, false); - setColumnHidden(AnimeListWidgetModel::AL_TITLE, false); - setColumnHidden(AnimeListWidgetModel::AL_AVG_SCORE, true); - setColumnHidden(AnimeListWidgetModel::AL_STARTED, true); - setColumnHidden(AnimeListWidgetModel::AL_COMPLETED, true); - setColumnHidden(AnimeListWidgetModel::AL_UPDATED, true); - setColumnHidden(AnimeListWidgetModel::AL_NOTES, true); -} - -void AnimeListWidget::DisplayColumnHeaderMenu() { - QMenu *menu = new QMenu(this); - menu->setAttribute(Qt::WA_DeleteOnClose); - menu->setTitle(tr("Column visibility")); - menu->setToolTipsVisible(true); - - for (int i = 0; i < AnimeListWidgetModel::NB_COLUMNS; i++) - { - if (i == AnimeListWidgetModel::AL_TITLE) - continue; - const auto column_name = model->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); - QAction *action = menu->addAction(column_name, this, [this, i](const bool checked) { - if (!checked && (VisibleColumnsCount() <= 1)) - return; - - setColumnHidden(i, !checked); - - if (checked && (columnWidth(i) <= 5)) - resizeColumnToContents(i); - - // SaveSettings(); - }); - action->setCheckable(true); - action->setChecked(!isColumnHidden(i)); - } - - menu->addSeparator(); - QAction *resetAction = menu->addAction(tr("Reset to defaults"), this, [this]() - { - for (int i = 0, count = header()->count(); i < count; ++i) - { - SetColumnDefaults(); - } - // SaveSettings(); - }); - - menu->popup(QCursor::pos()); -} - -void AnimeListWidget::DisplayListMenu() { - /* throw out any other garbage */ - const QModelIndexList selected_items = selectionModel()->selectedRows(); - if (selected_items.size() != 1 || !selected_items.first().isValid()) - return; - - const QModelIndex index = model->index(selected_items.first().row()); - Anime* anime = model->GetAnimeFromIndex(index); - if (!anime) - return; - -} - -void AnimeListWidget::ItemDoubleClicked() { - /* throw out any other garbage */ - const QModelIndexList selected_items = selectionModel()->selectedRows(); - if (selected_items.size() != 1 || !selected_items.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()); - Anime* anime = model->GetAnimeFromIndex(index); - if (!anime) - return; - - /* todo: open information dialog... */ -} - -AnimeListWidget::AnimeListWidget(QWidget* parent, AnimeList* alist) - : QTreeView(parent) { - model = new AnimeListWidgetModel(parent, alist); - this->setModel(model); - setUniformRowHeights(true); - setAllColumnsShowFocus(false); - setSortingEnabled(true); - setSelectionMode(QAbstractItemView::ExtendedSelection); - setItemsExpandable(false); - setRootIsDecorated(false); - setContextMenuPolicy(Qt::CustomContextMenu); - connect(this, &QAbstractItemView::doubleClicked, this, &ItemDoubleClicked); - connect(this, &QWidget::customContextMenuRequested, this, &DisplayListMenu); - - /* Enter & return keys */ - connect(new QShortcut(Qt::Key_Return, this, nullptr, nullptr, Qt::WidgetShortcut), - &QShortcut::activated, this, &ItemDoubleClicked); - - connect(new QShortcut(Qt::Key_Enter, this, nullptr, nullptr, Qt::WidgetShortcut), - &QShortcut::activated, this, &ItemDoubleClicked); - - header()->setStretchLastSection(false); - header()->setContextMenuPolicy(Qt::CustomContextMenu); - connect(header(), &QWidget::customContextMenuRequested, this, &DisplayColumnHeaderMenu); - // if(!session.config.anime_list.columns) { - SetColumnDefaults(); - // } -} - -AnimeListPage::AnimeListPage(QWidget* parent) : QTabWidget (parent) { - setDocumentMode(true); - SyncAnimeList(); - for (AnimeList& list : anime_lists) { - addTab(new AnimeListWidget(this, &list), QString::fromWCharArray(list.name.c_str())); - } -} - -void AnimeListPage::SyncAnimeList() { - 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); - break; - } - } -} - -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(); - } -} - -int AnimeListPage::GetTotalAnimeAmount() { - int total = 0; - for (auto& list : anime_lists) { - total += list.Size(); - } - return total; -} - -int AnimeListPage::GetTotalEpisodeAmount() { - /* FIXME: this also needs to take into account rewatches... */ - int total = 0; - for (auto& list : anime_lists) { - for (auto& anime : list) { - total += anime.progress; - } - } - return total; -} - -/* Returns the total watched amount in minutes. */ -int AnimeListPage::GetTotalWatchedAmount() { - int total = 0; - for (auto& list : anime_lists) { - for (auto& anime : list) { - total += anime.duration*anime.progress; - } - } - 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 AnimeListPage::GetTotalPlannedAmount() { - int total = 0; - for (auto& list : anime_lists) { - for (auto& anime : list) { - total += anime.duration*(anime.episodes-anime.progress); - } - } - return total; -} - -double AnimeListPage::GetAverageScore() { - double avg = 0; - int amt = 0; - for (auto& list : anime_lists) { - for (auto& anime : list) { - avg += anime.score; - if (anime.score != 0) - amt++; - } - } - return avg/amt; -} - -double AnimeListPage::GetScoreDeviation() { - double squares_sum = 0, avg = GetAverageScore(); - int amt = 0; - for (auto& list : anime_lists) { - for (auto& anime : list) { - if (anime.score != 0) { - squares_sum += std::pow((double)anime.score - avg, 2); - amt++; - } - } - } - return (amt > 0) ? std::sqrt(squares_sum / amt) : 0; -} - -#include "moc_anime.cpp" +#include <chrono> +#include <string> +#include <vector> +#include <cmath> +#include "window.h" +#include "anilist.h" +#include "config.h" +#include "anime.h" +#include "date.h" +#include "time_utils.h" +#include "information.h" +#include "ui_utils.h" + +std::map<enum AnimeWatchingStatus, std::string> AnimeWatchingToStringMap = { + {CURRENT, "Watching"}, + {PLANNING, "Planning"}, + {COMPLETED, "Completed"}, + {DROPPED, "Dropped"}, + {PAUSED, "On hold"}, + {REPEATING, "Rewatching"} +}; + +std::map<enum AnimeAiringStatus, std::string> AnimeAiringToStringMap = { + {FINISHED, "Finished"}, + {RELEASING, "Airing"}, + {NOT_YET_RELEASED, "Not aired yet"}, + {CANCELLED, "Cancelled"}, + {HIATUS, "On hiatus"} +}; + +std::map<enum AnimeSeason, std::string> AnimeSeasonToStringMap = { + {UNKNOWN, "Unknown"}, + {WINTER, "Winter"}, + {SPRING, "Spring"}, + {SUMMER, "Summer"}, + {FALL, "Fall"} +}; + +std::map<enum AnimeFormat, std::string> AnimeFormatToStringMap = { + {TV, "TV"}, + {TV_SHORT, "TV short"}, + {MOVIE, "Movie"}, + {SPECIAL, "Special"}, + {OVA, "OVA"}, + {ONA, "ONA"}, + {MUSIC, "Music video"}, + /* these should NEVER be in the list. naybe we should + remove them? */ + {MANGA, "Manga"}, + {NOVEL, "Novel"}, + {ONE_SHOT, "One-shot"} +}; + +Anime::Anime() {} +Anime::Anime(const Anime& a) { + status = a.status; + progress = a.progress; + score = a.score; + started = a.started; + completed = a.completed; + updated = a.updated; + notes = a.notes; + id = a.id; + title = a.title; + episodes = a.episodes; + airing = a.airing; + air_date = a.air_date; + genres = a.genres; + producers = a.producers; + type = a.type; + season = a.season; + audience_score = a.audience_score; + synopsis = a.synopsis; + duration = a.duration; +} + +void AnimeList::Add(Anime& anime) { + if (anime_id_to_anime.contains(anime.id)) + return; + anime_list.push_back(anime); + anime_id_to_anime.emplace(anime.id, &anime); +} + +void AnimeList::Insert(size_t pos, Anime& anime) { + if (anime_id_to_anime.contains(anime.id)) + return; + anime_list.insert(anime_list.begin()+pos, anime); + anime_id_to_anime.emplace(anime.id, &anime); +} + +void AnimeList::Delete(size_t index) { + anime_list.erase(anime_list.begin()+index); +} + +void AnimeList::Clear() { + anime_list.clear(); +} + +size_t AnimeList::Size() const { + return anime_list.size(); +} + +std::vector<Anime>::iterator AnimeList::begin() noexcept { + return anime_list.begin(); +} + +std::vector<Anime>::iterator AnimeList::end() noexcept { + return anime_list.end(); +} + +std::vector<Anime>::const_iterator AnimeList::cbegin() noexcept { + return anime_list.cbegin(); +} + +std::vector<Anime>::const_iterator AnimeList::cend() noexcept { + return anime_list.cend(); +} + +AnimeList::AnimeList() {} +AnimeList::AnimeList(const AnimeList& l) { + for (int i = 0; i < l.Size(); i++) { + anime_list.push_back(Anime(l[i])); + } + name = l.name; +} + +AnimeList::~AnimeList() { + anime_list.clear(); + anime_list.shrink_to_fit(); +} + +Anime* AnimeList::AnimeById(int id) { + return anime_id_to_anime.contains(id) ? anime_id_to_anime[id] : nullptr; +} + +bool AnimeList::AnimeInList(int id) { + return anime_id_to_anime.contains(id); +} + +int AnimeList::GetAnimeIndex(Anime& anime) const { + for (int i = 0; i < Size(); i++) { + if (&anime_list.at(i) == &anime) { // lazy + return i; + } + } + return -1; +} + +Anime& AnimeList::operator[](std::size_t index) { + return anime_list.at(index); +} + +const Anime& AnimeList::operator[](std::size_t index) const { + return anime_list.at(index); +} + +/* ------------------------------------------------------------------------- */ + +/* Thank you qBittorrent for having a great example of a + widget model. */ +AnimeListWidgetModel::AnimeListWidgetModel (QWidget* parent, AnimeList* alist) + : QAbstractListModel(parent) + , list(*alist) { + return; +} + +int AnimeListWidgetModel::rowCount(const QModelIndex& parent) const { + return list.Size(); +} + +int AnimeListWidgetModel::columnCount(const QModelIndex& parent) const { + return NB_COLUMNS; +} + +QVariant AnimeListWidgetModel::headerData(const int section, const Qt::Orientation orientation, const int role) const { + if (role == Qt::DisplayRole) { + switch (section) { + case AL_TITLE: + return tr("Anime title"); + case AL_PROGRESS: + return tr("Progress"); + case AL_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_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); +} + +Anime* AnimeListWidgetModel::GetAnimeFromIndex(const QModelIndex& index) { + return (index.isValid()) ? &(list[index.row()]) : nullptr; +} + +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()); + } + 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; + } + } + return QVariant(); +} + +void AnimeListWidgetModel::UpdateAnime(Anime& anime) { + int i = list.GetAnimeIndex(anime); + 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; + + for (int i = 0, end = header()->count(); i < end; i++) + { + if (!isColumnHidden(i)) + count++; + } + + return count; +} + +void AnimeListWidget::SetColumnDefaults() { + setColumnHidden(AnimeListWidgetModel::AL_SEASON, false); + setColumnHidden(AnimeListWidgetModel::AL_TYPE, false); + setColumnHidden(AnimeListWidgetModel::AL_UPDATED, false); + setColumnHidden(AnimeListWidgetModel::AL_PROGRESS, false); + setColumnHidden(AnimeListWidgetModel::AL_SCORE, false); + setColumnHidden(AnimeListWidgetModel::AL_TITLE, false); + setColumnHidden(AnimeListWidgetModel::AL_AVG_SCORE, true); + setColumnHidden(AnimeListWidgetModel::AL_STARTED, true); + setColumnHidden(AnimeListWidgetModel::AL_COMPLETED, true); + setColumnHidden(AnimeListWidgetModel::AL_UPDATED, true); + setColumnHidden(AnimeListWidgetModel::AL_NOTES, true); +} + +void AnimeListWidget::DisplayColumnHeaderMenu() { + QMenu *menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->setTitle(tr("Column visibility")); + menu->setToolTipsVisible(true); + + for (int i = 0; i < AnimeListWidgetModel::NB_COLUMNS; i++) + { + if (i == AnimeListWidgetModel::AL_TITLE) + continue; + const auto column_name = model->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); + QAction *action = menu->addAction(column_name, this, [this, i](const bool checked) { + if (!checked && (VisibleColumnsCount() <= 1)) + return; + + setColumnHidden(i, !checked); + + if (checked && (columnWidth(i) <= 5)) + resizeColumnToContents(i); + + // SaveSettings(); + }); + action->setCheckable(true); + action->setChecked(!isColumnHidden(i)); + } + + menu->addSeparator(); + QAction *resetAction = menu->addAction(tr("Reset to defaults"), this, [this]() + { + for (int i = 0, count = header()->count(); i < count; ++i) + { + SetColumnDefaults(); + } + // SaveSettings(); + }); + + menu->popup(QCursor::pos()); +} + +void AnimeListWidget::DisplayListMenu() { + /* throw out any other garbage */ + const QModelIndexList selected_items = selectionModel()->selectedRows(); + if (selected_items.size() != 1 || !selected_items.first().isValid()) + return; + + const QModelIndex index = model->index(selected_items.first().row()); + Anime* anime = model->GetAnimeFromIndex(index); + if (!anime) + return; + +} + +void AnimeListWidget::ItemDoubleClicked() { + /* throw out any other garbage */ + const QModelIndexList selected_items = selectionModel()->selectedRows(); + if (selected_items.size() != 1 || !selected_items.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()); + Anime* anime = model->GetAnimeFromIndex(index); + if (!anime) + return; + + InformationDialog* dialog = new InformationDialog(*anime, model, this); + + dialog->show(); + dialog->raise(); + dialog->activateWindow(); +} + +AnimeListWidget::AnimeListWidget(QWidget* parent, AnimeList* alist) + : QTreeView(parent) { + model = new AnimeListWidgetModel(parent, alist); + setModel(model); + setObjectName("listwidget"); + setStyleSheet("QTreeView#listwidget{border-top:0px;}"); + setUniformRowHeights(true); + setAllColumnsShowFocus(false); + setSortingEnabled(true); + setSelectionMode(QAbstractItemView::ExtendedSelection); + setItemsExpandable(false); + setRootIsDecorated(false); + setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, &QAbstractItemView::doubleClicked, this, &ItemDoubleClicked); + connect(this, &QWidget::customContextMenuRequested, this, &DisplayListMenu); + + /* Enter & return keys */ + connect(new QShortcut(Qt::Key_Return, this, nullptr, nullptr, Qt::WidgetShortcut), + &QShortcut::activated, this, &ItemDoubleClicked); + + connect(new QShortcut(Qt::Key_Enter, this, nullptr, nullptr, Qt::WidgetShortcut), + &QShortcut::activated, this, &ItemDoubleClicked); + + header()->setStretchLastSection(false); + header()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(header(), &QWidget::customContextMenuRequested, this, &DisplayColumnHeaderMenu); + // if(!session.config.anime_list.columns) { + SetColumnDefaults(); + // } +} + +AnimeListPage::AnimeListPage(QWidget* parent) : QTabWidget (parent) { + setDocumentMode(false); + SyncAnimeList(); + for (AnimeList& list : anime_lists) { + addTab(new AnimeListWidget(this, &list), QString::fromWCharArray(list.name.c_str())); + } +} + +void AnimeListPage::SyncAnimeList() { + 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); + break; + } + } +} + +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(); + } +} + +int AnimeListPage::GetTotalAnimeAmount() { + int total = 0; + for (auto& list : anime_lists) { + total += list.Size(); + } + return total; +} + +int AnimeListPage::GetTotalEpisodeAmount() { + /* FIXME: this also needs to take into account rewatches... */ + int total = 0; + for (auto& list : anime_lists) { + for (auto& anime : list) { + total += anime.progress; + } + } + return total; +} + +/* Returns the total watched amount in minutes. */ +int AnimeListPage::GetTotalWatchedAmount() { + int total = 0; + for (auto& list : anime_lists) { + for (auto& anime : list) { + total += anime.duration*anime.progress; + } + } + 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 AnimeListPage::GetTotalPlannedAmount() { + int total = 0; + for (auto& list : anime_lists) { + for (auto& anime : list) { + total += anime.duration*(anime.episodes-anime.progress); + } + } + return total; +} + +double AnimeListPage::GetAverageScore() { + double avg = 0; + int amt = 0; + for (auto& list : anime_lists) { + for (auto& anime : list) { + avg += anime.score; + if (anime.score != 0) + amt++; + } + } + return avg/amt; +} + +double AnimeListPage::GetScoreDeviation() { + double squares_sum = 0, avg = GetAverageScore(); + int amt = 0; + for (auto& list : anime_lists) { + for (auto& anime : list) { + if (anime.score != 0) { + squares_sum += std::pow((double)anime.score - avg, 2); + amt++; + } + } + } + return (amt > 0) ? std::sqrt(squares_sum / amt) : 0; +} + +#include "moc_anime.cpp"