Mercurial > minori
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); + } +}