2 // filepicker.cpp - A ROM chooser
5 // (C) 2010 Underground Software
7 // JLH = James Hammons <jlhamm@acm.org>
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
16 #include "filepicker.h"
20 #include "filelistmodel.h"
21 #include "filethread.h"
22 #include "imagedelegate.h"
23 //#include "settings.h"
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.
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.
36 Ideally, the label will go into the archive along with the ROM image, but that's
38 Maybe box art, screenshots will go as well...
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.
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)?
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
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),
60 setWindowTitle(tr("Insert Cartridge..."));
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());
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
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);
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. :-(
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
116 // fileList->setSpacing(4);
117 fileList->setUniformItemSizes(true);
120 // QVBoxLayout * layout = new QVBoxLayout;
121 QHBoxLayout * layout = new QHBoxLayout;
123 layout->addWidget(fileList);
125 // Weird note: This layout has to be added *before* putting anything into it...
126 QVBoxLayout * vLayout = new QVBoxLayout;
127 layout->addLayout(vLayout);
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"));
134 cartImage->setPixmap(QPixmap::fromImage(cartImg));
135 cartImage->setMargin(4);
136 vLayout->addWidget(cartImage);
138 title = new QLabel(QString(tr("<h2>...</h2>")));
140 title->setAlignment(Qt::AlignCenter);
142 //title->setFixedWidth(cartImage->width());
143 //title->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
145 title->setFixedWidth(cartImage->sizeHint().width());
146 vLayout->addWidget(title);
148 QHBoxLayout * dataLayout = new QHBoxLayout;
149 vLayout->addLayout(dataLayout);
151 QLabel * labels = new QLabel(QString(tr(
154 "<b>Compatibility: </b><br>"
157 labels->setAlignment(Qt::AlignRight);
158 labels->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
159 dataLayout->addWidget(labels);
160 data = new QLabel(QString(tr(
166 data->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
167 dataLayout->addWidget(data);
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);
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)));
185 // Let's defer this to the main window, so we can have some control over when this is done.
188 New sizes: 373x172 (label), 420x340 (cart)
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 &)));
195 connect(insertCart, SIGNAL(clicked()), this, SLOT(LoadButtonPressed()));
197 connect(fileList, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(CatchDoubleClick(const QModelIndex &)));
199 // connect(fileList, SIGNAL(doubleClicked()), this, SLOT(LoadButtonPressed()));
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());
206 void FilePickerWindow::keyPressEvent(QKeyEvent * e)
208 if (e->key() == Qt::Key_Escape)
211 emit(FilePickerHiding());
213 else if (e->key() == Qt::Key_Return)
217 void FilePickerWindow::CatchDoubleClick(const QModelIndex &)
222 QString FilePickerWindow::GetSelectedPrettyName(void)
224 return prettyFilename;
227 void FilePickerWindow::ScanSoftwareFolder(bool allow/*= false*/)
229 // "allow" is whether or not to allow scanning for unknown software.
231 fileThread->Go(allow);
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.
238 void FilePickerWindow::AddFileToList(unsigned long index)
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);
246 void FilePickerWindow::AddFileToList2(unsigned long index, QString str, QImage * img, unsigned long size)
248 if (index != 0xFFFFFFFF)
249 printf("FilePickerWindow(2): Found match [%s]...\n", romList[index].name);
253 model->AddData(index, str, *img, size);
254 //It would be better to pass the pointer into the model though...
258 model->AddData(index, str, QImage(), size);
261 void FilePickerWindow::AddFileToList3(unsigned long index, QString str, QImage * img, unsigned long size, bool haveUniversalHeader, unsigned long fileType, unsigned long crc)
263 //if (index != 0xFFFFFFFF)
264 // printf("FilePickerWindow(3): Found match [%s]...\n", romList[index].name);
268 model->AddData(index, str, *img, size, haveUniversalHeader, fileType, crc);
269 //It would be better to pass the pointer into the model though...
273 model->AddData(index, str, QImage(), size, haveUniversalHeader, fileType, crc);
276 void FilePickerWindow::LoadButtonPressed(void)
278 // TODO: Get the text of the current selection, call the MainWin slot for loading
279 emit(RequestLoad(currentFile));
284 // This slot gets called when the QListView gets clicked on. Updates
285 // the cart graphic and accompanying text.
287 void FilePickerWindow::UpdateSelection(const QModelIndex & current, const QModelIndex &/*previous*/)
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();
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);
308 // Disallow loading completely unknown files, but allow all others.
309 insertCart->setEnabled(haveUnknown && (fileType == JST_NONE) ? false : true);
313 //373x172 is label size...
318 QImage cartImg(":/res/cart-blank.png");
319 QPainter painter(&cartImg);
320 painter.drawPixmap(23, 87, QPixmap(":/res/label-blank.png"));
322 cartSmall = cartImg.scaled(488/4, 395/4, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
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));
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")));
339 cartImage->setPixmap(QPixmap::fromImage(cart));
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.
348 // We now have to sources of data for the passed in files:
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
354 // The DB takes precedence over the fileType.
355 if ((!haveUnknown && (romList[i].flags & FF_ROM))
356 || (haveUnknown && (fileType == JST_ROM) && !haveUniversalHeader))
358 cart = QImage(":/res/cart-blank.png");
359 QPainter painter(&cart);
360 painter.drawPixmap(27, 89, QPixmap::fromImage(QImage(":/res/label-blank.png")));
363 else if ((!haveUnknown && (romList[i].flags & FF_ALPINE))
365 && ((fileType == JST_ALPINE) || ((fileType == JST_ROM) && haveUniversalHeader))))
367 if (haveUniversalHeader)
368 cart = QImage(":/res/skunkboard-file.png");
370 cart = QImage(":/res/alpine-file.png");
372 else if (haveUnknown && (fileType == JST_ABS_TYPE1 || fileType == JST_ABS_TYPE2
373 || fileType == JST_JAGSERVER) || fileType == JST_WTFOMGBBQ)
375 cart = QImage(":/res/homebrew-file.png");
378 cart = QImage(":/res/unknown-file.png");
380 cartImage->setPixmap(QPixmap::fromImage(cart));
387 prettyFilename = romList[i].name;
390 int lastSlashPos = currentFile.lastIndexOf('/');
391 prettyFilename = "\"" + currentFile.mid(lastSlashPos + 1) + "\"";
394 // Ensure that the title isn't longer than the width of the dialog...
396 title->setText(QString("<h2>%1</h2>").arg(prettyFilename));
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);
404 //Kludge for now, we'll have to fix this later...
405 // So let's fix it now!
406 QString fileTypeString, crcString, notes, compatibility;
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);
416 fileTypeString = QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize);
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))
424 && ((fileType == JST_ALPINE) || ((fileType == JST_ROM) && haveUniversalHeader))))
426 if (haveUniversalHeader)
427 fileTypeString = QString(tr("%1MB Alpine ROM w/Universal Header"));
429 fileTypeString = QString(tr("%1MB Alpine ROM"));
431 fileTypeString = fileTypeString.arg((fileSize + 8192) / 1048576);
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);
438 fileTypeString = QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize);
441 // crcString = QString("%1").arg(romList[i].crc32, 8, 16, QChar('0')).toUpper();
442 crcString = QString("%1").arg(crc, 8, 16, QChar('0')).toUpper();
444 if (!haveUnknown && (romList[i].flags & FF_NON_WORKING))
445 compatibility = "DOES NOT WORK";
447 compatibility = "Unknown";
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>";
453 // if (haveUniversalHeader)
454 // notes += " Universal Header detected";
456 if (!haveUnknown && (romList[i].flags & FF_REQ_BIOS))
457 notes += " Requires BIOS";
459 if (!haveUnknown && (romList[i].flags & FF_REQ_DSP))
460 notes += " Requires DSP";
462 if (!haveUnknown && (romList[i].flags & FF_VERIFIED))
463 notes += " <i>(Verified)</i>";
465 data->setText(QString("%1<br>%2<br>%3<br>%4")
466 .arg(fileTypeString).arg(crcString).arg(compatibility).arg(notes));
470 Super Duper Awesome Guy (World)
474 Compatibility: DOES NOT WORK
475 Notes: Universal Header detected; Requires DSP
478 Stupid Homebrew Game That Sux
480 Type: ABS/COF Executable (43853 bytes)
482 Compatibility: Unknown
483 Notes: $4000 Load, $4000 Run
486 Action Hopscotch Plus (Prototype)
490 Compatibility: 80% (or ****)
491 Notes: EEPROM available