diff --git a/pcsx2-qt/GameList/GameListModel.cpp b/pcsx2-qt/GameList/GameListModel.cpp index ac1519f2d4..702f13b1f4 100644 --- a/pcsx2-qt/GameList/GameListModel.cpp +++ b/pcsx2-qt/GameList/GameListModel.cpp @@ -271,15 +271,18 @@ int GameListModel::columnCount(const QModelIndex& parent) const return Column_Count; } -QString GameListModel::formatTimespan(time_t timespan) +QString GameListModel::formatTimespan(const time_t timespan) { - // avoid an extra string conversion + // Avoid an extra string conversion over calling QString::fromStdString(GameList::FormatTimespan). const u32 hours = static_cast(timespan / 3600); - const u32 minutes = static_cast((timespan % 3600) / 60); if (hours > 0) return qApp->translate("GameList", "%n hours", "", hours); - else + + const u32 minutes = static_cast((timespan % 3600) / 60); + if (minutes > 0) return qApp->translate("GameList", "%n minutes", "", minutes); + else + return qApp->translate("GameList", "%n seconds", "", static_cast((timespan % 3600) % 60)); } QVariant GameListModel::data(const QModelIndex& index, int role) const diff --git a/pcsx2-qt/GameList/GameListModel.h b/pcsx2-qt/GameList/GameListModel.h index 48115b9b21..ec3e883e4f 100644 --- a/pcsx2-qt/GameList/GameListModel.h +++ b/pcsx2-qt/GameList/GameListModel.h @@ -84,7 +84,7 @@ private: void loadOrGenerateCover(const GameList::Entry* ge); void invalidateCoverForPath(const std::string& path); - static QString formatTimespan(time_t timespan); + static QString formatTimespan(const time_t timespan); float m_cover_scale = 0.0f; std::atomic m_cover_scale_counter{0}; diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 0ef3b9f5f7..20642d03ad 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -1455,12 +1455,15 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point) connect(menu.addAction(tr("Exclude From List")), &QAction::triggered, [this, entry]() { getSettingsWindow()->getGameListSettingsWidget()->addExcludedPath(entry->path); }); - connect(menu.addAction(tr("Reset Play Time")), &QAction::triggered, [this, entry]() { clearGameListEntryPlayTime(entry); }); + const time_t entry_played_time = GameList::GetCachedPlayedTimeForSerial(entry->serial); + // Best two options given zero play time are to grey this out or to not show it at all. + if (entry_played_time) + connect(menu.addAction(tr("Reset Play Time")), &QAction::triggered, [this, entry, entry_played_time]() + { clearGameListEntryPlayTime(entry, entry_played_time); }); + // Check Wiki Page functionality is based on a serial redirect. if (!entry->serial.empty()) - { connect(menu.addAction(tr("Check Wiki Page")), &QAction::triggered, [this, entry]() { goToWikiPage(entry); }); - } action = menu.addAction(tr("Open Screenshots Folder")); connect(action, &QAction::triggered, [this, entry]() { openScreenshotsFolderForGame(entry); }); @@ -2901,17 +2904,18 @@ void MainWindow::setGameListEntryCoverImage(const GameList::Entry* entry) m_game_list_widget->refreshGridCovers(); } -void MainWindow::clearGameListEntryPlayTime(const GameList::Entry* entry) +void MainWindow::clearGameListEntryPlayTime(const GameList::Entry* entry, const time_t entry_played_time) { if (QMessageBox::question(this, tr("Confirm Reset"), - tr("Are you sure you want to reset the play time for '%1'?\n\nThis action cannot be undone.") - .arg(QString::fromStdString(entry->title))) != QMessageBox::Yes) + tr("Are you sure you want to reset the play time for '%1' (%2)?\n\nYour current play time is %3.\n\nThis action cannot be undone.") + .arg(entry->title.empty() ? tr("empty title") : QString::fromStdString(entry->title), + entry->serial.empty() ? tr("no serial") : QString::fromStdString(entry->serial), + QString::fromStdString(GameList::FormatTimespan(entry_played_time, true))), + (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes) { - return; + GameList::ClearPlayedTimeForSerial(entry->serial); + m_game_list_widget->refresh(false); } - - GameList::ClearPlayedTimeForSerial(entry->serial); - m_game_list_widget->refresh(false); } void MainWindow::goToWikiPage(const GameList::Entry* entry) diff --git a/pcsx2-qt/MainWindow.h b/pcsx2-qt/MainWindow.h index b828680114..7ccf30cde7 100644 --- a/pcsx2-qt/MainWindow.h +++ b/pcsx2-qt/MainWindow.h @@ -273,7 +273,7 @@ private: void startGameListEntry( const GameList::Entry* entry, std::optional save_slot = std::nullopt, std::optional fast_boot = std::nullopt, bool load_backup = false); void setGameListEntryCoverImage(const GameList::Entry* entry); - void clearGameListEntryPlayTime(const GameList::Entry* entry); + void clearGameListEntryPlayTime(const GameList::Entry* entry, const time_t entry_played_time); void goToWikiPage(const GameList::Entry* entry); void openScreenshotsFolderForGame(const GameList::Entry* entry); diff --git a/pcsx2/GameList.cpp b/pcsx2/GameList.cpp index 5606e5e3cf..1b5b7535cd 100644 --- a/pcsx2/GameList.cpp +++ b/pcsx2/GameList.cpp @@ -1174,7 +1174,7 @@ std::string GameList::FormatTimestamp(std::time_t timestamp) return ret; } -std::string GameList::FormatTimespan(std::time_t timespan, bool long_format) +std::string GameList::FormatTimespan(const std::time_t timespan, const bool long_format) { const u32 hours = static_cast(timespan / 3600); const u32 minutes = static_cast((timespan % 3600) / 60); @@ -1198,8 +1198,10 @@ std::string GameList::FormatTimespan(std::time_t timespan, bool long_format) { if (hours > 0) ret.assign(TRANSLATE_PLURAL_STR("GameList", "%n hours", "", hours)); - else + else if (minutes > 0) ret.assign(TRANSLATE_PLURAL_STR("GameList", "%n minutes", "", minutes)); + else + ret.assign(TRANSLATE_PLURAL_STR("GameList", "%n seconds", "", seconds)); } return ret; diff --git a/pcsx2/GameList.h b/pcsx2/GameList.h index b686ca49a3..107e487fe1 100644 --- a/pcsx2/GameList.h +++ b/pcsx2/GameList.h @@ -145,7 +145,7 @@ namespace GameList std::string FormatTimestamp(std::time_t timestamp); /// Formats a timespan to something human readable (e.g. 1h2m3s or 1 hour). - std::string FormatTimespan(std::time_t timespan, bool long_format = false); + std::string FormatTimespan(const std::time_t timespan, const bool long_format = false); std::string GetCoverImagePathForEntry(const Entry* entry); std::string GetNewCoverImagePathForEntry(const Entry* entry, const char* new_filename, bool use_serial = false); diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp index 2335588984..7532d7545d 100644 --- a/pcsx2/ImGui/FullscreenUI.cpp +++ b/pcsx2/ImGui/FullscreenUI.cpp @@ -7880,13 +7880,17 @@ void FullscreenUI::HandleGameListOptions(const GameList::Entry* entry) {FSUI_ICONSTR(ICON_PF_STAR, "Default Boot"), false}, {FSUI_ICONSTR(ICON_FA_FORWARD_FAST, "Fast Boot"), false}, {FSUI_ICONSTR(ICON_FA_COMPACT_DISC, "Full Boot"), false}, - {FSUI_ICONSTR(ICON_FA_STOPWATCH, "Reset Play Time"), false}, - {FSUI_ICONSTR(ICON_FA_SQUARE_XMARK, "Close Menu"), false}, }; + const time_t entry_played_time = GameList::GetCachedPlayedTimeForSerial(entry->serial); + if (entry_played_time) + options.emplace_back(FSUI_ICONSTR(ICON_FA_STOPWATCH, "Reset Play Time"), false); + options.emplace_back(FSUI_ICONSTR(ICON_FA_SQUARE_XMARK, "Close Menu"), false); + const bool has_resume_state = VMManager::HasSaveStateInSlot(entry->serial.c_str(), entry->crc, -1); OpenChoiceDialog(entry->GetTitle(true).c_str(), false, std::move(options), - [has_resume_state, entry_path = entry->path, entry_serial = entry->serial](s32 index, const std::string& title, bool checked) { + [has_resume_state, entry_path = entry->path, entry_serial = entry->serial, entry_title = entry->title, entry_played_time] + (s32 index, const std::string& title, bool checked) { switch (index) { case 0: // Open Game Properties @@ -7907,10 +7911,26 @@ void FullscreenUI::HandleGameListOptions(const GameList::Entry* entry) case 5: // Full Boot DoStartPath(entry_path, std::nullopt, false); break; - case 6: // Reset Play Time - GameList::ClearPlayedTimeForSerial(entry_serial); + case 6: + { + // Close Menu + if (!entry_played_time) + break; + + // Reset Play Time + OpenConfirmMessageDialog(FSUI_ICONSTR(ICON_FA_STOPWATCH, "Confirm Reset"), + fmt::format(FSUI_FSTR("Are you sure you want to reset the play time for '{}' ({})?\n\n" + "Your current play time is {}.\n\nThis action cannot be undone."), + entry_title.empty() ? FSUI_STR("empty title") : entry_title, + entry_serial.empty() ? FSUI_STR("no serial") : entry_serial, + GameList::FormatTimespan(entry_played_time, true)), + [entry_serial](bool result) { + if (result) + GameList::ClearPlayedTimeForSerial(entry_serial); + }, false); + } break; - default: + default: // Close Menu break; }