]> Shamusworld >> Repos - virtualjaguar/blob - src/gui/filepicker.cpp
2.0.2 release.
[virtualjaguar] / src / gui / filepicker.cpp
1 //
2 // filepicker.cpp - A ROM chooser
3 //
4 // by James L. Hammons
5 // (C) 2010 Underground Software
6 //
7 // JLH = James L. Hammons <jlhamm@acm.org>
8 //
9 // Who  When        What
10 // ---  ----------  -------------------------------------------------------------
11 // JLH  01/22/2010  Created this file
12 // JLH  02/06/2010  Modified to use Qt model/view framework
13 // JLH  03/08/2010  Added large cart view and info text
14 //
15
16 #include "filepicker.h"
17
18 #include "file.h"
19 #include "filedb.h"
20 #include "filelistmodel.h"
21 #include "filethread.h"
22 #include "imagedelegate.h"
23 //#include "settings.h"
24 //#include "types.h"
25
26 /*
27 Our strategy here is like so:
28 Look at the files in the directory pointed to by ROMPath.
29 For each file in the directory, take the CRC32 of it and compare it to the CRC
30 in the romList[]. If there's a match, put it in a list and note it's index value
31 in romList for future reference.
32
33 When constructing the list, use the index to pull up an image of the cart and
34 put that in the list. User picks from a graphical image of the cart.
35
36 Ideally, the label will go into the archive along with the ROM image, but that's
37 for the future...
38 Maybe box art, screenshots will go as well...
39
40 I'm thinking compatibility info should be displayed as well... Just to stop the
41 inevitable stupid questions from people too lazy to do basic research for themselves.
42
43
44 Data strategy:
45
46 - Should keep a QImage of the blank cart with blank label
47 - Should keep a QImage of the blank cart? (For overpainting the ROMs label)
48 - Should we have a special Alpine image? Floppy image (for COF/ABS)?
49
50 - Need some way of keeping track of cart size and compatibility info
51   [compat info needs to be BAD DUMP or % of what works]
52 - Need to properly scale the thumbnails images in the list
53 */
54
55 //could use Window as well...
56 //FilePickerWindow::FilePickerWindow(QWidget * parent/*= 0*/): QWidget(parent, Qt::Dialog)
57 FilePickerWindow::FilePickerWindow(QWidget * parent/*= 0*/): QWidget(parent, Qt::Window),
58         currentFile("")
59 {
60         setWindowTitle(tr("Insert Cartridge..."));
61
62 //is there any reason why this must be cast as a QAbstractListModel? No
63 //Also, need to think about data structure for the model...
64         model = new FileListModel;
65         fileList = new QListView;
66         fileList->setModel(model);
67 //      fileList->setItemDelegate(new ImageDelegate(this));
68         fileList->setItemDelegate(new ImageDelegate());
69 #if 0
70         //nope.
71 //      fileList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
72 //      fileList->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
73 //small problem with this is that it doesn't take scrollbar into account...
74 QSize sbSize = fileList->verticalScrollBar()->minimumSizeHint();
75 printf("VSB minimumSizeHint: %u, %u\n", sbSize.width(), sbSize.height());
76 QSize sbSize2 = fileList->verticalScrollBar()->sizeHint();
77 printf("VSB sizeHint: %u, %u\n", sbSize2.width(), sbSize2.height());
78 QRect sbRect = fileList->verticalScrollBar()->normalGeometry();
79 printf("VSB normalGeometry: %u, %u\n", sbRect.width(), sbRect.height());
80 QSize sbSize3 = fileList->verticalScrollBar()->size();
81 printf("VSB size: %u, %u\n", sbSize3.width(), sbSize3.height());
82 //      int sbWidth = fileList->verticalScrollBar()->width();
83         int sbWidth = fileList->verticalScrollBar()->size().width();
84         fileList->setFixedWidth((488/4) + 4 + sbWidth);//ick
85 #else
86         // This sets it to the "too large size" as the minimum!
87         QScrollBar * vsb = new QScrollBar(Qt::Vertical, this);
88         int sbWidth = vsb->size().width();
89 //      printf("VSB size width: %u\n", sbWidth);
90         int sbWidth2 = vsb->sizeHint().width();
91 //      printf("VSB sizeHint width: %u\n", sbWidth2);
92         int sbWidth3 = vsb->minimumSize().width();
93 //      printf("VSB minimum width: %u\n", sbWidth3);
94         int sbWidth4 = vsb->frameSize().width();
95 //      printf("VSB frame width: %u\n", sbWidth4);
96         delete vsb;
97
98 //      fileList->setFixedWidth((488/4) + 4);
99         int sbWidth5 = fileList->frameWidth();
100 //      printf("List frame width: %u, (diff=%d)\n", sbWidth5, sbWidth5 - ((488/4) + 4));
101         int sbWidth6 = fileList->sizeHint().width();
102 //      printf("List sizeHint width: %u\n", sbWidth6);
103         int sbWidth7 = fileList->minimumSize().width();
104 //      printf("List minimum width: %u\n", sbWidth7);
105         int sbWidth8 = fileList->minimumSizeHint().width();
106 //      printf("List minimum hint width: %u\n", sbWidth8);
107 ////    fileList->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
108 ////    fileList->verticalScrollBar()->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
109         // (488/4) + 4 is the width of the object in the filelistmodel. Dunno why the QListView
110         // isn't picking that up. :-(
111         // 488/4 + 4 = 126
112         // 126 + 17 + 4 = 147 <-- correct width
113         fileList->setFixedWidth((488/4) + 4 + sbWidth2 + sbWidth5 + 1);//ick
114 //      fileList->setFixedWidth((488/4) + 4 + 17 + 4);//sbWidth);//ick
115
116 //      fileList->setSpacing(4);
117         fileList->setUniformItemSizes(true);
118 #endif
119
120 //      QVBoxLayout * layout = new QVBoxLayout;
121         QHBoxLayout * layout = new QHBoxLayout;
122         setLayout(layout);
123         layout->addWidget(fileList);
124
125         // Weird note: This layout has to be added *before* putting anything into it...
126         QVBoxLayout * vLayout = new QVBoxLayout;
127         layout->addLayout(vLayout);
128
129         cartImage = new QLabel;
130         QImage cartImg(":/res/cart-blank.png");
131         QPainter painter(&cartImg);
132         painter.drawPixmap(23, 87, QPixmap(":/res/label-blank.png"));
133         painter.end();
134         cartImage->setPixmap(QPixmap::fromImage(cartImg));
135         cartImage->setMargin(4);
136         vLayout->addWidget(cartImage);
137
138         title = new QLabel(QString(tr("<h2>...</h2>")));
139         title->setMargin(6);
140         title->setAlignment(Qt::AlignCenter);
141         vLayout->addWidget(title);
142
143 #if 1
144         QHBoxLayout * dataLayout = new QHBoxLayout;
145         vLayout->addLayout(dataLayout);
146
147         QLabel * labels = new QLabel(QString(tr(
148                 "<b>Type: </b><br>"
149                 "<b>CRC32: </b><br>"
150                 "<b>Compatibility: </b><br>"
151                 "<b>Notes:</b>"
152         )));
153         labels->setAlignment(Qt::AlignRight);
154         labels->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
155         dataLayout->addWidget(labels);
156         data = new QLabel(QString(tr(
157                 "?MB Cartridge<br>"
158                 "00000000<br>"
159                 "?<br>"
160                 "?"
161         )));
162         data->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
163         dataLayout->addWidget(data);
164
165 //#warning "!!! Icon size for pushbutton is tiny !!!"
166         insertCart = new QPushButton(this);
167         insertCart->setIconSize(QSize(40, 40));
168         insertCart->setIcon(QIcon(":/res/insert.png"));
169         insertCart->setDefault(true);                           // We want this button to be the default
170         insertCart->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
171         dataLayout->addWidget(insertCart);
172 #else
173         QLabel * text2 = new QLabel(QString(tr(
174                 "<table>"
175                 "<tr><td align='right'><b>Type: </b></td><td>4MB Cartridge</td></tr>"
176                 "<tr><td align='right'><b>CRC32: </b></td><td>FEDCBA98</td></tr>"
177                 "<tr><td align='right'><b>Compatibility: </b></td><td>DOES NOT WORK</td></tr>"
178                 "<tr><td align='right'><b>Notes: </b></td><td>Universal Header detected; Requires DSP</td></tr>"
179                 "</table>"
180         )));
181         vLayout->addWidget(text2);
182 #endif
183
184         fileThread = new FileThread(this);
185 //      connect(fileThread, SIGNAL(FoundAFile(unsigned long)), this, SLOT(AddFileToList(unsigned long)));
186 //      connect(fileThread, SIGNAL(FoundAFile2(unsigned long, QString, QImage *, unsigned long)), this, SLOT(AddFileToList2(unsigned long, QString, QImage *, unsigned long)));
187         connect(fileThread, SIGNAL(FoundAFile3(unsigned long, QString, QImage *,
188                 unsigned long, bool, unsigned long, unsigned long)), this,
189                 SLOT(AddFileToList3(unsigned long, QString, QImage *, unsigned long,
190                 bool, unsigned long, unsigned long)));
191
192 // Let's defer this to the main window, so we can have some control over when this is done.
193 //      fileThread->Go();
194 /*
195 New sizes: 373x172 (label), 420x340 (cart)
196 */
197
198 //      QItemSelectionModel * ism = fileList->selectionModel();
199 //      connect(ism, SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(UpdateSelection(const QModelIndex &, const QModelIndex &)));
200         connect(fileList->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(UpdateSelection(const QModelIndex &, const QModelIndex &)));
201
202         connect(insertCart, SIGNAL(clicked()), this, SLOT(LoadButtonPressed()));
203
204         connect(fileList, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(CatchDoubleClick(const QModelIndex &)));
205
206 //      connect(fileList, SIGNAL(doubleClicked()), this, SLOT(LoadButtonPressed()));
207 // This returns:
208 // Object::connect: No such signal QListView::QAbstractItemView::doubleClicked() in src/gui/filepicker.cpp:203
209 }
210
211 void FilePickerWindow::keyPressEvent(QKeyEvent * e)
212 {
213         if (e->key() == Qt::Key_Escape)
214         {
215                 hide();
216                 emit(FilePickerHiding());
217         }
218         else if (e->key() == Qt::Key_Return)
219                 LoadButtonPressed();
220 }
221
222 void FilePickerWindow::CatchDoubleClick(const QModelIndex &)
223 {
224         LoadButtonPressed();
225 }
226
227 QString FilePickerWindow::GetSelectedPrettyName(void)
228 {
229         return prettyFilename;
230 }
231
232 void FilePickerWindow::ScanSoftwareFolder(bool allow/*= false*/)
233 {
234         // "allow" is whether or not to allow scanning for unknown software.
235         model->ClearData();
236         fileThread->Go(allow);
237 }
238
239 //
240 // This slot gets called by the FileThread's run() function when it finds a
241 // match in the filesystem to a ROM on our CRC list.
242 //
243 void FilePickerWindow::AddFileToList(unsigned long index)
244 {
245 printf("FilePickerWindow: Found match [%s]...\n", romList[index].name);
246         // NOTE: The model *ignores* what you send it, so this is crap. !!! FIX !!! [DONE, somewhat]
247 //      model->AddData(QIcon(":/res/generic.png"));
248 //      model->AddData(index);
249 }
250
251 void FilePickerWindow::AddFileToList2(unsigned long index, QString str, QImage * img, unsigned long size)
252 {
253 if (index != 0xFFFFFFFF)
254         printf("FilePickerWindow(2): Found match [%s]...\n", romList[index].name);
255
256         if (img)
257         {
258                 model->AddData(index, str, *img, size);
259 //It would be better to pass the pointer into the model though...
260                 delete img;
261         }
262         else
263                 model->AddData(index, str, QImage(), size);
264 }
265
266 void FilePickerWindow::AddFileToList3(unsigned long index, QString str, QImage * img, unsigned long size, bool haveUniversalHeader, unsigned long fileType, unsigned long crc)
267 {
268 //if (index != 0xFFFFFFFF)
269 //      printf("FilePickerWindow(3): Found match [%s]...\n", romList[index].name);
270
271         if (img)
272         {
273                 model->AddData(index, str, *img, size, haveUniversalHeader, fileType, crc);
274 //It would be better to pass the pointer into the model though...
275                 delete img;
276         }
277         else
278                 model->AddData(index, str, QImage(), size, haveUniversalHeader, fileType, crc);
279 }
280
281 void FilePickerWindow::LoadButtonPressed(void)
282 {
283         // TODO: Get the text of the current selection, call the MainWin slot for loading
284         emit(RequestLoad(currentFile));
285         hide();
286 }
287
288 //
289 // This slot gets called when the QListView gets clicked on. Updates
290 // the cart graphic and accompanying text.
291 //
292 void FilePickerWindow::UpdateSelection(const QModelIndex & current, const QModelIndex &/*previous*/)
293 {
294 #if 0
295         QString s = current.model()->data(current, Qt::EditRole).toString();
296         unsigned long i = current.model()->data(current, Qt::DisplayRole).toUInt();
297         QImage label = current.model()->data(current, Qt::DecorationRole).value<QImage>();
298 //      printf("FPW: %s\n", s.toAscii().data());
299         unsigned long fileSize = current.model()->data(current, Qt::WhatsThisRole).toUInt();
300 #else
301 //      QString s = current.model()->data(current, FLM_FILENAME).toString();
302         currentFile = current.model()->data(current, FLM_FILENAME).toString();
303         unsigned long i = current.model()->data(current, FLM_INDEX).toUInt();
304         QImage label = current.model()->data(current, FLM_LABEL).value<QImage>();
305         unsigned long fileSize = current.model()->data(current, FLM_FILESIZE).toUInt();
306         bool haveUniversalHeader = current.model()->data(current, FLM_UNIVERSALHDR).toBool();
307         unsigned long fileType = current.model()->data(current, FLM_FILETYPE).toUInt();
308         uint32 crc = (uint32)current.model()->data(current, FLM_CRC).toUInt();
309 //      printf("FPW: %s\n", s.toAscii().data());
310         bool haveUnknown = (i == 0xFFFFFFFF ? true : false);
311 #endif
312
313         // Disallow loading completely unknown files, but allow all others.
314         insertCart->setEnabled(haveUnknown && (fileType == JST_NONE) ? false : true);
315 //hm.
316 //currentFile = s;
317
318 //373x172 is label size...
319 //365x168 now...
320         if (!label.isNull())
321         {
322 /*
323         QImage cartImg(":/res/cart-blank.png");
324         QPainter painter(&cartImg);
325         painter.drawPixmap(23, 87, QPixmap(":/res/label-blank.png"));
326         painter.end();
327         cartSmall = cartImg.scaled(488/4, 395/4, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
328 */
329                 QImage cart(":/res/cart-blank.png");
330                 QPainter painter(&cart);
331 //Though this should probably be done when this is loaded, instead of every time here...
332 //QImage scaledImg = label.scaled(373, 172, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
333 //painter.drawPixmap(23, 87, QPixmap::fromImage(scaledImg));
334                 // Now, looks like it is...
335 //              painter.drawPixmap(23, 87, QPixmap::fromImage(label));
336                 painter.drawPixmap(27, 89, QPixmap::fromImage(label));
337 //              painter.drawPixmap(23, 87, 373, 172, QPixmap::fromImage(label));
338
339 // Well, heck. This should be done to the label *before* we get here.
340                 painter.drawPixmap(27, 89, QPixmap::fromImage(QImage(":/res/upper-left.png")));
341                 painter.drawPixmap(27+355, 89, QPixmap::fromImage(QImage(":/res/upper-right.png")));
342
343                 painter.end();
344                 cartImage->setPixmap(QPixmap::fromImage(cart));
345         }
346         else
347         {
348                 // We should try to be intelligent with our updates here, and only redraw when
349                 // we're going from a selection with a label to a selection without. Now, we
350                 // redraw regardless.
351                 QImage cart;
352
353 // We now have to sources of data for the passed in files:
354 // - The file DB
355 // - The file type detection
356 // This means we have to be mindful of what's passed back by that stuff.
357 // We can assume that if it wasn't found in the DB, then the fileType
358 // should be valid.
359 // The DB takes precedence over the fileType.
360                 if ((!haveUnknown && (romList[i].flags & FF_ROM))
361                         || (haveUnknown && (fileType == JST_ROM) && !haveUniversalHeader))
362                 {
363                         cart = QImage(":/res/cart-blank.png");
364                         QPainter painter(&cart);
365                         painter.drawPixmap(27, 89, QPixmap::fromImage(QImage(":/res/label-blank.png")));
366                         painter.end();
367                 }
368                 else if ((!haveUnknown && (romList[i].flags & FF_ALPINE))
369                         || (haveUnknown
370                                 && ((fileType == JST_ALPINE) || ((fileType == JST_ROM) && haveUniversalHeader))))
371                 {
372                         if (haveUniversalHeader)
373                                 cart = QImage(":/res/skunkboard-file.png");
374                         else
375                                 cart = QImage(":/res/alpine-file.png");
376                 }
377                 else if (haveUnknown && (fileType == JST_ABS_TYPE1 || fileType == JST_ABS_TYPE2
378                         || fileType == JST_JAGSERVER))
379                 {
380                         cart = QImage(":/res/homebrew-file.png");
381                 }
382                 else
383                         cart = QImage(":/res/unknown-file.png");
384
385                 cartImage->setPixmap(QPixmap::fromImage(cart));
386         }
387
388 //1048576
389 //2097152
390 //4194304
391         if (!haveUnknown)
392                 prettyFilename = romList[i].name;
393         else
394         {
395                 int lastSlashPos = currentFile.lastIndexOf('/');
396                 prettyFilename = "\"" + currentFile.mid(lastSlashPos + 1) + "\"";
397         }
398
399         title->setText(QString("<h2>%1</h2>").arg(prettyFilename));
400
401 //Kludge for now, we'll have to fix this later...
402 // So let's fix it now!
403         QString fileTypeString, crcString, notes, compatibility;
404
405 #if 0
406         if (!haveUnknown)
407         {
408                 if (romList[i].flags & FF_ROM)
409                         fileTypeString = QString(tr("%1MB Cartridge")).arg(fileSize / 1048576);
410                 else if (romList[i].flags & FF_ALPINE)
411                         fileTypeString = QString(tr("%1MB Alpine ROM")).arg(fileSize / 1048576);
412                 else
413                         fileTypeString = QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize);
414         }
415 #else
416         if ((!haveUnknown && (romList[i].flags & FF_ROM))
417                 || (haveUnknown && (fileType == JST_ROM) && !haveUniversalHeader))
418                 fileTypeString = QString(tr("%1MB Cartridge")).arg(fileSize / 1048576);
419         else if ((!haveUnknown && (romList[i].flags & FF_ALPINE))
420                 || (haveUnknown
421                                 && ((fileType == JST_ALPINE) || ((fileType == JST_ROM) && haveUniversalHeader))))
422         {
423                 if (haveUniversalHeader)
424                         fileTypeString = QString(tr("%1MB Alpine ROM w/Universal Header"));
425                 else
426                         fileTypeString = QString(tr("%1MB Alpine ROM"));
427
428                 fileTypeString = fileTypeString.arg((fileSize + 8192) / 1048576);
429         }
430         else if (haveUnknown && (fileType == JST_ABS_TYPE1 || fileType == JST_ABS_TYPE2))
431                 fileTypeString = QString(tr("ABS/COF Executable (%1 bytes)")).arg(fileSize);
432         else if (haveUnknown && (fileType == JST_JAGSERVER))
433                 fileTypeString = QString(tr("Jaguar Server Executable (%1 bytes)")).arg(fileSize);
434         else
435                 fileTypeString = QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize);
436 #endif
437
438 //      crcString = QString("%1").arg(romList[i].crc32, 8, 16, QChar('0')).toUpper();
439         crcString = QString("%1").arg(crc, 8, 16, QChar('0')).toUpper();
440
441         if (!haveUnknown && (romList[i].flags & FF_NON_WORKING))
442                 compatibility = "DOES NOT WORK";
443         else
444                 compatibility = "Unknown";
445
446         // This is going to need some formatting love before long...
447         if (!haveUnknown && (romList[i].flags & FF_BAD_DUMP))
448                 notes = "<b>BAD DUMP</b>";
449
450 //      if (haveUniversalHeader)
451 //              notes += " Universal Header detected";
452
453         if (!haveUnknown && (romList[i].flags & FF_REQ_BIOS))
454                 notes += " Requires BIOS";
455
456         if (!haveUnknown && (romList[i].flags & FF_REQ_DSP))
457                 notes += " Requires DSP";
458
459         if (!haveUnknown && (romList[i].flags & FF_VERIFIED))
460                 notes += " <i>(Verified)</i>";
461
462         data->setText(QString("%1<br>%2<br>%3<br>%4")
463                 .arg(fileTypeString).arg(crcString).arg(compatibility).arg(notes));
464 }
465
466 /*
467     Super Duper Awesome Guy (World)
468
469          Type: 4MB Cartridge
470         CRC32: FEDCBA98
471 Compatibility: DOES NOT WORK
472         Notes: Universal Header detected; Requires DSP
473
474
475     Stupid Homebrew Game That Sux
476
477          Type: ABS/COF Executable (43853 bytes)
478         CRC32: 76543210
479 Compatibility: Unknown
480         Notes: $4000 Load, $4000 Run
481
482
483     Action Hopscotch Plus (Prototype)
484
485          Type: 2MB Alpine ROM
486         CRC32: 44889921
487 Compatibility: 80% (or ****)
488         Notes: EEPROM available
489 */