changeset 374:f7bb2978de48

gui/pages/anime_list: add Search right-click menu, don't create menu items that do nothing
author Paper <paper@tflc.us>
date Fri, 25 Jul 2025 11:03:34 -0400
parents fbc8c617de80
children abd956418fe9
files src/gui/pages/anime_list.cc
diffstat 1 files changed, 108 insertions(+), 83 deletions(-) [+]
line wrap: on
line diff
--- a/src/gui/pages/anime_list.cc	Fri Jul 25 11:03:05 2025 -0400
+++ b/src/gui/pages/anime_list.cc	Fri Jul 25 11:03:34 2025 -0400
@@ -55,7 +55,10 @@
 		int id = queue_.front();
 
 		/* unlock the mutex for a long blocking operation, so items
-		 * can be added without worry */
+		 * can be added without worry
+		 *
+		 * NOTE: this code is duplicated elsewhere; is it better to
+		 * have lots of threads, or just one main "worker" thread? */
 		queue_mutex_.unlock();
 		Services::UpdateAnimeEntry(id);
 		queue_mutex_.lock();
@@ -361,35 +364,8 @@
 		// ----------------
 		// Delete from list... <Del>
 	} else if (animes.size() > 0) {
-		// menu in Taiga:
-		//
-		// Information
-		// Search ->
-		//   AniDB
-		//   AniList
-		//   Anime News Network
-		//   Kitsu
-		//   MyAnimeList
-		//   Reddit
-		//   Wikipedia
-		//   YouTube
-		//   ----------------
-		//   Custom RSS feed
-		//   Nyaa.si
-		// ----------------
-		// Edit
-		// Delete from list... <Del>
-		// ----------------
-		// Open folder <Ctrl+O>
-		// Scan available episodes <F5>
-		// ----------------
-		// Play episode ->
-		//   grid of episodes (dunno how to implement this)
-		// Play last episode (#<episode>)
-		// Play next episode (#<episode>) <Ctrl+N>
-		// Play random episode <Ctrl+R> (why?)
-
 		Anime::Anime *anime = *animes.begin();
+		std::optional<std::filesystem::path> path = Library::db.GetAnimeFolder(anime->GetId());
 
 		menu->addAction(tr("Information"), [this, anime] {
 			InformationDialog *dialog = new InformationDialog(
@@ -402,6 +378,52 @@
 			connect(dialog, &InformationDialog::finished, dialog, &InformationDialog::deleteLater);
 		});
 
+		{
+			struct {
+				bool sep;
+				QString name;
+				QString format;
+			} net[] = {
+				/* note: the format should not be percent formatted; Qt does
+				 * that automatically */
+
+				{0, tr("AniDB"), "" /* ??? */},
+				{0, tr("AniList"), "" /* ??? */},
+				{0, tr("Anime News Network"), "https://www.animenewsnetwork.com/search?q=%1"},
+				{0, tr("Kitsu"), "" /* ??? */},
+				{0, tr("MyAnimeList"), "https://myanimelist.net/search/all?q=%1&cat=all"},
+				{0, tr("Reddit"), "https://www.reddit.com/search?q=%1"},
+				{0, tr("Wikipedia"), "https://en.wikipedia.org/w/index.php?search=%1&title=Special:Search&ns0=1"},
+				{0, tr("YouTube"), "https://www.youtube.com/results?search_query=%1"},
+				{1, /* ------------------- */},
+				{0, tr("Custom RSS feed"), "" /* ??? */},
+				{0, tr("Nyaa.si"), "https://nyaa.si/?f=0&c=0_0&q=%1"},
+			};
+			size_t i;
+			QMenu *msearch = menu->addMenu(tr("Search"));
+
+			for (i = 0; i < (sizeof(net)/sizeof(net[0])); i++) {
+				if (net[i].sep) {
+					msearch->addSeparator();
+				} else {
+					if (net[i].format.isEmpty())
+						continue;
+
+					msearch->addAction(net[i].name, [anime, net, i] {
+						/* I suppose romaji is probably the safest */
+						std::optional<std::string> title = anime->GetTitle(Anime::TitleLanguage::Romaji);
+						if (!title)
+							return; /* wat */
+
+						QString str = net[i].format.arg(Strings::ToQString(title.value()));
+						QUrl url(str);
+
+						QDesktopServices::openUrl(url);
+					});
+				}
+			}
+		}
+
 		menu->addSeparator();
 
 		menu->addAction(tr("Edit"), [this, anime] {
@@ -420,76 +442,79 @@
 
 		menu->addSeparator();
 
-		menu->addAction(tr("Open folder"), [this, anime] {
-			std::optional<std::filesystem::path> path = Library::db.GetAnimeFolder(anime->GetId());
-			if (!path) // ...
-				return;
-
-			QDesktopServices::openUrl(QUrl::fromLocalFile(Strings::ToQString(path.value().u8string())));
-		});
-		menu->addAction(tr("Scan available episodes"), [this, anime] { Library::db.Refresh(anime->GetId()); });
-
-		menu->addSeparator();
-
-		{
-			QMenu *submenu = menu->addMenu(tr("Play episode"));
-
-			// this submenu actually uses win32 API magic to
-			// make a *grid* of episodes (weird!)
-
-			(void)submenu;
+		if (path) {
+			menu->addAction(tr("Open folder"), [path] {
+				QDesktopServices::openUrl(QUrl::fromLocalFile(Strings::ToQString(path.value().u8string())));
+			}, QKeySequence(QKeySequence::Open));
 		}
 
-		const int progress = anime->GetUserProgress();
-		const int episodes = anime->GetEpisodes();
+		menu->addAction(tr("Scan available episodes"),
+			[this, anime] { Library::db.Refresh(anime->GetId()); },
+			QKeySequence(QKeySequence::Refresh));
+
+		if (path) {
+			menu->addSeparator();
+
+			{
+				QMenu *submenu = menu->addMenu(tr("Play episode"));
 
-		// I think this is right?
-		if (progress > 0) {
-			menu->addAction(tr("Play last episode (#%1)").arg(progress), [this, anime, progress] {
-				const int id = anime->GetId();
+				// this submenu actually uses win32 API magic to
+				// make a *grid* of episodes (weird!)
+
+				(void)submenu;
+			}
+
+			const int progress = anime->GetUserProgress();
+			const int episodes = anime->GetEpisodes();
+
+			// I think this is right?
+			if (progress > 0) {
+				menu->addAction(tr("Play last episode (#%1)").arg(progress), [this, anime, progress] {
+					const int id = anime->GetId();
 
-				if (Library::db.items.find(id) == Library::db.items.end() ||
-				    Library::db.items[id].find(progress) == Library::db.items[id].end())
-					return;
+					if (Library::db.items.find(id) == Library::db.items.end() ||
+					    Library::db.items[id].find(progress) == Library::db.items[id].end())
+						return;
+
+					QDesktopServices::openUrl(
+					    QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][progress].u8string())));
+				});
+			}
 
-				QDesktopServices::openUrl(
-				    QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][progress].u8string())));
-			});
-		}
+			if (progress < episodes) {
+				menu->addAction(
+				    tr("Play next episode (#%1)").arg(progress + 1),
+				    [this, anime, progress] {
+					    const int id = anime->GetId();
+
+					    if (Library::db.items.find(id) == Library::db.items.end() ||
+					        Library::db.items[id].find(progress + 1) == Library::db.items[id].end())
+						    return;
 
-		if (progress < episodes) {
+					    QDesktopServices::openUrl(
+					        QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][progress + 1].u8string())));
+				    },
+				    QKeySequence(Qt::CTRL | Qt::Key_N));
+			}
+
 			menu->addAction(
-			    tr("Play next episode (#%1)").arg(progress + 1),
-			    [this, anime, progress] {
+			    tr("Play random episode"),
+			    [this, anime, episodes] {
 				    const int id = anime->GetId();
 
+				    std::uniform_int_distribution<int> distrib(1, episodes);
+				    const int episode = distrib(session.gen);
+
 				    if (Library::db.items.find(id) == Library::db.items.end() ||
-				        Library::db.items[id].find(progress + 1) == Library::db.items[id].end())
+				        Library::db.items[id].find(episode) == Library::db.items[id].end())
 					    return;
 
 				    QDesktopServices::openUrl(
-				        QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][progress + 1].u8string())));
+				        QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][episode].u8string())));
 			    },
-			    QKeySequence(Qt::CTRL | Qt::Key_N));
+			    QKeySequence(Qt::CTRL | Qt::Key_R));
 		}
 
-		menu->addAction(
-		    tr("Play random episode"),
-		    [this, anime, episodes] {
-			    const int id = anime->GetId();
-
-			    std::uniform_int_distribution<int> distrib(1, episodes);
-			    const int episode = distrib(session.gen);
-
-			    if (Library::db.items.find(id) == Library::db.items.end() ||
-			        Library::db.items[id].find(episode) == Library::db.items[id].end())
-				    return;
-
-			    QDesktopServices::openUrl(
-			        QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][episode].u8string())));
-		    },
-		    QKeySequence(Qt::CTRL | Qt::Key_R));
-
 		menu->popup(QCursor::pos());
 	} else {
 		// Where are we now?