1 #include "mainwindow.h"
2 #include "ui_mainwindow.h"
3 #include "utils.h"
4 #include "sendinghandler.h"
5 #include "visualizerwidget.h"
6 #include "dockwidget.h"
7 #include "searchwidget.h"
8 #include "basiccodeviewerwindow.h"
9 #include "capture/animated-png.h"
10 #include "tivarslib/TIModels.h"
11 #include "tivarslib/TIVarTypes.h"
12 #include "tivarslib/TypeHandlers/TypeHandlers.h"
13 #include "../../core/emu.h"
14 #include "../../core/asic.h"
15 #include "../../core/cpu.h"
16 #include "../../core/misc.h"
17 #include "../../core/mem.h"
18 #include "../../core/extras.h"
19 #include "../../core/interrupt.h"
20 #include "../../core/keypad.h"
21 #include "../../core/control.h"
22 #include "../../core/flash.h"
23 #include "../../core/lcd.h"
24 #include "../../core/spi.h"
25 #include "../../core/backlight.h"
26 #include "../../core/timers.h"
27 #include "../../core/usb.h"
28 #include "../../core/realclock.h"
29 #include "../../core/sha256.h"
30 #include "../../core/link.h"
31 #include "../../tests/autotester/crc32.hpp"
32 #include "../../tests/autotester/autotester.h"
33 
34 #include <QtCore/QFileInfo>
35 #include <QtCore/QRegularExpression>
36 #include <QtCore/QBuffer>
37 #include <QtCore/QProcess>
38 #include <QtGui/QWindow>
39 #include <QtGui/QDesktopServices>
40 #include <QtGui/QClipboard>
41 #include <QtWidgets/QDesktopWidget>
42 #include <QtWidgets/QDialogButtonBox>
43 #include <QtWidgets/QShortcut>
44 #include <QtWidgets/QProgressDialog>
45 #include <QtWidgets/QInputDialog>
46 #include <QtWidgets/QComboBox>
47 #include <QtWidgets/QScrollBar>
48 #include <QtNetwork/QNetworkAccessManager>
49 #include <QtNetwork/QNetworkReply>
50 #include <fstream>
51 #include <iostream>
52 #include <math.h>
53 
54 #ifdef Q_OS_MACX
55     #include "os/mac/kdmactouchbar.h"
56 #endif
57 
58 Q_DECLARE_METATYPE(calc_var_t)
Q_DECLARE_METATYPE(emu_state_t)59 Q_DECLARE_METATYPE(emu_state_t)
60 Q_DECLARE_METATYPE(emu_data_t)
61 
62 #ifdef _MSC_VER
63     #include <direct.h>
64     #define chdir _chdir
65 #else
66     #include <unistd.h>
67 #endif
68 
69 MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new Ui::MainWindow), opts(cliOpts) {
70     keypadBridge = new QtKeypadBridge(this); // This must be before setupUi for some reason >.>
71 
72     // setup translations
73     m_appTranslator.load(QLocale::system().name(), QStringLiteral(":/i18n/i18n/"));
74     qApp->installTranslator(&m_appTranslator);
75 
76     // setting metatypes
77     qRegisterMetaTypeStreamOperators<QList<int>>("QList<int>");
78     qRegisterMetaTypeStreamOperators<QList<bool>>("QList<bool>");
79 
80     m_isInDarkMode = isRunningInDarkMode();
81 
82     ui->setupUi(this);
83 
84     setStyleSheet(QStringLiteral("QMainWindow::separator{ width: 0px; height: 0px; }"));
85 
86     if (!ipcSetup()) {
87         m_initPassed = false;
88         return;
89     }
90 
91 #ifdef Q_OS_MACX
92     KDMacTouchBar *touchBar = new KDMacTouchBar(this);
93 #endif
94 
95     // init tivars_lib stuff
96     tivars::TIModels::initTIModelsArray();
97     tivars::TIVarTypes::initTIVarTypesArray();
98     tivars::TH_Tokenized::initTokens();
99 
100     ui->centralWidget->hide();
101     ui->statusBar->addWidget(&m_speedLabel);
102     ui->statusBar->addPermanentWidget(&m_msgLabel);
103     ui->statusBar->addPermanentWidget(&m_fpsLabel);
104 
105     m_watchpoints = ui->watchpoints;
106     m_breakpoints = ui->breakpoints;
107     m_ports = ui->ports;
108     m_disasm = ui->disasm;
109 
110     m_disasmOpcodeColor = m_isInDarkMode ? "darkorange" : "darkblue";
111 
112     ui->console->setMaximumBlockCount(1000);
113 
114     varPreviewCEFont = QFont(QStringLiteral("TICELarge"), 11);
115     varPreviewItalicFont.setItalic(true);
116 
117     setWindowTitle(QStringLiteral("CEmu | ") + opts.idString);
118 
119     connect(keypadBridge, &QtKeypadBridge::keyStateChanged, ui->keypadWidget, &KeypadWidget::changeKeyState);
120     connect(keypadBridge, &QtKeypadBridge::sendKeys, &emu, &EmuThread::enqueueKeys);
121     installEventFilter(keypadBridge);
122     for (const auto &tab : ui->tabWidget->children()[0]->children()) {
123         tab->installEventFilter(keypadBridge);
124     }
125 
126     m_progressBar = new QProgressBar(this);
127     m_progressBar->setMaximumHeight(ui->statusBar->height() / 2);
128     ui->statusBar->addWidget(m_progressBar);
129     sendingHandler = new SendingHandler(this, m_progressBar, ui->varLoadedView);
130 
131     // emulator -> gui (Should be queued)
132     connect(&emu, &EmuThread::consoleStr, this, &MainWindow::consoleStr, Qt::UniqueConnection);
133     connect(&emu, &EmuThread::consoleClear, this, &MainWindow::consoleClear, Qt::QueuedConnection);
134     connect(&emu, &EmuThread::sendSpeed, this, &MainWindow::showEmuSpeed, Qt::QueuedConnection);
135     connect(&emu, &EmuThread::debugDisable, this, &MainWindow::debugDisable, Qt::QueuedConnection);
136     connect(&emu, &EmuThread::debugCommand, this, &MainWindow::debugCommand, Qt::QueuedConnection);
137     connect(&emu, &EmuThread::saved, this, &MainWindow::emuSaved, Qt::QueuedConnection);
138     connect(&emu, &EmuThread::loaded, this, &MainWindow::emuCheck, Qt::QueuedConnection);
139     connect(&emu, &EmuThread::blocked, this, &MainWindow::emuBlocked, Qt::QueuedConnection);
140     connect(&emu, &EmuThread::tested, this, &MainWindow::autotesterTested, Qt::QueuedConnection);
141 
142     // console actions
143     connect(ui->buttonConsoleclear, &QPushButton::clicked, ui->console, &QPlainTextEdit::clear);
144     connect(ui->radioDock, &QRadioButton::clicked, this, &MainWindow::consoleModified);
145     connect(ui->radioConsole, &QRadioButton::clicked, this, &MainWindow::consoleModified);
146 
147     // debug actions
148     connect(ui->buttonRun, &QPushButton::clicked, this, &MainWindow::debugToggle);
149     connect(ui->checkADLDisasm, &QCheckBox::stateChanged, this, &MainWindow::disasmUpdate);
150     connect(ui->checkADLStack, &QCheckBox::stateChanged, this, &MainWindow::stackUpdate);
151     connect(ui->checkADL, &QCheckBox::stateChanged, [this]{ disasmUpdate(); stackUpdate(); });
152     connect(ui->buttonAddPort, &QPushButton::clicked, this, &MainWindow::portAddSlot);
153     connect(ui->buttonAddBreakpoint, &QPushButton::clicked, this, &MainWindow::breakAddSlot);
154     connect(ui->buttonAddWatchpoint, &QPushButton::clicked, this, &MainWindow::watchAddSlot);
155     connect(ui->buttonStepIn, &QPushButton::clicked, this, &MainWindow::stepIn);
156     connect(ui->buttonStepOver, &QPushButton::clicked, this, &MainWindow::stepOver);
157     connect(ui->buttonStepNext, &QPushButton::clicked, this, &MainWindow::stepNext);
158     connect(ui->buttonStepOut, &QPushButton::clicked, this, &MainWindow::stepOut);
159     connect(ui->buttonGoto, &QPushButton::clicked, this, &MainWindow::gotoPressed);
160     connect(ui->console, &QWidget::customContextMenuRequested, this, &MainWindow::contextConsole);
161     connect(m_disasm, &QWidget::customContextMenuRequested, this, &MainWindow::contextDisasm);
162     connect(ui->vatView, &QWidget::customContextMenuRequested, this, &MainWindow::contextVat);
163     connect(ui->opView, &QWidget::customContextMenuRequested, this, &MainWindow::contextOp);
164     connect(ui->opStack, &QWidget::customContextMenuRequested, this, &MainWindow::contextOp);
165     connect(ui->fpStack, &QWidget::customContextMenuRequested, this, &MainWindow::contextOp);
166     connect(m_ports, &QTableWidget::itemChanged, this, &MainWindow::portModified);
167     connect(m_ports, &QTableWidget::currentItemChanged, this, &MainWindow::portSetPrev);
168     connect(m_ports, &QTableWidget::itemPressed, [this](QTableWidgetItem *item){ portSetPrev(item, Q_NULLPTR); });
169     connect(m_breakpoints, &QTableWidget::itemChanged, this, &MainWindow::breakModified);
170     connect(m_breakpoints, &QTableWidget::currentItemChanged, this, &MainWindow::breakSetPrev);
171     connect(m_breakpoints, &QTableWidget::itemPressed, [this](QTableWidgetItem *item){ breakSetPrev(item, Q_NULLPTR); });
172     connect(m_watchpoints, &QTableWidget::itemChanged, this, &MainWindow::watchModified);
173     connect(m_watchpoints, &QTableWidget::currentItemChanged, this, &MainWindow::watchSetPrev);
174     connect(m_watchpoints, &QTableWidget::itemPressed, [this](QTableWidgetItem *item){ watchSetPrev(item, Q_NULLPTR); });
175     connect(ui->checkCharging, &QCheckBox::toggled, this, &MainWindow::batterySetCharging);
176     connect(ui->sliderBattery, &QSlider::valueChanged, this, &MainWindow::batterySet);
177     connect(ui->checkAddSpace, &QCheckBox::toggled, this, &MainWindow::setDebugDisasmSpace);
178     connect(ui->checkDisableSoftCommands, &QCheckBox::toggled, this, &MainWindow::setDebugSoftCommands);
179     connect(ui->buttonZero, &QPushButton::clicked, this, &MainWindow::debugZeroCycles);
180     connect(ui->buttonCertID, &QPushButton::clicked, this, &MainWindow::setCalcId);
181     connect(m_disasm, &DataWidget::gotoDisasmAddress, this, &MainWindow::gotoDisasmAddr);
182     connect(m_disasm, &DataWidget::gotoMemoryAddress, this, &MainWindow::gotoMemAddr);
183 
184 #ifdef Q_OS_MACX
185     {
186         QAction *action = new QAction(ui->actionReportBug->icon(), tr("Run/Stop"));
187         touchBar->addAction(action);
188         connect(action, &QAction::triggered, this, &MainWindow::debugToggle);
189     }
190     {
191         QAction *action = new QAction(ui->buttonStepIn->icon(), "in");
192         touchBar->addAction(action);
193         connect(action, &QAction::triggered, this, &MainWindow::stepIn);
194     }
195     {
196         QAction *action = new QAction(ui->buttonStepOver->icon(), "over");
197         touchBar->addAction(action);
198         connect(action, &QAction::triggered, this, &MainWindow::stepOver);
199     }
200     {
201         QAction *action = new QAction(ui->buttonStepNext->icon(), "next");
202         touchBar->addAction(action);
203         connect(action, &QAction::triggered, this, &MainWindow::stepNext);
204     }
205     {
206         QAction *action = new QAction(ui->buttonStepOut->icon(), "out");
207         touchBar->addAction(action);
208         connect(action, &QAction::triggered, this, &MainWindow::stepOut);
209     }
210 #endif
211 
212     // ctrl + click
213     connect(ui->console, &QPlainTextEdit::cursorPositionChanged, [this]{ handleCtrlClickText(ui->console); });
214     connect(ui->stackView, &QPlainTextEdit::cursorPositionChanged, [this]{ handleCtrlClickText(ui->stackView); });
215     connect(ui->hlregView, &QLineEdit::cursorPositionChanged, [this]{ handleCtrlClickLine(ui->hlregView); });
216     connect(ui->bcregView, &QLineEdit::cursorPositionChanged, [this]{ handleCtrlClickLine(ui->bcregView); });
217     connect(ui->deregView, &QLineEdit::cursorPositionChanged, [this]{ handleCtrlClickLine(ui->deregView); });
218     connect(ui->ixregView, &QLineEdit::cursorPositionChanged, [this]{ handleCtrlClickLine(ui->ixregView); });
219     connect(ui->iyregView, &QLineEdit::cursorPositionChanged, [this]{ handleCtrlClickLine(ui->iyregView); });
220     connect(ui->pcregView, &QLineEdit::cursorPositionChanged, [this]{ handleCtrlClickLine(ui->pcregView); });
221     connect(ui->splregView, &QLineEdit::cursorPositionChanged, [this]{ handleCtrlClickLine(ui->splregView); });
222     connect(ui->spsregView, &QLineEdit::cursorPositionChanged, [this]{ handleCtrlClickLine(ui->spsregView); });
223     connect(ui->hl_regView, &QLineEdit::cursorPositionChanged, [this]{ handleCtrlClickLine(ui->hl_regView); });
224     connect(ui->bc_regView, &QLineEdit::cursorPositionChanged, [this]{ handleCtrlClickLine(ui->bc_regView); });
225     connect(ui->de_regView, &QLineEdit::cursorPositionChanged, [this]{ handleCtrlClickLine(ui->de_regView); });
226 
227     // debug options
228     connect(ui->buttonAddEquateFile, &QToolButton::clicked, this, &MainWindow::equatesAddDialog);
229     connect(ui->buttonClearEquates, &QToolButton::clicked, this, &MainWindow::equatesClear);
230     connect(ui->buttonRefreshEquates, &QToolButton::clicked, this, &MainWindow::equatesRefresh);
231     connect(ui->buttonToggleBreakpoints, &QToolButton::toggled, this, &MainWindow::setDebugIgnoreBreakpoints);
232     connect(ui->checkDebugResetTrigger, &QCheckBox::toggled, this, &MainWindow::setDebugResetTrigger);
233     connect(ui->checkDisasmDataCol, &QCheckBox::toggled, this, &MainWindow::setDebugDisasmDataCol);
234     connect(ui->checkDisasmBoldSymbols, &QCheckBox::toggled, this, &MainWindow::setDebugDisasmBoldSymbols);
235     connect(ui->checkDisasmAddr, &QCheckBox::toggled, this, &MainWindow::setDebugDisasmAddrCol);
236     connect(ui->checkDisasmImplict, &QCheckBox::toggled, this, &MainWindow::setDebugDisasmImplict);
237     connect(ui->checkDisasmUppercase, &QCheckBox::toggled, this, &MainWindow::setDebugDisasmUppercase);
238     connect(ui->checkDma, &QCheckBox::toggled, this, &MainWindow::setLcdDma);
239     connect(ui->textSize, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &MainWindow::setFont);
240 
241     // debug files
242     connect(ui->actionImportDebugger, &QAction::triggered, [this]{ debugImportFile(debugGetFile(false)); });
243     connect(ui->actionExportDebugger, &QAction::triggered, [this]{ debugExportFile(debugGetFile(true)); });
244 
245     // linking
246     connect(ui->buttonSend, &QPushButton::clicked, this, &MainWindow::varSelect);
247     connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::varSelect);
248     connect(ui->buttonRefreshList, &QPushButton::clicked, this, &MainWindow::varToggle);
249     connect(ui->buttonReceiveFile, &QPushButton::clicked, this, &MainWindow::varSaveSelected);
250     connect(ui->buttonReceiveFiles, &QPushButton::clicked, this, &MainWindow::varSaveSelectedFiles);
251     connect(ui->buttonResendFiles, &QPushButton::clicked, this, &MainWindow::varResend);
252 
253     // autotester
254     connect(ui->buttonOpenJSONconfig, &QPushButton::clicked, this, &MainWindow::autotesterLoad);
255     connect(ui->buttonReloadJSONconfig, &QPushButton::clicked, this, &MainWindow::autotesterReload);
256     connect(ui->buttonLaunchTest, &QPushButton::clicked, this, &MainWindow::autotesterLaunch);
257     connect(ui->comboBoxPresetCRC, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &MainWindow::autotesterUpdatePresets);
258     connect(ui->buttonRefreshCRC, &QPushButton::clicked, this, &MainWindow::autotesterRefreshCRC);
259 
260     // menubar actions
261     connect(ui->actionSetup, &QAction::triggered, this, &MainWindow::runSetup);
262     connect(ui->actionExit, &QAction::triggered, this, &MainWindow::close);
263     connect(ui->actionScreenshot, &QAction::triggered, this, &MainWindow::screenshot);
264 #ifdef PNG_WRITE_APNG_SUPPORTED
265     connect(ui->actionRecordAnimated, &QAction::triggered, this, &MainWindow::recordAnimated);
266 #endif
267     connect(ui->actionSaveState, &QAction::triggered, [this]{ stateToPath(m_pathImage); });
268     connect(ui->actionExportCalculatorState, &QAction::triggered, this, &MainWindow::stateToFile);
269     connect(ui->actionExportRomImage, &QAction::triggered, this, &MainWindow::romExport);
270     connect(ui->actionExportRamImage, &QAction::triggered, this, &MainWindow::ramExport);
271     connect(ui->actionImportRamImage, &QAction::triggered, this, &MainWindow::ramImport);
272     connect(ui->actionImportCalculatorState, &QAction::triggered, this, &MainWindow::stateFromFile);
273     connect(ui->actionReloadROM, &QAction::triggered, [this]{ emuLoad(EMU_DATA_ROM); });
274     connect(ui->actionRestoreState, &QAction::triggered, [this]{ emuLoad(EMU_DATA_IMAGE); });
275     connect(ui->actionResetALL, &QAction::triggered, this, &MainWindow::resetCEmu);
276     connect(ui->actionResetGUI, &QAction::triggered, this, &MainWindow::resetGui);
277     connect(ui->actionResetCalculator, &QAction::triggered, this, &MainWindow::resetEmu);
278     connect(ui->actionHideMenuBar, &QAction::triggered, this, &MainWindow::setMenuBarState);
279     connect(ui->actionHideStatusBar, &QAction::triggered, this, &MainWindow::setStatusBarState);
280     connect(ui->buttonResetCalculator, &QPushButton::clicked, this, &MainWindow::resetEmu);
281     connect(ui->buttonReloadROM, &QPushButton::clicked, [this]{ emuLoad(EMU_DATA_ROM); });
282 
283 #ifdef Q_OS_MACX
284     touchBar->addSeparator();
285     {
286         QAction *resetAction = new QAction(ui->actionResetCalculator->icon(), tr("Reset"));
287         QAction *reloadAction = new QAction(ui->actionReloadROM->icon(), tr("Reload"));
288         touchBar->addActions({ resetAction, reloadAction });
289 
290         connect(resetAction, &QAction::triggered, this, [touchBar, resetAction, reloadAction, this] {
291             touchBar->removeAction(resetAction);
292             touchBar->removeAction(reloadAction);
293             QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
294             touchBar->addDialogButtonBox(buttonBox);
295             connect(buttonBox, &QDialogButtonBox::accepted, this, [touchBar, buttonBox, resetAction, reloadAction, this] {
296                 this->resetEmu();
297                 touchBar->removeDialogButtonBox(buttonBox);
298                 touchBar->addActions({ resetAction, reloadAction });
299             });
300             connect(buttonBox, &QDialogButtonBox::rejected, this, [touchBar, buttonBox, resetAction, reloadAction] {
301                 touchBar->removeDialogButtonBox(buttonBox);
302                 touchBar->addActions({ resetAction, reloadAction });
303             });
304         });
305 
306         connect(reloadAction, &QAction::triggered, this, [touchBar, resetAction, reloadAction, this] {
307             touchBar->removeAction(resetAction);
308             touchBar->removeAction(reloadAction);
309             QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
310             touchBar->addDialogButtonBox(buttonBox);
311             connect(buttonBox, &QDialogButtonBox::accepted, this, [touchBar, buttonBox, resetAction, reloadAction, this] {
312                 this->emuLoad(EMU_DATA_ROM);
313                 touchBar->removeDialogButtonBox(buttonBox);
314                 touchBar->addActions({ resetAction, reloadAction });
315             });
316             connect(buttonBox, &QDialogButtonBox::rejected, this, [touchBar, buttonBox, resetAction, reloadAction] {
317                 touchBar->removeDialogButtonBox(buttonBox);
318                 touchBar->addActions({ resetAction, reloadAction });
319             });
320         });
321     }
322 #endif
323 
324     // lcd flow
325     connect(ui->lcd, &LCDWidget::updateLcd, this, &MainWindow::lcdUpdate, Qt::QueuedConnection);
326     connect(this, &MainWindow::setLcdMode, ui->lcd, &LCDWidget::setMode);
327     connect(this, &MainWindow::setLcdFrameskip, ui->lcd, &LCDWidget::setFrameskip);
328     connect(ui->statusInterval, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &MainWindow::setStatusInterval);
329     connect(&m_timerEmu, &QTimer::timeout, [this]{ m_timerEmuTriggered = true; });
330     connect(&m_timerFps, &QTimer::timeout, [this]{ m_timerFpsTriggered = true; });
331 
332     // screen capture
333     connect(ui->buttonSavePNG, &QPushButton::clicked, this, &MainWindow::screenshot);
334     connect(ui->buttonCopyPNG, &QPushButton::clicked, this, &MainWindow::lcdCopy);
335     connect(ui->actionClipScreen, &QAction::triggered, this, &MainWindow::lcdCopy);
336 #ifdef PNG_WRITE_APNG_SUPPORTED
337     connect(ui->buttonRecordAnimated, &QPushButton::clicked, this, &MainWindow::recordAnimated);
338     connect(ui->apngSkip, &QSlider::valueChanged, this, &MainWindow::setFrameskip);
339     connect(ui->checkOptimizeRecording, &QCheckBox::stateChanged, this, &MainWindow::setOptimizeRecord);
340 #else
341     ui->actionRecordAnimated->setEnabled(false);
342     ui->buttonRecordAnimated->setEnabled(false);
343     ui->apngSkip->setEnabled(false);
344     ui->checkOptimizeRecording->setEnabled(false);
345 #endif
346 
347     // about menus
348     connect(ui->actionCheckForUpdates, &QAction::triggered, [this]{ checkUpdate(true); });
349     connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::showAbout);
350     connect(ui->actionAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt);
351     connect(ui->actionReportBug, &QAction::triggered, []{ QDesktopServices::openUrl(QUrl("https://github.com/CE-Programming/CEmu/issues")); });
352 
353     // other gui actions
354     connect(ui->checkAllowGroupDrag, &QCheckBox::stateChanged, this, &MainWindow::setDockGroupDrag);
355     connect(ui->buttonRunSetup, &QPushButton::clicked, this, &MainWindow::runSetup);
356     connect(ui->scaleLCD, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &MainWindow::setLcdScale);
357     connect(ui->guiSkip, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &MainWindow::setGuiSkip);
358     connect(ui->checkSkin, &QCheckBox::stateChanged, this, &MainWindow::setSkinToggle);
359     connect(ui->checkSpi, &QCheckBox::toggled, this, &MainWindow::setLcdSpi);
360     connect(ui->checkAlwaysOnTop, &QCheckBox::stateChanged, this, &MainWindow::setTop);
361     connect(ui->emulationSpeed, &QSlider::valueChanged, this, &MainWindow::setEmuSpeed);
362     connect(ui->emulationSpeedSpin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &MainWindow::setEmuSpeed);
363     connect(ui->checkThrottle, &QCheckBox::stateChanged, this, &MainWindow::setThrottle);
364     connect(ui->checkAutoEquates, &QCheckBox::stateChanged, this, &MainWindow::setDebugAutoEquates);
365     connect(ui->checkSaveRestore, &QCheckBox::stateChanged, this, &MainWindow::setAutoSave);
366     connect(ui->checkPortable, &QCheckBox::stateChanged, this, &MainWindow::setPortable);
367     connect(ui->checkSaveRecent, &QCheckBox::stateChanged, this, &MainWindow::setRecentSave);
368     connect(ui->checkSaveLoadDebug, &QCheckBox::stateChanged, this, &MainWindow::setDebugAutoSave);
369     connect(ui->buttonChangeSavedImagePath, &QPushButton::clicked, this, &MainWindow::setImagePath);
370     connect(ui->buttonChangeSavedDebugPath, &QPushButton::clicked, this, &MainWindow::setDebugPath);
371     connect(ui->checkFocus, &QCheckBox::stateChanged, this, &MainWindow::setFocusSetting);
372     connect(ui->checkPreI, &QCheckBox::stateChanged, this, &MainWindow::setPreRevisionI);
373     connect(ui->checkNormOs, &QCheckBox::stateChanged, this, &MainWindow::setNormalOs);
374     connect(ui->flashBytes, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), ui->flashEdit, &HexWidget::setBytesPerLine);
375     connect(ui->ramBytes, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), ui->ramEdit, &HexWidget::setBytesPerLine);
376     connect(ui->ramAscii, &QToolButton::toggled, [this](bool set){ ui->ramEdit->setAsciiArea(set); });
377     connect(ui->flashAscii, &QToolButton::toggled, [this](bool set){ ui->flashEdit->setAsciiArea(set); });
378     connect(ui->emuVarView, &QTableWidget::itemDoubleClicked, this, &MainWindow::varPressed);
379     connect(ui->emuVarView, &QTableWidget::customContextMenuRequested, this, &MainWindow::contextVars);
380     connect(ui->buttonAddSlot, &QPushButton::clicked, this, &MainWindow::stateAddNew);
381     connect(ui->actionExportCEmuImage, &QAction::triggered, this, &MainWindow::bootImageExport);
382     connect(ui->lcd, &LCDWidget::sendROM, this, &MainWindow::setRom);
383     connect(ui->lcd, &LCDWidget::customContextMenuRequested, this, &MainWindow::contextLcd);
384     connect(ui->checkUpdates, &QCheckBox::stateChanged, this, &MainWindow::setAutoUpdates);
385 
386     // languages
387     connect(ui->actionEnglish,  &QAction::triggered, [this]{ translateSwitch(QStringLiteral("en_EN")); });
388     connect(ui->actionFran_ais, &QAction::triggered, [this]{ translateSwitch(QStringLiteral("fr_FR")); });
389     connect(ui->actionDutch,    &QAction::triggered, [this]{ translateSwitch(QStringLiteral("nl_NL")); });
390     connect(ui->actionEspanol,  &QAction::triggered, [this]{ translateSwitch(QStringLiteral("es_ES")); });
391 
392     // sending handler
393     connect(sendingHandler, &SendingHandler::send, &emu, &EmuThread::send, Qt::QueuedConnection);
394     connect(&emu, &EmuThread::sentFile, sendingHandler, &SendingHandler::sentFile, Qt::QueuedConnection);
395     connect(sendingHandler, &SendingHandler::loadEquateFile, this, &MainWindow::equatesAddFile);
396 
397     // memory editors
398     connect(ui->buttonFlashSearch, &QPushButton::clicked, [this]{ memSearchEdit(ui->flashEdit); });
399     connect(ui->buttonRamSearch, &QPushButton::clicked, [this]{ memSearchEdit(ui->ramEdit); });
400     connect(ui->buttonFlashGoto, &QPushButton::clicked, this, &MainWindow::flashGotoPressed);
401     connect(ui->buttonRamGoto, &QPushButton::clicked, this, &MainWindow::ramGotoPressed);
402     connect(ui->buttonFlashSync, &QToolButton::clicked, this, &MainWindow::flashSyncPressed);
403     connect(ui->buttonRamSync, &QToolButton::clicked, this, &MainWindow::ramSyncPressed);
404     connect(ui->flashEdit, &HexWidget::customContextMenuRequested, this, &MainWindow::contextMem);
405     connect(ui->ramEdit, &HexWidget::customContextMenuRequested, this, &MainWindow::contextMem);
406 
407     // keymap
408     connect(ui->radioNaturalKeys, &QRadioButton::clicked, this, &MainWindow::keymapChanged);
409     connect(ui->radioCEmuKeys, &QRadioButton::clicked, this, &MainWindow::keymapChanged);
410     connect(ui->radioTilEmKeys, &QRadioButton::clicked, this, &MainWindow::keymapChanged);
411     connect(ui->radioWabbitemuKeys, &QRadioButton::clicked, this, &MainWindow::keymapChanged);
412     connect(ui->radiojsTIfiedKeys, &QRadioButton::clicked, this, &MainWindow::keymapChanged);
413     connect(ui->radioCustomKeys, &QRadioButton::clicked, this, &MainWindow::keymapCustomSelected);
414 
415     // keypad
416     connect(ui->buttonTrueBlue, &QPushButton::clicked, this, &MainWindow::keypadChanged);
417     connect(ui->buttonDenim, &QPushButton::clicked, this, &MainWindow::keypadChanged);
418     connect(ui->buttonPink, &QPushButton::clicked, this, &MainWindow::keypadChanged);
419     connect(ui->buttonPlum, &QPushButton::clicked, this, &MainWindow::keypadChanged);
420     connect(ui->buttonRed, &QPushButton::clicked, this, &MainWindow::keypadChanged);
421     connect(ui->buttonLightning, &QPushButton::clicked, this, &MainWindow::keypadChanged);
422     connect(ui->buttonGolden, &QPushButton::clicked, this, &MainWindow::keypadChanged);
423     connect(ui->buttonWhite, &QPushButton::clicked, this, &MainWindow::keypadChanged);
424     connect(ui->buttonBlack, &QPushButton::clicked, this, &MainWindow::keypadChanged);
425     connect(ui->buttonSilver, &QPushButton::clicked, this, &MainWindow::keypadChanged);
426     connect(ui->buttonSpaceGrey, &QPushButton::clicked, this, &MainWindow::keypadChanged);
427     connect(ui->buttonCoral, &QPushButton::clicked, this, &MainWindow::keypadChanged);
428     connect(ui->buttonMint, &QPushButton::clicked, this, &MainWindow::keypadChanged);
429     connect(ui->buttonRoseGold, &QPushButton::clicked, this, &MainWindow::keypadChanged);
430     connect(ui->buttonCrystalClear, &QPushButton::clicked, this, &MainWindow::keypadChanged);
431 
432     // gui configurations
433     connect(ui->actionExportWindowConfig, &QAction::triggered, this, &MainWindow::guiExport);
434     connect(ui->actionImportWindowConfig, &QAction::triggered, this, &MainWindow::guiImport);
435 
436     // keypad mappings
437     connect(ui->actionExportKeypadMapping, &QAction::triggered, this, &MainWindow::keymapExport);
438 
439     // application state
440     connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &MainWindow::pauseEmu);
441 
442     // process communication
443     connect(ui->actionNew, &QAction::triggered, this, &MainWindow::ipcSpawn);
444     connect(ui->actionChangeID, &QAction::triggered, this, &MainWindow::ipcSetId);
445     connect(&com, &InterCom::readDone, this, &MainWindow::ipcReceived);
446 
447     // docks
448     translateExtras(TRANSLATE_INIT);
449     m_actionToggleUI = new QAction(MSG_EDIT_UI, this);
450     m_actionToggleUI->setCheckable(true);
451     connect(m_actionToggleUI, &QAction::triggered, [this]{ setUIEditMode(!m_uiEditMode); });
452 
453     m_actionAddMemory = new QAction(MSG_ADD_MEMORY, this);
454     connect(m_actionAddMemory, &QAction::triggered, [this]{ addMemDock(randomString(20), 8, true); });
455 
456     m_actionAddVisualizer = new QAction(MSG_ADD_VISUALIZER, this);
457     connect(m_actionAddVisualizer, &QAction::triggered, [this]{ addVisualizerDock(randomString(20), QString()); });
458 
459     // already have action for key history
460     connect(ui->actionKeyHistory, &QAction::triggered, [this]{ addKeyHistoryDock(randomString(20), 9); });
461 
462     // shortcut connections
463     m_shortcutStepIn = new QShortcut(QKeySequence(Qt::Key_F6), this);
464     m_shortcutStepOver = new QShortcut(QKeySequence(Qt::Key_F7), this);
465     m_shortcutStepNext = new QShortcut(QKeySequence(Qt::Key_F8), this);
466     m_shortcutStepOut = new QShortcut(QKeySequence(Qt::Key_F9), this);
467     m_shortcutDebug = new QShortcut(QKeySequence(Qt::Key_F10), this);
468     m_shortcutFullscreen = new QShortcut(QKeySequence(Qt::Key_F11), this);
469     m_shortcutAsm = new QShortcut(QKeySequence(Qt::Key_Pause), this);
470     m_shortcutResend = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_X), this);
471 
472     m_shortcutFullscreen->setAutoRepeat(false);
473     m_shortcutDebug->setAutoRepeat(false);
474     m_shortcutAsm->setAutoRepeat(false);
475     m_shortcutResend->setAutoRepeat(false);
476 
477     connect(m_shortcutFullscreen, &QShortcut::activated, [this]{ setFullscreen(m_fullscreen + 1); });
478     connect(m_shortcutResend, &QShortcut::activated, this, &MainWindow::varResend);
479     connect(m_shortcutAsm, &QShortcut::activated, [this]{ sendEmuKey(CE_KEY_ASM); });
480     connect(m_shortcutDebug, &QShortcut::activated, this, &MainWindow::debugToggle);
481     connect(m_shortcutStepIn, &QShortcut::activated, this, &MainWindow::stepIn);
482     connect(m_shortcutStepOver, &QShortcut::activated, this, &MainWindow::stepOver);
483     connect(m_shortcutStepNext, &QShortcut::activated, this, &MainWindow::stepNext);
484     connect(m_shortcutStepOut, &QShortcut::activated, this, &MainWindow::stepOut);
485 
486     setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
487     setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
488 
489     // configure table font
490     const QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont);
491     ui->breakpoints->setFont(fixedFont);
492     ui->watchpoints->setFont(fixedFont);
493     ui->ports->setFont(fixedFont);
494 
495     // absolute paths
496     QString portableConfig = appDir().path() + SETTING_DEFAULT_CONFIG_FILE;
497     QString sharedConfig = configPath + SETTING_DEFAULT_CONFIG_FILE;
498 
499     if (opts.settingsFile.isEmpty()) {
500         if (bootImageCheck()) {
501             m_pathConfig = sharedConfig;
502         } else if (fileExists(portableConfig)) {
503             m_pathConfig = portableConfig;
504             m_portable = true;
505         } else {
506             m_pathConfig = sharedConfig;
507         }
508     } else {
509         m_pathConfig = QFileInfo(opts.settingsFile).absoluteFilePath();
510     }
511 
512     if (opts.useSettings) {
513         m_config = new QSettings(m_pathConfig, QSettings::IniFormat);
514     } else {
515         ui->checkPortable->blockSignals(true);
516         ui->checkPortable->setEnabled(false);
517         ui->checkPortable->blockSignals(false);
518         m_config = new QSettings();
519         m_config->clear();
520     }
521     if (m_loadedBootImage) {
522         m_config->setValue(SETTING_FIRST_RUN, false);
523     }
524 
525     QFileInfo configDir(QFileInfo(m_pathConfig).path());
526     if (opts.useSettings && configDir.isDir() && !configDir.isWritable()) {
527         m_pathConfig = portableConfig;
528         ui->pathConfig->setText(portableConfig);
529         m_portableActivated = true;
530         m_portable = true;
531     }
532 
533     setAutoUpdates(m_config->value(SETTING_AUTOUPDATE, CEMU_RELEASE).toBool());
534     checkVersion();
535 
536 #ifdef Q_OS_WIN
537     installToggleConsole();
538 #endif
539 
540 #ifdef Q_OS_MACX
541     ui->actionHideMenuBar->setVisible(false);
542 #endif
543 
544     iconsLoad();
545     memLoadState();
546     optLoadFiles(opts);
547     setFrameskip(m_config->value(SETTING_CAPTURE_FRAMESKIP, 1).toInt());
548     setOptimizeRecord(m_config->value(SETTING_CAPTURE_OPTIMIZE, true).toBool());
549     setStatusInterval(m_config->value(SETTING_STATUS_INTERVAL, 1).toInt());
550     setLcdScale(m_config->value(SETTING_SCREEN_SCALE, 100).toInt());
551     setSkinToggle(m_config->value(SETTING_SCREEN_SKIN, true).toBool());
552     setLcdSpi(m_config->value(SETTING_SCREEN_SPI, true).toBool());
553     setGuiSkip(m_config->value(SETTING_SCREEN_FRAMESKIP, 0).toInt());
554     setEmuSpeed(m_config->value(SETTING_EMUSPEED, 100).toInt());
555     setAutoSave(m_config->value(SETTING_RESTORE_ON_OPEN, true).toBool());
556     setFont(m_config->value(SETTING_DEBUGGER_TEXT_SIZE, 9).toInt());
557     setDebugDisasmSpace(m_config->value(SETTING_DEBUGGER_DISASM_SPACE, false).toBool());
558     setDebugDisasmAddrCol(m_config->value(SETTING_DEBUGGER_ADDR_COL, true).toBool());
559     setDebugDisasmDataCol(m_config->value(SETTING_DEBUGGER_DATA_COL, true).toBool());
560     setDebugDisasmBoldSymbols(m_config->value(SETTING_DEBUGGER_BOLD_SYMBOLS, false).toBool());
561     setDebugDisasmUppercase(m_config->value(SETTING_DEBUGGER_UPPERCASE, false).toBool());
562     setDebugDisasmImplict(m_config->value(SETTING_DEBUGGER_IMPLICT, false).toBool());
563     setDebugAutoSave(m_config->value(SETTING_DEBUGGER_RESTORE_ON_OPEN, false).toBool());
564     setDebugAutoEquates(m_config->value(SETTING_DEBUGGER_AUTO_EQUATES, false).toBool());
565     setDebugResetTrigger(m_config->value(SETTING_DEBUGGER_RESET_OPENS, false).toBool());
566     setDebugIgnoreBreakpoints(m_config->value(SETTING_DEBUGGER_BREAK_IGNORE, false).toBool());
567     setDebugSoftCommands(m_config->value(SETTING_DEBUGGER_ENABLE_SOFT, true).toBool());
568     setPreRevisionI(m_config->value(SETTING_DEBUGGER_PRE_I, false).toBool());
569     setNormalOs(m_config->value(SETTING_DEBUGGER_NORM_OS, true).toBool());
570     setLcdDma(m_config->value(SETTING_DEBUGGER_IGNORE_DMA, true).toBool());
571     setFocusSetting(m_config->value(SETTING_PAUSE_FOCUS, false).toBool());
572     setRecentSave(m_config->value(SETTING_RECENT_SAVE, true).toBool());
573     setDockGroupDrag(m_config->value(SETTING_WINDOW_GROUP_DRAG, false).toBool());
574     setMenuBarState(m_config->value(SETTING_WINDOW_MENUBAR, false).toBool());
575     setStatusBarState(m_config->value(SETTING_WINDOW_STATUSBAR, false).toBool());
576     setTop(m_config->value(SETTING_ALWAYS_ON_TOP, false).toBool());
577 
578     if (m_config->value(SETTING_NATIVE_CONSOLE, false).toBool()) {
579         ui->radioConsole->setChecked(true);
580         consoleModified();
581     }
582 
583     // server name
584     console(QStringLiteral("[CEmu] Initialized Server [") + opts.idString +
585             QStringLiteral(" | ") + com.getServerName() + QStringLiteral("]\n"));
586 
587     m_dir.setPath(m_config->value(SETTING_CURRENT_DIR, appDir().path()).toString());
588 
589     if (!m_config->contains(SETTING_IMAGE_PATH) || m_portable) {
590         QString path = QFileInfo(m_pathConfig).absolutePath() + SETTING_DEFAULT_IMAGE_FILE;
591         if (m_portable) {
592             path = appDir().relativeFilePath(path);
593         }
594         m_config->setValue(SETTING_IMAGE_PATH, QDir::cleanPath(path));
595     }
596     ui->pathImage->setText(m_config->value(SETTING_IMAGE_PATH).toString());
597 
598     m_pathImage = QDir::cleanPath(appDir().absoluteFilePath(ui->pathImage->text()));
599 
600     if (!m_config->contains(SETTING_DEBUGGER_IMAGE_PATH) || m_portable) {
601         QString path = QFileInfo(m_pathConfig).absolutePath() + SETTING_DEFAULT_DEBUG_FILE;
602         if (m_portable) {
603             path = appDir().relativeFilePath(path);
604         }
605         m_config->setValue(SETTING_DEBUGGER_IMAGE_PATH, QDir::cleanPath(path));
606     }
607     ui->pathDebug->setText(m_config->value(SETTING_DEBUGGER_IMAGE_PATH).toString());
608 
609     keymapLoad();
610     debugInit();
611 
612     if (!fileExists(m_pathRom)) {
613         if (!runSetup()) {
614             m_initPassed = false;
615         }
616     } else {
617         if (opts.useSettings && opts.restoreOnOpen && m_config->value(SETTING_RESTORE_ON_OPEN, true).toBool()) {
618             emuLoad(opts.forceReloadRom ? EMU_DATA_ROM : EMU_DATA_IMAGE);
619         } else {
620             emuLoad(EMU_DATA_ROM);
621         }
622     }
623 
624     ui->pathRom->setText(m_portable ? appDir().relativeFilePath(m_pathRom) : m_pathRom);
625 
626     if (opts.useSettings) {
627         ui->pathConfig->setText(m_pathConfig);
628     }
629 
630     if (m_portable) {
631         ui->checkPortable->blockSignals(true);
632         ui->checkPortable->setChecked(true);
633         ui->buttonChangeSavedDebugPath->setEnabled(false);
634         ui->buttonChangeSavedImagePath->setEnabled(false);
635         ui->checkPortable->blockSignals(false);
636     }
637 
638     m_cBack.setColor(QPalette::Base, QColor(isRunningInDarkMode() ? Qt::blue : Qt::yellow).lighter(160));
639     m_consoleFormat = ui->console->currentCharFormat();
640 }
641 
translateSwitch(const QString & lang)642 void MainWindow::translateSwitch(const QString& lang) {
643     qApp->removeTranslator(&m_appTranslator);
644     // For English, nothing to load after removing the translator.
645     if (lang == QStringLiteral("en_EN") || (m_appTranslator.load(lang, QStringLiteral(":/i18n/i18n/"))
646                                             && qApp->installTranslator(&m_appTranslator))) {
647         m_config->setValue(SETTING_PREFERRED_LANG, lang);
648     } else {
649         QMessageBox::warning(this, MSG_WARNING, tr("No translation available for this language :("));
650     }
651 }
652 
translateExtras(int init)653 void MainWindow::translateExtras(int init) {
654     QAction *action;
655 
656     TITLE_DOCKS = tr("Docks");
657     TITLE_DEBUG = tr("Debug");
658     MSG_INFORMATION = tr("Information");
659     MSG_WARNING = tr("Warning");
660     MSG_ERROR = tr("Error");
661     MSG_ADD_MEMORY = tr("Add memory view");
662     MSG_ADD_VISUALIZER = tr("Add memory visualizer");
663     MSG_EDIT_UI = tr("Enable UI edit mode");
664 
665     QString __TXT_MEM_DOCK = tr("Memory View");
666     QString __TXT_VISUALIZER_DOCK = tr("Memory Visualizer");
667     QString __TXT_KEYHISTORY_DOCK = tr("Keypress History");
668 
669     QString __TXT_CLEAR_HISTORY = tr("Clear History");
670     QString __TXT_SIZE = tr("Size");
671 
672     QString __TXT_GOTO = tr("Goto");
673     QString __TXT_SEARCH = tr("Search");
674     QString __TXT_SYNC = tr("Sync Changes");
675     QString __TXT_ASCII = tr("Show ASCII");
676 
677     QString __TXT_CONSOLE = tr("Console");
678     QString __TXT_SETTINGS = tr("Settings");
679     QString __TXT_VARIABLES = tr("Variables");
680     QString __TXT_CAPTURE = tr("Capture");
681     QString __TXT_STATE = tr("State");
682     QString __TXT_KEYPAD = tr("Keypad");
683 
684     QString __TXT_DEBUG_CONTROL = tr("Debug Control");
685     QString __TXT_CPU_STATUS = tr("CPU Status");
686     QString __TXT_DISASSEMBLY = tr("Disassembly");
687     QString __TXT_MEMORY = tr("Memory");
688     QString __TXT_TIMERS = tr("Timers");
689     QString __TXT_BREAKPOINTS = tr("Breakpoints");
690     QString __TXT_WATCHPOINTS = tr("Watchpoints");
691     QString __TXT_PORTMON = tr("Port Monitor");
692     QString __TXT_OS_VIEW = tr("OS Variables");
693     QString __TXT_OS_STACKS = tr("OS Stacks");
694     QString __TXT_MISC = tr("Miscellaneous");
695     QString __TXT_AUTOTESTER = tr("AutoTester");
696 
697     setWindowTitle(QStringLiteral("CEmu | ") + opts.idString);
698 
699     if (init == TRANSLATE_UPDATE) {
700         for (const auto &dock : findChildren<DockWidget*>()) {
701             if (dock->windowTitle() == TXT_MEM_DOCK) {
702                 QList<QPushButton*> buttons = dock->findChildren<QPushButton*>();
703                 QList<QToolButton*> tools = dock->findChildren<QToolButton*>();
704                 dock->setWindowTitle(__TXT_MEM_DOCK);
705                 buttons.at(0)->setText(__TXT_GOTO);
706                 buttons.at(1)->setText(__TXT_SEARCH);
707                 tools.at(0)->setToolTip(__TXT_ASCII);
708                 tools.at(1)->setToolTip(__TXT_SYNC);
709             }
710             if (dock->windowTitle() == TXT_VISUALIZER_DOCK) {
711                 dock->setWindowTitle(__TXT_VISUALIZER_DOCK);
712                 static_cast<VisualizerWidget*>(dock->widget())->translate();
713             }
714             if (dock->windowTitle() == TXT_KEYHISTORY_DOCK) {
715                 QList<QPushButton*> buttons = dock->findChildren<QPushButton*>();
716                 QList<QLabel*> labels = dock->findChildren<QLabel*>();
717                 dock->setWindowTitle(__TXT_KEYHISTORY_DOCK);
718                 buttons.at(0)->setText(__TXT_CLEAR_HISTORY);
719                 labels.at(0)->setText(__TXT_SIZE);
720             }
721             if (dock->windowTitle() == TXT_CONSOLE) {
722                 dock->setWindowTitle(__TXT_CONSOLE);
723             }
724             if (dock->windowTitle() == TXT_SETTINGS) {
725                 dock->setWindowTitle(__TXT_SETTINGS);
726             }
727             if (dock->windowTitle() == TXT_VARIABLES) {
728                 dock->setWindowTitle(__TXT_VARIABLES);
729             }
730             if (dock->windowTitle() == TXT_CAPTURE) {
731                 dock->setWindowTitle(__TXT_CAPTURE);
732             }
733             if (dock->windowTitle() == TXT_STATE) {
734                 dock->setWindowTitle(__TXT_STATE);
735             }
736             if (dock->windowTitle() == TXT_KEYPAD) {
737                 dock->setWindowTitle(__TXT_KEYPAD);
738             }
739             if (dock->windowTitle() == TXT_DEBUG_CONTROL) {
740                 dock->setWindowTitle(__TXT_DEBUG_CONTROL);
741             }
742             if (dock->windowTitle() == TXT_CPU_STATUS) {
743                 dock->setWindowTitle(__TXT_CPU_STATUS);
744             }
745             if (dock->windowTitle() == TXT_DISASSEMBLY) {
746                 dock->setWindowTitle(__TXT_DISASSEMBLY);
747             }
748             if (dock->windowTitle() == TXT_MEMORY) {
749                 dock->setWindowTitle(__TXT_MEMORY);
750             }
751             if (dock->windowTitle() == TXT_TIMERS) {
752                 dock->setWindowTitle(__TXT_TIMERS);
753             }
754             if (dock->windowTitle() == TXT_BREAKPOINTS) {
755                 dock->setWindowTitle(__TXT_BREAKPOINTS);
756             }
757             if (dock->windowTitle() == TXT_WATCHPOINTS) {
758                 dock->setWindowTitle(__TXT_WATCHPOINTS);
759             }
760             if (dock->windowTitle() == TXT_PORTMON) {
761                 dock->setWindowTitle(__TXT_PORTMON);
762             }
763             if (dock->windowTitle() == TXT_OS_VIEW) {
764                 dock->setWindowTitle(__TXT_OS_VIEW);
765             }
766             if (dock->windowTitle() == TXT_OS_STACKS) {
767                 dock->setWindowTitle(__TXT_OS_STACKS);
768             }
769             if (dock->windowTitle() == TXT_MISC) {
770                 dock->setWindowTitle(__TXT_MISC);
771             }
772             if (dock->windowTitle() == TXT_AUTOTESTER) {
773                 dock->setWindowTitle(__TXT_AUTOTESTER);
774             }
775         }
776     }
777 
778     TXT_MEM_DOCK = __TXT_MEM_DOCK;
779     TXT_VISUALIZER_DOCK = __TXT_VISUALIZER_DOCK;
780     TXT_KEYHISTORY_DOCK = __TXT_KEYHISTORY_DOCK;
781 
782     TXT_CLEAR_HISTORY = __TXT_CLEAR_HISTORY;
783     TXT_SIZE = __TXT_SIZE;
784 
785     TXT_CONSOLE = __TXT_CONSOLE;
786     TXT_SETTINGS = __TXT_SETTINGS;
787     TXT_VARIABLES = __TXT_VARIABLES;
788     TXT_CAPTURE = __TXT_CAPTURE;
789     TXT_STATE = __TXT_STATE;
790     TXT_KEYPAD = __TXT_KEYPAD;
791 
792     TXT_DEBUG_CONTROL = __TXT_DEBUG_CONTROL;
793     TXT_CPU_STATUS = __TXT_CPU_STATUS;
794     TXT_DISASSEMBLY = __TXT_DISASSEMBLY;
795     TXT_MEMORY = __TXT_MEMORY;
796     TXT_TIMERS = __TXT_TIMERS;
797     TXT_BREAKPOINTS = __TXT_BREAKPOINTS;
798     TXT_WATCHPOINTS = __TXT_WATCHPOINTS;
799     TXT_PORTMON = __TXT_PORTMON;
800     TXT_OS_VIEW = __TXT_OS_VIEW;
801     TXT_OS_STACKS = __TXT_OS_STACKS;
802     TXT_MISC = __TXT_MISC;
803     TXT_AUTOTESTER = __TXT_AUTOTESTER;
804 
805 #ifdef _WIN32
806     TXT_TOGGLE_CONSOLE = tr("Toggle Windows Console");
807 #endif
808 
809     if (init == TRANSLATE_UPDATE) {
810         m_actionToggleUI->setText(MSG_EDIT_UI);
811         m_actionAddMemory->setText(MSG_ADD_MEMORY);
812         m_actionAddVisualizer->setText(MSG_ADD_VISUALIZER);
813         m_menuDebug->setTitle(TITLE_DEBUG);
814         m_menuDocks->setTitle(TITLE_DOCKS);
815 
816         action = m_menuDocks->actions().at(0);
817         action->setText(TXT_VARIABLES);
818         action = m_menuDocks->actions().at(1);
819         action->setText(TXT_CAPTURE);
820         action = m_menuDocks->actions().at(2);
821         action->setText(TXT_SETTINGS);
822         action = m_menuDocks->actions().at(3);
823         action->setText(TXT_CONSOLE);
824         action = m_menuDocks->actions().at(4);
825         action->setText(TXT_STATE);
826         action = m_menuDocks->actions().at(5);
827         action->setText(TXT_KEYPAD);
828 
829         action = m_menuDebug->actions().at(0);
830         action->setText(TXT_DEBUG_CONTROL);
831         action = m_menuDebug->actions().at(1);
832         action->setText(TXT_CPU_STATUS);
833         action = m_menuDebug->actions().at(2);
834         action->setText(TXT_DISASSEMBLY);
835         action = m_menuDebug->actions().at(3);
836         action->setText(TXT_MEMORY);
837         action = m_menuDebug->actions().at(4);
838         action->setText(TXT_TIMERS);
839         action = m_menuDebug->actions().at(5);
840         action->setText(TXT_BREAKPOINTS);
841         action = m_menuDebug->actions().at(6);
842         action->setText(TXT_WATCHPOINTS);
843         action = m_menuDebug->actions().at(7);
844         action->setText(TXT_PORTMON);
845         action = m_menuDebug->actions().at(8);
846         action->setText(TXT_OS_VIEW);
847         action = m_menuDebug->actions().at(9);
848         action->setText(TXT_OS_STACKS);
849         action = m_menuDebug->actions().at(10);
850         action->setText(TXT_MISC);
851         action = m_menuDebug->actions().at(11);
852         action->setText(TXT_AUTOTESTER);
853 
854 #ifdef _WIN32
855         actionToggleConsole->setText(TXT_TOGGLE_CONSOLE);
856 #endif
857     }
858 }
859 
changeEvent(QEvent * event)860 void MainWindow::changeEvent(QEvent* event) {
861     const auto eventType = event->type();
862     if (eventType == QEvent::LanguageChange) {
863         if (m_setup) {
864             ui->retranslateUi(this);
865             translateExtras(TRANSLATE_UPDATE);
866         }
867     } else if (eventType == QEvent::LocaleChange) {
868         translateSwitch(QLocale::system().name());
869     }
870     QMainWindow::changeEvent(event);
871 }
872 
showEvent(QShowEvent * e)873 void MainWindow::showEvent(QShowEvent *e) {
874     if (!m_setup) {
875         e->ignore();
876         return;
877     }
878     QMainWindow::showEvent(e);
879     e->accept();
880 }
881 
redistributeFindDock(const QPoint & pos)882 DockWidget *MainWindow::redistributeFindDock(const QPoint &pos) {
883     QWidget *child = childAt(pos);
884     if (QTabBar *tabBar = findSelfOrParent<QTabBar *>(child)) {
885         child = childAt({pos.x(), tabBar->mapTo(this, {}).y() - 1});
886     }
887     return findSelfOrParent<DockWidget *>(child);
888 }
889 
redistributeDocks(const QPoint & pos,const QPoint & offset,Qt::CursorShape cursorShape,int (QSize::* dimension)()const,Qt::Orientation orientation)890 bool MainWindow::redistributeDocks(const QPoint &pos, const QPoint &offset,
891                                    Qt::CursorShape cursorShape,
892                                    int (QSize::*dimension)() const,
893                                    Qt::Orientation orientation) {
894 #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
895     if (cursor().shape() == cursorShape) {
896         if (DockWidget *before = redistributeFindDock(pos - offset)) {
897             if (DockWidget *after = redistributeFindDock(pos + offset)) {
898                 if (before != after) {
899                     int size = (before->size().*dimension)() + (after->size().*dimension)();
900                     resizeDocks({before, after}, {size / 2, size - size / 2}, orientation);
901                     return true;
902                 }
903             }
904         }
905     }
906 #else
907     (void)pos; (void)offset; (void)cursorShape; (void)dimension; (void)orientation;
908 #endif
909     return false;
910 }
911 
mouseDoubleClickEvent(QMouseEvent * event)912 void MainWindow::mouseDoubleClickEvent(QMouseEvent *event) {
913     if (!childAt(event->pos())) {
914         int sep = style()->pixelMetric(QStyle::PM_DockWidgetSeparatorExtent, Q_NULLPTR, this);
915         if (redistributeDocks(event->pos(), {sep, 0}, Qt::SplitHCursor, &QSize::width, Qt::Horizontal) ||
916             redistributeDocks(event->pos(), {0, sep}, Qt::SplitVCursor, &QSize::height, Qt::Vertical)) {
917             event->accept();
918             return;
919         }
920     }
921     QMainWindow::mouseDoubleClickEvent(event);
922 }
923 
setup()924 void MainWindow::setup() {
925     if (!m_initPassed) {
926         QFile(m_pathConfig).remove();
927         close();
928         return;
929     }
930 
931     m_uiEditMode = m_config->value(SETTING_UI_EDIT_MODE, true).toBool();
932 
933 
934     setUIDocks();
935     show();
936 
937     const QByteArray geometry = m_config->value(SETTING_WINDOW_GEOMETRY, QByteArray()).toByteArray();
938     if (restoreState(m_config->value(SETTING_WINDOW_STATE).toByteArray()) && restoreGeometry(geometry) && restoreGeometry(geometry)) {
939         const QPoint position = m_config->value(SETTING_WINDOW_POSITION).toPoint();
940         QTimer::singleShot(0, [this, position]() { move(position); });
941     } else {
942         foreach (DockWidget *dw, m_dockPtrs) {
943             dw->setVisible(true);
944         }
945         resize(minimumWidth(), minimumHeight());
946         setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), qApp->desktop()->availableGeometry()));
947     }
948 
949     // restore the fullscreen modes as needed
950     if (opts.fullscreen == -1) {
951         setFullscreen(m_config->value(SETTING_WINDOW_FULLSCREEN, 0).toInt());
952     } else {
953         setFullscreen(opts.fullscreen);
954     }
955 
956     setUIEditMode(m_uiEditMode);
957 
958     stateLoadInfo();
959     recentLoadInfo();
960 
961     if (m_config->value(SETTING_DEBUGGER_RESTORE_ON_OPEN).toBool()) {
962         if (!opts.debugFile.isEmpty()) {
963             debugImportFile(opts.debugFile);
964         } else {
965             debugImportFile(m_config->value(SETTING_DEBUGGER_IMAGE_PATH).toString());
966         }
967     }
968 
969     if (opts.useSettings && isFirstRun() && m_initPassed && !m_needFullReset) {
970         QMessageBox *info = new QMessageBox();
971         m_config->setValue(SETTING_FIRST_RUN, true);
972         info->setWindowTitle(MSG_INFORMATION);
973         info->setText(tr("Welcome!\n\nCEmu uses a customizable dock-style interface. "
974                             "Drag and drop to move tabs and windows around on the screen, "
975                             "and choose which docks are available in the 'Docks' menu in the topmost bar. "
976                             "Be sure that 'Enable UI edit mode' is selected when laying out your interface. "
977                             "Enjoy!"
978                              "\n\n(Notice: depending on your version, you can drag grouped tabs or an individual tab from their title or tab bar, respectively)"
979                          ));
980         info->setWindowModality(Qt::NonModal);
981         info->setWindowFlags(info->windowFlags() | Qt::WindowStaysOnTopHint);
982         info->setAttribute(Qt::WA_DeleteOnClose);
983         info->show();
984     }
985 
986     QString prefLang = m_config->value(SETTING_PREFERRED_LANG, QStringLiteral("none")).toString();
987     if (prefLang != QStringLiteral("none")) {
988         translateSwitch(prefLang);
989     }
990 
991     translateExtras(TRANSLATE_UPDATE);
992 
993     optSend(opts);
994     if (opts.speed != -1) {
995         setEmuSpeed(opts.speed);
996     }
997 
998     if (m_portableActivated) {
999         QMessageBox *info = new QMessageBox();
1000         info->setWindowTitle(MSG_INFORMATION);
1001         info->setText(tr("CEmu was not able to write to the standard settings location.\n"
1002                          "Portable mode has been activated."));
1003         info->setWindowModality(Qt::NonModal);
1004         info->setWindowFlags(info->windowFlags() | Qt::WindowStaysOnTopHint);
1005         info->setAttribute(Qt::WA_DeleteOnClose);
1006         info->show();
1007     }
1008 
1009     setUIDockEditMode(m_uiEditMode);
1010     ui->lcd->setFocus();
1011     m_setup = true;
1012 }
1013 
sendEmuKey(uint16_t key)1014 void MainWindow::sendEmuKey(uint16_t key) {
1015     int retry = 200;
1016     do {
1017         if (sendKey(key)) {
1018             break;
1019         }
1020         guiDelay(50);
1021     } while(retry--);
1022 }
1023 
sendEmuLetterKey(char letter)1024 void MainWindow::sendEmuLetterKey(char letter) {
1025     int retry = 200;
1026     do {
1027         if (sendLetterKeyPress(letter)) {
1028             break;
1029         }
1030         guiDelay(50);
1031     } while(retry--);
1032 }
1033 
optSend(CEmuOpts & o)1034 void MainWindow::optSend(CEmuOpts &o) {
1035     int speed = m_config->value(SETTING_EMUSPEED).toInt();
1036     if (!o.autotesterFile.isEmpty()) {
1037         if (!autotesterOpen(o.autotesterFile)) {
1038            if (!o.deforceReset) {
1039                resetEmu();
1040            }
1041            setEmuSpeed(100);
1042 
1043            // Race condition requires this
1044            guiDelay(o.deforceReset ? 100 : 4000);
1045            autotesterLaunch();
1046         }
1047     }
1048 
1049     if (!o.sendFiles.isEmpty() || !o.sendArchFiles.isEmpty() || !o.sendRAMFiles.isEmpty()) {
1050         if (!o.deforceReset) {
1051             resetEmu();
1052         }
1053         setEmuSpeed(100);
1054 
1055         // Race condition requires this
1056         guiDelay(o.deforceReset ? 100 : 4000);
1057         if (!o.sendFiles.isEmpty()) {
1058             sendingHandler->sendFiles(o.sendFiles, LINK_FILE);
1059         }
1060         if (!o.sendArchFiles.isEmpty()) {
1061             sendingHandler->sendFiles(o.sendArchFiles, LINK_ARCH);
1062         }
1063         if (!o.sendRAMFiles.isEmpty()) {
1064             sendingHandler->sendFiles(o.sendRAMFiles, LINK_RAM);
1065         }
1066     }
1067 
1068     setThrottle(o.useUnthrottled ? Qt::Unchecked : Qt::Checked);
1069     setEmuSpeed(speed);
1070 
1071     if (!o.launchPrgm.isEmpty()) {
1072         guiDelay(50);
1073         sendEmuKey(CE_KEY_CLEAR);
1074         sendEmuKey(CE_KEY_PRGM);
1075         for (const QChar &c : o.launchPrgm) {
1076             sendEmuLetterKey(c.toLatin1());
1077         }
1078         sendEmuKey(CE_KEY_ENTER);
1079     }
1080 }
1081 
optLoadFiles(CEmuOpts & o)1082 void MainWindow::optLoadFiles(CEmuOpts &o) {
1083     if (o.romFile.isEmpty()) {
1084         if (m_loadedBootImage) {
1085             m_pathRom = configPath + SETTING_DEFAULT_ROM_FILE;
1086             m_config->setValue(SETTING_ROM_PATH, m_pathRom);
1087         } else {
1088             const QString path = m_config->value(SETTING_ROM_PATH, QString()).toString();
1089             if (path.isEmpty()) {
1090                 m_pathRom.clear();
1091             } else {
1092                 m_pathRom = QDir::cleanPath(appDir().absoluteFilePath(path));
1093             }
1094         }
1095     } else {
1096         m_pathRom = QDir::cleanPath(o.romFile);
1097         if (!m_config->contains(SETTING_ROM_PATH)) {
1098             m_config->setValue(SETTING_ROM_PATH, m_pathRom);
1099         }
1100     }
1101 
1102     if (!o.imageFile.isEmpty()) {
1103         if (fileExists(o.imageFile)) {
1104             m_pathImage = QDir::cleanPath(QFileInfo(o.imageFile).absoluteFilePath());
1105         }
1106     }
1107 }
1108 
optAttemptLoad(CEmuOpts & o)1109 void MainWindow::optAttemptLoad(CEmuOpts &o) {
1110     if (!fileExists(m_pathRom)) {
1111         if (!runSetup()) {
1112             close();
1113         }
1114     } else {
1115         if (o.restoreOnOpen && !o.imageFile.isEmpty() && fileExists(m_pathImage)) {
1116             emuLoad(EMU_DATA_IMAGE);
1117         } else {
1118             if (o.forceReloadRom) {
1119                 emuLoad(EMU_DATA_ROM);
1120                 guiDelay(500);
1121             }
1122         }
1123     }
1124 }
1125 
~MainWindow()1126 MainWindow::~MainWindow() {
1127     delete m_config;
1128     delete ui;
1129 }
1130 
isInitialized()1131 bool MainWindow::isInitialized() {
1132     return m_initPassed;
1133 }
1134 
resetCEmu()1135 void MainWindow::resetCEmu() {
1136     ipcCloseConnected();
1137     m_needReload = true;
1138     m_needFullReset = true;
1139     close();
1140 }
1141 
resetGui()1142 void MainWindow::resetGui() {
1143     ipcCloseConnected();
1144     m_config->remove(SETTING_SCREEN_SKIN);
1145     m_config->remove(SETTING_SCREEN_SCALE);
1146     m_config->remove(SETTING_WINDOW_FULLSCREEN);
1147     m_config->remove(SETTING_WINDOW_GEOMETRY);
1148     m_config->remove(SETTING_WINDOW_POSITION);
1149     m_config->remove(SETTING_WINDOW_MEMORY_DOCKS);
1150     m_config->remove(SETTING_WINDOW_MEMORY_DOCK_ASCII);
1151     m_config->remove(SETTING_WINDOW_MEMORY_DOCK_BYTES);
1152     m_config->remove(SETTING_WINDOW_VISUALIZER_DOCKS);
1153     m_config->remove(SETTING_WINDOW_VISUALIZER_CONFIG);
1154     m_config->remove(SETTING_WINDOW_STATE);
1155     m_config->remove(SETTING_WINDOW_MENUBAR);
1156     m_config->remove(SETTING_WINDOW_STATUSBAR);
1157     m_config->remove(SETTING_WINDOW_SEPARATOR);
1158     m_config->remove(SETTING_UI_EDIT_MODE);
1159     m_needReload = true;
1160     close();
1161 }
1162 
isReload()1163 bool MainWindow::isReload() {
1164     return m_needReload;
1165 }
1166 
isResetAll()1167 bool MainWindow::isResetAll() {
1168     if (m_needFullReset) {
1169         QFile(m_config->value(SETTING_IMAGE_PATH).toString()).remove();
1170         QFile(m_config->value(SETTING_DEBUGGER_IMAGE_PATH).toString()).remove();
1171         if (m_keepSetup) {
1172             m_config->remove(SETTING_WINDOW_FULLSCREEN);
1173             m_config->remove(SETTING_IMAGE_PATH);
1174             m_config->remove(SETTING_DEBUGGER_IMAGE_PATH);
1175             m_config->remove(SETTING_SCREEN_SKIN);
1176             m_config->remove(SETTING_WINDOW_POSITION);
1177             m_config->remove(SETTING_WINDOW_GEOMETRY);
1178             m_config->remove(SETTING_WINDOW_STATE);
1179             m_config->remove(SETTING_WINDOW_MEMORY_DOCKS);
1180             m_config->remove(SETTING_UI_EDIT_MODE);
1181             m_config->remove(SETTING_WINDOW_MENUBAR);
1182             m_config->remove(SETTING_WINDOW_STATUSBAR);
1183             m_config->remove(SETTING_WINDOW_SEPARATOR);
1184             m_config->sync();
1185         } else {
1186             QFile(m_pathConfig).remove();
1187         }
1188     }
1189     return m_needFullReset;
1190 }
1191 
stateToPath(const QString & path)1192 void MainWindow::stateToPath(const QString &path) {
1193     emu.save(EMU_DATA_IMAGE, appDir().absoluteFilePath(path));
1194 }
1195 
stateFromPath(const QString & path)1196 void MainWindow::stateFromPath(const QString &path) {
1197     QString prev = m_pathImage;
1198     m_pathImage = appDir().absoluteFilePath(path);
1199     emuLoad(EMU_DATA_IMAGE);
1200     m_pathImage = prev;
1201 }
1202 
stateFromFile()1203 void MainWindow::stateFromFile() {
1204     QString path = QFileDialog::getOpenFileName(this, tr("Select saved image to restore from"),
1205                                                       m_dir.absolutePath(),
1206                                                       tr("CEmu images (*.ce);;All files (*.*)"));
1207     if (!path.isEmpty()) {
1208         m_dir = QFileInfo(path).absoluteDir();
1209         stateFromPath(path);
1210     }
1211 }
1212 
stateToFile()1213 void MainWindow::stateToFile() {
1214     QString path = QFileDialog::getSaveFileName(this, tr("Set image to save to"),
1215                                                       m_dir.absolutePath(),
1216                                                       tr("CEmu images (*.ce);;All files (*.*)"));
1217     if (!path.isEmpty()) {
1218         m_dir = QFileInfo(path).absoluteDir();
1219         stateToPath(path);
1220     }
1221 }
1222 
romExport()1223 void MainWindow::romExport() {
1224     QString filter = tr("ROM images (*.rom)");
1225     QString path = QFileDialog::getSaveFileName(this, tr("Set ROM image to save to"),
1226                                                 m_dir.absolutePath(),
1227                                                 filter, &filter);
1228     if (!path.isEmpty()) {
1229         m_dir = QFileInfo(path).absoluteDir();
1230         emu.save(EMU_DATA_ROM, path);
1231     }
1232 }
1233 
ramExport()1234 void MainWindow::ramExport() {
1235     QString filter = tr("RAM images (*.ram)");
1236     QString path = QFileDialog::getSaveFileName(this, tr("Set RAM image to save to"),
1237                                                 m_dir.absolutePath(),
1238                                                 filter, &filter);
1239     if (!path.isEmpty()) {
1240         m_dir = QFileInfo(path).absoluteDir();
1241         emu.save(EMU_DATA_RAM, path);
1242     }
1243 }
1244 
ramImport()1245 void MainWindow::ramImport() {
1246     QString path = QFileDialog::getOpenFileName(this, tr("Select RAM image to load"),
1247                                                       m_dir.absolutePath(),
1248                                                       tr("RAM images (*.ram);;All files (*.*)"));
1249     if (!path.isEmpty()) {
1250         m_dir = QFileInfo(path).absoluteDir();
1251         m_pathRam = path;
1252         emuLoad(EMU_DATA_RAM);
1253     }
1254 }
1255 
dropEvent(QDropEvent * e)1256 void MainWindow::dropEvent(QDropEvent *e) {
1257     if (m_isSendingRom) {
1258         setRom(m_dragRom);
1259     } else {
1260         sendingHandler->dropOccured(e, LINK_FILE);
1261     }
1262     equatesRefresh();
1263 }
1264 
dragEnterEvent(QDragEnterEvent * e)1265 void MainWindow::dragEnterEvent(QDragEnterEvent *e) {
1266 
1267     // check if we are dragging a rom file
1268     m_dragRom = sendingROM(e, &m_isSendingRom);
1269 
1270     if (!m_isSendingRom) {
1271         sendingHandler->dragOccured(e);
1272     }
1273 }
1274 
emuSaved(bool success)1275 void MainWindow::emuSaved(bool success) {
1276     if (!success) {
1277         QMessageBox::warning(this, MSG_WARNING, tr("Saving failed. Please check write permissions in settings directory."));
1278     }
1279 
1280     if (m_shutdown) {
1281         close();
1282     }
1283 }
1284 
closeEvent(QCloseEvent * e)1285 void MainWindow::closeEvent(QCloseEvent *e) {
1286     if (!m_shutdown) {
1287         m_shutdown = true;
1288 
1289         com.idClose();
1290 
1291         if (!m_initPassed) {
1292             QMainWindow::closeEvent(e);
1293             return;
1294         }
1295 
1296         if (guiDebug) {
1297             debugToggle();
1298         }
1299 
1300         if (guiReceive) {
1301             varToggle();
1302         }
1303 
1304         if (!m_needReload) {
1305             saveSettings();
1306 
1307             if (m_config->value(SETTING_SAVE_ON_CLOSE).toBool()) {
1308                 stateToPath(m_pathImage);
1309                 e->ignore();
1310                 return;
1311             }
1312         }
1313     }
1314 
1315     guiEmuValid = false;
1316     emu.stop();
1317     QMainWindow::closeEvent(e);
1318 }
1319 
console(const QString & str,const QColor & colorFg,const QColor & colorBg,int type)1320 void MainWindow::console(const QString &str, const QColor &colorFg, const QColor &colorBg, int type) {
1321     if (m_nativeConsole) {
1322         fputs(str.toStdString().c_str(), type == EmuThread::ConsoleErr ? stderr : stdout);
1323     } else {
1324         if (type == EmuThread::ConsoleNorm) {
1325             m_consoleFormat.setFontWeight(QFont::Normal);
1326         } else {
1327             m_consoleFormat.setFontWeight(QFont::Black);
1328         }
1329         m_consoleFormat.setBackground(colorBg);
1330         m_consoleFormat.setForeground(colorFg);
1331         QTextCursor cur(ui->console->document());
1332         cur.movePosition(QTextCursor::End);
1333         cur.insertText(str, m_consoleFormat);
1334         if (ui->checkAutoScroll->isChecked()) {
1335             ui->console->setTextCursor(cur);
1336         }
1337     }
1338 }
1339 
console(int type,const char * str,int size)1340 void MainWindow::console(int type, const char *str, int size) {
1341     if (size == -1) {
1342         size = static_cast<int>(strlen(str));
1343     }
1344     if (m_nativeConsole) {
1345         fwrite(str, sizeof(char), static_cast<size_t>(size), type == EmuThread::ConsoleErr ? stderr : stdout);
1346     } else {
1347         static int state = CONSOLE_ESC;
1348         static QColor sColorFg = ui->console->palette().color(QPalette::Text);
1349         static QColor sColorBg = ui->console->palette().color(QPalette::Base);
1350         const char *tok;
1351         const QColor lookup[8] = { Qt::black, Qt::red, Qt::green, Qt::yellow, Qt::blue, Qt::magenta, Qt::cyan, Qt::white };
1352 
1353         QColor colorFg = sColorFg;
1354         QColor colorBg = sColorBg;
1355         if ((tok = static_cast<const char*>(memchr(str, '\x1B', static_cast<size_t>(size))))) {
1356             if (tok != str) {
1357                 console(QString::fromUtf8(str, static_cast<int>(tok - str)), sColorFg, sColorBg, type);
1358                 size -= tok - str;
1359             }
1360             do {
1361                 while(--size > 0) {
1362                     char x = *tok++;
1363                     switch (state) {
1364                         case CONSOLE_ESC:
1365                             if (x == '\x1B') {
1366                                 state = CONSOLE_BRACKET;
1367                             }
1368                             break;
1369                         case CONSOLE_BRACKET:
1370                             if (x == '[') {
1371                                 state = CONSOLE_PARSE;
1372                             } else {
1373                                 state = CONSOLE_ESC;
1374                             }
1375                             break;
1376                         case CONSOLE_PARSE:
1377                             switch (x) {
1378                                 case '0':
1379                                     state = CONSOLE_ENDVAL;
1380                                     colorBg = ui->console->palette().color(QPalette::Base);
1381                                     colorFg = ui->console->palette().color(QPalette::Text);
1382                                     break;
1383                                 case '3':
1384                                     state = CONSOLE_FGCOLOR;
1385                                     break;
1386                                 case '4':
1387                                     state = CONSOLE_BGCOLOR;
1388                                     break;
1389                                 default:
1390                                     state = CONSOLE_ESC;
1391                                     sColorBg = ui->console->palette().color(QPalette::Base);
1392                                     sColorFg = ui->console->palette().color(QPalette::Text);
1393                                     break;
1394                             }
1395                             break;
1396                         case CONSOLE_FGCOLOR:
1397                             if (x >= '0' && x <= '7') {
1398                                 state = CONSOLE_ENDVAL;
1399                                 colorFg = lookup[x - '0'];
1400                             } else {
1401                                 state = CONSOLE_ESC;
1402                             }
1403                             break;
1404                         case CONSOLE_BGCOLOR:
1405                             if (x >= '0' && x <= '7') {
1406                                 state = CONSOLE_ENDVAL;
1407                                 colorBg = lookup[x - '0'];
1408                             } else {
1409                                 state = CONSOLE_ESC;
1410                             }
1411                             break;
1412                         case CONSOLE_ENDVAL:
1413                             if (x == ';') {
1414                                 state = CONSOLE_PARSE;
1415                             } else if (x == 'm') {
1416                                 sColorBg = colorBg;
1417                                 sColorFg = colorFg;
1418                                 state = CONSOLE_ESC;
1419                             } else {
1420                                 state = CONSOLE_ESC;
1421                             }
1422                             break;
1423                         default:
1424                             state = CONSOLE_ESC;
1425                             break;
1426                     }
1427                     if (state == CONSOLE_ESC) {
1428                         break;
1429                     }
1430                 }
1431                 if (size > 0) {
1432                     const char *tokn = static_cast<const char*>(memchr(tok, '\x1B', static_cast<size_t>(size)));
1433                     if (tokn) {
1434                         console(QString::fromUtf8(tok, static_cast<int>(tokn - tok)), sColorFg, sColorBg, type);
1435                         size -= tokn - tok;
1436                     } else {
1437                         console(QString::fromUtf8(tok, static_cast<int>(size)), sColorFg, sColorBg, type);
1438                     }
1439                     tok = tokn;
1440                 } else {
1441                     tok = nullptr;
1442                 }
1443             } while (tok);
1444         } else {
1445             console(QString::fromUtf8(str, size), sColorFg, sColorBg, type);
1446         }
1447     }
1448 }
1449 
consoleClear()1450 void MainWindow::consoleClear() {
1451     if (m_nativeConsole) {
1452         int ret;
1453 #ifdef _WIN32
1454         ret = system("cls");
1455 #else
1456         ret = system("clear");
1457 #endif
1458         if (ret == -1) {
1459             console(QStringLiteral("[CEmu] Error clearing console\n"));
1460         }
1461     } else {
1462         ui->console->clear();
1463     }
1464 }
1465 
consoleStr()1466 void MainWindow::consoleStr() {
1467     if (int available = emu.read.available()) {
1468         int remaining = CONSOLE_BUFFER_SIZE - emu.readPos;
1469         emu.read.acquire(available);
1470 
1471         console(emu.type, emu.buffer + emu.readPos, available < remaining ? available : remaining);
1472         if (available < remaining) {
1473             emu.readPos += available;
1474         } else if (available == remaining) {
1475             emu.readPos = 0;
1476         } else {
1477             emu.readPos = available - remaining;
1478             console(emu.type, emu.buffer, emu.readPos);
1479         }
1480 
1481         emu.write.release(available);
1482     }
1483 }
1484 
showEmuSpeed(int speed)1485 void MainWindow::showEmuSpeed(int speed) {
1486     if (m_timerEmuTriggered) {
1487         m_speedLabel.setText(QStringLiteral("  ") + tr("Emulated Speed: ") + QString::number(speed) + QStringLiteral("%"));
1488         m_timerEmuTriggered = !m_timerEmuTriggerable;
1489     }
1490 }
1491 
showFpsSpeed(double emuFps,double guiFps)1492 void MainWindow::showFpsSpeed(double emuFps, double guiFps) {
1493     static double guiFpsPrev = 0;
1494     static double emuFpsPrev = 0;
1495     if (emuFps < emuFpsPrev - 1 || emuFps > emuFpsPrev + 1) {
1496         ui->maxFps->setText(tr("Actual FPS: ") + QString::number(emuFps, 'f', 2));
1497         emuFpsPrev = emuFps;
1498     }
1499     if (guiFps < guiFpsPrev - 1 || guiFps > guiFpsPrev + 1) {
1500         m_fpsLabel.setText("FPS: " + QString::number(guiFps, 'f', 2));
1501         guiFpsPrev = guiFps;
1502     }
1503 }
1504 
lcdUpdate(double emuFps)1505 void MainWindow::lcdUpdate(double emuFps) {
1506     double guiFps = ui->lcd->refresh();
1507     if (m_timerFpsTriggered) {
1508         showFpsSpeed(emuFps, guiFps);
1509         m_timerFpsTriggered = !m_timerFpsTriggerable;
1510     }
1511 }
1512 
showStatusMsg(const QString & str)1513 void MainWindow::showStatusMsg(const QString &str) {
1514     m_msgLabel.setText(str);
1515 }
1516 
setRom(const QString & path)1517 void MainWindow::setRom(const QString &path) {
1518     m_pathRom = QDir::cleanPath(appDir().absoluteFilePath(path));
1519     m_config->setValue(SETTING_ROM_PATH, m_pathRom);
1520     ui->pathRom->setText(m_portable ? appDir().relativeFilePath(m_pathRom) : m_pathRom);
1521     emuLoad(EMU_DATA_ROM);
1522 }
1523 
runSetup()1524 bool MainWindow::runSetup() {
1525     RomSelection *romWizard = new RomSelection();
1526     romWizard->setWindowModality(Qt::NonModal);
1527     romWizard->exec();
1528 
1529     const QString path = romWizard->getRomPath();
1530 
1531     delete romWizard;
1532 
1533     if (path.isEmpty()) {
1534         m_initPassed = false;
1535         return false;
1536     } else {
1537         setRom(path);
1538     }
1539 
1540     return true;
1541 }
1542 
screenshotSave(const QString & nameFilter,const QString & defaultSuffix,const QString & temppath)1543 void MainWindow::screenshotSave(const QString& nameFilter, const QString& defaultSuffix, const QString& temppath) {
1544     QFileDialog dialog(this);
1545 
1546     dialog.setAcceptMode(QFileDialog::AcceptSave);
1547     dialog.setFileMode(QFileDialog::AnyFile);
1548     dialog.setDirectory(m_dir);
1549     dialog.setNameFilter(nameFilter);
1550     dialog.setWindowTitle(tr("Save Screen"));
1551     dialog.setDefaultSuffix(defaultSuffix);
1552     dialog.exec();
1553 
1554     if (!(dialog.selectedFiles().isEmpty())) {
1555         QStringList selected = dialog.selectedFiles();
1556         QString filename = selected.first();
1557         if (filename.isEmpty()) {
1558             QFile::remove(temppath);
1559         } else {
1560             QFile::remove(filename);
1561             QFile::rename(temppath, filename);
1562         }
1563     } else {
1564         QFile::remove(temppath);
1565     }
1566     m_dir = dialog.directory().absolutePath();
1567 }
1568 
screenshot()1569 void MainWindow::screenshot() {
1570     QString path = QDir::tempPath() + QDir::separator() + QStringLiteral("cemu_tmp.img");
1571     if (!ui->lcd->getImage().save(path, "PNG", 0)) {
1572         QMessageBox::critical(this, MSG_ERROR, tr("Failed to save screenshot."));
1573     }
1574 
1575     screenshotSave(tr("PNG images (*.png)"), QStringLiteral("png"), path);
1576 }
1577 
lcdCopy()1578 void MainWindow::lcdCopy() {
1579     QApplication::clipboard()->setImage(ui->lcd->getImage(), QClipboard::Clipboard);
1580 }
1581 
1582 #ifdef PNG_WRITE_APNG_SUPPORTED
recordAnimated()1583 void MainWindow::recordAnimated() {
1584     static QString path;
1585 
1586     if (guiDebug || guiReceive || guiSend) {
1587         return;
1588     }
1589 
1590     if (path.isEmpty()) {
1591         if (m_recording) {
1592             return;
1593         }
1594         path = QDir::tempPath() + QDir::separator() + QStringLiteral("apng_tmp.png");
1595         apng_start(path.toStdString().c_str(), ui->apngSkip->value());
1596         showStatusMsg(tr("Recording..."));
1597     } else {
1598         showStatusMsg(tr("Saving Recording..."));
1599         if (apng_stop()) {
1600             int res;
1601 
1602             QFileDialog dialog(this);
1603 
1604             dialog.setAcceptMode(QFileDialog::AcceptSave);
1605             dialog.setFileMode(QFileDialog::AnyFile);
1606             dialog.setDirectory(m_dir);
1607             dialog.setNameFilter(tr("PNG images (*.png)"));
1608             dialog.setWindowTitle(tr("Save Recorded PNG"));
1609             dialog.setDefaultSuffix(QStringLiteral("png"));
1610             res = dialog.exec();
1611 
1612             QFile(path).remove();
1613             m_dir = dialog.directory();
1614             path.clear();
1615 
1616             if (res == QDialog::Accepted) {
1617                 QStringList selected = dialog.selectedFiles();
1618                 QString filename = selected.first();
1619                 recordSave(filename);
1620             } else {
1621                 recordControlUpdate();
1622             }
1623         } else {
1624             QMessageBox::critical(this, MSG_ERROR, tr("A failure occured during PNG recording."));
1625             m_msgLabel.clear();
1626             path.clear();
1627         }
1628         return;
1629     }
1630 
1631     m_recording = true;
1632     ui->apngSkip->setEnabled(false);
1633     ui->actionRecordAnimated->setChecked(true);
1634     ui->buttonRecordAnimated->setText(tr("Stop Recording"));
1635     ui->actionRecordAnimated->setText(tr("Stop Recording..."));
1636 }
1637 
recordSave(const QString & filename)1638 void MainWindow::recordSave(const QString &filename) {
1639     ui->apngSkip->setEnabled(false);
1640     ui->actionRecordAnimated->setEnabled(false);
1641     ui->buttonRecordAnimated->setEnabled(false);
1642     ui->buttonRecordAnimated->setText(tr("Saving..."));
1643     ui->actionRecordAnimated->setText(tr("Saving Animated PNG..."));
1644 
1645     RecordingThread *thread = new RecordingThread();
1646     connect(thread, &RecordingThread::done, this, &MainWindow::recordControlUpdate);
1647     connect(thread, &RecordingThread::finished, thread, &QObject::deleteLater);
1648     thread->m_filename = filename;
1649     thread->m_optimize = m_optimizeRecording;
1650     thread->start();
1651 }
1652 
recordControlUpdate()1653 void MainWindow::recordControlUpdate() {
1654     m_recording = false;
1655     ui->apngSkip->setEnabled(true);
1656     ui->actionRecordAnimated->setEnabled(true);
1657     ui->buttonRecordAnimated->setEnabled(true);
1658     ui->actionRecordAnimated->setChecked(false);
1659     ui->buttonRecordAnimated->setText(tr("Record"));
1660     ui->actionRecordAnimated->setText(tr("Record animated PNG..."));
1661     m_msgLabel.clear();
1662 }
1663 
run()1664 void RecordingThread::run() {
1665     apng_save(m_filename.toStdString().c_str(), m_optimize);
1666     emit done();
1667 }
1668 #endif
1669 
showAbout()1670 void MainWindow::showAbout() {
1671     QMessageBox *aboutBox = new QMessageBox(this);
1672 
1673     aboutBox->setStyleSheet("QLabel{min-width: 620px;}");
1674     aboutBox->setWindowTitle(tr("About CEmu"));
1675 
1676     QAbstractButton *buttonUpdateCheck = aboutBox->addButton(tr("Check for updates"), QMessageBox::ActionRole);
1677     connect(buttonUpdateCheck, &QAbstractButton::clicked, this, [=](){ this->checkUpdate(true); });
1678 
1679     QAbstractButton *buttonCopyVersion = aboutBox->addButton(tr("Copy version"), QMessageBox::ActionRole);
1680     // Needed to prevent the button from closing the dialog
1681     buttonCopyVersion->disconnect();
1682     connect(buttonCopyVersion, &QAbstractButton::clicked, this, [=](){ QApplication::clipboard()->setText("CEmu " CEMU_VERSION " (git: " CEMU_GIT_SHA ")", QClipboard::Clipboard); buttonCopyVersion->setEnabled(false); buttonCopyVersion->setText("Version copied!"); });
1683 
1684     QAbstractButton *okButton = aboutBox->addButton(QMessageBox::Ok);
1685     okButton->setFocus();
1686 
1687     aboutBox->setText(tr("%1<h3>CEmu %2</h3>"
1688                          "<a href='https://github.com/CE-Programming/CEmu'>On GitHub</a><br>"
1689                          "<br>Main authors:<br>%3"
1690                          "<br>Other contributors include:<br>%4"
1691                          "<br>Translations provided by:<br>%5"
1692                          "<br>Many thanks to the following projects: %6<br>In-program icons are courtesy of %7.<br>"
1693                          "<br>CEmu is licensed under the %8, and is not a TI product nor is it affiliated to/endorsed by TI.<br><br>")
1694                          .arg(QStringLiteral("<img src=':/icons/resources/icons/icon.png' align='right'>"),
1695                               QStringLiteral(CEMU_VERSION " <small><i>(git: " CEMU_GIT_SHA ")</i></small>"),
1696                               QStringLiteral("Matt Waltz (<a href='https://github.com/mateoconlechuga'>MateoConLechuga</a>)<br>"
1697                                              "Jacob Young (<a href='https://github.com/jacobly0'>jacobly0</a>)<br>"
1698                                              "Adrien Bertrand (<a href='https://github.com/adriweb'>adriweb</a>)<br>"),
1699                               QStringLiteral("Zachary Wassall (<a href='https://github.com/runer112'>Runer112</a>)<br>"
1700                                              "Albert Huang (<a href='https://github.com/alberthdev'>alberthdev</a>)<br>"
1701                                              "Lionel Debroux (<a href='https://github.com/debrouxl'>debrouxl</a>)<br>"
1702                                              "Fabian Vogt (<a href='https://github.com/Vogtinator'>Vogtinator</a>)<br>"),
1703                               QStringLiteral("Matt Waltz (ES), Adrien Bertrand (FR), Stephan Paternotte &amp; Peter Tillema (NL)<br>"),
1704                               QStringLiteral("<a href='https://github.com/KnightOS/z80e'>z80e</a>, "
1705                                              "<a href='https://github.com/nspire-emus/firebird'>Firebird Emu</a>, "
1706                                              "<a href='https://github.com/debrouxl/tilibs'>tilibs</a>, "
1707                                              "<a href='https://github.com/adriweb/tivars_lib_cpp'>tivars_lib_cpp</a>."),
1708                               QStringLiteral("<a href='http://www.fatcow.com/free-icons'>FatCow's 'Farm-Fresh Web Icons'</a>"),
1709                               QStringLiteral("<a href='https://www.gnu.org/licenses/gpl-3.0.html'>GPLv3</a>")
1710                           ));
1711 
1712     aboutBox->setTextInteractionFlags(Qt::TextSelectableByMouse);
1713     aboutBox->setTextFormat(Qt::RichText);
1714     aboutBox->setWindowModality(Qt::NonModal);
1715     aboutBox->setAttribute(Qt::WA_DeleteOnClose);
1716     aboutBox->show();
1717 }
1718 
contextLcd(const QPoint & posa)1719 void MainWindow::contextLcd(const QPoint &posa) {
1720     QMenu menu;
1721     QPoint globalPos = ui->lcd->mapToGlobal(posa);
1722     menu.addMenu(ui->menuFile);
1723     menu.addMenu(ui->menuCalculator);
1724     menu.addMenu(ui->menuCapture);
1725     menu.addMenu(m_menuDocks);
1726     menu.addMenu(ui->menuExtras);
1727     menu.exec(globalPos);
1728 }
1729 
consoleModified()1730 void MainWindow::consoleModified() {
1731     ui->console->clear();
1732     if (ui->radioConsole->isChecked()) {
1733         ui->console->setEnabled(false);
1734         console(tr("[CEmu] Dock output redirected to stdout. Use the radio button to enable dock."),
1735                 ui->console->palette().color(QPalette::Text),
1736                 ui->console->palette().color(QPalette::Base));
1737     } else {
1738         ui->console->setEnabled(true);
1739     }
1740     m_nativeConsole = ui->radioConsole->isChecked();
1741     m_config->setValue(SETTING_NATIVE_CONSOLE, m_nativeConsole);
1742 }
1743 
pauseEmu(Qt::ApplicationState state)1744 void MainWindow::pauseEmu(Qt::ApplicationState state) {
1745     if (m_pauseOnFocus) {
1746         if (state == Qt::ApplicationInactive) {
1747             emu.setSpeed(0);
1748         }
1749         if (state == Qt::ApplicationActive) {
1750             setEmuSpeed(m_config->value(SETTING_EMUSPEED).toInt());
1751         }
1752     }
1753 }
1754 
1755 // ------------------------------------------------
1756 //  Linking things
1757 // ------------------------------------------------
1758 
varToggle()1759 void MainWindow::varToggle() {
1760     if (guiReceive) {
1761         varShow();
1762         emu.unblock();
1763     } else {
1764         emu.receive();
1765     }
1766 }
1767 
varSelect()1768 void MainWindow::varSelect() {
1769     if (guiDebug) {
1770        return;
1771     }
1772 
1773     QStringList fileNames = varDialog(QFileDialog::AcceptOpen, tr("TI Variable (*.8xp *.8xv *.8xl *.8xn *.8xm *.8xy *.8xg *.8xs *.8xd *.8xw *.8xc *.8xl *.8xz *.8xt *.8ca *.8cg *.8ci *.8ek *.b84 *.b83);;All Files (*.*)"), Q_NULLPTR);
1774 
1775     sendingHandler->sendFiles(fileNames, LINK_FILE);
1776     equatesRefresh();
1777 }
1778 
varDialog(QFileDialog::AcceptMode mode,const QString & name_filter,const QString & defaultSuffix)1779 QStringList MainWindow::varDialog(QFileDialog::AcceptMode mode, const QString &name_filter, const QString &defaultSuffix) {
1780     QFileDialog dialog(this);
1781     int good;
1782 
1783     dialog.setDefaultSuffix(defaultSuffix);
1784     dialog.setAcceptMode(mode);
1785     dialog.setFileMode(mode == QFileDialog::AcceptOpen ? QFileDialog::ExistingFiles : QFileDialog::AnyFile);
1786     dialog.setDirectory(m_dir);
1787     dialog.setNameFilter(name_filter);
1788     good = dialog.exec();
1789 
1790     m_dir = dialog.directory().absolutePath();
1791 
1792     if (good) {
1793         return dialog.selectedFiles();
1794     }
1795 
1796     return QStringList();
1797 }
1798 
varPressed(QTableWidgetItem * item)1799 void MainWindow::varPressed(QTableWidgetItem *item) {
1800     calc_var_t var = ui->emuVarView->item(item->row(), VAR_NAME_COL)->data(Qt::UserRole).value<calc_var_t>();
1801     if (var.size <= 2 || calc_var_is_asmprog(&var)) {
1802         return;
1803     } else if (!calc_var_is_internal(&var) || var.name[0] == '#') {
1804         std::string str;
1805         try {
1806             str = calc_var_content_string(var);
1807         } catch(...) {
1808             return;
1809         }
1810         bool isHexAppVar = var.type == CALC_VAR_TYPE_APP_VAR && !calc_var_is_python_appvar(&var);
1811         BasicCodeViewerWindow *codePopup = new BasicCodeViewerWindow(this, !isHexAppVar);
1812         codePopup->setVariableName(ui->emuVarView->item(item->row(), VAR_NAME_COL)->text());
1813         codePopup->setWindowModality(Qt::NonModal);
1814         codePopup->setAttribute(Qt::WA_DeleteOnClose);
1815         codePopup->setOriginalCode(QString::fromStdString(str), calc_var_is_tokenized(&var));
1816         codePopup->show();
1817     }
1818 }
1819 
emuBlocked(int req)1820 void MainWindow::emuBlocked(int req) {
1821     switch (req) {
1822         default:
1823         case EmuThread::RequestNone:
1824         case EmuThread::RequestPause:
1825             break;
1826         case EmuThread::RequestReceive:
1827             varShow();
1828             break;
1829     }
1830 }
1831 
varShow()1832 void MainWindow::varShow() {
1833     calc_var_t var;
1834 
1835     if (guiSend || guiDebug) {
1836         return;
1837     }
1838 
1839     guiReceive = !guiReceive;
1840 
1841     ui->emuVarView->setRowCount(0);
1842     ui->emuVarView->setSortingEnabled(false);
1843 
1844     if (!guiReceive) {
1845         ui->buttonRefreshList->setText(tr("View Calculator Variables"));
1846         ui->buttonReceiveFiles->setEnabled(false);
1847         ui->buttonReceiveFile->setEnabled(false);
1848         ui->buttonRun->setEnabled(true);
1849         ui->buttonSend->setEnabled(true);
1850         ui->emuVarView->setEnabled(false);
1851         ui->buttonResendFiles->setEnabled(true);
1852     } else {
1853         ui->buttonRefreshList->setText(tr("Resume emulation"));
1854         ui->buttonSend->setEnabled(false);
1855         ui->buttonReceiveFiles->setEnabled(true);
1856         ui->buttonReceiveFile->setEnabled(true);
1857         ui->buttonResendFiles->setEnabled(false);
1858         ui->buttonRun->setEnabled(false);
1859         ui->emuVarView->setEnabled(true);
1860 
1861         vat_search_init(&var);
1862         while (vat_search_next(&var)) {
1863             if (var.named || var.size > 2) {
1864                 int row;
1865 
1866                 row = ui->emuVarView->rowCount();
1867                 ui->emuVarView->setRowCount(row + 1);
1868 
1869                 bool var_preview_needs_gray = false;
1870                 QString var_value;
1871                 if (var.size <= 2) {
1872                     var_value = tr("Empty");
1873                     var_preview_needs_gray = true;
1874                 } else if (calc_var_is_asmprog(&var)) {
1875                     var_value = tr("Can't preview this");
1876                     var_preview_needs_gray = true;
1877                 } else if (calc_var_is_internal(&var) && var.name[0] != '#') { // # is previewable
1878                     var_value = tr("Can't preview this OS variable");
1879                     var_preview_needs_gray = true;
1880                 } else {
1881                     try {
1882                         var_value = QString::fromStdString(calc_var_content_string(var)).trimmed().replace("\n", " \\ ");
1883                         if (var_value.size() > 50) {
1884                             var_value.truncate(50);
1885                             var_value += QStringLiteral(" [...]");
1886                         }
1887                     } catch(...) {
1888                         var_value = tr("Can't preview this");
1889                         var_preview_needs_gray = true;
1890                     }
1891                 }
1892 
1893                 // Do not translate - things rely on those names.
1894                 QString var_type_str = calc_var_type_names[var.type];
1895                 if (calc_var_is_asmprog(&var)) {
1896                     var_type_str += QStringLiteral(" (ASM)");
1897                 }
1898 
1899                 QTableWidgetItem *var_name = new QTableWidgetItem(calc_var_name_to_utf8(var.name));
1900                 QTableWidgetItem *var_location = new QTableWidgetItem(var.archived ? tr("Archive") : QStringLiteral("RAM"));
1901                 QTableWidgetItem *var_type = new QTableWidgetItem(var_type_str);
1902                 QTableWidgetItem *var_preview = new QTableWidgetItem(var_value);
1903                 QTableWidgetItem *var_size = new QTableWidgetItem();
1904 
1905                 // Attach var index (hidden) to the name. Needed elsewhere
1906                 var_name->setData(Qt::UserRole, QVariant::fromValue(var));
1907                 var_size->setData(Qt::DisplayRole, var.size);
1908 
1909                 var_name->setCheckState(Qt::Unchecked);
1910 
1911                 if (var_preview_needs_gray) {
1912                     var_preview->setFont(varPreviewItalicFont);
1913                     var_preview->setForeground(Qt::gray);
1914                 } else {
1915                     var_preview->setFont(varPreviewCEFont);
1916                 }
1917 
1918                 ui->emuVarView->setItem(row, VAR_NAME_COL, var_name);
1919                 ui->emuVarView->setItem(row, VAR_LOCATION_COL, var_location);
1920                 ui->emuVarView->setItem(row, VAR_TYPE_COL, var_type);
1921                 ui->emuVarView->setItem(row, VAR_SIZE_COL, var_size);
1922                 ui->emuVarView->setItem(row, VAR_PREVIEW_COL, var_preview);
1923             }
1924         }
1925         ui->emuVarView->resizeColumnToContents(VAR_NAME_COL);
1926         ui->emuVarView->resizeColumnToContents(VAR_LOCATION_COL);
1927         ui->emuVarView->resizeColumnToContents(VAR_TYPE_COL);
1928         ui->emuVarView->resizeColumnToContents(VAR_SIZE_COL);
1929     }
1930 
1931     ui->emuVarView->setSortingEnabled(true);
1932 }
1933 
varSaveSelected()1934 void MainWindow::varSaveSelected() {
1935     QVector<calc_var_t> selectedVars;
1936     QStringList fileNames;
1937     for (int currRow = 0; currRow < ui->emuVarView->rowCount(); currRow++) {
1938         if (ui->emuVarView->item(currRow, VAR_NAME_COL)->checkState() == Qt::Checked) {
1939             selectedVars.append(ui->emuVarView->item(currRow, VAR_NAME_COL)->data(Qt::UserRole).value<calc_var_t>());
1940         }
1941     }
1942     if (selectedVars.size() < 2) {
1943         QMessageBox::warning(this, MSG_WARNING, tr("Select at least two files to group"));
1944     } else {
1945          fileNames = varDialog(QFileDialog::AcceptSave, tr("TI Group (*.8cg);;All Files (*.*)"), QStringLiteral("8cg"));
1946         if (fileNames.size() == 1) {
1947             if (emu_receive_variable(fileNames.first().toUtf8(), selectedVars.constData(), selectedVars.size()) != LINK_GOOD) {
1948                 QMessageBox::critical(this, MSG_ERROR, tr("Transfer error, see console for information:\nFile: ") + fileNames.first());
1949             } else {
1950                 QMessageBox::information(this, MSG_INFORMATION, tr("Transfer completed successfully."));
1951             }
1952         }
1953     }
1954 }
1955 
varSaveSelectedFiles()1956 void MainWindow::varSaveSelectedFiles() {
1957     QFileDialog dialog(this);
1958     dialog.setFileMode(QFileDialog::DirectoryOnly);
1959     dialog.setOption(QFileDialog::ShowDirsOnly, false);
1960 
1961     dialog.setDirectory(m_dir);
1962     int good = 0;
1963 
1964     for (int currRow = 0; currRow < ui->emuVarView->rowCount(); currRow++) {
1965         if (ui->emuVarView->item(currRow, VAR_NAME_COL)->checkState() == Qt::Checked) {
1966             good = 1;
1967             break;
1968         }
1969     }
1970 
1971     if (!good) {
1972         QMessageBox::warning(this, MSG_WARNING, tr("Select at least one file to transfer"));
1973         return;
1974     }
1975 
1976     good = dialog.exec();
1977     m_dir = dialog.directory().absolutePath();
1978 
1979     if (!good) {
1980         return;
1981     }
1982 
1983     QString name;
1984     QString filename;
1985 
1986     for (int currRow = 0; currRow < ui->emuVarView->rowCount(); currRow++) {
1987         if (ui->emuVarView->item(currRow, VAR_NAME_COL)->checkState() == Qt::Checked) {
1988             calc_var_t var = ui->emuVarView->item(currRow, VAR_NAME_COL)->data(Qt::UserRole).value<calc_var_t>();
1989 
1990             name = QString(calc_var_name_to_utf8(var.name));
1991             filename = dialog.directory().absolutePath() + "/" + name + "." + m_varExtensions[var.type1];
1992 
1993             if (emu_receive_variable(filename.toStdString().c_str(), &var, 1) != LINK_GOOD) {
1994                 good = 0;
1995                 break;
1996             }
1997         }
1998     }
1999 
2000     if (good) {
2001         QMessageBox::information(this, MSG_INFORMATION, tr("Transfer completed successfully."));
2002     } else {
2003         QMessageBox::critical(this, MSG_ERROR, tr("Transfer error, see console for information:\nFile: ") + filename);
2004     }
2005 }
2006 
varResend()2007 void MainWindow::varResend() {
2008     sendingHandler->resendSelected();
2009 }
2010 
2011 // ------------------------------------------------
2012 // Autotester things
2013 // ------------------------------------------------
2014 
autotesterErr(int errCode)2015 void MainWindow::autotesterErr(int errCode) {
2016     QString errMsg;
2017     switch (errCode) {
2018         case 0:
2019             return; // no error
2020         case -1:
2021             errMsg = tr("Error. No config loaded");
2022             break;
2023         case 1:
2024             errMsg = tr("Error. Couldn't follow the test sequence defined in the configuration");
2025             break;
2026         default:
2027             errMsg = tr("Error. Unknown one - wat?");
2028             break;
2029     }
2030     QMessageBox::warning(this, MSG_ERROR, errMsg);
2031 }
2032 
2033 
autotesterOpen(const QString & jsonPath)2034 int MainWindow::autotesterOpen(const QString& jsonPath) {
2035     if (jsonPath.length() == 0) {
2036         QMessageBox::warning(this, MSG_ERROR, tr("Please choose a json file or type its path."));
2037         return 1;
2038     }
2039     std::string jsonContents;
2040     std::ifstream ifs(jsonPath.toStdString());
2041 
2042     if (ifs.good()) {
2043         const std::string absJsonDirPath = QDir::toNativeSeparators(QFileInfo(jsonPath).absoluteDir().path()).toStdString();
2044         // TODO: fix me (don't use chdir, or at the very least, go back to the previous path afterwards)
2045         if (chdir(absJsonDirPath.c_str()) != 0) {
2046             QMessageBox::warning(this, MSG_ERROR, tr("Couldn't go to where the JSON file is."));
2047             return 0;
2048         }
2049         std::getline(ifs, jsonContents, '\0');
2050         if (!ifs.eof()) {
2051             QMessageBox::critical(this, MSG_ERROR, tr("Couldn't read JSON file."));
2052             return 0;
2053         }
2054     } else {
2055         QMessageBox::critical(this, MSG_ERROR, tr("Unable to open the file."));
2056         return 1;
2057     }
2058 
2059     autotester::ignoreROMfield = true;
2060     autotester::debugMode = false;
2061     if (autotester::loadJSONConfig(jsonContents))
2062     {
2063         ui->JSONconfigPath->setText(jsonPath);
2064         ui->buttonLaunchTest->setEnabled(true);
2065         std::cout << jsonPath.toStdString() << " loaded and verified. " << autotester::config.hashes.size() << " unique tests found." << std::endl;
2066     } else {
2067         QMessageBox::critical(this, MSG_ERROR, tr("See the test config file format and make sure values are correct and referenced files are there."));
2068         return 1;
2069     }
2070     return 0;
2071 }
2072 
autotesterLoad()2073 void MainWindow::autotesterLoad() {
2074     QFileDialog dialog(this);
2075     int good;
2076 
2077     ui->buttonLaunchTest->setEnabled(false);
2078 
2079     dialog.setDirectory(m_dir);
2080     dialog.setFileMode(QFileDialog::ExistingFile);
2081     dialog.setNameFilter(QStringLiteral("JSON config (*.json)"));
2082 
2083     good = dialog.exec();
2084     m_dir = dialog.directory().absolutePath();
2085 
2086     if (!good) {
2087         return;
2088     }
2089 
2090     QStringList selected = dialog.selectedFiles();
2091     autotesterOpen(selected.first());
2092 }
2093 
autotesterReload()2094 void MainWindow::autotesterReload() {
2095     autotesterOpen(ui->JSONconfigPath->text());
2096 }
2097 
autotesterLaunch()2098 void MainWindow::autotesterLaunch() {
2099     if (!autotester::configLoaded) {
2100         autotesterErr(-1);
2101         return;
2102     }
2103 
2104     if (ui->checkBoxTestReset->isChecked()) {
2105         resetEmu();
2106         guiDelay(4000);
2107     }
2108 
2109     if (ui->checkBoxTestClear->isChecked()) {
2110         sendEmuKey(CE_KEY_CLEAR);
2111     }
2112 
2113     QStringList filesList;
2114     for (const auto &file : autotester::config.transfer_files) {
2115         filesList << QString::fromStdString(file);
2116     }
2117 
2118     sendingHandler->sendFiles(filesList, LINK_FILE);
2119     equatesRefresh();
2120     while (guiSend) {
2121         guiDelay(10);
2122     }
2123 
2124     // Follow the sequence
2125     ui->buttonLaunchTest->setEnabled(false);
2126     emu.test(ui->JSONconfigPath->text(), true);
2127 }
2128 
autotesterTested(int status)2129 void MainWindow::autotesterTested(int status) {
2130     autotesterErr(status);
2131     if (!opts.suppressTestDialog) {
2132         QMessageBox::information(this, tr("Test results"), QString(tr("Out of %2 tests attempted:\n%4 passed\n%6 failed")).arg(QString::number(autotester::hashesTested), QString::number(autotester::hashesPassed), QString::number(autotester::hashesFailed)));
2133     }
2134     ui->buttonLaunchTest->setEnabled(true);
2135 }
2136 
autotesterUpdatePresets(int comboBoxIndex)2137 void MainWindow::autotesterUpdatePresets(int comboBoxIndex) {
2138     // The order matters, here! (See the combobox in the GUI)
2139     static const std::pair<unsigned int, unsigned int> mapIdConsts[] = {
2140         std::make_pair(autotester::hash_consts.at("vram_start"),     autotester::hash_consts.at("vram_16_size")),
2141         std::make_pair(autotester::hash_consts.at("vram_start"),     autotester::hash_consts.at("vram_8_size")),
2142         std::make_pair(autotester::hash_consts.at("vram2_start"),    autotester::hash_consts.at("vram_8_size")),
2143         std::make_pair(autotester::hash_consts.at("textShadow"),     autotester::hash_consts.at("textShadow_size")),
2144         std::make_pair(autotester::hash_consts.at("cmdShadow"),      autotester::hash_consts.at("cmdShadow_size")),
2145         std::make_pair(autotester::hash_consts.at("pixelShadow"),    autotester::hash_consts.at("pixelShadow_size")),
2146         std::make_pair(autotester::hash_consts.at("pixelShadow2"),   autotester::hash_consts.at("pixelShadow2_size")),
2147         std::make_pair(autotester::hash_consts.at("cmdPixelShadow"), autotester::hash_consts.at("cmdPixelShadow_size")),
2148         std::make_pair(autotester::hash_consts.at("plotSScreen"),    autotester::hash_consts.at("plotSScreen_size")),
2149         std::make_pair(autotester::hash_consts.at("saveSScreen"),    autotester::hash_consts.at("saveSScreen_size")),
2150         std::make_pair(autotester::hash_consts.at("lcdPalette"),     autotester::hash_consts.at("lcdPalette_size")),
2151         std::make_pair(autotester::hash_consts.at("cursorImage"),    autotester::hash_consts.at("cursorImage_size")),
2152         std::make_pair(autotester::hash_consts.at("ram_start"),      autotester::hash_consts.at("ram_size"))
2153     };
2154     if (comboBoxIndex >= 1 && comboBoxIndex <= static_cast<int>((sizeof(mapIdConsts)/sizeof(mapIdConsts[0])))) {
2155         char buf[10] = {0};
2156         sprintf(buf, "0x%X", mapIdConsts[comboBoxIndex-1].first);
2157         ui->startCRC->setText(buf);
2158         sprintf(buf, "0x%X", mapIdConsts[comboBoxIndex-1].second);
2159         ui->sizeCRC->setText(buf);
2160         autotesterRefreshCRC();
2161     }
2162 }
2163 
autotesterRefreshCRC()2164 void MainWindow::autotesterRefreshCRC() {
2165     QLineEdit *startCRC = ui->startCRC;
2166     QLineEdit *sizeCRC = ui->sizeCRC;
2167 
2168     startCRC->setText(startCRC->text().trimmed());
2169     sizeCRC->setText(sizeCRC->text().trimmed());
2170 
2171     if (startCRC->text().isEmpty() || sizeCRC->text().isEmpty()) {
2172         QMessageBox::critical(this, MSG_ERROR, tr("Make sure you have entered a valid start/size pair or preset."));
2173         return;
2174     }
2175 
2176     // Get GUI values
2177     bool ok1, ok2; // catch conversion issues
2178     uint32_t tmp_start = startCRC->text().toUInt(&ok1, 0);
2179     int32_t  crc_size  = sizeCRC->text().toInt(&ok2, 0);
2180     if (!ok1 || !ok2) {
2181         QMessageBox::critical(this, MSG_ERROR, tr("Could not convert those values into numbers"));
2182         return;
2183     }
2184 
2185     // Get real start pointer
2186     void* start = virt_mem_dup(tmp_start, crc_size);
2187     if (!start) {
2188         QMessageBox::critical(this, MSG_ERROR, tr("Could not retrieve this memory chunk"));
2189         return;
2190     }
2191 
2192     // Compute and display CRC
2193     ui->valueCRC->setText(int2hex(crc32(start, static_cast<size_t>(crc_size)), 8));
2194     ui->valueCRC->repaint();
2195     free(start);
2196 }
2197 
resetEmu()2198 void MainWindow::resetEmu() {
2199     guiReset = true;
2200 
2201     if (guiReceive) {
2202         varToggle();
2203     }
2204     if (guiDebug) {
2205         debugToggle();
2206     }
2207 
2208     emu.reset();
2209 }
2210 
emuCheck(emu_state_t state,emu_data_t type)2211 void MainWindow::emuCheck(emu_state_t state, emu_data_t type) {
2212 
2213     /* don't need to do anything if just loading ram */
2214     if (type == EMU_DATA_RAM && state == EMU_STATE_VALID) {
2215         guiEmuValid = true;
2216         return;
2217     }
2218 
2219     /* verify emulation state */
2220     switch (state) {
2221         case EMU_STATE_VALID:
2222             break;
2223         case EMU_STATE_NOT_A_CE:
2224             if (QMessageBox::Yes == QMessageBox::question(this, MSG_WARNING, tr("Image does not appear to be from a CE. Do you want to attempt to load it anyway? "
2225                                                           "This may cause instability."), QMessageBox::Yes|QMessageBox::No)) {
2226                 state = EMU_STATE_VALID;
2227             }
2228             break;
2229         case EMU_STATE_INVALID:
2230             if (type == EMU_DATA_IMAGE) {
2231                 console(QStringLiteral("[CEmu] Failed loading image, falling back to ROM.\n"));
2232                 emu.load(EMU_DATA_ROM, m_pathRom);
2233             }
2234             break;
2235     }
2236 
2237     if (state == EMU_STATE_VALID) {
2238         ui->lcd->setMain();
2239         setCalcSkinTopFromType();
2240         setKeypadColor(m_config->value(SETTING_KEYPAD_COLOR, get_device_type() ? KEYPAD_WHITE : KEYPAD_BLACK).toUInt());
2241         for (const auto &dock : findChildren<DockWidget*>()) {
2242             if (dock->windowTitle() == TXT_VISUALIZER_DOCK) {
2243                 static_cast<VisualizerWidget*>(dock->widget())->forceUpdate();
2244             }
2245         }
2246         emu.start();
2247         guiEmuValid = true;
2248     }
2249 
2250     guiReset = false;
2251 }
2252 
emuLoad(emu_data_t type)2253 void MainWindow::emuLoad(emu_data_t type) {
2254     guiEmuValid = false;
2255     QString path;
2256 
2257     if (guiReceive) {
2258         varToggle();
2259     }
2260     if (guiDebug) {
2261         debugToggle();
2262     }
2263 
2264     switch (type) {
2265         case EMU_DATA_ROM:
2266             path = m_pathRom;
2267             break;
2268         case EMU_DATA_IMAGE:
2269             path = m_pathImage;
2270             break;
2271         case EMU_DATA_RAM:
2272             path = m_pathRam;
2273             break;
2274     }
2275 
2276     emu.load(type, path);
2277 }
2278 
disasmLine()2279 void MainWindow::disasmLine() {
2280     bool useLabel = false;
2281     map_t::iterator sit;
2282     std::pair<map_t::iterator, map_t::iterator> range;
2283     unsigned int numLines = 1;
2284 
2285     if (disasm.base != disasm.next) {
2286         disasm.base = disasm.next;
2287         if (disasm.map.count(static_cast<uint32_t>(disasm.next))) {
2288             range = disasm.map.equal_range(static_cast<uint32_t>(disasm.next));
2289 
2290             numLines = 0;
2291             for (sit = range.first;  sit != range.second;  ++sit) {
2292                numLines++;
2293             }
2294 
2295             disasm.highlight.watchR = false;
2296             disasm.highlight.watchW = false;
2297             disasm.highlight.breakP = false;
2298             disasm.highlight.pc = false;
2299 
2300             disasm.instr.data.clear();
2301             disasm.instr.opcode.clear();
2302             disasm.instr.operands.clear();
2303             disasm.instr.size = 0;
2304 
2305             useLabel = true;
2306         } else {
2307             disasmGet();
2308         }
2309     } else {
2310         disasmGet();
2311     }
2312 
2313     if (useLabel) {
2314         range = disasm.map.equal_range(static_cast<uint32_t>(disasm.next));
2315         sit = range.first;
2316     }
2317 
2318     for (unsigned int j = 0; j < numLines; j++) {
2319 
2320         QString line;
2321         QString symbols;
2322         QString highlighted;
2323 
2324         if (useLabel) {
2325             if (disasm.base > 511 || (disasm.base < 512 && sit->second[0] == '_')) {
2326                 line = QString(QStringLiteral("<pre><b><font color='#444'>%1</font></b>     %2</pre>"))
2327                                         .arg(int2hex(static_cast<uint32_t>(disasm.base), 6),
2328                                              QString::fromStdString(disasm.bold_sym ? "<b>" + sit->second + "</b>" : sit->second) + ":");
2329 
2330                 m_disasm->appendHtml(line);
2331             }
2332 
2333             if (numLines == j + 1) {
2334                 useLabel = false;
2335             }
2336             sit++;
2337         } else {
2338             symbols = QString(QStringLiteral("<b><font color='#008000'>%1</font><font color='#808000'>%2</font><font color='#800000'>%3</font></b>"))
2339                              .arg((disasm.highlight.watchR  ? QStringLiteral("R") : QStringLiteral(" ")),
2340                                   (disasm.highlight.watchW ? QStringLiteral("W") : QStringLiteral(" ")),
2341                                   (disasm.highlight.breakP  ? QStringLiteral("X") : QStringLiteral(" ")));
2342 
2343             highlighted = QString::fromStdString(disasm.instr.operands)
2344                                   .replace(QRegularExpression(QStringLiteral("(\\$[0-9a-fA-F]+)")), QStringLiteral("<font color='green'>\\1</font>")) // hex numbers
2345                                   .replace(QRegularExpression(QStringLiteral("(^\\d)")), QStringLiteral("<font color='blue'>\\1</font>"))             // dec number
2346                                   .replace(QRegularExpression(QStringLiteral("([()])")), QStringLiteral("<font color='#600'>\\1</font>"));            // parentheses
2347 
2348             line = QString(QStringLiteral("<pre><b><font color='#444'>%1</font></b> %2 %3  <font color='%4'>%5</font> %6</pre>"))
2349                            .arg(disasm.addr ? int2hex(static_cast<uint32_t>(disasm.base), 6) : QString(),
2350                                 symbols,
2351                                 disasm.bytes ? QString::fromStdString(disasm.instr.data).leftJustified(12, ' ') : QStringLiteral(" "),
2352                                 m_disasmOpcodeColor,
2353                                 QString::fromStdString(disasm.instr.opcode),
2354                                 highlighted);
2355 
2356             m_disasm->appendHtml(line);
2357         }
2358     }
2359 
2360     if (!m_disasmOffsetSet && disasm.next > m_disasmAddr) {
2361         m_disasmOffsetSet = true;
2362         m_disasmOffset = m_disasm->textCursor();
2363         m_disasmOffset.movePosition(QTextCursor::StartOfLine);
2364     }
2365 
2366     if (disasm.highlight.pc == true) {
2367         m_disasm->addHighlight(QColor(Qt::blue).lighter(160));
2368     }
2369 }
2370 
contextDisasm(const QPoint & posa)2371 void MainWindow::contextDisasm(const QPoint &posa) {
2372     QString setPc = tr("Set PC");
2373     QString toggleBreak = tr("Toggle Breakpoint");
2374     QString toggleWrite = tr("Toggle Write Watchpoint");
2375     QString toggleRead = tr("Toggle Read Watchpoint");
2376     QString toggleRw = tr("Toggle Read/Write Watchpoint");
2377     QString runUntil = tr("Run Until");
2378     QString gotoMem = tr("Goto Memory View");
2379 
2380     m_disasm->setTextCursor(m_disasm->cursorForPosition(posa));
2381     QPoint globalPos = m_disasm->mapToGlobal(posa);
2382     QString addrStr = m_disasm->getSelectedAddr();
2383     uint32_t addr = static_cast<uint32_t>(hex2int(addrStr));
2384 
2385     QMenu menu;
2386     menu.addAction(runUntil);
2387     menu.addSeparator();
2388     menu.addAction(toggleBreak);
2389     menu.addAction(toggleRead);
2390     menu.addAction(toggleWrite);
2391     menu.addAction(toggleRw);
2392     menu.addSeparator();
2393     menu.addAction(gotoMem);
2394     menu.addAction(setPc);
2395 
2396     QAction *item = menu.exec(globalPos);
2397     if (item) {
2398         if (item->text() == setPc) {
2399             ui->pcregView->setText(addrStr);
2400             debug_set_pc(addr);
2401             disasmUpdateAddr(static_cast<int>(cpu.registers.PC), true);
2402         } else if (item->text() == toggleBreak) {
2403             breakAddGui();
2404             memUpdate();
2405         } else if (item->text() == toggleRead) {
2406             watchAddGuiR();
2407             memUpdate();
2408         } else if (item->text() == toggleWrite) {
2409             watchAddGuiW();
2410             memUpdate();
2411         } else if (item->text() == toggleRw) {
2412             watchAddGuiRW();
2413             memUpdate();
2414         } else if (item->text() == runUntil) {
2415             m_runUntilAddr = addr;
2416             debugToggle();
2417             debugStep(DBG_RUN_UNTIL);
2418         } else if (item->text() == gotoMem) {
2419             gotoMemAddr(addr);
2420         }
2421     }
2422 }
2423 
varLaunch(const calc_var_t * prgm)2424 void MainWindow::varLaunch(const calc_var_t *prgm) {
2425     keypadBridge->releaseAll();
2426     ui->lcd->setFocus();
2427     guiDelay(50);
2428 
2429     sendEmuKey(CE_KEY_CLEAR);
2430     if (calc_var_is_asmprog(prgm)) {
2431         sendEmuKey(CE_KEY_ASM);
2432     }
2433     sendEmuKey(CE_KEY_PRGM);
2434     for (const uint8_t *c = prgm->name; *c; c++) {
2435         sendEmuLetterKey(static_cast<char>(*c)); // type program name
2436     }
2437     sendEmuKey(CE_KEY_ENTER);
2438     ui->lcd->setFocus();
2439 }
2440 
contextVars(const QPoint & posa)2441 void MainWindow::contextVars(const QPoint& posa) {
2442     int row = ui->emuVarView->rowAt(posa.y());
2443     if (row < 0) {
2444         return;
2445     }
2446 
2447     const calc_var_t var = ui->emuVarView->item(row, VAR_NAME_COL)->data(Qt::UserRole).value<calc_var_t>();
2448 
2449     QString launch = tr("Launch program");
2450 
2451     QMenu contextMenu;
2452     if (calc_var_is_prog(&var) && !calc_var_is_internal(&var)) {
2453         contextMenu.addAction(launch);
2454     }
2455 
2456     QAction *selectedItem = contextMenu.exec(ui->emuVarView->mapToGlobal(posa));
2457     if (selectedItem) {
2458         if (selectedItem->text() == launch) {
2459             varToggle();
2460             varLaunch(&var);
2461         }
2462     }
2463 }
2464 
contextConsole(const QPoint & posa)2465 void MainWindow::contextConsole(const QPoint &posa) {
2466     bool ok = true;
2467 
2468     QString gotoMem = tr("Goto Memory View");
2469     QString gotoDisasm = tr("Goto Disassembly View");
2470     QString toggleBreak = tr("Toggle Breakpoint");
2471     QString toggleWrite = tr("Toggle Write Watchpoint");
2472     QString toggleRead = tr("Toggle Read Watchpoint");
2473     QString toggleRw = tr("Toggle Read/Write Watchpoint");
2474     QPoint globalp = ui->console->mapToGlobal(posa);
2475     QTextCursor cursor = ui->console->cursorForPosition(posa);
2476     ui->console->setTextCursor(cursor);
2477     cursor.select(QTextCursor::WordUnderCursor);
2478 
2479     QString equ = getAddressOfEquate(cursor.selectedText().toUpper().toStdString());
2480     uint32_t address;
2481 
2482     if (!equ.isEmpty()) {
2483         address = static_cast<uint32_t>(hex2int(equ));
2484     } else {
2485         address = cursor.selectedText().toUInt(&ok, 16);
2486     }
2487 
2488     if (ok) {
2489         ui->console->setTextCursor(cursor);
2490 
2491         QMenu menu;
2492         menu.addAction(gotoMem);
2493         menu.addAction(gotoDisasm);
2494         menu.addSeparator();
2495         menu.addAction(toggleBreak);
2496         menu.addAction(toggleRead);
2497         menu.addAction(toggleWrite);
2498         menu.addAction(toggleRw);
2499 
2500         QAction *item = menu.exec(globalp);
2501         if (item) {
2502             if (item->text() == gotoMem) {
2503                 debugForce();
2504                 gotoMemAddr(address);
2505             } else if (item->text() == gotoDisasm) {
2506                 debugForce();
2507                 gotoDisasmAddr(address);
2508             } else if (item->text() == toggleBreak) {
2509                 breakAdd(breakNextLabel(), address, true, true, false);
2510             } else if (item->text() == toggleRead) {
2511                 watchAdd(watchNextLabel(), address, address, DBG_MASK_READ, true, false);
2512             } else if (item->text() == toggleWrite) {
2513                 watchAdd(watchNextLabel(), address, address, DBG_MASK_WRITE, true, false);
2514             } else if (item->text() == toggleRw) {
2515                 watchAdd(watchNextLabel(), address, address, DBG_MASK_READ | DBG_MASK_WRITE, true, false);
2516             }
2517             memDocksUpdate();
2518         }
2519     }
2520 }
2521 
2522 // ------------------------------------------------
2523 // GUI IPC things
2524 // ------------------------------------------------
2525 
ipcSetup()2526 bool MainWindow::ipcSetup() {
2527     // start the main communictions
2528     if (com.ipcSetup(opts.idString, opts.pidString)) {
2529         return true;
2530     }
2531 
2532     // if failure, then send a command to the other process with the command options
2533     QByteArray byteArray;
2534     QDataStream stream(&byteArray, QIODevice::WriteOnly);
2535     stream.setVersion(QDataStream::Qt_5_5);
2536     unsigned int type = IPC_CLI;
2537 
2538     stream << type
2539            << opts.useUnthrottled
2540            << opts.suppressTestDialog
2541            << opts.deforceReset
2542            << opts.forceReloadRom
2543            << opts.romFile
2544            << opts.autotesterFile
2545            << opts.imageFile
2546            << opts.sendFiles
2547            << opts.sendArchFiles
2548            << opts.sendRAMFiles
2549            << opts.restoreOnOpen
2550            << opts.speed
2551            << opts.launchPrgm;
2552 
2553     // blocking call
2554     com.send(byteArray);
2555     return false;
2556 }
2557 
ipcCli(QDataStream & stream)2558 void MainWindow::ipcCli(QDataStream &stream) {
2559     console(QStringLiteral("[CEmu] Received IPC: command line options\n"));
2560     CEmuOpts o;
2561 
2562     stream >> o.useUnthrottled
2563            >> o.suppressTestDialog
2564            >> o.deforceReset
2565            >> o.forceReloadRom
2566            >> o.romFile
2567            >> o.autotesterFile
2568            >> o.imageFile
2569            >> o.sendFiles
2570            >> o.sendArchFiles
2571            >> o.sendRAMFiles
2572            >> o.restoreOnOpen
2573            >> o.speed
2574            >> o.launchPrgm;
2575 
2576     optLoadFiles(o);
2577     optAttemptLoad(o);
2578     optSend(o);
2579     if (o.speed != -1) {
2580         setEmuSpeed(o.speed);
2581     }
2582 }
2583 
ipcCloseConnected()2584 void MainWindow::ipcCloseConnected() {
2585     QString idPath = configPath + "/id/";
2586     QDir dir(idPath);
2587     QStringList clients = dir.entryList(QDir::Filter::Files);
2588     int delay = 100;
2589 
2590     foreach (const QString &id, clients) {
2591         QFile file(idPath + id);
2592         if (file.open(QIODevice::ReadOnly)) {
2593             QTextStream stream(&file);
2594             QString pid = stream.readLine();
2595             if (!isProcRunning(static_cast<pid_t>(pid.toLongLong()))) {
2596                 file.close();
2597                 file.remove();
2598                 continue;
2599             } else {
2600                 if (opts.pidString != pid) {
2601                     QByteArray byteArray;
2602                     QDataStream stream(&byteArray, QIODevice::WriteOnly);
2603                     stream.setVersion(QDataStream::Qt_5_5);
2604                     unsigned int type = IPC_CLOSE;
2605                     stream << type;
2606 
2607                     com.clientSetup(pid);
2608                     com.send(byteArray);
2609                     delay += 100;
2610                 }
2611             }
2612         }
2613         file.close();
2614     }
2615 
2616     // wait for the settings to be written by the other processes
2617     guiDelay(delay);
2618 }
2619 
ipcReceived()2620 void MainWindow::ipcReceived() {
2621     QByteArray byteArray(com.getData());
2622 
2623     QDataStream stream(byteArray);
2624     stream.setVersion(QDataStream::Qt_5_5);
2625     unsigned int type;
2626 
2627     stream >> type;
2628 
2629     switch (type) {
2630         case IPC_CLI:
2631            if (m_setup) {
2632                show();
2633                raise();
2634            }
2635            ipcCli(stream);
2636            break;
2637         case IPC_CLOSE:
2638             close();
2639             break;
2640         default:
2641            console(QStringLiteral("[CEmu] IPC Unknown\n"));
2642            break;
2643     }
2644 }
2645 
ipcSetId()2646 void MainWindow::ipcSetId() {
2647     bool ok = true;
2648     QString text = QInputDialog::getText(this, tr("CEmu Change ID"), tr("New ID:"), QLineEdit::Normal, opts.idString, &ok);
2649     if (ok && !text.isEmpty() && text != opts.idString) {
2650         if (!InterCom::idOpen(text)) {
2651             com.idClose();
2652             com.ipcSetup(opts.idString = text, opts.pidString);
2653             console(QStringLiteral("[CEmu] Initialized Server [") + opts.idString + QStringLiteral(" | ") + com.getServerName() + QStringLiteral("]\n"));
2654             setWindowTitle(QStringLiteral("CEmu | ") + opts.idString);
2655         }
2656     }
2657 }
2658 
ipcSpawn()2659 void MainWindow::ipcSpawn() {
2660     QStringList arguments;
2661     arguments << QStringLiteral("--id") << randomString(15);
2662 
2663     QProcess *myProcess = new QProcess(this);
2664     myProcess->startDetached(execPath, arguments);
2665 }
2666 
stateAddNew()2667 void MainWindow::stateAddNew() {
2668     QString name = randomString(6);
2669     QString path = QDir::cleanPath(QFileInfo(m_config->fileName()).absoluteDir().absolutePath() + QStringLiteral("/") + name + QStringLiteral(".ce"));
2670     stateAdd(name, path);
2671 }
2672 
stateAdd(QString & name,QString & path)2673 void MainWindow::stateAdd(QString &name, QString &path) {
2674     const int row = ui->slotView->rowCount();
2675     ui->slotView->setSortingEnabled(false);
2676 
2677     QToolButton *btnLoad   = new QToolButton();
2678     QToolButton *btnSave   = new QToolButton();
2679     QToolButton *btnEdit   = new QToolButton();
2680     QToolButton *btnRemove = new QToolButton();
2681 
2682     btnLoad->setIcon(m_iconLoad);
2683     btnSave->setIcon(m_iconSave);
2684     btnEdit->setIcon(m_iconEdit);
2685     btnRemove->setIcon(m_iconRemove);
2686 
2687     connect(btnRemove, &QToolButton::clicked, this, &MainWindow::stateRemove);
2688     connect(btnLoad, &QToolButton::clicked, this, &MainWindow::stateLoad);
2689     connect(btnSave, &QToolButton::clicked, this, &MainWindow::stateSave);
2690     connect(btnEdit, &QToolButton::clicked, this, &MainWindow::stateEdit);
2691 
2692     QTableWidgetItem *itemName   = new QTableWidgetItem(name);
2693     QTableWidgetItem *itemLoad   = new QTableWidgetItem();
2694     QTableWidgetItem *itemSave   = new QTableWidgetItem();
2695     QTableWidgetItem *itemEdit   = new QTableWidgetItem();
2696     QTableWidgetItem *itemRemove = new QTableWidgetItem();
2697 
2698     itemEdit->setData(Qt::UserRole, path);
2699     stateToPath(itemEdit->data(Qt::UserRole).toString());
2700 
2701     ui->slotView->setRowCount(row + 1);
2702     ui->slotView->setItem(row, SLOT_NAME_COL, itemName);
2703     ui->slotView->setItem(row, SLOT_LOAD_COL, itemLoad);
2704     ui->slotView->setItem(row, SLOT_SAVE_COL, itemSave);
2705     ui->slotView->setItem(row, SLOT_EDIT_COL, itemEdit);
2706     ui->slotView->setItem(row, SLOT_REMOVE_COL, itemRemove);
2707 
2708     ui->slotView->setCellWidget(row, SLOT_LOAD_COL, btnLoad);
2709     ui->slotView->setCellWidget(row, SLOT_SAVE_COL, btnSave);
2710     ui->slotView->setCellWidget(row, SLOT_EDIT_COL, btnEdit);
2711     ui->slotView->setCellWidget(row, SLOT_REMOVE_COL, btnRemove);
2712 
2713     ui->slotView->setCurrentCell(row, SLOT_NAME_COL);
2714 
2715     stateSaveInfo();
2716 
2717     ui->slotView->setVisible(false);
2718     ui->slotView->resizeColumnsToContents();
2719     ui->slotView->setVisible(true);
2720     ui->slotView->setSortingEnabled(true);
2721 }
2722 
stateGet(QObject * obj,int col)2723 int MainWindow::stateGet(QObject *obj, int col) {
2724     int row;
2725 
2726     for (row = 0; row < ui->slotView->rowCount(); row++){
2727         if (obj == ui->slotView->cellWidget(row, col)) {
2728             break;
2729         }
2730     }
2731 
2732     return row;
2733 }
2734 
stateEdit()2735 void MainWindow::stateEdit() {
2736     bool ok;
2737     int row = stateGet(sender(), SLOT_EDIT_COL);
2738     QString old = ui->slotView->item(row, SLOT_EDIT_COL)->data(Qt::UserRole).toString();
2739     QString path = QInputDialog::getText(this, tr("Enter image path"), Q_NULLPTR, QLineEdit::Normal, old, &ok);
2740     if (ok && !path.isEmpty()) {
2741         QFile(old).rename(path);
2742         ui->slotView->item(row, SLOT_EDIT_COL)->setData(Qt::UserRole, path);
2743     }
2744 }
2745 
stateRemove()2746 void MainWindow::stateRemove() {
2747     int row = stateGet(sender(), SLOT_REMOVE_COL);
2748     QFile(ui->slotView->item(row, SLOT_EDIT_COL)->data(Qt::UserRole).toString()).remove();
2749     ui->slotView->removeRow(row);
2750 }
2751 
stateSave()2752 void MainWindow::stateSave() {
2753     int row = stateGet(sender(), SLOT_SAVE_COL);
2754     QString path = ui->slotView->item(row, SLOT_EDIT_COL)->data(Qt::UserRole).toString();
2755     stateToPath(path);
2756 }
2757 
stateLoad()2758 void MainWindow::stateLoad() {
2759     int row = stateGet(sender(), SLOT_LOAD_COL);
2760     QString path = ui->slotView->item(row, SLOT_EDIT_COL)->data(Qt::UserRole).toString();
2761     stateFromPath(path);
2762 }
2763 
2764 const char *MainWindow::m_varExtensions[] = {
2765         "8xn",  // 00
2766         "8xl",
2767         "8xm",
2768         "8xy",
2769         "8xs",
2770         "8xp",
2771         "8xp",
2772         "8ci",
2773         "8xd",  // 08
2774         "",
2775         "",
2776         "8xw",  // 0B
2777         "8xc",
2778         "8xl",  // 0D
2779         "",
2780         "8xw",  // 0F
2781         "8xz",  // 10
2782         "8xt",  // 11
2783         "",
2784         "",
2785         "",
2786         "8xv",  // 15
2787         "",
2788         "8cg",  // 17
2789         "8xn",  // 18
2790         "",
2791         "8ca",  // 1A
2792         "8xc",
2793         "8xn",
2794         "8xc",
2795         "8xc",
2796         "8xc",
2797         "8xn",
2798         "8xn",  // 21
2799         "",
2800         "8pu",  // 23
2801         "8ek",  // 24
2802         "",
2803         "",
2804         "",
2805         "",
2806     };
2807