comparison src/gui/pages/anime_list.cc @ 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 ea3a74ed2ef9
children
comparison
equal deleted inserted replaced
373:fbc8c617de80 374:f7bb2978de48
53 queue_mutex_.lock(); 53 queue_mutex_.lock();
54 while (!queue_.empty() && !isInterruptionRequested()) { 54 while (!queue_.empty() && !isInterruptionRequested()) {
55 int id = queue_.front(); 55 int id = queue_.front();
56 56
57 /* unlock the mutex for a long blocking operation, so items 57 /* unlock the mutex for a long blocking operation, so items
58 * can be added without worry */ 58 * can be added without worry
59 *
60 * NOTE: this code is duplicated elsewhere; is it better to
61 * have lots of threads, or just one main "worker" thread? */
59 queue_mutex_.unlock(); 62 queue_mutex_.unlock();
60 Services::UpdateAnimeEntry(id); 63 Services::UpdateAnimeEntry(id);
61 queue_mutex_.lock(); 64 queue_mutex_.lock();
62 65
63 queue_.pop(); 66 queue_.pop();
359 // ---------------- 362 // ----------------
360 // Invert selection 363 // Invert selection
361 // ---------------- 364 // ----------------
362 // Delete from list... <Del> 365 // Delete from list... <Del>
363 } else if (animes.size() > 0) { 366 } else if (animes.size() > 0) {
364 // menu in Taiga:
365 //
366 // Information
367 // Search ->
368 // AniDB
369 // AniList
370 // Anime News Network
371 // Kitsu
372 // MyAnimeList
373 // Reddit
374 // Wikipedia
375 // YouTube
376 // ----------------
377 // Custom RSS feed
378 // Nyaa.si
379 // ----------------
380 // Edit
381 // Delete from list... <Del>
382 // ----------------
383 // Open folder <Ctrl+O>
384 // Scan available episodes <F5>
385 // ----------------
386 // Play episode ->
387 // grid of episodes (dunno how to implement this)
388 // Play last episode (#<episode>)
389 // Play next episode (#<episode>) <Ctrl+N>
390 // Play random episode <Ctrl+R> (why?)
391
392 Anime::Anime *anime = *animes.begin(); 367 Anime::Anime *anime = *animes.begin();
368 std::optional<std::filesystem::path> path = Library::db.GetAnimeFolder(anime->GetId());
393 369
394 menu->addAction(tr("Information"), [this, anime] { 370 menu->addAction(tr("Information"), [this, anime] {
395 InformationDialog *dialog = new InformationDialog( 371 InformationDialog *dialog = new InformationDialog(
396 anime, [this](Anime::Anime *anime) { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MAIN_INFO, 372 anime, [this](Anime::Anime *anime) { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MAIN_INFO,
397 this); 373 this);
399 dialog->show(); 375 dialog->show();
400 dialog->raise(); 376 dialog->raise();
401 dialog->activateWindow(); 377 dialog->activateWindow();
402 connect(dialog, &InformationDialog::finished, dialog, &InformationDialog::deleteLater); 378 connect(dialog, &InformationDialog::finished, dialog, &InformationDialog::deleteLater);
403 }); 379 });
380
381 {
382 struct {
383 bool sep;
384 QString name;
385 QString format;
386 } net[] = {
387 /* note: the format should not be percent formatted; Qt does
388 * that automatically */
389
390 {0, tr("AniDB"), "" /* ??? */},
391 {0, tr("AniList"), "" /* ??? */},
392 {0, tr("Anime News Network"), "https://www.animenewsnetwork.com/search?q=%1"},
393 {0, tr("Kitsu"), "" /* ??? */},
394 {0, tr("MyAnimeList"), "https://myanimelist.net/search/all?q=%1&cat=all"},
395 {0, tr("Reddit"), "https://www.reddit.com/search?q=%1"},
396 {0, tr("Wikipedia"), "https://en.wikipedia.org/w/index.php?search=%1&title=Special:Search&ns0=1"},
397 {0, tr("YouTube"), "https://www.youtube.com/results?search_query=%1"},
398 {1, /* ------------------- */},
399 {0, tr("Custom RSS feed"), "" /* ??? */},
400 {0, tr("Nyaa.si"), "https://nyaa.si/?f=0&c=0_0&q=%1"},
401 };
402 size_t i;
403 QMenu *msearch = menu->addMenu(tr("Search"));
404
405 for (i = 0; i < (sizeof(net)/sizeof(net[0])); i++) {
406 if (net[i].sep) {
407 msearch->addSeparator();
408 } else {
409 if (net[i].format.isEmpty())
410 continue;
411
412 msearch->addAction(net[i].name, [anime, net, i] {
413 /* I suppose romaji is probably the safest */
414 std::optional<std::string> title = anime->GetTitle(Anime::TitleLanguage::Romaji);
415 if (!title)
416 return; /* wat */
417
418 QString str = net[i].format.arg(Strings::ToQString(title.value()));
419 QUrl url(str);
420
421 QDesktopServices::openUrl(url);
422 });
423 }
424 }
425 }
404 426
405 menu->addSeparator(); 427 menu->addSeparator();
406 428
407 menu->addAction(tr("Edit"), [this, anime] { 429 menu->addAction(tr("Edit"), [this, anime] {
408 InformationDialog *dialog = new InformationDialog( 430 InformationDialog *dialog = new InformationDialog(
418 tr("Delete from list..."), [this, anime] { RemoveAnime(anime->GetId()); }, 440 tr("Delete from list..."), [this, anime] { RemoveAnime(anime->GetId()); },
419 QKeySequence(QKeySequence::Delete)); 441 QKeySequence(QKeySequence::Delete));
420 442
421 menu->addSeparator(); 443 menu->addSeparator();
422 444
423 menu->addAction(tr("Open folder"), [this, anime] { 445 if (path) {
424 std::optional<std::filesystem::path> path = Library::db.GetAnimeFolder(anime->GetId()); 446 menu->addAction(tr("Open folder"), [path] {
425 if (!path) // ... 447 QDesktopServices::openUrl(QUrl::fromLocalFile(Strings::ToQString(path.value().u8string())));
426 return; 448 }, QKeySequence(QKeySequence::Open));
427
428 QDesktopServices::openUrl(QUrl::fromLocalFile(Strings::ToQString(path.value().u8string())));
429 });
430 menu->addAction(tr("Scan available episodes"), [this, anime] { Library::db.Refresh(anime->GetId()); });
431
432 menu->addSeparator();
433
434 {
435 QMenu *submenu = menu->addMenu(tr("Play episode"));
436
437 // this submenu actually uses win32 API magic to
438 // make a *grid* of episodes (weird!)
439
440 (void)submenu;
441 } 449 }
442 450
443 const int progress = anime->GetUserProgress(); 451 menu->addAction(tr("Scan available episodes"),
444 const int episodes = anime->GetEpisodes(); 452 [this, anime] { Library::db.Refresh(anime->GetId()); },
445 453 QKeySequence(QKeySequence::Refresh));
446 // I think this is right? 454
447 if (progress > 0) { 455 if (path) {
448 menu->addAction(tr("Play last episode (#%1)").arg(progress), [this, anime, progress] { 456 menu->addSeparator();
449 const int id = anime->GetId(); 457
450 458 {
451 if (Library::db.items.find(id) == Library::db.items.end() || 459 QMenu *submenu = menu->addMenu(tr("Play episode"));
452 Library::db.items[id].find(progress) == Library::db.items[id].end()) 460
453 return; 461 // this submenu actually uses win32 API magic to
454 462 // make a *grid* of episodes (weird!)
455 QDesktopServices::openUrl( 463
456 QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][progress].u8string()))); 464 (void)submenu;
457 }); 465 }
466
467 const int progress = anime->GetUserProgress();
468 const int episodes = anime->GetEpisodes();
469
470 // I think this is right?
471 if (progress > 0) {
472 menu->addAction(tr("Play last episode (#%1)").arg(progress), [this, anime, progress] {
473 const int id = anime->GetId();
474
475 if (Library::db.items.find(id) == Library::db.items.end() ||
476 Library::db.items[id].find(progress) == Library::db.items[id].end())
477 return;
478
479 QDesktopServices::openUrl(
480 QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][progress].u8string())));
481 });
482 }
483
484 if (progress < episodes) {
485 menu->addAction(
486 tr("Play next episode (#%1)").arg(progress + 1),
487 [this, anime, progress] {
488 const int id = anime->GetId();
489
490 if (Library::db.items.find(id) == Library::db.items.end() ||
491 Library::db.items[id].find(progress + 1) == Library::db.items[id].end())
492 return;
493
494 QDesktopServices::openUrl(
495 QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][progress + 1].u8string())));
496 },
497 QKeySequence(Qt::CTRL | Qt::Key_N));
498 }
499
500 menu->addAction(
501 tr("Play random episode"),
502 [this, anime, episodes] {
503 const int id = anime->GetId();
504
505 std::uniform_int_distribution<int> distrib(1, episodes);
506 const int episode = distrib(session.gen);
507
508 if (Library::db.items.find(id) == Library::db.items.end() ||
509 Library::db.items[id].find(episode) == Library::db.items[id].end())
510 return;
511
512 QDesktopServices::openUrl(
513 QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][episode].u8string())));
514 },
515 QKeySequence(Qt::CTRL | Qt::Key_R));
458 } 516 }
459
460 if (progress < episodes) {
461 menu->addAction(
462 tr("Play next episode (#%1)").arg(progress + 1),
463 [this, anime, progress] {
464 const int id = anime->GetId();
465
466 if (Library::db.items.find(id) == Library::db.items.end() ||
467 Library::db.items[id].find(progress + 1) == Library::db.items[id].end())
468 return;
469
470 QDesktopServices::openUrl(
471 QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][progress + 1].u8string())));
472 },
473 QKeySequence(Qt::CTRL | Qt::Key_N));
474 }
475
476 menu->addAction(
477 tr("Play random episode"),
478 [this, anime, episodes] {
479 const int id = anime->GetId();
480
481 std::uniform_int_distribution<int> distrib(1, episodes);
482 const int episode = distrib(session.gen);
483
484 if (Library::db.items.find(id) == Library::db.items.end() ||
485 Library::db.items[id].find(episode) == Library::db.items[id].end())
486 return;
487
488 QDesktopServices::openUrl(
489 QUrl::fromLocalFile(Strings::ToQString(Library::db.items[id][episode].u8string())));
490 },
491 QKeySequence(Qt::CTRL | Qt::Key_R));
492 517
493 menu->popup(QCursor::pos()); 518 menu->popup(QCursor::pos());
494 } else { 519 } else {
495 // Where are we now? 520 // Where are we now?
496 } 521 }