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