# HG changeset patch # User Paper # Date 1705157021 18000 # Node ID 69f4768a820cef02a0444ae97b172284bd051882 # Parent 2f5a9247e501e39248470529f0d3c7cb2a102cae# Parent d030b30526d58e54c88bf5a8c0c436ffc4ddd5a5 chore: merge divergent branches diff -r 2f5a9247e501 -r 69f4768a820c CMakeLists.txt --- a/CMakeLists.txt Sat Jan 13 09:42:02 2024 -0500 +++ b/CMakeLists.txt Sat Jan 13 09:43:41 2024 -0500 @@ -89,6 +89,7 @@ src/gui/dialog/settings/services.cc src/gui/dialog/settings/torrents.cc src/gui/dialog/settings/recognition.cc + src/gui/dialog/settings/library.cc # Translate src/gui/translate/anime.cc diff -r 2f5a9247e501 -r 69f4768a820c dep/animia/include/animia/util.h --- a/dep/animia/include/animia/util.h Sat Jan 13 09:42:02 2024 -0500 +++ b/dep/animia/include/animia/util.h Sat Jan 13 09:43:41 2024 -0500 @@ -2,6 +2,7 @@ #define __animia__animia__util_h #include +#include namespace animia::internal::util { @@ -12,6 +13,14 @@ bool TrimLeft(std::string& str, const char* chars); bool TrimRight(std::string& str, const char* chars); +template::value, bool> = true> +T StringToInt(const std::string& str, T def = 0) { + std::istringstream s(str); + s >> std::noboolalpha >> def; + return def; +} + } // namespace animia::internal::util -#endif // __animia__animia__util_h \ No newline at end of file +#endif // __animia__animia__util_h diff -r 2f5a9247e501 -r 69f4768a820c dep/animia/src/win/wayland.cc --- a/dep/animia/src/win/wayland.cc Sat Jan 13 09:42:02 2024 -0500 +++ b/dep/animia/src/win/wayland.cc Sat Jan 13 09:43:41 2024 -0500 @@ -1,6 +1,7 @@ #include "animia/win/wayland.h" #include "animia.h" #include "animia/win.h" +#include "animia/util.h" #include #include @@ -90,9 +91,10 @@ reinterpret_cast(data)->text = title; } -static void ext_foreign_handle_handle_identifier(void*, ext_foreign_toplevel_handle_v1*, const char* identifier) { - if (identifier) - reinterpret_cast(data)->id = identifier; +static void ext_foreign_handle_handle_identifier(void* data, ext_foreign_toplevel_handle_v1* handle, const char* identifier) { + if (identifier) { + reinterpret_cast(data)->id = util::StringToInt(identifier, 0); + } } static void ext_foreign_handle_handle_done(void* data, struct ext_foreign_toplevel_handle_v1* handle) { diff -r 2f5a9247e501 -r 69f4768a820c include/core/config.h --- a/include/core/config.h Sat Jan 13 09:42:02 2024 -0500 +++ b/include/core/config.h Sat Jan 13 09:43:41 2024 -0500 @@ -9,6 +9,7 @@ #include #include +#include #include struct MediaPlayer { @@ -34,12 +35,9 @@ bool highlighted_anime_above_others; } anime_list; - /* these should preferably be in an - "auth" struct... */ struct { struct { std::string auth_token; - std::string username; int user_id; } anilist; } auth; @@ -54,7 +52,8 @@ } torrents; struct { - std::vector paths; + bool real_time_monitor; + std::set paths; } library; }; diff -r 2f5a9247e501 -r 69f4768a820c include/core/strings.h --- a/include/core/strings.h Sat Jan 13 09:42:02 2024 -0500 +++ b/include/core/strings.h Sat Jan 13 09:43:41 2024 -0500 @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -16,6 +17,7 @@ * into a string, separated by delimiters. */ std::string Implode(const std::vector& vector, const std::string& delimiter); +std::string Implode(const std::set& set, const std::string& delimiter); std::vector Split(const std::string &text, const std::string& delimiter); /* Substring removal functions */ diff -r 2f5a9247e501 -r 69f4768a820c include/gui/dialog/settings.h --- a/include/gui/dialog/settings.h Sat Jan 13 09:42:02 2024 -0500 +++ b/include/gui/dialog/settings.h Sat Jan 13 09:43:41 2024 -0500 @@ -86,6 +86,19 @@ decltype(session.config.recognition.players) players; }; +class SettingsPageLibrary final : public SettingsPage { + Q_OBJECT + + public: + SettingsPageLibrary(QWidget* parent = nullptr); + void SaveInfo() override; + + private: + QWidget* CreateFoldersWidget(); + decltype(session.config.library.paths) paths; + decltype(session.config.library.real_time_monitor) real_time_monitor; +}; + class SettingsDialog final : public QDialog { Q_OBJECT diff -r 2f5a9247e501 -r 69f4768a820c include/library/library.h --- a/include/library/library.h Sat Jan 13 09:42:02 2024 -0500 +++ b/include/library/library.h Sat Jan 13 09:43:41 2024 -0500 @@ -6,9 +6,13 @@ #include #include +namespace Library { + // int = anime id, map = episode, paths extern std::unordered_map> library; void SearchLibraryFolders(); -#endif // __library__library_h \ No newline at end of file +} + +#endif // __library__library_h diff -r 2f5a9247e501 -r 69f4768a820c rc/locale/en_GB.ts --- a/rc/locale/en_GB.ts Sat Jan 13 09:42:02 2024 -0500 +++ b/rc/locale/en_GB.ts Sat Jan 13 09:43:41 2024 -0500 @@ -4,10 +4,50 @@ AboutWindow - + About Minori + + + Author: + + + + + Third party components: + + + + + Special thanks: + + + + + for creating Taiga + + + + + and + + + + + for creating BreezeStyleSheets, on which the dark theme in this program is based off of + + + + + for providing some sample code for detecting dark mode on Windows and macOS + + + + + for providing information on getting open file descriptors on macOS + + AnimeInfoWidget @@ -40,28 +80,28 @@ AnimeListPage - - + + Column visibility - + Reset to defaults - + Information - + Edit - + Delete from list... @@ -190,37 +230,37 @@ MainWindow - + Now Playing - + Anime List - + History - + Statistics - + Search - + Seasons - + Torrents @@ -235,38 +275,37 @@ - + &Add new folder... - + + Open Directory + + + + &Scan available episodes - - - ... - - - - + E&xit - + &Services - + Synchronize &list Synchronise &list - + &Tools @@ -275,87 +314,87 @@ Enable &auto synchronisation - + &Settings - + &View - + &Now Playing - + &Anime List - + &History - + &Statistics - + S&earch - + Se&asons - + &Torrents - + &Help - - &About Minori - - - - - About &Qt - - - + &About Minori + + + + + About &Qt + + + + &Synchronize &Synchronise - + S&ettings - + Error synchronizing with service! - + It seems you haven't yet selected a service to use. - + Would you like to select one now? @@ -371,212 +410,172 @@ QCoreApplication - + Currently watching - - Plan to watch - - - - - Completed - - - - - Dropped - - - + Plan to watch + + + + + Completed + + + + + Dropped + + + + On hold - + Not in list - + TV - - TV short - - - - - OVA - - - + TV short + + + + + OVA + + + + Special - + ONA - + Music - - - + + + Unknown - + Winter - + Summer - + 3-point - + 5-point - + 10-point - + 10-point (Decimal) - + 100-point - + Movie - + Fall - + Spring - + Currently airing - + Finished airing - + Not yet aired - + Cancelled - + On hiatus - + AniList - + None - + Native - + English - + Romaji - - - Author: - - - - - Third party components: - - - - - Special thanks: - - - - - for creating Taiga - - - - - and - - - - - for creating BreezeStyleSheets, on which the dark theme in this program is based off of - - - - - for providing some sample code for detecting dark mode on Windows and macOS - - - - - for providing information on getting open file descriptors on macOS - - SettingsDialog @@ -872,22 +871,22 @@ TorrentsPage - + &Check new torrents - + Download &marked torrents - + &Discard all - + &Settings @@ -895,57 +894,57 @@ TorrentsPageListModel - + Anime title - - Episode - - - - - Group - - - - - Size - - - - - Resolution - - - - Seeding + Episode - Leeching + Group - Downloading + Size - Description + Resolution + Seeding + + + + + Leeching + + + + + Downloading + + + + + Description + + + + Filename - + Release date diff -r 2f5a9247e501 -r 69f4768a820c rc/locale/es.ts --- a/rc/locale/es.ts Sat Jan 13 09:42:02 2024 -0500 +++ b/rc/locale/es.ts Sat Jan 13 09:43:41 2024 -0500 @@ -4,10 +4,50 @@ AboutWindow - + About Minori Acerca de Minori + + + Author: + Autor: + + + + Third party components: + Componentes de terceros: + + + + Special thanks: + Agradecimientos especiales: + + + + for creating Taiga + para crear Taiga + + + + and + y + + + + for creating BreezeStyleSheets, on which the dark theme in this program is based off of + para crear BreezeStyleSheets, en el que se basa el tema oscuro de este programa + + + + for providing some sample code for detecting dark mode on Windows and macOS + por proporcionar algunos ejemplos de código para detectar el modo oscuro en Windows y macOS + + + + for providing information on getting open file descriptors on macOS + para obtener información sobre descriptores de archivos abiertos en macOS + AnimeInfoWidget @@ -45,28 +85,28 @@ AnimeListPage - - + + Column visibility Visibilidad de la columna - + Reset to defaults Restablecer valores predeterminado - + Information Información - + Edit - + Delete from list... Borrar de la lista... @@ -195,37 +235,37 @@ MainWindow - + Now Playing Jugando ahora - + Anime List Lista de anime - + History Historia - + Statistics Estadísticas - + Search Buscar - + Seasons Temporadas - + Torrents @@ -240,20 +280,19 @@ Directorios de la biblioteca (&L) - + &Add new folder... &Añadir una nueva directorios... - - &Scan available episodes - E&scanear episodios disponibles + + Open Directory + - - - ... - + + &Scan available episodes + E&scanear episodios disponibles Play &next episode @@ -264,17 +303,17 @@ Ver episodio aleato&rio - + E&xit Salida (&X) - + &Services &Servicios - + Synchronize &list Sincronizar &lista @@ -315,7 +354,7 @@ Ir a mi &historia - + &Tools Ins&trumentos @@ -344,47 +383,47 @@ Activar la sincronización &automática - + &Settings Configuración (&S) - + &View &Ver - + &Now Playing Jugando ahora (&N) - + &Anime List Lista de &anime - + &History &Historia - + &Statistics E&stadísticas - + S&earch Buscar (&S) - + Se&asons Tempor&adas - + &Torrents @@ -393,22 +432,22 @@ Mostrar barra lateral - + &Help Ayuda (&H) - + &About Minori &Acerca de Minori - + About &Qt Acerca de &Qt - + &Synchronize &Sincronice @@ -421,22 +460,22 @@ Marcador de posición - + S&ettings Configuración (&S) - + Error synchronizing with service! Error al sincronizar con el servicio! - + It seems you haven't yet selected a service to use. Parece que aún no has seleccionado un servicio para usar. - + Would you like to select one now? ¿Quieres seleccionar uno ahora? @@ -452,211 +491,203 @@ QCoreApplication - + Currently watching Actualmente viendo - + Plan to watch Plan para ver - + Completed Completado - + Dropped Dejado - + On hold En espera - + Not in list No en la lista - + TV TV - + TV short Corto - + OVA OVA - + Movie Película - + Special Especial - + ONA ONA - + Music Música - - - + + + Unknown Desconocido - + Winter Invierno - + Summer Verano - + Fall Otoño - + Spring Primavera - + Currently airing En emisión - + Finished airing Emisión terminada - + Not yet aired Aún no emitido - + Cancelled Cancelado - + On hiatus En pausa - + AniList AniList - + None Ninguno - + Native Nativo - + English Inglés - + Romaji Romaji - + 3-point - + 5-point - + 10-point - + 10-point (Decimal) - + 100-point - Author: - Autor: + Autor: - Third party components: - Componentes de terceros: + Componentes de terceros: - Special thanks: - Agradecimientos especiales: + Agradecimientos especiales: - for creating Taiga - para crear Taiga + para crear Taiga - and - y + y - for creating BreezeStyleSheets, on which the dark theme in this program is based off of - para crear BreezeStyleSheets, en el que se basa el tema oscuro de este programa + para crear BreezeStyleSheets, en el que se basa el tema oscuro de este programa - for providing some sample code for detecting dark mode on Windows and macOS - por proporcionar algunos ejemplos de código para detectar el modo oscuro en Windows y macOS + por proporcionar algunos ejemplos de código para detectar el modo oscuro en Windows y macOS - for providing information on getting open file descriptors on macOS - para obtener información sobre descriptores de archivos abiertos en macOS + para obtener información sobre descriptores de archivos abiertos en macOS @@ -967,22 +998,22 @@ TorrentsPage - + &Check new torrents &Comprueba nuevos torrentes - + Download &marked torrents Descargar torrents &marcados - + &Discard all &Descartar todo - + &Settings Configuración (&S) @@ -990,57 +1021,57 @@ TorrentsPageListModel - + Anime title Título del anime - + Episode Episodio - + Group Grupo - + Size Tamaño del archivo - + Resolution Resolución - + Seeding Siembra - + Leeching Sanguijuela - + Downloading Descargando - + Description Descripción - + Filename Nombre del archivo - + Release date Fecha de lanzamiento diff -r 2f5a9247e501 -r 69f4768a820c src/core/config.cc --- a/src/core/config.cc Sat Jan 13 09:42:02 2024 -0500 +++ b/src/core/config.cc Sat Jan 13 09:43:41 2024 -0500 @@ -25,12 +25,15 @@ /* I'll use an INI-based config file instead of using an * XML file like Taiga. + * + * It technically isn't to spec, because I'm making these case-sensitive. + * Boohoo. */ int Config::Load() { std::filesystem::path cfg_path = Filesystem::GetConfigPath(); - mINI::INIFile file(cfg_path.string()); + mINI::INIFile file(cfg_path.u8string()); mINI::INIStructure ini; file.read(ini); @@ -90,6 +93,14 @@ theme.SetTheme(Translate::ToTheme(INI::GetIniValue(ini, "Appearance", "Theme", "Default"))); + { + std::vector v = Strings::Split(INI::GetIniValue(ini, "Library", "Folders", ""), ";"); + library.paths = std::set(std::make_move_iterator(v.begin()), + std::make_move_iterator(v.end())); + } + + library.real_time_monitor = INI::GetIniValue(ini, "Library", "Real-time monitor", true); + return 0; } @@ -131,6 +142,9 @@ } } + INI::SetIniValue(ini, "Library", "Folders", Strings::Implode(library.paths, ";")); + INI::SetIniValue(ini, "Library", "Real-time monitor", library.real_time_monitor); + file.write(ini); return 0; diff -r 2f5a9247e501 -r 69f4768a820c src/core/strings.cc --- a/src/core/strings.cc Sat Jan 13 09:42:02 2024 -0500 +++ b/src/core/strings.cc Sat Jan 13 09:43:41 2024 -0500 @@ -36,6 +36,21 @@ return out; } +std::string Implode(const std::set& set, const std::string& delimiter) { + if (set.size() < 1) + return "-"; + + std::string out; + + for (auto it = set.cbegin(); it != set.cend(); it++) { + out.append(*it); + if (it != std::prev(set.cend(), 1)) + out.append(delimiter); + } + + return out; +} + std::vector Split(const std::string &text, const std::string& delimiter) { std::vector tokens; diff -r 2f5a9247e501 -r 69f4768a820c src/gui/dialog/about.cc --- a/src/gui/dialog/about.cc Sat Jan 13 09:42:02 2024 -0500 +++ b/src/gui/dialog/about.cc Sat Jan 13 09:43:41 2024 -0500 @@ -49,11 +49,11 @@ "" "

Minori v" + Strings::ToQString(session.version.to_string()) + "

" "

" - " " + QCoreApplication::tr("Author:") + "
" + " " + tr("Author:") + "
" " Paper (@mrpapersonic)" "

" "

" - " " + QCoreApplication::tr("Third party components:") + "
" + " " + tr("Third party components:") + "
" "libcurl v") + get_curl_version() + "" ", " "Fugue Icons v3.5.6" @@ -69,16 +69,16 @@ "mINI v0.9.14" "

" "" - "" + QCoreApplication::tr("Special thanks:") + "" + "" + tr("Special thanks:") + "" "" "
    " - "
  • Eren Okka " + QCoreApplication::tr("for creating Taiga") + "
  • " - "
  • Alex Huszagh " + QCoreApplication::tr("and") + " Colin Duquesnoy " + - QCoreApplication::tr("for creating BreezeStyleSheets, on which the dark theme in this program is " + "
  • Eren Okka " + tr("for creating Taiga") + "
  • " + "
  • Alex Huszagh " + tr("and") + " Colin Duquesnoy " + + tr("for creating BreezeStyleSheets, on which the dark theme in this program is " "based off of") + "
  • " - "
  • Andy Brice " + QCoreApplication::tr("for providing some sample code for " + "
  • Andy Brice " + tr("for providing some sample code for " "detecting dark mode on Windows and macOS") + "
  • " - "
  • Manuel Wudka-Robles " + QCoreApplication::tr("for providing information on " + "
  • Manuel Wudka-Robles " + tr("for providing information on " "getting open file descriptors on macOS") + "
  • " "
" ""; diff -r 2f5a9247e501 -r 69f4768a820c src/gui/dialog/settings.cc --- a/src/gui/dialog/settings.cc Sat Jan 13 09:42:02 2024 -0500 +++ b/src/gui/dialog/settings.cc Sat Jan 13 09:43:41 2024 -0500 @@ -88,7 +88,7 @@ sidebar = new SideBar(widget); sidebar->setCurrentItem(sidebar->AddItem(tr("Services"), SideBar::CreateIcon(":/icons/24x24/globe.png"))); - // sidebar->AddItem(tr("Library"), SideBar::CreateIcon(":/icons/24x24/inbox-film.png")); + sidebar->AddItem(tr("Library"), SideBar::CreateIcon(":/icons/24x24/inbox-film.png")); sidebar->AddItem(tr("Application"), SideBar::CreateIcon(":/icons/24x24/application-sidebar-list.png")); sidebar->AddItem(tr("Recognition"), SideBar::CreateIcon(":/icons/24x24/question.png")); // sidebar->AddItem(tr("Sharing"), SideBar::CreateIcon(":/icons/24x24/megaphone.png")); @@ -109,6 +109,7 @@ { stacked = new QStackedWidget(widget); stacked->addWidget(new SettingsPageServices(stacked)); + stacked->addWidget(new SettingsPageLibrary(stacked)); stacked->addWidget(new SettingsPageApplication(stacked)); stacked->addWidget(new SettingsPageRecognition(stacked)); stacked->addWidget(new SettingsPageTorrents(stacked)); diff -r 2f5a9247e501 -r 69f4768a820c src/gui/dialog/settings/library.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/gui/dialog/settings/library.cc Sat Jan 13 09:43:41 2024 -0500 @@ -0,0 +1,207 @@ +#include "core/session.h" +#include "core/strings.h" +#include "gui/dialog/settings.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +class DroppableListWidget : public QListWidget { + Q_OBJECT + +public: + explicit DroppableListWidget(QWidget* parent); + +signals: + void FilesDropped(QStringList list); + +protected: + void dragEnterEvent(QDragEnterEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; + void dropEvent(QDropEvent* event) override; +}; + +DroppableListWidget::DroppableListWidget(QWidget* parent) : QListWidget(parent) { + setAcceptDrops(true); +} + +void DroppableListWidget::dragMoveEvent(QDragMoveEvent* event) { + if (event->mimeData()->hasUrls()) + event->acceptProposedAction(); +} + +void DroppableListWidget::dragEnterEvent(QDragEnterEvent* event) { + if (event->mimeData()->hasUrls()) + event->acceptProposedAction(); +} + +void DroppableListWidget::dropEvent(QDropEvent* event) { + const QMimeData* mime_data = event->mimeData(); + + if (!mime_data->hasUrls()) + return; + + QStringList path_list; + QList url_list = mime_data->urls(); + + for (const auto& url : url_list) { + if (!url.isLocalFile()) + continue; + + const QString file = url.toLocalFile(); + const QFileInfo fileinfo(file); + if (fileinfo.exists() && fileinfo.isDir()) + path_list.append(file); + } + + if (!path_list.isEmpty()) + emit FilesDropped(path_list); + + event->acceptProposedAction(); +} + +QWidget* SettingsPageLibrary::CreateFoldersWidget() { + QWidget* result = new QWidget(this); + result->setAutoFillBackground(true); + result->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + QVBoxLayout* full_layout = new QVBoxLayout(result); + + { + QGroupBox* group_box = new QGroupBox(tr("Library folders"), result); + group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + QVBoxLayout* group_box_layout = new QVBoxLayout(group_box); + + { + QLabel* label = new QLabel(tr("These folders will be scanned and monitored for new episodes."), group_box); + group_box_layout->addWidget(label); + } + + { + DroppableListWidget* listwidget = new DroppableListWidget(group_box); + listwidget->setSelectionMode(QAbstractItemView::ExtendedSelection); + + for (const auto& path : paths) { + QListWidgetItem* item = new QListWidgetItem(listwidget); + item->setText(Strings::ToQString(path)); + /* add icons as well soon */ + } + + connect(listwidget, &DroppableListWidget::FilesDropped, this, [this, listwidget](QStringList list){ + for (const auto& dir : list) { + paths.insert(Strings::ToUtf8String(dir)); + QListWidgetItem* item = new QListWidgetItem(listwidget); + item->setText(dir); + } + }); + + group_box_layout->addWidget(listwidget); + + { + QWidget* widget = new QWidget(group_box); + QHBoxLayout* widget_layout = new QHBoxLayout(widget); + + { + QLabel* label = new QLabel(tr("Tip: You can drag and drop folders here."), widget); + widget_layout->addWidget(label); + } + + widget_layout->addStretch(); + + { + QPushButton* button = new QPushButton(tr("Add new..."), widget); + + connect(button, &QPushButton::clicked, this, [this, listwidget]{ + const QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"), + QDir::homePath(), + QFileDialog::ShowDirsOnly + | QFileDialog::DontResolveSymlinks); + if (dir.isEmpty()) + return; + paths.insert(Strings::ToUtf8String(dir)); + QListWidgetItem* item = new QListWidgetItem(listwidget); + item->setText(dir); + }); + + widget_layout->addWidget(button); + } + + { + QPushButton* button = new QPushButton(tr("Remove"), widget); + + connect(listwidget, &QListWidget::itemSelectionChanged, this, [button, listwidget]{ + QList selection = listwidget->selectedItems(); + button->setEnabled(selection.size() > 0); + }); + + connect(button, &QPushButton::clicked, this, [this, listwidget]{ + QList selection = listwidget->selectedItems(); + for (const auto& item : selection) { + paths.erase(Strings::ToUtf8String(item->text())); + delete item; + } + }); + + widget_layout->addWidget(button); + } + + group_box_layout->addWidget(widget); + } + } + + full_layout->addWidget(group_box); + } + + { + QGroupBox* group_box = new QGroupBox(tr("Real-time monitor"), result); + group_box->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + QVBoxLayout* group_box_layout = new QVBoxLayout(group_box); + + { + QCheckBox* checkbox = new QCheckBox(tr("Detect new files and folders under library folders"), group_box); + checkbox->setCheckState(real_time_monitor ? Qt::Checked : Qt::Unchecked); + + connect(checkbox, &QCheckBox::stateChanged, this, [this](int state) { + real_time_monitor = (state != Qt::Unchecked); + }); + + group_box_layout->addWidget(checkbox); + } + + full_layout->addWidget(group_box); + } + + full_layout->setSpacing(10); + full_layout->addStretch(); + + return result; +} + +void SettingsPageLibrary::SaveInfo() { + session.config.library.paths = paths; + session.config.library.real_time_monitor = real_time_monitor; +} + +SettingsPageLibrary::SettingsPageLibrary(QWidget* parent) + : SettingsPage(parent, tr("Library")), + paths(session.config.library.paths) { + real_time_monitor = session.config.library.real_time_monitor; + AddTab(CreateFoldersWidget(), tr("Folder")); +} + +#include "gui/dialog/settings/library.moc" diff -r 2f5a9247e501 -r 69f4768a820c src/gui/window.cc --- a/src/gui/window.cc Sat Jan 13 09:42:02 2024 -0500 +++ b/src/gui/window.cc Sat Jan 13 09:43:41 2024 -0500 @@ -16,13 +16,16 @@ #include "gui/widgets/sidebar.h" #include "services/services.h" #include "track/media.h" +#include "library/library.h" #include "anitomy/anitomy.h" #include #include #include +#include #include +#include #include #include #include @@ -154,25 +157,52 @@ } 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); + QMenu* folder_menu; /* this is used twice, so we declare it here */ { /* File */ QMenu* menu = menubar->addMenu(tr("&File")); { - QMenu* submenu = menu->addMenu(tr("&Library folders")); + folder_menu = menu->addMenu(tr("&Library folders")); + + /* 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) + action->setShortcut(QKeySequence(Qt::ALT | static_cast(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(); + { - QAction* action = submenu->addAction(tr("&Add new folder...")); + 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)); + /* we have to recreate the menu bar to add the new folder */ + CreateBars(); + }); } } { - QAction* action = menu->addAction(tr("&Scan available episodes")); + menu->addAction(tr("&Scan available episodes"), []{ + Library::SearchLibraryFolders(); + }); } menu->addSeparator(); @@ -191,6 +221,7 @@ { QAction* action = menu->addAction(tr("E&xit"), qApp, &QApplication::quit); + action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q)); } } @@ -384,10 +415,7 @@ { QToolButton* button = new QToolButton(toolbar); { - QMenu* menu = new QMenu(button); - QAction* action = menu->addAction(tr("...")); - - button->setMenu(menu); + button->setMenu(folder_menu); } button->setIcon(QIcon(":/icons/24x24/folder-open.png")); button->setPopupMode(QToolButton::InstantPopup); @@ -398,8 +426,31 @@ QToolButton* button = new QToolButton(toolbar); { + /* links */ QMenu* menu = new QMenu(button); - QAction* action = menu->addAction(tr("...")); + 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); } diff -r 2f5a9247e501 -r 69f4768a820c src/library/library.cc --- a/src/library/library.cc Sat Jan 13 09:42:02 2024 -0500 +++ b/src/library/library.cc Sat Jan 13 09:43:41 2024 -0500 @@ -9,19 +9,23 @@ #include #include +#include + +namespace Library { + // int = anime id, map = episode, paths std::unordered_map> library; void SearchLibraryFolders() { library.clear(); - for (const auto& spath : session.config.library.paths) { - const std::filesystem::path path(spath); - for (const auto& entry : std::filesystem::recursive_directory_iterator(path)) { - if (!std::filesystem::is_regular_file(entry.path())) + for (const auto& folder : session.config.library.paths) { + for (const auto& entry : std::filesystem::recursive_directory_iterator(folder)) { + const std::filesystem::path path = entry.path(); + if (!std::filesystem::is_regular_file(path)) continue; - const std::string basename = path.filename(); + const std::string basename = path.filename().u8string(); anitomy::Anitomy anitomy; anitomy.Parse(Strings::ToWstring(basename)); @@ -34,10 +38,12 @@ if (id <= 0) continue; - const int episode = Strings::ToInt(Strings::ToUtf8String(elements.get(anitomy::kElementAnimeTitle))); + const int episode = Strings::ToInt(Strings::ToUtf8String(elements.get(anitomy::kElementEpisodeNumber))); // we have an ID now! - library[id][episode] = entry.path(); + library[id][episode] = path.u8string(); } } } + +} diff -r 2f5a9247e501 -r 69f4768a820c src/services/anilist.cc --- a/src/services/anilist.cc Sat Jan 13 09:42:02 2024 -0500 +++ b/src/services/anilist.cc Sat Jan 13 09:43:41 2024 -0500 @@ -30,9 +30,6 @@ class Account { public: - std::string Username() const { return session.config.auth.anilist.username; } - void SetUsername(std::string const& username) { session.config.auth.anilist.username = username; } - int UserId() const { return session.config.auth.anilist.user_id; } void SetUserId(const int id) { session.config.auth.anilist.user_id = id; } @@ -300,7 +297,6 @@ } int ParseUser(const nlohmann::json& json) { - account.SetUsername(JSON::GetString(json, "/name"_json_pointer, "")); account.SetUserId(JSON::GetNumber(json, "/id"_json_pointer, 0)); return account.UserId(); } diff -r 2f5a9247e501 -r 69f4768a820c src/track/media.cc --- a/src/track/media.cc Sat Jan 13 09:42:02 2024 -0500 +++ b/src/track/media.cc Sat Jan 13 09:43:41 2024 -0500 @@ -11,8 +11,6 @@ #include #include -#include - #include "animia.h" namespace Track {