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"