]> Shamusworld >> Repos - virtualjaguar/blob - src/gui/filepicker.cpp
More enhancements to the file chooser.
[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                 hide();
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))
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         title->setText(QString("<h2>%1</h2>").arg(prettyFilename));
395
396 //Kludge for now, we'll have to fix this later...
397 // So let's fix it now!
398         QString fileTypeString, crcString, notes, compatibility;
399
400 #if 0
401         if (!haveUnknown)
402         {
403                 if (romList[i].flags & FF_ROM)
404                         fileTypeString = QString(tr("%1MB Cartridge")).arg(fileSize / 1048576);
405                 else if (romList[i].flags & FF_ALPINE)
406                         fileTypeString = QString(tr("%1MB Alpine ROM")).arg(fileSize / 1048576);
407                 else
408                         fileTypeString = QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize);
409         }
410 #else
411         if ((!haveUnknown && (romList[i].flags & FF_ROM))
412                 || (haveUnknown && (fileType == JST_ROM) && !haveUniversalHeader))
413                 fileTypeString = QString(tr("%1MB Cartridge")).arg(fileSize / 1048576);
414         else if ((!haveUnknown && (romList[i].flags & FF_ALPINE))
415                 || (haveUnknown
416                                 && ((fileType == JST_ALPINE) || ((fileType == JST_ROM) && haveUniversalHeader))))
417         {
418                 if (haveUniversalHeader)
419                         fileTypeString = QString(tr("%1MB Alpine ROM w/Universal Header"));
420                 else
421                         fileTypeString = QString(tr("%1MB Alpine ROM"));
422
423                 fileTypeString = fileTypeString.arg(fileSize / 1048576);
424         }
425         else if (haveUnknown && (fileType == JST_ABS_TYPE1 || fileType == JST_ABS_TYPE2))
426                 fileTypeString = QString(tr("ABS/COF Executable (%1 bytes)")).arg(fileSize);
427         else if (haveUnknown && (fileType == JST_JAGSERVER))
428                 fileTypeString = QString(tr("Jaguar Server Executable (%1 bytes)")).arg(fileSize);
429         else
430                 fileTypeString = QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize);
431 #endif
432
433 //      crcString = QString("%1").arg(romList[i].crc32, 8, 16, QChar('0')).toUpper();
434         crcString = QString("%1").arg(crc, 8, 16, QChar('0')).toUpper();
435
436         if (!haveUnknown && (romList[i].flags & FF_NON_WORKING))
437                 compatibility = "DOES NOT WORK";
438         else
439                 compatibility = "Unknown";
440
441         // This is going to need some formatting love before long...
442         if (!haveUnknown && (romList[i].flags & FF_BAD_DUMP))
443                 notes = "<b>BAD DUMP</b>";
444
445 //      if (haveUniversalHeader)
446 //              notes += " Universal Header detected";
447
448         if (!haveUnknown && (romList[i].flags & FF_REQ_BIOS))
449                 notes += " Requires BIOS";
450
451         if (!haveUnknown && (romList[i].flags & FF_REQ_DSP))
452                 notes += " Requires DSP";
453
454         if (!haveUnknown && (romList[i].flags & FF_VERIFIED))
455                 notes += " <i>(Verified)</i>";
456
457         data->setText(QString("%1<br>%2<br>%3<br>%4")
458                 .arg(fileTypeString).arg(crcString).arg(compatibility).arg(notes));
459 }
460
461 /*
462     Super Duper Awesome Guy (World)
463
464          Type: 4MB Cartridge
465         CRC32: FEDCBA98
466 Compatibility: DOES NOT WORK
467         Notes: Universal Header detected; Requires DSP
468
469
470     Stupid Homebrew Game That Sux
471
472          Type: ABS/COF Executable (43853 bytes)
473         CRC32: 76543210
474 Compatibility: Unknown
475         Notes: $4000 Load, $4000 Run
476
477
478     Action Hopscotch Plus (Prototype)
479
480          Type: 2MB Alpine ROM
481         CRC32: 44889921
482 Compatibility: 80% (or ****)
483         Notes: EEPROM available
484 */