Mercurial > minori
view src/gui/window.cc @ 309:bf89fbf7ff38
misc: remove copyrighted app icon
if someone comes up with a good icon, please be my guest
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Tue, 11 Jun 2024 04:24:59 -0400 |
parents | 9a88e1725fd2 |
children | 34347fd2a2de |
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 <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); 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.GetAnimeFromTitle(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); } /* 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(); }