// Qt Desktop UI: works on Linux, Windows and Mac OSX #include "mainwindow.h" #include #include #include #include #include #include "base/display.h" #include "Core/MIPS/MIPSDebugInterface.h" #include "Core/Debugger/SymbolMap.h" #include "Core/SaveState.h" #include "Core/System.h" #include "GPU/GPUInterface.h" #include "UI/GamepadEmu.h" MainWindow::MainWindow(QWidget *parent, bool fullscreen) : QMainWindow(parent), currentLanguage("en"), nextState(CORE_POWERDOWN), lastUIState(UISTATE_MENU), dialogDisasm(0), memoryWindow(0), memoryTexWindow(0), displaylistWindow(0) { QDesktopWidget *desktop = QApplication::desktop(); int screenNum = QProcessEnvironment::systemEnvironment().value("SDL_VIDEO_FULLSCREEN_HEAD", "0").toInt(); // Move window to top left coordinate of selected screen QRect rect = desktop->screenGeometry(screenNum); move(rect.topLeft()); SetGameTitle(""); emugl = new MainUI(this); setCentralWidget(emugl); createMenus(); updateMenus(); SetWindowScale(-1); if(fullscreen) fullscrAct(); QObject::connect(emugl, SIGNAL(doubleClick()), this, SLOT(fullscrAct())); QObject::connect(emugl, SIGNAL(newFrame()), this, SLOT(newFrame())); } void MainWindow::ShowMemory(u32 addr) { if(memoryWindow) memoryWindow->Goto(addr); } inline float clamp1(float x) { if (x > 1.0f) return 1.0f; if (x < -1.0f) return -1.0f; return x; } void MainWindow::newFrame() { if (lastUIState != GetUIState()) { lastUIState = GetUIState(); if (lastUIState == UISTATE_INGAME && g_Config.bFullScreen && !QApplication::overrideCursor() && !g_Config.bShowTouchControls) QApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); if (lastUIState != UISTATE_INGAME && g_Config.bFullScreen && QApplication::overrideCursor()) QApplication::restoreOverrideCursor(); updateMenus(); } } void MainWindow::updateMenus() { foreach(QAction * action, anisotropicGroup->actions()) { if (g_Config.iAnisotropyLevel == action->data().toInt()) { action->setChecked(true); break; } } foreach(QAction * action, screenGroup->actions()) { int width = (g_Config.IsPortrait() ? 272 : 480) * action->data().toInt(); int height = (g_Config.IsPortrait() ? 480 : 272) * action->data().toInt(); if (g_Config.iWindowWidth == width && g_Config.iWindowHeight == height) { action->setChecked(true); break; } } foreach(QAction * action, displayLayoutGroup->actions()) { if (g_Config.iSmallDisplayZoomType == action->data().toInt()) { if (gpu) gpu->Resized(); action->setChecked(true); break; } } int defaultLevel = LogManager::GetInstance()->GetLogLevel(LogTypes::COMMON); foreach(QAction * action, defaultLogGroup->actions()) { if (defaultLevel == action->data().toInt()) { action->setChecked(true); break; } } int g3dLevel = LogManager::GetInstance()->GetLogLevel(LogTypes::G3D); foreach(QAction * action, g3dLogGroup->actions()) { if (g3dLevel == action->data().toInt()) { action->setChecked(true); break; } } int hleLevel = LogManager::GetInstance()->GetLogLevel(LogTypes::HLE); foreach(QAction * action, hleLogGroup->actions()) { if (hleLevel == action->data().toInt()) { action->setChecked(true); break; } } emit updateMenu(); } /* SLOTS */ void MainWindow::Boot() { dialogDisasm = new Debugger_Disasm(currentDebugMIPS, this, this); if(g_Config.bShowDebuggerOnLoad) dialogDisasm->show(); if(g_Config.bFullScreen != isFullScreen()) fullscrAct(); memoryWindow = new Debugger_Memory(currentDebugMIPS, this, this); memoryTexWindow = new Debugger_MemoryTex(this); displaylistWindow = new Debugger_DisplayList(currentDebugMIPS, this, this); notifyMapsLoaded(); if (nextState == CORE_RUNNING) runAct(); updateMenus(); } void MainWindow::openAct() { QString filename = QFileDialog::getOpenFileName(NULL, "Load File", g_Config.currentDirectory.c_str(), "PSP ROMs (*.pbp *.elf *.iso *.cso *.prx)"); if (QFile::exists(filename)) { QFileInfo info(filename); g_Config.currentDirectory = info.absolutePath().toStdString(); NativeMessageReceived("boot", filename.toStdString().c_str()); } } void MainWindow::closeAct() { if(dialogDisasm) dialogDisasm->Stop(); if(dialogDisasm && dialogDisasm->isVisible()) dialogDisasm->close(); if(memoryWindow && memoryWindow->isVisible()) memoryWindow->close(); if(memoryTexWindow && memoryTexWindow->isVisible()) memoryTexWindow->close(); if(displaylistWindow && displaylistWindow->isVisible()) displaylistWindow->close(); NativeMessageReceived("stop", ""); SetGameTitle(""); } void SaveStateActionFinished(bool result, void *userdata) { // TODO: Improve messaging? if (!result) { QMessageBox msgBox; msgBox.setWindowTitle("Load Save State"); msgBox.setText("Savestate failure. Please try again later"); msgBox.exec(); return; } } void MainWindow::qlstateAct() { std::string gamePath = PSP_CoreParameter().fileToStart; SaveState::LoadSlot(gamePath, 0, SaveStateActionFinished, this); } void MainWindow::qsstateAct() { std::string gamePath = PSP_CoreParameter().fileToStart; SaveState::SaveSlot(gamePath, 0, SaveStateActionFinished, this); } void MainWindow::lstateAct() { QFileDialog dialog(0,"Load state"); dialog.setFileMode(QFileDialog::ExistingFile); QStringList filters; filters << "Save States (*.ppst)" << "|All files (*.*)"; dialog.setNameFilters(filters); dialog.setAcceptMode(QFileDialog::AcceptOpen); if (dialog.exec()) { QStringList fileNames = dialog.selectedFiles(); SaveState::Load(fileNames[0].toStdString(), SaveStateActionFinished, this); } } void MainWindow::sstateAct() { QFileDialog dialog(0,"Save state"); dialog.setFileMode(QFileDialog::AnyFile); dialog.setAcceptMode(QFileDialog::AcceptSave); QStringList filters; filters << "Save States (*.ppst)" << "|All files (*.*)"; dialog.setNameFilters(filters); if (dialog.exec()) { QStringList fileNames = dialog.selectedFiles(); SaveState::Save(fileNames[0].toStdString(), SaveStateActionFinished, this); } } void MainWindow::exitAct() { closeAct(); QApplication::exit(0); } void MainWindow::runAct() { NativeMessageReceived("run", ""); } void MainWindow::pauseAct() { NativeMessageReceived("pause", ""); } void MainWindow::resetAct() { if(dialogDisasm) dialogDisasm->Stop(); if(dialogDisasm) dialogDisasm->close(); if(memoryWindow) memoryWindow->close(); if(memoryTexWindow) memoryTexWindow->close(); if(displaylistWindow) displaylistWindow->close(); NativeMessageReceived("reset", ""); } void MainWindow::runonloadAct() { g_Config.bAutoRun = !g_Config.bAutoRun; } void MainWindow::lmapAct() { QFileDialog dialog(0,"Load .MAP"); dialog.setFileMode(QFileDialog::ExistingFile); QStringList filters; filters << "Maps (*.map)" << "|All files (*.*)"; dialog.setNameFilters(filters); dialog.setAcceptMode(QFileDialog::AcceptOpen); QStringList fileNames; if (dialog.exec()) fileNames = dialog.selectedFiles(); if (fileNames.count() > 0) { QString fileName = QFileInfo(fileNames[0]).absoluteFilePath(); g_symbolMap->LoadSymbolMap(fileName.toStdString().c_str()); notifyMapsLoaded(); } } void MainWindow::smapAct() { QFileDialog dialog(0,"Save .MAP"); dialog.setFileMode(QFileDialog::AnyFile); dialog.setAcceptMode(QFileDialog::AcceptSave); QStringList filters; filters << "Save .MAP (*.map)" << "|All files (*.*)"; dialog.setNameFilters(filters); QStringList fileNames; if (dialog.exec()) { fileNames = dialog.selectedFiles(); g_symbolMap->SaveSymbolMap(fileNames[0].toStdString().c_str()); } } void MainWindow::resetTableAct() { g_symbolMap->Clear(); notifyMapsLoaded(); } void MainWindow::dumpNextAct() { gpu->DumpNextFrame(); } void MainWindow::disasmAct() { if(dialogDisasm) dialogDisasm->show(); } void MainWindow::dpyListAct() { if(displaylistWindow) displaylistWindow->show(); } void MainWindow::consoleAct() { LogManager::GetInstance()->GetConsoleListener()->Show(LogManager::GetInstance()->GetConsoleListener()->Hidden()); } void MainWindow::memviewAct() { if (memoryWindow) memoryWindow->show(); } void MainWindow::memviewTexAct() { if(memoryTexWindow) memoryTexWindow->show(); } void MainWindow::raiseTopMost() { setWindowState( (windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); raise(); activateWindow(); } void MainWindow::fullscrAct() { if(isFullScreen()) { g_Config.bFullScreen = false; menuBar()->show(); updateMenus(); showNormal(); SetWindowScale(-1); InitPadLayout(dp_xres, dp_yres); if (GetUIState() == UISTATE_INGAME && QApplication::overrideCursor()) QApplication::restoreOverrideCursor(); } else { g_Config.bFullScreen = true; menuBar()->hide(); emugl->setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); showFullScreen(); if (gpu) gpu->Resized(); InitPadLayout(dp_xres, dp_yres); if (GetUIState() == UISTATE_INGAME && !g_Config.bShowTouchControls) QApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); } QTimer::singleShot(1000, this, SLOT(raiseTopMost())); } void MainWindow::websiteAct() { QDesktopServices::openUrl(QUrl("http://www.ppsspp.org/")); } void MainWindow::forumAct() { QDesktopServices::openUrl(QUrl("http://forums.ppsspp.org/")); } void MainWindow::aboutAct() { QMessageBox::about(this, "About", QString::fromUtf8("PPSSPP Qt " PPSSPP_GIT_VERSION "\n\n" "PSP emulator and debugger\n\n" "Copyright (c) by Henrik Rydg\xc3\xa5rd and the PPSSPP Project 2012-\n" "Qt port maintained by xSacha\n\n" "Additional credits:\n" " PSPSDK by #pspdev (freenode)\n" " CISO decompression code by BOOSTER\n" " zlib by Jean-loup Gailly (compression) and Mark Adler (decompression)\n" " Qt project by Digia\n\n" "All trademarks are property of their respective owners.\n" "The emulator is for educational and development purposes only and it may not be used to play games you do not legally own.")); } /* Private functions */ void MainWindow::SetWindowScale(int zoom) { if (isFullScreen()) fullscrAct(); int width, height; if (zoom == -1 && (g_Config.iWindowWidth <= 0 || g_Config.iWindowHeight <= 0)) { // Default to zoom level 2. zoom = 2; } if (zoom == -1) { // Take the last setting. width = g_Config.iWindowWidth; height = g_Config.iWindowHeight; } else { // Update to the specified factor. Let's clamp first. if (zoom < 1) zoom = 1; if (zoom > 4) zoom = 4; width = (g_Config.IsPortrait() ? 272 : 480) * zoom; height = (g_Config.IsPortrait() ? 480 : 272) * zoom; } g_Config.iWindowWidth = width; g_Config.iWindowHeight = height; emugl->setFixedSize(g_Config.iWindowWidth, g_Config.iWindowHeight); setFixedSize(sizeHint()); } void MainWindow::SetGameTitle(QString text) { QString title = "PPSSPP " PPSSPP_GIT_VERSION; if (text != "") title += QString(" - %1").arg(text); setWindowTitle(title); } void MainWindow::loadLanguage(const QString& language, bool translate) { if (currentLanguage != language) { QLocale::setDefault(QLocale(language)); QApplication::removeTranslator(&translator); currentLanguage = language; if (translator.load(QString(":/languages/ppsspp_%1.qm").arg(language))) { QApplication::installTranslator(&translator); } if (translate) emit retranslate(); } } void MainWindow::createMenus() { // File MenuTree* fileMenu = new MenuTree(this, menuBar(), QT_TR_NOOP("&File")); fileMenu->add(new MenuAction(this, SLOT(openAct()), QT_TR_NOOP("&Open..."), QKeySequence::Open)) ->addEnableState(UISTATE_MENU); fileMenu->add(new MenuAction(this, SLOT(closeAct()), QT_TR_NOOP("&Close"), QKeySequence::Close)) ->addDisableState(UISTATE_MENU); fileMenu->addSeparator(); fileMenu->add(new MenuAction(this, SLOT(qlstateAct()), QT_TR_NOOP("Quickload State"), Qt::Key_F4)) ->addDisableState(UISTATE_MENU); fileMenu->add(new MenuAction(this, SLOT(qsstateAct()), QT_TR_NOOP("Quicksave State"), Qt::Key_F2)) ->addDisableState(UISTATE_MENU); fileMenu->add(new MenuAction(this, SLOT(lstateAct()), QT_TR_NOOP("&Load State File..."))) ->addDisableState(UISTATE_MENU); fileMenu->add(new MenuAction(this, SLOT(sstateAct()), QT_TR_NOOP("&Save State File..."))) ->addDisableState(UISTATE_MENU); fileMenu->addSeparator(); fileMenu->add(new MenuAction(this, SLOT(exitAct()), QT_TR_NOOP("E&xit"), QKeySequence::Quit)); // Emulation MenuTree* emuMenu = new MenuTree(this, menuBar(), QT_TR_NOOP("&Emulation")); emuMenu->add(new MenuAction(this, SLOT(runAct()), QT_TR_NOOP("&Run"), Qt::Key_F7)) ->addEnableStepping()->addEnableState(UISTATE_PAUSEMENU); emuMenu->add(new MenuAction(this, SLOT(pauseAct()), QT_TR_NOOP("&Pause"), Qt::Key_F8)) ->addEnableState(UISTATE_INGAME); emuMenu->add(new MenuAction(this, SLOT(resetAct()), QT_TR_NOOP("Re&set"))) ->addEnableState(UISTATE_INGAME); emuMenu->addSeparator(); emuMenu->add(new MenuAction(this, SLOT(runonloadAct()), QT_TR_NOOP("Run on &load"))) ->addEventChecked(&g_Config.bAutoRun); // Debug MenuTree* debugMenu = new MenuTree(this, menuBar(), QT_TR_NOOP("De&bug")); debugMenu->add(new MenuAction(this, SLOT(lmapAct()), QT_TR_NOOP("Load Map File..."))) ->addDisableState(UISTATE_MENU); debugMenu->add(new MenuAction(this, SLOT(smapAct()), QT_TR_NOOP("Save Map File..."))) ->addDisableState(UISTATE_MENU); debugMenu->add(new MenuAction(this, SLOT(resetTableAct()),QT_TR_NOOP("Reset Symbol Table"))) ->addDisableState(UISTATE_MENU); debugMenu->addSeparator(); debugMenu->add(new MenuAction(this, SLOT(dumpNextAct()), QT_TR_NOOP("Dump next frame to log"))) ->addDisableState(UISTATE_MENU); debugMenu->add(new MenuAction(this, SLOT(takeScreen()), QT_TR_NOOP("Take Screenshot"), Qt::Key_F12)) ->addDisableState(UISTATE_MENU); debugMenu->addSeparator(); debugMenu->add(new MenuAction(this, SLOT(disasmAct()), QT_TR_NOOP("Disassembly"), Qt::CTRL + Qt::Key_D)) ->addDisableState(UISTATE_MENU); //commented out until someone bothers to maintain it //debugMenu->add(new MenuAction(this, SLOT(dpyListAct()), QT_TR_NOOP("Display List..."))) // ->addDisableState(UISTATE_MENU); debugMenu->add(new MenuAction(this, SLOT(consoleAct()), QT_TR_NOOP("Log Console"))) ->addDisableState(UISTATE_MENU); debugMenu->add(new MenuAction(this, SLOT(memviewAct()), QT_TR_NOOP("Memory View"))) ->addDisableState(UISTATE_MENU); debugMenu->add(new MenuAction(this, SLOT(memviewTexAct()),QT_TR_NOOP("Memory View Texture"))) ->addDisableState(UISTATE_MENU); // Options MenuTree* optionsMenu = new MenuTree(this, menuBar(), QT_TR_NOOP("&Options")); // - Core MenuTree* coreMenu = new MenuTree(this, optionsMenu, QT_TR_NOOP("&Core")); coreMenu->add(new MenuAction(this, SLOT(vertexDynarecAct()), QT_TR_NOOP("&Vertex Decoder Dynarec"))) ->addEventChecked(&g_Config.bVertexDecoderJit); coreMenu->add(new MenuAction(this, SLOT(fastmemAct()), QT_TR_NOOP("Fast &Memory (unstable)"))) ->addEventChecked(&g_Config.bFastMemory); coreMenu->add(new MenuAction(this, SLOT(ignoreIllegalAct()), QT_TR_NOOP("&Ignore Illegal reads/writes"))) ->addEventChecked(&g_Config.bIgnoreBadMemAccess); // - Video MenuTree* videoMenu = new MenuTree(this, optionsMenu, QT_TR_NOOP("&Video")); // - Anisotropic Filtering MenuTree* anisotropicMenu = new MenuTree(this, videoMenu, QT_TR_NOOP("&Anisotropic Filtering")); anisotropicGroup = new MenuActionGroup(this, anisotropicMenu, SLOT(anisotropicGroup_triggered(QAction *)), QStringList() << "Off" << "2x" << "4x" << "8x" << "16x", QList() << 0 << 1 << 2 << 3 << 4); // TODO: Check for newer buffer render options videoMenu->add(new MenuAction(this, SLOT(bufferRenderAct()), QT_TR_NOOP("&Buffered Rendering"), Qt::Key_F5)) ->addEventChecked(&g_Config.iRenderingMode); videoMenu->add(new MenuAction(this, SLOT(linearAct()), QT_TR_NOOP("&Linear Filtering"))) ->addEventChecked(&g_Config.iTexFiltering); videoMenu->addSeparator(); // - Screen Size MenuTree* screenMenu = new MenuTree(this, videoMenu, QT_TR_NOOP("&Screen Size")); screenGroup = new MenuActionGroup(this, screenMenu, SLOT(screenGroup_triggered(QAction *)), QStringList() << "1x" << "2x" << "3x" << "4x", QList() << 1 << 2 << 3 << 4, QList() << Qt::CTRL + Qt::Key_1 << Qt::CTRL + Qt::Key_2 << Qt::CTRL + Qt::Key_3 << Qt::CTRL + Qt::Key_4); MenuTree* displayLayoutMenu = new MenuTree(this, videoMenu, QT_TR_NOOP("&Display Layout Options")); displayLayoutGroup = new MenuActionGroup(this, displayLayoutMenu, SLOT(displayLayoutGroup_triggered(QAction *)), QStringList() << "Stretched" << "Partialy stretched" << "Auto Scaling" << "Manual Scaling", QList() << 0 << 1 << 2 << 3); videoMenu->addSeparator(); videoMenu->add(new MenuAction(this, SLOT(transformAct()), QT_TR_NOOP("&Hardware Transform"), Qt::Key_F6)) ->addEventChecked(&g_Config.bHardwareTransform); videoMenu->add(new MenuAction(this, SLOT(vertexCacheAct()), QT_TR_NOOP("&Vertex Cache"))) ->addEventChecked(&g_Config.bVertexCache); videoMenu->add(new MenuAction(this, SLOT(frameskipAct()), QT_TR_NOOP("&Frameskip"))) ->addEventChecked(&g_Config.iFrameSkip); optionsMenu->add(new MenuAction(this, SLOT(audioAct()), QT_TR_NOOP("&Audio"))) ->addEventChecked(&g_Config.bEnableSound); optionsMenu->addSeparator(); #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) optionsMenu->add(new MenuAction(this, SLOT(fullscrAct()), QT_TR_NOOP("&Fullscreen"), Qt::Key_F11)) #else optionsMenu->add(new MenuAction(this, SLOT(fullscrAct()), QT_TR_NOOP("&Fullscreen"), QKeySequence::FullScreen)) #endif ->addEventChecked(&g_Config.bFullScreen); optionsMenu->add(new MenuAction(this, SLOT(statsAct()), QT_TR_NOOP("&Show debug statistics"))) ->addEventChecked(&g_Config.bShowDebugStats); optionsMenu->add(new MenuAction(this, SLOT(showFPSAct()), QT_TR_NOOP("&Show FPS"))) ->addEventChecked(&g_Config.iShowFPSCounter); optionsMenu->addSeparator(); // - Log Levels MenuTree* levelsMenu = new MenuTree(this, optionsMenu, QT_TR_NOOP("Lo&g levels")); QMenu* defaultLogMenu = levelsMenu->addMenu("Default"); defaultLogGroup = new MenuActionGroup(this, defaultLogMenu, SLOT(defaultLogGroup_triggered(QAction *)), QStringList() << "Debug" << "Warning" << "Info" << "Error", QList() << LogTypes::LDEBUG << LogTypes::LWARNING << LogTypes::LINFO << LogTypes::LERROR); QMenu* g3dLogMenu = levelsMenu->addMenu("G3D"); g3dLogGroup = new MenuActionGroup(this, g3dLogMenu, SLOT(g3dLogGroup_triggered(QAction *)), QStringList() << "Debug" << "Warning" << "Info" << "Error", QList() << LogTypes::LDEBUG << LogTypes::LWARNING << LogTypes::LINFO << LogTypes::LERROR); QMenu* hleLogMenu = levelsMenu->addMenu("HLE"); hleLogGroup = new MenuActionGroup(this, hleLogMenu, SLOT(hleLogGroup_triggered(QAction *)), QStringList() << "Debug" << "Warning" << "Info" << "Error", QList() << LogTypes::LDEBUG << LogTypes::LWARNING << LogTypes::LINFO << LogTypes::LERROR); optionsMenu->addSeparator(); // - Language MenuTree* langMenu = new MenuTree(this, optionsMenu, QT_TR_NOOP("&Language")); QActionGroup* langGroup = new QActionGroup(this); QStringList fileNames = QDir(":/languages").entryList(QStringList("ppsspp_*.qm")); if (fileNames.size() == 0) { QAction *action = new QAction("No translations", this); action->setDisabled(true); langGroup->addAction(action); } else { connect(langGroup, SIGNAL(triggered(QAction *)), this, SLOT(langChanged(QAction *))); bool found = false; QString currentLocale = g_Config.sLanguageIni.c_str(); QString currentLang = currentLocale.split('_').first(); for (int i = 0; i < fileNames.size(); ++i) { QString locale = fileNames[i]; locale.truncate(locale.lastIndexOf('.')); locale.remove(0, locale.indexOf('_') + 1); #if QT_VERSION >= QT_VERSION_CHECK(4, 8, 0) QString language = QLocale(locale).nativeLanguageName(); #else QString language = QLocale::languageToString(QLocale(locale).language()); #endif QAction *action = new MenuAction(this, langGroup, locale, language); std::string testLang = g_Config.sLanguageIni; if (currentLocale == locale || currentLang == locale) { action->setChecked(true); loadLanguage(locale, false); found = true; } if (!found && locale == "en") { action->setChecked(true); } } } langMenu->addActions(langGroup->actions()); // Help MenuTree* helpMenu = new MenuTree(this, menuBar(), QT_TR_NOOP("&Help")); helpMenu->add(new MenuAction(this, SLOT(websiteAct()), QT_TR_NOOP("Official &website"), QKeySequence::HelpContents)); helpMenu->add(new MenuAction(this, SLOT(forumAct()), QT_TR_NOOP("Official &forum"))); helpMenu->add(new MenuAction(this, SLOT(aboutAct()), QT_TR_NOOP("&About PPSSPP..."), QKeySequence::WhatsThis)); retranslate(); } void MainWindow::notifyMapsLoaded() { if (dialogDisasm) dialogDisasm->NotifyMapLoaded(); if (memoryWindow) memoryWindow->NotifyMapLoaded(); }