2 // mainwin.cpp: Implementation of the MainWin class
4 // Part of the WOZ Maker project
6 // (C) 2018 Underground Software
11 #include <stdlib.h> // For rand()
12 #include <time.h> // For time()
13 #include "diskwidget.h"
16 #include "infowidget.h"
17 #include "mainwidget.h"
18 #include "navwidget.h"
19 #include "nibblewidget.h"
20 #include "settingsdialog.h"
21 #include "waveformwidget.h"
26 mainWidget = new MainWidget(this);
27 setCentralWidget(mainWidget);
29 setWindowTitle("WOZ Maker");
30 setWindowIcon(QIcon(":/woz-icon.png"));
32 loadFile = CreateAction(tr("&Open"), tr("Open A2R"), tr("Open an A2R file"), QIcon(), QKeySequence(tr("ctrl+o")));
33 connect(loadFile, SIGNAL(triggered()), this, SLOT(HandleLoadFile()));
35 saveFile = CreateAction(tr("&Save"), tr("Save WOZ"), tr("Save a WOZ file"), QIcon(), QKeySequence(tr("ctrl+s")));
36 connect(saveFile, SIGNAL(triggered()), this, SLOT(HandleSaveFile()));
38 settings = CreateAction(tr("&Disk Settings"), tr("Disk Settings"), tr("Settings for the loaded A2R file"), QIcon(), QKeySequence(tr("ctrl+d")));
39 connect(settings, SIGNAL(triggered()), this, SLOT(HandleSettings()));
41 undoAction = CreateAction(tr("&Undo"), tr(""), tr(""), QIcon(), QKeySequence(tr("ctrl+z")));
42 // connect(undoAction, SIGNAL(triggered()), this, SLOT(HandleUndo()));
44 resetLevel = CreateAction(tr("&Reset Level"), tr("Reset Level"), tr("Start the current level over"), QIcon(), QKeySequence(tr("ctrl+r")));
45 // connect(resetLevel, SIGNAL(triggered()), this, SLOT(HandleReset()));
47 skipLevel = CreateAction(tr("&Skip Level"), tr("Skip Level"), tr("Skip the current level"), QIcon(), QKeySequence(tr("ctrl+k")));
48 // connect(skipLevel, SIGNAL(triggered()), this, SLOT(HandleSkipLevel()));
50 gameOptions = CreateAction(tr("&Options..."), tr("Options"), tr("Configure Warehouse Man Deluxe's options"), QIcon(), QKeySequence(tr("ctrl+o")));
51 // connect(gameOptions, SIGNAL(triggered()), this, SLOT(OnGameOptions()));
53 gameStats = CreateAction(tr("&Stats..."), tr("Game Stats"), tr("Show game stats"), QIcon(), QKeySequence(tr("ctrl+s")));
54 // connect(gameStats, SIGNAL(triggered()), this, SLOT(OnGameStats()));
56 appExit = CreateAction(tr("E&xit"), tr("Exit"), tr("Quit playing Warehouse Man Deluxe..."), QIcon(), QKeySequence(tr("ctrl+q")));
57 connect(appExit, SIGNAL(triggered()), this, SLOT(close()));
59 appAbout = CreateAction(tr("&About"), tr("About"), tr("Display program information..."), QIcon(), QKeySequence(tr("")));
60 connect(appAbout, SIGNAL(triggered()), this, SLOT(AboutWozMaker()));
62 // Get signals from the gameboard to update scores...
63 // connect(gameBoard, SIGNAL(UpdateScore(int)), this, SLOT(OnUpdateScore(int)));
64 // connect(gameWidget, SIGNAL(GameWasWon()), this, SLOT(WeHaveAWinner()));
66 // Connect editor to game widget...
67 // connect(editorWindow, SIGNAL(SetupLevel(Level *)), gameWidget,
68 // SLOT(HandlePlayGameFromEditor(Level *)));
71 QMenu * menu = menuBar()->addMenu(tr("&File"));
72 menu->addAction(loadFile);
73 menu->addAction(saveFile);
74 menu->addAction(settings);
76 // menu->addAction(undoAction);
77 // menu->addAction(resetLevel);
78 // menu->addAction(skipLevel);
79 // menu->addSeparator();
80 // menu->addAction(gameOptions);
81 // menu->addAction(gameStats);
82 // menu->addSeparator();
83 menu->addAction(appExit);
85 menu = menuBar()->addMenu(tr("&Help"));
86 menu->addAction(appAbout);
88 // statusBar()->addPermanentWidget(gameBoard->score);
89 statusBar()->showMessage(tr("Ready"));
91 // Create dock widgets
92 QDockWidget * dw1 = new QDockWidget("A2R Info", this);
93 infoWidget = new InfoWidget;
94 dw1->setWidget(infoWidget);
95 addDockWidget(Qt::RightDockWidgetArea, dw1);
97 QDockWidget * dw2 = new QDockWidget("Disk Nav", this);
98 navWidget = new NavWidget;
99 dw2->setWidget(navWidget);
100 addDockWidget(Qt::RightDockWidgetArea, dw2);
102 connect(navWidget, SIGNAL(UpdateWaveform()), mainWidget->wfWidget, SLOT(HandleUpdate()));
103 connect(navWidget, SIGNAL(InitSync()), mainWidget->wfWidget, SLOT(HandleInitSync()));
104 connect(navWidget, SIGNAL(StepBack()), mainWidget->wfWidget, SLOT(HandleStepBack()));
105 connect(navWidget, SIGNAL(ManualSync()), mainWidget->wfWidget, SLOT(HandleManualSync()));
106 connect(navWidget, SIGNAL(ZeroAll()), mainWidget->wfWidget, SLOT(HandleZeroAll()));
107 connect(navWidget, SIGNAL(Synthesize()), mainWidget->wfWidget, SLOT(HandleSynthesize()));
108 connect(navWidget, SIGNAL(Sync(int, int)), mainWidget->wfWidget, SLOT(HandleSync(int, int)));
109 connect(navWidget, SIGNAL(WaveSync(int)), mainWidget->wfWidget, SLOT(HandleWaveSync(int)));
110 connect(navWidget, SIGNAL(Ratio(int, int)), mainWidget->wfWidget, SLOT(HandleRatio(int, int)));
111 connect(navWidget, SIGNAL(Synthesize2()), mainWidget->wfWidget, SLOT(HandleSynthesize2()));
113 connect(mainWidget->diskWidget, SIGNAL(UpdateWaveform()), mainWidget->wfWidget, SLOT(HandleUpdate()));
114 connect(mainWidget->diskWidget, SIGNAL(UpdateInfo()), navWidget, SLOT(ShowInfo()));
116 connect(mainWidget->wfWidget, SIGNAL(UpdateInfo()), navWidget, SLOT(ShowInfo()));
118 QSettings settings("Underground Software", "WOZ Maker");
119 QPoint mainWinPosition = settings.value("pos", QPoint(200, 100)).toPoint();
120 move(mainWinPosition);
121 QSize mainWinSize = settings.value("size", QSize(500, 500)).toSize();
123 lastDir = settings.value("lastDir", "").toString();
135 // Consolidates action creation from a multi-step process to a single-step one.
137 QAction * MainWin::CreateAction(QString name, QString tooltip, QString statustip,
138 QIcon icon, QKeySequence key, bool checkable/*= false*/)
140 QAction * action = new QAction(icon, name, this);
141 action->setToolTip(tooltip);
142 action->setStatusTip(statustip);
143 action->setShortcut(key);
144 action->setCheckable(checkable);
151 // This is essentially the same as the previous function, but this allows more
152 // than one key sequence to be added as key shortcuts.
154 QAction * MainWin::CreateAction(QString name, QString tooltip, QString statustip,
155 QIcon icon, QKeySequence key1, QKeySequence key2, bool checkable/*= false*/)
157 QAction * action = new QAction(icon, name, this);
158 action->setToolTip(tooltip);
159 action->setStatusTip(statustip);
160 QList<QKeySequence> keyList;
161 keyList.append(key1);
162 keyList.append(key2);
163 action->setShortcuts(keyList);
164 action->setCheckable(checkable);
170 void MainWin::closeEvent(QCloseEvent * event)
172 QSettings settings("Underground Software", "WOZ Maker");
173 settings.setValue("pos", pos());
174 settings.setValue("size", size());
175 settings.setValue("lastDir", lastDir);
181 void MainWin::HandleLoadFile(void)
183 QString filename = QFileDialog::getOpenFileName(this, tr("Open A2R"), lastDir, tr("A2R Disk Image Files (*.a2r)"));
185 if (filename.isEmpty())
188 lastDir = QFileInfo(filename).path();
190 if (!LoadA2R(filename.toUtf8().data()))
192 QMessageBox::critical(this, "Bad A2R file", "It may have an .a2r extension, but it isn't a valid A2R file.");
196 Global::trackNum = 0;
197 mainWidget->wfWidget->HandleUpdate();
198 mainWidget->nibbleWidget->Update();
199 mainWidget->diskWidget->DrawDisk();
200 infoWidget->ShowInfo();
201 navWidget->ShowInfo();
202 setWindowTitle(QString("WOZ Maker - %1").arg(QFileInfo(filename).baseName()));
206 void MainWin::HandleSaveFile(void)
211 void MainWin::HandleSettings(void)
213 SettingsDialog dlg(this);
215 if (dlg.exec() == false)
218 // Deal with stuff here, since user hit "OK" button
222 void MainWin::AboutWozMaker(void)
224 QMessageBox::about(this, tr("About WOZ Maker"), tr("WOZ Maker v1.0.0\n\nWritten by James Hammons\n\nCopyright (C) 2018 Underground Software"));
229 bool MainWin::LoadA2R(const QString filename)
232 Really, this crap should go into fileio.cpp. !!! FIX !!!
234 static uint8_t a2rHdr[8] = { 0x41, 0x32, 0x52, 0x32, 0xFF, 0x0A, 0x0D, 0x0A };
235 static uint8_t a2rHdr1[8] = { 0x41, 0x32, 0x52, 0x31, 0xFF, 0x0A, 0x0D, 0x0A };
237 if (Global::a2r != NULL)
240 Global::a2r = (A2R *)ReadFile(filename.toUtf8().data(), &Global::a2rSize);
242 if (Global::a2r == NULL)
245 // Sanity check, to see if what we asked for is what we got
246 if (memcmp(Global::a2r->magic1, a2rHdr, 8) != 0)
248 // It's not a v2 A2R file, maybe it's v1?
249 if (memcmp(Global::a2r->magic1, a2rHdr1, 8) == 0)
251 // Bytes 8-F are usually: 01 00 8D 00 A5 01 00 00
252 // First, allocate mem to copy the old version to the new:
253 uint8_t * oldfile = (uint8_t *)Global::a2r;
254 uint8_t * newfile = (uint8_t *)malloc(Global::a2rSize + 36);
255 memcpy(newfile + 52, oldfile + 16, Global::a2rSize - 16);
257 // Make sure creator is set correctly (v1 doesn't have it)
258 memset(Global::a2r->creator, 0x20, 32);
260 // Swap in the new file, free the old one
262 Global::a2r = (A2R *)newfile;
263 Global::a2rSize += 36; // Add in the size of the INFO chunk
265 // Fix up stuff that's different between v1 and v2
266 //printf("A2R Size: %08X\n", Global::a2rSize);
267 //printf("A2R Stream Size: %08X (preswap)\n", Global::a2r->strmSize);
268 SwapBytes32((uint8_t *)&Global::a2r->strmSize);
269 //printf("A2R Stream Size: %08X (postswap)\n", Global::a2r->strmSize);
270 uint32_t dataSize = Uint32LE(Global::a2r->strmSize);
273 while (pos < dataSize)
275 A2RStream * stream = (A2RStream *)(&Global::a2r->data[pos]);
277 if (stream->location == 0xFF)
280 SwapBytes32(stream->dataLength);
281 SwapBytes32(stream->estLoopPoint);
282 pos += 10 + Uint32LE(stream->dataLength);
285 // Change INFO to META & fix size (if any, dunno if it's optional)
286 if (Global::a2rSize > (60 + dataSize))
288 memcpy(&Global::a2r->data[dataSize], "META", 4);
289 SwapBytes32(&Global::a2r->data[dataSize + 4]);
297 QMessageBox::critical(this, "Bad A2R file", "It may have an .a2r extension, but it isn't a valid A2R file.");
303 // Setup individual stream pointers
304 Global::numStreams = 0;
305 uint8_t * streamPtr = Global::a2r->data;
307 while ((*streamPtr != 0xFF) && (Global::numStreams < 800))
309 Global::stream[Global::numStreams] = (A2RStream *)streamPtr;
310 streamPtr += 10 + Uint32LE(Global::stream[Global::numStreams]->dataLength);
311 Global::numStreams++;
315 Global::metadata = NULL;
317 if (Global::a2rSize > (60 + Uint32LE(Global::a2r->strmSize)))
319 Global::metadata = (A2RMetadata *)((uint8_t *)Global::a2r + (60 + Uint32LE(Global::a2r->strmSize)));
321 // Make sure it's plausible metadata
322 if (memcmp(Global::metadata->metaTag, "META", 4) != 0)
323 Global::metadata = NULL;
326 // Unpack TMNG & XTMG streams to simplify analysis
327 for(uint32_t i=0; i<Global::numStreams; i++)
329 Global::waveLen[i] = 0;
331 // Don't bother with BITS captures
332 if (Global::stream[i]->captureType == 2)
335 // We skip the first timing byte because it can't tell you what the actual time was when the pulse was seen--it's the time between when the *capture* started and the pulse was seen, not the time between the previous pulse and this one! So that first one is discarded since it's worse than useless; it's misleading.
336 for(uint32_t j=1; j<Uint32LE(Global::stream[i]->dataLength); j++)
338 uint32_t a = Global::stream[i]->data[j];
340 while ((Global::stream[i]->data[j] == 0xFF) || (a < 24))
343 a += Global::stream[i]->data[j];
346 Global::wave[i][Global::waveLen[i]] = a;
347 Global::waveLen[i]++;
351 //ISO-8061 date format
354 time_t t = time(NULL);
355 tm * tmp = gmtime(&t);
356 strftime(buf, sizeof(buf), "%FT%TZ", tmp);
357 printf("Time is: %s\n", buf);