Mercurial > minori
view src/gui/window.cc @ 327:b5d6c27c308f
anime: refactor Anime::SeriesSeason to Season class
ToLocalString has also been altered to take in both season
and year because lots of locales actually treat formatting
seasons differently! most notably is Russian which adds a
suffix at the end to notate seasons(??)
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Thu, 13 Jun 2024 01:49:18 -0400 |
parents | d928ec7b6a0d |
children | 71396ecb6f7e |
line wrap: on
line source
#include "gui/window.h" #include "core/anime_db.h" #include "core/config.h" #include "core/session.h" #include "core/strings.h" #include "gui/dialog/about.h" #include "gui/dialog/settings.h" #include "gui/pages/anime_list.h" #include "gui/pages/history.h" #include "gui/pages/now_playing.h" #include "gui/pages/search.h" #include "gui/pages/seasons.h" #include "gui/pages/statistics.h" #include "gui/pages/torrents.h" #include "gui/theme.h" #include "gui/widgets/sidebar.h" #include "library/library.h" #include "services/services.h" #include "track/media.h" #include "anitomy/anitomy.h" #include <QActionGroup> #include <QApplication> #include <QDebug> #include <QDesktopServices> #include <QFile> #include <QFileDialog> #include <QHBoxLayout> #include <QMainWindow> #include <QMenuBar> #include <QMessageBox> #include <QPlainTextEdit> #include <QStackedWidget> #include <QStatusBar> #include <QTextStream> #include <QThread> #include <QThreadPool> #include <QTimer> #include <QToolBar> #include <QToolButton> #include <iostream> #ifdef MACOSX # include "sys/osx/dark_theme.h" # include "sys/osx/permissions.h" #elif defined(WIN32) # include "sys/win32/dark_theme.h" #endif void MainWindowPlayingThread::run() { std::vector<std::string> files; Track::Media::GetCurrentlyPlaying(files); emit Done(files); } 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) { sidebar_.setFixedWidth(128); sidebar_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); statusBar(); new QHBoxLayout(&main_widget_); CreateBars(); stack_.addWidget(&now_playing_page_); /* ---- */ stack_.addWidget(&anime_list_page_); stack_.addWidget(&history_page_); stack_.addWidget(&statistics_page_); /* ---- */ stack_.addWidget(&search_page_); stack_.addWidget(&seasons_page_); stack_.addWidget(&torrents_page_); AddMainWidgets(); sidebar_.SetCurrentItem(static_cast<int>(Pages::ANIME_LIST)); setCentralWidget(&main_widget_); NowPlayingPage* page = reinterpret_cast<NowPlayingPage*>(stack_.widget(static_cast<int>(Pages::NOW_PLAYING))); 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)); const auto& elements = anitomy.elements(); const std::string title = Strings::ToUtf8String(elements.get(anitomy::kElementAnimeTitle)); int id = Anime::db.LookupAnimeTitle(title); if (id <= 0) continue; page->SetPlaying(Anime::db.items[id], elements); break; } }); connect(&playing_thread_timer_, &QTimer::timeout, this, [this] { if (playing_thread_.isRunning()) return; playing_thread_.start(); }); #ifdef MACOSX if (!osx::AskForPermissions()) return; #endif playing_thread_timer_.start(5000); } void MainWindow::SetStatusMessage(const std::string& message) { statusBar()->showMessage(Strings::ToQString(message), 2000); } /* Does the main part of what Qt's generic "RetranslateUI" function would do */ void MainWindow::AddMainWidgets() { int page = sidebar_.GetCurrentItem(); sidebar_.clear(); 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_.SetCurrentItem(page); main_widget_.layout()->addWidget(&sidebar_); main_widget_.layout()->addWidget(&stack_); } void MainWindow::CreateBars() { QMenuBar* menubar = new QMenuBar(this); QAction* sync_action; { /* File */ QMenu* menu = menubar->addMenu(tr("&File")); { folder_menu = menu->addMenu(tr("&Library folders")); UpdateFolderMenu(); } { menu->addAction(tr("&Scan available episodes"), [] { Library::SearchLibraryFolders(); }); } menu->addSeparator(); // { // QAction* action = menu->addAction(tr("Play &next episode")); // action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_N)); // } // // { // QAction* action = menu->addAction(tr("Play &random episode")); // action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_R)); // } menu->addSeparator(); { QAction* action = menu->addAction(tr("E&xit"), this, &MainWindow::close); action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q)); } } { /* Services */ QMenu* menu = menubar->addMenu(tr("&Services")); { { sync_action = menu->addAction(tr("Synchronize &list")); connect(sync_action, &QAction::triggered, this, [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)); } // menu->addSeparator(); // // { // /* AniList */ // QMenu* submenu = menu->addMenu(tr("&AniList")); // QAction* action = submenu->addAction(tr("Go to my &profile")); // action = submenu->addAction(tr("Go to my &stats")); // } // // { // /* Kitsu */ // QMenu* submenu = menu->addMenu(tr("&Kitsu")); // QAction* action = submenu->addAction(tr("Go to my &feed")); // action = submenu->addAction(tr("Go to my &library")); // action = submenu->addAction(tr("Go to my &profile")); // } // { // QMenu* submenu = menu->addMenu(tr("&MyAnimeList")); // QAction* action = submenu->addAction(tr("Go to my p&anel")); // action = submenu->addAction(tr("Go to my &profile")); // action = submenu->addAction(tr("Go to my &history")); // } } } { /* Tools */ QMenu* menu = menubar->addMenu(tr("&Tools")); // { // /* Export anime list */ // QMenu* submenu = menu->addMenu(tr("&Export anime list")); // // { // /* Markdown export */ // QAction* action = submenu->addAction(tr("Export as &Markdown...")); // } // // { // /* XML export */ // QAction* action = submenu->addAction(tr("Export as MyAnimeList &XML...")); // } // } // menu->addSeparator(); // // { // QAction* action = menu->addAction(tr("Enable anime &recognition")); // action->setCheckable(true); // } // // { // QAction* action = menu->addAction(tr("Enable auto &sharing")); // action->setCheckable(true); // } // // { // QAction* action = menu->addAction(tr("Enable &auto synchronization")); // action->setCheckable(true); // } // // menu->addSeparator(); { QAction* action = menu->addAction(tr("&Settings"), [this] { SettingsDialog dialog(this); dialog.exec(); UpdateFolderMenu(); }); action->setMenuRole(QAction::PreferencesRole); } } { /* View */ QMenu* menu = menubar->addMenu(tr("&View")); { /* Pages... */ QActionGroup* pages_group = new QActionGroup(menu); pages_group->setExclusive(true); { QAction* action = pages_group->addAction(menu->addAction(tr("&Now Playing"))); action->setCheckable(true); 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); }); } { QAction* action = pages_group->addAction(menu->addAction(tr("&History"))); action->setCheckable(true); 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); }); } { QAction* action = pages_group->addAction(menu->addAction(tr("S&earch"))); action->setCheckable(true); 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); }); } { QAction* action = pages_group->addAction(menu->addAction(tr("&Torrents"))); action->setCheckable(true); connect(action, &QAction::toggled, this, [this] { sidebar_.SetCurrentItem(6); }); } /* pain in the ass */ disconnect(&sidebar_, &SideBar::CurrentItemChanged, nullptr, nullptr); connect(&sidebar_, &SideBar::CurrentItemChanged, &stack_, &QStackedWidget::setCurrentIndex); connect(&sidebar_, &SideBar::CurrentItemChanged, this, [pages_group](int index) { QAction* checked = pages_group->checkedAction(); const QList<QAction*>& actions = pages_group->actions(); if (index > actions.size()) return; if (checked) checked->setChecked(false); actions[index]->setChecked(true); }); } menu->addSeparator(); // { // QAction* action = menu->addAction(tr("Show sidebar")); // } } { /* Help */ QMenu* menu = menubar->addMenu(tr("&Help")); { /* About Minori */ menu->addAction(tr("&About Minori"), this, [this] { AboutWindow dialog(this); dialog.exec(); }); } { /* About Qt */ QAction* action = menu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); action->setMenuRole(QAction::AboutQtRole); } } /* QMainWindow will delete the old one for us, * according to the docs */ setMenuBar(menubar); /* Toolbar */ /* remove old toolbar(s) */ /* the empty QString() is a Qt 5 wart... */ for (QToolBar*& t : findChildren<QToolBar*>(QString(), Qt::FindDirectChildrenOnly)) { removeToolBar(t); delete t; } { /* Toolbar */ QToolBar* toolbar = new QToolBar(this); toolbar->addAction(sync_action); toolbar->addSeparator(); { QToolButton* button = new QToolButton(toolbar); { button->setMenu(folder_menu); } button->setIcon(QIcon(":/icons/24x24/folder-open.png")); button->setPopupMode(QToolButton::InstantPopup); toolbar->addWidget(button); } { QToolButton* button = new QToolButton(toolbar); { /* links */ QMenu* menu = new QMenu(button); menu->addAction("Hibari", [] { QDesktopServices::openUrl(QUrl("https://hb.wopian.me/")); }); menu->addAction("MALgraph", [] { QDesktopServices::openUrl(QUrl("https://graph.anime.plus/")); }); menu->addSeparator(); menu->addAction("AniChart", [] { QDesktopServices::openUrl(QUrl("https://anichart.net/airing")); }); menu->addAction("Monthly.moe", [] { QDesktopServices::openUrl(QUrl("https://www.monthly.moe/weekly")); }); menu->addAction("Senpai Anime Charts", [] { QDesktopServices::openUrl(QUrl("https://www.senpai.moe/?mode=calendar")); }); menu->addSeparator(); menu->addAction("Anime Streaming Search Engine", [] { QDesktopServices::openUrl(QUrl("https://because.moe/")); }); menu->addAction("The Fansub Database", [] { QDesktopServices::openUrl(QUrl("https://fansubdb.com")); }); button->setMenu(menu); } button->setIcon(QIcon(":/icons/24x24/application-export.png")); button->setPopupMode(QToolButton::InstantPopup); toolbar->addWidget(button); } toolbar->addSeparator(); toolbar->addAction(QIcon(":/icons/24x24/gear.png"), tr("S&ettings"), [this] { SettingsDialog dialog(this); dialog.exec(); /* library folders might have changed! */ UpdateFolderMenu(); }); addToolBar(toolbar); } } void MainWindow::UpdateFolderMenu() { if (!folder_menu) return; folder_menu->clear(); /* add in all of our existing folders... */ std::size_t i = 0; for (const auto& path : session.config.library.paths) { const QString folder = Strings::ToQString(path); QAction* action = folder_menu->addAction(folder, [folder] { QDesktopServices::openUrl(QUrl::fromLocalFile(folder)); }); if (i < 9) { /* Qt::Key_1 is equivalent to 1 in ASCII, so we can use the same * stupid `'0' + i` trick here */ action->setShortcut(QKeySequence(Qt::ALT | static_cast<Qt::Modifier>(Qt::Key_1 + i))); } else if (i == 9) { action->setShortcut(QKeySequence(Qt::ALT | Qt::Key_0)); } /* don't bother with a shortcut in case of more... */ i++; } folder_menu->addSeparator(); { folder_menu->addAction(tr("&Add new folder..."), [this] { const QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (dir.isEmpty()) return; session.config.library.paths.insert(Strings::ToUtf8String(dir)); UpdateFolderMenu(); }); } } void MainWindow::SetActivePage(QWidget* page) { this->setCentralWidget(page); } void MainWindow::AsyncSynchronize(QAction* action, QStackedWidget* stack) { if (session.config.service == Anime::Service::None) { QMessageBox msg; msg.setWindowTitle(tr("Error synchronizing with service!")); msg.setText(tr("It seems you haven't yet selected a service to use.")); msg.setInformativeText(tr("Would you like to select one now?")); msg.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msg.setDefaultButton(QMessageBox::Yes); int ret = msg.exec(); if (ret == QMessageBox::Yes) { SettingsDialog dialog; dialog.exec(); } } /* 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() { /* This sucks a LOT */ setUpdatesEnabled(false); AddMainWidgets(); CreateBars(); setUpdatesEnabled(true); } void MainWindow::changeEvent(QEvent* event) { if (event) { /* is this really necessary */ switch (event->type()) { // this event is send if a translator is loaded case QEvent::LanguageChange: RetranslateUI(); break; default: break; } } QMainWindow::changeEvent(event); } void MainWindow::showEvent(QShowEvent* event) { QMainWindow::showEvent(event); #ifdef WIN32 /* Technically this *should* be * session.config.theme.IsInDarkTheme() && win32::IsInDarkTheme() * but I prefer the title bar being black even when light mode * is enabled :/ */ win32::SetTitleBarsToBlack(session.config.theme.IsInDarkTheme()); #endif } void MainWindow::closeEvent(QCloseEvent* event) { playing_thread_timer_.stop(); playing_thread_.wait(); async_synchronize_thread_.wait(); session.config.Save(); Anime::db.SaveDatabaseToDisk(); event->accept(); }