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)
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))
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 title->setText(QString("<h2>%1</h2>").arg(prettyFilename));
396 //Kludge for now, we'll have to fix this later...
397 // So let's fix it now!
398 QString fileTypeString, crcString, notes, compatibility;
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);
408 fileTypeString = QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize);
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))
416 && ((fileType == JST_ALPINE) || ((fileType == JST_ROM) && haveUniversalHeader))))
418 if (haveUniversalHeader)
419 fileTypeString = QString(tr("%1MB Alpine ROM w/Universal Header"));
421 fileTypeString = QString(tr("%1MB Alpine ROM"));
423 fileTypeString = fileTypeString.arg(fileSize / 1048576);
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);
430 fileTypeString = QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize);
433 // crcString = QString("%1").arg(romList[i].crc32, 8, 16, QChar('0')).toUpper();
434 crcString = QString("%1").arg(crc, 8, 16, QChar('0')).toUpper();
436 if (!haveUnknown && (romList[i].flags & FF_NON_WORKING))
437 compatibility = "DOES NOT WORK";
439 compatibility = "Unknown";
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>";
445 // if (haveUniversalHeader)
446 // notes += " Universal Header detected";
448 if (!haveUnknown && (romList[i].flags & FF_REQ_BIOS))
449 notes += " Requires BIOS";
451 if (!haveUnknown && (romList[i].flags & FF_REQ_DSP))
452 notes += " Requires DSP";
454 if (!haveUnknown && (romList[i].flags & FF_VERIFIED))
455 notes += " <i>(Verified)</i>";
457 data->setText(QString("%1<br>%2<br>%3<br>%4")
458 .arg(fileTypeString).arg(crcString).arg(compatibility).arg(notes));
462 Super Duper Awesome Guy (World)
466 Compatibility: DOES NOT WORK
467 Notes: Universal Header detected; Requires DSP
470 Stupid Homebrew Game That Sux
472 Type: ABS/COF Executable (43853 bytes)
474 Compatibility: Unknown
475 Notes: $4000 Load, $4000 Run
478 Action Hopscotch Plus (Prototype)
482 Compatibility: 80% (or ****)
483 Notes: EEPROM available