Mercurial > minori
changeset 7:07a9095eaeed
Update
Refactored some code, moved some around
author | Paper <mrpapersonic@gmail.com> |
---|---|
date | Thu, 24 Aug 2023 23:11:38 -0400 |
parents | 1d82f6e04d7d |
children | b1f73678ef61 |
files | CMakeLists.txt src/anilist.cpp src/anime.cpp src/config.cpp src/dialog/information.cpp src/dialog/settings.cpp src/dialog/settings/application.cpp src/dialog/settings/services.cpp src/filesystem.cpp src/include/anilist.h src/include/anime.h src/include/anime_list.h src/include/config.h src/include/information.h src/include/json.h src/include/now_playing.h src/include/session.h src/include/settings.h src/include/sidebar.h src/include/statistics.h src/include/ui_utils.h src/include/window.h src/json.cpp src/main.cpp src/pages/anime_list.cpp src/pages/now_playing.cpp src/pages/statistics.cpp src/sidebar.cpp src/string_utils.cpp src/ui_utils.cpp |
diffstat | 30 files changed, 1209 insertions(+), 843 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Wed Aug 16 00:49:17 2023 -0400 +++ b/CMakeLists.txt Thu Aug 24 23:11:38 2023 -0400 @@ -12,6 +12,9 @@ src/time.cpp src/sidebar.cpp src/progress.cpp + src/pages/anime_list.cpp + src/pages/now_playing.cpp + src/pages/statistics.cpp src/dialog/settings.cpp src/dialog/information.cpp src/dialog/settings/services.cpp @@ -20,8 +23,6 @@ src/string_utils.cpp rc/icons.qrc dep/darkstyle/darkstyle.qrc -# src/pages/statistics.cpp -# src/pages/now_playing.cpp ) if(APPLE) @@ -33,7 +34,7 @@ list(APPEND SRC_FILES src/sys/win32/dark_theme.cpp) endif() -add_executable(weeaboo WIN32 MACOSX_BUNDLE ${SRC_FILES}) +add_executable(weeaboo MACOSX_BUNDLE ${SRC_FILES}) set_property(TARGET weeaboo PROPERTY CXX_STANDARD 20) set_property(TARGET weeaboo PROPERTY AUTOMOC ON) set_property(TARGET weeaboo PROPERTY AUTORCC ON)
--- a/src/anilist.cpp Wed Aug 16 00:49:17 2023 -0400 +++ b/src/anilist.cpp Thu Aug 24 23:11:38 2023 -0400 @@ -1,15 +1,22 @@ -#include "window.h" -#include "json.h" +#include <QMessageBox> +#include <QDesktopServices> +#include <QInputDialog> +#include <QLineEdit> #include <curl/curl.h> #include <chrono> #include <exception> #include <format> +#include "json.h" #include "anilist.h" #include "anime.h" #include "config.h" #include "string_utils.h" +#include "session.h" #define CLIENT_ID "13706" +CURL* AniList::curl = NULL; +CURLcode AniList::res = (CURLcode)0; + size_t AniList::CurlWriteCallback(void *contents, size_t size, size_t nmemb, void *userdata) { ((std::string*)userdata)->append((char*)contents, size * nmemb); return size * nmemb; @@ -190,10 +197,6 @@ anime.title.native = JSON::GetString(entry.value(), "/media/title/native"_json_pointer); anime.title.english = JSON::GetString(entry.value(), "/media/title/english"_json_pointer); anime.title.romaji = JSON::GetString(entry.value(), "/media/title/romaji"_json_pointer); - /* fallback to romaji if english is not available - note that this takes up more space in memory and is stinky */ - if (anime.title.english.empty()) - anime.title.english = anime.title.romaji; anime.id = JSON::GetInt(entry.value(), "/media/id"_json_pointer); anime.episodes = JSON::GetInt(entry.value(), "/media/episodes"_json_pointer); @@ -210,8 +213,10 @@ anime.duration = JSON::GetInt(entry.value(), "/media/duration"_json_pointer); anime.synopsis = StringUtils::TextifySynopsis(JSON::GetString(entry.value(), "/media/description"_json_pointer)); - if (entry.value()["media"]["genres"].is_array()) - anime.genres = entry.value()["media"]["genres"].get<std::vector<std::string>>(); + if (entry.value().contains("/media/genres"_json_pointer) && entry.value()["/media/genres"_json_pointer].is_array()) + anime.genres = entry.value()["/media/genres"_json_pointer].get<std::vector<std::string>>(); + if (entry.value().contains("/media/synonyms"_json_pointer) && entry.value()["/media/synonyms"_json_pointer].is_array()) + anime.synonyms = entry.value()["/media/synonyms"_json_pointer].get<std::vector<std::string>>(); anime_list.Add(anime); } anime_lists->push_back(anime_list);
--- a/src/anime.cpp Wed Aug 16 00:49:17 2023 -0400 +++ b/src/anime.cpp Thu Aug 24 23:11:38 2023 -0400 @@ -1,16 +1,16 @@ +/* + * anime.cpp: defining of custom anime-related + * datatypes & variables +*/ #include <chrono> #include <string> #include <vector> #include <cmath> -#include "window.h" +#include <algorithm> #include "anilist.h" -#include "config.h" #include "anime.h" #include "date.h" -#include "time_utils.h" -#include "information.h" -#include "ui_utils.h" -#include <iostream> +#include "session.h" std::map<enum AnimeWatchingStatus, std::string> AnimeWatchingToStringMap = { {CURRENT, "Watching"}, @@ -76,9 +76,31 @@ } std::string Anime::GetUserPreferredTitle() { - if (title.english.empty()) - return title.romaji; - return title.english; + switch (session.config.anime_list.language) { + case NATIVE: + return (title.native.empty()) ? title.romaji : title.native; + case ENGLISH: + return (title.english.empty()) ? title.romaji : title.english; + default: + return title.romaji; + } +} + +std::vector<std::string> Anime::GetTitleSynonyms() { + std::vector<std::string> result; +#define IN_VECTOR(v, k) \ + (std::count(v.begin(), v.end(), k)) +#define ADD_TO_SYNONYMS(v, k) \ + if (!k.empty() && !IN_VECTOR(v, k) && k != GetUserPreferredTitle()) v.push_back(k) + ADD_TO_SYNONYMS(result, title.english); + ADD_TO_SYNONYMS(result, title.romaji); + ADD_TO_SYNONYMS(result, title.native); + for (auto& synonym : synonyms) { + ADD_TO_SYNONYMS(result, synonym); + } +#undef ADD_TO_SYNONYMS +#undef IN_VECTOR + return result; } void AnimeList::Add(Anime& anime) { @@ -131,6 +153,16 @@ name = l.name; } +AnimeList& AnimeList::operator=(const AnimeList& l) { + if (this != &l) { + for (unsigned long long i = 0; i < l.Size(); i++) { + this->anime_list.push_back(Anime(l[i])); + } + this->name = l.name; + } + return *this; +} + AnimeList::~AnimeList() { anime_list.clear(); anime_list.shrink_to_fit(); @@ -160,466 +192,3 @@ const Anime& AnimeList::operator[](std::size_t index) const { return anime_list.at(index); } - -/* ------------------------------------------------------------------------- */ - -AnimeListWidgetDelegate::AnimeListWidgetDelegate(QObject* parent) - : QStyledItemDelegate (parent) { -} - -QWidget *AnimeListWidgetDelegate::createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const -{ - // LOL - 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()); - - QStyleOptionViewItem customOption (option); - customOption.state.setFlag(QStyle::State_Enabled, true); - - progress_bar.paint(painter, customOption, index.data().toString(), progress, episodes); - 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 left.toString() < right.toString(); - } -} - -/* Thank you qBittorrent for having a great example of a - widget model. */ -AnimeListWidgetModel::AnimeListWidgetModel (QWidget* parent, AnimeList* alist) - : QAbstractListModel(parent) - , list(*alist) { - 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); -} - -Anime* AnimeListWidgetModel::GetAnimeFromIndex(const QModelIndex& index) { - return (index.isValid()) ? &(list[index.row()]) : nullptr; -} - -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(AnimeFormatToStringMap[list[index.row()].type]); - case AL_SEASON: - return QString::fromStdString(AnimeSeasonToStringMap[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_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(Anime& anime) { - int i = list.GetAnimeIndex(anime); - emit dataChanged(index(i), index(i)); -} - -int AnimeListWidget::VisibleColumnsCount() const { - int count = 0; - - for (int i = 0, end = header()->count(); i < end; i++) - { - if (!isColumnHidden(i)) - count++; - } - - return count; -} - -void AnimeListWidget::SetColumnDefaults() { - setColumnHidden(AnimeListWidgetModel::AL_SEASON, false); - setColumnHidden(AnimeListWidgetModel::AL_TYPE, false); - setColumnHidden(AnimeListWidgetModel::AL_UPDATED, false); - setColumnHidden(AnimeListWidgetModel::AL_PROGRESS, false); - setColumnHidden(AnimeListWidgetModel::AL_SCORE, false); - setColumnHidden(AnimeListWidgetModel::AL_TITLE, false); - setColumnHidden(AnimeListWidgetModel::AL_EPISODES, true); - setColumnHidden(AnimeListWidgetModel::AL_AVG_SCORE, true); - setColumnHidden(AnimeListWidgetModel::AL_STARTED, true); - setColumnHidden(AnimeListWidgetModel::AL_COMPLETED, true); - setColumnHidden(AnimeListWidgetModel::AL_UPDATED, true); - 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 = model->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); - QAction *action = menu->addAction(column_name, this, [this, i](const bool checked) { - if (!checked && (VisibleColumnsCount() <= 1)) - return; - - setColumnHidden(i, !checked); - - if (checked && (columnWidth(i) <= 5)) - resizeColumnToContents(i); - - // SaveSettings(); - }); - action->setCheckable(true); - action->setChecked(!isColumnHidden(i)); - } - - menu->addSeparator(); - QAction *resetAction = menu->addAction(tr("Reset to defaults"), this, [this]() { - for (int i = 0, count = 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_model->mapSelectionToSource(selectionModel()->selection()); - if (!selection.indexes().first().isValid()) { - return; - } - - QAction* action = menu->addAction("Information", [this, selection]{ - const QModelIndex index = model->index(selection.indexes().first().row()); - Anime* anime = model->GetAnimeFromIndex(index); - if (!anime) { - return; - } - - InformationDialog* dialog = new InformationDialog(*anime, model, this); - - dialog->show(); - dialog->raise(); - dialog->activateWindow(); - }); - menu->popup(QCursor::pos()); -} - -void AnimeListWidget::ItemDoubleClicked() { - /* throw out any other garbage */ - const QItemSelection selection = sort_model->mapSelectionToSource(selectionModel()->selection()); - if (!selection.indexes().first().isValid()) { - return; - } - - const QModelIndex index = model->index(selection.indexes().first().row()); - const QString title = index.siblingAtColumn(AnimeListWidgetModel::AL_TITLE).data(Qt::UserRole).toString(); - QMessageBox box; - box.setText(QString::number(title.size())); - box.exec(); - Anime* anime = model->GetAnimeFromIndex(index); - if (!anime) { - return; - } - - InformationDialog* dialog = new InformationDialog(*anime, model, this); - - dialog->show(); - dialog->raise(); - dialog->activateWindow(); -} - -AnimeListWidget::AnimeListWidget(QWidget* parent, AnimeList* alist) - : QTreeView(parent) { - setItemDelegate(new AnimeListWidgetDelegate(this)); - model = new AnimeListWidgetModel(parent, alist); - sort_model = new AnimeListWidgetSortFilter(this); - sort_model->setSourceModel(model); - sort_model->setSortRole(Qt::UserRole); - setModel(sort_model); - setObjectName("listwidget"); - setStyleSheet("QTreeView#listwidget{border:0px;}"); - setUniformRowHeights(true); - setAllColumnsShowFocus(false); - setSortingEnabled(true); - setSelectionMode(QAbstractItemView::ExtendedSelection); - setItemsExpandable(false); - setRootIsDecorated(false); - setContextMenuPolicy(Qt::CustomContextMenu); - connect(this, &QAbstractItemView::doubleClicked, this, &AnimeListWidget::ItemDoubleClicked); - connect(this, &QWidget::customContextMenuRequested, this, &AnimeListWidget::DisplayListMenu); - - /* Enter & return keys */ - connect(new QShortcut(Qt::Key_Return, this, nullptr, nullptr, Qt::WidgetShortcut), - &QShortcut::activated, this, &AnimeListWidget::ItemDoubleClicked); - - connect(new QShortcut(Qt::Key_Enter, this, nullptr, nullptr, Qt::WidgetShortcut), - &QShortcut::activated, this, &AnimeListWidget::ItemDoubleClicked); - - header()->setStretchLastSection(false); - header()->setContextMenuPolicy(Qt::CustomContextMenu); - connect(header(), &QWidget::customContextMenuRequested, this, &AnimeListWidget::DisplayColumnHeaderMenu); - // if(!session.config.anime_list.columns) { - SetColumnDefaults(); - // } -} - -AnimeListPage::AnimeListPage(QWidget* parent) : QTabWidget (parent) { - setDocumentMode(false); - setObjectName("animepage"); - //setStyleSheet("QTabWidget#animepage{border-bottom:0px;border-left:0px;border-right:0px;}"); - SyncAnimeList(); - for (AnimeList& list : anime_lists) { - addTab(new AnimeListWidget(this, &list), QString::fromUtf8(list.name.c_str())); - } -} - -void AnimeListPage::SyncAnimeList() { - switch (session.config.service) { - case ANILIST: { - AniList anilist = AniList(); - session.config.anilist.user_id = anilist.GetUserId(session.config.anilist.username); - FreeAnimeList(); - anilist.UpdateAnimeList(&anime_lists, session.config.anilist.user_id); - break; - } - default: - break; - } -} - -void AnimeListPage::FreeAnimeList() { - for (auto& list : anime_lists) { - list.Clear(); - } - anime_lists.clear(); -} - -int AnimeListPage::GetTotalAnimeAmount() { - int total = 0; - for (auto& list : anime_lists) { - total += list.Size(); - } - return total; -} - -int AnimeListPage::GetTotalEpisodeAmount() { - /* FIXME: this also needs to take into account rewatches... */ - int total = 0; - for (auto& list : anime_lists) { - for (auto& anime : list) { - total += anime.progress; - } - } - return total; -} - -/* Returns the total watched amount in minutes. */ -int AnimeListPage::GetTotalWatchedAmount() { - int total = 0; - for (auto& list : anime_lists) { - for (auto& anime : list) { - total += anime.duration*anime.progress; - } - } - return total; -} - -/* Returns the total planned amount in minutes. - Note that we should probably limit progress to the - amount of episodes, as AniList will let you - set episode counts up to 32768. But that should - rather be handled elsewhere. */ -int AnimeListPage::GetTotalPlannedAmount() { - int total = 0; - for (auto& list : anime_lists) { - for (auto& anime : list) { - total += anime.duration*(anime.episodes-anime.progress); - } - } - return total; -} - -double AnimeListPage::GetAverageScore() { - double avg = 0; - int amt = 0; - for (auto& list : anime_lists) { - for (auto& anime : list) { - avg += anime.score; - if (anime.score != 0) - amt++; - } - } - return avg/amt; -} - -double AnimeListPage::GetScoreDeviation() { - double squares_sum = 0, avg = GetAverageScore(); - int amt = 0; - for (auto& list : anime_lists) { - for (auto& anime : list) { - if (anime.score != 0) { - squares_sum += std::pow((double)anime.score - avg, 2); - amt++; - } - } - } - return (amt > 0) ? std::sqrt(squares_sum / amt) : 0; -} - -#include "moc_anime.cpp"
--- a/src/config.cpp Wed Aug 16 00:49:17 2023 -0400 +++ b/src/config.cpp Thu Aug 24 23:11:38 2023 -0400 @@ -41,6 +41,18 @@ {"AniList", ANILIST} }; +std::map<enum AnimeTitleLanguage, std::string> AnimeTitleToStringMap = { + {ROMAJI, "Romaji"}, + {NATIVE, "Native"}, + {ENGLISH, "English"} +}; + +std::map<std::string, enum AnimeTitleLanguage> StringToAnimeTitleMap = { + {"Romaji", ROMAJI}, + {"Native", NATIVE}, + {"English", ENGLISH} +}; + int Config::Load() { std::filesystem::path cfg_path = get_config_path(); if (!std::filesystem::exists(cfg_path)) @@ -48,6 +60,11 @@ std::ifstream config_in(cfg_path.string().c_str(), std::ifstream::in); auto config_js = nlohmann::json::parse(config_in); service = StringToService[JSON::GetString(config_js, "/General/Service"_json_pointer)]; + anime_list.language = StringToAnimeTitleMap[JSON::GetString(config_js, "/Anime List/Display only aired episodes"_json_pointer, "Romaji")]; + anime_list.display_aired_episodes = JSON::GetBoolean(config_js, "/Anime List/Display only aired episodes"_json_pointer, true); + anime_list.display_available_episodes = JSON::GetBoolean(config_js, "/Anime List/Display only available episodes in library"_json_pointer, true); + anime_list.highlight_anime_if_available = JSON::GetBoolean(config_js, "/Anime List/Highlight anime if available"_json_pointer, true); + anime_list.highlighted_anime_above_others = JSON::GetBoolean(config_js, "/Anime List/Display highlighted anime above others"_json_pointer); anilist.auth_token = JSON::GetString(config_js, "/Authorization/AniList/Auth Token"_json_pointer); anilist.username = JSON::GetString(config_js, "/Authorization/AniList/Username"_json_pointer); anilist.user_id = JSON::GetInt(config_js, "/Authorization/AniList/User ID"_json_pointer); @@ -65,6 +82,13 @@ {"General", { {"Service", ServiceToString[service]} }}, + {"Anime List", { + {"Title language", AnimeTitleToStringMap[anime_list.language]}, + {"Display only aired episodes", anime_list.display_aired_episodes}, + {"Display only available episodes in library", anime_list.display_available_episodes}, + {"Highlight anime if available", anime_list.highlight_anime_if_available}, + {"Display highlighted anime above others", anime_list.highlighted_anime_above_others} + }}, {"Authorization", { {"AniList", { {"Auth Token", anilist.auth_token},
--- a/src/dialog/information.cpp Wed Aug 16 00:49:17 2023 -0400 +++ b/src/dialog/information.cpp Thu Aug 24 23:11:38 2023 -0400 @@ -1,5 +1,10 @@ +#include <QPlainTextEdit> +#include <QVBoxLayout> +#include <QTextStream> +#include <QDebug> #include "window.h" #include "anime.h" +#include "anime_list.h" #include "information.h" #include "ui_utils.h" #include "string_utils.h" @@ -25,7 +30,7 @@ widget->move(175, 0); widget->setStyleSheet(UiUtils::IsInDarkMode() ? "" : "background-color: white"); widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - QPlainTextEdit* anime_title = new QPlainTextEdit(QString::fromUtf8(anime->title.english.c_str()), widget); + QPlainTextEdit* anime_title = new QPlainTextEdit(QString::fromUtf8(anime->GetUserPreferredTitle().c_str()), widget); anime_title->setReadOnly(true); anime_title->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); anime_title->setWordWrapMode(QTextOption::NoWrap); @@ -41,7 +46,14 @@ tabbed_widget->move(0, 45); tabbed_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); QWidget* main_information_widget = new QWidget(tabbed_widget); - UiUtils::CreateSelectableTextParagraph(main_information_widget, "Alternative titles", "-", QPoint(6, 6), QSize(636-18, 56)); + main_information_widget->setLayout(new QVBoxLayout); + + QString alternative_titles = QString::fromUtf8(StringUtils::Implode(anime->GetTitleSynonyms(), ", ").c_str()); + + QWidget* alternative_titles_w = UiUtils::CreateSelectableTextParagraph(main_information_widget, "Alternative titles", alternative_titles)->parentWidget()->parentWidget(); + //alternative_titles_w->setFixedHeight(60); + main_information_widget->layout()->addWidget(alternative_titles_w); + QString details_data(""); QTextStream details_data_s(&details_data); details_data_s << AnimeFormatToStringMap[anime->type].c_str() << "\n" @@ -50,8 +62,17 @@ << AnimeSeasonToStringMap[anime->season].c_str() << " " << anime->air_date.GetYear() << "\n" << StringUtils::Implode(anime->genres, ", ").c_str() << "\n" << anime->audience_score << "%\n"; - UiUtils::CreateTextParagraphWithLabels(main_information_widget, "Details", "Type:\nEpisodes:\nStatus:\nSeason:\nGenres:\nScore:", details_data, QPoint(6, 62), QSize(636-18, 142)); - UiUtils::CreateSelectableTextParagraph(main_information_widget, "Synopsis", QString::fromUtf8(anime->synopsis.c_str()), QPoint(6, 202), QSize(636-18, 253)); + QWidget* soidjhfh = UiUtils::CreateTextParagraphWithLabels(main_information_widget, "Details", "Type:\nEpisodes:\nStatus:\nSeason:\nGenres:\nScore:", details_data)->parentWidget()->parentWidget(); + main_information_widget->layout()->addWidget(soidjhfh); + + QPlainTextEdit* synopsis = UiUtils::CreateSelectableTextParagraph(main_information_widget, "Synopsis", QString::fromUtf8(anime->synopsis.c_str())); + synopsis->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + synopsis->parentWidget()->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + synopsis->parentWidget()->parentWidget()->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + ((QVBoxLayout*)main_information_widget->layout())->addWidget(synopsis->parentWidget()->parentWidget()); + + //((QVBoxLayout*)main_information_widget->layout())->addStretch(); + tabbed_widget->addTab(main_information_widget, "Main information"); QWidget* settings_widget = new QWidget(tabbed_widget); tabbed_widget->addTab(settings_widget, "My list and settings");
--- a/src/dialog/settings.cpp Wed Aug 16 00:49:17 2023 -0400 +++ b/src/dialog/settings.cpp Thu Aug 24 23:11:38 2023 -0400 @@ -6,7 +6,7 @@ #include <QComboBox> #include <QGroupBox> #include <QWidget> -#include <QTextDocument> +#include <QStackedWidget> #include "settings.h" #include "sidebar.h" #include "ui_utils.h" @@ -44,15 +44,10 @@ // no-op... child classes will implement this } -void SettingsDialog::OnSidebar(QListWidgetItem* item) { - layout->itemAt(1)->widget()->setVisible(false); // old widget - layout->replaceWidget(layout->itemAt(1)->widget(), pages[item->listWidget()->row(item)], Qt::FindDirectChildrenOnly); - pages[item->listWidget()->row(item)]->setVisible(true); // new widget -} - void SettingsDialog::OnOK() { - for (const auto& page : pages) { - page->SaveInfo(); + QStackedWidget* stacked = (QStackedWidget*)layout->itemAt(1)->widget(); + for (int i = 0; i < stacked->count(); i++) { + ((SettingsPage*)stacked->widget(i))->SaveInfo(); } QDialog::accept(); } @@ -77,17 +72,17 @@ sidebar->setStyleSheet("QListWidget { background-color: white; font-size: 12px; }"); sidebar->setFixedWidth(158); sidebar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); - connect(sidebar, &QListWidget::itemActivated, this, &SettingsDialog::OnSidebar); - SettingsPageServices* services_page = new SettingsPageServices(this); - pages.push_back(services_page); - SettingsPageApplication* application_page = new SettingsPageApplication(this); - application_page->setVisible(false); - pages.push_back(application_page); + QStackedWidget* stacked = new QStackedWidget; + stacked->addWidget(new SettingsPageServices(stacked)); + stacked->addWidget(new SettingsPageApplication(stacked)); + stacked->setCurrentIndex(0); + + connect(sidebar, &QListWidget::currentRowChanged, stacked, &QStackedWidget::setCurrentIndex); layout = new QHBoxLayout; layout->addWidget(sidebar); - layout->addWidget(services_page); + layout->addWidget(stacked); layout->setMargin(0); widget->setLayout(layout);
--- a/src/dialog/settings/application.cpp Wed Aug 16 00:49:17 2023 -0400 +++ b/src/dialog/settings/application.cpp Thu Aug 24 23:11:38 2023 -0400 @@ -1,13 +1,13 @@ #include "settings.h" #include "anilist.h" -#include "window.h" +#include "session.h" #include <QGroupBox> #include <QComboBox> #include <QCheckBox> #include <QPushButton> #include <QSizePolicy> -QWidget* SettingsPageApplication::CreateAnimeListPage() { +QWidget* SettingsPageApplication::CreateAnimeListWidget() { QWidget* result = new QWidget(this); result->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); @@ -23,6 +23,7 @@ QVBoxLayout* double_click_layout = new QVBoxLayout; double_click_layout->addWidget(dc_combo_box_label); double_click_layout->addWidget(dc_combo_box); + double_click_layout->setMargin(0); double_click_widget->setLayout(double_click_layout); /* Actions/Middle click */ @@ -34,6 +35,7 @@ QVBoxLayout* middle_click_layout = new QVBoxLayout; middle_click_layout->addWidget(mc_combo_box_label); middle_click_layout->addWidget(mc_combo_box); + middle_click_layout->setMargin(0); middle_click_widget->setLayout(middle_click_layout); /* Actions */ @@ -47,15 +49,27 @@ QLabel* lang_combo_box_label = new QLabel(tr("Title language preference:"), appearance_group_box); QComboBox* lang_combo_box = new QComboBox(appearance_group_box); + lang_combo_box->addItem(tr("Romaji")); + lang_combo_box->addItem(tr("Native")); lang_combo_box->addItem(tr("English")); + connect(lang_combo_box, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index){ + language = static_cast<enum AnimeTitleLanguage>(index); + }); + lang_combo_box->setCurrentIndex(language); + QCheckBox* hl_anime_box = new QCheckBox(tr("Highlight anime if next episode is available in library folders"), appearance_group_box); QCheckBox* hl_above_anime_box = new QCheckBox(tr("Display highlighted anime above others"), appearance_group_box); - hl_above_anime_box->setEnabled((hl_anime_box->checkState() == Qt::Unchecked) ? 0 : 1); - hl_above_anime_box->setStyleSheet("margin-left: 10px;"); - - connect(hl_anime_box, &QCheckBox::stateChanged, this, [hl_above_anime_box](int state){ + connect(hl_anime_box, &QCheckBox::stateChanged, this, [this, hl_above_anime_box](int state){ + highlight_anime_if_available = (state == Qt::Unchecked) ? false : true; hl_above_anime_box->setEnabled(state); }); + connect(hl_above_anime_box, &QCheckBox::stateChanged, this, [this](int state){ + highlight_anime_if_available = (state == Qt::Unchecked) ? false : true; + }); + hl_anime_box->setCheckState(highlight_anime_if_available ? Qt::Checked : Qt::Unchecked); + hl_above_anime_box->setCheckState(highlighted_anime_above_others ? Qt::Checked : Qt::Unchecked); + hl_above_anime_box->setEnabled(hl_anime_box->checkState() != Qt::Unchecked); + hl_above_anime_box->setStyleSheet("margin-left: 10px;"); /* Appearance */ QVBoxLayout* appearance_layout = new QVBoxLayout; @@ -68,12 +82,21 @@ QGroupBox* progress_group_box = new QGroupBox(tr("Progress"), result); progress_group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); - QCheckBox* display_aired_episodes = new QCheckBox(tr("Display aired episodes (estimated)"), progress_group_box); - QCheckBox* display_available_episodes = new QCheckBox(tr("Display available episodes in library folders"), progress_group_box); + QCheckBox* progress_display_aired_episodes = new QCheckBox(tr("Display aired episodes (estimated)"), progress_group_box); + connect(progress_display_aired_episodes, &QCheckBox::stateChanged, this, [this](int state){ + display_aired_episodes = (state == Qt::Unchecked) ? false : true; + }); + progress_display_aired_episodes->setCheckState(display_aired_episodes ? Qt::Checked : Qt::Unchecked); + + QCheckBox* progress_display_available_episodes = new QCheckBox(tr("Display available episodes in library folders"), progress_group_box); + connect(progress_display_available_episodes, &QCheckBox::stateChanged, this, [this](int state){ + display_available_episodes = (state == Qt::Unchecked) ? false : true; + }); + progress_display_available_episodes->setCheckState(display_available_episodes ? Qt::Checked : Qt::Unchecked); QVBoxLayout* progress_layout = new QVBoxLayout; - progress_layout->addWidget(display_aired_episodes); - progress_layout->addWidget(display_available_episodes); + progress_layout->addWidget(progress_display_aired_episodes); + progress_layout->addWidget(progress_display_available_episodes); progress_group_box->setLayout(progress_layout); QVBoxLayout* full_layout = new QVBoxLayout; @@ -86,10 +109,19 @@ } void SettingsPageApplication::SaveInfo() { - + session.config.anime_list.language = language; + session.config.anime_list.highlighted_anime_above_others = highlighted_anime_above_others; + session.config.anime_list.highlight_anime_if_available = highlight_anime_if_available; + session.config.anime_list.display_aired_episodes = display_aired_episodes; + session.config.anime_list.display_available_episodes = display_available_episodes; } SettingsPageApplication::SettingsPageApplication(QWidget* parent) : SettingsPage(parent, tr("Application")) { - AddTab(CreateAnimeListPage(), tr("Anime list")); + language = session.config.anime_list.language; + highlighted_anime_above_others = session.config.anime_list.highlighted_anime_above_others; + highlight_anime_if_available = session.config.anime_list.highlight_anime_if_available; + display_aired_episodes = session.config.anime_list.display_aired_episodes; + display_available_episodes = session.config.anime_list.display_available_episodes; + AddTab(CreateAnimeListWidget(), tr("Anime list")); }
--- a/src/dialog/settings/services.cpp Wed Aug 16 00:49:17 2023 -0400 +++ b/src/dialog/settings/services.cpp Thu Aug 24 23:11:38 2023 -0400 @@ -1,6 +1,6 @@ #include "settings.h" #include "anilist.h" -#include "window.h" +#include "session.h" #include <QGroupBox> #include <QComboBox> #include <QPushButton> @@ -15,8 +15,12 @@ QLabel* sync_combo_box_label = new QLabel(tr("Active service and metadata provider:"), sync_group_box); - sync_combo_box = new QComboBox(sync_group_box); + QComboBox* sync_combo_box = new QComboBox(sync_group_box); sync_combo_box->addItem(tr("AniList")); + connect(sync_combo_box, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index){ + service = static_cast<enum AnimeListServices>(index + 1); + }); + sync_combo_box->setCurrentIndex(service - 1); QLabel* sync_note_label = new QLabel(tr("Note: Weeaboo is unable to synchronize multiple services at the same time."), sync_group_box); @@ -43,9 +47,13 @@ QLabel* username_entry_label = new QLabel(tr("Username: (not your email address)"), group_box); QWidget* auth_widget = new QWidget(group_box); - username_entry = new QLineEdit(QString::fromUtf8(session.config.anilist.username.c_str()), auth_widget); + QLineEdit* username_entry = new QLineEdit(username, auth_widget); + connect(username_entry, &QLineEdit::editingFinished, this, [this, username_entry]{ + username = username_entry->text(); + }); + QPushButton* auth_button = new QPushButton(auth_widget); - connect(auth_button, &QPushButton::clicked, this, [this]{ + connect(auth_button, &QPushButton::clicked, this, []{ AniList a; a.Authorize(); }); @@ -75,12 +83,14 @@ } void SettingsPageServices::SaveInfo() { - session.config.anilist.username = username_entry->displayText().toStdString(); - session.config.service = static_cast<enum AnimeListServices>(sync_combo_box->currentIndex()+1); + session.config.anilist.username = username.toStdString(); + session.config.service = service; } SettingsPageServices::SettingsPageServices(QWidget* parent) : SettingsPage(parent, tr("Services")) { + username = QString::fromUtf8(session.config.anilist.username.c_str()); + service = session.config.service; AddTab(CreateMainPage(), tr("Main")); AddTab(CreateAniListPage(), tr("AniList")); }
--- a/src/filesystem.cpp Wed Aug 16 00:49:17 2023 -0400 +++ b/src/filesystem.cpp Thu Aug 24 23:11:38 2023 -0400 @@ -2,6 +2,10 @@ #include <shlobj.h> #elif defined(MACOSX) #include "sys/osx/filesystem.h" +#elif defined(__linux__) +#include <unistd.h> +#include <sys/types.h> +#include <pwd.h> #endif #include <filesystem> #include <limits.h> @@ -17,10 +21,15 @@ if (SHGetFolderPathAndSubDir(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, CONFIG_DIR, buf) == S_OK) cfg_path = std::filesystem::path(buf) / CONFIG_NAME; #elif defined(MACOSX) - /* hope and pray that std::filesystem can handle tildes... */ + /* pass all of our problems to */ cfg_path = std::filesystem::path(StringUtils::Utf8ToWstr(osx::GetApplicationSupportDirectory())) / CONFIG_DIR / CONFIG_NAME; #else // just assume POSIX - cfg_path = std::filesystem::path(getenv("HOME")) / ".config" / CONFIG_DIR / CONFIG_NAME; + if (getenv("HOME") != NULL) + cfg_path = std::filesystem::path(getenv("HOME")) / ".config" / CONFIG_DIR / CONFIG_NAME; +#ifdef __linux__ + else + cfg_path = std::filesystem::path(getpwuid(getuid())->pw_dir) / ".config" / CONFIG_DIR / CONFIG_NAME; +#endif // __linux__ #endif return cfg_path; }
--- a/src/include/anilist.h Wed Aug 16 00:49:17 2023 -0400 +++ b/src/include/anilist.h Thu Aug 24 23:11:38 2023 -0400 @@ -6,17 +6,17 @@ class AniList { public: static int Authorize(); - int GetUserId(std::string name); - int UpdateAnimeList(std::vector<AnimeList>* anime_lists, int id); + static int GetUserId(std::string name); + static int UpdateAnimeList(std::vector<AnimeList>* anime_lists, int id); private: static size_t CurlWriteCallback(void *contents, size_t size, size_t nmemb, void *userdata); - enum AnimeWatchingStatus ConvertWatchingStatusToEnum(std::string status); - enum AnimeAiringStatus ConvertAiringStatusToEnum(std::string status); - enum AnimeFormat ConvertFormatToEnum(std::string format); - enum AnimeSeason ConvertSeasonToEnum(std::string season); - std::string SendRequest(std::string data); - CURL* curl; - CURLcode res; + static std::string SendRequest(std::string data); + static enum AnimeWatchingStatus ConvertWatchingStatusToEnum(std::string status); + static enum AnimeAiringStatus ConvertAiringStatusToEnum(std::string status); + static enum AnimeFormat ConvertFormatToEnum(std::string format); + static enum AnimeSeason ConvertSeasonToEnum(std::string season); + static CURL* curl; + static CURLcode res; }; #endif // __anilist_h
--- a/src/include/anime.h Wed Aug 16 00:49:17 2023 -0400 +++ b/src/include/anime.h Thu Aug 24 23:11:38 2023 -0400 @@ -2,8 +2,6 @@ #define __anime_h #include <vector> #include <map> -#include <QStyledItemDelegate> -#include <QProgressBar> #include "date.h" #include "window.h" #include "progress.h" @@ -66,6 +64,7 @@ std::string english; std::string native; } title; + std::vector<std::string> synonyms; int episodes; enum AnimeAiringStatus airing; Date air_date; @@ -78,6 +77,7 @@ int duration; std::string GetUserPreferredTitle(); + std::vector<std::string> GetTitleSynonyms(); }; /* This is a simple wrapper on a vector that provides @@ -86,6 +86,7 @@ public: AnimeList(); AnimeList(const AnimeList& l); + AnimeList& operator=(const AnimeList& l); ~AnimeList(); void Add(Anime& anime); void Insert(size_t pos, Anime& anime); @@ -108,96 +109,6 @@ std::map<int, Anime*> anime_id_to_anime; }; -class AnimeListWidgetDelegate : public QStyledItemDelegate { - Q_OBJECT - - public: - explicit AnimeListWidgetDelegate(QObject *parent); - - QWidget *createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const override; - void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; - - protected: - AnimeProgressBar progress_bar; -}; - -class AnimeListWidgetSortFilter : public QSortFilterProxyModel -{ - Q_OBJECT - - public: - AnimeListWidgetSortFilter(QObject *parent = nullptr); - - protected: - bool lessThan(const QModelIndex &l, const QModelIndex &r) const override; -}; - -class AnimeListWidgetModel : public QAbstractListModel { - Q_OBJECT - public: - enum columns { - AL_TITLE, - AL_PROGRESS, - AL_EPISODES, - AL_SCORE, - AL_AVG_SCORE, - AL_TYPE, - AL_SEASON, - AL_STARTED, - AL_COMPLETED, - AL_UPDATED, - AL_NOTES, - - NB_COLUMNS - }; - - AnimeListWidgetModel(QWidget* parent, AnimeList* alist); - ~AnimeListWidgetModel() override = default; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex& index, int role) const override; - QVariant headerData(const int section, const Qt::Orientation orientation, const int role) const override; - Anime* GetAnimeFromIndex(const QModelIndex& index); - void UpdateAnime(Anime& anime); - - private: - //void AddAnime(AnimeList& list); - AnimeList& list; -}; - -class AnimeListWidget : public QTreeView { - Q_OBJECT - public: - AnimeListWidget(QWidget* parent, AnimeList* alist); - - private slots: - void DisplayColumnHeaderMenu(); - void DisplayListMenu(); - void ItemDoubleClicked(); - void SetColumnDefaults(); - int VisibleColumnsCount() const; - - private: - AnimeListWidgetModel* model = nullptr; - AnimeListWidgetSortFilter* sort_model = nullptr; -}; - -class AnimeListPage : public QTabWidget { - public: - AnimeListPage(QWidget* parent = nullptr); - void SyncAnimeList(); - void FreeAnimeList(); - int GetTotalAnimeAmount(); - int GetTotalEpisodeAmount(); - int GetTotalWatchedAmount(); - int GetTotalPlannedAmount(); - double GetAverageScore(); - double GetScoreDeviation(); - - private: - std::vector<AnimeList> anime_lists; -}; - extern std::map<enum AnimeSeason, std::string> AnimeSeasonToStringMap; extern std::map<enum AnimeFormat, std::string> AnimeFormatToStringMap; extern std::map<enum AnimeWatchingStatus, std::string> AnimeWatchingToStringMap;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/include/anime_list.h Thu Aug 24 23:11:38 2023 -0400 @@ -0,0 +1,109 @@ +#ifndef __anime_list_h +#define __anime_list_h +#include <vector> +#include <QStyledItemDelegate> +#include <QSortFilterProxyModel> +#include <QAbstractListModel> +#include <QTreeView> +#include <QWidget> +#include "anime.h" +#include "progress.h" + +class AnimeListWidgetDelegate : public QStyledItemDelegate { + Q_OBJECT + + public: + explicit AnimeListWidgetDelegate(QObject *parent); + + QWidget *createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const override; + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + + protected: + AnimeProgressBar progress_bar; +}; + +class AnimeListWidgetSortFilter : public QSortFilterProxyModel +{ + Q_OBJECT + + public: + AnimeListWidgetSortFilter(QObject *parent = nullptr); + + protected: + bool lessThan(const QModelIndex &l, const QModelIndex &r) const override; +}; + +class AnimeListWidgetModel : public QAbstractListModel { + Q_OBJECT + + public: + enum columns { + AL_TITLE, + AL_PROGRESS, + AL_EPISODES, + AL_SCORE, + AL_AVG_SCORE, + AL_TYPE, + AL_SEASON, + AL_STARTED, + AL_COMPLETED, + AL_UPDATED, + AL_NOTES, + + NB_COLUMNS + }; + + AnimeListWidgetModel(QWidget* parent, AnimeList* alist); + ~AnimeListWidgetModel() override = default; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role) const override; + QVariant headerData(const int section, const Qt::Orientation orientation, const int role) const override; + Anime* GetAnimeFromIndex(const QModelIndex& index); + void UpdateAnime(Anime& anime); + void Update(const AnimeList& new_list); + + private: + //void AddAnime(AnimeList& list); + AnimeList& list; +}; + +/* todo: rename these to "page" or something more + sensible than "widget" */ +class AnimeListWidget : public QWidget { + Q_OBJECT + + public: + AnimeListWidget(QWidget* parent); + void SyncAnimeList(); + void FreeAnimeList(); + int GetTotalAnimeAmount(); + int GetTotalEpisodeAmount(); + int GetTotalWatchedAmount(); + int GetTotalPlannedAmount(); + double GetAverageScore(); + double GetScoreDeviation(); + + protected: + void paintEvent(QPaintEvent*) override; + void InitStyle(QStyleOptionTabWidgetFrame *option) const; + void InitBasicStyle(QStyleOptionTabWidgetFrame *option) const; + void SetupLayout(); + void showEvent(QShowEvent*) override; + void resizeEvent(QResizeEvent* e) override; + + private slots: + void DisplayColumnHeaderMenu(); + void DisplayListMenu(); + void ItemDoubleClicked(); + void SetColumnDefaults(); + int VisibleColumnsCount() const; + + private: + QTabBar* tab_bar; + QTreeView* tree_view; + QRect panelRect; + std::vector<AnimeListWidgetSortFilter*> sort_models; + std::vector<AnimeList> anime_lists; +}; +#endif // __anime_list_h \ No newline at end of file
--- a/src/include/config.h Wed Aug 16 00:49:17 2023 -0400 +++ b/src/include/config.h Thu Aug 24 23:11:38 2023 -0400 @@ -1,15 +1,17 @@ #ifndef __config_h #define __config_h -/* This should be moved to anime_list.h, but unfortunately - #include-ing anime_list.h in this file causes a shitstorm for - whatever reason, so I'll just leave it here */ +enum AnimeTitleLanguage { + ROMAJI, + NATIVE, + ENGLISH +}; + enum AnimeListServices { NONE, ANILIST, NB_SERVICES }; -/* todo: make this a class enum */ enum Themes { LIGHT, DARK, @@ -23,6 +25,15 @@ enum AnimeListServices service; enum Themes theme; + + struct { + enum AnimeTitleLanguage language; + bool display_aired_episodes; + bool display_available_episodes; + bool highlight_anime_if_available; + bool highlighted_anime_above_others; + } anime_list; + struct { std::string auth_token; std::string username;
--- a/src/include/information.h Wed Aug 16 00:49:17 2023 -0400 +++ b/src/include/information.h Thu Aug 24 23:11:38 2023 -0400 @@ -1,5 +1,6 @@ #ifndef __information_h #define __information_h +#include <QDialog> #include "anime.h" class InformationDialog: public QDialog { Q_OBJECT
--- a/src/include/json.h Wed Aug 16 00:49:17 2023 -0400 +++ b/src/include/json.h Thu Aug 24 23:11:38 2023 -0400 @@ -1,8 +1,10 @@ #include "../../dep/json/json.h" - +#ifndef __json_h +#define __json_h namespace JSON { - std::string GetString(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr); - int GetInt(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr); - bool GetBoolean(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr); - double GetDouble(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr); + std::string GetString(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, std::string def = ""); + int GetInt(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, int def = 0); + bool GetBoolean(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, bool def = false); + double GetDouble(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, double def = 0); } +#endif // __json_h \ No newline at end of file
--- a/src/include/now_playing.h Wed Aug 16 00:49:17 2023 -0400 +++ b/src/include/now_playing.h Thu Aug 24 23:11:38 2023 -0400 @@ -1,9 +1,12 @@ #ifndef __now_playing_h #define __now_playing_h +#include <QWidget> -class NowPlaying { +class NowPlayingWidget : public QWidget { + Q_OBJECT + public: - NowPlaying(page_t* page, wxPanel* frame); + NowPlayingWidget(QWidget* parent = nullptr); }; #endif // __now_playing_h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/include/session.h Thu Aug 24 23:11:38 2023 -0400 @@ -0,0 +1,16 @@ +#ifndef __session_h +#define __session_h +#include <QElapsedTimer> +#include "config.h" + +struct Session { + Config config; + Session() { timer.start(); } + int uptime() { return timer.nsecsElapsed() / 1000; } + + private: QElapsedTimer timer; +}; + +extern Session session; + +#endif // __session_h \ No newline at end of file
--- a/src/include/settings.h Wed Aug 16 00:49:17 2023 -0400 +++ b/src/include/settings.h Thu Aug 24 23:11:38 2023 -0400 @@ -8,6 +8,7 @@ #include <QComboBox> #include <QHBoxLayout> #include "sidebar.h" +#include "anime.h" class SettingsPage : public QWidget { Q_OBJECT @@ -30,8 +31,8 @@ private: QWidget* CreateMainPage(); QWidget* CreateAniListPage(); - QLineEdit* username_entry; - QComboBox* sync_combo_box; + QString username; + enum AnimeListServices service; }; class SettingsPageApplication : public SettingsPage { @@ -40,7 +41,12 @@ void SaveInfo() override; private: - QWidget* CreateAnimeListPage(); + QWidget* CreateAnimeListWidget(); + enum AnimeTitleLanguage language; + bool display_aired_episodes; + bool display_available_episodes; + bool highlight_anime_if_available; + bool highlighted_anime_above_others; }; class SettingsDialog : public QDialog { @@ -49,11 +55,9 @@ public: SettingsDialog(QWidget* parent = nullptr); QWidget* CreateServicesMainPage(QWidget* parent); - void OnSidebar(QListWidgetItem* item); void OnOK(); private: - std::vector<SettingsPage*> pages; QHBoxLayout* layout; SideBar* sidebar; };
--- a/src/include/sidebar.h Wed Aug 16 00:49:17 2023 -0400 +++ b/src/include/sidebar.h Thu Aug 24 23:11:38 2023 -0400 @@ -1,10 +1,23 @@ #ifndef __sidebar_h #define __sidebar_h #include <QListWidget> +#include <QListWidgetItem> +#include <QItemSelectionModel> class SideBar : public QListWidget { + Q_OBJECT + public: SideBar(QWidget *parent = nullptr); QListWidgetItem* AddItem(QString name, QIcon icon = QIcon()); QListWidgetItem* AddSeparator(); + bool IndexIsSeparator(QModelIndex index) const; + + signals: + void CurrentItemChanged(int index); + + protected: + virtual void mouseMoveEvent(QMouseEvent* event) override; + QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex & index, const QEvent * event) const override; + int RemoveSeparatorsFromIndex(int index); }; #endif // __sidebar_h
--- a/src/include/statistics.h Wed Aug 16 00:49:17 2023 -0400 +++ b/src/include/statistics.h Thu Aug 24 23:11:38 2023 -0400 @@ -1,30 +1,23 @@ #ifndef __statistics_h #define __statistics_h -class Statistics; - -class StatisticsTimer : public wxTimer { - public: - StatisticsTimer(Statistics* caller); - virtual void Notify(); +#include <QWidget> +#include <QFrame> +#include <QPlainTextEdit> +#include "anime_list.h" - private: - Statistics* statistics; -}; - -class Statistics { +class StatisticsWidget : public QFrame { public: - Statistics(page_t* page, wxPanel* frame); + StatisticsWidget(AnimeListWidget* listwidget, QWidget* parent = nullptr); void UpdateStatistics(); private: std::string MinutesToDateString(int minutes); - wxPanel* panel; - AnimeListPage* anime_list; - wxStaticText* anime_list_data; + AnimeListWidget* anime_list; + QPlainTextEdit* anime_list_data; - wxStaticText* score_distribution_title; - wxStaticText* score_distribution_labels; + //QPlainTextEdit* score_distribution_title; + //QPlainTextEdit* score_distribution_labels; //wxStaticText* score_distribution_graph; // how am I gonna do this /* we don't HAVE a local database (yet ;)) */ @@ -32,9 +25,6 @@ //wxStaticText* local_database_labels; //wxStaticText* local_database_data; - wxStaticText* application_title; - wxStaticText* application_labels; - wxStaticText* application_data; - StatisticsTimer* timer; + QPlainTextEdit* application_data; }; #endif // __statistics_h \ No newline at end of file
--- a/src/include/ui_utils.h Wed Aug 16 00:49:17 2023 -0400 +++ b/src/include/ui_utils.h Thu Aug 24 23:11:38 2023 -0400 @@ -6,13 +6,22 @@ #include <QSize> #include <QDateTime> #include <QIcon> +#include <QPlainTextEdit> namespace UiUtils { QIcon CreateSideBarIcon(const char* file); bool IsInDarkMode(); std::string GetLengthFromQDateTime(QDateTime stamp); - QPlainTextEdit* CreateTextParagraph(QWidget* parent, QString title, QString data, QPoint point, QSize size); - QPlainTextEdit* CreateTextParagraphWithLabels(QWidget* parent, QString title, QString label, QString data, QPoint point, QSize size); - QPlainTextEdit* CreateSelectableTextParagraph(QWidget* parent, QString title, QString data, QPoint point, QSize size); - void CreateTextHeader(QWidget* parent, QString title, QPoint point, QSize size); + QPlainTextEdit* CreateTextParagraph(QWidget* parent, QString title, QString data); + QPlainTextEdit* CreateTextParagraphWithLabels(QWidget* parent, QString title, QString label, QString data); + QPlainTextEdit* CreateSelectableTextParagraph(QWidget* parent, QString title, QString data); + void SetPlainTextEditData(QPlainTextEdit* text_edit, QString data); + void CreateTextHeader(QWidget* parent, QString title); +}; + +class Paragraph : public QPlainTextEdit { + public: + Paragraph(QString text, QWidget* parent = nullptr); + QSize minimumSizeHint() const override; + QSize sizeHint() const override; }; #endif // __ui_utils_h \ No newline at end of file
--- a/src/include/window.h Wed Aug 16 00:49:17 2023 -0400 +++ b/src/include/window.h Thu Aug 24 23:11:38 2023 -0400 @@ -1,30 +1,9 @@ #ifndef __window_h -# define __window_h -/* FIXME: include these in specific .cpp files */ -# include <QApplication> -# include <QMainWindow> -# include <QToolBar> -# include <QMenuBar> -# include <QWidget> -# include <QTreeView> -# include <QMessageBox> -# include <QDesktopServices> -# include <QUrl> -# include <QInputDialog> -# include <QDate> -# include <QHeaderView> -# include <QShortcut> -# include <QFile> -# include <QTextStream> -# include <QCloseEvent> -# include <QPlainTextEdit> -# include <QLabel> -# include <QHBoxLayout> -# include <QTextStream> -# include <QSortFilterProxyModel> -# include "config.h" -//# include "statistics.h" -//# include "now_playing.h" +#define __window_h +#include <QMainWindow> +#include <QWidget> +#include <QCloseEvent> +#include "config.h" class MainWindow : public QMainWindow { public: @@ -36,12 +15,6 @@ private: QWidget* main_widget; - QWidget* anime_list_page; }; -struct Session { - Config config; -}; - -extern Session session; #endif // __window_h
--- a/src/json.cpp Wed Aug 16 00:49:17 2023 -0400 +++ b/src/json.cpp Thu Aug 24 23:11:38 2023 -0400 @@ -2,28 +2,28 @@ namespace JSON { -std::string GetString(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr) { +std::string GetString(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, std::string def) { if (json.contains(ptr) && json[ptr].is_string()) return json[ptr].get<std::string>(); - else return ""; + else return def; } -int GetInt(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr) { +int GetInt(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, int def) { if (json.contains(ptr) && json[ptr].is_number()) return json[ptr].get<int>(); - else return 0; + else return def; } -bool GetBoolean(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr) { +bool GetBoolean(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, bool def) { if (json.contains(ptr) && json[ptr].is_boolean()) return json[ptr].get<bool>(); - else return false; + else return def; } -double GetDouble(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr) { +double GetDouble(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, double def) { if (json.contains(ptr) && json[ptr].is_number()) return json[ptr].get<double>(); - else return 0; + else return def; } }
--- a/src/main.cpp Wed Aug 16 00:49:17 2023 -0400 +++ b/src/main.cpp Thu Aug 24 23:11:38 2023 -0400 @@ -1,18 +1,27 @@ +#include <QApplication> +#include <QMainWindow> +#include <QMenuBar> +#include <QPlainTextEdit> +#include <QStackedWidget> +#include <QFile> +#include <QTextStream> +#include <QMessageBox> #include "window.h" #include "config.h" -#include "anime.h" +#include "anime_list.h" +#include "now_playing.h" +#include "statistics.h" #include "sidebar.h" #include "ui_utils.h" #include "settings.h" +#include "session.h" #if MACOSX #include "sys/osx/dark_theme.h" #elif WIN32 #include "sys/win32/dark_theme.h" #endif -Session session = { - .config = Config() -}; +Session session; /* note that this code was originally created for use in wxWidgets, but I thought the API was a little meh, so @@ -79,8 +88,7 @@ }); setMenuBar(menubar); - - /* Side toolbar */ + SideBar* sidebar = new SideBar(main_widget); sidebar->AddItem("Now Playing", UiUtils::CreateSideBarIcon(":/icons/16x16/film.png")); sidebar->AddSeparator(); @@ -94,59 +102,31 @@ sidebar->setFixedWidth(128); sidebar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); - anime_list_page = new AnimeListPage(parent); + QStackedWidget* stack = new QStackedWidget(main_widget); + stack->addWidget(new NowPlayingWidget(parent)); + AnimeListWidget* list_widget = new AnimeListWidget(parent); + list_widget->SyncAnimeList(); + stack->addWidget(list_widget); + stack->addWidget(new StatisticsWidget(list_widget, parent)); + + connect(sidebar, &SideBar::CurrentItemChanged, stack, [stack](int index){ + switch (index) { + case 0: + case 1: + case 2: + stack->setCurrentIndex(index); + break; + default: + break; + } + }); + sidebar->setCurrentRow(2); QHBoxLayout* layout = new QHBoxLayout(main_widget); layout->addWidget(sidebar, 0, Qt::AlignLeft | Qt::AlignTop); - layout->addWidget(anime_list_page); - SetActivePage(main_widget); -/* - QToolBar* toolbar = new QToolBar(parent); - QActionGroup* tb_action_group = new QActionGroup(toolbar); - - action = toolbar->addAction("Now Playing"); - action->setActionGroup(tb_action_group); - action->setCheckable(true); - - toolbar->addSeparator(); + layout->addWidget(stack); + setCentralWidget(main_widget); - action = toolbar->addAction("Anime List", [this]() { - setCentralWidget(anime_list_page); - }); - action->setActionGroup(tb_action_group); - action->setCheckable(true); - action->setChecked(true); - anime_list_page = new AnimeListPage(parent); - SetActivePage(anime_list_page); - action = toolbar->addAction("History"); - action->setActionGroup(tb_action_group); - action->setCheckable(true); - action = toolbar->addAction("Statistics"); - action->setActionGroup(tb_action_group); - action->setCheckable(true); - - toolbar->addSeparator(); - - action = toolbar->addAction("Search"); - action->setActionGroup(tb_action_group); - action->setCheckable(true); - action = toolbar->addAction("Seasons"); - action->setActionGroup(tb_action_group); - action->setCheckable(true); - action = toolbar->addAction("Torrents"); - action->setActionGroup(tb_action_group); - action->setCheckable(true); - - toolbar->setMovable(false); - toolbar->setFloatable(false); - toolbar->setMinimumSize(QSize(140, 0)); - toolbar->setObjectName("sidebar"); - toolbar->setStyleSheet("QToolBar#sidebar{margin: 6px;}"); - //toolbar->setFrameShape(QFrame::NoFrame); - toolbar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Maximum); - - addToolBar(Qt::LeftToolBarArea, toolbar); -*/ ThemeChanged(); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pages/anime_list.cpp Thu Aug 24 23:11:38 2023 -0400 @@ -0,0 +1,565 @@ +/** + * 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 <cmath> +#include <QStyledItemDelegate> +#include <QProgressBar> +#include <QShortcut> +#include <QHBoxLayout> +#include <QStylePainter> +#include <QMenu> +#include <QHeaderView> +#include "anilist.h" +#include "anime.h" +#include "anime_list.h" +#include "information.h" +#include "session.h" +#include "time_utils.h" + +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()); + + QStyleOptionViewItem customOption (option); + customOption.state.setFlag(QStyle::State_Enabled, true); + + progress_bar.paint(painter, customOption, index.data().toString(), progress, episodes); + 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, AnimeList* alist) + : QAbstractListModel(parent) + , list(*alist) { + 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); +} + +Anime* AnimeListWidgetModel::GetAnimeFromIndex(const QModelIndex& index) { + return (index.isValid()) ? &(list[index.row()]) : nullptr; +} + +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(AnimeFormatToStringMap[list[index.row()].type]); + case AL_SEASON: + return QString::fromStdString(AnimeSeasonToStringMap[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_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(Anime& anime) { + int i = list.GetAnimeIndex(anime); + emit dataChanged(index(i), index(i)); +} + +void AnimeListWidgetModel::Update(AnimeList const& new_list) { + list = AnimeList(new_list); + emit dataChanged(index(0), index(rowCount())); +} + +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 = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->GetAnimeFromIndex(index); + if (!anime) { + return; + } + + InformationDialog* dialog = new InformationDialog(*anime, ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel()), 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 = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->GetAnimeFromIndex(index); + if (!anime) { + return; + } + + InformationDialog* dialog = new InformationDialog(*anime, ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel()), 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->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); + 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 (index < sort_models.size()) + tree_view->setModel(sort_models[index]); + }); + + setFocusPolicy(Qt::TabFocus); + setFocusProxy(tab_bar); +} + +void AnimeListWidget::SyncAnimeList() { + switch (session.config.service) { + case ANILIST: { + session.config.anilist.user_id = AniList::GetUserId(session.config.anilist.username); + FreeAnimeList(); + AniList::UpdateAnimeList(&anime_lists, session.config.anilist.user_id); + break; + } + default: + break; + } + for (unsigned int i = 0; i < anime_lists.size(); i++) { + tab_bar->addTab(QString::fromStdString(anime_lists[i].name)); + AnimeListWidgetSortFilter* sort_model = new AnimeListWidgetSortFilter(tree_view); + sort_model->setSourceModel(new AnimeListWidgetModel(this, &anime_lists[i])); + sort_model->setSortRole(Qt::UserRole); + sort_model->setSortCaseSensitivity(Qt::CaseInsensitive); + sort_models.push_back(sort_model); + } + if (anime_lists.size() > 0) + tree_view->setModel(sort_models.at(0)); + SetColumnDefaults(); + SetupLayout(); +} + +void AnimeListWidget::FreeAnimeList() { + while (tab_bar->count()) + tab_bar->removeTab(0); + while (sort_models.size()) { + delete sort_models[sort_models.size()-1]; + sort_models.pop_back(); + } + for (auto& list : anime_lists) { + list.Clear(); + } + anime_lists.clear(); +} + +int AnimeListWidget::GetTotalAnimeAmount() { + int total = 0; + for (auto& list : anime_lists) { + total += list.Size(); + } + return total; +} + +int AnimeListWidget::GetTotalEpisodeAmount() { + /* FIXME: this also needs to take into account rewatches... */ + int total = 0; + for (auto& list : anime_lists) { + for (auto& anime : list) { + total += anime.progress; + } + } + return total; +} + +/* Returns the total watched amount in minutes. */ +int AnimeListWidget::GetTotalWatchedAmount() { + int total = 0; + for (auto& list : anime_lists) { + for (auto& anime : list) { + total += anime.duration*anime.progress; + } + } + return total; +} + +/* Returns the total planned amount in minutes. + Note that we should probably limit progress to the + amount of episodes, as AniList will let you + set episode counts up to 32768. But that should + rather be handled elsewhere. */ +int AnimeListWidget::GetTotalPlannedAmount() { + int total = 0; + for (auto& list : anime_lists) { + for (auto& anime : list) { + total += anime.duration*(anime.episodes-anime.progress); + } + } + return total; +} + +double AnimeListWidget::GetAverageScore() { + double avg = 0; + int amt = 0; + for (auto& list : anime_lists) { + for (auto& anime : list) { + avg += anime.score; + if (anime.score != 0) + amt++; + } + } + return avg/amt; +} + +double AnimeListWidget::GetScoreDeviation() { + double squares_sum = 0, avg = GetAverageScore(); + int amt = 0; + for (auto& list : anime_lists) { + for (auto& anime : list) { + if (anime.score != 0) { + squares_sum += std::pow((double)anime.score - avg, 2); + amt++; + } + } + } + return (amt > 0) ? std::sqrt(squares_sum / amt) : 0; +} + +#include "moc_anime_list.cpp"
--- a/src/pages/now_playing.cpp Wed Aug 16 00:49:17 2023 -0400 +++ b/src/pages/now_playing.cpp Thu Aug 24 23:11:38 2023 -0400 @@ -1,6 +1,7 @@ -#include "window.h" +#include "now_playing.h" -NowPlaying::NowPlaying(page_t* page, wxPanel* frame) { - page->panel = new wxPanel(frame, wxID_ANY, wxPoint(0, 0), wxSize(400, 600)); - page->panel->Show(false); +NowPlayingWidget::NowPlayingWidget(QWidget* parent) : QWidget(parent) { + } + +#include "moc_now_playing.cpp" \ No newline at end of file
--- a/src/pages/statistics.cpp Wed Aug 16 00:49:17 2023 -0400 +++ b/src/pages/statistics.cpp Thu Aug 24 23:11:38 2023 -0400 @@ -1,32 +1,41 @@ -#include "window.h" +#include <sstream> +#include <QWidget> +#include <QTimer> +#include <QTextStream> +#include <QString> +#include <QTextDocument> +#include <QVBoxLayout> +#include "anime_list.h" #include "ui_utils.h" -#include <sstream> +#include "statistics.h" + +StatisticsWidget::StatisticsWidget(AnimeListWidget* listwidget, QWidget* parent) + : QFrame(parent) { + setLayout(new QVBoxLayout); + anime_list = listwidget; -StatisticsTimer::StatisticsTimer(Statistics* caller) { - statistics = caller; -} + setFrameShape(QFrame::Panel); + setFrameShadow(QFrame::Plain); + + layout()->addWidget((anime_list_data = UiUtils::CreateTextParagraphWithLabels(this, "Anime list", "Anime count:\nEpisode count:\nTime spent watching:\nTime to complete:\nAverage score:\nScore deviation:", ""))->parentWidget()->parentWidget()); + ((QBoxLayout*)layout())->addStretch(); -void StatisticsTimer::Notify() { - if (status.current_page == PAGE_STATISTICS) - statistics->UpdateStatistics(); + QPalette pal = QPalette(); + pal.setColor(QPalette::Window, Qt::white); + setAutoFillBackground(true); + setPalette(pal); + + UpdateStatistics(); // load in statistics as soon as possible + + QTimer* timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, [this]{ + if (isVisible()) + UpdateStatistics(); + }); + timer->start(1000); // update statistics every second } -Statistics::Statistics(page_t* page, wxPanel* frame) { - page->panel = new wxPanel(frame, wxID_ANY, wxPoint(0, 0), wxSize(400, 600)); - page->panel->Show(false); - panel = new wxPanel(page->panel, wxID_ANY, wxPoint(12, 12), wxSize(376, 576)); - anime_list = ((WeeabooFrame*)frame->GetParent())->GetAnimeList(); - - /* FIXME: this should be moved to a separate function, it's also used in information.cpp */ - // wxWindow* parent, const char* title, const char* label, const char* data, int width, int height, int x = 0, int y = 0, int selectable = 0 - anime_list_data = UiUtils::CreateTextParagraphWithLabels(panel, L"Anime list", L"Anime count:\nEpisode count:\nTime spent watching:\nTime to complete:\nAverage score:\nScore deviation:", L"", 376, 94); - - UpdateStatistics(); // load in statistics as soon as possible - timer = new StatisticsTimer(this); - timer->Start(1000); // update statistics every second -} - -std::string Statistics::MinutesToDateString(int minutes) { +std::string StatisticsWidget::MinutesToDateString(int minutes) { /* NOTE: these duration_casts may not be needed... */ std::chrono::duration<int, std::ratio<60>> int_total_mins(minutes); auto int_years = std::chrono::duration_cast<std::chrono::years>(int_total_mins); @@ -51,13 +60,14 @@ return return_stream.str(); } -void Statistics::UpdateStatistics() { - wxString string = ""; - string << anime_list->GetTotalAnimeAmount() << '\n'; - string << anime_list->GetTotalEpisodeAmount() << '\n'; - string << MinutesToDateString(anime_list->GetTotalWatchedAmount()) << '\n'; - string << MinutesToDateString(anime_list->GetTotalPlannedAmount()) << '\n'; - string << anime_list->GetAverageScore() << '\n'; - string << anime_list->GetScoreDeviation() << '\n'; - anime_list_data->SetLabel(string); +void StatisticsWidget::UpdateStatistics() { + QString string = ""; + QTextStream ts(&string); + ts << anime_list->GetTotalAnimeAmount() << '\n'; + ts << anime_list->GetTotalEpisodeAmount() << '\n'; + ts << MinutesToDateString(anime_list->GetTotalWatchedAmount()).c_str() << '\n'; + ts << MinutesToDateString(anime_list->GetTotalPlannedAmount()).c_str() << '\n'; + ts << anime_list->GetAverageScore() << '\n'; + ts << anime_list->GetScoreDeviation() << '\n'; + UiUtils::SetPlainTextEditData(anime_list_data, string); }
--- a/src/sidebar.cpp Wed Aug 16 00:49:17 2023 -0400 +++ b/src/sidebar.cpp Thu Aug 24 23:11:38 2023 -0400 @@ -1,7 +1,9 @@ +#include "sidebar.h" #include <QListWidget> #include <QListWidgetItem> #include <QFrame> -#include "sidebar.h" +#include <QMouseEvent> +#include <QMessageBox> SideBar::SideBar(QWidget *parent) : QListWidget(parent) @@ -10,8 +12,14 @@ setFrameShape(QFrame::NoFrame); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setSelectionMode(QAbstractItemView::SingleSelection); + setSelectionBehavior(QAbstractItemView::SelectItems); + setMouseTracking(true); viewport()->setAutoFillBackground(false); setStyleSheet("font-size: 12px"); + connect(this, &QListWidget::currentRowChanged, this, [this](int index){ + emit CurrentItemChanged(RemoveSeparatorsFromIndex(index)); + }); } QListWidgetItem* SideBar::AddItem(QString name, QIcon icon) { @@ -24,12 +32,44 @@ QListWidgetItem* SideBar::AddSeparator() { QListWidgetItem* item = new QListWidgetItem(this); - item->setFlags(item->flags() & ~Qt::ItemIsEnabled); setStyleSheet("QListWidget::item:disabled {background: transparent;}"); QFrame* line = new QFrame(this); line->setFrameShape(QFrame::HLine); line->setFrameShadow(QFrame::Sunken); + line->setMouseTracking(true); line->setEnabled(false); setItemWidget(item, line); + item->setFlags(Qt::NoItemFlags); return item; } + +int SideBar::RemoveSeparatorsFromIndex(int index) { + int i, j; + for (i = 0, j = 0; i < index; i++) { + if (!IndexIsSeparator(indexFromItem(item(i)))) j++; + } + return j; +} + +bool SideBar::IndexIsSeparator(QModelIndex index) const { + return !(index.isValid() && index.flags() & Qt::ItemIsEnabled); +} + +QItemSelectionModel::SelectionFlags SideBar::selectionCommand(const QModelIndex & index, + const QEvent * event) const { + if (IndexIsSeparator(index)) + return QItemSelectionModel::NoUpdate; + return QItemSelectionModel::ClearAndSelect; + /* silence unused parameter warnings */ + (void)event; +} + +void SideBar::mouseMoveEvent(QMouseEvent *event) { + if (!IndexIsSeparator(indexAt(event->pos()))) + setCursor(Qt::PointingHandCursor); + else + unsetCursor(); + QListView::mouseMoveEvent(event); +} + +#include "moc_sidebar.cpp"
--- a/src/string_utils.cpp Wed Aug 16 00:49:17 2023 -0400 +++ b/src/string_utils.cpp Thu Aug 24 23:11:38 2023 -0400 @@ -1,7 +1,8 @@ /** * string_utils.cpp: Useful functions for manipulating strings * - * Every function in here *should* have a working wstring equivalent. + * Every function in here *should* have a working wstring equivalent, + * although we don't use wstrings anymore... **/ #include <vector> #include <string> @@ -9,10 +10,10 @@ #include <locale> #include "string_utils.h" -/* It's actually pretty insane how the standard library still doesn't - have a function for this. Look at how simple this is. */ std::string StringUtils::Implode(const std::vector<std::string>& vector, const std::string& delimiter) { + if (vector.size() < 1) + return "-"; std::string out = ""; for (unsigned long long i = 0; i < vector.size(); i++) { out.append(vector.at(i));
--- a/src/ui_utils.cpp Wed Aug 16 00:49:17 2023 -0400 +++ b/src/ui_utils.cpp Thu Aug 24 23:11:38 2023 -0400 @@ -1,5 +1,11 @@ +#include <QPixmap> +#include <QLabel> +#include <QFrame> +#include <QVBoxLayout> +#include <QTextBlock> #include "window.h" #include "ui_utils.h" +#include "session.h" #ifdef MACOSX #include "sys/osx/dark_theme.h" #else @@ -37,75 +43,130 @@ return (session.config.theme == DARK); } -void UiUtils::CreateTextHeader(QWidget* parent, QString title, QPoint point, QSize size) { +void UiUtils::CreateTextHeader(QWidget* parent, QString title) { QLabel* static_text_title = new QLabel(title, parent); static_text_title->setTextFormat(Qt::PlainText); - static_text_title->setStyleSheet("font-weight: bold"); - static_text_title->move(point.x(), point.y()); - static_text_title->resize(size.width(), 16); + + QFont font = static_text_title->font(); + font.setWeight(QFont::Bold); + static_text_title->setFont(font); + + static_text_title->setFixedHeight(16); + parent->layout()->addWidget(static_text_title); QFrame* static_text_line = new QFrame(parent); static_text_line->setFrameShape(QFrame::HLine); static_text_line->setFrameShadow(QFrame::Sunken); - static_text_line->resize(size.width(), 2); - static_text_line->move(point.x(), point.y()+18); + static_text_line->setFixedHeight(2); + parent->layout()->addWidget(static_text_line); } -QPlainTextEdit* UiUtils::CreateTextParagraph(QWidget* parent, QString title, QString data, QPoint point, QSize size) { - CreateTextHeader(parent, title, point, size); +QPlainTextEdit* UiUtils::CreateTextParagraph(QWidget* parent, QString title, QString data) { + QWidget* paragraph_master = new QWidget(parent); + paragraph_master->setLayout(new QVBoxLayout); - QPlainTextEdit* paragraph = new QPlainTextEdit(data, parent); + CreateTextHeader(paragraph_master, title); + + Paragraph* paragraph = new Paragraph(data, paragraph_master); paragraph->setTextInteractionFlags(Qt::NoTextInteraction); paragraph->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents); paragraph->setWordWrapMode(QTextOption::NoWrap); - paragraph->setFrameShape(QFrame::NoFrame); - paragraph->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - paragraph->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - paragraph->setStyleSheet("background: transparent;"); - paragraph->move(point.x()+12, point.y()+32); - paragraph->resize(size.width()-12, size.height()-32); + paragraph->setContentsMargins(12, 0, 0, 0); + + paragraph_master->layout()->addWidget(paragraph); + paragraph_master->layout()->setSpacing(0); + paragraph_master->layout()->setMargin(0); + paragraph_master->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + return paragraph; } -QPlainTextEdit* UiUtils::CreateTextParagraphWithLabels(QWidget* parent, QString title, QString label, QString data, QPoint point, QSize size) { - CreateTextHeader(parent, title, point, size); +QPlainTextEdit* UiUtils::CreateTextParagraphWithLabels(QWidget* parent, QString title, QString label, QString data) { + QWidget* paragraph_master = new QWidget(parent); + paragraph_master->setLayout(new QVBoxLayout); + + CreateTextHeader(paragraph_master, title); - QPlainTextEdit* label_t = new QPlainTextEdit(label, parent); + QWidget* paragraph_and_label = new QWidget(paragraph_master); + paragraph_and_label->setLayout(new QHBoxLayout); + paragraph_and_label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + + Paragraph* label_t = new Paragraph(label, paragraph_and_label); label_t->setTextInteractionFlags(Qt::NoTextInteraction); label_t->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents); label_t->setWordWrapMode(QTextOption::NoWrap); - label_t->setFrameShape(QFrame::NoFrame); - label_t->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - label_t->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - label_t->setStyleSheet("background: transparent;"); - label_t->move(point.x()+12, point.y()+32); - label_t->resize(90, size.height()-32); + label_t->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + label_t->setFixedWidth(123); - QPlainTextEdit* paragraph = new QPlainTextEdit(data, parent); + Paragraph* paragraph = new Paragraph(data, paragraph_and_label); paragraph->setTextInteractionFlags(Qt::NoTextInteraction); paragraph->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents); paragraph->setWordWrapMode(QTextOption::NoWrap); - paragraph->setFrameShape(QFrame::NoFrame); - paragraph->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - paragraph->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - paragraph->setStyleSheet("background: transparent;"); - paragraph->move(point.x()+102, point.y()+32); - paragraph->resize(size.width()-102, size.height()-32); + + ((QBoxLayout*)paragraph_and_label->layout())->addWidget(label_t, 0, Qt::AlignTop); + ((QBoxLayout*)paragraph_and_label->layout())->addWidget(paragraph, 0, Qt::AlignTop); + + paragraph_and_label->setContentsMargins(12, 0, 0, 0); + + paragraph_master->layout()->addWidget(paragraph_and_label); + paragraph_master->layout()->setSpacing(0); + paragraph_master->layout()->setMargin(0); + paragraph_master->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + return paragraph; } /* As far as I can tell, this is identical to the way Taiga implements it. Kind of cool, I didn't even look into the source code for it :p */ -QPlainTextEdit* UiUtils::CreateSelectableTextParagraph(QWidget* parent, QString title, QString data, QPoint point, QSize size) { - CreateTextHeader(parent, title, point, size); +QPlainTextEdit* UiUtils::CreateSelectableTextParagraph(QWidget* parent, QString title, QString data) { + QWidget* paragraph_master = new QWidget(parent); + paragraph_master->setLayout(new QVBoxLayout); + + CreateTextHeader(paragraph_master, title); + + QWidget* paragraph_widget = new QWidget(paragraph_master); + paragraph_widget->setLayout(new QHBoxLayout); + paragraph_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - QPlainTextEdit* text_edit = new QPlainTextEdit(data, parent); - text_edit->setReadOnly(true); - text_edit->setFrameShape(QFrame::NoFrame); - text_edit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - text_edit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - text_edit->setStyleSheet("background: transparent;"); - text_edit->move(point.x()+12, point.y()+32); - text_edit->resize(size.width()-12, size.height()-32); + Paragraph* text_edit = new Paragraph(data, paragraph_widget); + + paragraph_widget->layout()->addWidget(text_edit); + + paragraph_widget->setContentsMargins(12, 0, 0, 0); + + paragraph_master->layout()->addWidget(paragraph_widget); + paragraph_master->layout()->setSpacing(0); + paragraph_master->layout()->setMargin(0); + + paragraph_master->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + return text_edit; } + +void UiUtils::SetPlainTextEditData(QPlainTextEdit* text_edit, QString data) { + QTextDocument* document = new QTextDocument(text_edit); + document->setDocumentLayout(new QPlainTextDocumentLayout(document)); + document->setPlainText(data); + text_edit->setDocument(document); +} + +Paragraph::Paragraph(QString text, QWidget* parent) + : QPlainTextEdit(text, parent) { + setReadOnly(true); + setTextInteractionFlags(Qt::TextBrowserInteraction); + setFrameShape(QFrame::NoFrame); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setStyleSheet("background: transparent;"); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); +} + +QSize Paragraph::minimumSizeHint() const { + QTextDocument* doc = document(); + long h = (long)(blockBoundingGeometry(doc->findBlockByNumber(doc->blockCount() - 1)).bottom() + (2 * doc->documentMargin())); + return QSize(QPlainTextEdit::sizeHint().width(), (long)h); +} + +QSize Paragraph::sizeHint() const { + return minimumSizeHint(); +}