Mercurial > minori
view src/gui/window.cc @ 337:a7d4e5107531
dep/animone: REFACTOR ALL THE THINGS
1: animone now has its own syntax divergent from anisthesia,
making different platforms actually have their own sections
2: process names in animone are now called `comm' (this will
probably break things). this is what its called in bsd/linux
so I'm just going to use it everywhere
3: the X11 code now checks for the existence of a UTF-8 window title
and passes it if available
4: ANYTHING THATS NOT LINUX IS 100% UNTESTED AND CAN AND WILL BREAK!
I still actually need to test the bsd code. to be honest I'm probably
going to move all of the bsds into separate files because they're
all essentially different operating systems at this point
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Wed, 19 Jun 2024 12:51:15 -0400 |
parents | 71396ecb6f7e |
children | a0aa8c8c4307 |
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::db.Refresh(); }); } 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(); }