diff src/gui/pages/search.cc @ 250:c130f47f6f48

*: many many changes e.g. the search page is actually implemented now!
author Paper <paper@paper.us.eu.org>
date Sun, 04 Feb 2024 21:17:17 -0500
parents 4d461ef7d424
children 862d0d8619f6
line wrap: on
line diff
--- a/src/gui/pages/search.cc	Wed Jan 24 20:18:59 2024 -0500
+++ b/src/gui/pages/search.cc	Sun Feb 04 21:17:17 2024 -0500
@@ -1,4 +1,346 @@
 #include "gui/pages/search.h"
+#include "core/anime.h"
+#include "core/anime_db.h"
+#include "core/strings.h"
+#include "core/http.h"
+#include "core/session.h"
+#include "core/filesystem.h"
+#include "gui/widgets/text.h"
+#include "gui/dialog/information.h"
+#include "track/media.h"
+#include "gui/translate/anime.h"
+#include "services/services.h"
+
+#include <QHeaderView>
+#include <QVBoxLayout>
+#include <QToolBar>
+#include <QTreeView>
+#include <QDate>
+#include <QMenu>
+
+#include <iostream>
+#include <sstream>
+#include <fstream>
+#include <algorithm>
+
+#include "pugixml.hpp"
+#include "anitomy/anitomy.h"
+
+SearchPageListSortFilter::SearchPageListSortFilter(QObject* parent) : QSortFilterProxyModel(parent) {
+}
+
+bool SearchPageListSortFilter::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: // meh
+			return QString::compare(left.toString(), right.toString(), Qt::CaseInsensitive) < 0;
+	}
+}
+
+/* -------------------------------------------- */
+
+SearchPageListModel::SearchPageListModel(QObject* parent) : QAbstractListModel(parent) {
+}
+
+void SearchPageListModel::ParseSearch(const std::vector<int>& ids) {
+	/* hack!!! */
+	if (!rowCount(index(0))) {
+		beginInsertRows(QModelIndex(), 0, 0);
+		endInsertRows();
+	}
+
+	beginResetModel();
+
+	this->ids = ids;
+
+	endResetModel();
+}
+
+int SearchPageListModel::rowCount(const QModelIndex& parent) const {
+	return ids.size();
+	(void)(parent);
+}
+
+int SearchPageListModel::columnCount(const QModelIndex& parent) const {
+	return NB_COLUMNS;
+	(void)(parent);
+}
+
+QVariant SearchPageListModel::headerData(const int section, const Qt::Orientation orientation, const int role) const {
+	switch (role) {
+		case Qt::DisplayRole: {
+			switch (section) {
+				case SR_TITLE: return tr("Anime title");
+				case SR_EPISODES: return tr("Episode");
+				case SR_TYPE: return tr("Type");
+				case SR_SCORE: return tr("Score");
+				case SR_SEASON: return tr("Season");
+				default: return {};
+			}
+			break;
+		}
+		case Qt::TextAlignmentRole: {
+			switch (section) {
+				case SR_TITLE: return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
+				case SR_TYPE: return QVariant(Qt::AlignHCenter | Qt::AlignVCenter);
+				case SR_EPISODES:
+				case SR_SCORE:
+				case SR_SEASON: return QVariant(Qt::AlignRight | Qt::AlignVCenter);
+				default: return {};
+			}
+			break;
+		}
+	}
+	return QAbstractListModel::headerData(section, orientation, role);
+}
+
+QVariant SearchPageListModel::data(const QModelIndex& index, int role) const {
+	if (!index.isValid())
+		return QVariant();
+
+	const Anime::Anime& anime = Anime::db.items[ids[index.row()]];
+
+	switch (role) {
+		case Qt::DisplayRole:
+			switch (index.column()) {
+				case SR_TITLE: return Strings::ToQString(anime.GetUserPreferredTitle());
+				case SR_TYPE: return Strings::ToQString(Translate::ToLocalString(anime.GetFormat()));
+				case SR_EPISODES: return anime.GetEpisodes();
+				case SR_SCORE: return QString::number(anime.GetAudienceScore()) + "%";
+				case SR_SEASON: return Strings::ToQString(Translate::ToLocalString(anime.GetSeason())) + " " + QString::number(anime.GetAirDate().GetYear().value_or(2000));
+				default: return {};
+			}
+			break;
+		case Qt::UserRole:
+			switch (index.column()) {
+				case SR_SCORE: return anime.GetAudienceScore();
+				case SR_EPISODES: return anime.GetEpisodes();
+				case SR_SEASON: return anime.GetAirDate().GetAsQDate();
+				/* We have to use this to work around some stupid
+				 * "conversion ambiguous" error on Linux
+				*/
+				default: return data(index, Qt::DisplayRole);
+			}
+			break;
+		case Qt::SizeHintRole: {
+			switch (index.column()) {
+				default: {
+					/* max horizontal size of 100, height size = size of current font */
+					const QString d = data(index, Qt::DisplayRole).toString();
+					const QFontMetrics metric = QFontMetrics(QFont());
+
+					return QSize(std::max(metric.horizontalAdvance(d), 100), metric.height());
+				}
+			}
+			break;
+		}
+		case Qt::TextAlignmentRole:
+			return headerData(index.column(), Qt::Horizontal, Qt::TextAlignmentRole);
+	}
+	return QVariant();
+}
+
+Qt::ItemFlags SearchPageListModel::flags(const QModelIndex& index) const {
+	if (!index.isValid())
+		return Qt::NoItemFlags;
+
+	return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+}
+
+Anime::Anime* SearchPageListModel::GetAnimeFromIndex(const QModelIndex& index) const {
+	return &Anime::db.items[ids[index.row()]];
+}
+
+void SearchPage::DisplayListMenu() {
+	QMenu* menu = new QMenu(this);
+	menu->setAttribute(Qt::WA_DeleteOnClose);
+	menu->setToolTipsVisible(true);
+
+	const QItemSelection selection = sort_model->mapSelectionToSource(treeview->selectionModel()->selection());
+
+	bool add_to_list_enable = true;
 
-SearchPage::SearchPage(QWidget* parent) : QWidget(parent) {
+	std::set<Anime::Anime*> animes;
+	for (const auto& index : selection.indexes()) {
+		if (!index.isValid())
+			continue;
+
+		Anime::Anime* anime = model->GetAnimeFromIndex(index);
+		if (anime) {
+			animes.insert(anime);
+			if (anime->IsInUserList())
+				add_to_list_enable = false;
+		}
+	}
+
+	menu->addAction(tr("Information"), [this, animes] {
+		for (auto& anime : animes) {
+			InformationDialog* dialog = new InformationDialog(*anime, [this, anime] {
+				//UpdateAnime(anime->GetId());
+			}, InformationDialog::PAGE_MAIN_INFO, this);
+
+			dialog->show();
+			dialog->raise();
+			dialog->activateWindow();
+		}
+	});
+	menu->addSeparator();
+	{
+		QMenu* submenu = menu->addMenu(tr("Add to list..."));
+		submenu->addAction(tr("Currently watching"), [animes]{
+			for (auto& anime : animes) {
+				if (!anime->IsInUserList())
+					anime->AddToUserList();
+				anime->SetUserStatus(Anime::ListStatus::CURRENT);
+				Services::UpdateAnimeEntry(anime->GetId());
+			}
+		});
+		submenu->addAction(tr("Completed"), [animes]{
+			for (auto& anime : animes) {
+				if (!anime->IsInUserList())
+					anime->AddToUserList();
+				anime->SetUserStatus(Anime::ListStatus::COMPLETED);
+				Services::UpdateAnimeEntry(anime->GetId());
+			}
+		});
+		submenu->addAction(tr("On hold"), [animes]{
+			for (auto& anime : animes) {
+				if (!anime->IsInUserList())
+					anime->AddToUserList();
+				anime->SetUserStatus(Anime::ListStatus::PAUSED);
+				Services::UpdateAnimeEntry(anime->GetId());
+			}
+		});
+		submenu->addAction(tr("Dropped"), [animes]{
+			for (auto& anime : animes) {
+				if (!anime->IsInUserList())
+					anime->AddToUserList();
+				anime->SetUserStatus(Anime::ListStatus::DROPPED);
+				Services::UpdateAnimeEntry(anime->GetId());
+			}
+		});
+		submenu->addAction(tr("Plan to watch"), [animes]{
+			for (auto& anime : animes) {
+				if (!anime->IsInUserList())
+					anime->AddToUserList();
+				anime->SetUserStatus(Anime::ListStatus::PLANNING);
+				Services::UpdateAnimeEntry(anime->GetId());
+			}
+		});
+		submenu->setEnabled(add_to_list_enable);
+	}
+	menu->popup(QCursor::pos());
 }
+
+void SearchPage::ItemDoubleClicked() {
+	/* throw out any other garbage */
+	const QItemSelection selection = sort_model->mapSelectionToSource(treeview->selectionModel()->selection());
+	if (!selection.indexes().first().isValid())
+		return;
+
+	const QModelIndex index = model->index(selection.indexes().first().row());
+	Anime::Anime* anime = model->GetAnimeFromIndex(index);
+
+	InformationDialog* dialog = new InformationDialog(*anime, [this, anime] {
+		//UpdateAnime(anime->GetId());
+	}, InformationDialog::PAGE_MAIN_INFO, this);
+
+	dialog->show();
+	dialog->raise();
+	dialog->activateWindow();
+}
+
+SearchPage::SearchPage(QWidget* parent) : QFrame(parent) {
+	setFrameShape(QFrame::Box);
+	setFrameShadow(QFrame::Sunken);
+
+	QVBoxLayout* layout = new QVBoxLayout(this);
+	layout->setContentsMargins(0, 0, 0, 0);
+	layout->setSpacing(0);
+
+	{
+		/* Toolbar */
+		QToolBar* toolbar = new QToolBar(this);
+		toolbar->setMovable(false);
+
+		{
+			QLineEdit* line_edit = new QLineEdit("", toolbar);
+			connect(line_edit, &QLineEdit::returnPressed, this, [this, line_edit]{
+				/* static thread here. */
+				static QThread* thread = nullptr;
+
+				if (thread)
+					return;
+
+				thread = QThread::create([this, line_edit]{
+					model->ParseSearch(Services::Search(Strings::ToUtf8String(line_edit->text())));
+				});
+
+				connect(thread, &QThread::finished, this, []{
+					thread->deleteLater();
+					thread = nullptr;
+				});
+
+				thread->start();
+			});
+			toolbar->addWidget(line_edit);
+		}
+
+		layout->addWidget(toolbar);
+	}
+
+	{
+		QFrame* line = new QFrame(this);
+		line->setFrameShape(QFrame::HLine);
+		line->setFrameShadow(QFrame::Sunken);
+		line->setLineWidth(1);
+		layout->addWidget(line);
+	}
+
+	{
+		treeview = new QTreeView(this);
+		treeview->setUniformRowHeights(true);
+		treeview->setAllColumnsShowFocus(false);
+		treeview->setAlternatingRowColors(true);
+		treeview->setSortingEnabled(true);
+		treeview->setSelectionMode(QAbstractItemView::ExtendedSelection);
+		treeview->setItemsExpandable(false);
+		treeview->setRootIsDecorated(false);
+		treeview->setContextMenuPolicy(Qt::CustomContextMenu);
+		treeview->setFrameShape(QFrame::NoFrame);
+
+		{
+			sort_model = new SearchPageListSortFilter(treeview);
+			model = new SearchPageListModel(treeview);
+			sort_model->setSourceModel(model);
+			sort_model->setSortRole(Qt::UserRole);
+			sort_model->setSortCaseSensitivity(Qt::CaseInsensitive);
+			treeview->setModel(sort_model);
+		}
+
+		// set column sizes
+		treeview->setColumnWidth(SearchPageListModel::SR_TITLE,    400);
+		treeview->setColumnWidth(SearchPageListModel::SR_TYPE,     60);
+		treeview->setColumnWidth(SearchPageListModel::SR_EPISODES, 60);
+		treeview->setColumnWidth(SearchPageListModel::SR_SCORE,    60);
+		treeview->setColumnWidth(SearchPageListModel::SR_SEASON,   100);
+
+		treeview->header()->setStretchLastSection(false);
+
+		/* Double click stuff */
+		connect(treeview, &QAbstractItemView::doubleClicked, this, &SearchPage::ItemDoubleClicked);
+		connect(treeview, &QWidget::customContextMenuRequested, this, &SearchPage::DisplayListMenu);
+
+		layout->addWidget(treeview);
+	}
+}