2 // filepicker.cpp - A ROM chooser
5 // (C) 2010 Underground Software
7 // JLH = James Hammons <jlhamm@acm.org>
8 // JPM = Jean-Paul Mari <djipi.mari@gmail.com>
11 // --- ---------- -------------------------------------------------------------
12 // JLH 01/22/2010 Created this file
13 // JLH 02/06/2010 Modified to use Qt model/view framework
14 // JLH 03/08/2010 Added large cart view and info text
15 // JPM 06/16/2016 ELF format support
18 #include "filepicker.h"
22 #include "filelistmodel.h"
23 #include "filethread.h"
24 #include "imagedelegate.h"
29 Our strategy here is like so:
30 Look at the files in the directory pointed to by ROMPath.
31 For each file in the directory, take the CRC32 of it and compare it to the CRC
32 in the romList[]. If there's a match, put it in a list and note it's index value
33 in romList for future reference.
35 When constructing the list, use the index to pull up an image of the cart and
36 put that in the list. User picks from a graphical image of the cart.
38 Ideally, the label will go into the archive along with the ROM image, but that's
40 Maybe box art, screenshots will go as well...
42 I'm thinking compatibility info should be displayed as well... Just to stop the
43 inevitable stupid questions from people too lazy to do basic research for themselves.
48 - Should keep a QImage of the blank cart with blank label
49 - Should keep a QImage of the blank cart? (For overpainting the ROMs label)
50 - Should we have a special Alpine image? Floppy image (for COF/ABS)?
52 - Need some way of keeping track of cart size and compatibility info
53 [compat info needs to be BAD DUMP or % of what works]
54 - Need to properly scale the thumbnails images in the list
57 //could use Window as well...
58 //FilePickerWindow::FilePickerWindow(QWidget * parent/*= 0*/): QWidget(parent, Qt::Dialog)
59 FilePickerWindow::FilePickerWindow(QWidget
* parent
/*= 0*/): QWidget(parent
, Qt::Window
),
62 if (!vjs
.softTypeDebugger
)
64 setWindowTitle(tr("Insert Cartridge..."));
68 setWindowTitle(tr("Load executable file..."));
71 //is there any reason why this must be cast as a QAbstractListModel? No
72 //Also, need to think about data structure for the model...
73 model
= new FileListModel
;
74 fileList
= new QListView
;
75 fileList
->setModel(model
);
76 // fileList->setItemDelegate(new ImageDelegate(this));
77 fileList
->setItemDelegate(new ImageDelegate());
80 // fileList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
81 // fileList->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
82 //small problem with this is that it doesn't take scrollbar into account...
83 QSize sbSize
= fileList
->verticalScrollBar()->minimumSizeHint();
84 printf("VSB minimumSizeHint: %u, %u\n", sbSize
.width(), sbSize
.height());
85 QSize sbSize2
= fileList
->verticalScrollBar()->sizeHint();
86 printf("VSB sizeHint: %u, %u\n", sbSize2
.width(), sbSize2
.height());
87 QRect sbRect
= fileList
->verticalScrollBar()->normalGeometry();
88 printf("VSB normalGeometry: %u, %u\n", sbRect
.width(), sbRect
.height());
89 QSize sbSize3
= fileList
->verticalScrollBar()->size();
90 printf("VSB size: %u, %u\n", sbSize3
.width(), sbSize3
.height());
91 // int sbWidth = fileList->verticalScrollBar()->width();
92 int sbWidth
= fileList
->verticalScrollBar()->size().width();
93 fileList
->setFixedWidth((488/4) + 4 + sbWidth
);//ick
95 // This sets it to the "too large size" as the minimum!
96 QScrollBar
* vsb
= new QScrollBar(Qt::Vertical
, this);
97 // int sbWidth = vsb->size().width();
98 // printf("VSB size width: %u\n", sbWidth);
99 int sbWidth2
= vsb
->sizeHint().width();
100 // printf("VSB sizeHint width: %u\n", sbWidth2);
101 // int sbWidth3 = vsb->minimumSize().width();
102 // printf("VSB minimum width: %u\n", sbWidth3);
103 // int sbWidth4 = vsb->frameSize().width();
104 // printf("VSB frame width: %u\n", sbWidth4);
107 // fileList->setFixedWidth((488/4) + 4);
108 int sbWidth5
= fileList
->frameWidth();
109 // printf("List frame width: %u, (diff=%d)\n", sbWidth5, sbWidth5 - ((488/4) + 4));
110 // int sbWidth6 = fileList->sizeHint().width();
111 // printf("List sizeHint width: %u\n", sbWidth6);
112 // int sbWidth7 = fileList->minimumSize().width();
113 // printf("List minimum width: %u\n", sbWidth7);
114 // int sbWidth8 = fileList->minimumSizeHint().width();
115 // printf("List minimum hint width: %u\n", sbWidth8);
116 //// fileList->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
117 //// fileList->verticalScrollBar()->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
118 // (488/4) + 4 is the width of the object in the filelistmodel. Dunno why the QListView
119 // isn't picking that up. :-(
121 // 126 + 17 + 4 = 147 <-- correct width
122 fileList
->setFixedWidth((488/4) + 4 + sbWidth2
+ sbWidth5
+ 1);//ick
123 // fileList->setFixedWidth((488/4) + 4 + 17 + 4);//sbWidth);//ick
125 // fileList->setSpacing(4);
126 fileList
->setUniformItemSizes(true);
129 // QVBoxLayout * layout = new QVBoxLayout;
130 QHBoxLayout
* layout
= new QHBoxLayout
;
132 layout
->addWidget(fileList
);
134 // Weird note: This layout has to be added *before* putting anything into it...
135 QVBoxLayout
* vLayout
= new QVBoxLayout
;
136 layout
->addLayout(vLayout
);
138 cartImage
= new QLabel
;
139 QImage
cartImg(":/res/cart-blank.png");
140 QPainter
painter(&cartImg
);
141 painter
.drawPixmap(23, 87, QPixmap(":/res/label-blank.png"));
143 cartImage
->setPixmap(QPixmap::fromImage(cartImg
));
144 cartImage
->setMargin(4);
145 vLayout
->addWidget(cartImage
);
147 title
= new QLabel(QString(tr("<h2>...</h2>")));
149 title
->setAlignment(Qt::AlignCenter
);
151 //title->setFixedWidth(cartImage->width());
152 //title->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
154 title
->setFixedWidth(cartImage
->sizeHint().width());
155 vLayout
->addWidget(title
);
157 QHBoxLayout
* dataLayout
= new QHBoxLayout
;
158 vLayout
->addLayout(dataLayout
);
160 QLabel
* labels
= new QLabel(QString(tr(
163 "<b>Compatibility: </b><br>"
166 labels
->setAlignment(Qt::AlignRight
);
167 labels
->setSizePolicy(QSizePolicy::Minimum
, QSizePolicy::Preferred
);
168 dataLayout
->addWidget(labels
);
169 data
= new QLabel(QString(tr(
175 data
->setSizePolicy(QSizePolicy::Expanding
, QSizePolicy::Preferred
);
176 dataLayout
->addWidget(data
);
178 //#warning "!!! Icon size for pushbutton is tiny !!!"
179 insertCart
= new QPushButton(this);
180 insertCart
->setIconSize(QSize(40, 40));
181 insertCart
->setIcon(QIcon(":/res/insert.png"));
182 insertCart
->setDefault(true); // We want this button to be the default
183 insertCart
->setSizePolicy(QSizePolicy::Minimum
, QSizePolicy::Preferred
);
184 dataLayout
->addWidget(insertCart
);
186 fileThread
= new FileThread(this);
187 // connect(fileThread, SIGNAL(FoundAFile(unsigned long)), this, SLOT(AddFileToList(unsigned long)));
188 // connect(fileThread, SIGNAL(FoundAFile2(unsigned long, QString, QImage *, unsigned long)), this, SLOT(AddFileToList2(unsigned long, QString, QImage *, unsigned long)));
189 connect(fileThread
, SIGNAL(FoundAFile3(unsigned long, QString
, QImage
*,
190 unsigned long, bool, unsigned long, unsigned long)), this,
191 SLOT(AddFileToList3(unsigned long, QString
, QImage
*, unsigned long,
192 bool, unsigned long, unsigned long)));
194 // Let's defer this to the main window, so we can have some control over when this is done.
197 New sizes: 373x172 (label), 420x340 (cart)
200 // QItemSelectionModel * ism = fileList->selectionModel();
201 // connect(ism, SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(UpdateSelection(const QModelIndex &, const QModelIndex &)));
202 connect(fileList
->selectionModel(), SIGNAL(currentChanged(const QModelIndex
&, const QModelIndex
&)), this, SLOT(UpdateSelection(const QModelIndex
&, const QModelIndex
&)));
204 connect(insertCart
, SIGNAL(clicked()), this, SLOT(LoadButtonPressed()));
206 connect(fileList
, SIGNAL(doubleClicked(const QModelIndex
&)), this, SLOT(CatchDoubleClick(const QModelIndex
&)));
208 // connect(fileList, SIGNAL(doubleClicked()), this, SLOT(LoadButtonPressed()));
210 // Object::connect: No such signal QListView::QAbstractItemView::doubleClicked() in src/gui/filepicker.cpp:203
211 //can't do this, nothing's rendered yet...
212 //setFixedWidth(width());
215 void FilePickerWindow::keyPressEvent(QKeyEvent
* e
)
217 if (e
->key() == Qt::Key_Escape
)
220 emit(FilePickerHiding());
222 else if (e
->key() == Qt::Key_Return
)
226 void FilePickerWindow::CatchDoubleClick(const QModelIndex
&)
231 QString
FilePickerWindow::GetSelectedPrettyName(void)
233 return prettyFilename
;
236 void FilePickerWindow::ScanSoftwareFolder(bool allow
/*= false*/)
238 // "allow" is whether or not to allow scanning for unknown software.
240 fileThread
->Go(allow
);
244 // This slot gets called by the FileThread's run() function when it finds a
245 // match in the filesystem to a ROM on our CRC list.
247 void FilePickerWindow::AddFileToList(unsigned long index
)
249 printf("FilePickerWindow: Found match [%s]...\n", romList
[index
].name
);
250 // NOTE: The model *ignores* what you send it, so this is crap. !!! FIX !!! [DONE, somewhat]
251 // model->AddData(QIcon(":/res/generic.png"));
252 // model->AddData(index);
255 void FilePickerWindow::AddFileToList2(unsigned long index
, QString str
, QImage
* img
, unsigned long size
)
257 if (index
!= 0xFFFFFFFF)
258 printf("FilePickerWindow(2): Found match [%s]...\n", romList
[index
].name
);
262 model
->AddData(index
, str
, *img
, size
);
263 //It would be better to pass the pointer into the model though...
267 model
->AddData(index
, str
, QImage(), size
);
270 void FilePickerWindow::AddFileToList3(unsigned long index
, QString str
, QImage
* img
, unsigned long size
, bool haveUniversalHeader
, unsigned long fileType
, unsigned long crc
)
272 //if (index != 0xFFFFFFFF)
273 // printf("FilePickerWindow(3): Found match [%s]...\n", romList[index].name);
277 model
->AddData(index
, str
, *img
, size
, haveUniversalHeader
, fileType
, crc
);
278 //It would be better to pass the pointer into the model though...
282 model
->AddData(index
, str
, QImage(), size
, haveUniversalHeader
, fileType
, crc
);
285 void FilePickerWindow::LoadButtonPressed(void)
287 // TODO: Get the text of the current selection, call the MainWin slot for loading
288 emit(RequestLoad(currentFile
));
293 // This slot gets called when the QListView gets clicked on. Updates
294 // the cart graphic and accompanying text.
296 void FilePickerWindow::UpdateSelection(const QModelIndex
& current
, const QModelIndex
&/*previous*/)
299 QString s
= current
.model()->data(current
, Qt::EditRole
).toString();
300 unsigned long i
= current
.model()->data(current
, Qt::DisplayRole
).toUInt();
301 QImage label
= current
.model()->data(current
, Qt::DecorationRole
).value
<QImage
>();
302 // printf("FPW: %s\n", s.toAscii().data());
303 unsigned long fileSize
= current
.model()->data(current
, Qt::WhatsThisRole
).toUInt();
305 // QString s = current.model()->data(current, FLM_FILENAME).toString();
306 currentFile
= current
.model()->data(current
, FLM_FILENAME
).toString();
307 unsigned long i
= current
.model()->data(current
, FLM_INDEX
).toUInt();
308 QImage label
= current
.model()->data(current
, FLM_LABEL
).value
<QImage
>();
309 unsigned long fileSize
= current
.model()->data(current
, FLM_FILESIZE
).toUInt();
310 bool haveUniversalHeader
= current
.model()->data(current
, FLM_UNIVERSALHDR
).toBool();
311 unsigned long fileType
= current
.model()->data(current
, FLM_FILETYPE
).toUInt();
312 uint32_t crc
= (uint32_t)current
.model()->data(current
, FLM_CRC
).toUInt();
313 // printf("FPW: %s\n", s.toAscii().data());
314 bool haveUnknown
= (i
== 0xFFFFFFFF ? true : false);
317 // Disallow loading completely unknown files, but allow all others.
318 insertCart
->setEnabled(haveUnknown
&& (fileType
== JST_NONE
) ? false : true);
322 //373x172 is label size...
327 QImage cartImg(":/res/cart-blank.png");
328 QPainter painter(&cartImg);
329 painter.drawPixmap(23, 87, QPixmap(":/res/label-blank.png"));
331 cartSmall = cartImg.scaled(488/4, 395/4, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
333 QImage
cart(":/res/cart-blank.png");
334 QPainter
painter(&cart
);
335 //Though this should probably be done when this is loaded, instead of every time here...
336 //QImage scaledImg = label.scaled(373, 172, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
337 //painter.drawPixmap(23, 87, QPixmap::fromImage(scaledImg));
338 // Now, looks like it is...
339 // painter.drawPixmap(23, 87, QPixmap::fromImage(label));
340 painter
.drawPixmap(27, 89, QPixmap::fromImage(label
));
341 // painter.drawPixmap(23, 87, 373, 172, QPixmap::fromImage(label));
343 // Well, heck. This should be done to the label *before* we get here.
344 painter
.drawPixmap(27, 89, QPixmap::fromImage(QImage(":/res/upper-left.png")));
345 painter
.drawPixmap(27+355, 89, QPixmap::fromImage(QImage(":/res/upper-right.png")));
348 cartImage
->setPixmap(QPixmap::fromImage(cart
));
352 // We should try to be intelligent with our updates here, and only redraw when
353 // we're going from a selection with a label to a selection without. Now, we
354 // redraw regardless.
357 // We now have to sources of data for the passed in files:
359 // - The file type detection
360 // This means we have to be mindful of what's passed back by that stuff.
361 // We can assume that if it wasn't found in the DB, then the fileType
363 // The DB takes precedence over the fileType.
364 if ((!haveUnknown
&& (romList
[i
].flags
& FF_ROM
))
365 || (haveUnknown
&& (fileType
== JST_ROM
) && !haveUniversalHeader
))
367 cart
= QImage(":/res/cart-blank.png");
368 QPainter
painter(&cart
);
369 painter
.drawPixmap(27, 89, QPixmap::fromImage(QImage(":/res/label-blank.png")));
372 else if ((!haveUnknown
&& (romList
[i
].flags
& FF_ALPINE
))
374 && ((fileType
== JST_ALPINE
) || ((fileType
== JST_ROM
) && haveUniversalHeader
))))
376 if (haveUniversalHeader
)
377 cart
= QImage(":/res/skunkboard-file.png");
379 cart
= QImage(":/res/alpine-file.png");
381 else if (haveUnknown
&& (fileType
== JST_ELF32
))
383 cart
= QImage(":/res/ELF-file.png");
385 else if (haveUnknown
&& (fileType
== JST_ABS_TYPE1
|| fileType
== JST_ABS_TYPE2
386 || fileType
== JST_JAGSERVER
) || fileType
== JST_WTFOMGBBQ
)
388 cart
= QImage(":/res/homebrew-file.png");
391 cart
= QImage(":/res/unknown-file.png");
393 cartImage
->setPixmap(QPixmap::fromImage(cart
));
400 prettyFilename
= romList
[i
].name
;
403 int lastSlashPos
= currentFile
.lastIndexOf('/');
404 prettyFilename
= "\"" + currentFile
.mid(lastSlashPos
+ 1) + "\"";
407 // Ensure that the title isn't longer than the width of the dialog...
409 title
->setText(QString("<h2>%1</h2>").arg(prettyFilename
));
411 // This doesn't work...
412 QFontMetrics
metrics(title
->font());
413 QString elidedText
= metrics
.elidedText(QString("<h2>%1</h2>").arg(prettyFilename
), Qt::ElideRight
, title
->sizeHint().width());
414 title
->setText(elidedText
);
417 //Kludge for now, we'll have to fix this later...
418 // So let's fix it now!
419 QString fileTypeString
, crcString
, notes
, compatibility
;
424 if (romList
[i
].flags
& FF_ROM
)
425 fileTypeString
= QString(tr("%1MB Cartridge")).arg(fileSize
/ 1048576);
426 else if (romList
[i
].flags
& FF_ALPINE
)
427 fileTypeString
= QString(tr("%1MB Alpine ROM")).arg(fileSize
/ 1048576);
429 fileTypeString
= QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize
);
432 if ((!haveUnknown
&& (romList
[i
].flags
& FF_ROM
))
433 || (haveUnknown
&& (fileType
== JST_ROM
) && !haveUniversalHeader
))
434 fileTypeString
= QString(tr("%1MB Cartridge")).arg(fileSize
/ 1048576);
435 else if ((!haveUnknown
&& (romList
[i
].flags
& FF_ALPINE
))
437 && ((fileType
== JST_ALPINE
) || ((fileType
== JST_ROM
) && haveUniversalHeader
))))
439 if (haveUniversalHeader
)
440 fileTypeString
= QString(tr("%1MB Alpine ROM w/Universal Header"));
442 fileTypeString
= QString(tr("%1MB Alpine ROM"));
444 fileTypeString
= fileTypeString
.arg((fileSize
+ 8192) / 1048576);
446 else if (haveUnknown
&& (fileType
== JST_ELF32
))
447 fileTypeString
= QString(tr("ELF 32bits Executable (%1 bytes)")).arg(fileSize
);
448 else if (haveUnknown
&& (fileType
== JST_ABS_TYPE1
|| fileType
== JST_ABS_TYPE2
))
449 fileTypeString
= QString(tr("ABS/COF Executable (%1 bytes)")).arg(fileSize
);
450 else if (haveUnknown
&& (fileType
== JST_JAGSERVER
))
451 fileTypeString
= QString(tr("Jaguar Server Executable (%1 bytes)")).arg(fileSize
);
453 fileTypeString
= QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize
);
456 // crcString = QString("%1").arg(romList[i].crc32, 8, 16, QChar('0')).toUpper();
457 crcString
= QString("%1").arg(crc
, 8, 16, QChar('0')).toUpper();
459 if (!haveUnknown
&& (romList
[i
].flags
& FF_NON_WORKING
))
460 compatibility
= "DOES NOT WORK";
462 compatibility
= "Unknown";
464 // This is going to need some formatting love before long...
465 if (!haveUnknown
&& (romList
[i
].flags
& FF_BAD_DUMP
))
466 notes
= "<b>BAD DUMP</b>";
468 // if (haveUniversalHeader)
469 // notes += " Universal Header detected";
471 if (!haveUnknown
&& (romList
[i
].flags
& FF_REQ_BIOS
))
472 notes
+= " Requires BIOS";
474 if (!haveUnknown
&& (romList
[i
].flags
& FF_REQ_DSP
))
475 notes
+= " Requires DSP";
477 if (!haveUnknown
&& (romList
[i
].flags
& FF_VERIFIED
))
478 notes
+= " <i>(Verified)</i>";
480 data
->setText(QString("%1<br>%2<br>%3<br>%4")
481 .arg(fileTypeString
).arg(crcString
).arg(compatibility
).arg(notes
));
485 Super Duper Awesome Guy (World)
489 Compatibility: DOES NOT WORK
490 Notes: Universal Header detected; Requires DSP
493 Stupid Homebrew Game That Sux
495 Type: ABS/COF Executable (43853 bytes)
497 Compatibility: Unknown
498 Notes: $4000 Load, $4000 Run
501 Action Hopscotch Plus (Prototype)
505 Compatibility: 80% (or ****)
506 Notes: EEPROM available