summaryrefslogtreecommitdiff
path: root/src/citra_qt/main.cpp
diff options
context:
space:
mode:
authorGravatar James Rowe2018-01-11 19:21:20 -0700
committerGravatar James Rowe2018-01-12 19:11:03 -0700
commitebf9a784a9f7f4148a669dbb39e7cd50df779a14 (patch)
treed585685a1c0a34b903af1d086d62560bf56bb29f /src/citra_qt/main.cpp
parentconfig: Default CPU core to Unicorn. (diff)
downloadyuzu-ebf9a784a9f7f4148a669dbb39e7cd50df779a14.tar.gz
yuzu-ebf9a784a9f7f4148a669dbb39e7cd50df779a14.tar.xz
yuzu-ebf9a784a9f7f4148a669dbb39e7cd50df779a14.zip
Massive removal of unused modules
Diffstat (limited to 'src/citra_qt/main.cpp')
-rw-r--r--src/citra_qt/main.cpp877
1 files changed, 0 insertions, 877 deletions
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
deleted file mode 100644
index 943aee30d..000000000
--- a/src/citra_qt/main.cpp
+++ /dev/null
@@ -1,877 +0,0 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <cinttypes>
6#include <clocale>
7#include <memory>
8#include <thread>
9#include <glad/glad.h>
10#define QT_NO_OPENGL
11#include <QDesktopWidget>
12#include <QFileDialog>
13#include <QMessageBox>
14#include <QtGui>
15#include <QtWidgets>
16#include "citra_qt/bootmanager.h"
17#include "citra_qt/configuration/config.h"
18#include "citra_qt/configuration/configure_dialog.h"
19#include "citra_qt/debugger/graphics/graphics.h"
20#include "citra_qt/debugger/graphics/graphics_breakpoints.h"
21#include "citra_qt/debugger/graphics/graphics_cmdlists.h"
22#include "citra_qt/debugger/graphics/graphics_surface.h"
23#include "citra_qt/debugger/graphics/graphics_tracing.h"
24#include "citra_qt/debugger/graphics/graphics_vertex_shader.h"
25#include "citra_qt/debugger/profiler.h"
26#include "citra_qt/debugger/registers.h"
27#include "citra_qt/debugger/wait_tree.h"
28#include "citra_qt/game_list.h"
29#include "citra_qt/hotkeys.h"
30#include "citra_qt/main.h"
31#include "citra_qt/ui_settings.h"
32#include "common/logging/backend.h"
33#include "common/logging/filter.h"
34#include "common/logging/log.h"
35#include "common/logging/text_formatter.h"
36#include "common/microprofile.h"
37#include "common/platform.h"
38#include "common/scm_rev.h"
39#include "common/scope_exit.h"
40#include "common/string_util.h"
41#include "core/core.h"
42#include "core/gdbstub/gdbstub.h"
43#include "core/loader/loader.h"
44#include "core/settings.h"
45
46#ifdef QT_STATICPLUGIN
47Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
48#endif
49
50/**
51 * "Callouts" are one-time instructional messages shown to the user. In the config settings, there
52 * is a bitfield "callout_flags" options, used to track if a message has already been shown to the
53 * user. This is 32-bits - if we have more than 32 callouts, we should retire and recyle old ones.
54 */
55enum class CalloutFlag : uint32_t {
56 Telemetry = 0x1,
57};
58
59static void ShowCalloutMessage(const QString& message, CalloutFlag flag) {
60 if (UISettings::values.callout_flags & static_cast<uint32_t>(flag)) {
61 return;
62 }
63
64 UISettings::values.callout_flags |= static_cast<uint32_t>(flag);
65
66 QMessageBox msg;
67 msg.setText(message);
68 msg.setStandardButtons(QMessageBox::Ok);
69 msg.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
70 msg.setStyleSheet("QLabel{min-width: 900px;}");
71 msg.exec();
72}
73
74void GMainWindow::ShowCallouts() {
75 static const QString telemetry_message =
76 tr("To help improve Citra, the Citra Team collects anonymous usage data. No private or "
77 "personally identifying information is collected. This data helps us to understand how "
78 "people use Citra and prioritize our efforts. Furthermore, it helps us to more easily "
79 "identify emulation bugs and performance issues. This data includes:<ul><li>Information"
80 " about the version of Citra you are using</li><li>Performance data about the games you "
81 "play</li><li>Your configuration settings</li><li>Information about your computer "
82 "hardware</li><li>Emulation errors and crash information</li></ul>By default, this "
83 "feature is enabled. To disable this feature, click 'Emulation' from the menu and then "
84 "select 'Configure...'. Then, on the 'Web' tab, uncheck 'Share anonymous usage data with"
85 " the Citra team'. <br/><br/>By using this software, you agree to the above terms.<br/>"
86 "<br/><a href='https://citra-emu.org/entry/telemetry-and-why-thats-a-good-thing/'>Learn "
87 "more</a>");
88 ShowCalloutMessage(telemetry_message, CalloutFlag::Telemetry);
89}
90
91GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
92 Pica::g_debug_context = Pica::DebugContext::Construct();
93 setAcceptDrops(true);
94 ui.setupUi(this);
95 statusBar()->hide();
96
97 InitializeWidgets();
98 InitializeDebugWidgets();
99 InitializeRecentFileMenuActions();
100 InitializeHotkeys();
101
102 SetDefaultUIGeometry();
103 RestoreUIState();
104
105 ConnectMenuEvents();
106 ConnectWidgetEvents();
107
108 setWindowTitle(QString("Citra %1| %2-%3")
109 .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
110 show();
111
112 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
113
114 UpdateUITheme();
115
116 // Show one-time "callout" messages to the user
117 ShowCallouts();
118
119 QStringList args = QApplication::arguments();
120 if (args.length() >= 2) {
121 BootGame(args[1]);
122 }
123}
124
125GMainWindow::~GMainWindow() {
126 // will get automatically deleted otherwise
127 if (render_window->parent() == nullptr)
128 delete render_window;
129
130 Pica::g_debug_context.reset();
131}
132
133void GMainWindow::InitializeWidgets() {
134 render_window = new GRenderWindow(this, emu_thread.get());
135 render_window->hide();
136
137 game_list = new GameList(this);
138 ui.horizontalLayout->addWidget(game_list);
139
140 // Create status bar
141 message_label = new QLabel();
142 // Configured separately for left alignment
143 message_label->setVisible(false);
144 message_label->setFrameStyle(QFrame::NoFrame);
145 message_label->setContentsMargins(4, 0, 4, 0);
146 message_label->setAlignment(Qt::AlignLeft);
147 statusBar()->addPermanentWidget(message_label, 1);
148
149 emu_speed_label = new QLabel();
150 emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% "
151 "indicate emulation is running faster or slower than a 3DS."));
152 game_fps_label = new QLabel();
153 game_fps_label->setToolTip(tr("How many frames per second the game is currently displaying. "
154 "This will vary from game to game and scene to scene."));
155 emu_frametime_label = new QLabel();
156 emu_frametime_label->setToolTip(
157 tr("Time taken to emulate a 3DS frame, not counting framelimiting or v-sync. For "
158 "full-speed emulation this should be at most 16.67 ms."));
159
160 for (auto& label : {emu_speed_label, game_fps_label, emu_frametime_label}) {
161 label->setVisible(false);
162 label->setFrameStyle(QFrame::NoFrame);
163 label->setContentsMargins(4, 0, 4, 0);
164 statusBar()->addPermanentWidget(label, 0);
165 }
166 statusBar()->setVisible(true);
167 setStyleSheet("QStatusBar::item{border: none;}");
168}
169
170void GMainWindow::InitializeDebugWidgets() {
171 connect(ui.action_Create_Pica_Surface_Viewer, &QAction::triggered, this,
172 &GMainWindow::OnCreateGraphicsSurfaceViewer);
173
174 QMenu* debug_menu = ui.menu_View_Debugging;
175
176#if MICROPROFILE_ENABLED
177 microProfileDialog = new MicroProfileDialog(this);
178 microProfileDialog->hide();
179 debug_menu->addAction(microProfileDialog->toggleViewAction());
180#endif
181
182 registersWidget = new RegistersWidget(this);
183 addDockWidget(Qt::RightDockWidgetArea, registersWidget);
184 registersWidget->hide();
185 debug_menu->addAction(registersWidget->toggleViewAction());
186 connect(this, &GMainWindow::EmulationStarting, registersWidget,
187 &RegistersWidget::OnEmulationStarting);
188 connect(this, &GMainWindow::EmulationStopping, registersWidget,
189 &RegistersWidget::OnEmulationStopping);
190
191 graphicsWidget = new GPUCommandStreamWidget(this);
192 addDockWidget(Qt::RightDockWidgetArea, graphicsWidget);
193 graphicsWidget->hide();
194 debug_menu->addAction(graphicsWidget->toggleViewAction());
195
196 graphicsCommandsWidget = new GPUCommandListWidget(this);
197 addDockWidget(Qt::RightDockWidgetArea, graphicsCommandsWidget);
198 graphicsCommandsWidget->hide();
199 debug_menu->addAction(graphicsCommandsWidget->toggleViewAction());
200
201 graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(Pica::g_debug_context, this);
202 addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget);
203 graphicsBreakpointsWidget->hide();
204 debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
205
206 graphicsVertexShaderWidget = new GraphicsVertexShaderWidget(Pica::g_debug_context, this);
207 addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget);
208 graphicsVertexShaderWidget->hide();
209 debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction());
210
211 graphicsTracingWidget = new GraphicsTracingWidget(Pica::g_debug_context, this);
212 addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget);
213 graphicsTracingWidget->hide();
214 debug_menu->addAction(graphicsTracingWidget->toggleViewAction());
215 connect(this, &GMainWindow::EmulationStarting, graphicsTracingWidget,
216 &GraphicsTracingWidget::OnEmulationStarting);
217 connect(this, &GMainWindow::EmulationStopping, graphicsTracingWidget,
218 &GraphicsTracingWidget::OnEmulationStopping);
219
220 waitTreeWidget = new WaitTreeWidget(this);
221 addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget);
222 waitTreeWidget->hide();
223 debug_menu->addAction(waitTreeWidget->toggleViewAction());
224 connect(this, &GMainWindow::EmulationStarting, waitTreeWidget,
225 &WaitTreeWidget::OnEmulationStarting);
226 connect(this, &GMainWindow::EmulationStopping, waitTreeWidget,
227 &WaitTreeWidget::OnEmulationStopping);
228}
229
230void GMainWindow::InitializeRecentFileMenuActions() {
231 for (int i = 0; i < max_recent_files_item; ++i) {
232 actions_recent_files[i] = new QAction(this);
233 actions_recent_files[i]->setVisible(false);
234 connect(actions_recent_files[i], SIGNAL(triggered()), this, SLOT(OnMenuRecentFile()));
235
236 ui.menu_recent_files->addAction(actions_recent_files[i]);
237 }
238
239 UpdateRecentFiles();
240}
241
242void GMainWindow::InitializeHotkeys() {
243 RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
244 RegisterHotkey("Main Window", "Swap Screens", QKeySequence::NextChild);
245 RegisterHotkey("Main Window", "Start Emulation");
246 LoadHotkeys();
247
248 connect(GetHotkey("Main Window", "Load File", this), SIGNAL(activated()), this,
249 SLOT(OnMenuLoadFile()));
250 connect(GetHotkey("Main Window", "Start Emulation", this), SIGNAL(activated()), this,
251 SLOT(OnStartGame()));
252 connect(GetHotkey("Main Window", "Swap Screens", render_window), SIGNAL(activated()), this,
253 SLOT(OnSwapScreens()));
254}
255
256void GMainWindow::SetDefaultUIGeometry() {
257 // geometry: 55% of the window contents are in the upper screen half, 45% in the lower half
258 const QRect screenRect = QApplication::desktop()->screenGeometry(this);
259
260 const int w = screenRect.width() * 2 / 3;
261 const int h = screenRect.height() / 2;
262 const int x = (screenRect.x() + screenRect.width()) / 2 - w / 2;
263 const int y = (screenRect.y() + screenRect.height()) / 2 - h * 55 / 100;
264
265 setGeometry(x, y, w, h);
266}
267
268void GMainWindow::RestoreUIState() {
269 restoreGeometry(UISettings::values.geometry);
270 restoreState(UISettings::values.state);
271 render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
272#if MICROPROFILE_ENABLED
273 microProfileDialog->restoreGeometry(UISettings::values.microprofile_geometry);
274 microProfileDialog->setVisible(UISettings::values.microprofile_visible);
275#endif
276
277 game_list->LoadInterfaceLayout();
278
279 ui.action_Single_Window_Mode->setChecked(UISettings::values.single_window_mode);
280 ToggleWindowMode();
281
282 ui.action_Display_Dock_Widget_Headers->setChecked(UISettings::values.display_titlebar);
283 OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked());
284
285 ui.action_Show_Filter_Bar->setChecked(UISettings::values.show_filter_bar);
286 game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked());
287
288 ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar);
289 statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked());
290}
291
292void GMainWindow::ConnectWidgetEvents() {
293 connect(game_list, SIGNAL(GameChosen(QString)), this, SLOT(OnGameListLoadFile(QString)));
294 connect(game_list, SIGNAL(OpenSaveFolderRequested(u64)), this,
295 SLOT(OnGameListOpenSaveFolder(u64)));
296
297 connect(this, SIGNAL(EmulationStarting(EmuThread*)), render_window,
298 SLOT(OnEmulationStarting(EmuThread*)));
299 connect(this, SIGNAL(EmulationStopping()), render_window, SLOT(OnEmulationStopping()));
300
301 connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar);
302}
303
304void GMainWindow::ConnectMenuEvents() {
305 // File
306 connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile);
307 connect(ui.action_Select_Game_List_Root, &QAction::triggered, this,
308 &GMainWindow::OnMenuSelectGameListRoot);
309 connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close);
310
311 // Emulation
312 connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame);
313 connect(ui.action_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame);
314 connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame);
315 connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
316
317 // View
318 connect(ui.action_Single_Window_Mode, &QAction::triggered, this,
319 &GMainWindow::ToggleWindowMode);
320 connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this,
321 &GMainWindow::OnDisplayTitleBars);
322 ui.action_Show_Filter_Bar->setShortcut(tr("CTRL+F"));
323 connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar);
324 connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);
325}
326
327void GMainWindow::OnDisplayTitleBars(bool show) {
328 QList<QDockWidget*> widgets = findChildren<QDockWidget*>();
329
330 if (show) {
331 for (QDockWidget* widget : widgets) {
332 QWidget* old = widget->titleBarWidget();
333 widget->setTitleBarWidget(nullptr);
334 if (old != nullptr)
335 delete old;
336 }
337 } else {
338 for (QDockWidget* widget : widgets) {
339 QWidget* old = widget->titleBarWidget();
340 widget->setTitleBarWidget(new QWidget());
341 if (old != nullptr)
342 delete old;
343 }
344 }
345}
346
347bool GMainWindow::LoadROM(const QString& filename) {
348 // Shutdown previous session if the emu thread is still active...
349 if (emu_thread != nullptr)
350 ShutdownGame();
351
352 render_window->InitRenderTarget();
353 render_window->MakeCurrent();
354
355 if (!gladLoadGL()) {
356 QMessageBox::critical(this, tr("Error while initializing OpenGL 3.3 Core!"),
357 tr("Your GPU may not support OpenGL 3.3, or you do not "
358 "have the latest graphics driver."));
359 return false;
360 }
361
362 Core::System& system{Core::System::GetInstance()};
363
364 const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())};
365
366 Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt");
367
368 if (result != Core::System::ResultStatus::Success) {
369 switch (result) {
370 case Core::System::ResultStatus::ErrorGetLoader:
371 LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!",
372 filename.toStdString().c_str());
373 QMessageBox::critical(this, tr("Error while loading ROM!"),
374 tr("The ROM format is not supported."));
375 break;
376
377 case Core::System::ResultStatus::ErrorSystemMode:
378 LOG_CRITICAL(Frontend, "Failed to load ROM!");
379 QMessageBox::critical(this, tr("Error while loading ROM!"),
380 tr("Could not determine the system mode."));
381 break;
382
383 case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: {
384 QMessageBox::critical(
385 this, tr("Error while loading ROM!"),
386 tr("The game that you are trying to load must be decrypted before being used with "
387 "Citra. A real 3DS is required.<br/><br/>"
388 "For more information on dumping and decrypting games, please see the following "
389 "wiki pages: <ul>"
390 "<li><a href='https://citra-emu.org/wiki/dumping-game-cartridges/'>Dumping Game "
391 "Cartridges</a></li>"
392 "<li><a href='https://citra-emu.org/wiki/dumping-installed-titles/'>Dumping "
393 "Installed Titles</a></li>"
394 "</ul>"));
395 break;
396 }
397 case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat:
398 QMessageBox::critical(this, tr("Error while loading ROM!"),
399 tr("The ROM format is not supported."));
400 break;
401
402 case Core::System::ResultStatus::ErrorVideoCore:
403 QMessageBox::critical(
404 this, tr("An error occured in the video core."),
405 tr("Citra has encountered an error while running the video core, please see the "
406 "log for more details."
407 "For more information on accessing the log, please see the following page: "
408 "<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How "
409 "to "
410 "Upload the Log File</a>."
411 "Ensure that you have the latest graphics drivers for your GPU."));
412
413 break;
414
415 default:
416 QMessageBox::critical(
417 this, tr("Error while loading ROM!"),
418 tr("An unknown error occured. Please see the log for more details."));
419 break;
420 }
421 return false;
422 }
423 return true;
424}
425
426void GMainWindow::BootGame(const QString& filename) {
427 LOG_INFO(Frontend, "Citra starting...");
428 StoreRecentFile(filename); // Put the filename on top of the list
429
430 if (!LoadROM(filename))
431 return;
432
433 // Create and start the emulation thread
434 emu_thread = std::make_unique<EmuThread>(render_window);
435 emit EmulationStarting(emu_thread.get());
436 render_window->moveContext();
437 emu_thread->start();
438
439 connect(render_window, SIGNAL(Closed()), this, SLOT(OnStopGame()));
440 // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views
441 // before the CPU continues
442 connect(emu_thread.get(), SIGNAL(DebugModeEntered()), registersWidget,
443 SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection);
444 connect(emu_thread.get(), SIGNAL(DebugModeEntered()), waitTreeWidget,
445 SLOT(OnDebugModeEntered()), Qt::BlockingQueuedConnection);
446 connect(emu_thread.get(), SIGNAL(DebugModeLeft()), registersWidget, SLOT(OnDebugModeLeft()),
447 Qt::BlockingQueuedConnection);
448 connect(emu_thread.get(), SIGNAL(DebugModeLeft()), waitTreeWidget, SLOT(OnDebugModeLeft()),
449 Qt::BlockingQueuedConnection);
450
451 // Update the GUI
452 registersWidget->OnDebugModeEntered();
453 if (ui.action_Single_Window_Mode->isChecked()) {
454 game_list->hide();
455 }
456 status_bar_update_timer.start(2000);
457
458 render_window->show();
459 render_window->setFocus();
460
461 emulation_running = true;
462 OnStartGame();
463}
464
465void GMainWindow::ShutdownGame() {
466 emu_thread->RequestStop();
467
468 // Release emu threads from any breakpoints
469 // This belongs after RequestStop() and before wait() because if emulation stops on a GPU
470 // breakpoint after (or before) RequestStop() is called, the emulation would never be able
471 // to continue out to the main loop and terminate. Thus wait() would hang forever.
472 // TODO(bunnei): This function is not thread safe, but it's being used as if it were
473 Pica::g_debug_context->ClearBreakpoints();
474
475 emit EmulationStopping();
476
477 // Wait for emulation thread to complete and delete it
478 emu_thread->wait();
479 emu_thread = nullptr;
480
481 // The emulation is stopped, so closing the window or not does not matter anymore
482 disconnect(render_window, SIGNAL(Closed()), this, SLOT(OnStopGame()));
483
484 // Update the GUI
485 ui.action_Start->setEnabled(false);
486 ui.action_Start->setText(tr("Start"));
487 ui.action_Pause->setEnabled(false);
488 ui.action_Stop->setEnabled(false);
489 render_window->hide();
490 game_list->show();
491 game_list->setFilterFocus();
492
493 // Disable status bar updates
494 status_bar_update_timer.stop();
495 message_label->setVisible(false);
496 emu_speed_label->setVisible(false);
497 game_fps_label->setVisible(false);
498 emu_frametime_label->setVisible(false);
499
500 emulation_running = false;
501}
502
503void GMainWindow::StoreRecentFile(const QString& filename) {
504 UISettings::values.recent_files.prepend(filename);
505 UISettings::values.recent_files.removeDuplicates();
506 while (UISettings::values.recent_files.size() > max_recent_files_item) {
507 UISettings::values.recent_files.removeLast();
508 }
509
510 UpdateRecentFiles();
511}
512
513void GMainWindow::UpdateRecentFiles() {
514 unsigned int num_recent_files =
515 std::min(UISettings::values.recent_files.size(), static_cast<int>(max_recent_files_item));
516
517 for (unsigned int i = 0; i < num_recent_files; i++) {
518 QString text = QString("&%1. %2").arg(i + 1).arg(
519 QFileInfo(UISettings::values.recent_files[i]).fileName());
520 actions_recent_files[i]->setText(text);
521 actions_recent_files[i]->setData(UISettings::values.recent_files[i]);
522 actions_recent_files[i]->setToolTip(UISettings::values.recent_files[i]);
523 actions_recent_files[i]->setVisible(true);
524 }
525
526 for (int j = num_recent_files; j < max_recent_files_item; ++j) {
527 actions_recent_files[j]->setVisible(false);
528 }
529
530 // Grey out the recent files menu if the list is empty
531 if (num_recent_files == 0) {
532 ui.menu_recent_files->setEnabled(false);
533 } else {
534 ui.menu_recent_files->setEnabled(true);
535 }
536}
537
538void GMainWindow::OnGameListLoadFile(QString game_path) {
539 BootGame(game_path);
540}
541
542void GMainWindow::OnGameListOpenSaveFolder(u64 program_id) {
543 UNIMPLEMENTED();
544}
545
546void GMainWindow::OnMenuLoadFile() {
547 QString extensions;
548 for (const auto& piece : game_list->supported_file_extensions)
549 extensions += "*." + piece + " ";
550
551 QString file_filter = tr("3DS Executable") + " (" + extensions + ")";
552 file_filter += ";;" + tr("All Files (*.*)");
553
554 QString filename = QFileDialog::getOpenFileName(this, tr("Load File"),
555 UISettings::values.roms_path, file_filter);
556 if (!filename.isEmpty()) {
557 UISettings::values.roms_path = QFileInfo(filename).path();
558
559 BootGame(filename);
560 }
561}
562
563void GMainWindow::OnMenuSelectGameListRoot() {
564 QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
565 if (!dir_path.isEmpty()) {
566 UISettings::values.gamedir = dir_path;
567 game_list->PopulateAsync(dir_path, UISettings::values.gamedir_deepscan);
568 }
569}
570
571void GMainWindow::OnMenuRecentFile() {
572 QAction* action = qobject_cast<QAction*>(sender());
573 assert(action);
574
575 QString filename = action->data().toString();
576 QFileInfo file_info(filename);
577 if (file_info.exists()) {
578 BootGame(filename);
579 } else {
580 // Display an error message and remove the file from the list.
581 QMessageBox::information(this, tr("File not found"),
582 tr("File \"%1\" not found").arg(filename));
583
584 UISettings::values.recent_files.removeOne(filename);
585 UpdateRecentFiles();
586 }
587}
588
589void GMainWindow::OnStartGame() {
590 emu_thread->SetRunning(true);
591 qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");
592 qRegisterMetaType<std::string>("std::string");
593 connect(emu_thread.get(), SIGNAL(ErrorThrown(Core::System::ResultStatus, std::string)), this,
594 SLOT(OnCoreError(Core::System::ResultStatus, std::string)));
595
596 ui.action_Start->setEnabled(false);
597 ui.action_Start->setText(tr("Continue"));
598
599 ui.action_Pause->setEnabled(true);
600 ui.action_Stop->setEnabled(true);
601}
602
603void GMainWindow::OnPauseGame() {
604 emu_thread->SetRunning(false);
605
606 ui.action_Start->setEnabled(true);
607 ui.action_Pause->setEnabled(false);
608 ui.action_Stop->setEnabled(true);
609}
610
611void GMainWindow::OnStopGame() {
612 ShutdownGame();
613}
614
615void GMainWindow::ToggleWindowMode() {
616 if (ui.action_Single_Window_Mode->isChecked()) {
617 // Render in the main window...
618 render_window->BackupGeometry();
619 ui.horizontalLayout->addWidget(render_window);
620 render_window->setFocusPolicy(Qt::ClickFocus);
621 if (emulation_running) {
622 render_window->setVisible(true);
623 render_window->setFocus();
624 game_list->hide();
625 }
626
627 } else {
628 // Render in a separate window...
629 ui.horizontalLayout->removeWidget(render_window);
630 render_window->setParent(nullptr);
631 render_window->setFocusPolicy(Qt::NoFocus);
632 if (emulation_running) {
633 render_window->setVisible(true);
634 render_window->RestoreGeometry();
635 game_list->show();
636 }
637 }
638}
639
640void GMainWindow::OnConfigure() {
641 ConfigureDialog configureDialog(this);
642 auto result = configureDialog.exec();
643 if (result == QDialog::Accepted) {
644 configureDialog.applyConfiguration();
645 UpdateUITheme();
646 config->Save();
647 }
648}
649
650void GMainWindow::OnToggleFilterBar() {
651 game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked());
652 if (ui.action_Show_Filter_Bar->isChecked()) {
653 game_list->setFilterFocus();
654 } else {
655 game_list->clearFilter();
656 }
657}
658
659void GMainWindow::OnSwapScreens() {
660 Settings::values.swap_screen = !Settings::values.swap_screen;
661 Settings::Apply();
662}
663
664void GMainWindow::OnCreateGraphicsSurfaceViewer() {
665 auto graphicsSurfaceViewerWidget = new GraphicsSurfaceWidget(Pica::g_debug_context, this);
666 addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceViewerWidget);
667 // TODO: Maybe graphicsSurfaceViewerWidget->setFloating(true);
668 graphicsSurfaceViewerWidget->show();
669}
670
671void GMainWindow::UpdateStatusBar() {
672 if (emu_thread == nullptr) {
673 status_bar_update_timer.stop();
674 return;
675 }
676
677 auto results = Core::System::GetInstance().GetAndResetPerfStats();
678
679 emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
680 game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0));
681 emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));
682
683 emu_speed_label->setVisible(true);
684 game_fps_label->setVisible(true);
685 emu_frametime_label->setVisible(true);
686}
687
688void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) {
689 QMessageBox::StandardButton answer;
690 QString status_message;
691 const QString common_message =
692 tr("The game you are trying to load requires additional files from your 3DS to be dumped "
693 "before playing.<br/><br/>For more information on dumping these files, please see the "
694 "following wiki page: <a "
695 "href='https://citra-emu.org/wiki/"
696 "dumping-system-archives-and-the-shared-fonts-from-a-3ds-console/'>Dumping System "
697 "Archives and the Shared Fonts from a 3DS Console</a>.<br/><br/>Would you like to quit "
698 "back to the game list? Continuing emulation may result in crashes, corrupted save "
699 "data, or other bugs.");
700 switch (result) {
701 case Core::System::ResultStatus::ErrorSystemFiles: {
702 QString message = "Citra was unable to locate a 3DS system archive";
703 if (!details.empty()) {
704 message.append(tr(": %1. ").arg(details.c_str()));
705 } else {
706 message.append(". ");
707 }
708 message.append(common_message);
709
710 answer = QMessageBox::question(this, tr("System Archive Not Found"), message,
711 QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
712 status_message = "System Archive Missing";
713 break;
714 }
715
716 case Core::System::ResultStatus::ErrorSharedFont: {
717 QString message = tr("Citra was unable to locate the 3DS shared fonts. ");
718 message.append(common_message);
719 answer = QMessageBox::question(this, tr("Shared Fonts Not Found"), message,
720 QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
721 status_message = "Shared Font Missing";
722 break;
723 }
724
725 default:
726 answer = QMessageBox::question(
727 this, tr("Fatal Error"),
728 tr("Citra has encountered a fatal error, please see the log for more details. "
729 "For more information on accessing the log, please see the following page: "
730 "<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to "
731 "Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? "
732 "Continuing emulation may result in crashes, corrupted save data, or other bugs."),
733 QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
734 status_message = "Fatal Error encountered";
735 break;
736 }
737
738 if (answer == QMessageBox::Yes) {
739 if (emu_thread) {
740 ShutdownGame();
741 }
742 } else {
743 // Only show the message if the game is still running.
744 if (emu_thread) {
745 message_label->setText(status_message);
746 message_label->setVisible(true);
747 }
748 }
749}
750
751bool GMainWindow::ConfirmClose() {
752 if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)
753 return true;
754
755 QMessageBox::StandardButton answer =
756 QMessageBox::question(this, tr("Citra"), tr("Are you sure you want to close Citra?"),
757 QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
758 return answer != QMessageBox::No;
759}
760
761void GMainWindow::closeEvent(QCloseEvent* event) {
762 if (!ConfirmClose()) {
763 event->ignore();
764 return;
765 }
766
767 UISettings::values.geometry = saveGeometry();
768 UISettings::values.state = saveState();
769 UISettings::values.renderwindow_geometry = render_window->saveGeometry();
770#if MICROPROFILE_ENABLED
771 UISettings::values.microprofile_geometry = microProfileDialog->saveGeometry();
772 UISettings::values.microprofile_visible = microProfileDialog->isVisible();
773#endif
774 UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked();
775 UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked();
776 UISettings::values.show_filter_bar = ui.action_Show_Filter_Bar->isChecked();
777 UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked();
778 UISettings::values.first_start = false;
779
780 game_list->SaveInterfaceLayout();
781 SaveHotkeys();
782
783 // Shutdown session if the emu thread is active...
784 if (emu_thread != nullptr)
785 ShutdownGame();
786
787 render_window->close();
788
789 QWidget::closeEvent(event);
790}
791
792static bool IsSingleFileDropEvent(QDropEvent* event) {
793 const QMimeData* mimeData = event->mimeData();
794 return mimeData->hasUrls() && mimeData->urls().length() == 1;
795}
796
797void GMainWindow::dropEvent(QDropEvent* event) {
798 if (IsSingleFileDropEvent(event) && ConfirmChangeGame()) {
799 const QMimeData* mimeData = event->mimeData();
800 QString filename = mimeData->urls().at(0).toLocalFile();
801 BootGame(filename);
802 }
803}
804
805void GMainWindow::dragEnterEvent(QDragEnterEvent* event) {
806 if (IsSingleFileDropEvent(event)) {
807 event->acceptProposedAction();
808 }
809}
810
811void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
812 event->acceptProposedAction();
813}
814
815bool GMainWindow::ConfirmChangeGame() {
816 if (emu_thread == nullptr)
817 return true;
818
819 auto answer = QMessageBox::question(
820 this, tr("Citra"),
821 tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."),
822 QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
823 return answer != QMessageBox::No;
824}
825
826void GMainWindow::filterBarSetChecked(bool state) {
827 ui.action_Show_Filter_Bar->setChecked(state);
828 emit(OnToggleFilterBar());
829}
830
831void GMainWindow::UpdateUITheme() {
832 if (UISettings::values.theme != UISettings::themes[0].second) {
833 QString theme_uri(":" + UISettings::values.theme + "/style.qss");
834 QFile f(theme_uri);
835 if (!f.exists()) {
836 LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found");
837 } else {
838 f.open(QFile::ReadOnly | QFile::Text);
839 QTextStream ts(&f);
840 qApp->setStyleSheet(ts.readAll());
841 GMainWindow::setStyleSheet(ts.readAll());
842 }
843 } else {
844 qApp->setStyleSheet("");
845 GMainWindow::setStyleSheet("");
846 }
847}
848
849#ifdef main
850#undef main
851#endif
852
853int main(int argc, char* argv[]) {
854 Log::Filter log_filter(Log::Level::Info);
855 Log::SetFilter(&log_filter);
856
857 MicroProfileOnThreadCreate("Frontend");
858 SCOPE_EXIT({ MicroProfileShutdown(); });
859
860 // Init settings params
861 QCoreApplication::setOrganizationName("Citra team");
862 QCoreApplication::setApplicationName("Citra");
863
864 QApplication::setAttribute(Qt::AA_X11InitThreads);
865 QApplication app(argc, argv);
866
867 // Qt changes the locale and causes issues in float conversion using std::to_string() when
868 // generating shaders
869 setlocale(LC_ALL, "C");
870
871 GMainWindow main_window;
872 // After settings have been loaded by GMainWindow, apply the filter
873 log_filter.ParseFilterString(Settings::values.log_filter);
874
875 main_window.show();
876 return app.exec();
877}