Added JS log file functionality.

This commit is contained in:
harry 2024-02-17 07:54:00 -05:00
parent ab46158f05
commit a929eda845
2 changed files with 267 additions and 17 deletions

View File

@ -28,12 +28,14 @@
#include <Windows.h> #include <Windows.h>
#endif #endif
#include <QUrl>
#include <QTextEdit> #include <QTextEdit>
#include <QFileInfo> #include <QFileInfo>
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>
#include <QSettings> #include <QSettings>
#include <QHeaderView> #include <QHeaderView>
#include <QDesktopServices>
#include <QJSValueIterator> #include <QJSValueIterator>
#ifdef __QT_UI_TOOLS__ #ifdef __QT_UI_TOOLS__
@ -1744,6 +1746,7 @@ int QtScriptInstance::loadScriptFile( QString filepath )
evalResult.toString() + "\nStack:\n" + evalResult.toString() + "\nStack:\n" +
evalResult.property("stack").toString() + "\n"; evalResult.property("stack").toString() + "\n";
print(msg); print(msg);
emit errorNotify();
return -1; return -1;
} }
else else
@ -1920,6 +1923,8 @@ int QtScriptInstance::runFunc(QJSValue &func, const QJSValueList& args)
callResult.toString() + "\nStack:\n" + callResult.toString() + "\nStack:\n" +
callResult.property("stack").toString() + "\n"; callResult.property("stack").toString() + "\n";
print(msg); print(msg);
emit errorNotify();
} }
return retval; return retval;
} }
@ -1933,6 +1938,7 @@ int QtScriptInstance::call(const QString& funcName, const QJSValueList& args)
if (!engine->globalObject().hasProperty(funcName)) if (!engine->globalObject().hasProperty(funcName))
{ {
print(QString("No function exists: ") + funcName); print(QString("No function exists: ") + funcName);
emit errorNotify();
return -1; return -1;
} }
QJSValue func = engine->globalObject().property(funcName); QJSValue func = engine->globalObject().property(funcName);
@ -1996,6 +2002,14 @@ void QtScriptInstance::onFrameFinish()
} }
} }
//---------------------------------------------------- //----------------------------------------------------
void QtScriptInstance::flushLog()
{
if (dialog != nullptr)
{
dialog->flushLog();
}
}
//----------------------------------------------------
void QtScriptInstance::onGuiUpdate() void QtScriptInstance::onGuiUpdate()
{ {
if (running && onGuiUpdateCallback != nullptr && onGuiUpdateCallback->isCallable()) if (running && onGuiUpdateCallback != nullptr && onGuiUpdateCallback->isCallable())
@ -2228,6 +2242,11 @@ void QtScriptManager::guiUpdate()
script->onGuiUpdate(); script->onGuiUpdate();
} }
FCEU_WRAPPER_UNLOCK(); FCEU_WRAPPER_UNLOCK();
//for (auto script : scriptList)
//{
// script->flushLog();
//}
} }
//---------------------------------------------------- //----------------------------------------------------
//---- Qt Script Monitor Thread //---- Qt Script Monitor Thread
@ -2269,14 +2288,29 @@ QScriptDialog_t::QScriptDialog_t(QWidget *parent)
resize(512, 512); resize(512, 512);
setWindowTitle(tr("Qt Java Script Control")); setWindowTitle(tr("JavaScript Control"));
menuBar = buildMenuBar();
mainLayout = new QVBoxLayout(); mainLayout = new QVBoxLayout();
mainLayout->setMenuBar( menuBar );
lbl = new QLabel(tr("Script File:")); lbl = new QLabel(tr("Script File:"));
scriptPath = new QLineEdit(); scriptPath = new QLineEdit();
scriptArgs = 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); g_config->getOption("SDL.LastLoadJs", &filename);
@ -2287,9 +2321,6 @@ QScriptDialog_t::QScriptDialog_t(QWidget *parent)
jsOutput = new QTextEdit(); jsOutput = new QTextEdit();
jsOutput->setReadOnly(true); jsOutput->setReadOnly(true);
hbox = new QHBoxLayout();
browseButton = new QPushButton(tr("Browse"));
stopButton = new QPushButton(tr("Stop")); stopButton = new QPushButton(tr("Stop"));
scriptInstance = new QtScriptInstance(this); scriptInstance = new QtScriptInstance(this);
@ -2309,20 +2340,9 @@ QScriptDialog_t::QScriptDialog_t(QWidget *parent)
connect(stopButton, SIGNAL(clicked()), this, SLOT(stopScript(void))); connect(stopButton, SIGNAL(clicked()), this, SLOT(stopScript(void)));
connect(startButton, SIGNAL(clicked()), this, SLOT(startScript(void))); connect(startButton, SIGNAL(clicked()), this, SLOT(startScript(void)));
hbox->addWidget(browseButton); hbox = new QHBoxLayout();
hbox->addWidget(stopButton); hbox->addWidget(stopButton);
hbox->addWidget(startButton); 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); mainLayout->addLayout(hbox);
tabWidget = new QTabWidget(); tabWidget = new QTabWidget();
@ -2349,12 +2369,17 @@ QScriptDialog_t::QScriptDialog_t(QWidget *parent)
tabWidget->addTab(propTree, tr("Global Properties")); 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 = new QPushButton( tr("Close") );
closeButton->setIcon(style()->standardIcon(QStyle::SP_DialogCloseButton)); closeButton->setIcon(style()->standardIcon(QStyle::SP_DialogCloseButton));
connect(closeButton, SIGNAL(clicked(void)), this, SLOT(closeWindow(void))); connect(closeButton, SIGNAL(clicked(void)), this, SLOT(closeWindow(void)));
hbox = new QHBoxLayout(); hbox = new QHBoxLayout();
hbox->addStretch(5); hbox->addWidget( logFilepathLbl, 1 );
hbox->addWidget( logFilepath, 10 );
hbox->addWidget( closeButton, 1 ); hbox->addWidget( closeButton, 1 );
mainLayout->addLayout( hbox ); mainLayout->addLayout( hbox );
@ -2371,6 +2396,8 @@ QScriptDialog_t::QScriptDialog_t(QWidget *parent)
periodicTimer->start(200); // 5hz periodicTimer->start(200); // 5hz
restoreGeometry(settings.value("QScriptWindow/geometry").toByteArray()); restoreGeometry(settings.value("QScriptWindow/geometry").toByteArray());
connect(scriptInstance, SIGNAL(errorNotify()), this, SLOT(onScriptError(void)));
} }
//---------------------------------------------------- //----------------------------------------------------
QScriptDialog_t::~QScriptDialog_t(void) QScriptDialog_t::~QScriptDialog_t(void)
@ -2408,6 +2435,169 @@ void QScriptDialog_t::closeWindow(void)
deleteLater(); 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<QUrl> 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() void QScriptDialog_t::clearPropertyTree()
{ {
propTree->childMap.clear(); 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("<a href=\"file://") +
logFile->fileName() + QString("\">") + logFile->fileName() + QString("</a>");
logFilepath->setText( link );
}
//----------------------------------------------------
void QScriptDialog_t::startScript(void) void QScriptDialog_t::startScript(void)
{ {
FCEU_WRAPPER_LOCK(); FCEU_WRAPPER_LOCK();
resetLog();
jsOutput->clear(); jsOutput->clear();
clearPropertyTree(); clearPropertyTree();
scriptInstance->resetEngine(); scriptInstance->resetEngine();
@ -2834,6 +3041,11 @@ void QScriptDialog_t::logOutput(const QString& text)
vbar->setValue( vbar->maximum() ); vbar->setValue( vbar->maximum() );
} }
} }
if (logFile != nullptr)
{
logFile->write( text.toLocal8Bit() );
}
} }
//---------------------------------------------------- //----------------------------------------------------
bool FCEU_JSRerecordCountSkip() bool FCEU_JSRerecordCountSkip()

View File

@ -27,6 +27,10 @@
#include <QTreeWidgetItem> #include <QTreeWidgetItem>
#include <QJSEngine> #include <QJSEngine>
#include <QThread> #include <QThread>
#include <QTemporaryFile>
#include <QMenu>
#include <QMenuBar>
#include <QAction>
#include "Qt/main.h" #include "Qt/main.h"
#include "utils/mutex.h" #include "utils/mutex.h"
@ -596,6 +600,7 @@ public:
void onFrameFinish(); void onFrameFinish();
void onGuiUpdate(); void onGuiUpdate();
void checkForHang(); void checkForHang();
void flushLog();
int runFunc(QJSValue &func, const QJSValueList& args = QJSValueList()); int runFunc(QJSValue &func, const QJSValueList& args = QJSValueList());
int throwError(QJSValue::ErrorType errorType, const QString &message = QString()); int throwError(QJSValue::ErrorType errorType, const QString &message = QString());
@ -629,6 +634,9 @@ private:
int frameAdvanceState = 0; int frameAdvanceState = 0;
bool running = false; bool running = false;
signals:
void errorNotify();
public slots: public slots:
Q_INVOKABLE void print(const QString& msg); Q_INVOKABLE void print(const QString& msg);
Q_INVOKABLE void loadUI(const QString& uiFilePath); Q_INVOKABLE void loadUI(const QString& uiFilePath);
@ -718,6 +726,24 @@ public:
QMap<QString, JsPropertyItem*> childMap; QMap<QString, JsPropertyItem*> 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 class QScriptDialog_t : public QDialog
{ {
Q_OBJECT Q_OBJECT
@ -730,11 +756,15 @@ public:
void logOutput(const QString& text); void logOutput(const QString& text);
protected: protected:
void resetLog();
void closeEvent(QCloseEvent *bar); void closeEvent(QCloseEvent *bar);
void openJSKillMessageBox(void); void openJSKillMessageBox(void);
void clearPropertyTree(); void clearPropertyTree();
void loadPropertyTree(QJSValue& val, JsPropertyItem* parentItem = nullptr); void loadPropertyTree(QJSValue& val, JsPropertyItem* parentItem = nullptr);
QMenuBar* buildMenuBar();
QMenuBar* menuBar;
QScriptLogFile *logFile = nullptr;
QTimer *periodicTimer; QTimer *periodicTimer;
QLineEdit *scriptPath; QLineEdit *scriptPath;
QLineEdit *scriptArgs; QLineEdit *scriptArgs;
@ -747,15 +777,23 @@ protected:
JsPropertyTree *propTree; JsPropertyTree *propTree;
QtScriptInstance *scriptInstance; QtScriptInstance *scriptInstance;
QString emuThreadText; QString emuThreadText;
QString logSavePath;
QLabel *logFilepathLbl;
QLabel *logFilepath;
private: private:
public slots: public slots:
void flushLog();
void closeWindow(void); void closeWindow(void);
private slots: private slots:
void saveLog(bool openFileBrowser = false);
void updatePeriodic(void); void updatePeriodic(void);
void openScriptFile(void); void openScriptFile(void);
void startScript(void); void startScript(void);
void stopScript(void); void stopScript(void);
void onLogLinkClicked(const QString&);
void onScriptError(void);
}; };
bool FCEU_JSRerecordCountSkip(); bool FCEU_JSRerecordCountSkip();