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 & 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