changeset 64:fe719c109dbc

*: update 1. add media tracking ability, and it displays info on the `now playing` page 2. the `now playing` page now actually shows something 3. renamed every page class to be more accurate to what it is 4. ...
author Paper <mrpapersonic@gmail.com>
date Sun, 01 Oct 2023 23:15:43 -0400
parents 3d2decf093bb
children 26721c28bf22
files CMakeLists.txt dep/animia/.clang-format dep/animia/.hgignore dep/animia/CMakeLists.txt dep/animia/test/CMakeLists.txt include/core/anime_db.h include/core/json.h include/core/strings.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/anime_info.h include/gui/widgets/text.h include/track/media.h src/core/anime_db.cpp src/core/filesystem.cpp src/core/strings.cpp src/core/time.cpp src/gui/dialog/about.cpp src/gui/dialog/information.cpp src/gui/dialog/settings.cpp src/gui/pages/anime_list.cpp src/gui/pages/history.cpp src/gui/pages/now_playing.cpp src/gui/pages/search.cpp src/gui/pages/seasons.cpp src/gui/pages/statistics.cpp src/gui/pages/torrents.cpp src/gui/widgets/anime_info.cpp src/gui/widgets/text.cpp src/gui/window.cpp src/services/anilist.cpp src/track/media.cpp
diffstat 37 files changed, 579 insertions(+), 262 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Sun Oct 01 06:39:47 2023 -0400
+++ b/CMakeLists.txt	Sun Oct 01 23:15:43 2023 -0400
@@ -7,8 +7,15 @@
 	enable_language(OBJCXX)
 endif()
 
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
+set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
+set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
+
+option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
+option(USE_QT6 "Build with Qt 6 instead of Qt 5" OFF)
+
 add_subdirectory(dep/anitomy)
-# add_subdirectory(dep/animia)
+add_subdirectory(dep/animia)
 add_subdirectory(dep/pugixml)
 
 # Fix for mingw64
@@ -24,6 +31,7 @@
 set(LIBRARIES
 	${CURL_LIBRARIES}
 	anitomy
+	animia
 )
 
 if(USE_QT6)
@@ -67,6 +75,7 @@
 
 
 	# Custom widgets
+	src/gui/widgets/anime_info.cpp
 	src/gui/widgets/sidebar.cpp
 	src/gui/widgets/text.cpp
 	src/gui/widgets/optional_date.cpp
@@ -86,6 +95,9 @@
 	src/services/services.cpp
 	src/services/anilist.cpp
 
+	# Tracking
+	src/track/media.cpp
+
 	# Qt resources
 	rc/icons.qrc
 	dep/darkstyle/darkstyle.qrc
@@ -105,13 +117,13 @@
 set_property(TARGET minori PROPERTY AUTOMOC ON)
 set_property(TARGET minori PROPERTY AUTORCC ON)
 
-target_include_directories(minori PUBLIC ${CURL_INCLUDE_DIRS} PRIVATE include dep/pugixml/src dep/animia/include)
+target_include_directories(minori PUBLIC ${CURL_INCLUDE_DIRS} PRIVATE include dep/pugixml/src dep/animia/include dep/anitomy)
 if(USE_QT6)
 	target_include_directories(minori PUBLIC ${Qt6Widgets_INCLUDE_DIRS})
 else()
 	target_include_directories(minori PUBLIC ${Qt5Widgets_INCLUDE_DIRS})
 endif()
-target_compile_options(minori PRIVATE -Wall -Wpedantic -Wextra -Wsuggest-override)
+target_compile_options(minori PRIVATE -Wall -Wpedantic -Wextra -Wsuggest-override -Wold-style-cast)
 if(APPLE)
 	target_compile_definitions(minori PUBLIC MACOSX)
 elseif(WIN32)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dep/animia/.clang-format	Sun Oct 01 23:15:43 2023 -0400
@@ -0,0 +1,30 @@
+---
+BasedOnStyle: LLVM
+UseTab: ForIndentation
+PointerAlignment: Left
+ColumnLimit: 120
+IndentWidth: 4
+TabWidth: 4
+AccessModifierOffset: 4
+
+IndentCaseLabels: true
+IndentAccessModifiers: true
+IndentPPDirectives: AfterHash
+
+BreakBeforeBraces: Attach
+BreakStringLiterals: true
+
+AlignAfterOpenBracket: Align
+AlignArrayOfStructures: Left
+AlignEscapedNewlines: DontAlign
+AlignConsecutiveMacros: true
+
+AllowShortIfStatementsOnASingleLine: false
+AllowShortBlocksOnASingleLine: Empty
+AllowShortEnumsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: InlineOnly
+AllowShortCaseLabelsOnASingleLine: true
+
+---
+Language: Cpp
+Standard: Cpp11
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dep/animia/.hgignore	Sun Oct 01 23:15:43 2023 -0400
@@ -0,0 +1,40 @@
+syntax: glob
+
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+
+syntax: regexp
+
+# Build dir
+^build/
+^test/build/
\ No newline at end of file
--- a/dep/animia/CMakeLists.txt	Sun Oct 01 06:39:47 2023 -0400
+++ b/dep/animia/CMakeLists.txt	Sun Oct 01 23:15:43 2023 -0400
@@ -11,10 +11,14 @@
 	list(APPEND SRC_FILES src/win32.cpp)
 endif()
 add_library(animia SHARED ${SRC_FILES})
-set_target_properties(animia PROPERTIES
-    PUBLIC_HEADER animia/animia.h CXX_STANDARD 17)
+set_target_properties(animia PROPERTIES CXX_STANDARD 17)
 target_include_directories(animia PRIVATE include)
 
+install(TARGETS animia
+        ARCHIVE DESTINATION lib
+        LIBRARY DESTINATION lib
+        RUNTIME DESTINATION bin)
+
 if(BUILD_TESTS)
 	project(test LANGUAGES CXX)
 	add_executable(test test/main.cpp)
--- a/dep/animia/test/CMakeLists.txt	Sun Oct 01 06:39:47 2023 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-cmake_minimum_required(VERSION 3.16)
-add_subdirectory(.. ${CMAKE_CURRENT_BINARY_DIR}/animia)
-project(test LANGUAGES CXX)
-add_executable(test main.cpp)
-
-target_include_directories(test PUBLIC ../include)
\ No newline at end of file
--- a/include/core/anime_db.h	Sun Oct 01 06:39:47 2023 -0400
+++ b/include/core/anime_db.h	Sun Oct 01 23:15:43 2023 -0400
@@ -2,6 +2,7 @@
 #define __core__anime_db_h
 #include "core/anime.h"
 #include <unordered_map>
+#include <string>
 
 namespace Anime {
 
@@ -15,6 +16,7 @@
 		double GetAverageScore();
 		double GetScoreDeviation();
 		int GetListsAnimeAmount(ListStatus status);
+		int GetAnimeFromTitle(std::string title);
 };
 
 extern Database db;
--- a/include/core/json.h	Sun Oct 01 06:39:47 2023 -0400
+++ b/include/core/json.h	Sun Oct 01 23:15:43 2023 -0400
@@ -1,10 +1,15 @@
 #ifndef __core__json_h
 #define __core__json_h
+
 #include "../../dep/json/json.h"
+
 namespace JSON {
+
 std::string GetString(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, std::string def = "");
 int GetInt(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, int def = 0);
 bool GetBoolean(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, bool def = false);
 double GetDouble(nlohmann::json const& json, nlohmann::json::json_pointer const& ptr, double def = 0);
+
 } // namespace JSON
+
 #endif // __core__json_h
\ No newline at end of file
--- a/include/core/strings.h	Sun Oct 01 06:39:47 2023 -0400
+++ b/include/core/strings.h	Sun Oct 01 23:15:43 2023 -0400
@@ -4,6 +4,8 @@
 #include <string>
 #include <vector>
 
+class QString;
+
 namespace Strings {
 
 /* Implode function: takes a vector of strings and turns it
@@ -22,7 +24,11 @@
 std::string ToLower(const std::string& string);
 
 std::wstring ToWstring(const std::string& string);
+std::wstring ToWstring(const QString& string);
 std::string ToUtf8String(const std::wstring& wstring);
+std::string ToUtf8String(const QString& string);
+QString ToQString(const std::string& string);
+QString ToQString(const std::wstring& wstring);
 
 };     // namespace Strings
 #endif // __core__strings_h
\ No newline at end of file
--- a/include/gui/pages/anime_list.h	Sun Oct 01 06:39:47 2023 -0400
+++ b/include/gui/pages/anime_list.h	Sun Oct 01 23:15:43 2023 -0400
@@ -8,27 +8,27 @@
 #include <QWidget>
 #include <vector>
 
-class AnimeListWidgetDelegate : public QStyledItemDelegate {
+class AnimeListPageDelegate : public QStyledItemDelegate {
 		Q_OBJECT
 
 	public:
-		explicit AnimeListWidgetDelegate(QObject* parent);
+		explicit AnimeListPageDelegate(QObject* parent);
 
 		QWidget* createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const override;
 		void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
 };
 
-class AnimeListWidgetSortFilter : public QSortFilterProxyModel {
+class AnimeListPageSortFilter : public QSortFilterProxyModel {
 		Q_OBJECT
 
 	public:
-		AnimeListWidgetSortFilter(QObject* parent = nullptr);
+		AnimeListPageSortFilter(QObject* parent = nullptr);
 
 	protected:
 		bool lessThan(const QModelIndex& l, const QModelIndex& r) const override;
 };
 
-class AnimeListWidgetModel : public QAbstractListModel {
+class AnimeListPageModel : public QAbstractListModel {
 		Q_OBJECT
 
 	public:
@@ -48,8 +48,8 @@
 			NB_COLUMNS
 		};
 
-		AnimeListWidgetModel(QWidget* parent, Anime::ListStatus _status);
-		~AnimeListWidgetModel() override = default;
+		AnimeListPageModel(QWidget* parent, Anime::ListStatus _status);
+		~AnimeListPageModel() override = default;
 		int rowCount(const QModelIndex& parent = QModelIndex()) const override;
 		int columnCount(const QModelIndex& parent = QModelIndex()) const override;
 		QVariant data(const QModelIndex& index, int role) const override;
@@ -65,11 +65,11 @@
 
 /* todo: rename these to "page" or something more
    sensible than "widget" */
-class AnimeListWidget : public QWidget {
+class AnimeListPage : public QWidget {
 		Q_OBJECT
 
 	public:
-		AnimeListWidget(QWidget* parent);
+		AnimeListPage(QWidget* parent);
 		void Refresh();
 		void Reset();
 
@@ -94,6 +94,6 @@
 		QTabBar* tab_bar;
 		QTreeView* tree_view;
 		QRect panelRect;
-		AnimeListWidgetSortFilter* sort_models[5];
+		AnimeListPageSortFilter* sort_models[5];
 };
 #endif // __gui__pages__anime_list_h
\ No newline at end of file
--- a/include/gui/pages/history.h	Sun Oct 01 06:39:47 2023 -0400
+++ b/include/gui/pages/history.h	Sun Oct 01 23:15:43 2023 -0400
@@ -2,11 +2,11 @@
 #define __gui__pages__history_h
 #include <QWidget>
 
-class HistoryWidget : public QWidget {
+class HistoryPage : public QWidget {
 		Q_OBJECT
 
 	public:
-		HistoryWidget(QWidget* parent = nullptr);
+		HistoryPage(QWidget* parent = nullptr);
 };
 
 #endif // __gui__pages__history_h
--- a/include/gui/pages/now_playing.h	Sun Oct 01 06:39:47 2023 -0400
+++ b/include/gui/pages/now_playing.h	Sun Oct 01 23:15:43 2023 -0400
@@ -1,12 +1,19 @@
 #ifndef __gui__pages__now_playing_h
 #define __gui__pages__now_playing_h
-#include <QWidget>
+#include <QFrame>
 
-class NowPlayingWidget : public QWidget {
+class QStackedWidget;
+
+class NowPlayingPage : public QFrame {
 		Q_OBJECT
 
 	public:
-		NowPlayingWidget(QWidget* parent = nullptr);
+		NowPlayingPage(QWidget* parent = nullptr);
+		void SetDefault();
+		void SetPlaying(int id);
+
+	private:
+		QStackedWidget* stack;
 };
 
 #endif // __gui__pages__now_playing_h
--- a/include/gui/pages/search.h	Sun Oct 01 06:39:47 2023 -0400
+++ b/include/gui/pages/search.h	Sun Oct 01 23:15:43 2023 -0400
@@ -2,11 +2,11 @@
 #define __gui__pages__search_h
 #include <QWidget>
 
-class SearchWidget : public QWidget {
+class SearchPage : public QWidget {
 		Q_OBJECT
 
 	public:
-		SearchWidget(QWidget* parent = nullptr);
+		SearchPage(QWidget* parent = nullptr);
 };
 
 #endif // __gui__pages__search_h
--- a/include/gui/pages/seasons.h	Sun Oct 01 06:39:47 2023 -0400
+++ b/include/gui/pages/seasons.h	Sun Oct 01 23:15:43 2023 -0400
@@ -2,11 +2,11 @@
 #define __gui__pages__seasons_h
 #include <QWidget>
 
-class SeasonsWidget : public QWidget {
+class SeasonsPage : public QWidget {
 		Q_OBJECT
 
 	public:
-		SeasonsWidget(QWidget* parent = nullptr);
+		SeasonsPage(QWidget* parent = nullptr);
 };
 
 #endif // __gui__pages__seasons_h
--- a/include/gui/pages/statistics.h	Sun Oct 01 06:39:47 2023 -0400
+++ b/include/gui/pages/statistics.h	Sun Oct 01 23:15:43 2023 -0400
@@ -1,15 +1,18 @@
 #ifndef __gui__pages__statistics_h
 #define __gui__pages__statistics_h
-#include "gui/pages/anime_list.h"
 #include <QFrame>
-#include <QPlainTextEdit>
 #include <QWidget>
+//#include "gui/widgets/text.h"
 
-class StatisticsWidget : public QFrame {
+namespace TextWidgets {
+	class Paragraph;
+}
+
+class StatisticsPage : public QFrame {
 		Q_OBJECT
 
 	public:
-		StatisticsWidget(QWidget* parent = nullptr);
+		StatisticsPage(QWidget* parent = nullptr);
 		void UpdateStatistics();
 
 	protected:
@@ -19,7 +22,7 @@
 		std::string MinutesToDateString(int minutes);
 		std::string SecondsToDateString(int seconds);
 
-		QPlainTextEdit* anime_list_data;
+		TextWidgets::Paragraph* anime_list_data;
 
 		// QPlainTextEdit* score_distribution_title;
 		// QPlainTextEdit* score_distribution_labels;
@@ -28,6 +31,6 @@
 		/* we don't HAVE a local database (yet ;)) */
 		// QPlainTextEdit* local_database_data;
 
-		QPlainTextEdit* application_data;
+		TextWidgets::Paragraph* application_data;
 };
 #endif // __gui__pages__statistics_h
\ No newline at end of file
--- a/include/gui/pages/torrents.h	Sun Oct 01 06:39:47 2023 -0400
+++ b/include/gui/pages/torrents.h	Sun Oct 01 23:15:43 2023 -0400
@@ -2,11 +2,11 @@
 #define __gui__pages__torrents_h
 #include <QWidget>
 
-class TorrentsWidget : public QWidget {
+class TorrentsPage : public QWidget {
 		Q_OBJECT
 
 	public:
-		TorrentsWidget(QWidget* parent = nullptr);
+		TorrentsPage(QWidget* parent = nullptr);
 };
 
 #endif // __gui__pages__torrents_h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/gui/widgets/anime_info.h	Sun Oct 01 23:15:43 2023 -0400
@@ -0,0 +1,16 @@
+#ifndef __gui__widgets__anime_info_h
+#define __gui__widgets__anime_info_h
+#include <QWidget>
+
+namespace Anime {
+	class Anime;
+}
+
+class AnimeInfoWidget : public QWidget {
+		Q_OBJECT
+
+	public:
+		AnimeInfoWidget(const Anime::Anime& anime, QWidget* parent = nullptr);
+};
+
+#endif // __gui__widgets__anime_info_h
--- a/include/gui/widgets/text.h	Sun Oct 01 06:39:47 2023 -0400
+++ b/include/gui/widgets/text.h	Sun Oct 01 23:15:43 2023 -0400
@@ -1,20 +1,21 @@
 #ifndef __gui__ui_utils_h
 #define __gui__ui_utils_h
+
 #include <QFrame>
 #include <QLabel>
 #include <QPlainTextEdit>
 #include <QSize>
 #include <QString>
 #include <QWidget>
+
 namespace TextWidgets {
-void SetPlainTextEditData(QPlainTextEdit* text_edit, QString data);
 
 class Header : public QWidget {
 		Q_OBJECT
 
 	public:
 		Header(QString title, QWidget* parent = nullptr);
-		void SetTitle(QString title);
+		void SetText(QString title);
 
 	private:
 		QLabel* static_text_title;
@@ -26,6 +27,7 @@
 
 	public:
 		Paragraph(QString text, QWidget* parent = nullptr);
+		void SetText(QString text);
 		QSize minimumSizeHint() const override;
 		QSize sizeHint() const override;
 };
@@ -73,5 +75,14 @@
 		Header* header;
 		Paragraph* paragraph;
 };
-};     // namespace TextWidgets
+
+class Title : public Paragraph {
+		Q_OBJECT
+
+	public:
+		Title(QString title, QWidget* parent = nullptr);
+};
+
+} // namespace TextWidgets
+
 #endif // __gui__ui_utils_h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/track/media.h	Sun Oct 01 23:15:43 2023 -0400
@@ -0,0 +1,14 @@
+#ifndef __track__media_h
+#define __track__media_h
+#include "core/filesystem.h"
+
+namespace Track {
+namespace Media {
+
+Filesystem::Path GetCurrentPlaying();
+std::string GetFileTitle(Filesystem::Path path);
+
+} // namespace Media
+} // namespace Track
+
+#endif // __track__media_h
--- a/src/core/anime_db.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/core/anime_db.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -1,5 +1,7 @@
 #include "core/anime_db.h"
 #include "core/anime.h"
+#include "core/strings.h"
+#include <QDebug>
 
 namespace Anime {
 
@@ -79,13 +81,28 @@
 	int amt = 0;
 	for (const auto& a : items) {
 		if (a.second.IsInUserList() && a.second.GetUserScore()) {
-			squares_sum += std::pow((double)a.second.GetUserScore() - avg, 2);
+			squares_sum += std::pow(static_cast<double>(a.second.GetUserScore()) - avg, 2);
 			amt++;
 		}
 	}
 	return (amt > 0) ? std::sqrt(squares_sum / amt) : 0;
 }
 
+int Database::GetAnimeFromTitle(std::string title) {
+	if (title.empty())
+		return 0;
+	for (const auto& a : items) {
+		if (a.second.GetUserPreferredTitle().find(title) != std::string::npos)
+			return a.second.GetId();
+		for (const auto& t : a.second.GetTitleSynonyms()) {
+			if (t.find(title) != std::string::npos) {
+				return a.second.GetId();
+			}
+		}
+	}
+	return 0;
+}
+
 Database db;
 
 } // namespace Anime
\ No newline at end of file
--- a/src/core/filesystem.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/core/filesystem.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -67,17 +67,20 @@
 }
 
 std::string Path::Basename() const {
-	return _path.substr(0, _path.find_last_of(DELIM));
+	unsigned long long pos = _path.find_last_of(DELIM);
+	return pos != std::string::npos ? _path.substr(pos+1, _path.length()) : "";
 }
 
 std::string Path::Stem() const {
 	std::string basename = Basename();
-	return basename.substr(0, basename.find_last_of("."));
+	unsigned long long pos = basename.find_last_of(".");
+	return pos != std::string::npos ? basename.substr(0, pos) : "";
 }
 
 std::string Path::Extension() const {
 	std::string basename = Basename();
-	return basename.substr(basename.find_last_of("."), basename.length());
+	unsigned long long pos = basename.find_last_of(".");
+	return pos != std::string::npos ? basename.substr(pos+1, basename.length()) : "";
 }
 
 Path Path::GetParent() const {
--- a/src/core/strings.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/core/strings.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -2,6 +2,8 @@
  * strings.cpp: Useful functions for manipulating strings
  **/
 #include "core/strings.h"
+#include <QByteArray>
+#include <QString>
 #include <algorithm>
 #include <cctype>
 #include <codecvt>
@@ -62,8 +64,8 @@
 }
 
 /* these functions suck for i18n!...
-   but we only use them with JSON
-   stuff anyway */
+	but we only use them with JSON
+	stuff anyway */
 std::string ToUpper(const std::string& string) {
 	std::string result(string);
 	std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return std::toupper(c); });
@@ -81,9 +83,28 @@
 	return converter.from_bytes(string);
 }
 
+std::wstring ToWstring(const QString& string) {
+	std::wstring arr(string.size(), L'\0');
+	string.toWCharArray(&arr.front());
+	return arr;
+}
+
 std::string ToUtf8String(const std::wstring& wstring) {
 	std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
 	return converter.to_bytes(wstring);
 }
 
+std::string ToUtf8String(const QString& string) {
+	QByteArray ba = string.toUtf8();
+	return std::string(ba.data(), ba.size());
+}
+
+QString ToQString(const std::string& string) {
+	return QString::fromUtf8(string.c_str(), string.length());
+}
+
+QString ToQString(const std::wstring& wstring) {
+	return QString::fromWCharArray(wstring.c_str(), wstring.length());
+}
+
 } // namespace Strings
--- a/src/core/time.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/core/time.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -44,15 +44,15 @@
 }
 
 int64_t Duration::InMinutes() {
-	return std::llround((double)length / 60.0);
+	return std::llround(static_cast<double>(length) / 60.0);
 }
 
 int64_t Duration::InHours() {
-	return std::llround((double)length / 3600.0);
+	return std::llround(static_cast<double>(length) / 3600.0);
 }
 
 int64_t Duration::InDays() {
-	return std::llround((double)length / 86400.0);
+	return std::llround(static_cast<double>(length) / 86400.0);
 }
 
 int64_t GetSystemTime() {
--- a/src/gui/dialog/about.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/gui/dialog/about.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -62,7 +62,7 @@
 AboutWindow::AboutWindow(QWidget* parent) : QDialog(parent) {
 	setWindowTitle(tr("About Minori"));
 	setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint);
-	setLayout(new QHBoxLayout);
+	QHBoxLayout* layout = new QHBoxLayout(this);
 
 	QPalette pal = QPalette();
 	pal.setColor(QPalette::Window, pal.color(QPalette::Base));
@@ -127,5 +127,5 @@
 	cursor.insertText(tr("Links:"));
 	UNSET_FONT_BOLD(font, format, cursor);
 	cursor.insertBlock();
-	layout()->addWidget(paragraph);
+	layout->addWidget(paragraph);
 }
--- a/src/gui/dialog/information.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/gui/dialog/information.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -5,6 +5,7 @@
 #include "core/strings.h"
 #include "gui/pages/anime_list.h"
 #include "gui/translate/anime.h"
+#include "gui/widgets/anime_info.h"
 #include "gui/widgets/optional_date.h"
 #include "gui/widgets/text.h"
 #include "gui/window.h"
@@ -65,60 +66,15 @@
 
 	id = anime.GetId();
 	/* anime title header text */
-	TextWidgets::Paragraph* anime_title =
-	    new TextWidgets::Paragraph(QString::fromUtf8(anime.GetUserPreferredTitle().c_str()), main_widget);
-	anime_title->setReadOnly(true);
-	anime_title->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-	anime_title->setWordWrapMode(QTextOption::NoWrap);
-	anime_title->setFrameShape(QFrame::NoFrame);
-	anime_title->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
-	anime_title->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-	anime_title->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-
-	{
-		QFont font(anime_title->font());
-		font.setPointSize(12);
-		anime_title->setFont(font);
-	}
-
-	{
-		QPalette pal;
-		pal.setColor(QPalette::Window, Qt::transparent);
-		pal.setColor(QPalette::WindowText, Qt::blue);
-	}
+	TextWidgets::Title* anime_title =
+	    new TextWidgets::Title(QString::fromStdString(anime.GetUserPreferredTitle()), main_widget);
 
 	/* tabbed widget */
 	QTabWidget* tabbed_widget = new QTabWidget(main_widget);
 	tabbed_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
 
 	/* main info tab */
-	QWidget* main_information_widget = new QWidget(tabbed_widget);
-	main_information_widget->setLayout(new QVBoxLayout);
-
-	/* alt titles */
-	main_information_widget->layout()->addWidget(new TextWidgets::SelectableTextParagraph(
-	    tr("Alternative titles"), QString::fromUtf8(Strings::Implode(anime.GetTitleSynonyms(), ", ").c_str()),
-	    main_information_widget));
-
-	/* details */
-	QString details_data;
-	QTextStream details_data_s(&details_data);
-	details_data_s << Translate::ToString(anime.GetFormat()).c_str() << "\n"
-	               << anime.GetEpisodes() << "\n"
-	               << Translate::ToString(anime.GetUserStatus()).c_str() << "\n"
-	               << Translate::ToString(anime.GetSeason()).c_str() << " " << anime.GetAirDate().GetYear() << "\n"
-	               << Strings::Implode(anime.GetGenres(), ", ").c_str() << "\n"
-	               << anime.GetAudienceScore() << "%";
-	main_information_widget->layout()->addWidget(
-	    new TextWidgets::LabelledTextParagraph(tr("Details"), tr("Type:\nEpisodes:\nStatus:\nSeason:\nGenres:\nScore:"),
-	                                           details_data, main_information_widget));
-
-	/* synopsis */
-	TextWidgets::SelectableTextParagraph* synopsis = new TextWidgets::SelectableTextParagraph(
-	    tr("Synopsis"), QString::fromUtf8(anime.GetSynopsis().c_str()), main_information_widget);
-
-	synopsis->GetParagraph()->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
-	main_information_widget->layout()->addWidget(synopsis);
+	AnimeInfoWidget* main_information_widget = new AnimeInfoWidget(anime, tabbed_widget);
 
 	QWidget* settings_widget = new QWidget(tabbed_widget);
 	settings_widget->setLayout(new QVBoxLayout);
@@ -136,29 +92,6 @@
 #define LAYOUT_HORIZ_SPACING 25
 #define LAYOUT_VERT_SPACING  5
 #define LAYOUT_ITEM_WIDTH    175
-/* Creates a subsection with a width of 175 */
-#define CREATE_SUBSECTION(x) \
-	{ \
-		QWidget* subsection = new QWidget(section); \
-		subsection->setLayout(new QVBoxLayout); \
-		subsection->setFixedWidth(LAYOUT_ITEM_WIDTH); \
-		subsection->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); \
-		subsection->layout()->setSpacing(LAYOUT_VERT_SPACING); \
-		subsection->layout()->setContentsMargins(0, 0, 0, 0); \
-		x; \
-		layout->addWidget(subsection); \
-	}
-/* Creates a section in the parent `a` */
-#define CREATE_SECTION(a, x) \
-	{ \
-		QWidget* section = new QWidget(a); \
-		QHBoxLayout* layout = new QHBoxLayout(section); \
-		layout->setSpacing(LAYOUT_HORIZ_SPACING); \
-		layout->setContentsMargins(0, 0, 0, 0); \
-		x; \
-		layout->addStretch(); \
-		a->layout()->addWidget(section); \
-	}
 /* Creates a subsection that takes up whatever space is necessary */
 #define CREATE_FULL_WIDTH_SUBSECTION(x) \
 	{ \
@@ -170,6 +103,10 @@
 		x; \
 		layout->addWidget(subsection); \
 	}
+
+/* Creates a subsection with a width of 175 */
+#define CREATE_SUBSECTION(x) CREATE_FULL_WIDTH_SUBSECTION(x subsection->setFixedWidth(LAYOUT_ITEM_WIDTH);)
+
 /* Creates a section in the parent `a` */
 #define CREATE_FULL_WIDTH_SECTION(a, x) \
 	{ \
@@ -181,6 +118,9 @@
 		a->layout()->addWidget(section); \
 	}
 
+/* Creates a section in the parent `a` */
+#define CREATE_SECTION(a, x) CREATE_FULL_WIDTH_SECTION(a, x layout->addStretch();)
+
 	CREATE_SECTION(sg_anime_list_content, {
 		/* Episodes watched section */
 		CREATE_SUBSECTION({
@@ -304,7 +244,7 @@
 #undef CREATE_FULL_WIDTH_SECTION
 #undef CREATE_FULL_WIDTH_SUBSECTION
 
-	static_cast<QBoxLayout*>(settings_widget->layout())->addStretch();
+	reinterpret_cast<QBoxLayout*>(settings_widget->layout())->addStretch();
 
 	tabbed_widget->addTab(main_information_widget, tr("Main information"));
 	tabbed_widget->addTab(settings_widget, tr("My list and settings"));
--- a/src/gui/dialog/settings.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/gui/dialog/settings.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -56,9 +56,9 @@
 }
 
 void SettingsDialog::OnOK() {
-	QStackedWidget* stacked = (QStackedWidget*)layout->itemAt(1)->widget();
+	QStackedWidget* stacked = reinterpret_cast<QStackedWidget*>(layout->itemAt(1)->widget());
 	for (int i = 0; i < stacked->count(); i++) {
-		((SettingsPage*)stacked->widget(i))->SaveInfo();
+		reinterpret_cast<SettingsPage*>(stacked->widget(i))->SaveInfo();
 	}
 	QDialog::accept();
 }
--- a/src/gui/pages/anime_list.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/gui/pages/anime_list.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -27,22 +27,22 @@
 #include <QStyledItemDelegate>
 #include <cmath>
 
-AnimeListWidgetDelegate::AnimeListWidgetDelegate(QObject* parent) : QStyledItemDelegate(parent) {
+AnimeListPageDelegate::AnimeListPageDelegate(QObject* parent) : QStyledItemDelegate(parent) {
 }
 
-QWidget* AnimeListWidgetDelegate::createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const {
+QWidget* AnimeListPageDelegate::createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const {
 	// no edit 4 u
 	return nullptr;
 }
 
-void AnimeListWidgetDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
-                                    const QModelIndex& index) const {
+void AnimeListPageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
+                                  const QModelIndex& index) const {
 	switch (index.column()) {
 #if 0
-		case AnimeListWidgetModel::AL_PROGRESS: {
+		case AnimeListPageModel::AL_PROGRESS: {
 			const int progress = static_cast<int>(index.data(Qt::UserRole).toReal());
 			const int episodes =
-			    static_cast<int>(index.siblingAtColumn(AnimeListWidgetModel::AL_EPISODES).data(Qt::UserRole).toReal());
+			    static_cast<int>(index.siblingAtColumn(AnimeListPageModel::AL_EPISODES).data(Qt::UserRole).toReal());
 
 			int text_width = 59;
 			QRectF text_rect(option.rect.x() + text_width, option.rect.y(), text_width, option.decorationSize.height());
@@ -63,10 +63,10 @@
 	}
 }
 
-AnimeListWidgetSortFilter::AnimeListWidgetSortFilter(QObject* parent) : QSortFilterProxyModel(parent) {
+AnimeListPageSortFilter::AnimeListPageSortFilter(QObject* parent) : QSortFilterProxyModel(parent) {
 }
 
-bool AnimeListWidgetSortFilter::lessThan(const QModelIndex& l, const QModelIndex& r) const {
+bool AnimeListPageSortFilter::lessThan(const QModelIndex& l, const QModelIndex& r) const {
 	QVariant left = sourceModel()->data(l, sortRole());
 	QVariant right = sourceModel()->data(r, sortRole());
 
@@ -81,22 +81,22 @@
 	}
 }
 
-AnimeListWidgetModel::AnimeListWidgetModel(QWidget* parent, Anime::ListStatus _status) : QAbstractListModel(parent) {
+AnimeListPageModel::AnimeListPageModel(QWidget* parent, Anime::ListStatus _status) : QAbstractListModel(parent) {
 	status = _status;
 	return;
 }
 
-int AnimeListWidgetModel::rowCount(const QModelIndex& parent) const {
+int AnimeListPageModel::rowCount(const QModelIndex& parent) const {
 	return list.size();
 	(void)(parent);
 }
 
-int AnimeListWidgetModel::columnCount(const QModelIndex& parent) const {
+int AnimeListPageModel::columnCount(const QModelIndex& parent) const {
 	return NB_COLUMNS;
 	(void)(parent);
 }
 
-QVariant AnimeListWidgetModel::headerData(const int section, const Qt::Orientation orientation, const int role) const {
+QVariant AnimeListPageModel::headerData(const int section, const Qt::Orientation orientation, const int role) const {
 	if (role == Qt::DisplayRole) {
 		switch (section) {
 			case AL_TITLE: return tr("Anime title");
@@ -131,7 +131,7 @@
 	return QAbstractListModel::headerData(section, orientation, role);
 }
 
-QVariant AnimeListWidgetModel::data(const QModelIndex& index, int role) const {
+QVariant AnimeListPageModel::data(const QModelIndex& index, int role) const {
 	if (!index.isValid())
 		return QVariant();
 	switch (role) {
@@ -190,7 +190,7 @@
 	return QVariant();
 }
 
-void AnimeListWidgetModel::UpdateAnime(int id) {
+void AnimeListPageModel::UpdateAnime(int id) {
 	/* meh... it might be better to just reinit the entire list */
 	int i = 0;
 	for (const auto& a : Anime::db.items) {
@@ -201,11 +201,11 @@
 	}
 }
 
-Anime::Anime* AnimeListWidgetModel::GetAnimeFromIndex(QModelIndex index) {
+Anime::Anime* AnimeListPageModel::GetAnimeFromIndex(QModelIndex index) {
 	return &list.at(index.row());
 }
 
-void AnimeListWidgetModel::RefreshList() {
+void AnimeListPageModel::RefreshList() {
 	bool has_children = !!rowCount(index(0));
 	if (has_children)
 		beginResetModel();
@@ -231,7 +231,7 @@
 		endInsertRows();
 }
 
-int AnimeListWidget::VisibleColumnsCount() const {
+int AnimeListPage::VisibleColumnsCount() const {
 	int count = 0;
 
 	for (int i = 0, end = tree_view->header()->count(); i < end; i++) {
@@ -242,29 +242,29 @@
 	return count;
 }
 
-void AnimeListWidget::SetColumnDefaults() {
-	tree_view->setColumnHidden(AnimeListWidgetModel::AL_SEASON, false);
-	tree_view->setColumnHidden(AnimeListWidgetModel::AL_TYPE, false);
-	tree_view->setColumnHidden(AnimeListWidgetModel::AL_UPDATED, false);
-	tree_view->setColumnHidden(AnimeListWidgetModel::AL_PROGRESS, false);
-	tree_view->setColumnHidden(AnimeListWidgetModel::AL_SCORE, false);
-	tree_view->setColumnHidden(AnimeListWidgetModel::AL_TITLE, false);
-	tree_view->setColumnHidden(AnimeListWidgetModel::AL_EPISODES, true);
-	tree_view->setColumnHidden(AnimeListWidgetModel::AL_AVG_SCORE, true);
-	tree_view->setColumnHidden(AnimeListWidgetModel::AL_STARTED, true);
-	tree_view->setColumnHidden(AnimeListWidgetModel::AL_COMPLETED, true);
-	tree_view->setColumnHidden(AnimeListWidgetModel::AL_UPDATED, true);
-	tree_view->setColumnHidden(AnimeListWidgetModel::AL_NOTES, true);
+void AnimeListPage::SetColumnDefaults() {
+	tree_view->setColumnHidden(AnimeListPageModel::AL_SEASON, false);
+	tree_view->setColumnHidden(AnimeListPageModel::AL_TYPE, false);
+	tree_view->setColumnHidden(AnimeListPageModel::AL_UPDATED, false);
+	tree_view->setColumnHidden(AnimeListPageModel::AL_PROGRESS, false);
+	tree_view->setColumnHidden(AnimeListPageModel::AL_SCORE, false);
+	tree_view->setColumnHidden(AnimeListPageModel::AL_TITLE, false);
+	tree_view->setColumnHidden(AnimeListPageModel::AL_EPISODES, true);
+	tree_view->setColumnHidden(AnimeListPageModel::AL_AVG_SCORE, true);
+	tree_view->setColumnHidden(AnimeListPageModel::AL_STARTED, true);
+	tree_view->setColumnHidden(AnimeListPageModel::AL_COMPLETED, true);
+	tree_view->setColumnHidden(AnimeListPageModel::AL_UPDATED, true);
+	tree_view->setColumnHidden(AnimeListPageModel::AL_NOTES, true);
 }
 
-void AnimeListWidget::DisplayColumnHeaderMenu() {
+void AnimeListPage::DisplayColumnHeaderMenu() {
 	QMenu* menu = new QMenu(this);
 	menu->setAttribute(Qt::WA_DeleteOnClose);
 	menu->setTitle(tr("Column visibility"));
 	menu->setToolTipsVisible(true);
 
-	for (int i = 0; i < AnimeListWidgetModel::NB_COLUMNS; i++) {
-		if (i == AnimeListWidgetModel::AL_TITLE)
+	for (int i = 0; i < AnimeListPageModel::NB_COLUMNS; i++) {
+		if (i == AnimeListPageModel::AL_TITLE)
 			continue;
 		const auto column_name =
 		    sort_models[tab_bar->currentIndex()]->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString();
@@ -294,7 +294,7 @@
 	(void)(resetAction);
 }
 
-void AnimeListWidget::DisplayListMenu() {
+void AnimeListPage::DisplayListMenu() {
 	QMenu* menu = new QMenu(this);
 	menu->setAttribute(Qt::WA_DeleteOnClose);
 	menu->setTitle(tr("Column visibility"));
@@ -307,10 +307,10 @@
 	}
 
 	QAction* action = menu->addAction(tr("Information"), [this, selection] {
-		const QModelIndex index = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())
-		                              ->index(selection.indexes().first().row());
-		Anime::Anime* anime =
-		    ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->GetAnimeFromIndex(index);
+		AnimeListPageModel* source_model =
+		    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);
 		if (!anime) {
 			return;
 		}
@@ -330,7 +330,7 @@
 	menu->popup(QCursor::pos());
 }
 
-void AnimeListWidget::ItemDoubleClicked() {
+void AnimeListPage::ItemDoubleClicked() {
 	/* throw out any other garbage */
 	const QItemSelection selection =
 	    sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection());
@@ -338,10 +338,11 @@
 		return;
 	}
 
-	const QModelIndex index = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())
-	                              ->index(selection.indexes().first().row());
-	Anime::Anime* anime =
-	    ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->GetAnimeFromIndex(index);
+	AnimeListPageModel* source_model =
+	    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,
@@ -356,7 +357,7 @@
 	dialog->activateWindow();
 }
 
-void AnimeListWidget::paintEvent(QPaintEvent*) {
+void AnimeListPage::paintEvent(QPaintEvent*) {
 	QStylePainter p(this);
 
 	QStyleOptionTabWidgetFrame opt;
@@ -365,16 +366,16 @@
 	p.drawPrimitive(QStyle::PE_FrameTabWidget, opt);
 }
 
-void AnimeListWidget::resizeEvent(QResizeEvent* e) {
+void AnimeListPage::resizeEvent(QResizeEvent* e) {
 	QWidget::resizeEvent(e);
 	SetupLayout();
 }
 
-void AnimeListWidget::showEvent(QShowEvent*) {
+void AnimeListPage::showEvent(QShowEvent*) {
 	SetupLayout();
 }
 
-void AnimeListWidget::InitBasicStyle(QStyleOptionTabWidgetFrame* option) const {
+void AnimeListPage::InitBasicStyle(QStyleOptionTabWidgetFrame* option) const {
 	if (!option)
 		return;
 
@@ -384,7 +385,7 @@
 	option->tabBarRect = tab_bar->geometry();
 }
 
-void AnimeListWidget::InitStyle(QStyleOptionTabWidgetFrame* option) const {
+void AnimeListPage::InitStyle(QStyleOptionTabWidgetFrame* option) const {
 	if (!option)
 		return;
 
@@ -406,7 +407,7 @@
 	option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, this);
 }
 
-void AnimeListWidget::SetupLayout() {
+void AnimeListPage::SetupLayout() {
 	QStyleOptionTabWidgetFrame option;
 	InitStyle(&option);
 
@@ -419,7 +420,7 @@
 	tree_view->parentWidget()->setGeometry(contentsRect);
 }
 
-AnimeListWidget::AnimeListWidget(QWidget* parent) : QWidget(parent) {
+AnimeListPage::AnimeListPage(QWidget* parent) : QWidget(parent) {
 	/* Tab bar */
 	tab_bar = new QTabBar(this);
 	tab_bar->setExpanding(false);
@@ -428,7 +429,7 @@
 	/* Tree view... */
 	QWidget* tree_widget = new QWidget(this);
 	tree_view = new QTreeView(tree_widget);
-	tree_view->setItemDelegate(new AnimeListWidgetDelegate(tree_view));
+	tree_view->setItemDelegate(new AnimeListPageDelegate(tree_view));
 	tree_view->setUniformRowHeights(true);
 	tree_view->setAllColumnsShowFocus(false);
 	tree_view->setAlternatingRowColors(true);
@@ -442,8 +443,8 @@
 	for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++) {
 		tab_bar->addTab(QString::fromStdString(Translate::ToString(Anime::ListStatuses[i])) + " (" +
 		                QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")");
-		sort_models[i] = new AnimeListWidgetSortFilter(tree_view);
-		sort_models[i]->setSourceModel(new AnimeListWidgetModel(this, 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);
 		sort_models[i]->setSortCaseSensitivity(Qt::CaseInsensitive);
 	}
@@ -455,19 +456,19 @@
 	tree_widget->setLayout(layout);
 
 	/* Double click stuff */
-	connect(tree_view, &QAbstractItemView::doubleClicked, this, &AnimeListWidget::ItemDoubleClicked);
-	connect(tree_view, &QWidget::customContextMenuRequested, this, &AnimeListWidget::DisplayListMenu);
+	connect(tree_view, &QAbstractItemView::doubleClicked, this, &AnimeListPage::ItemDoubleClicked);
+	connect(tree_view, &QWidget::customContextMenuRequested, this, &AnimeListPage::DisplayListMenu);
 
 	/* Enter & return keys */
 	connect(new QShortcut(Qt::Key_Return, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this,
-	        &AnimeListWidget::ItemDoubleClicked);
+	        &AnimeListPage::ItemDoubleClicked);
 
 	connect(new QShortcut(Qt::Key_Enter, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this,
-	        &AnimeListWidget::ItemDoubleClicked);
+	        &AnimeListPage::ItemDoubleClicked);
 
 	tree_view->header()->setStretchLastSection(false);
 	tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu);
-	connect(tree_view->header(), &QWidget::customContextMenuRequested, this, &AnimeListWidget::DisplayColumnHeaderMenu);
+	connect(tree_view->header(), &QWidget::customContextMenuRequested, this, &AnimeListPage::DisplayColumnHeaderMenu);
 
 	connect(tab_bar, &QTabBar::currentChanged, this, [this](int index) {
 		if (sort_models[index])
@@ -478,18 +479,18 @@
 	setFocusProxy(tab_bar);
 }
 
-void AnimeListWidget::RefreshList() {
+void AnimeListPage::RefreshList() {
 	for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++)
-		((AnimeListWidgetModel*)sort_models[i]->sourceModel())->RefreshList();
+		reinterpret_cast<AnimeListPageModel*>(sort_models[i]->sourceModel())->RefreshList();
 }
 
-void AnimeListWidget::RefreshTabs() {
+void AnimeListPage::RefreshTabs() {
 	for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++)
 		tab_bar->setTabText(i, QString::fromStdString(Translate::ToString(Anime::ListStatuses[i])) + " (" +
 		                           QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")");
 }
 
-void AnimeListWidget::Refresh() {
+void AnimeListPage::Refresh() {
 	RefreshList();
 	RefreshTabs();
 }
@@ -497,7 +498,7 @@
 /* This function, really, really should not be called.
    Ever. Why would you ever need to clear the anime list?
    Also, this sucks. */
-void AnimeListWidget::Reset() {
+void AnimeListPage::Reset() {
 	while (tab_bar->count())
 		tab_bar->removeTab(0);
 	for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++)
--- a/src/gui/pages/history.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/gui/pages/history.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -1,6 +1,6 @@
 #include "gui/pages/history.h"
 
-HistoryWidget::HistoryWidget(QWidget* parent) : QWidget(parent) {
+HistoryPage::HistoryPage(QWidget* parent) : QWidget(parent) {
 }
 
 #include "gui/pages/moc_history.cpp"
--- a/src/gui/pages/now_playing.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/gui/pages/now_playing.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -1,6 +1,83 @@
 #include "gui/pages/now_playing.h"
+#include "core/anime_db.h"
+#include "gui/widgets/anime_info.h"
+#include "gui/widgets/text.h"
+#include <QLabel>
+#include <QStackedWidget>
+#include <QVBoxLayout>
+#include <QWidget>
+
+namespace NowPlayingPages {
+
+class Default : public QWidget {
+		Q_OBJECT
+
+	public:
+		Default(QWidget* parent = nullptr);
+};
+
+class Playing : public QWidget {
+		Q_OBJECT
+
+	public:
+		Playing(QWidget* parent = nullptr);
+
+		void SetPlayingAnime(int id) {
+			if (info.get())
+				layout()->removeWidget(info.get());
+			info.reset(new AnimeInfoWidget(Anime::db.items[id]));
+			layout()->addWidget(info.get());
+		}
+
+	private:
+		std::unique_ptr<AnimeInfoWidget> info = nullptr;
+};
+
+Default::Default(QWidget* parent) : QWidget(parent) {
+	QVBoxLayout* layout = new QVBoxLayout(this);
+	layout->setContentsMargins(0, 0, 0, 0);
 
-NowPlayingWidget::NowPlayingWidget(QWidget* parent) : QWidget(parent) {
+	layout->addStretch();
+}
+
+Playing::Playing(QWidget* parent) : QWidget(parent) {
+	QVBoxLayout* layout = new QVBoxLayout(this);
+	layout->setContentsMargins(0, 0, 0, 0);
+}
+
+} // namespace NowPlayingPages
+
+NowPlayingPage::NowPlayingPage(QWidget* parent) : QFrame(parent) {
+	QVBoxLayout* layout = new QVBoxLayout(this);
+
+	setFrameShape(QFrame::Box);
+	setFrameShadow(QFrame::Sunken);
+
+	QPalette pal = QPalette();
+	pal.setColor(QPalette::Window, pal.color(QPalette::Base));
+	setPalette(pal);
+	setAutoFillBackground(true);
+
+	TextWidgets::Title* title = new TextWidgets::Title(tr("Now Playing"), this);
+	layout->addWidget(title);
+
+	stack = new QStackedWidget(this);
+	stack->addWidget(new NowPlayingPages::Default(stack));
+	stack->addWidget(new NowPlayingPages::Playing(stack));
+	layout->addWidget(stack);
+
+	layout->addStretch();
+	SetDefault();
+}
+
+void NowPlayingPage::SetDefault() {
+	stack->setCurrentIndex(0);
+}
+
+void NowPlayingPage::SetPlaying(int id) {
+	reinterpret_cast<NowPlayingPages::Playing*>(stack->widget(1))->SetPlayingAnime(id);
+	stack->setCurrentIndex(1);
 }
 
 #include "gui/pages/moc_now_playing.cpp"
+#include "now_playing.moc"
--- a/src/gui/pages/search.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/gui/pages/search.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -1,6 +1,6 @@
 #include "gui/pages/search.h"
 
-SearchWidget::SearchWidget(QWidget* parent) : QWidget(parent) {
+SearchPage::SearchPage(QWidget* parent) : QWidget(parent) {
 }
 
 #include "gui/pages/moc_search.cpp"
--- a/src/gui/pages/seasons.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/gui/pages/seasons.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -1,6 +1,6 @@
 #include "gui/pages/seasons.h"
 
-SeasonsWidget::SeasonsWidget(QWidget* parent) : QWidget(parent) {
+SeasonsPage::SeasonsPage(QWidget* parent) : QWidget(parent) {
 }
 
 #include "gui/pages/moc_seasons.cpp"
--- a/src/gui/pages/statistics.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/gui/pages/statistics.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -11,10 +11,10 @@
 #include <QWidget>
 #include <sstream>
 
-StatisticsWidget::StatisticsWidget(QWidget* parent) : QFrame(parent) {
-	setLayout(new QVBoxLayout);
+StatisticsPage::StatisticsPage(QWidget* parent) : QFrame(parent) {
+	QVBoxLayout* layout = new QVBoxLayout(this);
 
-	setFrameShape(QFrame::Panel);
+	setFrameShape(QFrame::Box);
 	setFrameShadow(QFrame::Sunken);
 
 	QPalette pal = QPalette();
@@ -32,9 +32,9 @@
 	    new TextWidgets::LabelledTextParagraph(tr("Minori"), tr("Uptime:\nRequests made:"), "\n\n", this);
 	application_data = application_paragraph->GetParagraph();
 
-	layout()->addWidget(anime_list_paragraph);
-	layout()->addWidget(application_paragraph);
-	((QBoxLayout*)layout())->addStretch();
+	layout->addWidget(anime_list_paragraph);
+	layout->addWidget(application_paragraph);
+	layout->addStretch();
 
 	QTimer* timer = new QTimer(this);
 	connect(timer, &QTimer::timeout, this, [this] {
@@ -44,17 +44,15 @@
 	timer->start(1000); // update statistics every second
 }
 
-void StatisticsWidget::showEvent(QShowEvent*) {
+void StatisticsPage::showEvent(QShowEvent*) {
 	UpdateStatistics();
 }
 
 /* me abusing macros :) */
 #define ADD_TIME_SEGMENT(r, x, s, p) \
-	{ \
-		if (x > 0) \
-			r << x << ((x == 1) ? s : p); \
-	}
-std::string StatisticsWidget::MinutesToDateString(int minutes) {
+	if (x > 0) \
+	r << x << ((x == 1) ? s : p)
+std::string StatisticsPage::MinutesToDateString(int minutes) {
 	/* ew */
 	int years = (minutes * (1 / 525949.2F));
 	int months = (minutes * (1 / 43829.1F)) - (years * 12);
@@ -71,7 +69,7 @@
 	return return_stream.str();
 }
 
-std::string StatisticsWidget::SecondsToDateString(int sec) {
+std::string StatisticsPage::SecondsToDateString(int sec) {
 	/* this is all fairly unnecessary, but works:tm: */
 	int years = sec * (1 / 31556952.0F);
 	int months = sec * (1 / 2629746.0F) - (years * 12);
@@ -92,7 +90,7 @@
 }
 #undef ADD_TIME_SEGMENT
 
-void StatisticsWidget::UpdateStatistics() {
+void StatisticsPage::UpdateStatistics() {
 	/* Anime list */
 	QString string = "";
 	QTextStream ts(&string);
@@ -102,14 +100,14 @@
 	ts << MinutesToDateString(Anime::db.GetTotalPlannedAmount()).c_str() << '\n';
 	ts << Anime::db.GetAverageScore() << '\n';
 	ts << Anime::db.GetScoreDeviation();
-	TextWidgets::SetPlainTextEditData(anime_list_data, string);
+	anime_list_data->SetText(string);
 
 	string = "";
 	ts << QString::fromUtf8(SecondsToDateString(session.uptime() / 1000).c_str()) << '\n';
 	ts << session.GetRequests();
 	/* Application */
 	// UiUtils::SetPlainTextEditData(application_data, QString::number(session.uptime() / 1000));
-	TextWidgets::SetPlainTextEditData(application_data, string);
+	application_data->SetText(string);
 }
 
 #include "gui/pages/moc_statistics.cpp"
--- a/src/gui/pages/torrents.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/gui/pages/torrents.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -1,6 +1,6 @@
 #include "gui/pages/torrents.h"
 
-TorrentsWidget::TorrentsWidget(QWidget* parent) : QWidget(parent) {
+TorrentsPage::TorrentsPage(QWidget* parent) : QWidget(parent) {
 }
 
 #include "gui/pages/moc_torrents.cpp"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui/widgets/anime_info.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -0,0 +1,38 @@
+#include "gui/widgets/anime_info.h"
+#include "core/anime.h"
+#include "core/strings.h"
+#include "gui/translate/anime.h"
+#include "gui/widgets/text.h"
+#include <QHBoxLayout>
+#include <QTextStream>
+
+AnimeInfoWidget::AnimeInfoWidget(const Anime::Anime& anime, QWidget* parent) : QWidget(parent) {
+	QVBoxLayout* layout = new QVBoxLayout(this);
+
+	/* alt titles */
+	TextWidgets::SelectableTextParagraph* title = new TextWidgets::SelectableTextParagraph(
+	    tr("Alternative titles"), QString::fromUtf8(Strings::Implode(anime.GetTitleSynonyms(), ", ").c_str()), this);
+	title->GetParagraph()->setWordWrapMode(QTextOption::NoWrap);
+	layout->addWidget(title);
+
+	/* details */
+	QString details_data;
+	QTextStream details_data_s(&details_data);
+	details_data_s << Translate::ToString(anime.GetFormat()).c_str() << "\n"
+	               << anime.GetEpisodes() << "\n"
+	               << Translate::ToString(anime.GetUserStatus()).c_str() << "\n"
+	               << Translate::ToString(anime.GetSeason()).c_str() << " " << anime.GetAirDate().GetYear() << "\n"
+	               << Strings::Implode(anime.GetGenres(), ", ").c_str() << "\n"
+	               << anime.GetAudienceScore() << "%";
+	layout->addWidget(new TextWidgets::LabelledTextParagraph(
+	    tr("Details"), tr("Type:\nEpisodes:\nStatus:\nSeason:\nGenres:\nScore:"), details_data, this));
+
+	/* synopsis */
+	TextWidgets::SelectableTextParagraph* synopsis =
+	    new TextWidgets::SelectableTextParagraph(tr("Synopsis"), QString::fromUtf8(anime.GetSynopsis().c_str()), this);
+
+	synopsis->GetParagraph()->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
+	layout->addWidget(synopsis);
+}
+
+#include "gui/widgets/moc_anime_info.cpp"
--- a/src/gui/widgets/text.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/gui/widgets/text.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -10,7 +10,7 @@
 namespace TextWidgets {
 
 Header::Header(QString title, QWidget* parent) : QWidget(parent) {
-	setLayout(new QVBoxLayout);
+	QVBoxLayout* layout = new QVBoxLayout(this);
 	setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
 
 	static_text_title = new QLabel(title, this);
@@ -18,6 +18,7 @@
 	QFont font = static_text_title->font();
 	font.setWeight(QFont::Bold);
 	static_text_title->setFont(font);
+	/* FIXME: is this needed? */
 	static_text_title->setFixedHeight(16);
 
 	static_text_line = new QFrame(this);
@@ -25,38 +26,38 @@
 	static_text_line->setFrameShadow(QFrame::Sunken);
 	static_text_line->setFixedHeight(2);
 
-	layout()->addWidget(static_text_title);
-	layout()->addWidget(static_text_line);
-	layout()->setSpacing(0);
-	layout()->setContentsMargins(0, 0, 0, 0);
+	layout->addWidget(static_text_title);
+	layout->addWidget(static_text_line);
+	layout->setSpacing(0);
+	layout->setContentsMargins(0, 0, 0, 0);
 }
 
-void Header::SetTitle(QString title) {
-	static_text_title->setText(title);
+void Header::SetText(QString text) {
+	static_text_title->setText(text);
 }
 
 TextParagraph::TextParagraph(QString title, QString data, QWidget* parent) : QWidget(parent) {
-	setLayout(new QVBoxLayout);
+	QVBoxLayout* layout = new QVBoxLayout(this);
 
 	header = new Header(title, this);
 
 	QWidget* content = new QWidget(this);
-	content->setLayout(new QHBoxLayout);
+	QHBoxLayout* content_layout = new QHBoxLayout(content);
 
 	paragraph = new Paragraph(data, this);
 	paragraph->setTextInteractionFlags(Qt::NoTextInteraction);
 	paragraph->setAttribute(Qt::WidgetAttribute::WA_TransparentForMouseEvents);
 	paragraph->setWordWrapMode(QTextOption::NoWrap);
 
-	content->layout()->addWidget(paragraph);
-	content->layout()->setSpacing(0);
-	content->layout()->setContentsMargins(0, 0, 0, 0);
+	content_layout->addWidget(paragraph);
+	content_layout->setSpacing(0);
+	content_layout->setContentsMargins(0, 0, 0, 0);
 	content->setContentsMargins(12, 0, 0, 0);
 
-	layout()->addWidget(header);
-	layout()->addWidget(paragraph);
-	layout()->setSpacing(0);
-	layout()->setContentsMargins(0, 0, 0, 0);
+	layout->addWidget(header);
+	layout->addWidget(paragraph);
+	layout->setSpacing(0);
+	layout->setContentsMargins(0, 0, 0, 0);
 }
 
 Header* TextParagraph::GetHeader() {
@@ -69,7 +70,7 @@
 
 LabelledTextParagraph::LabelledTextParagraph(QString title, QString label, QString data, QWidget* parent)
     : QWidget(parent) {
-	setLayout(new QVBoxLayout);
+	QVBoxLayout* layout = new QVBoxLayout(this);
 
 	header = new Header(title, this);
 
@@ -97,10 +98,10 @@
 
 	content->setContentsMargins(12, 0, 0, 0);
 
-	layout()->addWidget(header);
-	layout()->addWidget(content);
-	layout()->setSpacing(0);
-	layout()->setContentsMargins(0, 0, 0, 0);
+	layout->addWidget(header);
+	layout->addWidget(content);
+	layout->setSpacing(0);
+	layout->setContentsMargins(0, 0, 0, 0);
 }
 
 Header* LabelledTextParagraph::GetHeader() {
@@ -116,24 +117,24 @@
 }
 
 SelectableTextParagraph::SelectableTextParagraph(QString title, QString data, QWidget* parent) : QWidget(parent) {
-	setLayout(new QVBoxLayout);
+	QVBoxLayout* layout = new QVBoxLayout(this);
 
 	header = new Header(title, this);
 
 	QWidget* content = new QWidget(this);
-	content->setLayout(new QHBoxLayout);
+	QHBoxLayout* content_layout = new QHBoxLayout(content);
 
 	paragraph = new Paragraph(data, content);
 
-	content->layout()->addWidget(paragraph);
-	content->layout()->setSpacing(0);
-	content->layout()->setContentsMargins(0, 0, 0, 0);
+	content_layout->addWidget(paragraph);
+	content_layout->setSpacing(0);
+	content_layout->setContentsMargins(0, 0, 0, 0);
 	content->setContentsMargins(12, 0, 0, 0);
 
-	layout()->addWidget(header);
-	layout()->addWidget(content);
-	layout()->setSpacing(0);
-	layout()->setContentsMargins(0, 0, 0, 0);
+	layout->addWidget(header);
+	layout->addWidget(content);
+	layout->setSpacing(0);
+	layout->setContentsMargins(0, 0, 0, 0);
 }
 
 Header* SelectableTextParagraph::GetHeader() {
@@ -158,8 +159,19 @@
 	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
 }
 
+void Paragraph::SetText(QString text) {
+	QTextDocument* document = new QTextDocument(this);
+	document->setDocumentLayout(new QPlainTextDocumentLayout(document));
+	document->setPlainText(text);
+	setDocument(document);
+}
+
 /* highly based upon... some stackoverflow answer for PyQt */
 QSize Paragraph::minimumSizeHint() const {
+	return QSize(0, 0);
+}
+
+QSize Paragraph::sizeHint() const {
 	QTextDocument* doc = document();
 	doc->adjustSize();
 	long h = 0;
@@ -169,16 +181,23 @@
 	return QSize(doc->size().width(), h);
 }
 
-QSize Paragraph::sizeHint() const {
-	return minimumSizeHint();
-}
+Title::Title(QString title, QWidget* parent) : Paragraph(title, parent) {
+	setReadOnly(true);
+	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+	setWordWrapMode(QTextOption::NoWrap);
+	setFrameShape(QFrame::NoFrame);
+	setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
+	setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
 
-/* this is still actually useful, so we'll keep it */
-void SetPlainTextEditData(QPlainTextEdit* text_edit, QString data) {
-	QTextDocument* document = new QTextDocument(text_edit);
-	document->setDocumentLayout(new QPlainTextDocumentLayout(document));
-	document->setPlainText(data);
-	text_edit->setDocument(document);
+	QFont fnt(font());
+	fnt.setPointSize(12);
+	setFont(fnt);
+
+	QPalette pal(palette());
+	pal.setColor(QPalette::Window, Qt::transparent);
+	pal.setColor(QPalette::Text, QColor(0x00, 0x33, 0x99));
+	setPalette(pal);
 }
 
 } // namespace TextWidgets
--- a/src/gui/window.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/gui/window.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -1,6 +1,8 @@
 #include "gui/window.h"
+#include "core/anime_db.h"
 #include "core/config.h"
 #include "core/session.h"
+#include "core/strings.h"
 #include "gui/dark_theme.h"
 #include "gui/dialog/about.h"
 #include "gui/dialog/settings.h"
@@ -13,8 +15,10 @@
 #include "gui/pages/torrents.h"
 #include "gui/widgets/sidebar.h"
 #include "services/services.h"
+#include "track/media.h"
 #include <QActionGroup>
 #include <QApplication>
+#include <QDebug>
 #include <QFile>
 #include <QMainWindow>
 #include <QMenuBar>
@@ -58,16 +62,16 @@
 	sidebar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
 
 	QStackedWidget* stack = new QStackedWidget(main_widget);
-	stack->addWidget(new NowPlayingWidget(main_widget));
-	stack->addWidget(new AnimeListWidget(main_widget));
-	stack->addWidget(new HistoryWidget(main_widget));
-	stack->addWidget(new StatisticsWidget(main_widget));
-	stack->addWidget(new SearchWidget(main_widget));
-	stack->addWidget(new SeasonsWidget(main_widget));
-	stack->addWidget(new TorrentsWidget(main_widget));
+	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 SearchPage(main_widget));
+	stack->addWidget(new SeasonsPage(main_widget));
+	stack->addWidget(new TorrentsPage(main_widget));
 
 	connect(sidebar, &SideBar::CurrentItemChanged, stack, &QStackedWidget::setCurrentIndex);
-	sidebar->SetCurrentItem((int)Pages::ANIME_LIST);
+	sidebar->SetCurrentItem(static_cast<int>(Pages::ANIME_LIST));
 
 	/* Menu Bar */
 	QAction* action;
@@ -93,7 +97,7 @@
 	menu = menubar->addMenu(tr("&Services"));
 	action = menu->addAction(tr("Synchronize &list"), [stack] {
 		Services::Synchronize();
-		((AnimeListWidget*)stack->widget((int)Pages::ANIME_LIST))->Refresh();
+		reinterpret_cast<AnimeListPage*>(stack->widget(static_cast<int>(Pages::ANIME_LIST)))->Refresh();
 	});
 	action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_S));
 
@@ -181,7 +185,7 @@
 	menu->addAction(tr("Show sidebar"));
 
 	menu = menubar->addMenu(tr("&Help"));
-	action = menu->addAction(tr("About Minori"), this, [this] {
+	action = menu->addAction(tr("&About Minori"), this, [this] {
 		AboutWindow dialog(this);
 		dialog.exec();
 	});
@@ -195,6 +199,22 @@
 	layout->addWidget(stack);
 	setCentralWidget(main_widget);
 
+	QTimer* timer = new QTimer(this);
+	connect(timer, &QTimer::timeout, this, [stack] {
+		NowPlayingPage* page = reinterpret_cast<NowPlayingPage*>(stack->widget(static_cast<int>(Pages::NOW_PLAYING)));
+
+		Filesystem::Path p = Track::Media::GetCurrentPlaying();
+		std::string title = Track::Media::GetFileTitle(p);
+		int id = Anime::db.GetAnimeFromTitle(title);
+		if (id == 0) {
+			page->SetDefault();
+			return;
+		}
+		
+		page->SetPlaying(id);
+	});
+	timer->start(5000);
+
 	DarkTheme::SetTheme(session.config.theme);
 }
 
--- a/src/services/anilist.cpp	Sun Oct 01 06:39:47 2023 -0400
+++ b/src/services/anilist.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -6,7 +6,6 @@
 #include "core/session.h"
 #include "core/strings.h"
 #include "gui/translate/anilist.h"
-#include <QDebug>
 #include <QDesktopServices>
 #include <QInputDialog>
 #include <QLineEdit>
@@ -17,7 +16,7 @@
 #include <exception>
 #define CLIENT_ID "13706"
 
-using nlohmann::literals::operator"" _json_pointer;
+using namespace nlohmann::literals::json_literals;
 
 namespace Services {
 namespace AniList {
@@ -39,7 +38,7 @@
 static Account account;
 
 static size_t CurlWriteCallback(void* contents, size_t size, size_t nmemb, void* userdata) {
-	((std::string*)userdata)->append((char*)contents, size * nmemb);
+	reinterpret_cast<std::string*>(userdata)->append(reinterpret_cast<char*>(contents), size * nmemb);
 	return size * nmemb;
 }
 
@@ -117,6 +116,8 @@
 
 Date ParseDate(const nlohmann::json& json) {
 	Date date;
+	/* JSON for Modern C++ warns here. I'm not too sure why, this code works when I set the
+	   standard to C++17 :/ */
 	if (json.contains("/year"_json_pointer) && json.at("/year"_json_pointer).is_number())
 		date.SetYear(JSON::GetInt(json, "/year"_json_pointer));
 	else
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/track/media.cpp	Sun Oct 01 23:15:43 2023 -0400
@@ -0,0 +1,38 @@
+#include "track/media.h"
+#include "core/filesystem.h"
+#include "core/strings.h"
+#include "animia.h"
+#include "anitomy/anitomy.h"
+#include <string>
+#include <vector>
+
+namespace Track {
+namespace Media {
+
+Filesystem::Path GetCurrentPlaying() {
+	/* getting all open files */
+	std::vector<int> pids = Animia::get_all_pids();
+	for (int i : pids) {
+		if (Animia::get_process_name(i) == "mpc-hc64.exe") {
+			std::vector<std::string> files = Animia::filter_system_files(Animia::get_open_files(i));
+			for (std::string s : files) {
+				Filesystem::Path p(s);
+				if (p.Extension() == "mkv")
+					return p;
+			}
+		}
+	}
+	return Filesystem::Path();
+}
+
+std::string GetFileTitle(Filesystem::Path path) {
+	anitomy::Anitomy anitomy;
+	anitomy.Parse(Strings::ToWstring(path.Basename()));
+
+	const auto& elements = anitomy.elements();
+
+	return Strings::ToUtf8String(elements.get(anitomy::kElementAnimeTitle));
+}
+
+} // namespace Media
+} // namespace Track