]> Shamusworld >> Repos - virtualjaguar/blob - src/gui/filepicker.cpp
More compliance fixes, artwork changes, build system changes.
[virtualjaguar] / src / gui / filepicker.cpp
1 //
2 // filepicker.cpp - A ROM chooser
3 //
4 // by James Hammons
5 // (C) 2010 Underground Software
6 //
7 // JLH = James 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 //no.
142 //title->setFixedWidth(cartImage->width());
143 //title->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
144 //YESH!!!!
145         title->setFixedWidth(cartImage->sizeHint().width());
146         vLayout->addWidget(title);
147
148         QHBoxLayout * dataLayout = new QHBoxLayout;
149         vLayout->addLayout(dataLayout);
150
151         QLabel * labels = new QLabel(QString(tr(
152                 "<b>Type: </b><br>"
153                 "<b>CRC32: </b><br>"
154                 "<b>Compatibility: </b><br>"
155                 "<b>Notes:</b>"
156         )));
157         labels->setAlignment(Qt::AlignRight);
158         labels->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
159         dataLayout->addWidget(labels);
160         data = new QLabel(QString(tr(
161                 "?MB Cartridge<br>"
162                 "00000000<br>"
163                 "?<br>"
164                 "?"
165         )));
166         data->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
167         dataLayout->addWidget(data);
168
169 //#warning "!!! Icon size for pushbutton is tiny !!!"
170         insertCart = new QPushButton(this);
171         insertCart->setIconSize(QSize(40, 40));
172         insertCart->setIcon(QIcon(":/res/insert.png"));
173         insertCart->setDefault(true);                           // We want this button to be the default
174         insertCart->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
175         dataLayout->addWidget(insertCart);
176
177         fileThread = new FileThread(this);
178 //      connect(fileThread, SIGNAL(FoundAFile(unsigned long)), this, SLOT(AddFileToList(unsigned long)));
179 //      connect(fileThread, SIGNAL(FoundAFile2(unsigned long, QString, QImage *, unsigned long)), this, SLOT(AddFileToList2(unsigned long, QString, QImage *, unsigned long)));
180         connect(fileThread, SIGNAL(FoundAFile3(unsigned long, QString, QImage *,
181                 unsigned long, bool, unsigned long, unsigned long)), this,
182                 SLOT(AddFileToList3(unsigned long, QString, QImage *, unsigned long,
183                 bool, unsigned long, unsigned long)));
184
185 // Let's defer this to the main window, so we can have some control over when this is done.
186 //      fileThread->Go();
187 /*
188 New sizes: 373x172 (label), 420x340 (cart)
189 */
190
191 //      QItemSelectionModel * ism = fileList->selectionModel();
192 //      connect(ism, SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(UpdateSelection(const QModelIndex &, const QModelIndex &)));
193         connect(fileList->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(UpdateSelection(const QModelIndex &, const QModelIndex &)));
194
195         connect(insertCart, SIGNAL(clicked()), this, SLOT(LoadButtonPressed()));
196
197         connect(fileList, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(CatchDoubleClick(const QModelIndex &)));
198
199 //      connect(fileList, SIGNAL(doubleClicked()), this, SLOT(LoadButtonPressed()));
200 // This returns:
201 // Object::connect: No such signal QListView::QAbstractItemView::doubleClicked() in src/gui/filepicker.cpp:203
202 //can't do this, nothing's rendered yet...
203 //setFixedWidth(width());
204 }
205
206 void FilePickerWindow::keyPressEvent(QKeyEvent * e)
207 {
208         if (e->key() == Qt::Key_Escape)
209         {
210                 hide();
211                 emit(FilePickerHiding());
212         }
213         else if (e->key() == Qt::Key_Return)
214                 LoadButtonPressed();
215 }
216
217 void FilePickerWindow::CatchDoubleClick(const QModelIndex &)
218 {
219         LoadButtonPressed();
220 }
221
222 QString FilePickerWindow::GetSelectedPrettyName(void)
223 {
224         return prettyFilename;
225 }
226
227 void FilePickerWindow::ScanSoftwareFolder(bool allow/*= false*/)
228 {
229         // "allow" is whether or not to allow scanning for unknown software.
230         model->ClearData();
231         fileThread->Go(allow);
232 }
233
234 //
235 // This slot gets called by the FileThread's run() function when it finds a
236 // match in the filesystem to a ROM on our CRC list.
237 //
238 void FilePickerWindow::AddFileToList(unsigned long index)
239 {
240 printf("FilePickerWindow: Found match [%s]...\n", romList[index].name);
241         // NOTE: The model *ignores* what you send it, so this is crap. !!! FIX !!! [DONE, somewhat]
242 //      model->AddData(QIcon(":/res/generic.png"));
243 //      model->AddData(index);
244 }
245
246 void FilePickerWindow::AddFileToList2(unsigned long index, QString str, QImage * img, unsigned long size)
247 {
248 if (index != 0xFFFFFFFF)
249         printf("FilePickerWindow(2): Found match [%s]...\n", romList[index].name);
250
251         if (img)
252         {
253                 model->AddData(index, str, *img, size);
254 //It would be better to pass the pointer into the model though...
255                 delete img;
256         }
257         else
258                 model->AddData(index, str, QImage(), size);
259 }
260
261 void FilePickerWindow::AddFileToList3(unsigned long index, QString str, QImage * img, unsigned long size, bool haveUniversalHeader, unsigned long fileType, unsigned long crc)
262 {
263 //if (index != 0xFFFFFFFF)
264 //      printf("FilePickerWindow(3): Found match [%s]...\n", romList[index].name);
265
266         if (img)
267         {
268                 model->AddData(index, str, *img, size, haveUniversalHeader, fileType, crc);
269 //It would be better to pass the pointer into the model though...
270                 delete img;
271         }
272         else
273                 model->AddData(index, str, QImage(), size, haveUniversalHeader, fileType, crc);
274 }
275
276 void FilePickerWindow::LoadButtonPressed(void)
277 {
278         // TODO: Get the text of the current selection, call the MainWin slot for loading
279         emit(RequestLoad(currentFile));
280         hide();
281 }
282
283 //
284 // This slot gets called when the QListView gets clicked on. Updates
285 // the cart graphic and accompanying text.
286 //
287 void FilePickerWindow::UpdateSelection(const QModelIndex & current, const QModelIndex &/*previous*/)
288 {
289 #if 0
290         QString s = current.model()->data(current, Qt::EditRole).toString();
291         unsigned long i = current.model()->data(current, Qt::DisplayRole).toUInt();
292         QImage label = current.model()->data(current, Qt::DecorationRole).value<QImage>();
293 //      printf("FPW: %s\n", s.toAscii().data());
294         unsigned long fileSize = current.model()->data(current, Qt::WhatsThisRole).toUInt();
295 #else
296 //      QString s = current.model()->data(current, FLM_FILENAME).toString();
297         currentFile = current.model()->data(current, FLM_FILENAME).toString();
298         unsigned long i = current.model()->data(current, FLM_INDEX).toUInt();
299         QImage label = current.model()->data(current, FLM_LABEL).value<QImage>();
300         unsigned long fileSize = current.model()->data(current, FLM_FILESIZE).toUInt();
301         bool haveUniversalHeader = current.model()->data(current, FLM_UNIVERSALHDR).toBool();
302         unsigned long fileType = current.model()->data(current, FLM_FILETYPE).toUInt();
303         uint32 crc = (uint32)current.model()->data(current, FLM_CRC).toUInt();
304 //      printf("FPW: %s\n", s.toAscii().data());
305         bool haveUnknown = (i == 0xFFFFFFFF ? true : false);
306 #endif
307
308         // Disallow loading completely unknown files, but allow all others.
309         insertCart->setEnabled(haveUnknown && (fileType == JST_NONE) ? false : true);
310 //hm.
311 //currentFile = s;
312
313 //373x172 is label size...
314 //365x168 now...
315         if (!label.isNull())
316         {
317 /*
318         QImage cartImg(":/res/cart-blank.png");
319         QPainter painter(&cartImg);
320         painter.drawPixmap(23, 87, QPixmap(":/res/label-blank.png"));
321         painter.end();
322         cartSmall = cartImg.scaled(488/4, 395/4, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
323 */
324                 QImage cart(":/res/cart-blank.png");
325                 QPainter painter(&cart);
326 //Though this should probably be done when this is loaded, instead of every time here...
327 //QImage scaledImg = label.scaled(373, 172, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
328 //painter.drawPixmap(23, 87, QPixmap::fromImage(scaledImg));
329                 // Now, looks like it is...
330 //              painter.drawPixmap(23, 87, QPixmap::fromImage(label));
331                 painter.drawPixmap(27, 89, QPixmap::fromImage(label));
332 //              painter.drawPixmap(23, 87, 373, 172, QPixmap::fromImage(label));
333
334 // Well, heck. This should be done to the label *before* we get here.
335                 painter.drawPixmap(27, 89, QPixmap::fromImage(QImage(":/res/upper-left.png")));
336                 painter.drawPixmap(27+355, 89, QPixmap::fromImage(QImage(":/res/upper-right.png")));
337
338                 painter.end();
339                 cartImage->setPixmap(QPixmap::fromImage(cart));
340         }
341         else
342         {
343                 // We should try to be intelligent with our updates here, and only redraw when
344                 // we're going from a selection with a label to a selection without. Now, we
345                 // redraw regardless.
346                 QImage cart;
347
348 // We now have to sources of data for the passed in files:
349 // - The file DB
350 // - The file type detection
351 // This means we have to be mindful of what's passed back by that stuff.
352 // We can assume that if it wasn't found in the DB, then the fileType
353 // should be valid.
354 // The DB takes precedence over the fileType.
355                 if ((!haveUnknown && (romList[i].flags & FF_ROM))
356                         || (haveUnknown && (fileType == JST_ROM) && !haveUniversalHeader))
357                 {
358                         cart = QImage(":/res/cart-blank.png");
359                         QPainter painter(&cart);
360                         painter.drawPixmap(27, 89, QPixmap::fromImage(QImage(":/res/label-blank.png")));
361                         painter.end();
362                 }
363                 else if ((!haveUnknown && (romList[i].flags & FF_ALPINE))
364                         || (haveUnknown
365                                 && ((fileType == JST_ALPINE) || ((fileType == JST_ROM) && haveUniversalHeader))))
366                 {
367                         if (haveUniversalHeader)
368                                 cart = QImage(":/res/skunkboard-file.png");
369                         else
370                                 cart = QImage(":/res/alpine-file.png");
371                 }
372                 else if (haveUnknown && (fileType == JST_ABS_TYPE1 || fileType == JST_ABS_TYPE2
373                         || fileType == JST_JAGSERVER) || fileType == JST_WTFOMGBBQ)
374                 {
375                         cart = QImage(":/res/homebrew-file.png");
376                 }
377                 else
378                         cart = QImage(":/res/unknown-file.png");
379
380                 cartImage->setPixmap(QPixmap::fromImage(cart));
381         }
382
383 //1048576
384 //2097152
385 //4194304
386         if (!haveUnknown)
387                 prettyFilename = romList[i].name;
388         else
389         {
390                 int lastSlashPos = currentFile.lastIndexOf('/');
391                 prettyFilename = "\"" + currentFile.mid(lastSlashPos + 1) + "\"";
392         }
393
394         // Ensure that the title isn't longer than the width of the dialog...
395 #if 1
396         title->setText(QString("<h2>%1</h2>").arg(prettyFilename));
397 #else
398         // This doesn't work...
399         QFontMetrics metrics(title->font());
400         QString elidedText = metrics.elidedText(QString("<h2>%1</h2>").arg(prettyFilename), Qt::ElideRight, title->sizeHint().width());
401         title->setText(elidedText);
402 #endif
403
404 //Kludge for now, we'll have to fix this later...
405 // So let's fix it now!
406         QString fileTypeString, crcString, notes, compatibility;
407
408 #if 0
409         if (!haveUnknown)
410         {
411                 if (romList[i].flags & FF_ROM)
412                         fileTypeString = QString(tr("%1MB Cartridge")).arg(fileSize / 1048576);
413                 else if (romList[i].flags & FF_ALPINE)
414                         fileTypeString = QString(tr("%1MB Alpine ROM")).arg(fileSize / 1048576);
415                 else
416                         fileTypeString = QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize);
417         }
418 #else
419         if ((!haveUnknown && (romList[i].flags & FF_ROM))
420                 || (haveUnknown && (fileType == JST_ROM) && !haveUniversalHeader))
421                 fileTypeString = QString(tr("%1MB Cartridge")).arg(fileSize / 1048576);
422         else if ((!haveUnknown && (romList[i].flags & FF_ALPINE))
423                 || (haveUnknown
424                                 && ((fileType == JST_ALPINE) || ((fileType == JST_ROM) && haveUniversalHeader))))
425         {
426                 if (haveUniversalHeader)
427                         fileTypeString = QString(tr("%1MB Alpine ROM w/Universal Header"));
428                 else
429                         fileTypeString = QString(tr("%1MB Alpine ROM"));
430
431                 fileTypeString = fileTypeString.arg((fileSize + 8192) / 1048576);
432         }
433         else if (haveUnknown && (fileType == JST_ABS_TYPE1 || fileType == JST_ABS_TYPE2))
434                 fileTypeString = QString(tr("ABS/COF Executable (%1 bytes)")).arg(fileSize);
435         else if (haveUnknown && (fileType == JST_JAGSERVER))
436                 fileTypeString = QString(tr("Jaguar Server Executable (%1 bytes)")).arg(fileSize);
437         else
438                 fileTypeString = QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize);
439 #endif
440
441 //      crcString = QString("%1").arg(romList[i].crc32, 8, 16, QChar('0')).toUpper();
442         crcString = QString("%1").arg(crc, 8, 16, QChar('0')).toUpper();
443
444         if (!haveUnknown && (romList[i].flags & FF_NON_WORKING))
445                 compatibility = "DOES NOT WORK";
446         else
447                 compatibility = "Unknown";
448
449         // This is going to need some formatting love before long...
450         if (!haveUnknown && (romList[i].flags & FF_BAD_DUMP))
451                 notes = "<b>BAD DUMP</b>";
452
453 //      if (haveUniversalHeader)
454 //              notes += " Universal Header detected";
455
456         if (!haveUnknown && (romList[i].flags & FF_REQ_BIOS))
457                 notes += " Requires BIOS";
458
459         if (!haveUnknown && (romList[i].flags & FF_REQ_DSP))
460                 notes += " Requires DSP";
461
462         if (!haveUnknown && (romList[i].flags & FF_VERIFIED))
463                 notes += " <i>(Verified)</i>";
464
465         data->setText(QString("%1<br>%2<br>%3<br>%4")
466                 .arg(fileTypeString).arg(crcString).arg(compatibility).arg(notes));
467 }
468
469 /*
470     Super Duper Awesome Guy (World)
471
472          Type: 4MB Cartridge
473         CRC32: FEDCBA98
474 Compatibility: DOES NOT WORK
475         Notes: Universal Header detected; Requires DSP
476
477
478     Stupid Homebrew Game That Sux
479
480          Type: ABS/COF Executable (43853 bytes)
481         CRC32: 76543210
482 Compatibility: Unknown
483         Notes: $4000 Load, $4000 Run
484
485
486     Action Hopscotch Plus (Prototype)
487
488          Type: 2MB Alpine ROM
489         CRC32: 44889921
490 Compatibility: 80% (or ****)
491         Notes: EEPROM available
492 */