]> Shamusworld >> Repos - wozmaker/blob - src/mainwin.cpp
e98031dbc0919565b30781f4c70c688ff9b9f25b
[wozmaker] / src / mainwin.cpp
1 //
2 // mainwin.cpp: Implementation of the MainWin class
3 //
4 // Part of the WOZ Maker project
5 // by James Hammons
6 // (C) 2018 Underground Software
7 //
8
9 #include "mainwin.h"
10
11 #include <stdlib.h>                     // For rand()
12 #include <time.h>                       // For time()
13 #include "diskwidget.h"
14 #include "fileio.h"
15 #include "global.h"
16 #include "infowidget.h"
17 #include "mainwidget.h"
18 #include "navwidget.h"
19 #include "nibblewidget.h"
20 #include "waveformwidget.h"
21
22
23 MainWin::MainWin()
24 {
25         mainWidget = new MainWidget(this);
26         setCentralWidget(mainWidget);
27
28         setWindowTitle("WOZ Maker");
29         setWindowIcon(QIcon(":/woz-icon.png"));
30
31         loadFile = CreateAction(tr("&Open"), tr("Open A2R"), tr("Open an A2R file"), QIcon(), QKeySequence(tr("ctrl+o")));
32         connect(loadFile, SIGNAL(triggered()), this, SLOT(HandleLoadFile()));
33
34         newGame = CreateAction(tr("&New"), tr("New Game"), tr("Start a new game of Warehouse Man Deluxe"), QIcon(), QKeySequence(tr("ctrl+n")));
35 //      connect(newGame, SIGNAL(triggered()), this, SLOT(HandleNewGame()));
36
37         undoAction = CreateAction(tr("&Undo"), tr(""), tr(""), QIcon(), QKeySequence(tr("ctrl+z")));
38 //      connect(undoAction, SIGNAL(triggered()), this, SLOT(HandleUndo()));
39
40         resetLevel = CreateAction(tr("&Reset Level"), tr("Reset Level"), tr("Start the current level over"), QIcon(), QKeySequence(tr("ctrl+r")));
41 //      connect(resetLevel, SIGNAL(triggered()), this, SLOT(HandleReset()));
42
43         skipLevel = CreateAction(tr("&Skip Level"), tr("Skip Level"), tr("Skip the current level"), QIcon(), QKeySequence(tr("ctrl+k")));
44 //      connect(skipLevel, SIGNAL(triggered()), this, SLOT(HandleSkipLevel()));
45
46         gameOptions = CreateAction(tr("&Options..."), tr("Options"), tr("Configure Warehouse Man Deluxe's options"), QIcon(), QKeySequence(tr("ctrl+o")));
47 //      connect(gameOptions, SIGNAL(triggered()), this, SLOT(OnGameOptions()));
48
49         gameStats = CreateAction(tr("&Stats..."), tr("Game Stats"), tr("Show game stats"), QIcon(), QKeySequence(tr("ctrl+s")));
50 //      connect(gameStats, SIGNAL(triggered()), this, SLOT(OnGameStats()));
51
52         appExit = CreateAction(tr("E&xit"), tr("Exit"), tr("Quit playing Warehouse Man Deluxe..."), QIcon(), QKeySequence(tr("ctrl+q")));
53         connect(appExit, SIGNAL(triggered()), this, SLOT(close()));
54
55         appAbout = CreateAction(tr("&About"), tr("About"), tr("Display program information..."), QIcon(), QKeySequence(tr("")));
56         connect(appAbout, SIGNAL(triggered()), this, SLOT(AboutWozMaker()));
57
58         // Get signals from the gameboard to update scores...
59 //      connect(gameBoard, SIGNAL(UpdateScore(int)), this, SLOT(OnUpdateScore(int)));
60 //      connect(gameWidget, SIGNAL(GameWasWon()), this, SLOT(WeHaveAWinner()));
61
62         // Connect editor to game widget...
63 //      connect(editorWindow, SIGNAL(SetupLevel(Level *)), gameWidget,
64 //              SLOT(HandlePlayGameFromEditor(Level *)));
65
66
67         QMenu * menu = menuBar()->addMenu(tr("&File"));
68         menu->addAction(loadFile);
69         menu->addSeparator();
70 //      menu->addAction(undoAction);
71 //      menu->addAction(resetLevel);
72 //      menu->addAction(skipLevel);
73 //      menu->addSeparator();
74 //      menu->addAction(gameOptions);
75 //      menu->addAction(gameStats);
76 //      menu->addSeparator();
77         menu->addAction(appExit);
78
79         menu = menuBar()->addMenu(tr("&Help"));
80         menu->addAction(appAbout);
81
82 //      statusBar()->addPermanentWidget(gameBoard->score);
83         statusBar()->showMessage(tr("Ready"));
84
85         // Create dock widgets
86         QDockWidget * dw1 = new QDockWidget("A2R Info", this);
87         infoWidget = new InfoWidget;
88         dw1->setWidget(infoWidget);
89         addDockWidget(Qt::RightDockWidgetArea, dw1);
90
91         QDockWidget * dw2 = new QDockWidget("Disk Nav", this);
92         navWidget = new NavWidget;
93         dw2->setWidget(navWidget);
94         addDockWidget(Qt::RightDockWidgetArea, dw2);
95
96         connect(navWidget, SIGNAL(UpdateWaveform()), mainWidget->wfWidget, SLOT(HandleUpdate()));
97         connect(navWidget, SIGNAL(InitSync()), mainWidget->wfWidget, SLOT(HandleInitSync()));
98         connect(navWidget, SIGNAL(StepBack()), mainWidget->wfWidget, SLOT(HandleStepBack()));
99         connect(navWidget, SIGNAL(ManualSync()), mainWidget->wfWidget, SLOT(HandleManualSync()));
100         connect(navWidget, SIGNAL(ZeroAll()), mainWidget->wfWidget, SLOT(HandleZeroAll()));
101         connect(navWidget, SIGNAL(Synthesize()), mainWidget->wfWidget, SLOT(HandleSynthesize()));
102         connect(navWidget, SIGNAL(Sync(int, int)), mainWidget->wfWidget, SLOT(HandleSync(int, int)));
103         connect(navWidget, SIGNAL(WaveSync(int)), mainWidget->wfWidget, SLOT(HandleWaveSync(int)));
104         connect(navWidget, SIGNAL(Ratio(int, int)), mainWidget->wfWidget, SLOT(HandleRatio(int, int)));
105         connect(navWidget, SIGNAL(Synthesize2()), mainWidget->wfWidget, SLOT(HandleSynthesize2()));
106
107         connect(mainWidget->diskWidget, SIGNAL(UpdateWaveform()), mainWidget->wfWidget, SLOT(HandleUpdate()));
108         connect(mainWidget->diskWidget, SIGNAL(UpdateInfo()), navWidget, SLOT(ShowInfo()));
109
110         connect(mainWidget->wfWidget, SIGNAL(UpdateInfo()), navWidget, SLOT(ShowInfo()));
111
112         QSettings settings("Underground Software", "WOZ Maker");
113         QPoint mainWinPosition = settings.value("pos", QPoint(200, 100)).toPoint();
114         move(mainWinPosition);
115         QSize mainWinSize = settings.value("size", QSize(500, 500)).toSize();
116         resize(mainWinSize);
117         lastDir = settings.value("lastDir", "").toString();
118 }
119
120
121 MainWin::~MainWin()
122 {
123         if (Global::a2r)
124                 free(Global::a2r);
125 }
126
127
128 //
129 // Consolidates action creation from a multi-step process to a single-step one.
130 //
131 QAction * MainWin::CreateAction(QString name, QString tooltip, QString statustip,
132         QIcon icon, QKeySequence key, bool checkable/*= false*/)
133 {
134         QAction * action = new QAction(icon, name, this);
135         action->setToolTip(tooltip);
136         action->setStatusTip(statustip);
137         action->setShortcut(key);
138         action->setCheckable(checkable);
139
140         return action;
141 }
142
143
144 //
145 // This is essentially the same as the previous function, but this allows more
146 // than one key sequence to be added as key shortcuts.
147 //
148 QAction * MainWin::CreateAction(QString name, QString tooltip, QString statustip,
149         QIcon icon, QKeySequence key1, QKeySequence key2, bool checkable/*= false*/)
150 {
151         QAction * action = new QAction(icon, name, this);
152         action->setToolTip(tooltip);
153         action->setStatusTip(statustip);
154         QList<QKeySequence> keyList;
155         keyList.append(key1);
156         keyList.append(key2);
157         action->setShortcuts(keyList);
158         action->setCheckable(checkable);
159
160         return action;
161 }
162
163
164 void MainWin::closeEvent(QCloseEvent * event)
165 {
166         QSettings settings("Underground Software", "WOZ Maker");
167         settings.setValue("pos", pos());
168         settings.setValue("size", size());
169         settings.setValue("lastDir", lastDir);
170
171         event->accept();
172 }
173
174
175 void MainWin::HandleLoadFile(void)
176 {
177         QString filename = QFileDialog::getOpenFileName(this, tr("Open A2R"), lastDir, tr("A2R Disk Image Files (*.a2r)"));
178
179         if (filename.isEmpty())
180                 return;
181
182         lastDir = QFileInfo(filename).path();
183
184         if (!LoadA2R(filename.toUtf8().data()))
185         {
186                 QMessageBox::critical(this, "Bad A2R file", "It may have an .a2r extension, but it isn't a valid A2R file.");
187                 return;
188         }
189
190         Global::trackNum = 0;
191         mainWidget->wfWidget->HandleUpdate();
192         mainWidget->nibbleWidget->Update();
193         mainWidget->diskWidget->DrawDisk();
194         infoWidget->ShowInfo();
195         navWidget->ShowInfo();
196         setWindowTitle(QString("WOZ Maker - %1").arg(QFileInfo(filename).baseName()));
197 }
198
199
200 void MainWin::AboutWozMaker(void)
201 {
202         QMessageBox::about(this, tr("About WOZ Maker"), tr("WOZ Maker v1.0.0\n\nWritten by James Hammons\n\nCopyright (C) 2018 Underground Software"));
203 }
204
205
206 #if 0
207 bool MainWin::LoadA2R(const QString filename)
208 {
209 /*
210 Really, this crap should go into fileio.cpp.  !!! FIX !!!
211 */
212         static uint8_t a2rHdr[8] = { 0x41, 0x32, 0x52, 0x32, 0xFF, 0x0A, 0x0D, 0x0A };
213         static uint8_t a2rHdr1[8] = { 0x41, 0x32, 0x52, 0x31, 0xFF, 0x0A, 0x0D, 0x0A };
214
215         if (Global::a2r != NULL)
216                 free(Global::a2r);
217
218         Global::a2r = (A2R *)ReadFile(filename.toUtf8().data(), &Global::a2rSize);
219
220         if (Global::a2r == NULL)
221                 return false;
222
223         // Sanity check, to see if what we asked for is what we got
224         if (memcmp(Global::a2r->magic1, a2rHdr, 8) != 0)
225         {
226                 // It's not a v2 A2R file, maybe it's v1?
227                 if (memcmp(Global::a2r->magic1, a2rHdr1, 8) == 0)
228                 {
229                         // Bytes 8-F are usually: 01 00 8D 00 A5 01 00 00
230                         // First, allocate mem to copy the old version to the new:
231                         uint8_t * oldfile = (uint8_t *)Global::a2r;
232                         uint8_t * newfile = (uint8_t *)malloc(Global::a2rSize + 36);
233                         memcpy(newfile + 52, oldfile + 16, Global::a2rSize - 16);
234
235                         // Make sure creator is set correctly (v1 doesn't have it)
236                         memset(Global::a2r->creator, 0x20, 32);
237
238                         // Swap in the new file, free the old one
239                         free(Global::a2r);
240                         Global::a2r = (A2R *)newfile;
241                         Global::a2rSize += 36;  // Add in the size of the INFO chunk
242
243                         // Fix up stuff that's different between v1 and v2
244 //printf("A2R Size: %08X\n", Global::a2rSize);
245 //printf("A2R Stream Size: %08X (preswap)\n", Global::a2r->strmSize);
246                         SwapBytes32((uint8_t *)&Global::a2r->strmSize);
247 //printf("A2R Stream Size: %08X (postswap)\n", Global::a2r->strmSize);
248                         uint32_t dataSize = Uint32LE(Global::a2r->strmSize);
249                         uint32_t pos = 0;
250
251                         while (pos < dataSize)
252                         {
253                                 A2RStream * stream = (A2RStream *)(&Global::a2r->data[pos]);
254
255                                 if (stream->location == 0xFF)
256                                         break;
257
258                                 SwapBytes32(stream->dataLength);
259                                 SwapBytes32(stream->estLoopPoint);
260                                 pos += 10 + Uint32LE(stream->dataLength);
261                         }
262
263                         // Change INFO to META & fix size (if any, dunno if it's optional)
264                         if (Global::a2rSize > (60 + dataSize))
265                         {
266                                 memcpy(&Global::a2r->data[dataSize], "META", 4);
267                                 SwapBytes32(&Global::a2r->data[dataSize + 4]);
268                         }
269                 }
270                 else
271                 {
272                         free(Global::a2r);
273                         Global::a2r = NULL;
274
275                         QMessageBox::critical(this, "Bad A2R file", "It may have an .a2r extension, but it isn't a valid A2R file.");
276
277                         return false;
278                 }
279         }
280
281         // Setup individual stream pointers
282         Global::numStreams = 0;
283         uint8_t * streamPtr = Global::a2r->data;
284
285         while ((*streamPtr != 0xFF) && (Global::numStreams < 800))
286         {
287                 Global::stream[Global::numStreams] = (A2RStream *)streamPtr;
288                 streamPtr += 10 + Uint32LE(Global::stream[Global::numStreams]->dataLength);
289                 Global::numStreams++;
290         }
291
292         // Metadata check
293         Global::metadata = NULL;
294
295         if (Global::a2rSize > (60 + Uint32LE(Global::a2r->strmSize)))
296         {
297                 Global::metadata = (A2RMetadata *)((uint8_t *)Global::a2r + (60 + Uint32LE(Global::a2r->strmSize)));
298
299                 // Make sure it's plausible metadata
300                 if (memcmp(Global::metadata->metaTag, "META", 4) != 0)
301                         Global::metadata = NULL;
302         }
303
304         // Unpack TMNG & XTMG streams to simplify analysis
305         for(uint32_t i=0; i<Global::numStreams; i++)
306         {
307                 Global::waveLen[i] = 0;
308
309                 // Don't bother with BITS captures
310                 if (Global::stream[i]->captureType == 2)
311                         continue;
312
313                 // 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.
314                 for(uint32_t j=1; j<Uint32LE(Global::stream[i]->dataLength); j++)
315                 {
316                         uint32_t a = Global::stream[i]->data[j];
317
318                         while ((Global::stream[i]->data[j] == 0xFF) || (a < 24))
319                         {
320                                 j++;
321                                 a += Global::stream[i]->data[j];
322                         }
323
324                         Global::wave[i][Global::waveLen[i]] = a;
325                         Global::waveLen[i]++;
326                 }
327         }
328
329 //ISO-8061 date format
330 #if 0
331         char buf[200];
332         time_t t = time(NULL);
333         tm * tmp = gmtime(&t);
334         strftime(buf, sizeof(buf), "%FT%TZ", tmp);
335         printf("Time is: %s\n", buf);
336 #endif
337
338         return true;
339 }
340 #endif
341