Mercurial > minori
changeset 273:f31305b9f60a
*: various code safety changes
this also makes the code build on Qt 5.7. I can't test it though
because I don't have it working... FAIL!
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Thu, 18 Apr 2024 16:53:17 -0400 |
parents | 5437009cb10e |
children | f6a756c19bfb |
files | configure.ac include/core/strings.h include/gui/pages/anime_list.h include/gui/pages/search.h include/gui/widgets/anime_button.h include/gui/widgets/graph.h include/gui/widgets/poster.h include/gui/window.h scripts/osx/deploy_build.sh src/core/strings.cc src/gui/pages/anime_list.cc src/gui/pages/search.cc src/gui/pages/torrents.cc src/gui/widgets/anime_button.cc src/gui/widgets/elided_label.cc src/gui/widgets/poster.cc src/gui/window.cc src/main.cc src/sys/osx/dark_theme.cc |
diffstat | 19 files changed, 348 insertions(+), 216 deletions(-) [+] |
line wrap: on
line diff
--- a/configure.ac Thu Apr 18 16:51:35 2024 -0400 +++ b/configure.ac Thu Apr 18 16:53:17 2024 -0400 @@ -13,6 +13,7 @@ AC_PROG_CC dnl Do we have a C++17 compiler +: ${CXXFLAGS=""} AC_PROG_CXX AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory])
--- a/include/core/strings.h Thu Apr 18 16:51:35 2024 -0400 +++ b/include/core/strings.h Thu Apr 18 16:53:17 2024 -0400 @@ -67,6 +67,7 @@ std::string ToUtf8String(bool b); uint64_t HumanReadableSizeToBytes(const std::string& str); +std::string BytesToHumanReadableSize(uint64_t bytes, int precision = 2); void RemoveLeadingChars(std::string& s, const char c); void RemoveTrailingChars(std::string& s, const char c);
--- a/include/gui/pages/anime_list.h Thu Apr 18 16:51:35 2024 -0400 +++ b/include/gui/pages/anime_list.h Thu Apr 18 16:53:17 2024 -0400 @@ -6,10 +6,27 @@ #include <QSortFilterProxyModel> #include <QStyledItemDelegate> #include <QWidget> +#include <QThread> #include <vector> +#include <queue> class QTreeView; class QTabBar; +class AnimeListPage; + +class AnimeListPageUpdateEntryThread final : public QThread { +public: + AnimeListPageUpdateEntryThread(AnimeListPage* parent); + + void AddToQueue(int id); + +protected: + void run() override; + +private: + AnimeListPage* page_ = nullptr; + std::queue<int> queue_; +}; class AnimeListPageSortFilter final : public QSortFilterProxyModel { Q_OBJECT @@ -87,6 +104,8 @@ QTabBar* tab_bar; QTreeView* tree_view; QRect panelRect; + + AnimeListPageUpdateEntryThread update_entry_thread_; std::array<AnimeListPageSortFilter*, 5> sort_models; };
--- a/include/gui/pages/search.h Thu Apr 18 16:51:35 2024 -0400 +++ b/include/gui/pages/search.h Thu Apr 18 16:53:17 2024 -0400 @@ -7,9 +7,27 @@ #include <QFrame> #include <QItemSelection> #include <QSortFilterProxyModel> +#include <QThread> class QTreeView; +class SearchPageSearchThread : public QThread { + Q_OBJECT + +public: + SearchPageSearchThread(QObject* parent = nullptr); + void SetSearch(const std::string& search); + +protected: + void run() override; + +private: + std::string search_; + +signals: + void GotResults(const std::vector<int>& search); +}; + class SearchPageListSortFilter final : public QSortFilterProxyModel { Q_OBJECT @@ -62,5 +80,7 @@ SearchPageListModel* model = nullptr; SearchPageListSortFilter* sort_model = nullptr; QTreeView* treeview = nullptr; + + SearchPageSearchThread thread_; }; #endif // MINORI_GUI_PAGES_SEARCH_H_
--- a/include/gui/widgets/anime_button.h Thu Apr 18 16:51:35 2024 -0400 +++ b/include/gui/widgets/anime_button.h Thu Apr 18 16:53:17 2024 -0400 @@ -2,16 +2,12 @@ #define MINORI_GUI_WIDGETS_ANIME_BUTTON_H_ #include <QFrame> - -class QWidget; -class QLabel; +#include <QWidget> +#include <QLabel> -class Poster; -class ElidedLabel; - -namespace TextWidgets { -class LabelledParagraph; -} +#include "gui/widgets/poster.h" +#include "gui/widgets/elided_label.h" +#include "gui/widgets/text.h" namespace Anime { class Anime; @@ -24,10 +20,10 @@ void SetAnime(const Anime::Anime& anime); protected: - Poster* _poster = nullptr; - QLabel* _title = nullptr; - TextWidgets::LabelledParagraph* _info = nullptr; - ElidedLabel* _synopsis = nullptr; + Poster _poster; + QLabel _title; + TextWidgets::LabelledParagraph _info; + ElidedLabel _synopsis; }; -#endif // MINORI_GUI_WIDGETS_ANIME_BUTTON_H_ \ No newline at end of file +#endif // MINORI_GUI_WIDGETS_ANIME_BUTTON_H_
--- a/include/gui/widgets/graph.h Thu Apr 18 16:51:35 2024 -0400 +++ b/include/gui/widgets/graph.h Thu Apr 18 16:53:17 2024 -0400 @@ -56,7 +56,7 @@ QFontMetrics metric(font()); for (const auto& item : map) { - unsigned long width = metric.horizontalAdvance(QString::number(item.first), -1); + unsigned long width = metric.boundingRect(QString::number(item.first)).width(); if (width > ret) ret = width; } @@ -69,7 +69,7 @@ QFontMetrics metric(font()); for (const auto& item : map) { - unsigned long width = metric.horizontalAdvance(QString::number(item.second), -1); + unsigned long width = metric.boundingRect(QString::number(item.second)).width(); if (width > ret) ret = width; }
--- a/include/gui/widgets/poster.h Thu Apr 18 16:51:35 2024 -0400 +++ b/include/gui/widgets/poster.h Thu Apr 18 16:53:17 2024 -0400 @@ -3,8 +3,9 @@ #include <QFrame> #include <QImage> +#include "gui/widgets/clickable_label.h" + class QWidget; -class ClickableLabel; namespace Anime { class Anime; } @@ -24,10 +25,10 @@ void RenderToLabel(); private: - QImage img; - QString service_url; - ClickableLabel* label; - bool clickable = true; + QImage img_; + QString service_url_; + ClickableLabel label_; + bool clickable_ = true; }; -#endif // MINORI_GUI_WIDGETS_POSTER_H_ \ No newline at end of file +#endif // MINORI_GUI_WIDGETS_POSTER_H_
--- a/include/gui/window.h Thu Apr 18 16:51:35 2024 -0400 +++ b/include/gui/window.h Thu Apr 18 16:53:17 2024 -0400 @@ -11,25 +11,43 @@ #include <QCloseEvent> #include <QStackedWidget> #include <QThread> +#include <QTimer> #include <QWidget> class QMenu; +class AnimeListPage; Q_DECLARE_METATYPE(std::vector<std::string>); -class PlayingThread : public QThread { +class MainWindowPlayingThread final : public QThread { Q_OBJECT public: - PlayingThread(QObject* object = nullptr) : QThread(object) {} + MainWindowPlayingThread(QObject* object = nullptr) : QThread(object) {} -private: +protected: void run() override; signals: void Done(const std::vector<std::string>& files); }; +class MainWindowAsyncSynchronizeThread final : public QThread { + Q_OBJECT + +public: + MainWindowAsyncSynchronizeThread(QAction* action, AnimeListPage* page, QObject* object = nullptr); + void SetAction(QAction* action); + void SetPage(AnimeListPage* page); + +protected: + void run() override; + +private: + QAction* action_ = nullptr; + AnimeListPage* page_ = nullptr; +}; + class MainWindow final : public QMainWindow { Q_OBJECT @@ -58,11 +76,14 @@ void closeEvent(QCloseEvent* event) override; private: - std::unique_ptr<QWidget> main_widget = nullptr; - std::unique_ptr<QStackedWidget> stack = nullptr; - std::unique_ptr<SideBar> sidebar = nullptr; + QWidget main_widget_; + QStackedWidget stack_; + SideBar sidebar_; - std::unique_ptr<PlayingThread> thread = nullptr; + MainWindowPlayingThread playing_thread_; + QTimer playing_thread_timer_; + + MainWindowAsyncSynchronizeThread async_synchronize_thread_; QMenu* folder_menu = nullptr; };
--- a/scripts/osx/deploy_build.sh Thu Apr 18 16:51:35 2024 -0400 +++ b/scripts/osx/deploy_build.sh Thu Apr 18 16:53:17 2024 -0400 @@ -4,6 +4,11 @@ # run this in your build dir to get a usable app bundle SCRIPT_DIR=$(dirname -- "$0") +FRAMEWORK_MODE=false +if test "x$1x" = "x--frameworksx"; then + echo "framework mode enabled" + FRAMEWORK_MODE=true +fi BUNDLE_NAME="Minori" cp -r "$SCRIPT_DIR/../../rc/sys/osx/$BUNDLE_NAME.app" . @@ -12,9 +17,15 @@ cp ".libs/minori" "$BUNDLE_NAME.app/Contents/MacOS/minori" mkdir -p "$BUNDLE_NAME.app/Contents/Frameworks" -for i in animia pugixml anitomy; do +for i in animone pugixml anitomy; do cp "dep/$i/.libs/lib$i.0.dylib" "$BUNDLE_NAME.app/Contents/Frameworks" install_name_tool -change "/usr/local/lib/lib$i.0.dylib" "@executable_path/../Frameworks/lib$i.0.dylib" "$BUNDLE_NAME.app/Contents/MacOS/minori" done macdeployqt "$BUNDLE_NAME.app" +if $FRAMEWORK_MODE; then + for i in QtCore QtGui QtWidgets; do + install_name_tool -id @executable_path/../Frameworks/$i.framework/Versions/5/$i $BUNDLE_NAME.app/Contents/Frameworks/$i.framework/Versions/5/$i + install_name_tool -change @rpath/$i.framework/Versions/5/$i @executable_path/../Frameworks/$i.framework/Versions/5/$i $BUNDLE_NAME.app/Contents/MacOS/minori + done +fi
--- a/src/core/strings.cc Thu Apr 18 16:51:35 2024 -0400 +++ b/src/core/strings.cc Thu Apr 18 16:53:17 2024 -0400 @@ -13,6 +13,7 @@ #include <cctype> #include <codecvt> #include <iostream> +#include <iomanip> #include <locale> #include <string> #include <unordered_map> @@ -243,11 +244,16 @@ /* util funcs */ uint64_t HumanReadableSizeToBytes(const std::string& str) { static const std::unordered_map<std::string, uint64_t> bytes_map = { - {"KB", 1ull << 10}, - {"MB", 1ull << 20}, - {"GB", 1ull << 30}, - {"TB", 1ull << 40}, - {"PB", 1ull << 50} /* surely we won't need more than this */ + {"KB", 1000ull}, + {"MB", 1000000ull}, + {"GB", 1000000000ull}, + {"TB", 1000000000000ull}, + {"PB", 1000000000000000ull}, + {"KiB", 1ull << 10}, + {"MiB", 1ull << 20}, + {"GiB", 1ull << 30}, + {"TiB", 1ull << 40}, + {"PiB", 1ull << 50} /* surely we won't need more than this */ }; for (const auto& suffix : bytes_map) { @@ -264,6 +270,35 @@ return ToInt(str, 0); } +std::string BytesToHumanReadableSize(uint64_t bytes, int precision) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + /* QLocale in Qt >= 5.10.0 has a function for this */ + return Strings::ToUtf8String(session.config.locale.GetLocale().formattedDataSize(bytes), precision); +#else + static const std::unordered_map<uint64_t, std::string> map = { + {1ull << 10, "KiB"}, + {1ull << 20, "MiB"}, + {1ull << 30, "GiB"}, + {1ull << 40, "TiB"}, + {1ull << 50, "PiB"} + }; + + for (const auto& suffix : map) { + if (bytes / suffix.first < 1) + continue; + + std::stringstream ss; + ss << std::setprecision(precision) + << (static_cast<double>(bytes) / suffix.first) << " " + << suffix.second; + return ss.str(); + } + + /* better luck next time */ + return "0 bytes"; +#endif +} + void RemoveLeadingChars(std::string& s, const char c) { s.erase(0, std::min(s.find_first_not_of(c), s.size() - 1)); }
--- a/src/gui/pages/anime_list.cc Thu Apr 18 16:51:35 2024 -0400 +++ b/src/gui/pages/anime_list.cc Thu Apr 18 16:53:17 2024 -0400 @@ -27,11 +27,32 @@ #include <QShortcut> #include <QStylePainter> #include <QStyledItemDelegate> -#include <QThread> +#include <QThreadPool> +#include <QRunnable> #include <QTreeView> #include <set> +AnimeListPageUpdateEntryThread::AnimeListPageUpdateEntryThread(AnimeListPage* parent) : QThread(parent) { + page_ = parent; +} + +void AnimeListPageUpdateEntryThread::AddToQueue(int id) { + if (isRunning()) + return; /* don't let us fuck ourselves */ + + queue_.push(id); +} + +/* processes the queue... */ +void AnimeListPageUpdateEntryThread::run() { + while (!queue_.empty() && !isInterruptionRequested()) { + Services::UpdateAnimeEntry(queue_.front()); + queue_.pop(); + } + page_->Refresh(); +} + AnimeListPageSortFilter::AnimeListPageSortFilter(QObject* parent) : QSortFilterProxyModel(parent) { } @@ -111,7 +132,7 @@ case AL_TITLE: return Strings::ToQString(list[index.row()].GetUserPreferredTitle()); case AL_PROGRESS: return QString::number(list[index.row()].GetUserProgress()) + "/" + - QString::number(list[index.row()].GetEpisodes()); + QString::number(list[index.row()].GetEpisodes()); case AL_EPISODES: return list[index.row()].GetEpisodes(); case AL_SCORE: return Strings::ToQString(list[index.row()].GetUserPresentableScore()); case AL_TYPE: return Strings::ToQString(Translate::ToString(list[index.row()].GetFormat())); @@ -120,7 +141,7 @@ if (!year) return "Unknown Unknown"; return Strings::ToQString(Translate::ToLocalString(list[index.row()].GetSeason()) + " " + - Strings::ToUtf8String(year.value())); + Strings::ToUtf8String(year.value())); } case AL_AVG_SCORE: return QString::number(list[index.row()].GetAudienceScore()) + "%"; case AL_STARTED: return list[index.row()].GetUserDateStarted().GetAsQDate(); @@ -219,12 +240,14 @@ } void AnimeListPage::UpdateAnime(int id) { - QThread* thread = QThread::create([this, id] { Services::UpdateAnimeEntry(id); }); + /* this ought to just add to the thread's buffer. */ + if (update_entry_thread_.isRunning()) { + update_entry_thread_.requestInterruption(); + update_entry_thread_.wait(); + } - connect(thread, &QThread::finished, this, &AnimeListPage::Refresh); - connect(thread, &QThread::finished, thread, &QThread::deleteLater); - - thread->start(); + update_entry_thread_.AddToQueue(id); + update_entry_thread_.start(); } void AnimeListPage::RemoveAnime(int id) { @@ -243,7 +266,7 @@ if (i == AnimeListPageModel::AL_TITLE) continue; const auto column_name = - sort_models[tab_bar->currentIndex()]->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); + 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)) @@ -277,9 +300,9 @@ menu->setToolTipsVisible(true); AnimeListPageModel* source_model = - reinterpret_cast<AnimeListPageModel*>(sort_models[tab_bar->currentIndex()]->sourceModel()); + reinterpret_cast<AnimeListPageModel*>(sort_models[tab_bar->currentIndex()]->sourceModel()); const QItemSelection selection = - sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); + sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); std::set<Anime::Anime*> animes; for (const auto& index : selection.indexes()) { @@ -293,7 +316,7 @@ 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); + *anime, [this, anime] { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MAIN_INFO, this); dialog->show(); dialog->raise(); @@ -304,7 +327,7 @@ menu->addAction(tr("Edit"), [this, animes] { for (auto& anime : animes) { InformationDialog* dialog = new InformationDialog( - *anime, [this, anime] { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MY_LIST, this); + *anime, [this, anime] { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MY_LIST, this); dialog->show(); dialog->raise(); @@ -322,19 +345,19 @@ void AnimeListPage::ItemDoubleClicked() { /* throw out any other garbage */ const QItemSelection selection = - sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); + sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); if (!selection.indexes().first().isValid()) { return; } AnimeListPageModel* source_model = - reinterpret_cast<AnimeListPageModel*>(sort_models[tab_bar->currentIndex()]->sourceModel()); + reinterpret_cast<AnimeListPageModel*>(sort_models[tab_bar->currentIndex()]->sourceModel()); const QModelIndex index = source_model->index(selection.indexes().first().row()); Anime::Anime* anime = source_model->GetAnimeFromIndex(index); InformationDialog* dialog = new InformationDialog( - *anime, [this, anime] { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MAIN_INFO, this); + *anime, [this, anime] { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MAIN_INFO, this); dialog->show(); dialog->raise(); @@ -349,7 +372,7 @@ void AnimeListPage::RefreshTabs() { for (unsigned int i = 0; i < sort_models.size(); i++) tab_bar->setTabText(i, Strings::ToQString(Translate::ToString(Anime::ListStatuses[i])) + " (" + - QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); + QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); } void AnimeListPage::Refresh() { @@ -424,7 +447,7 @@ /* --------- QTabWidget replication end ---------- */ -AnimeListPage::AnimeListPage(QWidget* parent) : QWidget(parent) { +AnimeListPage::AnimeListPage(QWidget* parent) : QWidget(parent), update_entry_thread_(this) { /* Tab bar */ tab_bar = new QTabBar(this); tab_bar->setExpanding(false); @@ -445,7 +468,7 @@ for (unsigned int i = 0; i < sort_models.size(); i++) { tab_bar->addTab(Strings::ToQString(Translate::ToString(Anime::ListStatuses[i])) + " (" + - QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); + QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); sort_models[i] = new AnimeListPageSortFilter(tree_view); sort_models[i]->setSourceModel(new AnimeListPageModel(this, Anime::ListStatuses[i])); sort_models[i]->setSortRole(Qt::UserRole); @@ -476,10 +499,10 @@ /* Enter & return keys */ connect(new QShortcut(Qt::Key_Return, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this, - &AnimeListPage::ItemDoubleClicked); + &AnimeListPage::ItemDoubleClicked); connect(new QShortcut(Qt::Key_Enter, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this, - &AnimeListPage::ItemDoubleClicked); + &AnimeListPage::ItemDoubleClicked); tree_view->header()->setStretchLastSection(false); tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu);
--- a/src/gui/pages/search.cc Thu Apr 18 16:51:35 2024 -0400 +++ b/src/gui/pages/search.cc Thu Apr 18 16:53:17 2024 -0400 @@ -26,6 +26,17 @@ #include "anitomy/anitomy.h" #include "pugixml.hpp" +SearchPageSearchThread::SearchPageSearchThread(QObject* parent) : QThread(parent) { +} + +void SearchPageSearchThread::SetSearch(const std::string& search) { + search_ = search; +} + +void SearchPageSearchThread::run() { + emit GotResults(Services::Search(search_)); +} + SearchPageListSortFilter::SearchPageListSortFilter(QObject* parent) : QSortFilterProxyModel(parent) { } @@ -139,7 +150,7 @@ const QString d = data(index, Qt::DisplayRole).toString(); const QFontMetrics metric = QFontMetrics(QFont()); - return QSize(std::max(metric.horizontalAdvance(d), 100), metric.height()); + return QSize(std::max(metric.boundingRect(d).width(), 100), metric.height()); } } break; @@ -282,21 +293,15 @@ QLineEdit* line_edit = new QLineEdit("", toolbar); connect(line_edit, &QLineEdit::returnPressed, this, [this, line_edit] { /* static thread here. */ - static QThread* thread = nullptr; + if (thread_.isRunning()) + thread_.exit(1); /* fail */ - if (thread) - return; + thread_.SetSearch(Strings::ToUtf8String(line_edit->text())); - 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(); + thread_.start(); + }); + connect(&thread_, &SearchPageSearchThread::GotResults, this, [this](const std::vector<int>& search) { + model->ParseSearch(search); }); toolbar->addWidget(line_edit); }
--- a/src/gui/pages/torrents.cc Thu Apr 18 16:51:35 2024 -0400 +++ b/src/gui/pages/torrents.cc Thu Apr 18 16:53:17 2024 -0400 @@ -14,6 +14,7 @@ #include <QToolBar> #include <QTreeView> #include <QVBoxLayout> +#include <QtGlobal> #include <algorithm> #include <fstream> @@ -256,7 +257,7 @@ case TL_TITLE: return Strings::ToQString(item.GetTitle()); case TL_EPISODE: return Strings::ToQString(item.GetEpisode()); case TL_GROUP: return Strings::ToQString(item.GetGroup()); - case TL_SIZE: return session.config.locale.GetLocale().formattedDataSize(item.GetSize()); + case TL_SIZE: return Strings::ToQString(Strings::BytesToHumanReadableSize(item.GetSize())); case TL_RESOLUTION: return Strings::ToQString(item.GetResolution()); case TL_SEEDERS: return item.GetSeeders(); case TL_LEECHERS: return item.GetLeechers(); @@ -284,7 +285,7 @@ const QString d = data(index, Qt::DisplayRole).toString(); const QFontMetrics metric = QFontMetrics(QFont()); - return QSize(std::max(metric.horizontalAdvance(d), 100), metric.height()); + return QSize(std::max(metric.boundingRect(d).width(), 100), metric.height()); } } break;
--- a/src/gui/widgets/anime_button.cc Thu Apr 18 16:51:35 2024 -0400 +++ b/src/gui/widgets/anime_button.cc Thu Apr 18 16:53:17 2024 -0400 @@ -26,15 +26,17 @@ *|_________| Synopsis * \***********************************/ -AnimeButton::AnimeButton(QWidget* parent) : QFrame(parent) { +AnimeButton::AnimeButton(QWidget* parent) + : QFrame(parent) + , _info(tr("Aired:\nEpisodes:\nGenres:\nProducers:\nScore:\nPopularity:"), "\n\n\n\n\n", nullptr) + , _synopsis("", nullptr) { setFrameShadow(QFrame::Plain); setFrameShape(QFrame::Box); QHBoxLayout* ly = new QHBoxLayout(this); - _poster = new Poster(this); - _poster->setFixedSize(120, 170); - _poster->SetClickable(false); - ly->addWidget(_poster, 0, Qt::AlignTop); + _poster.setFixedSize(120, 170); + _poster.SetClickable(false); + ly->addWidget(&_poster, 0, Qt::AlignTop); { QWidget* misc_section = new QWidget(this); @@ -43,39 +45,37 @@ QVBoxLayout* misc_layout = new QVBoxLayout(misc_section); misc_layout->setContentsMargins(0, 0, 0, 0); - _title = new QLabel("", misc_section); - _title->setAutoFillBackground(true); - _title->setContentsMargins(4, 4, 4, 4); - _title->setStyleSheet("background-color: rgba(0, 245, 25, 50);"); + _title.setAutoFillBackground(true); + _title.setContentsMargins(4, 4, 4, 4); + _title.setStyleSheet("background-color: rgba(0, 245, 25, 50);"); { - QFont fnt(_title->font()); + QFont fnt(_title.font()); fnt.setWeight(QFont::Bold); - _title->setFont(fnt); + _title.setFont(fnt); } - misc_layout->addWidget(_title); + misc_layout->addWidget(&_title); - _info = new TextWidgets::LabelledParagraph(tr("Aired:\nEpisodes:\nGenres:\nProducers:\nScore:\nPopularity:"), - "\n\n\n\n\n", misc_section); { - QFont fnt(_info->GetLabels()->font()); + QFont fnt(_info.GetLabels()->font()); fnt.setWeight(QFont::Bold); - _info->GetLabels()->setFont(fnt); + _info.GetLabels()->setFont(fnt); } - _info->setContentsMargins(4, 0, 4, 0); - _info->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); - misc_layout->addWidget(_info); + + _info.setContentsMargins(4, 0, 4, 0); + _info.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); + misc_layout->addWidget(&_info); - QWidget* dummy = new QWidget(misc_section); - dummy->setContentsMargins(4, 0, 4, 0); - QVBoxLayout* dummy_layout = new QVBoxLayout(dummy); - dummy_layout->setSpacing(0); - // dummy_layout->setContentsMargins(0, 0, 0, 0); - dummy_layout->setContentsMargins(0, 0, 0, 0); + { + QWidget* dummy = new QWidget(misc_section); + dummy->setContentsMargins(4, 0, 4, 0); + QVBoxLayout* dummy_layout = new QVBoxLayout(dummy); + dummy_layout->setSpacing(0); + dummy_layout->setContentsMargins(0, 0, 0, 0); - _synopsis = new ElidedLabel("", dummy); - _synopsis->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - dummy_layout->addWidget(_synopsis); - misc_layout->addWidget(dummy); + _synopsis.setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + dummy_layout->addWidget(&_synopsis); + misc_layout->addWidget(dummy); + } ly->addWidget(misc_section, 0, Qt::AlignTop); } @@ -86,20 +86,16 @@ } void AnimeButton::SetAnime(const Anime::Anime& anime) { - _poster->SetAnime(anime); - _title->setText(Strings::ToQString(anime.GetUserPreferredTitle())); + _poster.SetAnime(anime); + _title.setText(Strings::ToQString(anime.GetUserPreferredTitle())); { const QLocale& locale = session.config.locale.GetLocale(); - _info->GetParagraph()->SetText(locale.toString(anime.GetAirDate().GetAsQDate(), "dd MMM yyyy") + "\n" + - QString::number(anime.GetEpisodes()) + "\n" + - Strings::ToQString(Strings::Implode(anime.GetGenres(), ", ")) + "\n" + "...\n" + - QString::number(anime.GetAudienceScore()) + "%\n" + "..."); + _info.GetParagraph()->SetText(locale.toString(anime.GetAirDate().GetAsQDate(), "dd MMM yyyy") + "\n" + + QString::number(anime.GetEpisodes()) + "\n" + + Strings::ToQString(Strings::Implode(anime.GetGenres(), ", ")) + "\n" + "...\n" + + QString::number(anime.GetAudienceScore()) + "%\n" + "..."); } - { - QString synopsis = Strings::ToQString(anime.GetSynopsis()); - QFontMetrics metrics(_synopsis->font()); - _synopsis->SetText(Strings::ToQString(anime.GetSynopsis())); - } -} \ No newline at end of file + _synopsis.SetText(Strings::ToQString(anime.GetSynopsis())); +}
--- a/src/gui/widgets/elided_label.cc Thu Apr 18 16:51:35 2024 -0400 +++ b/src/gui/widgets/elided_label.cc Thu Apr 18 16:53:17 2024 -0400 @@ -49,32 +49,30 @@ QFrame::paintEvent(event); QPainter painter(this); - QFontMetrics fontMetrics = painter.fontMetrics(); + QFontMetrics metrics = painter.fontMetrics(); - int line_spacing = fontMetrics.lineSpacing(); + const int line_spacing = metrics.lineSpacing(); int y = 0; - QTextLayout textLayout(content, painter.font()); - textLayout.beginLayout(); + QTextLayout text_layout(content, painter.font()); + text_layout.beginLayout(); for (;;) { - QTextLine line = textLayout.createLine(); - + QTextLine line = text_layout.createLine(); if (!line.isValid()) break; line.setLineWidth(width()); - int nextLineY = y + line_spacing; - if (height() >= nextLineY + line_spacing) { + if (height() >= y + (2 * line_spacing)) { line.draw(&painter, QPoint(0, y)); - y = nextLineY; + y += line_spacing; } else { QString last_line = content.mid(line.textStart()); - QString elided_last_line = fontMetrics.elidedText(last_line, Qt::ElideRight, width()); - painter.drawText(QPoint(0, y + fontMetrics.ascent()), elided_last_line); - line = textLayout.createLine(); + QString elided_last_line = metrics.elidedText(last_line, Qt::ElideRight, width()); + painter.drawText(QPoint(0, y + metrics.ascent()), elided_last_line); + line = text_layout.createLine(); break; } } - textLayout.endLayout(); + text_layout.endLayout(); }
--- a/src/gui/widgets/poster.cc Thu Apr 18 16:51:35 2024 -0400 +++ b/src/gui/widgets/poster.cc Thu Apr 18 16:53:17 2024 -0400 @@ -25,9 +25,8 @@ setFrameShape(QFrame::Box); setFrameShadow(QFrame::Plain); - label = new ClickableLabel(this); - label->setAlignment(Qt::AlignCenter); - layout->addWidget(label); + label_.setAlignment(Qt::AlignCenter); + layout->addWidget(&label_); } Poster::Poster(const Anime::Anime& anime, QWidget* parent) : Poster(parent) { @@ -45,37 +44,37 @@ thread->start(); } - service_url = Strings::ToQString(anime.GetServiceUrl()); + service_url_ = Strings::ToQString(anime.GetServiceUrl()); - if (clickable) { - label->disconnect(); - connect(label, &ClickableLabel::clicked, this, [this] { QDesktopServices::openUrl(service_url); }); + if (clickable_) { + label_.disconnect(); + connect(&label_, &ClickableLabel::clicked, this, [this] { QDesktopServices::openUrl(service_url_); }); } } void Poster::SetClickable(bool enabled) { - clickable = enabled; + clickable_ = enabled; - if (clickable && !service_url.isEmpty()) { + if (clickable_ && !service_url_.isEmpty()) { setCursor(Qt::PointingHandCursor); - label->disconnect(); - connect(label, &ClickableLabel::clicked, this, [this] { QDesktopServices::openUrl(service_url); }); + label_.disconnect(); + connect(&label_, &ClickableLabel::clicked, this, [this] { QDesktopServices::openUrl(service_url_); }); } else { setCursor(Qt::ArrowCursor); - label->disconnect(); + label_.disconnect(); } } void Poster::ImageDownloadFinished(const QByteArray& arr) { - img.loadFromData(arr); + img_.loadFromData(arr); RenderToLabel(); } void Poster::RenderToLabel() { - const QPixmap pixmap = QPixmap::fromImage(img); + const QPixmap pixmap = QPixmap::fromImage(img_); if (pixmap.isNull()) return; - label->setPixmap(pixmap.scaled(label->size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); + label_.setPixmap(pixmap.scaled(label_.size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); } void Poster::resizeEvent(QResizeEvent*) {
--- a/src/gui/window.cc Thu Apr 18 16:51:35 2024 -0400 +++ b/src/gui/window.cc Thu Apr 18 16:53:17 2024 -0400 @@ -48,34 +48,50 @@ # include "sys/win32/dark_theme.h" #endif -void PlayingThread::run() { +void MainWindowPlayingThread::run() { std::vector<std::string> files; Track::Media::GetCurrentlyPlaying(files); emit Done(files); } -MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { +MainWindowAsyncSynchronizeThread::MainWindowAsyncSynchronizeThread(QAction* action, AnimeListPage* page, QObject* parent) : QThread(parent) { + SetAction(action); + SetPage(page); +} + +void MainWindowAsyncSynchronizeThread::SetAction(QAction* action) { + action_ = action; +} + +void MainWindowAsyncSynchronizeThread::SetPage(AnimeListPage* page) { + page_ = page; +} + +void MainWindowAsyncSynchronizeThread::run() { + action_->setEnabled(false); + Services::Synchronize(); + page_->Refresh(); + action_->setEnabled(true); +} + +MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), async_synchronize_thread_(nullptr, nullptr) { setWindowIcon(QIcon(":/icons/favicon.png")); - main_widget.reset(new QWidget(this)); - new QHBoxLayout(main_widget.get()); + sidebar_.setFixedWidth(128); + sidebar_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + + connect(&sidebar_, &SideBar::CurrentItemChanged, &stack_, &QStackedWidget::setCurrentIndex); + + new QHBoxLayout(&main_widget_); AddMainWidgets(); - setCentralWidget(main_widget.get()); + setCentralWidget(&main_widget_); CreateBars(); - NowPlayingPage* page = reinterpret_cast<NowPlayingPage*>(stack->widget(static_cast<int>(Pages::NOW_PLAYING))); - - qRegisterMetaType<std::vector<std::string>>(); + NowPlayingPage* page = reinterpret_cast<NowPlayingPage*>(stack_.widget(static_cast<int>(Pages::NOW_PLAYING))); - /* This thread will be destroyed on - * close of the program OR on the destruction - * of MainWindow - */ - thread.reset(new PlayingThread(this)); - - connect(thread.get(), &PlayingThread::Done, this, [page](const std::vector<std::string>& files) { + connect(&playing_thread_, &MainWindowPlayingThread::Done, this, [page](const std::vector<std::string>& files) { for (const auto& file : files) { anitomy::Anitomy anitomy; anitomy.Parse(Strings::ToWstring(file)); @@ -92,13 +108,11 @@ } }); - QTimer* timer = new QTimer(this); - - connect(timer, &QTimer::timeout, this, [this, page] { - if (!thread.get() || thread->isRunning()) + connect(&playing_thread_timer_, &QTimer::timeout, this, [this] { + if (playing_thread_.isRunning()) return; - thread->start(); + playing_thread_.start(); }); #ifdef MACOSX @@ -106,53 +120,42 @@ return; #endif - timer->start(5000); + playing_thread_timer_.start(5000); } void MainWindow::AddMainWidgets() { int page = static_cast<int>(Pages::ANIME_LIST); - if (sidebar.get()) { - main_widget->layout()->removeWidget(sidebar.get()); - sidebar.reset(); - } + sidebar_.clear(); - if (stack.get()) { - page = stack->currentIndex(); - main_widget->layout()->removeWidget(stack.get()); - } + sidebar_.AddItem(tr("Now Playing"), SideBar::CreateIcon(":/icons/16x16/film.png")); + sidebar_.AddSeparator(); + sidebar_.AddItem(tr("Anime List"), SideBar::CreateIcon(":/icons/16x16/document-list.png")); + sidebar_.AddItem(tr("History"), SideBar::CreateIcon(":/icons/16x16/clock-history-frame.png")); + sidebar_.AddItem(tr("Statistics"), SideBar::CreateIcon(":/icons/16x16/chart.png")); + sidebar_.AddSeparator(); + sidebar_.AddItem(tr("Search"), SideBar::CreateIcon(":/icons/16x16/magnifier.png")); + sidebar_.AddItem(tr("Seasons"), SideBar::CreateIcon(":/icons/16x16/calendar.png")); + sidebar_.AddItem(tr("Torrents"), SideBar::CreateIcon(":/icons/16x16/feed.png")); - sidebar.reset(new SideBar(main_widget.get())); - sidebar->setFixedWidth(128); - sidebar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + while (stack_.count()) + stack_.removeWidget(stack_.widget(0)); - sidebar->AddItem(tr("Now Playing"), SideBar::CreateIcon(":/icons/16x16/film.png")); - sidebar->AddSeparator(); - sidebar->AddItem(tr("Anime List"), SideBar::CreateIcon(":/icons/16x16/document-list.png")); - sidebar->AddItem(tr("History"), SideBar::CreateIcon(":/icons/16x16/clock-history-frame.png")); - sidebar->AddItem(tr("Statistics"), SideBar::CreateIcon(":/icons/16x16/chart.png")); - sidebar->AddSeparator(); - sidebar->AddItem(tr("Search"), SideBar::CreateIcon(":/icons/16x16/magnifier.png")); - sidebar->AddItem(tr("Seasons"), SideBar::CreateIcon(":/icons/16x16/calendar.png")); - sidebar->AddItem(tr("Torrents"), SideBar::CreateIcon(":/icons/16x16/feed.png")); - - stack.reset(new QStackedWidget(main_widget.get())); - - stack->addWidget(new NowPlayingPage(main_widget.get())); + /* can we allocate these on the stack? */ + stack_.addWidget(new NowPlayingPage(&main_widget_)); + /* ---- */ + stack_.addWidget(new AnimeListPage(&main_widget_)); + stack_.addWidget(new HistoryPage(&main_widget_)); + stack_.addWidget(new StatisticsPage(&main_widget_)); /* ---- */ - stack->addWidget(new AnimeListPage(main_widget.get())); - stack->addWidget(new HistoryPage(main_widget.get())); - stack->addWidget(new StatisticsPage(main_widget.get())); - /* ---- */ - stack->addWidget(new SearchPage(main_widget.get())); - stack->addWidget(new SeasonsPage(main_widget.get())); - stack->addWidget(new TorrentsPage(main_widget.get())); + stack_.addWidget(new SearchPage(&main_widget_)); + stack_.addWidget(new SeasonsPage(&main_widget_)); + stack_.addWidget(new TorrentsPage(&main_widget_)); - connect(sidebar.get(), &SideBar::CurrentItemChanged, stack.get(), &QStackedWidget::setCurrentIndex); - sidebar->SetCurrentItem(page); + sidebar_.SetCurrentItem(page); - main_widget->layout()->addWidget(sidebar.get()); - main_widget->layout()->addWidget(stack.get()); + main_widget_.layout()->addWidget(&sidebar_); + main_widget_.layout()->addWidget(&stack_); } void MainWindow::CreateBars() { @@ -201,7 +204,7 @@ sync_action = menu->addAction(tr("Synchronize &list")); connect(sync_action, &QAction::triggered, this, - [this, sync_action] { AsyncSynchronize(sync_action, stack.get()); }); + [this, sync_action] { AsyncSynchronize(sync_action, &stack_); }); sync_action->setIcon(QIcon(":/icons/24x24/arrow-circle-double-135.png")); sync_action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_S)); @@ -290,48 +293,48 @@ { QAction* action = pages_group->addAction(menu->addAction(tr("&Now Playing"))); action->setCheckable(true); - connect(action, &QAction::toggled, this, [this] { sidebar->SetCurrentItem(0); }); + connect(action, &QAction::toggled, this, [this] { sidebar_.SetCurrentItem(0); }); } { QAction* action = pages_group->addAction(menu->addAction(tr("&Anime List"))); action->setCheckable(true); action->setChecked(true); - connect(action, &QAction::toggled, this, [this] { sidebar->SetCurrentItem(1); }); + connect(action, &QAction::toggled, this, [this] { sidebar_.SetCurrentItem(1); }); } { QAction* action = pages_group->addAction(menu->addAction(tr("&History"))); action->setCheckable(true); - connect(action, &QAction::toggled, this, [this] { sidebar->SetCurrentItem(2); }); + connect(action, &QAction::toggled, this, [this] { sidebar_.SetCurrentItem(2); }); } { QAction* action = pages_group->addAction(menu->addAction(tr("&Statistics"))); action->setCheckable(true); - connect(action, &QAction::toggled, this, [this] { sidebar->SetCurrentItem(3); }); + connect(action, &QAction::toggled, this, [this] { sidebar_.SetCurrentItem(3); }); } { QAction* action = pages_group->addAction(menu->addAction(tr("S&earch"))); action->setCheckable(true); - connect(action, &QAction::toggled, this, [this] { sidebar->SetCurrentItem(4); }); + connect(action, &QAction::toggled, this, [this] { sidebar_.SetCurrentItem(4); }); } { QAction* action = pages_group->addAction(menu->addAction(tr("Se&asons"))); action->setCheckable(true); - connect(action, &QAction::toggled, this, [this] { sidebar->SetCurrentItem(5); }); + connect(action, &QAction::toggled, this, [this] { sidebar_.SetCurrentItem(5); }); } { QAction* action = pages_group->addAction(menu->addAction(tr("&Torrents"))); action->setCheckable(true); - connect(action, &QAction::toggled, this, [this] { sidebar->SetCurrentItem(6); }); + connect(action, &QAction::toggled, this, [this] { sidebar_.SetCurrentItem(6); }); } - /* pain in my ass */ - connect(sidebar.get(), &SideBar::CurrentItemChanged, this, [pages_group](int index) { + /* pain in the ass */ + connect(&sidebar_, &SideBar::CurrentItemChanged, this, [pages_group](int index) { QAction* checked = pages_group->checkedAction(); const QList<QAction*>& actions = pages_group->actions(); @@ -495,12 +498,13 @@ } } - QThreadPool::globalInstance()->start([stack, action] { - action->setEnabled(false); - Services::Synchronize(); - reinterpret_cast<AnimeListPage*>(stack->widget(static_cast<int>(Pages::ANIME_LIST)))->Refresh(); - action->setEnabled(true); - }); + /* FIXME: make this use a QThread; this is *very* unsafe */ + AnimeListPage* page = reinterpret_cast<AnimeListPage*>(stack->widget(static_cast<int>(Pages::ANIME_LIST))); + if (!async_synchronize_thread_.isRunning()) { + async_synchronize_thread_.SetAction(action); + async_synchronize_thread_.SetPage(page); + async_synchronize_thread_.start(); + } } void MainWindow::RetranslateUI() {
--- a/src/main.cc Thu Apr 18 16:51:35 2024 -0400 +++ b/src/main.cc Thu Apr 18 16:53:17 2024 -0400 @@ -19,6 +19,9 @@ app.setApplicationDisplayName("Minori"); app.setAttribute(Qt::AA_DontShowIconsInMenus, true); + qRegisterMetaType<std::vector<std::string>>(); /* window.cc */ + qRegisterMetaType<std::vector<int>>(); /* search.cc */ + session.config.Load(); Anime::db.LoadDatabaseFromDisk();
--- a/src/sys/osx/dark_theme.cc Thu Apr 18 16:51:35 2024 -0400 +++ b/src/sys/osx/dark_theme.cc Thu Apr 18 16:53:17 2024 -0400 @@ -5,8 +5,6 @@ #include <CoreFoundation/CoreFoundation.h> -#include <QOperatingSystemVersion> - namespace osx { typedef id (*object_message_send)(id, SEL, ...); @@ -39,7 +37,7 @@ } bool DarkThemeAvailable() { - return (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave); + return (__builtin_available(macOS 10.14, *)); } bool IsInDarkTheme() {