From a929eda845381fd325d9ff26fa70493f6fd0d638 Mon Sep 17 00:00:00 2001 From: harry Date: Sat, 17 Feb 2024 07:54:00 -0500 Subject: [PATCH] Added JS log file functionality. --- src/drivers/Qt/QtScriptManager.cpp | 246 +++++++++++++++++++++++++++-- src/drivers/Qt/QtScriptManager.h | 38 +++++ 2 files changed, 267 insertions(+), 17 deletions(-) diff --git a/src/drivers/Qt/QtScriptManager.cpp b/src/drivers/Qt/QtScriptManager.cpp index f68d39ab..34533633 100644 --- a/src/drivers/Qt/QtScriptManager.cpp +++ b/src/drivers/Qt/QtScriptManager.cpp @@ -28,12 +28,14 @@ #include #endif +#include #include #include #include #include #include #include +#include #include #ifdef __QT_UI_TOOLS__ @@ -1744,6 +1746,7 @@ int QtScriptInstance::loadScriptFile( QString filepath ) evalResult.toString() + "\nStack:\n" + evalResult.property("stack").toString() + "\n"; print(msg); + emit errorNotify(); return -1; } else @@ -1920,6 +1923,8 @@ int QtScriptInstance::runFunc(QJSValue &func, const QJSValueList& args) callResult.toString() + "\nStack:\n" + callResult.property("stack").toString() + "\n"; print(msg); + + emit errorNotify(); } return retval; } @@ -1933,6 +1938,7 @@ int QtScriptInstance::call(const QString& funcName, const QJSValueList& args) if (!engine->globalObject().hasProperty(funcName)) { print(QString("No function exists: ") + funcName); + emit errorNotify(); return -1; } QJSValue func = engine->globalObject().property(funcName); @@ -1996,6 +2002,14 @@ void QtScriptInstance::onFrameFinish() } } //---------------------------------------------------- +void QtScriptInstance::flushLog() +{ + if (dialog != nullptr) + { + dialog->flushLog(); + } +} +//---------------------------------------------------- void QtScriptInstance::onGuiUpdate() { if (running && onGuiUpdateCallback != nullptr && onGuiUpdateCallback->isCallable()) @@ -2228,6 +2242,11 @@ void QtScriptManager::guiUpdate() script->onGuiUpdate(); } FCEU_WRAPPER_UNLOCK(); + + //for (auto script : scriptList) + //{ + // script->flushLog(); + //} } //---------------------------------------------------- //---- Qt Script Monitor Thread @@ -2269,14 +2288,29 @@ QScriptDialog_t::QScriptDialog_t(QWidget *parent) resize(512, 512); - setWindowTitle(tr("Qt Java Script Control")); + setWindowTitle(tr("JavaScript Control")); + menuBar = buildMenuBar(); mainLayout = new QVBoxLayout(); + mainLayout->setMenuBar( menuBar ); lbl = new QLabel(tr("Script File:")); scriptPath = new QLineEdit(); scriptArgs = new QLineEdit(); + browseButton = new QPushButton(tr("Browse")); + + hbox = new QHBoxLayout(); + hbox->addWidget(lbl); + hbox->addWidget(scriptPath); + hbox->addWidget(browseButton); + mainLayout->addLayout(hbox); + + hbox = new QHBoxLayout(); + lbl = new QLabel(tr("Arguments:")); + hbox->addWidget(lbl); + hbox->addWidget(scriptArgs); + mainLayout->addLayout(hbox); g_config->getOption("SDL.LastLoadJs", &filename); @@ -2287,9 +2321,6 @@ QScriptDialog_t::QScriptDialog_t(QWidget *parent) jsOutput = new QTextEdit(); jsOutput->setReadOnly(true); - hbox = new QHBoxLayout(); - - browseButton = new QPushButton(tr("Browse")); stopButton = new QPushButton(tr("Stop")); scriptInstance = new QtScriptInstance(this); @@ -2309,20 +2340,9 @@ QScriptDialog_t::QScriptDialog_t(QWidget *parent) connect(stopButton, SIGNAL(clicked()), this, SLOT(stopScript(void))); connect(startButton, SIGNAL(clicked()), this, SLOT(startScript(void))); - hbox->addWidget(browseButton); + hbox = new QHBoxLayout(); hbox->addWidget(stopButton); hbox->addWidget(startButton); - - mainLayout->addWidget(lbl); - mainLayout->addWidget(scriptPath); - mainLayout->addLayout(hbox); - - hbox = new QHBoxLayout(); - lbl = new QLabel(tr("Arguments:")); - - hbox->addWidget(lbl); - hbox->addWidget(scriptArgs); - mainLayout->addLayout(hbox); tabWidget = new QTabWidget(); @@ -2349,12 +2369,17 @@ QScriptDialog_t::QScriptDialog_t(QWidget *parent) tabWidget->addTab(propTree, tr("Global Properties")); + logFilepathLbl = new QLabel( tr("Logging to:") ); + logFilepath = new QLabel(); + logFilepath->setTextInteractionFlags(Qt::TextBrowserInteraction); + connect( logFilepath, SIGNAL(linkActivated(const QString&)), this, SLOT(onLogLinkClicked(const QString&)) ); closeButton = new QPushButton( tr("Close") ); closeButton->setIcon(style()->standardIcon(QStyle::SP_DialogCloseButton)); connect(closeButton, SIGNAL(clicked(void)), this, SLOT(closeWindow(void))); hbox = new QHBoxLayout(); - hbox->addStretch(5); + hbox->addWidget( logFilepathLbl, 1 ); + hbox->addWidget( logFilepath, 10 ); hbox->addWidget( closeButton, 1 ); mainLayout->addLayout( hbox ); @@ -2371,6 +2396,8 @@ QScriptDialog_t::QScriptDialog_t(QWidget *parent) periodicTimer->start(200); // 5hz restoreGeometry(settings.value("QScriptWindow/geometry").toByteArray()); + + connect(scriptInstance, SIGNAL(errorNotify()), this, SLOT(onScriptError(void))); } //---------------------------------------------------- QScriptDialog_t::~QScriptDialog_t(void) @@ -2408,6 +2435,169 @@ void QScriptDialog_t::closeWindow(void) deleteLater(); } //---------------------------------------------------- +QMenuBar *QScriptDialog_t::buildMenuBar(void) +{ + QMenu *fileMenu; + //QActionGroup *actGroup; + QAction *act; + int useNativeMenuBar=0; + + QMenuBar *menuBar = new QMenuBar(this); + + // This is needed for menu bar to show up on MacOS + g_config->getOption( "SDL.UseNativeMenuBar", &useNativeMenuBar ); + + menuBar->setNativeMenuBar( useNativeMenuBar ? true : false ); + + //----------------------------------------------------------------------- + // Menu Start + //----------------------------------------------------------------------- + // File + fileMenu = menuBar->addMenu(tr("&File")); + + // File -> Open Script + act = new QAction(tr("&Open Script"), this); + act->setShortcut(QKeySequence::Open); + act->setStatusTip(tr("Open Script")); + connect(act, SIGNAL(triggered()), this, SLOT(openScriptFile(void)) ); + + fileMenu->addAction(act); + + // File -> Save Log + act = new QAction(tr("&Save Log"), this); + //act->setShortcut(QKeySequence::Close); + act->setStatusTip(tr("Save Log")); + connect(act, &QAction::triggered, [ this ] { saveLog(false); } ); + + fileMenu->addAction(act); + + // File -> Save Log As + act = new QAction(tr("Save Log &As"), this); + //act->setShortcut(QKeySequence::Close); + act->setStatusTip(tr("Save Log As")); + connect(act, &QAction::triggered, [ this ] { saveLog(true); } ); + + fileMenu->addAction(act); + + // File -> Flush Log + act = new QAction(tr("Flush &Log"), this); + //act->setShortcut(QKeySequence::Close); + act->setStatusTip(tr("Flush Log to Disk")); + connect(act, SIGNAL(triggered()), this, SLOT(flushLog(void)) ); + + fileMenu->addAction(act); + + // File -> Close + act = new QAction(tr("&Close"), this); + act->setShortcut(QKeySequence::Close); + act->setStatusTip(tr("Close Window")); + connect(act, SIGNAL(triggered()), this, SLOT(closeWindow(void)) ); + + fileMenu->addAction(act); + + return menuBar; +} +//---------------------------------------------------- +void QScriptDialog_t::saveLog(bool openFileBrowser) +{ + if (logFile != nullptr) + { + if (logSavePath.isEmpty() || openFileBrowser) + { + QString initialPath; + QFileDialog dialog(this, tr("Save Log File") ); + QList urls; + bool useNativeFileDialogVal = false; + + g_config->getOption("SDL.UseNativeFileDialog", &useNativeFileDialogVal); + + const QStringList filters({ + "Any files (*)" + }); + + urls << QUrl::fromLocalFile( QDir::rootPath() ); + urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first()); + urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DesktopLocation).first()); + urls << QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DownloadLocation).first()); + urls << QUrl::fromLocalFile( QDir( FCEUI_GetBaseDirectory() ).absolutePath() ); + + dialog.setFileMode(QFileDialog::AnyFile); + + dialog.setNameFilters( filters ); + + dialog.setViewMode(QFileDialog::List); + dialog.setFilter( QDir::AllEntries | QDir::AllDirs | QDir::Hidden ); + dialog.setLabelText( QFileDialog::Accept, tr("Save") ); + + if (!initialPath.isEmpty() ) + { + dialog.setDirectory( initialPath ); + } + + dialog.setOption(QFileDialog::DontUseNativeDialog, !useNativeFileDialogVal); + dialog.setSidebarUrls(urls); + + int ret = dialog.exec(); + + if ( ret != QDialog::Rejected ) + { + QStringList fileList; + fileList = dialog.selectedFiles(); + + if ( fileList.size() > 0 ) + { + logSavePath = fileList[0]; + } + } + else + { + return; + } + } + + if (QFile::exists(logSavePath)) + { + QFile::remove(logSavePath); + } + printf("Saving Log File: %s\n", logSavePath.toLocal8Bit().data()); + FCEU_WRAPPER_LOCK(); + flushLog(); + if ( logFile->copy( logSavePath ) ) + { + // Log file needs to be reopened on a successful copy + logFile->reopen(); + } + FCEU_WRAPPER_UNLOCK(); + } +} +//---------------------------------------------------- +void QScriptDialog_t::flushLog() +{ + if (logFile != nullptr) + { + logFile->flush(); + } +} +//---------------------------------------------------- +void QScriptDialog_t::onScriptError() +{ + //printf("QScriptDialog_t::onScriptError\n"); + + flushLog(); +} +//---------------------------------------------------- +void QScriptDialog_t::onLogLinkClicked(const QString& link) +{ + QUrl url = QUrl::fromUserInput(link); + + if( url.isValid() ) + { + flushLog(); + + QDesktopServices::openUrl(url); + } +} +//---------------------------------------------------- void QScriptDialog_t::clearPropertyTree() { propTree->childMap.clear(); @@ -2760,9 +2950,26 @@ void QScriptDialog_t::openScriptFile(void) } //---------------------------------------------------- +void QScriptDialog_t::resetLog() +{ + if (logFile != nullptr) + { + delete logFile; + logFile = nullptr; + } + logFile = new QScriptLogFile(this); + logFile->setAutoRemove(true); + logFile->setFileTemplate(QDir::tempPath() + QString("/fceux_js_XXXXXX.log")); + logFile->open(); + QString link = QString("fileName() + QString("\">") + logFile->fileName() + QString(""); + logFilepath->setText( link ); +} +//---------------------------------------------------- void QScriptDialog_t::startScript(void) { FCEU_WRAPPER_LOCK(); + resetLog(); jsOutput->clear(); clearPropertyTree(); scriptInstance->resetEngine(); @@ -2834,6 +3041,11 @@ void QScriptDialog_t::logOutput(const QString& text) vbar->setValue( vbar->maximum() ); } } + + if (logFile != nullptr) + { + logFile->write( text.toLocal8Bit() ); + } } //---------------------------------------------------- bool FCEU_JSRerecordCountSkip() diff --git a/src/drivers/Qt/QtScriptManager.h b/src/drivers/Qt/QtScriptManager.h index c6028347..f94e1bdf 100644 --- a/src/drivers/Qt/QtScriptManager.h +++ b/src/drivers/Qt/QtScriptManager.h @@ -27,6 +27,10 @@ #include #include #include +#include +#include +#include +#include #include "Qt/main.h" #include "utils/mutex.h" @@ -596,6 +600,7 @@ public: void onFrameFinish(); void onGuiUpdate(); void checkForHang(); + void flushLog(); int runFunc(QJSValue &func, const QJSValueList& args = QJSValueList()); int throwError(QJSValue::ErrorType errorType, const QString &message = QString()); @@ -629,6 +634,9 @@ private: int frameAdvanceState = 0; bool running = false; +signals: + void errorNotify(); + public slots: Q_INVOKABLE void print(const QString& msg); Q_INVOKABLE void loadUI(const QString& uiFilePath); @@ -718,6 +726,24 @@ public: QMap childMap; }; +class QScriptLogFile : public QTemporaryFile +{ + Q_OBJECT + +public: + QScriptLogFile(QObject* parent = nullptr) + : QTemporaryFile(parent) + { + } + + ~QScriptLogFile(void){} + + void reopen() + { + open(QIODeviceBase::Append | QIODeviceBase::ReadWrite); + } +}; + class QScriptDialog_t : public QDialog { Q_OBJECT @@ -730,11 +756,15 @@ public: void logOutput(const QString& text); protected: + void resetLog(); void closeEvent(QCloseEvent *bar); void openJSKillMessageBox(void); void clearPropertyTree(); void loadPropertyTree(QJSValue& val, JsPropertyItem* parentItem = nullptr); + QMenuBar* buildMenuBar(); + QMenuBar* menuBar; + QScriptLogFile *logFile = nullptr; QTimer *periodicTimer; QLineEdit *scriptPath; QLineEdit *scriptArgs; @@ -747,15 +777,23 @@ protected: JsPropertyTree *propTree; QtScriptInstance *scriptInstance; QString emuThreadText; + QString logSavePath; + QLabel *logFilepathLbl; + QLabel *logFilepath; private: public slots: + void flushLog(); void closeWindow(void); + private slots: + void saveLog(bool openFileBrowser = false); void updatePeriodic(void); void openScriptFile(void); void startScript(void); void stopScript(void); + void onLogLinkClicked(const QString&); + void onScriptError(void); }; bool FCEU_JSRerecordCountSkip();