Mercurial > minori
diff src/gui/pages/anime_list.cpp @ 10:4b198a111713
Update
things actually compile now btw
qttest wants to fuck over the model but that might be my fault so /shrug
author | Paper <mrpapersonic@gmail.com> |
---|---|
date | Sat, 16 Sep 2023 02:06:01 -0400 |
parents | 5c0397762b53 |
children | fc1bf97c528b |
line wrap: on
line diff
--- a/src/gui/pages/anime_list.cpp Sun Sep 10 03:59:16 2023 -0400 +++ b/src/gui/pages/anime_list.cpp Sat Sep 16 02:06:01 2023 -0400 @@ -1,468 +1,479 @@ -/** - * anime_list.cpp: defines the anime list page - * and widgets. - * - * much of this file is based around - * Qt's original QTabWidget implementation, because - * I needed a somewhat native way to create a tabbed - * widget with only one subwidget that worked exactly - * like a native tabbed widget. - **/ -#include "gui/pages/anime_list.h" -#include "core/anime.h" -#include "core/anime_db.h" -#include "core/session.h" -#include "core/time.h" -#include "gui/dialog/information.h" -#include "gui/translate/anime.h" -#include "services/anilist.h" -#include <QHBoxLayout> -#include <QHeaderView> -#include <QMenu> -#include <QProgressBar> -#include <QShortcut> -#include <QStylePainter> -#include <QStyledItemDelegate> -#include <cmath> - -#if 0 -AnimeListWidgetDelegate::AnimeListWidgetDelegate(QObject* parent) : QStyledItemDelegate(parent) { -} - -QWidget* AnimeListWidgetDelegate::createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const { - // no edit 4 u - return nullptr; -} - -void AnimeListWidgetDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const { - switch (index.column()) { -/* - case AnimeListWidgetModel::AL_PROGRESS: { - const int progress = static_cast<int>(index.data(Qt::UserRole).toReal()); - const int episodes = - static_cast<int>(index.siblingAtColumn(AnimeListWidgetModel::AL_EPISODES).data(Qt::UserRole).toReal()); - - int text_width = 59; - QRectF text_rect(option.rect.x() + text_width, option.rect.y(), text_width, option.decorationSize.height()); - painter->save(); - painter->drawText(text_rect, "/", QTextOption(Qt::AlignCenter | Qt::AlignVCenter)); - // drawText(const QRectF &rectangle, const QString &text, const QTextOption &option = QTextOption()) - painter->drawText(QRectF(text_rect.x(), text_rect.y(), text_width / 2 - 2, text_rect.height()), - QString::number(progress), QTextOption(Qt::AlignRight | Qt::AlignVCenter)); - painter->drawText( - QRectF(text_rect.x() + text_width / 2 + 2, text_rect.y(), text_width / 2 - 2, text_rect.height()), - QString::number(episodes), QTextOption(Qt::AlignLeft | Qt::AlignVCenter)); - painter->restore(); - QStyledItemDelegate::paint(painter, option, index); - break; - } -*/ - default: QStyledItemDelegate::paint(painter, option, index); break; - } -} - -AnimeListWidgetSortFilter::AnimeListWidgetSortFilter(QObject* parent) : QSortFilterProxyModel(parent) { -} - -bool AnimeListWidgetSortFilter::lessThan(const QModelIndex& l, const QModelIndex& r) const { - QVariant left = sourceModel()->data(l, sortRole()); - QVariant right = sourceModel()->data(r, sortRole()); - - switch (left.userType()) { - case QMetaType::Int: - case QMetaType::UInt: - case QMetaType::LongLong: - case QMetaType::ULongLong: return left.toInt() < right.toInt(); - case QMetaType::QDate: return left.toDate() < right.toDate(); - case QMetaType::QString: - default: return QString::compare(left.toString(), right.toString(), Qt::CaseInsensitive) < 0; - } -} - -AnimeListWidgetModel::AnimeListWidgetModel(QWidget* parent) : QAbstractListModel(parent) { - return; -} - -int AnimeListWidgetModel::rowCount(const QModelIndex& parent) const { - int count = 0; - for (const auto& [id, anime] : Anime::db.items) { - if (anime.IsInUserList()) - count++; - } - return count; - (void)(parent); -} - -int AnimeListWidgetModel::columnCount(const QModelIndex& parent) const { - return NB_COLUMNS; - (void)(parent); -} - -QVariant AnimeListWidgetModel::headerData(const int section, const Qt::Orientation orientation, const int role) const { - if (role == Qt::DisplayRole) { - switch (section) { - case AL_TITLE: return tr("Anime title"); - case AL_PROGRESS: return tr("Progress"); - case AL_EPISODES: return tr("Episodes"); - case AL_TYPE: return tr("Type"); - case AL_SCORE: return tr("Score"); - case AL_SEASON: return tr("Season"); - case AL_STARTED: return tr("Date started"); - case AL_COMPLETED: return tr("Date completed"); - case AL_NOTES: return tr("Notes"); - case AL_AVG_SCORE: return tr("Average score"); - case AL_UPDATED: return tr("Last updated"); - default: return {}; - } - } else if (role == Qt::TextAlignmentRole) { - switch (section) { - case AL_TITLE: - case AL_NOTES: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); - case AL_PROGRESS: - case AL_EPISODES: - case AL_TYPE: - case AL_SCORE: - case AL_AVG_SCORE: return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - case AL_SEASON: - case AL_STARTED: - case AL_COMPLETED: - case AL_UPDATED: return QVariant(Qt::AlignRight | Qt::AlignVCenter); - default: return QAbstractListModel::headerData(section, orientation, role); - } - } - return QAbstractListModel::headerData(section, orientation, role); -} - -QVariant AnimeListWidgetModel::data(const QModelIndex& index, int role) const { - if (!index.isValid()) - return QVariant(); - switch (role) { - case Qt::DisplayRole: - switch (index.column()) { - case AL_TITLE: return QString::fromUtf8(list[index.row()].GetUserPreferredTitle().c_str()); - case AL_PROGRESS: - return QString::number(list[index.row()].progress) + "/" + QString::number(list[index.row()].episodes); - case AL_EPISODES: return list[index.row()].episodes; - case AL_SCORE: return list[index.row()].score; - case AL_TYPE: return QString::fromStdString(Translate::TranslateSeriesFormat(list[index.row()].type)); - case AL_SEASON: - return QString::fromStdString(Translate::TranslateSeriesSeason(list[index.row()].season)) + " " + - QString::number(list[index.row()].air_date.GetYear()); - case AL_AVG_SCORE: return QString::number(list[index.row()].audience_score) + "%"; - case AL_STARTED: return list[index.row()].started.GetAsQDate(); - case AL_COMPLETED: return list[index.row()].completed.GetAsQDate(); - case AL_UPDATED: { - if (list[index.row()].updated == 0) - return QString("-"); - Time::Duration duration(Time::GetSystemTime() - list[index.row()].updated); - return QString::fromUtf8(duration.AsRelativeString().c_str()); - } - case AL_NOTES: return QString::fromUtf8(list[index.row()].notes.c_str()); - default: return ""; - } - break; - case Qt::UserRole: - switch (index.column()) { - case AL_ID: return - case AL_PROGRESS: return list[index.row()].progress; - case AL_TYPE: return list[index.row()].type; - case AL_SEASON: return list[index.row()].air_date.GetAsQDate(); - case AL_AVG_SCORE: return list[index.row()].audience_score; - case AL_UPDATED: return list[index.row()].updated; - default: return data(index, Qt::DisplayRole); - } - break; - case Qt::TextAlignmentRole: - switch (index.column()) { - case AL_TITLE: - case AL_NOTES: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); - case AL_PROGRESS: - case AL_EPISODES: - case AL_TYPE: - case AL_SCORE: - case AL_AVG_SCORE: return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - case AL_SEASON: - case AL_STARTED: - case AL_COMPLETED: - case AL_UPDATED: return QVariant(Qt::AlignRight | Qt::AlignVCenter); - default: break; - } - break; - } - return QVariant(); -} - -void AnimeListWidgetModel::UpdateAnime(int id) { - /* meh... it might be better to just redraw the entire list */ - int i = 0; - for (const auto& [a_id, anime] : Anime:db.items) { - if (anime.IsInUserList() && a_id == id && anime.GetUserStatus() == Anime::ListStatus::WATCHING) { - emit dataChanged(index(i), index(i)); - } - i++; - } -} -#endif - -int AnimeListWidget::VisibleColumnsCount() const { - int count = 0; - - for (int i = 0, end = tree_view->header()->count(); i < end; i++) { - if (!tree_view->isColumnHidden(i)) - count++; - } - - return count; -} - -void AnimeListWidget::SetColumnDefaults() { - tree_view->setColumnHidden(AnimeListWidgetModel::AL_SEASON, false); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_TYPE, false); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_UPDATED, false); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_PROGRESS, false); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_SCORE, false); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_TITLE, false); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_EPISODES, true); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_AVG_SCORE, true); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_STARTED, true); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_COMPLETED, true); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_UPDATED, true); - tree_view->setColumnHidden(AnimeListWidgetModel::AL_NOTES, true); -} - -void AnimeListWidget::DisplayColumnHeaderMenu() { - QMenu* menu = new QMenu(this); - menu->setAttribute(Qt::WA_DeleteOnClose); - menu->setTitle(tr("Column visibility")); - menu->setToolTipsVisible(true); - - for (int i = 0; i < AnimeListWidgetModel::NB_COLUMNS; i++) { - if (i == AnimeListWidgetModel::AL_TITLE) - continue; - const auto column_name = - sort_models[tab_bar->currentIndex()]->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); - QAction* action = menu->addAction(column_name, this, [this, i](const bool checked) { - if (!checked && (VisibleColumnsCount() <= 1)) - return; - - tree_view->setColumnHidden(i, !checked); - - if (checked && (tree_view->columnWidth(i) <= 5)) - tree_view->resizeColumnToContents(i); - - // SaveSettings(); - }); - action->setCheckable(true); - action->setChecked(!tree_view->isColumnHidden(i)); - } - - menu->addSeparator(); - QAction* resetAction = menu->addAction(tr("Reset to defaults"), this, [this]() { - for (int i = 0, count = tree_view->header()->count(); i < count; ++i) { - SetColumnDefaults(); - } - // SaveSettings(); - }); - menu->popup(QCursor::pos()); - (void)(resetAction); -} - -void AnimeListWidget::DisplayListMenu() { - QMenu* menu = new QMenu(this); - menu->setAttribute(Qt::WA_DeleteOnClose); - menu->setTitle(tr("Column visibility")); - menu->setToolTipsVisible(true); - - const QItemSelection selection = sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); - if (!selection.indexes().first().isValid()) { - return; - } - -/* - QAction* action = menu->addAction("Information", [this, selection] { - const QModelIndex index = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel()) - ->index(selection.indexes().first().row()); - Anime::Anime* anime = - ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->GetAnimeFromIndex(index); - if (!anime) { - return; - } - - InformationDialog* dialog = new InformationDialog( - *anime, - [this, anime] { - ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->UpdateAnime(*anime); - }, - this); - - dialog->show(); - dialog->raise(); - dialog->activateWindow(); - }); -*/ - menu->popup(QCursor::pos()); -} - -void AnimeListWidget::ItemDoubleClicked() { - /* throw out any other garbage */ - const QItemSelection selection = - sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); - if (!selection.indexes().first().isValid()) { - return; - } - -/* - const QModelIndex index = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel()) - ->index(selection.indexes().first().row()); - Anime::Anime* anime = - ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->GetAnimeFromIndex(index); - if (!anime) { - return; - } - - InformationDialog* dialog = new InformationDialog(*anime, [this, anime] { - ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->UpdateAnime(*anime); - }, this); - - dialog->show(); - dialog->raise(); - dialog->activateWindow(); -*/ -} - -void AnimeListWidget::paintEvent(QPaintEvent*) { - QStylePainter p(this); - - QStyleOptionTabWidgetFrame opt; - InitStyle(&opt); - opt.rect = panelRect; - p.drawPrimitive(QStyle::PE_FrameTabWidget, opt); -} - -void AnimeListWidget::resizeEvent(QResizeEvent* e) { - QWidget::resizeEvent(e); - SetupLayout(); -} - -void AnimeListWidget::showEvent(QShowEvent*) { - SetupLayout(); -} - -void AnimeListWidget::InitBasicStyle(QStyleOptionTabWidgetFrame* option) const { - if (!option) - return; - - option->initFrom(this); - option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, this); - option->shape = QTabBar::RoundedNorth; - option->tabBarRect = tab_bar->geometry(); -} - -void AnimeListWidget::InitStyle(QStyleOptionTabWidgetFrame* option) const { - if (!option) - return; - - InitBasicStyle(option); - - // int exth = style()->pixelMetric(QStyle::PM_TabBarBaseHeight, nullptr, this); - QSize t(0, tree_view->frameWidth()); - if (tab_bar->isVisibleTo(this)) { - t = tab_bar->sizeHint(); - t.setWidth(width()); - } - option->tabBarSize = t; - - QRect selected_tab_rect = tab_bar->tabRect(tab_bar->currentIndex()); - selected_tab_rect.moveTopLeft(selected_tab_rect.topLeft() + option->tabBarRect.topLeft()); - option->selectedTabRect = selected_tab_rect; - - option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, this); -} - -void AnimeListWidget::SetupLayout() { - QStyleOptionTabWidgetFrame option; - InitStyle(&option); - - QRect tabRect = style()->subElementRect(QStyle::SE_TabWidgetTabBar, &option, this); - tabRect.setLeft(tabRect.left() + 1); - panelRect = style()->subElementRect(QStyle::SE_TabWidgetTabPane, &option, this); - QRect contentsRect = style()->subElementRect(QStyle::SE_TabWidgetTabContents, &option, this); - - tab_bar->setGeometry(tabRect); - tree_view->parentWidget()->setGeometry(contentsRect); -} - -AnimeListWidget::AnimeListWidget(QWidget* parent) : QWidget(parent) { - /* Tab bar */ - tab_bar = new QTabBar(this); - tab_bar->setExpanding(false); - tab_bar->setDrawBase(false); - for (int i = 0; i < ARRAYSIZE(sort_models); i++) { - tab_bar->addTab(QString::fromStdString(Translate::TranslateListStatus(Anime::ListStatuses[i]))); - - /* Tree view... */ - QWidget* tree_widget = new QWidget(this); - tree_view = new QTreeView(tree_widget); - tree_view->setItemDelegate(new AnimeListWidgetDelegate(tree_view)); - tree_view->setUniformRowHeights(true); - tree_view->setAllColumnsShowFocus(false); - tree_view->setAlternatingRowColors(true); - tree_view->setSortingEnabled(true); - tree_view->setSelectionMode(QAbstractItemView::ExtendedSelection); - tree_view->setItemsExpandable(false); - tree_view->setRootIsDecorated(false); - tree_view->setContextMenuPolicy(Qt::CustomContextMenu); - tree_view->setFrameShape(QFrame::NoFrame); - - QHBoxLayout* layout = new QHBoxLayout; - layout->addWidget(tree_view); - layout->setMargin(0); - tree_widget->setLayout(layout); - - /* Double click stuff */ - connect(tree_view, &QAbstractItemView::doubleClicked, this, &AnimeListWidget::ItemDoubleClicked); - connect(tree_view, &QWidget::customContextMenuRequested, this, &AnimeListWidget::DisplayListMenu); - - /* Enter & return keys */ - connect(new QShortcut(Qt::Key_Return, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, - this, &AnimeListWidget::ItemDoubleClicked); - - connect(new QShortcut(Qt::Key_Enter, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, - this, &AnimeListWidget::ItemDoubleClicked); - - tree_view->header()->setStretchLastSection(false); - tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu); - connect(tree_view->header(), &QWidget::customContextMenuRequested, this, - &AnimeListWidget::DisplayColumnHeaderMenu); - - connect(tab_bar, &QTabBar::currentChanged, this, [this](int index) { - if (sort_models[index]) - tree_view->setModel(sort_models[index]); - }); - - setFocusPolicy(Qt::TabFocus); - setFocusProxy(tab_bar); - } - - void AnimeListWidget::UpdateAnimeList() { - for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++) { - sort_models[i] = new AnimeListWidgetSortFilter(tree_view); - sort_models[i]->setSourceModel(new AnimeListWidgetModel(this, &anime_lists[i])); - sort_models[i]->setSortRole(Qt::UserRole); - sort_models[i]->setSortCaseSensitivity(Qt::CaseInsensitive); - } - if (ARRAYSIZE(sort_models) > 0) - tree_view->setModel(sort_models[0]); - SetColumnDefaults(); - SetupLayout(); - } - - void AnimeListWidget::Reset() { - while (tab_bar->count()) - tab_bar->removeTab(0); - for (int i = 0; i < ARRAYSIZE(sort_models); i++) - delete sort_models[i]; - } - -#include "gui/pages/moc_anime_list.cpp" +/** + * anime_list.cpp: defines the anime list page + * and widgets. + * + * much of this file is based around + * Qt's original QTabWidget implementation, because + * I needed a somewhat native way to create a tabbed + * widget with only one subwidget that worked exactly + * like a native tabbed widget. + **/ +#include "gui/pages/anime_list.h" +#include "core/anime.h" +#include "core/anime_db.h" +#include "core/session.h" +#include "core/time.h" +#include "gui/dialog/information.h" +#include "gui/translate/anime.h" +#include "services/anilist.h" +#include <QHBoxLayout> +#include <QHeaderView> +#include <QMenu> +#include <QProgressBar> +#include <QDebug> +#include <QShortcut> +#include <QStylePainter> +#include <QStyledItemDelegate> +#include <QAbstractItemModelTester> +#include <cmath> + +AnimeListWidgetDelegate::AnimeListWidgetDelegate(QObject* parent) : QStyledItemDelegate(parent) { +} + +QWidget* AnimeListWidgetDelegate::createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const { + // no edit 4 u + return nullptr; +} + +void AnimeListWidgetDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, + const QModelIndex& index) const { + switch (index.column()) { +/* + case AnimeListWidgetModel::AL_PROGRESS: { + const int progress = static_cast<int>(index.data(Qt::UserRole).toReal()); + const int episodes = + static_cast<int>(index.siblingAtColumn(AnimeListWidgetModel::AL_EPISODES).data(Qt::UserRole).toReal()); + + int text_width = 59; + QRectF text_rect(option.rect.x() + text_width, option.rect.y(), text_width, option.decorationSize.height()); + painter->save(); + painter->drawText(text_rect, "/", QTextOption(Qt::AlignCenter | Qt::AlignVCenter)); + // drawText(const QRectF &rectangle, const QString &text, const QTextOption &option = QTextOption()) + painter->drawText(QRectF(text_rect.x(), text_rect.y(), text_width / 2 - 2, text_rect.height()), + QString::number(progress), QTextOption(Qt::AlignRight | Qt::AlignVCenter)); + painter->drawText( + QRectF(text_rect.x() + text_width / 2 + 2, text_rect.y(), text_width / 2 - 2, text_rect.height()), + QString::number(episodes), QTextOption(Qt::AlignLeft | Qt::AlignVCenter)); + painter->restore(); + QStyledItemDelegate::paint(painter, option, index); + break; + } +*/ + default: QStyledItemDelegate::paint(painter, option, index); break; + } +} + +AnimeListWidgetSortFilter::AnimeListWidgetSortFilter(QObject* parent) : QSortFilterProxyModel(parent) { +} + +bool AnimeListWidgetSortFilter::lessThan(const QModelIndex& l, const QModelIndex& r) const { + QVariant left = sourceModel()->data(l, sortRole()); + QVariant right = sourceModel()->data(r, sortRole()); + + switch (left.userType()) { + case QMetaType::Int: + case QMetaType::UInt: + case QMetaType::LongLong: + case QMetaType::ULongLong: return left.toInt() < right.toInt(); + case QMetaType::QDate: return left.toDate() < right.toDate(); + case QMetaType::QString: + default: return QString::compare(left.toString(), right.toString(), Qt::CaseInsensitive) < 0; + } +} + +AnimeListWidgetModel::AnimeListWidgetModel(QWidget* parent, Anime::ListStatus _status) : QAbstractListModel(parent) { + status = _status; + return; +} + +int AnimeListWidgetModel::rowCount(const QModelIndex& parent) const { + return list.size(); + (void)(parent); +} + +int AnimeListWidgetModel::columnCount(const QModelIndex& parent) const { + return NB_COLUMNS; + (void)(parent); +} + +QVariant AnimeListWidgetModel::headerData(const int section, const Qt::Orientation orientation, const int role) const { + if (role == Qt::DisplayRole) { + switch (section) { + case AL_TITLE: return tr("Anime title"); + case AL_PROGRESS: return tr("Progress"); + case AL_EPISODES: return tr("Episodes"); + case AL_TYPE: return tr("Type"); + case AL_SCORE: return tr("Score"); + case AL_SEASON: return tr("Season"); + case AL_STARTED: return tr("Date started"); + case AL_COMPLETED: return tr("Date completed"); + case AL_NOTES: return tr("Notes"); + case AL_AVG_SCORE: return tr("Average score"); + case AL_UPDATED: return tr("Last updated"); + default: return {}; + } + } else if (role == Qt::TextAlignmentRole) { + switch (section) { + case AL_TITLE: + case AL_NOTES: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + case AL_PROGRESS: + case AL_EPISODES: + case AL_TYPE: + case AL_SCORE: + case AL_AVG_SCORE: return QVariant(Qt::AlignCenter | Qt::AlignVCenter); + case AL_SEASON: + case AL_STARTED: + case AL_COMPLETED: + case AL_UPDATED: return QVariant(Qt::AlignRight | Qt::AlignVCenter); + default: return QAbstractListModel::headerData(section, orientation, role); + } + } + return QAbstractListModel::headerData(section, orientation, role); +} + +QVariant AnimeListWidgetModel::data(const QModelIndex& index, int role) const { + if (!index.isValid()) + return QVariant(); + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case AL_TITLE: return QString::fromUtf8(list[index.row()].GetUserPreferredTitle().c_str()); + case AL_PROGRESS: + return QString::number(list[index.row()].GetUserProgress()) + "/" + QString::number(list[index.row()].GetEpisodes()); + case AL_EPISODES: return list[index.row()].GetEpisodes(); + case AL_SCORE: return list[index.row()].GetUserScore(); + case AL_TYPE: return QString::fromStdString(Translate::TranslateSeriesFormat(list[index.row()].GetFormat())); + case AL_SEASON: + return QString::fromStdString(Translate::TranslateSeriesSeason(list[index.row()].GetSeason())) + " " + + QString::number(list[index.row()].GetAirDate().GetYear()); + case AL_AVG_SCORE: return QString::number(list[index.row()].GetAudienceScore()) + "%"; + case AL_STARTED: return list[index.row()].GetUserDateStarted().GetAsQDate(); + case AL_COMPLETED: return list[index.row()].GetUserDateCompleted().GetAsQDate(); + case AL_UPDATED: { + if (list[index.row()].GetUserTimeUpdated() == 0) + return QString("-"); + Time::Duration duration(Time::GetSystemTime() - list[index.row()].GetUserTimeUpdated()); + return QString::fromUtf8(duration.AsRelativeString().c_str()); + } + case AL_NOTES: return QString::fromUtf8(list[index.row()].GetUserNotes().c_str()); + default: return ""; + } + break; + case Qt::UserRole: + switch (index.column()) { + case AL_ID: return list[index.row()].GetId(); + case AL_PROGRESS: return list[index.row()].GetUserProgress(); + case AL_TYPE: return static_cast<int>(list[index.row()].GetFormat()); + case AL_SEASON: return list[index.row()].GetAirDate().GetAsQDate(); + case AL_AVG_SCORE: return list[index.row()].GetAudienceScore(); + case AL_UPDATED: return list[index.row()].GetUserTimeUpdated(); + default: return data(index, Qt::DisplayRole); + } + break; + case Qt::TextAlignmentRole: + switch (index.column()) { + case AL_TITLE: + case AL_NOTES: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + case AL_PROGRESS: + case AL_EPISODES: + case AL_TYPE: + case AL_SCORE: + case AL_AVG_SCORE: return QVariant(Qt::AlignCenter | Qt::AlignVCenter); + case AL_SEASON: + case AL_STARTED: + case AL_COMPLETED: + case AL_UPDATED: return QVariant(Qt::AlignRight | Qt::AlignVCenter); + default: break; + } + break; + } + return QVariant(); +} + +void AnimeListWidgetModel::UpdateAnime(int id) { + /* meh... it might be better to just reinit the entire list */ + int i = 0; + for (const auto& [a_id, anime] : Anime::db.items) { + if (anime.IsInUserList() && a_id == id && anime.GetUserStatus() == status) { + emit dataChanged(index(i), index(i)); + } + i++; + } +} + +Anime::Anime* AnimeListWidgetModel::GetAnimeFromIndex(QModelIndex index) { + return &list.at(index.row()); +} + +void AnimeListWidgetModel::RefreshList() { + bool has_children = !!rowCount(index(0)); + if (has_children) beginResetModel(); + list.clear(); + + for (const auto& [id, anime] : Anime::db.items) { + if (anime.IsInUserList() && anime.GetUserStatus() == status) { + list.push_back(anime); + } + } + if (has_children) endResetModel(); +} + +int AnimeListWidget::VisibleColumnsCount() const { + int count = 0; + + for (int i = 0, end = tree_view->header()->count(); i < end; i++) { + if (!tree_view->isColumnHidden(i)) + count++; + } + + return count; +} + +void AnimeListWidget::SetColumnDefaults() { + tree_view->setColumnHidden(AnimeListWidgetModel::AL_SEASON, false); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_TYPE, false); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_UPDATED, false); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_PROGRESS, false); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_SCORE, false); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_TITLE, false); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_EPISODES, true); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_AVG_SCORE, true); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_STARTED, true); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_COMPLETED, true); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_UPDATED, true); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_NOTES, true); + tree_view->setColumnHidden(AnimeListWidgetModel::AL_ID, true); +} + +void AnimeListWidget::DisplayColumnHeaderMenu() { + QMenu* menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->setTitle(tr("Column visibility")); + menu->setToolTipsVisible(true); + + for (int i = 0; i < AnimeListWidgetModel::NB_COLUMNS; i++) { + if (i == AnimeListWidgetModel::AL_TITLE || i == AnimeListWidgetModel::AL_ID) + continue; + const auto column_name = + sort_models[tab_bar->currentIndex()]->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); + QAction* action = menu->addAction(column_name, this, [this, i](const bool checked) { + if (!checked && (VisibleColumnsCount() <= 1)) + return; + + tree_view->setColumnHidden(i, !checked); + + if (checked && (tree_view->columnWidth(i) <= 5)) + tree_view->resizeColumnToContents(i); + + // SaveSettings(); + }); + action->setCheckable(true); + action->setChecked(!tree_view->isColumnHidden(i)); + } + + menu->addSeparator(); + QAction* resetAction = menu->addAction(tr("Reset to defaults"), this, [this]() { + for (int i = 0, count = tree_view->header()->count(); i < count; ++i) { + SetColumnDefaults(); + } + // SaveSettings(); + }); + menu->popup(QCursor::pos()); + (void)(resetAction); +} + +void AnimeListWidget::DisplayListMenu() { + QMenu* menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->setTitle(tr("Column visibility")); + menu->setToolTipsVisible(true); + + const QItemSelection selection = sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); + if (!selection.indexes().first().isValid()) { + return; + } + + QAction* action = menu->addAction("Information", [this, selection] { + const QModelIndex index = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel()) + ->index(selection.indexes().first().row()); + Anime::Anime* anime = + ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->GetAnimeFromIndex(index); + if (!anime) { + return; + } + + InformationDialog* dialog = new InformationDialog( + *anime, + [this, anime] { + ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->UpdateAnime(anime->GetId()); + }, + this); + + dialog->show(); + dialog->raise(); + dialog->activateWindow(); + }); + menu->popup(QCursor::pos()); +} + +void AnimeListWidget::ItemDoubleClicked() { + /* throw out any other garbage */ + const QItemSelection selection = + sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); + if (!selection.indexes().first().isValid()) { + return; + } + + const QModelIndex index = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel()) + ->index(selection.indexes().first().row()); + Anime::Anime* anime = + ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->GetAnimeFromIndex(index); + + InformationDialog* dialog = new InformationDialog( + *anime, + [this, anime] { + ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->UpdateAnime(anime->GetId()); + }, + this); + + dialog->show(); + dialog->raise(); + dialog->activateWindow(); +} + +void AnimeListWidget::paintEvent(QPaintEvent*) { + QStylePainter p(this); + + QStyleOptionTabWidgetFrame opt; + InitStyle(&opt); + opt.rect = panelRect; + p.drawPrimitive(QStyle::PE_FrameTabWidget, opt); +} + +void AnimeListWidget::resizeEvent(QResizeEvent* e) { + QWidget::resizeEvent(e); + SetupLayout(); +} + +void AnimeListWidget::showEvent(QShowEvent*) { + SetupLayout(); +} + +void AnimeListWidget::InitBasicStyle(QStyleOptionTabWidgetFrame* option) const { + if (!option) + return; + + option->initFrom(this); + option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, this); + option->shape = QTabBar::RoundedNorth; + option->tabBarRect = tab_bar->geometry(); +} + +void AnimeListWidget::InitStyle(QStyleOptionTabWidgetFrame* option) const { + if (!option) + return; + + InitBasicStyle(option); + + // int exth = style()->pixelMetric(QStyle::PM_TabBarBaseHeight, nullptr, this); + QSize t(0, tree_view->frameWidth()); + if (tab_bar->isVisibleTo(this)) { + t = tab_bar->sizeHint(); + t.setWidth(width()); + } + option->tabBarSize = t; + + QRect selected_tab_rect = tab_bar->tabRect(tab_bar->currentIndex()); + selected_tab_rect.moveTopLeft(selected_tab_rect.topLeft() + option->tabBarRect.topLeft()); + option->selectedTabRect = selected_tab_rect; + + option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, this); +} + +void AnimeListWidget::SetupLayout() { + QStyleOptionTabWidgetFrame option; + InitStyle(&option); + + QRect tabRect = style()->subElementRect(QStyle::SE_TabWidgetTabBar, &option, this); + tabRect.setLeft(tabRect.left() + 1); + panelRect = style()->subElementRect(QStyle::SE_TabWidgetTabPane, &option, this); + QRect contentsRect = style()->subElementRect(QStyle::SE_TabWidgetTabContents, &option, this); + + tab_bar->setGeometry(tabRect); + tree_view->parentWidget()->setGeometry(contentsRect); +} + +AnimeListWidget::AnimeListWidget(QWidget* parent) : QWidget(parent) { + /* Tab bar */ + tab_bar = new QTabBar(this); + tab_bar->setExpanding(false); + tab_bar->setDrawBase(false); + + /* Tree view... */ + QWidget* tree_widget = new QWidget(this); + tree_view = new QTreeView(tree_widget); + tree_view->setItemDelegate(new AnimeListWidgetDelegate(tree_view)); + tree_view->setUniformRowHeights(true); + tree_view->setAllColumnsShowFocus(false); + tree_view->setAlternatingRowColors(true); + tree_view->setSortingEnabled(true); + tree_view->setSelectionMode(QAbstractItemView::ExtendedSelection); + tree_view->setItemsExpandable(false); + tree_view->setRootIsDecorated(false); + tree_view->setContextMenuPolicy(Qt::CustomContextMenu); + tree_view->setFrameShape(QFrame::NoFrame); + + for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++) { + tab_bar->addTab(QString::fromStdString(Translate::TranslateListStatus(Anime::ListStatuses[i])) + " (" + QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); + sort_models[i] = new AnimeListWidgetSortFilter(tree_view); + AnimeListWidgetModel* model = new AnimeListWidgetModel(this, Anime::ListStatuses[i]); + new QAbstractItemModelTester(model, QAbstractItemModelTester::FailureReportingMode::Fatal, this); + sort_models[i]->setSourceModel(model); + sort_models[i]->setSortRole(Qt::UserRole); + sort_models[i]->setSortCaseSensitivity(Qt::CaseInsensitive); + } + + QHBoxLayout* layout = new QHBoxLayout; + layout->addWidget(tree_view); + layout->setMargin(0); + tree_widget->setLayout(layout); + + /* Double click stuff */ + connect(tree_view, &QAbstractItemView::doubleClicked, this, &AnimeListWidget::ItemDoubleClicked); + connect(tree_view, &QWidget::customContextMenuRequested, this, &AnimeListWidget::DisplayListMenu); + + /* Enter & return keys */ + connect(new QShortcut(Qt::Key_Return, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, + this, &AnimeListWidget::ItemDoubleClicked); + + connect(new QShortcut(Qt::Key_Enter, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, + this, &AnimeListWidget::ItemDoubleClicked); + + tree_view->header()->setStretchLastSection(false); + tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(tree_view->header(), &QWidget::customContextMenuRequested, this, + &AnimeListWidget::DisplayColumnHeaderMenu); + + connect(tab_bar, &QTabBar::currentChanged, this, [this](int index) { + if (sort_models[index]) + tree_view->setModel(sort_models[index]); + }); + + setFocusPolicy(Qt::TabFocus); + setFocusProxy(tab_bar); +} + +void AnimeListWidget::RefreshList() { + for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++) { + ((AnimeListWidgetModel*)sort_models[i]->sourceModel())->RefreshList(); + } +} + +void AnimeListWidget::Reset() { + while (tab_bar->count()) + tab_bar->removeTab(0); + for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++) + delete sort_models[i]; +} + +#include "gui/pages/moc_anime_list.cpp"