mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	citra_qt: Rebuilt movie frontend
This is completely rebuilt, in order to allow setting author, displaying movie metadata, and toggling read-only mode. The UX is changed to more closely match other emulators' behaviour. Now you can only record/play from start/reset (In the future, we might want to introduce 'record from savestate') Also fixed a critical bug where movie file can be corrupted when ending the recording while game is still running.
This commit is contained in:
		
							parent
							
								
									5a42a80f40
								
							
						
					
					
						commit
						113e0c7331
					
				
					 14 changed files with 541 additions and 151 deletions
				
			
		|  | @ -125,6 +125,12 @@ add_executable(citra-qt | |||
|     main.cpp | ||||
|     main.h | ||||
|     main.ui | ||||
|     movie/movie_play_dialog.cpp | ||||
|     movie/movie_play_dialog.h | ||||
|     movie/movie_play_dialog.ui | ||||
|     movie/movie_record_dialog.cpp | ||||
|     movie/movie_record_dialog.h | ||||
|     movie/movie_record_dialog.ui | ||||
|     multiplayer/chat_room.cpp | ||||
|     multiplayer/chat_room.h | ||||
|     multiplayer/chat_room.ui | ||||
|  |  | |||
|  | @ -722,17 +722,17 @@ void GameList::RefreshGameDirectory() { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| QString GameList::FindGameByProgramID(u64 program_id) { | ||||
|     return FindGameByProgramID(item_model->invisibleRootItem(), program_id); | ||||
| QString GameList::FindGameByProgramID(u64 program_id, int role) { | ||||
|     return FindGameByProgramID(item_model->invisibleRootItem(), program_id, role); | ||||
| } | ||||
| 
 | ||||
| QString GameList::FindGameByProgramID(QStandardItem* current_item, u64 program_id) { | ||||
| QString GameList::FindGameByProgramID(QStandardItem* current_item, u64 program_id, int role) { | ||||
|     if (current_item->type() == static_cast<int>(GameListItemType::Game) && | ||||
|         current_item->data(GameListItemPath::ProgramIdRole).toULongLong() == program_id) { | ||||
|         return current_item->data(GameListItemPath::FullPathRole).toString(); | ||||
|         return current_item->data(role).toString(); | ||||
|     } else if (current_item->hasChildren()) { | ||||
|         for (int child_id = 0; child_id < current_item->rowCount(); child_id++) { | ||||
|             QString path = FindGameByProgramID(current_item->child(child_id, 0), program_id); | ||||
|             QString path = FindGameByProgramID(current_item->child(child_id, 0), program_id, role); | ||||
|             if (!path.isEmpty()) | ||||
|                 return path; | ||||
|         } | ||||
|  |  | |||
|  | @ -70,7 +70,7 @@ public: | |||
| 
 | ||||
|     QStandardItemModel* GetModel() const; | ||||
| 
 | ||||
|     QString FindGameByProgramID(u64 program_id); | ||||
|     QString FindGameByProgramID(u64 program_id, int role); | ||||
| 
 | ||||
|     void RefreshGameDirectory(); | ||||
| 
 | ||||
|  | @ -105,7 +105,7 @@ private: | |||
|     void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected); | ||||
|     void AddPermDirPopup(QMenu& context_menu, QModelIndex selected); | ||||
| 
 | ||||
|     QString FindGameByProgramID(QStandardItem* current_item, u64 program_id); | ||||
|     QString FindGameByProgramID(QStandardItem* current_item, u64 program_id, int role); | ||||
| 
 | ||||
|     GameListSearchField* search_field; | ||||
|     GMainWindow* main_window = nullptr; | ||||
|  |  | |||
|  | @ -51,6 +51,8 @@ | |||
| #include "citra_qt/hotkeys.h" | ||||
| #include "citra_qt/loading_screen.h" | ||||
| #include "citra_qt/main.h" | ||||
| #include "citra_qt/movie/movie_play_dialog.h" | ||||
| #include "citra_qt/movie/movie_record_dialog.h" | ||||
| #include "citra_qt/multiplayer/state.h" | ||||
| #include "citra_qt/qt_image_interface.h" | ||||
| #include "citra_qt/uisettings.h" | ||||
|  | @ -174,6 +176,9 @@ GMainWindow::GMainWindow() | |||
| 
 | ||||
|     Network::Init(); | ||||
| 
 | ||||
|     Core::Movie::GetInstance().SetPlaybackCompletionCallback( | ||||
|         [this] { QMetaObject::invokeMethod(this, "OnMoviePlaybackCompleted"); }); | ||||
| 
 | ||||
|     InitializeWidgets(); | ||||
|     InitializeDebugWidgets(); | ||||
|     InitializeRecentFileMenuActions(); | ||||
|  | @ -742,8 +747,9 @@ void GMainWindow::ConnectMenuEvents() { | |||
|     // Movie
 | ||||
|     connect(ui->action_Record_Movie, &QAction::triggered, this, &GMainWindow::OnRecordMovie); | ||||
|     connect(ui->action_Play_Movie, &QAction::triggered, this, &GMainWindow::OnPlayMovie); | ||||
|     connect(ui->action_Stop_Recording_Playback, &QAction::triggered, this, | ||||
|             &GMainWindow::OnStopRecordingPlayback); | ||||
|     connect(ui->action_Close_Movie, &QAction::triggered, this, &GMainWindow::OnCloseMovie); | ||||
|     connect(ui->action_Movie_Read_Only_Mode, &QAction::toggled, this, | ||||
|             [this](bool checked) { Core::Movie::GetInstance().SetReadOnly(checked); }); | ||||
|     connect(ui->action_Enable_Frame_Advancing, &QAction::triggered, this, [this] { | ||||
|         if (emulation_running) { | ||||
|             Core::System::GetInstance().frame_limiter.SetFrameAdvancing( | ||||
|  | @ -1105,7 +1111,7 @@ void GMainWindow::ShutdownGame() { | |||
|     AllowOSSleep(); | ||||
| 
 | ||||
|     discord_rpc->Pause(); | ||||
|     OnStopRecordingPlayback(); | ||||
|     OnCloseMovie(true); | ||||
|     emu_thread->RequestStop(); | ||||
| 
 | ||||
|     // Release emu threads from any breakpoints
 | ||||
|  | @ -1534,9 +1540,11 @@ void GMainWindow::OnStartGame() { | |||
|     Camera::QtMultimediaCameraHandler::ResumeCameras(); | ||||
| 
 | ||||
|     if (movie_record_on_start) { | ||||
|         Core::Movie::GetInstance().StartRecording(movie_record_path.toStdString()); | ||||
|         Core::Movie::GetInstance().StartRecording(movie_record_path.toStdString(), | ||||
|                                                   movie_record_author.toStdString()); | ||||
|         movie_record_on_start = false; | ||||
|         movie_record_path.clear(); | ||||
|         movie_record_author.clear(); | ||||
|     } | ||||
| 
 | ||||
|     PreventOSSleep(); | ||||
|  | @ -1839,144 +1847,63 @@ void GMainWindow::OnCreateGraphicsSurfaceViewer() { | |||
| } | ||||
| 
 | ||||
| void GMainWindow::OnRecordMovie() { | ||||
|     if (emulation_running) { | ||||
|         QMessageBox::StandardButton answer = QMessageBox::warning( | ||||
|             this, tr("Record Movie"), | ||||
|             tr("To keep consistency with the RNG, it is recommended to record the movie from game " | ||||
|                "start.<br>Are you sure you still want to record movies now?"), | ||||
|             QMessageBox::Yes | QMessageBox::No); | ||||
|         if (answer == QMessageBox::No) | ||||
|             return; | ||||
|     } | ||||
|     const QString path = | ||||
|         QFileDialog::getSaveFileName(this, tr("Record Movie"), UISettings::values.movie_record_path, | ||||
|                                      tr("Citra TAS Movie (*.ctm)")); | ||||
|     if (path.isEmpty()) | ||||
|     MovieRecordDialog dialog(this); | ||||
|     if (dialog.exec() != QDialog::Accepted) { | ||||
|         return; | ||||
|     UISettings::values.movie_record_path = QFileInfo(path).path(); | ||||
|     } | ||||
| 
 | ||||
|     if (emulation_running) { | ||||
|         Core::Movie::GetInstance().StartRecording(path.toStdString()); | ||||
|         // Restart game
 | ||||
|         BootGame(QString(game_path)); | ||||
|         Core::Movie::GetInstance().StartRecording(dialog.GetPath().toStdString(), | ||||
|                                                   dialog.GetAuthor().toStdString()); | ||||
|     } else { | ||||
|         movie_record_on_start = true; | ||||
|         movie_record_path = path; | ||||
|         QMessageBox::information(this, tr("Record Movie"), | ||||
|                                  tr("Recording will start once you boot a game.")); | ||||
|         movie_record_path = dialog.GetPath(); | ||||
|         movie_record_author = dialog.GetAuthor(); | ||||
|     } | ||||
|     ui->action_Record_Movie->setEnabled(false); | ||||
|     ui->action_Play_Movie->setEnabled(false); | ||||
|     ui->action_Stop_Recording_Playback->setEnabled(true); | ||||
| } | ||||
| 
 | ||||
| bool GMainWindow::ValidateMovie(const QString& path, u64 program_id) { | ||||
|     using namespace Core; | ||||
|     Movie::ValidationResult result = | ||||
|         Core::Movie::GetInstance().ValidateMovie(path.toStdString(), program_id); | ||||
|     const QString revision_dismatch_text = | ||||
|         tr("The movie file you are trying to load was created on a different revision of Citra." | ||||
|            "<br/>Citra has had some changes during the time, and the playback may desync or not " | ||||
|            "work as expected." | ||||
|            "<br/><br/>Are you sure you still want to load the movie file?"); | ||||
|     const QString game_dismatch_text = | ||||
|         tr("The movie file you are trying to load was recorded with a different game." | ||||
|            "<br/>The playback may not work as expected, and it may cause unexpected results." | ||||
|            "<br/><br/>Are you sure you still want to load the movie file?"); | ||||
|     const QString invalid_movie_text = | ||||
|         tr("The movie file you are trying to load is invalid." | ||||
|            "<br/>Either the file is corrupted, or Citra has had made some major changes to the " | ||||
|            "Movie module." | ||||
|            "<br/>Please choose a different movie file and try again."); | ||||
|     int answer; | ||||
|     switch (result) { | ||||
|     case Movie::ValidationResult::RevisionDismatch: | ||||
|         answer = QMessageBox::question(this, tr("Revision Dismatch"), revision_dismatch_text, | ||||
|                                        QMessageBox::Yes | QMessageBox::No, QMessageBox::No); | ||||
|         if (answer != QMessageBox::Yes) | ||||
|             return false; | ||||
|         break; | ||||
|     case Movie::ValidationResult::GameDismatch: | ||||
|         answer = QMessageBox::question(this, tr("Game Dismatch"), game_dismatch_text, | ||||
|                                        QMessageBox::Yes | QMessageBox::No, QMessageBox::No); | ||||
|         if (answer != QMessageBox::Yes) | ||||
|             return false; | ||||
|         break; | ||||
|     case Movie::ValidationResult::Invalid: | ||||
|         QMessageBox::critical(this, tr("Invalid Movie File"), invalid_movie_text); | ||||
|         return false; | ||||
|     default: | ||||
|         break; | ||||
|     } | ||||
|     return true; | ||||
|     ui->action_Close_Movie->setEnabled(true); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnPlayMovie() { | ||||
|     if (emulation_running) { | ||||
|         QMessageBox::StandardButton answer = QMessageBox::warning( | ||||
|             this, tr("Play Movie"), | ||||
|             tr("To keep consistency with the RNG, it is recommended to play the movie from game " | ||||
|                "start.<br>Are you sure you still want to play movies now?"), | ||||
|             QMessageBox::Yes | QMessageBox::No); | ||||
|         if (answer == QMessageBox::No) | ||||
|             return; | ||||
|     } | ||||
| 
 | ||||
|     const QString path = | ||||
|         QFileDialog::getOpenFileName(this, tr("Play Movie"), UISettings::values.movie_playback_path, | ||||
|                                      tr("Citra TAS Movie (*.ctm)")); | ||||
|     if (path.isEmpty()) | ||||
|     MoviePlayDialog dialog(this, game_list); | ||||
|     if (dialog.exec() != QDialog::Accepted) { | ||||
|         return; | ||||
|     UISettings::values.movie_playback_path = QFileInfo(path).path(); | ||||
| 
 | ||||
|     if (emulation_running) { | ||||
|         if (!ValidateMovie(path)) | ||||
|             return; | ||||
|     } else { | ||||
|         const QString invalid_movie_text = | ||||
|             tr("The movie file you are trying to load is invalid." | ||||
|                "<br/>Either the file is corrupted, or Citra has had made some major changes to the " | ||||
|                "Movie module." | ||||
|                "<br/>Please choose a different movie file and try again."); | ||||
|         u64 program_id = Core::Movie::GetInstance().GetMovieProgramID(path.toStdString()); | ||||
|         if (!program_id) { | ||||
|             QMessageBox::critical(this, tr("Invalid Movie File"), invalid_movie_text); | ||||
|             return; | ||||
|         } | ||||
|         QString game_path = game_list->FindGameByProgramID(program_id); | ||||
|         if (game_path.isEmpty()) { | ||||
|             QMessageBox::warning(this, tr("Game Not Found"), | ||||
|                                  tr("The movie you are trying to play is from a game that is not " | ||||
|                                     "in the game list. If you own the game, please add the game " | ||||
|                                     "folder to the game list and try to play the movie again.")); | ||||
|             return; | ||||
|         } | ||||
|         if (!ValidateMovie(path, program_id)) | ||||
|             return; | ||||
|         Core::Movie::GetInstance().PrepareForPlayback(path.toStdString()); | ||||
|         BootGame(game_path); | ||||
|     } | ||||
|     Core::Movie::GetInstance().StartPlayback(path.toStdString(), [this] { | ||||
|         QMetaObject::invokeMethod(this, "OnMoviePlaybackCompleted"); | ||||
|     }); | ||||
|     ui->action_Record_Movie->setEnabled(false); | ||||
|     ui->action_Play_Movie->setEnabled(false); | ||||
|     ui->action_Stop_Recording_Playback->setEnabled(true); | ||||
| 
 | ||||
|     const auto movie_path = dialog.GetMoviePath().toStdString(); | ||||
|     Core::Movie::GetInstance().PrepareForPlayback(movie_path); | ||||
|     BootGame(dialog.GetGamePath()); | ||||
| 
 | ||||
|     Core::Movie::GetInstance().StartPlayback(movie_path); | ||||
|     ui->action_Close_Movie->setEnabled(true); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnStopRecordingPlayback() { | ||||
| void GMainWindow::OnCloseMovie(bool shutting_down) { | ||||
|     if (movie_record_on_start) { | ||||
|         QMessageBox::information(this, tr("Record Movie"), tr("Movie recording cancelled.")); | ||||
|         movie_record_on_start = false; | ||||
|         movie_record_path.clear(); | ||||
|         movie_record_author.clear(); | ||||
|     } else { | ||||
|         const bool was_running = !shutting_down && emu_thread && emu_thread->IsRunning(); | ||||
|         if (was_running) { | ||||
|             OnPauseGame(); | ||||
|         } | ||||
| 
 | ||||
|         const bool was_recording = Core::Movie::GetInstance().IsRecordingInput(); | ||||
|         Core::Movie::GetInstance().Shutdown(); | ||||
|         if (was_recording) { | ||||
|             QMessageBox::information(this, tr("Movie Saved"), | ||||
|                                      tr("The movie is successfully saved.")); | ||||
|         } | ||||
| 
 | ||||
|         if (was_running) { | ||||
|             OnStartGame(); | ||||
|         } | ||||
|     } | ||||
|     ui->action_Record_Movie->setEnabled(true); | ||||
|     ui->action_Play_Movie->setEnabled(true); | ||||
|     ui->action_Stop_Recording_Playback->setEnabled(false); | ||||
| 
 | ||||
|     ui->action_Close_Movie->setEnabled(false); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnCaptureScreenshot() { | ||||
|  | @ -2345,9 +2272,7 @@ void GMainWindow::OnLanguageChanged(const QString& locale) { | |||
| 
 | ||||
| void GMainWindow::OnMoviePlaybackCompleted() { | ||||
|     QMessageBox::information(this, tr("Playback Completed"), tr("Movie playback completed.")); | ||||
|     ui->action_Record_Movie->setEnabled(true); | ||||
|     ui->action_Play_Movie->setEnabled(true); | ||||
|     ui->action_Stop_Recording_Playback->setEnabled(false); | ||||
|     ui->action_Close_Movie->setEnabled(false); | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::UpdateWindowTitle() { | ||||
|  |  | |||
|  | @ -208,7 +208,7 @@ private slots: | |||
|     void OnCreateGraphicsSurfaceViewer(); | ||||
|     void OnRecordMovie(); | ||||
|     void OnPlayMovie(); | ||||
|     void OnStopRecordingPlayback(); | ||||
|     void OnCloseMovie(bool shutting_down = false); | ||||
|     void OnCaptureScreenshot(); | ||||
| #ifdef ENABLE_FFMPEG_VIDEO_DUMPER | ||||
|     void OnStartVideoDumping(); | ||||
|  | @ -224,7 +224,6 @@ private slots: | |||
|     void OnMouseActivity(); | ||||
| 
 | ||||
| private: | ||||
|     bool ValidateMovie(const QString& path, u64 program_id = 0); | ||||
|     Q_INVOKABLE void OnMoviePlaybackCompleted(); | ||||
|     void UpdateStatusBar(); | ||||
|     void LoadTranslation(); | ||||
|  | @ -267,6 +266,7 @@ private: | |||
|     // Movie
 | ||||
|     bool movie_record_on_start = false; | ||||
|     QString movie_record_path; | ||||
|     QString movie_record_author; | ||||
| 
 | ||||
|     // Video dumping
 | ||||
|     bool video_dumping_on_start = false; | ||||
|  |  | |||
|  | @ -163,7 +163,9 @@ | |||
|      </property> | ||||
|      <addaction name="action_Record_Movie"/> | ||||
|      <addaction name="action_Play_Movie"/> | ||||
|      <addaction name="action_Stop_Recording_Playback"/> | ||||
|      <addaction name="action_Close_Movie"/> | ||||
|      <addaction name="separator"/> | ||||
|      <addaction name="action_Movie_Read_Only_Mode"/> | ||||
|     </widget> | ||||
|     <widget class="QMenu" name="menu_Frame_Advance"> | ||||
|      <property name="title"> | ||||
|  | @ -318,27 +320,29 @@ | |||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Record_Movie"> | ||||
|    <property name="enabled"> | ||||
|     <bool>true</bool> | ||||
|    </property> | ||||
|    <property name="text"> | ||||
|     <string>Record Movie</string> | ||||
|     <string>Record...</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Play_Movie"> | ||||
|    <property name="enabled"> | ||||
|    <property name="text"> | ||||
|     <string>Play...</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Close_Movie"> | ||||
|    <property name="text"> | ||||
|     <string>Close</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Movie_Read_Only_Mode"> | ||||
|    <property name="checkable"> | ||||
|     <bool>true</bool> | ||||
|    </property> | ||||
|    <property name="checked"> | ||||
|     <bool>true</bool> | ||||
|    </property> | ||||
|    <property name="text"> | ||||
|     <string>Play Movie</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Stop_Recording_Playback"> | ||||
|    <property name="enabled"> | ||||
|     <bool>false</bool> | ||||
|    </property> | ||||
|    <property name="text"> | ||||
|     <string>Stop Recording / Playback</string> | ||||
|     <string>Read-Only Mode</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Enable_Frame_Advancing"> | ||||
|  |  | |||
							
								
								
									
										130
									
								
								src/citra_qt/movie/movie_play_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								src/citra_qt/movie/movie_play_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,130 @@ | |||
| // Copyright 2020 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <QFileDialog> | ||||
| #include <QPushButton> | ||||
| #include <QTime> | ||||
| #include "citra_qt/game_list.h" | ||||
| #include "citra_qt/game_list_p.h" | ||||
| #include "citra_qt/movie/movie_play_dialog.h" | ||||
| #include "citra_qt/uisettings.h" | ||||
| #include "core/core.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/hle/service/hid/hid.h" | ||||
| #include "core/movie.h" | ||||
| #include "ui_movie_play_dialog.h" | ||||
| 
 | ||||
| MoviePlayDialog::MoviePlayDialog(QWidget* parent, GameList* game_list_) | ||||
|     : QDialog(parent), ui(std::make_unique<Ui::MoviePlayDialog>()), game_list(game_list_) { | ||||
|     ui->setupUi(this); | ||||
| 
 | ||||
|     ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); | ||||
| 
 | ||||
|     connect(ui->filePathButton, &QToolButton::clicked, this, &MoviePlayDialog::OnToolButtonClicked); | ||||
|     connect(ui->filePath, &QLineEdit::editingFinished, this, &MoviePlayDialog::UpdateUIDisplay); | ||||
|     connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &MoviePlayDialog::accept); | ||||
|     connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &MoviePlayDialog::reject); | ||||
| 
 | ||||
|     if (Core::System::GetInstance().IsPoweredOn()) { | ||||
|         QString note_text; | ||||
|         note_text = tr("Current running game will be stopped."); | ||||
|         if (Core::Movie::GetInstance().IsRecordingInput()) { | ||||
|             note_text.append(tr("<br>Current recording will be discarded.")); | ||||
|         } | ||||
|         ui->note2Label->setText(note_text); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| MoviePlayDialog::~MoviePlayDialog() = default; | ||||
| 
 | ||||
| QString MoviePlayDialog::GetMoviePath() const { | ||||
|     return ui->filePath->text(); | ||||
| } | ||||
| 
 | ||||
| QString MoviePlayDialog::GetGamePath() const { | ||||
|     const auto metadata = Core::Movie::GetInstance().GetMovieMetadata(GetMoviePath().toStdString()); | ||||
|     return game_list->FindGameByProgramID(metadata.program_id, GameListItemPath::FullPathRole); | ||||
| } | ||||
| 
 | ||||
| void MoviePlayDialog::OnToolButtonClicked() { | ||||
|     const QString path = | ||||
|         QFileDialog::getOpenFileName(this, tr("Play Movie"), UISettings::values.movie_playback_path, | ||||
|                                      tr("Citra TAS Movie (*.ctm)")); | ||||
|     if (path.isEmpty()) { | ||||
|         return; | ||||
|     } | ||||
|     ui->filePath->setText(path); | ||||
|     UISettings::values.movie_playback_path = path; | ||||
|     UpdateUIDisplay(); | ||||
| } | ||||
| 
 | ||||
| void MoviePlayDialog::UpdateUIDisplay() { | ||||
|     ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); | ||||
|     ui->gameLineEdit->clear(); | ||||
|     ui->authorLineEdit->clear(); | ||||
|     ui->rerecordCountLineEdit->clear(); | ||||
|     ui->lengthLineEdit->clear(); | ||||
|     ui->note1Label->setVisible(true); | ||||
| 
 | ||||
|     const auto path = GetMoviePath().toStdString(); | ||||
| 
 | ||||
|     const auto validation_result = Core::Movie::GetInstance().ValidateMovie(path); | ||||
|     if (validation_result == Core::Movie::ValidationResult::Invalid) { | ||||
|         ui->note1Label->setText(tr("Invalid movie file.")); | ||||
|         ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ui->note2Label->setVisible(true); | ||||
|     ui->infoGroupBox->setVisible(true); | ||||
| 
 | ||||
|     switch (validation_result) { | ||||
|     case Core::Movie::ValidationResult::OK: | ||||
|         ui->note1Label->setText(QString{}); | ||||
|         break; | ||||
|     case Core::Movie::ValidationResult::RevisionDismatch: | ||||
|         ui->note1Label->setText(tr("Revision dismatch, playback may desync.")); | ||||
|         break; | ||||
|     case Core::Movie::ValidationResult::InputCountDismatch: | ||||
|         ui->note1Label->setText(tr("Indicated length is incorrect, file may be corrupted.")); | ||||
|         ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); | ||||
|         break; | ||||
|     default: | ||||
|         UNREACHABLE(); | ||||
|     } | ||||
| 
 | ||||
|     const auto metadata = Core::Movie::GetInstance().GetMovieMetadata(path); | ||||
| 
 | ||||
|     // Format game title
 | ||||
|     const auto title = | ||||
|         game_list->FindGameByProgramID(metadata.program_id, GameListItemPath::TitleRole); | ||||
|     if (title.isEmpty()) { | ||||
|         ui->gameLineEdit->setText(tr("(unknown)")); | ||||
|         ui->note1Label->setText(tr("Game used in this movie is not in game list.")); | ||||
|         ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); | ||||
|     } else { | ||||
|         ui->gameLineEdit->setText(title); | ||||
|     } | ||||
| 
 | ||||
|     ui->authorLineEdit->setText(metadata.author.empty() ? tr("(unknown)") | ||||
|                                                         : QString::fromStdString(metadata.author)); | ||||
|     ui->rerecordCountLineEdit->setText( | ||||
|         metadata.rerecord_count == 0 ? tr("(unknown)") : QString::number(metadata.rerecord_count)); | ||||
| 
 | ||||
|     // Format length
 | ||||
|     if (metadata.input_count == 0) { | ||||
|         ui->lengthLineEdit->setText(tr("(unknown)")); | ||||
|     } else { | ||||
|         if (metadata.input_count > | ||||
|             BASE_CLOCK_RATE_ARM11 * 24 * 60 * 60 / Service::HID::Module::pad_update_ticks) { | ||||
|             // More than a day
 | ||||
|             ui->lengthLineEdit->setText(tr("(>1 day)")); | ||||
|         } else { | ||||
|             const u64 msecs = Service::HID::Module::pad_update_ticks * metadata.input_count * 1000 / | ||||
|                               BASE_CLOCK_RATE_ARM11; | ||||
|             ui->lengthLineEdit->setText( | ||||
|                 QTime::fromMSecsSinceStartOfDay(msecs).toString(QStringLiteral("hh:mm:ss.zzz"))); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										30
									
								
								src/citra_qt/movie/movie_play_dialog.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/citra_qt/movie/movie_play_dialog.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| // Copyright 2020 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <QDialog> | ||||
| 
 | ||||
| class GameList; | ||||
| 
 | ||||
| namespace Ui { | ||||
| class MoviePlayDialog; | ||||
| } | ||||
| 
 | ||||
| class MoviePlayDialog : public QDialog { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     explicit MoviePlayDialog(QWidget* parent, GameList* game_list); | ||||
|     ~MoviePlayDialog() override; | ||||
| 
 | ||||
|     QString GetMoviePath() const; | ||||
|     QString GetGamePath() const; | ||||
| 
 | ||||
| private: | ||||
|     void OnToolButtonClicked(); | ||||
|     void UpdateUIDisplay(); | ||||
| 
 | ||||
|     std::unique_ptr<Ui::MoviePlayDialog> ui; | ||||
|     GameList* game_list; | ||||
| }; | ||||
							
								
								
									
										136
									
								
								src/citra_qt/movie/movie_play_dialog.ui
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								src/citra_qt/movie/movie_play_dialog.ui
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,136 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>MoviePlayDialog</class> | ||||
|  <widget class="QDialog" name="MoviePlayDialog"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>600</width> | ||||
|     <height>100</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>Play Movie</string> | ||||
|   </property> | ||||
|   <layout class="QVBoxLayout"> | ||||
|    <item> | ||||
|     <layout class="QHBoxLayout"> | ||||
|      <item> | ||||
|       <widget class="QLabel"> | ||||
|        <property name="text"> | ||||
|         <string>File:</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QLineEdit" name="filePath"/> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QToolButton" name="filePathButton"> | ||||
|        <property name="text"> | ||||
|         <string>...</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|     </layout> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QLabel" name="note1Label"> | ||||
|      <property name="visible"> | ||||
|       <bool>false</bool> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QGroupBox" name="infoGroupBox"> | ||||
|      <property name="title"> | ||||
|       <string>Info</string> | ||||
|      </property> | ||||
|      <property name="visible"> | ||||
|       <bool>false</bool> | ||||
|      </property> | ||||
|      <layout class="QFormLayout"> | ||||
|       <item row="0" column="0"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Game:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="0" column="1"> | ||||
|        <widget class="QLineEdit" name="gameLineEdit"> | ||||
|         <property name="readOnly"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="1" column="0"> | ||||
|        <widget class="QLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Author:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="1" column="1"> | ||||
|        <widget class="QLineEdit" name="authorLineEdit"> | ||||
|         <property name="readOnly"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="0"> | ||||
|        <widget class="QLabel" name="rerecordCountLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Rerecord Count:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="2" column="1"> | ||||
|        <widget class="QLineEdit" name="rerecordCountLineEdit"> | ||||
|         <property name="readOnly"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="3" column="0"> | ||||
|        <widget class="QLabel" name="lengthLabel"> | ||||
|         <property name="text"> | ||||
|          <string>Length:</string> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|       <item row="3" column="1"> | ||||
|        <widget class="QLineEdit" name="lengthLineEdit"> | ||||
|         <property name="readOnly"> | ||||
|          <bool>true</bool> | ||||
|         </property> | ||||
|        </widget> | ||||
|       </item> | ||||
|      </layout> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <spacer> | ||||
|      <property name="orientation"> | ||||
|       <enum>Qt::Vertical</enum> | ||||
|      </property> | ||||
|     </spacer> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QLabel" name="note2Label"> | ||||
|      <property name="visible"> | ||||
|       <bool>false</bool> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QDialogButtonBox" name="buttonBox"> | ||||
|      <property name="standardButtons"> | ||||
|       <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
| </ui> | ||||
							
								
								
									
										61
									
								
								src/citra_qt/movie/movie_record_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/citra_qt/movie/movie_record_dialog.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,61 @@ | |||
| // Copyright 2020 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <QFileDialog> | ||||
| #include <QPushButton> | ||||
| #include "citra_qt/movie/movie_record_dialog.h" | ||||
| #include "citra_qt/uisettings.h" | ||||
| #include "core/core.h" | ||||
| #include "core/movie.h" | ||||
| #include "ui_movie_record_dialog.h" | ||||
| 
 | ||||
| MovieRecordDialog::MovieRecordDialog(QWidget* parent) | ||||
|     : QDialog(parent), ui(std::make_unique<Ui::MovieRecordDialog>()) { | ||||
|     ui->setupUi(this); | ||||
| 
 | ||||
|     ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); | ||||
| 
 | ||||
|     connect(ui->filePathButton, &QToolButton::clicked, this, | ||||
|             &MovieRecordDialog::OnToolButtonClicked); | ||||
|     connect(ui->filePath, &QLineEdit::editingFinished, this, &MovieRecordDialog::UpdateUIDisplay); | ||||
|     connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &MovieRecordDialog::accept); | ||||
|     connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &MovieRecordDialog::reject); | ||||
| 
 | ||||
|     QString note_text; | ||||
|     if (Core::System::GetInstance().IsPoweredOn()) { | ||||
|         note_text = tr("Current running game will be restarted."); | ||||
|         if (Core::Movie::GetInstance().IsRecordingInput()) { | ||||
|             note_text.append(tr("<br>Current recording will be discarded.")); | ||||
|         } | ||||
|     } else { | ||||
|         note_text = tr("Recording will start once you boot a game."); | ||||
|     } | ||||
|     ui->noteLabel->setText(note_text); | ||||
| } | ||||
| 
 | ||||
| MovieRecordDialog::~MovieRecordDialog() = default; | ||||
| 
 | ||||
| QString MovieRecordDialog::GetPath() const { | ||||
|     return ui->filePath->text(); | ||||
| } | ||||
| 
 | ||||
| QString MovieRecordDialog::GetAuthor() const { | ||||
|     return ui->authorLineEdit->text(); | ||||
| } | ||||
| 
 | ||||
| void MovieRecordDialog::OnToolButtonClicked() { | ||||
|     const QString path = | ||||
|         QFileDialog::getSaveFileName(this, tr("Record Movie"), UISettings::values.movie_record_path, | ||||
|                                      tr("Citra TAS Movie (*.ctm)")); | ||||
|     if (path.isEmpty()) { | ||||
|         return; | ||||
|     } | ||||
|     ui->filePath->setText(path); | ||||
|     UISettings::values.movie_record_path = path; | ||||
|     UpdateUIDisplay(); | ||||
| } | ||||
| 
 | ||||
| void MovieRecordDialog::UpdateUIDisplay() { | ||||
|     ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!ui->filePath->text().isEmpty()); | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/citra_qt/movie/movie_record_dialog.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/citra_qt/movie/movie_record_dialog.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| // Copyright 2020 Citra Emulator Project
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <QDialog> | ||||
| 
 | ||||
| namespace Ui { | ||||
| class MovieRecordDialog; | ||||
| } | ||||
| 
 | ||||
| class MovieRecordDialog : public QDialog { | ||||
|     Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|     explicit MovieRecordDialog(QWidget* parent); | ||||
|     ~MovieRecordDialog() override; | ||||
| 
 | ||||
|     QString GetPath() const; | ||||
|     QString GetAuthor() const; | ||||
| 
 | ||||
| private: | ||||
|     void OnToolButtonClicked(); | ||||
|     void UpdateUIDisplay(); | ||||
| 
 | ||||
|     std::unique_ptr<Ui::MovieRecordDialog> ui; | ||||
| }; | ||||
							
								
								
									
										71
									
								
								src/citra_qt/movie/movie_record_dialog.ui
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/citra_qt/movie/movie_record_dialog.ui
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>MovieRecordDialog</class> | ||||
|  <widget class="QDialog" name="MovieRecordDialog"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>600</width> | ||||
|     <height>150</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle"> | ||||
|    <string>Record Movie</string> | ||||
|   </property> | ||||
|   <layout class="QVBoxLayout"> | ||||
|    <item> | ||||
|     <layout class="QGridLayout"> | ||||
|      <item row="0" column="0"> | ||||
|       <widget class="QLabel"> | ||||
|        <property name="text"> | ||||
|         <string>File:</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item row="0" column="1"> | ||||
|       <widget class="QLineEdit" name="filePath"/> | ||||
|      </item> | ||||
|      <item row="0" column="2"> | ||||
|       <widget class="QToolButton" name="filePathButton"> | ||||
|        <property name="text"> | ||||
|         <string>...</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item row="1" column="0"> | ||||
|       <widget class="QLabel"> | ||||
|        <property name="text"> | ||||
|         <string>Author:</string> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item row="1" column="1"> | ||||
|       <widget class="QLineEdit" name="authorLineEdit"> | ||||
|        <property name="maxLength"> | ||||
|         <number>32</number> | ||||
|        </property> | ||||
|       </widget> | ||||
|      </item> | ||||
|     </layout> | ||||
|    </item> | ||||
|    <item> | ||||
|     <spacer> | ||||
|      <property name="orientation"> | ||||
|       <enum>Qt::Vertical</enum> | ||||
|      </property> | ||||
|     </spacer> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QLabel" name="noteLabel"/> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QDialogButtonBox" name="buttonBox"> | ||||
|      <property name="standardButtons"> | ||||
|       <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
| </ui> | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue