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);
141 vLayout->addWidget(title);
144 QHBoxLayout * dataLayout = new QHBoxLayout;
145 vLayout->addLayout(dataLayout);
147 QLabel * labels = new QLabel(QString(tr(
150 "<b>Compatibility: </b><br>"
153 labels->setAlignment(Qt::AlignRight);
154 labels->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
155 dataLayout->addWidget(labels);
156 data = new QLabel(QString(tr(
162 data->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
163 dataLayout->addWidget(data);
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);
173 QLabel * text2 = new QLabel(QString(tr(
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>"
181 vLayout->addWidget(text2);
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)));
192 // Let's defer this to the main window, so we can have some control over when this is done.
195 New sizes: 373x172 (label), 420x340 (cart)
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 &)));
202 connect(insertCart, SIGNAL(clicked()), this, SLOT(LoadButtonPressed()));
204 connect(fileList, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(CatchDoubleClick(const QModelIndex &)));
206 // connect(fileList, SIGNAL(doubleClicked()), this, SLOT(LoadButtonPressed()));
208 // Object::connect: No such signal QListView::QAbstractItemView::doubleClicked() in src/gui/filepicker.cpp:203
211 void FilePickerWindow::keyPressEvent(QKeyEvent * e)
213 if (e->key() == Qt::Key_Escape)
216 emit(FilePickerHiding());
218 else if (e->key() == Qt::Key_Return)
222 void FilePickerWindow::CatchDoubleClick(const QModelIndex &)
227 QString FilePickerWindow::GetSelectedPrettyName(void)
229 return prettyFilename;
232 void FilePickerWindow::ScanSoftwareFolder(bool allow/*= false*/)
234 // "allow" is whether or not to allow scanning for unknown software.
236 fileThread->Go(allow);
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.
243 void FilePickerWindow::AddFileToList(unsigned long index)
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);
251 void FilePickerWindow::AddFileToList2(unsigned long index, QString str, QImage * img, unsigned long size)
253 if (index != 0xFFFFFFFF)
254 printf("FilePickerWindow(2): Found match [%s]...\n", romList[index].name);
258 model->AddData(index, str, *img, size);
259 //It would be better to pass the pointer into the model though...
263 model->AddData(index, str, QImage(), size);
266 void FilePickerWindow::AddFileToList3(unsigned long index, QString str, QImage * img, unsigned long size, bool haveUniversalHeader, unsigned long fileType, unsigned long crc)
268 //if (index != 0xFFFFFFFF)
269 // printf("FilePickerWindow(3): Found match [%s]...\n", romList[index].name);
273 model->AddData(index, str, *img, size, haveUniversalHeader, fileType, crc);
274 //It would be better to pass the pointer into the model though...
278 model->AddData(index, str, QImage(), size, haveUniversalHeader, fileType, crc);
281 void FilePickerWindow::LoadButtonPressed(void)
283 // TODO: Get the text of the current selection, call the MainWin slot for loading
284 emit(RequestLoad(currentFile));
289 // This slot gets called when the QListView gets clicked on. Updates
290 // the cart graphic and accompanying text.
292 void FilePickerWindow::UpdateSelection(const QModelIndex & current, const QModelIndex &/*previous*/)
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();
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);
313 // Disallow loading completely unknown files, but allow all others.
314 insertCart->setEnabled(haveUnknown && (fileType == JST_NONE) ? false : true);
318 //373x172 is label size...
323 QImage cartImg(":/res/cart-blank.png");
324 QPainter painter(&cartImg);
325 painter.drawPixmap(23, 87, QPixmap(":/res/label-blank.png"));
327 cartSmall = cartImg.scaled(488/4, 395/4, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
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));
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")));
344 cartImage->setPixmap(QPixmap::fromImage(cart));
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.
353 // We now have to sources of data for the passed in files:
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
359 // The DB takes precedence over the fileType.
360 if ((!haveUnknown && (romList[i].flags & FF_ROM))
361 || (haveUnknown && (fileType == JST_ROM) && !haveUniversalHeader))
363 cart = QImage(":/res/cart-blank.png");
364 QPainter painter(&cart);
365 painter.drawPixmap(27, 89, QPixmap::fromImage(QImage(":/res/label-blank.png")));
368 else if ((!haveUnknown && (romList[i].flags & FF_ALPINE))
370 && ((fileType == JST_ALPINE) || ((fileType == JST_ROM) && haveUniversalHeader))))
372 if (haveUniversalHeader)
373 cart = QImage(":/res/skunkboard-file.png");
375 cart = QImage(":/res/alpine-file.png");
377 else if (haveUnknown && (fileType == JST_ABS_TYPE1 || fileType == JST_ABS_TYPE2
378 || fileType == JST_JAGSERVER))
380 cart = QImage(":/res/homebrew-file.png");
383 cart = QImage(":/res/unknown-file.png");
385 cartImage->setPixmap(QPixmap::fromImage(cart));
392 prettyFilename = romList[i].name;
395 int lastSlashPos = currentFile.lastIndexOf('/');
396 prettyFilename = "\"" + currentFile.mid(lastSlashPos + 1) + "\"";
399 title->setText(QString("<h2>%1</h2>").arg(prettyFilename));
401 //Kludge for now, we'll have to fix this later...
402 // So let's fix it now!
403 QString fileTypeString, crcString, notes, compatibility;
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);
413 fileTypeString = QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize);
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))
421 && ((fileType == JST_ALPINE) || ((fileType == JST_ROM) && haveUniversalHeader))))
423 if (haveUniversalHeader)
424 fileTypeString = QString(tr("%1MB Alpine ROM w/Universal Header"));
426 fileTypeString = QString(tr("%1MB Alpine ROM"));
428 fileTypeString = fileTypeString.arg((fileSize + 8192) / 1048576);
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);
435 fileTypeString = QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize);
438 // crcString = QString("%1").arg(romList[i].crc32, 8, 16, QChar('0')).toUpper();
439 crcString = QString("%1").arg(crc, 8, 16, QChar('0')).toUpper();
441 if (!haveUnknown && (romList[i].flags & FF_NON_WORKING))
442 compatibility = "DOES NOT WORK";
444 compatibility = "Unknown";
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>";
450 // if (haveUniversalHeader)
451 // notes += " Universal Header detected";
453 if (!haveUnknown && (romList[i].flags & FF_REQ_BIOS))
454 notes += " Requires BIOS";
456 if (!haveUnknown && (romList[i].flags & FF_REQ_DSP))
457 notes += " Requires DSP";
459 if (!haveUnknown && (romList[i].flags & FF_VERIFIED))
460 notes += " <i>(Verified)</i>";
462 data->setText(QString("%1<br>%2<br>%3<br>%4")
463 .arg(fileTypeString).arg(crcString).arg(compatibility).arg(notes));
467 Super Duper Awesome Guy (World)
471 Compatibility: DOES NOT WORK
472 Notes: Universal Header detected; Requires DSP
475 Stupid Homebrew Game That Sux
477 Type: ABS/COF Executable (43853 bytes)
479 Compatibility: Unknown
480 Notes: $4000 Load, $4000 Run
483 Action Hopscotch Plus (Prototype)
487 Compatibility: 80% (or ****)
488 Notes: EEPROM available