view src/gui/window.cc @ 186:6ef31dbb90ca

anime: no unnecessary conversion to floating point
author Paper <mrpapersonic@gmail.com>
date Wed, 06 Dec 2023 11:47:59 -0500
parents f88eda79c60a
children 2d5823df870f
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/theme.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/widgets/sidebar.h"
#include "services/services.h"
#include "track/media.h"

#include "anitomy/anitomy.h"

#include <QActionGroup>
#include <QApplication>
#include <QDebug>
#include <QFile>
#include <QHBoxLayout>
#include <QMainWindow>
#include <QMenuBar>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QStackedWidget>
#include <QTextStream>
#include <QThread>
#include <QThreadPool>
#include <QTimer>
#include <QToolBar>
#include <QToolButton>

#ifdef MACOSX
#	include "sys/osx/dark_theme.h"
#elif defined(WIN32)
#	include "sys/win32/dark_theme.h"
#endif

enum class Pages {
	NOW_PLAYING,

	ANIME_LIST,
	HISTORY,
	STATISTICS,

	SEARCH,
	SEASONS,
	TORRENTS
};

void PlayingThread::run() {
	std::vector<std::string> files;
	Track::Media::GetCurrentlyPlaying(files);
	emit Done(files);
}

MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
	setWindowIcon(QIcon(":/favicon.png"));

	main_widget.reset(new QWidget(this));
	/*QHBoxLayout* layout = */new QHBoxLayout(main_widget.get());

	AddMainWidgets();

	setCentralWidget(main_widget.get());

	CreateBars();

	NowPlayingPage* page = reinterpret_cast<NowPlayingPage*>(stack->widget(static_cast<int>(Pages::NOW_PLAYING)));

	qRegisterMetaType<std::vector<std::string>>();

	thread.reset(new PlayingThread(this));

	QTimer* timer = new QTimer;

	connect(timer, &QTimer::timeout, this, [this, page] {
		if (!thread.get() || thread->isRunning())
			return;
		connect(thread.get(), &QThread::finished, thread.get(), &QThread::deleteLater);
		connect(thread.get(), &PlayingThread::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();

				int id = Anime::db.GetAnimeFromTitle(Strings::ToUtf8String(elements.get(anitomy::kElementAnimeTitle)));
				if (id <= 0)
					continue;

				page->SetPlaying(Anime::db.items[id], elements);
				break;
			}
		});
		thread->start();
	});

	timer->start(5000);
	timer->moveToThread(thread.get());
}

void MainWindow::AddMainWidgets() {
	int page = static_cast<int>(Pages::ANIME_LIST);
	if (sidebar.get()) {
		main_widget->layout()->removeWidget(sidebar.get());
		sidebar.reset();
	}

	if (stack.get()) {
		page = stack->currentIndex();
		main_widget->layout()->removeWidget(stack.get());
	}

	sidebar.reset(new SideBar(main_widget.get()));
	sidebar->setFixedWidth(128);
	sidebar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);

	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()));
	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()));

	connect(sidebar.get(), &SideBar::CurrentItemChanged, stack.get(), &QStackedWidget::setCurrentIndex);
	sidebar->SetCurrentItem(page);

	main_widget->layout()->addWidget(sidebar.get());
	main_widget->layout()->addWidget(stack.get());
}

void MainWindow::CreateBars() {
	/* Menu Bar
	   The notation of these might seem ugly at first, but it's actually very nice
	   (just trust me). It makes it much easier to edit the lists and makes it clear
	   if you're in submenu or not. */
	QMenuBar* menubar = new QMenuBar(this);

	{
		/* File */
		QMenu* menu = menubar->addMenu(tr("&File"));

		{
			QMenu* submenu = menu->addMenu(tr("&Library folders"));
			{
				QAction* action = submenu->addAction(tr("&Add new folder..."));
			}
		}

		{
			QAction* action = menu->addAction(tr("&Scan available episodes"));
		}

		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"), qApp, &QApplication::quit);
		}
	}

	{
		/* Services */
		QMenu* menu = menubar->addMenu(tr("&Services"));
		{
			{
				QAction* action = menu->addAction(tr("Synchronize &list"), [this] { AsyncSynchronize(stack.get()); });
				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();
			});
			action->setMenuRole(QAction::PreferencesRole);
		}
	}

	{
		/* View */
		QMenu* menu = menubar->addMenu(tr("&View"));

		{
			/* Pages... */
			std::map<QAction*, int> page_to_index_map = {};

			QActionGroup* pages_group = new QActionGroup(this);
			pages_group->setExclusive(true);

			{
				QAction* action = pages_group->addAction(menu->addAction(tr("&Now Playing")));
				action->setCheckable(true);
				page_to_index_map[action] = 0;
			}

			{
				QAction* action = pages_group->addAction(menu->addAction(tr("&Anime List")));
				action->setCheckable(true);
				action->setChecked(true);
				page_to_index_map[action] = 1;
			}

			{
				QAction* action = pages_group->addAction(menu->addAction(tr("&History")));
				action->setCheckable(true);
				page_to_index_map[action] = 2;
			}

			{
				QAction* action = pages_group->addAction(menu->addAction(tr("&Statistics")));
				action->setCheckable(true);
				page_to_index_map[action] = 3;
			}

			{
				QAction* action = pages_group->addAction(menu->addAction(tr("S&earch")));
				action->setCheckable(true);
				page_to_index_map[action] = 4;
			}

			{
				QAction* action = pages_group->addAction(menu->addAction(tr("Se&asons")));
				action->setCheckable(true);
				page_to_index_map[action] = 5;
			}

			{
				QAction* action = pages_group->addAction(menu->addAction(tr("&Torrents")));
				action->setCheckable(true);
				page_to_index_map[action] = 6;
			}

			/* pain in my ass */
			connect(sidebar.get(), &SideBar::CurrentItemChanged, this,
					[pages_group](int index) { pages_group->actions()[index]->setChecked(true); });

			connect(pages_group, &QActionGroup::triggered, this,
					[this, page_to_index_map](QAction* action) { sidebar->SetCurrentItem(page_to_index_map.at(action)); });
		}

		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(QIcon(":/icons/24x24/arrow-circle-double-135.png"), tr("&Synchronize"),
						   [this] { AsyncSynchronize(stack.get()); });

		toolbar->addSeparator();

		{
			QToolButton* button = new QToolButton(toolbar);
			{
				QMenu* menu = new QMenu(button);
				QAction* action = menu->addAction(tr("..."));

				button->setMenu(menu);
			}
			button->setIcon(QIcon(":/icons/24x24/folder-open.png"));
			button->setPopupMode(QToolButton::InstantPopup);
			toolbar->addWidget(button);
		}

		{
			QToolButton* button = new QToolButton(toolbar);

			{
				QMenu* menu = new QMenu(button);
				QAction* action = menu->addAction(tr("..."));

				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();
		});
		addToolBar(toolbar);
	}
}

void MainWindow::SetActivePage(QWidget* page) {
	this->setCentralWidget(page);
}

void MainWindow::AsyncSynchronize(QStackedWidget* stack) {
	if (session.config.service == Anime::Services::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();
		}
	}
	QThreadPool::globalInstance()->start([stack] {
		Services::Synchronize();
		reinterpret_cast<AnimeListPage*>(stack->widget(static_cast<int>(Pages::ANIME_LIST)))->Refresh();
	});
}

void MainWindow::RetranslateUI() {
	/* This kinda sucks but nobody's really going to be changing
	   the application language all the time :p */
	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) {
	session.config.Save();
	Anime::db.SaveDatabaseToDisk();
	event->accept();
}

#include "gui/moc_window.cpp"