1 // Qt Desktop UI: works on Linux, Windows and Mac OSX
2 #include "ppsspp_config.h"
3 #include "mainwindow.h"
4 
5 #include <QApplication>
6 #include <QDesktopServices>
7 #include <QDesktopWidget>
8 #include <QFileDialog>
9 #include <QMessageBox>
10 
11 #include "Common/System/Display.h"
12 #include "Common/System/NativeApp.h"
13 #include "Common/System/System.h"
14 #include "Common/File/Path.h"
15 #include "Core/MIPS/MIPSDebugInterface.h"
16 #include "Core/Debugger/SymbolMap.h"
17 #include "Core/HLE/sceUmd.h"
18 #include "Core/SaveState.h"
19 #include "Core/System.h"
20 #include "GPU/GPUInterface.h"
21 #include "UI/GamepadEmu.h"
22 
MainWindow(QWidget * parent,bool fullscreen)23 MainWindow::MainWindow(QWidget *parent, bool fullscreen) :
24 	QMainWindow(parent),
25 	currentLanguage("en"),
26 	nextState(CORE_POWERDOWN),
27 	lastUIState(UISTATE_MENU)
28 {
29 	setWindowIcon(QIcon(qApp->applicationDirPath() + "/assets/icon_regular_72.png"));
30 
31 	SetGameTitle("");
32 	emugl = new MainUI(this);
33 
34 	setCentralWidget(emugl);
35 	createMenus();
36 	updateMenus();
37 
38 	SetFullScreen(fullscreen);
39 
40 	QObject::connect(emugl, SIGNAL(doubleClick()), this, SLOT(fullscrAct()));
41 	QObject::connect(emugl, SIGNAL(newFrame()), this, SLOT(newFrame()));
42 }
43 
clamp1(float x)44 inline float clamp1(float x) {
45 	if (x > 1.0f) return 1.0f;
46 	if (x < -1.0f) return -1.0f;
47 	return x;
48 }
49 
newFrame()50 void MainWindow::newFrame()
51 {
52 	if (lastUIState != GetUIState()) {
53 		lastUIState = GetUIState();
54 		if (lastUIState == UISTATE_INGAME && g_Config.bFullScreen && !QApplication::overrideCursor() && !g_Config.bShowTouchControls)
55 			QApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
56 		if (lastUIState != UISTATE_INGAME && g_Config.bFullScreen && QApplication::overrideCursor())
57 			QApplication::restoreOverrideCursor();
58 
59 		updateMenus();
60 	}
61 
62 	if (g_Config.bFullScreen != isFullScreen())
63 		SetFullScreen(g_Config.bFullScreen);
64 
65 	std::unique_lock<std::mutex> lock(msgMutex_);
66 	while (!msgQueue_.empty()) {
67 		MainWindowMsg msg = msgQueue_.front();
68 		msgQueue_.pop();
69 		switch (msg) {
70 		case MainWindowMsg::BOOT_DONE:
71 			bootDone();
72 			break;
73 		case MainWindowMsg::WINDOW_TITLE_CHANGED:
74 			std::unique_lock<std::mutex> lock(titleMutex_);
75 			setWindowTitle(QString::fromUtf8(newWindowTitle_.c_str()));
76 			break;
77 		}
78 	}
79 }
80 
updateMenuGroupInt(QActionGroup * group,int value)81 void MainWindow::updateMenuGroupInt(QActionGroup *group, int value) {
82 	foreach (QAction *action, group->actions()) {
83 		action->setChecked(action->data().toInt() == value);
84 	}
85 }
86 
updateMenus()87 void MainWindow::updateMenus()
88 {
89 	updateMenuGroupInt(saveStateGroup, g_Config.iCurrentStateSlot);
90 	updateMenuGroupInt(displayRotationGroup, g_Config.iInternalScreenRotation);
91 	updateMenuGroupInt(renderingResolutionGroup, g_Config.iInternalResolution);
92 	updateMenuGroupInt(renderingModeGroup, g_Config.iRenderingMode);
93 	updateMenuGroupInt(frameSkippingGroup, g_Config.iFrameSkip);
94 	updateMenuGroupInt(frameSkippingTypeGroup, g_Config.iFrameSkipType);
95 	updateMenuGroupInt(textureFilteringGroup, g_Config.iTexFiltering);
96 	updateMenuGroupInt(screenScalingFilterGroup, g_Config.iBufFilter);
97 	updateMenuGroupInt(textureScalingLevelGroup, g_Config.iTexScalingLevel);
98 	updateMenuGroupInt(textureScalingTypeGroup, g_Config.iTexScalingType);
99 
100 	foreach(QAction * action, windowGroup->actions()) {
101 		int width = (g_Config.IsPortrait() ? 272 : 480) * action->data().toInt();
102 		int height = (g_Config.IsPortrait() ? 480 : 272) * action->data().toInt();
103 		if (g_Config.iWindowWidth == width && g_Config.iWindowHeight == height) {
104 			action->setChecked(true);
105 			break;
106 		}
107 	}
108 	emit updateMenu();
109 }
110 
bootDone()111 void MainWindow::bootDone()
112 {
113 	if (nextState == CORE_RUNNING)
114 		runAct();
115 	updateMenus();
116 }
117 
118 /* SIGNALS */
loadAct()119 void MainWindow::loadAct()
120 {
121 	QString filename = QFileDialog::getOpenFileName(NULL, "Load File", g_Config.currentDirectory.c_str(), "PSP ROMs (*.pbp *.elf *.iso *.cso *.prx)");
122 	if (QFile::exists(filename))
123 	{
124 		QFileInfo info(filename);
125 		g_Config.currentDirectory = Path(info.absolutePath().toStdString());
126 		NativeMessageReceived("boot", filename.toStdString().c_str());
127 	}
128 }
129 
closeAct()130 void MainWindow::closeAct()
131 {
132 	updateMenus();
133 
134 	NativeMessageReceived("stop", "");
135 	SetGameTitle("");
136 }
137 
openmsAct()138 void MainWindow::openmsAct()
139 {
140 	QString confighome = getenv("XDG_CONFIG_HOME");
141 	QString memorystick = confighome + "/ppsspp";
142 	QDesktopServices::openUrl(QUrl(memorystick));
143 }
144 
SaveStateActionFinished(SaveState::Status status,const std::string & message,void * userdata)145 void SaveStateActionFinished(SaveState::Status status, const std::string &message, void *userdata)
146 {
147 	// TODO: Improve messaging?
148 	if (status == SaveState::Status::FAILURE)
149 	{
150 		QMessageBox msgBox;
151 		msgBox.setWindowTitle("Load Save State");
152 		msgBox.setText("Savestate failure. Please try again later");
153 		msgBox.exec();
154 		return;
155 	}
156 }
157 
qlstateAct()158 void MainWindow::qlstateAct()
159 {
160 	Path gamePath = PSP_CoreParameter().fileToStart;
161 	SaveState::LoadSlot(gamePath, 0, SaveStateActionFinished, this);
162 }
163 
qsstateAct()164 void MainWindow::qsstateAct()
165 {
166 	Path gamePath = PSP_CoreParameter().fileToStart;
167 	SaveState::SaveSlot(gamePath, 0, SaveStateActionFinished, this);
168 }
169 
lstateAct()170 void MainWindow::lstateAct()
171 {
172 	QFileDialog dialog(0,"Load state");
173 	dialog.setFileMode(QFileDialog::ExistingFile);
174 	QStringList filters;
175 	filters << "Save States (*.ppst)" << "|All files (*.*)";
176 	dialog.setNameFilters(filters);
177 	dialog.setAcceptMode(QFileDialog::AcceptOpen);
178 	if (dialog.exec())
179 	{
180 		QStringList fileNames = dialog.selectedFiles();
181 		SaveState::Load(Path(fileNames[0].toStdString()), -1, SaveStateActionFinished, this);
182 	}
183 }
184 
sstateAct()185 void MainWindow::sstateAct()
186 {
187 	QFileDialog dialog(0,"Save state");
188 	dialog.setFileMode(QFileDialog::AnyFile);
189 	dialog.setAcceptMode(QFileDialog::AcceptSave);
190 	QStringList filters;
191 	filters << "Save States (*.ppst)" << "|All files (*.*)";
192 	dialog.setNameFilters(filters);
193 	if (dialog.exec())
194 	{
195 		QStringList fileNames = dialog.selectedFiles();
196 		SaveState::Save(Path(fileNames[0].toStdString()), -1, SaveStateActionFinished, this);
197 	}
198 }
199 
recordDisplayAct()200 void MainWindow::recordDisplayAct()
201 {
202 	g_Config.bDumpFrames = !g_Config.bDumpFrames;
203 }
204 
useLosslessVideoCodecAct()205 void MainWindow::useLosslessVideoCodecAct()
206 {
207 	g_Config.bUseFFV1 = !g_Config.bUseFFV1;
208 }
209 
useOutputBufferAct()210 void MainWindow::useOutputBufferAct()
211 {
212 	g_Config.bDumpVideoOutput = !g_Config.bDumpVideoOutput;
213 }
214 
recordAudioAct()215 void MainWindow::recordAudioAct()
216 {
217 	g_Config.bDumpAudio = !g_Config.bDumpAudio;
218 }
219 
exitAct()220 void MainWindow::exitAct()
221 {
222 	closeAct();
223 	QApplication::exit(0);
224 }
225 
runAct()226 void MainWindow::runAct()
227 {
228 	NativeMessageReceived("run", "");
229 }
230 
pauseAct()231 void MainWindow::pauseAct()
232 {
233 	NativeMessageReceived("pause", "");
234 }
235 
stopAct()236 void MainWindow::stopAct()
237 {
238 	Core_Stop();
239 	NativeMessageReceived("stop", "");
240 }
241 
resetAct()242 void MainWindow::resetAct()
243 {
244 	updateMenus();
245 
246 	NativeMessageReceived("reset", "");
247 }
248 
switchUMDAct()249 void MainWindow::switchUMDAct()
250 {
251 	QString filename = QFileDialog::getOpenFileName(NULL, "Switch UMD", g_Config.currentDirectory.c_str(), "PSP ROMs (*.pbp *.elf *.iso *.cso *.prx)");
252 	if (QFile::exists(filename))
253 	{
254 		QFileInfo info(filename);
255 		g_Config.currentDirectory = Path(info.absolutePath().toStdString());
256 		__UmdReplace(Path(filename.toStdString()));
257 	}
258 }
259 
breakonloadAct()260 void MainWindow::breakonloadAct()
261 {
262 	g_Config.bAutoRun = !g_Config.bAutoRun;
263 }
264 
lmapAct()265 void MainWindow::lmapAct()
266 {
267 	QFileDialog dialog(0,"Load .MAP");
268 	dialog.setFileMode(QFileDialog::ExistingFile);
269 	QStringList filters;
270 	filters << "Maps (*.map)" << "|All files (*.*)";
271 	dialog.setNameFilters(filters);
272 	dialog.setAcceptMode(QFileDialog::AcceptOpen);
273 	QStringList fileNames;
274 	if (dialog.exec())
275 		fileNames = dialog.selectedFiles();
276 
277 	if (fileNames.count() > 0)
278 	{
279 		QString fileName = QFileInfo(fileNames[0]).absoluteFilePath();
280 		g_symbolMap->LoadSymbolMap(Path(fileName.toStdString()));
281 	}
282 }
283 
smapAct()284 void MainWindow::smapAct()
285 {
286 	QFileDialog dialog(0,"Save .MAP");
287 	dialog.setFileMode(QFileDialog::AnyFile);
288 	dialog.setAcceptMode(QFileDialog::AcceptSave);
289 	QStringList filters;
290 	filters << "Save .MAP (*.map)" << "|All files (*.*)";
291 	dialog.setNameFilters(filters);
292 	QStringList fileNames;
293 	if (dialog.exec())
294 	{
295 		fileNames = dialog.selectedFiles();
296 		g_symbolMap->SaveSymbolMap(Path(fileNames[0].toStdString()));
297 	}
298 }
299 
lsymAct()300 void MainWindow::lsymAct()
301 {
302 	QFileDialog dialog(0,"Load .SYM");
303 	dialog.setFileMode(QFileDialog::ExistingFile);
304 	QStringList filters;
305 	filters << "Symbols (*.sym)" << "|All files (*.*)";
306 	dialog.setNameFilters(filters);
307 	dialog.setAcceptMode(QFileDialog::AcceptOpen);
308 	QStringList fileNames;
309 	if (dialog.exec())
310 		fileNames = dialog.selectedFiles();
311 
312 	if (fileNames.count() > 0)
313 	{
314 		QString fileName = QFileInfo(fileNames[0]).absoluteFilePath();
315 		g_symbolMap->LoadNocashSym(Path(fileName.toStdString()));
316 	}
317 }
318 
ssymAct()319 void MainWindow::ssymAct()
320 {
321 	QFileDialog dialog(0,"Save .SYM");
322 	dialog.setFileMode(QFileDialog::AnyFile);
323 	dialog.setAcceptMode(QFileDialog::AcceptSave);
324 	QStringList filters;
325 	filters << "Save .SYM (*.sym)" << "|All files (*.*)";
326 	dialog.setNameFilters(filters);
327 	QStringList fileNames;
328 	if (dialog.exec())
329 	{
330 		fileNames = dialog.selectedFiles();
331 		g_symbolMap->SaveNocashSym(Path(fileNames[0].toStdString()));
332 	}
333 }
334 
resetTableAct()335 void MainWindow::resetTableAct()
336 {
337 	g_symbolMap->Clear();
338 }
339 
dumpNextAct()340 void MainWindow::dumpNextAct()
341 {
342 	gpu->DumpNextFrame();
343 }
344 
consoleAct()345 void MainWindow::consoleAct()
346 {
347 	LogManager::GetInstance()->GetConsoleListener()->Show(LogManager::GetInstance()->GetConsoleListener()->Hidden());
348 }
349 
raiseTopMost()350 void MainWindow::raiseTopMost()
351 {
352 	setWindowState( (windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
353 	raise();
354 	activateWindow();
355 }
356 
SetFullScreen(bool fullscreen)357 void MainWindow::SetFullScreen(bool fullscreen) {
358 	if (fullscreen) {
359 #if !PPSSPP_PLATFORM(MAC)
360 		menuBar()->hide();
361 
362 		emugl->setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
363 		// TODO: Shouldn't this be physicalSize()?
364 		emugl->resizeGL(emugl->size().width(), emugl->size().height());
365 		// TODO: Won't showFullScreen do this for us?
366 		setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
367 		setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
368 #endif
369 
370 		showFullScreen();
371 		InitPadLayout(dp_xres, dp_yres);
372 
373 		if (GetUIState() == UISTATE_INGAME && !g_Config.bShowTouchControls)
374 			QApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
375 	} else {
376 #if !PPSSPP_PLATFORM(MAC)
377 		menuBar()->show();
378 		updateMenus();
379 #endif
380 
381 		showNormal();
382 		SetWindowScale(-1);
383 		InitPadLayout(dp_xres, dp_yres);
384 
385 		if (GetUIState() == UISTATE_INGAME && QApplication::overrideCursor())
386 			QApplication::restoreOverrideCursor();
387 
388 		QDesktopWidget *desktop = QApplication::desktop();
389 		int screenNum = QProcessEnvironment::systemEnvironment().value("SDL_VIDEO_FULLSCREEN_HEAD", "0").toInt();
390 
391 		// Move window to the center of selected screen
392 		QRect rect = desktop->screenGeometry(screenNum);
393 		move((rect.width() - frameGeometry().width()) / 4, (rect.height() - frameGeometry().height()) / 4);
394 	}
395 }
396 
fullscrAct()397 void MainWindow::fullscrAct()
398 {
399 	// Toggle the current state.
400 	g_Config.bFullScreen = !isFullScreen();
401 	SetFullScreen(g_Config.bFullScreen);
402 
403 	QTimer::singleShot(1000, this, SLOT(raiseTopMost()));
404 }
405 
websiteAct()406 void MainWindow::websiteAct()
407 {
408 	QDesktopServices::openUrl(QUrl("https://www.ppsspp.org/"));
409 }
410 
forumAct()411 void MainWindow::forumAct()
412 {
413 	QDesktopServices::openUrl(QUrl("https://forums.ppsspp.org/"));
414 }
415 
goldAct()416 void MainWindow::goldAct()
417 {
418 	QDesktopServices::openUrl(QUrl("https://central.ppsspp.org/buygold"));
419 }
420 
gitAct()421 void MainWindow::gitAct()
422 {
423 	QDesktopServices::openUrl(QUrl("https://github.com/hrydgard/ppsspp/"));
424 }
425 
discordAct()426 void MainWindow::discordAct()
427 {
428 	QDesktopServices::openUrl(QUrl("https://discord.gg/5NJB6dD"));
429 }
430 
aboutAct()431 void MainWindow::aboutAct()
432 {
433 	QMessageBox::about(this, "About", QString("PPSSPP Qt %1\n\n"
434 	                                                    "PSP emulator and debugger\n\n"
435 	                                                    "Copyright (c) by Henrik Rydg\xc3\xa5rd and the PPSSPP Project 2012-\n"
436 	                                                    "Qt port maintained by xSacha\n\n"
437 	                                                    "Additional credits:\n"
438 	                                                    "    PSPSDK by #pspdev (freenode)\n"
439 	                                                    "    CISO decompression code by BOOSTER\n"
440 	                                                    "    zlib by Jean-loup Gailly (compression) and Mark Adler (decompression)\n"
441 	                                                    "    Qt project by Digia\n\n"
442 	                                                    "All trademarks are property of their respective owners.\n"
443 	                                                    "The emulator is for educational and development purposes only and it may not be used to play games you do not legally own.").arg(PPSSPP_GIT_VERSION));
444 }
445 
446 /* Private functions */
SetWindowScale(int zoom)447 void MainWindow::SetWindowScale(int zoom) {
448 	if (isFullScreen())
449 		fullscrAct();
450 
451 	int width, height;
452 	if (zoom == -1 && (g_Config.iWindowWidth <= 0 || g_Config.iWindowHeight <= 0)) {
453 		// Default to zoom level 2.
454 		zoom = 2;
455 	}
456 	if (zoom == -1) {
457 		// Take the last setting.
458 		width = g_Config.iWindowWidth;
459 		height = g_Config.iWindowHeight;
460 	} else {
461 		// Update to the specified factor.  Let's clamp first.
462 		if (zoom < 1)
463 			zoom = 1;
464 		if (zoom > 10)
465 			zoom = 10;
466 
467 		width = (g_Config.IsPortrait() ? 272 : 480) * zoom;
468 		height = (g_Config.IsPortrait() ? 480 : 272) * zoom;
469 	}
470 
471 	g_Config.iWindowWidth = width;
472 	g_Config.iWindowHeight = height;
473 
474 #if !PPSSPP_PLATFORM(MAC)
475 	emugl->setFixedSize(g_Config.iWindowWidth, g_Config.iWindowHeight);
476 	// TODO: Shouldn't this be scaled size?
477 	emugl->resizeGL(g_Config.iWindowWidth, g_Config.iWindowHeight);
478 	setFixedSize(sizeHint());
479 #else
480 	resize(g_Config.iWindowWidth, g_Config.iWindowHeight);
481 #endif
482 	updateMenus();
483 }
484 
SetGameTitle(QString text)485 void MainWindow::SetGameTitle(QString text)
486 {
487 	QString title = QString("PPSSPP %1").arg(PPSSPP_GIT_VERSION);
488 	if (text != "")
489 		title += QString(" - %1").arg(text);
490 
491 	setWindowTitle(title);
492 }
493 
loadLanguage(const QString & language,bool translate)494 void MainWindow::loadLanguage(const QString& language, bool translate)
495 {
496 	if (currentLanguage != language)
497 	{
498 		QLocale::setDefault(QLocale(language));
499 		QApplication::removeTranslator(&translator);
500 
501 		currentLanguage = language;
502 		if (translator.load(QString(":/languages/ppsspp_%1.qm").arg(language))) {
503 			QApplication::installTranslator(&translator);
504 		}
505 		if (translate)
506 			emit retranslate();
507 	}
508 }
509 
createMenus()510 void MainWindow::createMenus()
511 {
512 	// File
513 	MenuTree* fileMenu = new MenuTree(this, menuBar(),    QT_TR_NOOP("&File"));
514 	fileMenu->add(new MenuAction(this, SLOT(loadAct()),       QT_TR_NOOP("&Load..."), QKeySequence::Open))
515 		->addEnableState(UISTATE_MENU);
516 	fileMenu->addSeparator();
517 	fileMenu->add(new MenuAction(this, SLOT(openmsAct()),       QT_TR_NOOP("Open &Memory Stick")))
518 		->addEnableState(UISTATE_MENU);
519 	fileMenu->addSeparator();
520 	MenuTree* savestateMenu = new MenuTree(this, fileMenu, QT_TR_NOOP("Saves&tate slot"));
521 	saveStateGroup = new MenuActionGroup(this, savestateMenu, SLOT(saveStateGroup_triggered(QAction *)),
522 		QStringList() << "1" << "2" << "3" << "4" << "5",
523 		QList<int>() << 0 << 1 << 2 << 3 << 4);
524 	fileMenu->add(new MenuAction(this, SLOT(qlstateAct()),    QT_TR_NOOP("L&oad state"), Qt::Key_F4))
525 		->addDisableState(UISTATE_MENU);
526 	fileMenu->add(new MenuAction(this, SLOT(qsstateAct()),    QT_TR_NOOP("S&ave state"), Qt::Key_F2))
527 		->addDisableState(UISTATE_MENU);
528 	fileMenu->add(new MenuAction(this, SLOT(lstateAct()),     QT_TR_NOOP("&Load state file...")))
529 		->addDisableState(UISTATE_MENU);
530 	fileMenu->add(new MenuAction(this, SLOT(sstateAct()),     QT_TR_NOOP("&Save state file...")))
531 		->addDisableState(UISTATE_MENU);
532 	MenuTree* recordMenu = new MenuTree(this, fileMenu, QT_TR_NOOP("&Record"));
533 	recordMenu->add(new MenuAction(this, SLOT(recordDisplayAct()),         QT_TR_NOOP("Record &display")))
534 		->addEventChecked(&g_Config.bDumpFrames);
535 	recordMenu->add(new MenuAction(this, SLOT(useLosslessVideoCodecAct()), QT_TR_NOOP("&Use lossless video codec (FFV1)")))
536 		->addEventChecked(&g_Config.bUseFFV1);
537 	recordMenu->add(new MenuAction(this, SLOT(useOutputBufferAct()),       QT_TR_NOOP("Use output buffer for video")))
538 		->addEventChecked(&g_Config.bDumpVideoOutput);
539 	recordMenu->addSeparator();
540 	recordMenu->add(new MenuAction(this, SLOT(recordAudioAct()),        QT_TR_NOOP("Record &audio")))
541 		->addEventChecked(&g_Config.bDumpAudio);
542 	fileMenu->addSeparator();
543 	fileMenu->add(new MenuAction(this, SLOT(exitAct()),       QT_TR_NOOP("E&xit"), QKeySequence::Quit));
544 
545 	// Emulation
546 	MenuTree* emuMenu = new MenuTree(this, menuBar(),     QT_TR_NOOP("&Emulation"));
547 	emuMenu->add(new MenuAction(this, SLOT(pauseAct()),       QT_TR_NOOP("&Pause")))
548 		->addEnableState(UISTATE_INGAME);
549 	emuMenu->add(new MenuAction(this, SLOT(stopAct()),       QT_TR_NOOP("&Stop"), Qt::CTRL + Qt::Key_W))
550 		->addEnableState(UISTATE_INGAME);
551 	emuMenu->add(new MenuAction(this, SLOT(resetAct()),       QT_TR_NOOP("R&eset"), Qt::CTRL + Qt::Key_B))
552 		->addEnableState(UISTATE_INGAME);
553 	emuMenu->add(new MenuAction(this, SLOT(switchUMDAct()),       QT_TR_NOOP("Switch UMD"), Qt::CTRL + Qt::Key_U))
554 		->addEnableState(UISTATE_INGAME);
555 	MenuTree* displayRotationMenu = new MenuTree(this, emuMenu, QT_TR_NOOP("Display rotation"));
556 	displayRotationGroup = new MenuActionGroup(this, displayRotationMenu, SLOT(displayRotationGroup_triggered(QAction *)),
557 		QStringList() << "Landscape" << "Portrait" << "Landscape reversed" << "Portrait reversed",
558 		QList<int>() << 1 << 2 << 3 << 4);
559 
560 	// Debug
561 	MenuTree* debugMenu = new MenuTree(this, menuBar(),   QT_TR_NOOP("&Debug"));
562 	debugMenu->add(new MenuAction(this, SLOT(breakonloadAct()),   QT_TR_NOOP("Break on load")))
563 		->addEventUnchecked(&g_Config.bAutoRun);
564 	debugMenu->add(new MenuAction(this, SLOT(ignoreIllegalAct()),  QT_TR_NOOP("&Ignore illegal reads/writes")))
565 		->addEventChecked(&g_Config.bIgnoreBadMemAccess);
566 	debugMenu->addSeparator();
567 	debugMenu->add(new MenuAction(this, SLOT(lmapAct()),      QT_TR_NOOP("&Load MAP file...")))
568 		->addDisableState(UISTATE_MENU);
569 	debugMenu->add(new MenuAction(this, SLOT(smapAct()),      QT_TR_NOOP("&Save MAP file...")))
570 		->addDisableState(UISTATE_MENU);
571 	debugMenu->add(new MenuAction(this, SLOT(lsymAct()),      QT_TR_NOOP("Lo&ad SYM file...")))
572 		->addDisableState(UISTATE_MENU);
573 	debugMenu->add(new MenuAction(this, SLOT(ssymAct()),      QT_TR_NOOP("Sav&e SYM file...")))
574 		->addDisableState(UISTATE_MENU);
575 	debugMenu->add(new MenuAction(this, SLOT(resetTableAct()),QT_TR_NOOP("Reset s&ymbol table")))
576 		->addDisableState(UISTATE_MENU);
577 	debugMenu->addSeparator();
578 	debugMenu->add(new MenuAction(this, SLOT(takeScreen()),  QT_TR_NOOP("&Take screenshot"), Qt::Key_F12))
579 		->addDisableState(UISTATE_MENU);
580 	debugMenu->add(new MenuAction(this, SLOT(dumpNextAct()),  QT_TR_NOOP("D&ump next frame to log")))
581 		->addDisableState(UISTATE_MENU);
582 	debugMenu->add(new MenuAction(this, SLOT(statsAct()),   QT_TR_NOOP("Show debu&g statistics")))
583 		->addEventChecked(&g_Config.bShowDebugStats);
584 	debugMenu->addSeparator();
585 	debugMenu->add(new MenuAction(this, SLOT(consoleAct()),   QT_TR_NOOP("&Log console"), Qt::CTRL + Qt::Key_L))
586 		->addDisableState(UISTATE_MENU);
587 
588 	// Game settings
589 	MenuTree* gameSettingsMenu = new MenuTree(this, menuBar(), QT_TR_NOOP("&Game settings"));
590 	gameSettingsMenu->add(new MenuAction(this, SLOT(languageAct()),        QT_TR_NOOP("La&nguage...")));
591 	gameSettingsMenu->add(new MenuAction(this, SLOT(controlMappingAct()),        QT_TR_NOOP("C&ontrol mapping...")));
592 	gameSettingsMenu->add(new MenuAction(this, SLOT(displayLayoutEditorAct()),        QT_TR_NOOP("Display layout editor...")));
593 	gameSettingsMenu->add(new MenuAction(this, SLOT(moreSettingsAct()),        QT_TR_NOOP("&More settings...")));
594 	gameSettingsMenu->addSeparator();
595 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
596 	gameSettingsMenu->add(new MenuAction(this, SLOT(fullscrAct()), QT_TR_NOOP("&Fullscreen"), Qt::Key_F11))
597 #else
598 	gameSettingsMenu->add(new MenuAction(this, SLOT(fullscrAct()), QT_TR_NOOP("Fu&llscreen"), QKeySequence::FullScreen))
599 #endif
600 		->addEventChecked(&g_Config.bFullScreen);
601 	MenuTree* renderingResolutionMenu = new MenuTree(this, gameSettingsMenu, QT_TR_NOOP("&Rendering resolution"));
602 	renderingResolutionGroup = new MenuActionGroup(this, renderingResolutionMenu, SLOT(renderingResolutionGroup_triggered(QAction *)),
603 		QStringList() << "&Auto" << "&1x" << "&2x" << "&3x" << "&4x" << "&5x" << "&6x" << "&7x" << "&8x" << "&9x" << "1&0x",
604 		QList<int>() << 0 << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10);
605 	// - Window Size
606 	MenuTree* windowMenu = new MenuTree(this, gameSettingsMenu, QT_TR_NOOP("&Window size"));
607 	windowGroup = new MenuActionGroup(this, windowMenu, SLOT(windowGroup_triggered(QAction *)),
608 		QStringList() << "&1x" << "&2x" << "&3x" << "&4x" << "&5x" << "&6x" << "&7x" << "&8x" << "&9x" << "1&0x",
609 		QList<int>() << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10);
610 
611 	MenuTree* renderingModeMenu = new MenuTree(this, gameSettingsMenu, QT_TR_NOOP("Rendering m&ode"));
612 	renderingModeGroup = new MenuActionGroup(this, renderingModeMenu, SLOT(renderingModeGroup_triggered(QAction *)),
613 		QStringList() << "&Skip buffered effects (non-buffered, faster)" << "&Buffered rendering",
614 		QList<int>() << 0 << 1);
615 	MenuTree* frameSkippingMenu = new MenuTree(this, gameSettingsMenu, QT_TR_NOOP("&Frame skipping"));
616 	frameSkippingMenu->add(new MenuAction(this, SLOT(autoframeskipAct()),        QT_TR_NOOP("&Auto")))
617 		->addEventChecked(&g_Config.bAutoFrameSkip);
618 	frameSkippingMenu->addSeparator();
619 	frameSkippingGroup = new MenuActionGroup(this, frameSkippingMenu, SLOT(frameSkippinGroup_triggered(QAction *)),
620 		QStringList() << "&Off" << "&1" << "&2" << "&3" << "&4" << "&5" << "&6" << "&7" << "&8",
621 		QList<int>() << 0 << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8);
622 	MenuTree* frameSkippingTypeMenu = new MenuTree(this, gameSettingsMenu, QT_TR_NOOP("Frame skipping type"));
623 	frameSkippingTypeGroup = new MenuActionGroup(this, frameSkippingTypeMenu, SLOT(frameSkippingTypeGroup_triggered(QAction *)),
624 		QStringList() << "Skip number of frames" << "Skip percent of FPS",
625 		QList<int>() << 0 << 1);
626 	MenuTree* textureFilteringMenu = new MenuTree(this, gameSettingsMenu, QT_TR_NOOP("Te&xture filtering"));
627 	textureFilteringGroup = new MenuActionGroup(this, textureFilteringMenu, SLOT(textureFilteringGroup_triggered(QAction *)),
628 		QStringList() << "&Auto" << "&Nearest" << "&Linear" << "Auto Max &Quality",
629 		QList<int>() << 1 << 2 << 3 << 4);
630 	MenuTree* screenScalingFilterMenu = new MenuTree(this, gameSettingsMenu, QT_TR_NOOP("Scr&een scaling filter"));
631 	screenScalingFilterGroup = new MenuActionGroup(this, screenScalingFilterMenu, SLOT(screenScalingFilterGroup_triggered(QAction *)),
632 		QStringList() << "&Linear" << "&Nearest",
633 		QList<int>() << 0 << 1);
634 
635 	MenuTree* textureScalingMenu = new MenuTree(this, gameSettingsMenu, QT_TR_NOOP("&Texture scaling"));
636 	textureScalingLevelGroup = new MenuActionGroup(this, textureScalingMenu, SLOT(textureScalingLevelGroup_triggered(QAction *)),
637 		QStringList() << "&Off" << "&2x" << "&3x" << "&4x" << "&5x",
638 		QList<int>() << 1 << 2 << 3 << 4 << 5);
639 	textureScalingMenu->addSeparator();
640 	textureScalingTypeGroup = new MenuActionGroup(this, textureScalingMenu, SLOT(textureScalingTypeGroup_triggered(QAction *)),
641 		QStringList() << "&xBRZ" << "&Hybrid" << "&Bicubic" << "H&ybrid + bicubic",
642 		QList<int>() << 0 << 1 << 2 << 3);
643 	textureScalingMenu->addSeparator();
644 	textureScalingMenu->add(new MenuAction(this, SLOT(deposterizeAct()),        QT_TR_NOOP("&Deposterize")))
645 		->addEventChecked(&g_Config.bTexDeposterize);
646 
647 	gameSettingsMenu->add(new MenuAction(this, SLOT(transformAct()),     QT_TR_NOOP("&Hardware transform")))
648 		->addEventChecked(&g_Config.bHardwareTransform);
649 	gameSettingsMenu->add(new MenuAction(this, SLOT(vertexCacheAct()),   QT_TR_NOOP("&Vertex cache")))
650 		->addEventChecked(&g_Config.bVertexCache);
651 	gameSettingsMenu->add(new MenuAction(this, SLOT(showFPSAct()), QT_TR_NOOP("&Show FPS counter")))
652 		->addEventChecked(&g_Config.iShowFPSCounter);
653 	gameSettingsMenu->addSeparator();
654 	gameSettingsMenu->add(new MenuAction(this, SLOT(audioAct()),   QT_TR_NOOP("Enable s&ound")))
655 		->addEventChecked(&g_Config.bEnableSound);
656 	gameSettingsMenu->addSeparator();
657 	gameSettingsMenu->add(new MenuAction(this, SLOT(cheatsAct()),   QT_TR_NOOP("Enable &cheats"), Qt::CTRL + Qt::Key_T))
658 		->addEventChecked(&g_Config.bEnableCheats);
659 	gameSettingsMenu->addSeparator();
660 	gameSettingsMenu->add(new MenuAction(this, SLOT(chatAct()),   QT_TR_NOOP("Open chat"), Qt::CTRL + Qt::Key_C))
661 		->SetEnabledFunc([=]() {
662 			return g_Config.bEnableNetworkChat && GetUIState() == UISTATE_INGAME;
663 		});
664 
665 	// Help
666 	MenuTree* helpMenu = new MenuTree(this, menuBar(),    QT_TR_NOOP("&Help"));
667 	helpMenu->add(new MenuAction(this, SLOT(websiteAct()),    QT_TR_NOOP("Visit www.&ppsspp.org")));
668 	helpMenu->add(new MenuAction(this, SLOT(forumAct()),      QT_TR_NOOP("PPSSPP &forums")));
669 	helpMenu->add(new MenuAction(this, SLOT(goldAct()),       QT_TR_NOOP("Buy &Gold")));
670 	helpMenu->add(new MenuAction(this, SLOT(gitAct()),        QT_TR_NOOP("Git&Hub")));
671 	helpMenu->add(new MenuAction(this, SLOT(discordAct()),      QT_TR_NOOP("Discord")));
672 	helpMenu->addSeparator();
673 	helpMenu->add(new MenuAction(this, SLOT(aboutAct()),      QT_TR_NOOP("&About PPSSPP...")));
674 
675 	retranslate();
676 }
677