view src/gui/pages/search.cc @ 253:b3549da699a6

*: ooooh! stupid big commit! oops
author Paper <paper@paper.us.eu.org>
date Tue, 06 Feb 2024 16:56:32 -0500
parents c130f47f6f48
children 862d0d8619f6
line wrap: on
line source

#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;

	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);
	}
}