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