1 #include "mainwindow.h"
2 #include "ui_mainwindow.h"
3
4 #include <QDir>
5 #include <QFileDialog>
6 #include <QTimer>
7 #include <QTouchEvent>
8 #include <QMessageBox>
9 #include <QSettings>
10 #include <QFont>
11 #include <QIcon>
12 #include <QObject>
13 #include <QEvent>
14 #include <QKeyEvent>
15 #include <QGraphicsScene>
16 #include <QCoreApplication>
17 #include <QAudioOutput>
18 #include <QAudioFormat>
19
20 #include <stdint.h>
21
22 #include "debugviewer.h"
23 #include "settingsmanager.h"
24 #include "statemanager.h"
25
26
MainWindow(QWidget * parent)27 MainWindow::MainWindow(QWidget* parent) :
28 QMainWindow(parent),
29 ui(new Ui::MainWindow){
30 QAudioFormat format;
31
32 //audio output
33 format.setSampleRate(AUDIO_SAMPLE_RATE);
34 format.setChannelCount(2);
35 format.setSampleSize(16);
36 format.setCodec("audio/pcm");
37 #if Q_BYTE_ORDER == Q_BIG_ENDIAN
38 format.setByteOrder(QAudioFormat::BigEndian);
39 #else
40 format.setByteOrder(QAudioFormat::LittleEndian);
41 #endif
42 format.setSampleType(QAudioFormat::SignedInt);
43
44 //submodules
45 settings = new QSettings(QDir::homePath() + "/MuCfg.txt", QSettings::IniFormat);//settings is public, create it first
46 settingsManager = new SettingsManager(this);
47 stateManager = new StateManager(this);
48 emuDebugger = new DebugViewer(this);
49 refreshDisplay = new QTimer(this);
50 audioDevice = new QAudioOutput(format, this);
51 audioOut = audioDevice->start();
52
53 //set variables to there default if its the first boot
54 if(settings->value("firstBootCompleted", "").toString() == ""){
55 //resource dir
56 #if defined(Q_OS_ANDROID)
57 settings->setValue("resourceDirectory", "/sdcard/Mu");
58 #elif defined(Q_OS_IOS)
59 settings->setValue("resourceDirectory", "/var/mobile/Media/Mu");
60 #else
61 settings->setValue("resourceDirectory", QDir::homePath() + "/Mu");
62 #endif
63 createHomeDirectoryTree(settings->value("resourceDirectory", "").toString());
64
65 //onscreen keys
66 #if defined(Q_OS_ANDROID) && defined(Q_OS_IOS)
67 settings->setValue("hideOnscreenKeys", false);
68 #else
69 //not a mobile device disable the on screen buttons
70 settings->setValue("hideOnscreenKeys", true);
71 #endif
72
73 //skip boot screen, most users dont want to wait 5 seconds on boot
74 settings->setValue("fastBoot", true);
75
76 settings->setValue("palmOsVersion", 4);
77
78 //dont run this function again unless the config is deleted
79 settings->setValue("firstBootCompleted", true);
80 }
81
82 //keyboard
83 for(uint8_t index = 0; index < EmuWrapper::BUTTON_TOTAL_COUNT; index++)
84 keyForButton[index] = settings->value("palmButton" + QString::number(index) + "Key", '\0').toInt();
85
86 //GUI
87 ui->setupUi(this);
88
89 //this makes the display window and button icons resize properly
90 ui->centralWidget->installEventFilter(this);
91 ui->centralWidget->setObjectName("centralWidget");
92
93 ui->up->installEventFilter(this);
94 ui->down->installEventFilter(this);
95 ui->left->installEventFilter(this);
96 ui->right->installEventFilter(this);
97 ui->center->installEventFilter(this);
98
99 ui->calendar->installEventFilter(this);
100 ui->addressBook->installEventFilter(this);
101 ui->todo->installEventFilter(this);
102 ui->notes->installEventFilter(this);
103 ui->voiceMemo->installEventFilter(this);
104
105 ui->power->installEventFilter(this);
106
107 ui->ctrlBtn->installEventFilter(this);
108 ui->reset->installEventFilter(this);
109 ui->stateManager->installEventFilter(this);
110 ui->screenshot->installEventFilter(this);
111 ui->settings->installEventFilter(this);
112 ui->install->installEventFilter(this);
113 ui->debugger->installEventFilter(this);
114 ui->bootApp->installEventFilter(this);
115
116 redraw();
117
118 #if !defined(EMU_DEBUG) || defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
119 //doesnt support debug tools
120 ui->debugger->hide();
121 #endif
122 connect(refreshDisplay, SIGNAL(timeout()), this, SLOT(updateDisplay()));
123 refreshDisplay->start(1000 / EMU_FPS);//update display every X milliseconds
124 }
125
~MainWindow()126 MainWindow::~MainWindow(){
127 refreshDisplay->stop();
128 delete stateManager;
129 delete emuDebugger;
130 delete refreshDisplay;
131 delete audioDevice;
132 delete settings;//settings is public, destroy it last
133 delete ui;
134 }
135
keyPressEvent(QKeyEvent * event)136 void MainWindow::keyPressEvent(QKeyEvent* event){
137 int key = event->key();
138
139 for(uint8_t index = 0; index < EmuWrapper::BUTTON_TOTAL_COUNT; index++)
140 if(keyForButton[index] == key)
141 emu.setKeyValue(index, true);
142 }
143
keyReleaseEvent(QKeyEvent * event)144 void MainWindow::keyReleaseEvent(QKeyEvent* event){
145 int key = event->key();
146
147 for(uint8_t index = 0; index < EmuWrapper::BUTTON_TOTAL_COUNT; index++)
148 if(keyForButton[index] == key)
149 emu.setKeyValue(index, false);
150 }
151
createHomeDirectoryTree(const QString & path)152 void MainWindow::createHomeDirectoryTree(const QString& path){
153 QDir homeDir(path);
154
155 //creates directorys if not present, does nothing if they exist already
156 homeDir.mkpath(".");
157 homeDir.mkpath("./screenshots");
158 homeDir.mkpath("./debugDumps");
159 }
160
popupErrorDialog(const QString & error)161 void MainWindow::popupErrorDialog(const QString& error){
162 QMessageBox::critical(this, "Mu", error, QMessageBox::Ok);
163 }
164
popupInformationDialog(const QString & info)165 void MainWindow::popupInformationDialog(const QString& info){
166 QMessageBox::information(this, "Mu", info, QMessageBox::Ok);
167 }
168
redraw()169 void MainWindow::redraw(){
170 bool hideOnscreenKeys = settings->value("hideOnscreenKeys", false).toBool();
171 QResizeEvent* resizeEvent = new QResizeEvent(ui->centralWidget->size(), ui->centralWidget->size());
172
173 //update current keys
174 ui->up->setHidden(hideOnscreenKeys);
175 ui->down->setHidden(hideOnscreenKeys);
176 ui->left->setHidden(hideOnscreenKeys);
177 ui->right->setHidden(hideOnscreenKeys);
178 ui->center->setHidden(hideOnscreenKeys);
179
180 ui->calendar->setHidden(hideOnscreenKeys);
181 ui->addressBook->setHidden(hideOnscreenKeys);
182 ui->todo->setHidden(hideOnscreenKeys);
183 ui->notes->setHidden(hideOnscreenKeys);
184 ui->voiceMemo->setHidden(hideOnscreenKeys);
185
186 ui->power->setHidden(hideOnscreenKeys);
187
188 //update current size
189 QCoreApplication::postEvent(ui->centralWidget, resizeEvent);
190 }
191
eventFilter(QObject * object,QEvent * event)192 bool MainWindow::eventFilter(QObject* object, QEvent* event){
193 if(event->type() == QEvent::Resize){
194 if(QString(object->metaObject()->className()) == "QPushButton"){
195 QPushButton* button = (QPushButton*)object;
196
197 button->setIconSize(QSize(button->size().width() / 1.7, button->size().height() / 1.7));
198 }
199
200 if(object->objectName() == "centralWidget"){
201 float smallestRatio;
202 bool hideOnscreenKeys = settings->value("hideOnscreenKeys", false).toBool();
203
204 //update displayContainer first, make the display occupy the top 3/5 of the screen if there are Palm keys or 4/5 if theres not
205 ui->displayContainer->setFixedHeight(ui->centralWidget->height() * (hideOnscreenKeys ? 0.80 : 0.60));
206
207 smallestRatio = qMin(ui->displayContainer->size().width() * 0.98 / 3.0 , ui->displayContainer->size().height() * 0.98 / 4.0);
208 //the 0.98 above allows the display to shrink, without it the displayContainer couldnt shrink because of the fixed size of the display
209
210 //set new size
211 ui->display->setFixedSize(smallestRatio * 3.0, smallestRatio * 4.0);
212
213 //scale framebuffer to new size and refresh
214 if(emu.isInited())
215 ui->display->repaint();
216 }
217 }
218
219 return QMainWindow::eventFilter(object, event);
220 }
221
222 //display
updateDisplay()223 void MainWindow::updateDisplay(){
224 if(emu.newFrameReady()){
225 //video
226 ui->display->repaint();
227
228 //audio
229 audioOut->write((const char*)emu.getAudioSamples(), AUDIO_SAMPLES_PER_FRAME * 2/*channels*/ * sizeof(int16_t));
230
231 //power LED
232 ui->powerButtonLed->setStyleSheet(emu.getPowerButtonLed() ? "background: lime" : "");
233
234 //allow next frame to start
235 emu.frameHandled();
236 }
237 }
238
239 //Palm buttons
on_power_pressed()240 void MainWindow::on_power_pressed(){
241 emu.setKeyValue(EmuWrapper::BUTTON_POWER, true);
242 }
243
on_power_released()244 void MainWindow::on_power_released(){
245 emu.setKeyValue(EmuWrapper::BUTTON_POWER, false);
246 }
247
on_calendar_pressed()248 void MainWindow::on_calendar_pressed(){
249 emu.setKeyValue(EmuWrapper::BUTTON_CALENDAR, true);
250 }
251
on_calendar_released()252 void MainWindow::on_calendar_released(){
253 emu.setKeyValue(EmuWrapper::BUTTON_CALENDAR, false);
254 }
255
on_addressBook_pressed()256 void MainWindow::on_addressBook_pressed(){
257 emu.setKeyValue(EmuWrapper::BUTTON_ADDRESS, true);
258 }
259
on_addressBook_released()260 void MainWindow::on_addressBook_released(){
261 emu.setKeyValue(EmuWrapper::BUTTON_ADDRESS, false);
262 }
263
on_todo_pressed()264 void MainWindow::on_todo_pressed(){
265 emu.setKeyValue(EmuWrapper::BUTTON_TODO, true);
266 }
267
on_todo_released()268 void MainWindow::on_todo_released(){
269 emu.setKeyValue(EmuWrapper::BUTTON_TODO, false);
270 }
271
on_voiceMemo_pressed()272 void MainWindow::on_voiceMemo_pressed(){
273 emu.setKeyValue(EmuWrapper::BUTTON_VOICE_MEMO, false);
274 }
275
on_voiceMemo_released()276 void MainWindow::on_voiceMemo_released(){
277 emu.setKeyValue(EmuWrapper::BUTTON_VOICE_MEMO, false);
278 }
279
on_notes_pressed()280 void MainWindow::on_notes_pressed(){
281 emu.setKeyValue(EmuWrapper::BUTTON_NOTES, true);
282 }
283
on_notes_released()284 void MainWindow::on_notes_released(){
285 emu.setKeyValue(EmuWrapper::BUTTON_NOTES, false);
286 }
287
on_up_pressed()288 void MainWindow::on_up_pressed(){
289 emu.setKeyValue(EmuWrapper::BUTTON_UP, true);
290 }
291
on_up_released()292 void MainWindow::on_up_released(){
293 emu.setKeyValue(EmuWrapper::BUTTON_UP, false);
294 }
295
on_down_pressed()296 void MainWindow::on_down_pressed(){
297 emu.setKeyValue(EmuWrapper::BUTTON_DOWN, true);
298 }
299
on_down_released()300 void MainWindow::on_down_released(){
301 emu.setKeyValue(EmuWrapper::BUTTON_DOWN, false);
302 }
303
on_left_pressed()304 void MainWindow::on_left_pressed(){
305 emu.setKeyValue(EmuWrapper::BUTTON_LEFT, true);
306 }
307
on_left_released()308 void MainWindow::on_left_released(){
309 emu.setKeyValue(EmuWrapper::BUTTON_LEFT, false);
310 }
311
on_right_pressed()312 void MainWindow::on_right_pressed(){
313 emu.setKeyValue(EmuWrapper::BUTTON_RIGHT, true);
314 }
315
on_right_released()316 void MainWindow::on_right_released(){
317 emu.setKeyValue(EmuWrapper::BUTTON_RIGHT, false);
318 }
319
on_center_pressed()320 void MainWindow::on_center_pressed(){
321 emu.setKeyValue(EmuWrapper::BUTTON_CENTER, true);
322 }
323
on_center_released()324 void MainWindow::on_center_released(){
325 emu.setKeyValue(EmuWrapper::BUTTON_CENTER, false);
326 }
327
328 //emu control
on_ctrlBtn_clicked()329 void MainWindow::on_ctrlBtn_clicked(){
330 if(!emu.isInited()){
331 QString sysDir = settings->value("resourceDirectory", "").toString();
332 uint32_t error = emu.init(sysDir, settings->value("palmOsVersionString", "Palm m515/Palm OS 4.1").toString(), settings->value("featureSyncedRtc", false).toBool(), settings->value("featureDurable", false).toBool(), settings->value("fastBoot", false).toBool());
333
334 if(error == EMU_ERROR_NONE){
335 emu.setCpuSpeed(settings->value("cpuSpeed", 1.00).toDouble());
336
337 ui->up->setEnabled(true);
338 ui->down->setEnabled(true);
339
340 if(emu.isTungstenT3()){
341 ui->left->setEnabled(true);
342 ui->right->setEnabled(true);
343 ui->center->setEnabled(true);
344 }
345
346 ui->calendar->setEnabled(true);
347 ui->addressBook->setEnabled(true);
348 ui->todo->setEnabled(true);
349 ui->notes->setEnabled(true);
350
351 if(emu.isTungstenT3())
352 ui->voiceMemo->setEnabled(true);
353
354 ui->power->setEnabled(true);
355
356 ui->screenshot->setEnabled(true);
357 ui->install->setEnabled(true);
358 ui->stateManager->setEnabled(true);
359 ui->debugger->setEnabled(true);
360 ui->reset->setEnabled(true);
361 ui->bootApp->setEnabled(true);
362
363 ui->ctrlBtn->setIcon(QIcon(":/buttons/images/pause.svg"));
364 }
365 else{
366 popupErrorDialog("Emu error:" + QString::number(error) + ", cant run!");
367 }
368 }
369 else if(emu.isRunning()){
370 emu.pause();
371 ui->ctrlBtn->setIcon(QIcon(":/buttons/images/play.svg"));
372 }
373 else if(emu.isPaused()){
374 emu.resume();
375 ui->ctrlBtn->setIcon(QIcon(":/buttons/images/pause.svg"));
376 }
377 }
378
on_install_clicked()379 void MainWindow::on_install_clicked(){
380 if(emu.isInited()){
381 QString app;
382 bool loadedNewApp = false;
383 bool wasPaused = emu.isPaused();
384
385 if(!wasPaused)
386 emu.pause();
387
388 app = QFileDialog::getOpenFileName(this, "Select File", QDir::root().path(), "Palm OS File (*.prc *.pdb *.pqa)");
389 if(app != ""){
390 uint32_t error = emu.installApplication(app);
391
392 if(error == EMU_ERROR_NONE)
393 loadedNewApp = true;
394 else
395 popupErrorDialog("Could not install app, Error:" + QString::number(error));
396 }
397
398 if(!wasPaused || loadedNewApp)
399 emu.resume();
400 }
401 }
402
on_debugger_clicked()403 void MainWindow::on_debugger_clicked(){
404 if(emu.isInited()){
405 emu.pause();
406 ui->ctrlBtn->setIcon(QIcon(":/buttons/images/play.svg"));
407 emuDebugger->exec();
408 }
409 }
410
on_screenshot_clicked()411 void MainWindow::on_screenshot_clicked(){
412 if(emu.isInited()){
413 qlonglong screenshotNumber = settings->value("screenshotNum", 0).toLongLong();
414 QString screenshotPath = settings->value("resourceDirectory", "").toString() + "/screenshots/screenshot" + QString::number(screenshotNumber, 10) + ".png";
415
416 emu.getFramebufferImage().save(screenshotPath, "PNG", 100);
417 screenshotNumber++;
418 settings->setValue("screenshotNum", screenshotNumber);
419 }
420 }
421
on_stateManager_clicked()422 void MainWindow::on_stateManager_clicked(){
423 if(emu.isInited()){
424 bool wasPaused = emu.isPaused();
425
426 if(!wasPaused)
427 emu.pause();
428
429 stateManager->exec();
430
431 if(!wasPaused)
432 emu.resume();
433 }
434 }
435
on_reset_clicked()436 void MainWindow::on_reset_clicked(){
437 emu.reset(false);
438 }
439
on_settings_clicked()440 void MainWindow::on_settings_clicked(){
441 bool wasInited = emu.isInited();
442 bool wasPaused = emu.isPaused();
443
444 if(wasInited && !wasPaused)
445 emu.pause();
446
447 settingsManager->exec();
448
449 //redraw the main window
450 redraw();
451
452 //update keyboard settings too
453 for(uint8_t index = 0; index < EmuWrapper::BUTTON_TOTAL_COUNT; index++)
454 keyForButton[index] = settings->value("palmButton" + QString::number(index) + "Key", '\0').toInt();
455
456 if(wasInited && !wasPaused)
457 emu.resume();
458 }
459
on_bootApp_clicked()460 void MainWindow::on_bootApp_clicked(){
461 if(emu.isInited()){
462 QString app;
463 bool loadedNewApp = false;
464 bool wasPaused = emu.isPaused();
465
466 if(!wasPaused)
467 emu.pause();
468
469 app = QFileDialog::getOpenFileName(this, "Select File", QDir::root().path(), "Palm OS File (*.prc *.pqa *.img)");
470 if(app != ""){
471 uint32_t error = emu.bootFromFile(app);
472
473 if(error == EMU_ERROR_NONE)
474 loadedNewApp = true;
475 else
476 popupErrorDialog("Could not boot app, Error:" + QString::number(error));
477 }
478
479 if(!wasPaused || loadedNewApp)
480 emu.resume();
481 }
482 }
483