2 // filepicker.cpp - A ROM chooser
5 // (C) 2010 Underground Software
7 // JLH = James L. 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. :-(
111 fileList->setFixedWidth((488/4) + 4 + sbWidth);//ick
112 // fileList->setFixedWidth((488/4) + 4 + 17 + 4);//sbWidth);//ick
114 // fileList->setSpacing(4);
115 fileList->setUniformItemSizes(true);
118 // QVBoxLayout * layout = new QVBoxLayout;
119 QHBoxLayout * layout = new QHBoxLayout;
121 layout->addWidget(fileList);
123 // Weird note: This layout has to be added *before* putting anything into it...
124 QVBoxLayout * vLayout = new QVBoxLayout;
125 layout->addLayout(vLayout);
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"));
132 cartImage->setPixmap(QPixmap::fromImage(cartImg));
133 cartImage->setMargin(4);
134 vLayout->addWidget(cartImage);
136 title = new QLabel(QString(tr("<h2>...</h2>")));
138 title->setAlignment(Qt::AlignCenter);
139 vLayout->addWidget(title);
142 QHBoxLayout * dataLayout = new QHBoxLayout;
143 vLayout->addLayout(dataLayout);
145 QLabel * labels = new QLabel(QString(tr(
148 "<b>Compatibility: </b><br>"
151 labels->setAlignment(Qt::AlignRight);
152 labels->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
153 dataLayout->addWidget(labels);
154 data = new QLabel(QString(tr(
160 data->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
161 dataLayout->addWidget(data);
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);
171 QLabel * text2 = new QLabel(QString(tr(
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>"
179 vLayout->addWidget(text2);
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)));
190 // Let's defer this to the main window, so we can have some control over when this is done.
193 New sizes: 373x172 (label), 420x340 (cart)
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 &)));
200 connect(insertCart, SIGNAL(clicked()), this, SLOT(LoadButtonPressed()));
202 connect(fileList, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(CatchDoubleClick(const QModelIndex &)));
204 // connect(fileList, SIGNAL(doubleClicked()), this, SLOT(LoadButtonPressed()));
206 // Object::connect: No such signal QListView::QAbstractItemView::doubleClicked() in src/gui/filepicker.cpp:203
209 void FilePickerWindow::keyPressEvent(QKeyEvent * e)
211 if (e->key() == Qt::Key_Escape)
214 emit(FilePickerHiding());
216 else if (e->key() == Qt::Key_Return)
220 void FilePickerWindow::CatchDoubleClick(const QModelIndex &)
225 QString FilePickerWindow::GetSelectedPrettyName(void)
227 return prettyFilename;
230 void FilePickerWindow::ScanSoftwareFolder(bool allow/*= false*/)
232 // "allow" is whether or not to allow scanning for unknown software.
234 fileThread->Go(allow);
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.
241 void FilePickerWindow::AddFileToList(unsigned long index)
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);
249 void FilePickerWindow::AddFileToList2(unsigned long index, QString str, QImage * img, unsigned long size)
251 if (index != 0xFFFFFFFF)
252 printf("FilePickerWindow(2): Found match [%s]...\n", romList[index].name);
256 model->AddData(index, str, *img, size);
257 //It would be better to pass the pointer into the model though...
261 model->AddData(index, str, QImage(), size);
264 void FilePickerWindow::AddFileToList3(unsigned long index, QString str, QImage * img, unsigned long size, bool haveUniversalHeader, unsigned long fileType, unsigned long crc)
266 //if (index != 0xFFFFFFFF)
267 // printf("FilePickerWindow(3): Found match [%s]...\n", romList[index].name);
271 model->AddData(index, str, *img, size, haveUniversalHeader, fileType, crc);
272 //It would be better to pass the pointer into the model though...
276 model->AddData(index, str, QImage(), size, haveUniversalHeader, fileType, crc);
279 void FilePickerWindow::LoadButtonPressed(void)
281 // TODO: Get the text of the current selection, call the MainWin slot for loading
282 emit(RequestLoad(currentFile));
287 // This slot gets called when the QListView gets clicked on. Updates
288 // the cart graphic and accompanying text.
290 void FilePickerWindow::UpdateSelection(const QModelIndex & current, const QModelIndex &/*previous*/)
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();
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);
311 // Disallow loading completely unknown files, but allow all others.
312 insertCart->setEnabled(haveUnknown && (fileType == JST_NONE) ? false : true);
316 //373x172 is label size...
321 QImage cartImg(":/res/cart-blank.png");
322 QPainter painter(&cartImg);
323 painter.drawPixmap(23, 87, QPixmap(":/res/label-blank.png"));
325 cartSmall = cartImg.scaled(488/4, 395/4, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
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));
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")));
342 cartImage->setPixmap(QPixmap::fromImage(cart));
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.
351 // We now have to sources of data for the passed in files:
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
357 // The DB takes precedence over the fileType.
358 if ((!haveUnknown && (romList[i].flags & FF_ROM))
359 || (haveUnknown && (fileType == JST_ROM) && !haveUniversalHeader))
361 cart = QImage(":/res/cart-blank.png");
362 QPainter painter(&cart);
363 painter.drawPixmap(27, 89, QPixmap::fromImage(QImage(":/res/label-blank.png")));
366 else if ((!haveUnknown && (romList[i].flags & FF_ALPINE))
368 && ((fileType == JST_ALPINE) || ((fileType == JST_ROM) && haveUniversalHeader))))
370 if (haveUniversalHeader)
371 cart = QImage(":/res/skunkboard-file.png");
373 cart = QImage(":/res/alpine-file.png");
375 else if (haveUnknown && (fileType == JST_ABS_TYPE1 || fileType == JST_ABS_TYPE2
376 || fileType == JST_JAGSERVER))
378 cart = QImage(":/res/homebrew-file.png");
381 cart = QImage(":/res/unknown-file.png");
383 cartImage->setPixmap(QPixmap::fromImage(cart));
390 prettyFilename = romList[i].name;
393 int lastSlashPos = currentFile.lastIndexOf('/');
394 prettyFilename = "\"" + currentFile.mid(lastSlashPos + 1) + "\"";
397 title->setText(QString("<h2>%1</h2>").arg(prettyFilename));
399 //Kludge for now, we'll have to fix this later...
400 // So let's fix it now!
401 QString fileTypeString, crcString, notes, compatibility;
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);
411 fileTypeString = QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize);
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))
419 && ((fileType == JST_ALPINE) || ((fileType == JST_ROM) && haveUniversalHeader))))
421 if (haveUniversalHeader)
422 fileTypeString = QString(tr("%1MB Alpine ROM w/Universal Header"));
424 fileTypeString = QString(tr("%1MB Alpine ROM"));
426 fileTypeString = fileTypeString.arg((fileSize + 8192) / 1048576);
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);
433 fileTypeString = QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize);
436 // crcString = QString("%1").arg(romList[i].crc32, 8, 16, QChar('0')).toUpper();
437 crcString = QString("%1").arg(crc, 8, 16, QChar('0')).toUpper();
439 if (!haveUnknown && (romList[i].flags & FF_NON_WORKING))
440 compatibility = "DOES NOT WORK";
442 compatibility = "Unknown";
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>";
448 // if (haveUniversalHeader)
449 // notes += " Universal Header detected";
451 if (!haveUnknown && (romList[i].flags & FF_REQ_BIOS))
452 notes += " Requires BIOS";
454 if (!haveUnknown && (romList[i].flags & FF_REQ_DSP))
455 notes += " Requires DSP";
457 if (!haveUnknown && (romList[i].flags & FF_VERIFIED))
458 notes += " <i>(Verified)</i>";
460 data->setText(QString("%1<br>%2<br>%3<br>%4")
461 .arg(fileTypeString).arg(crcString).arg(compatibility).arg(notes));
465 Super Duper Awesome Guy (World)
469 Compatibility: DOES NOT WORK
470 Notes: Universal Header detected; Requires DSP
473 Stupid Homebrew Game That Sux
475 Type: ABS/COF Executable (43853 bytes)
477 Compatibility: Unknown
478 Notes: $4000 Load, $4000 Run
481 Action Hopscotch Plus (Prototype)
485 Compatibility: 80% (or ****)
486 Notes: EEPROM available