Added screenshot feature
[clinton/Virtual-Jaguar-Rx.git] / src / gui / filepicker.cpp
CommitLineData
cf76e892
JPM
1//
2// filepicker.cpp - A ROM chooser
3//
4// by James Hammons
5// (C) 2010 Underground Software
6//
7// JLH = James Hammons <jlhamm@acm.org>
8// JPM = Jean-Paul Mari <djipi.mari@gmail.com>
9//
10// Who When What
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
16//
17
18#include "filepicker.h"
19
20#include "file.h"
21#include "filedb.h"
22#include "filelistmodel.h"
23#include "filethread.h"
24#include "imagedelegate.h"
25#include "settings.h"
26//#include "types.h"
27
28/*
29Our strategy here is like so:
30Look at the files in the directory pointed to by ROMPath.
31For each file in the directory, take the CRC32 of it and compare it to the CRC
32in the romList[]. If there's a match, put it in a list and note it's index value
33in romList for future reference.
34
35When constructing the list, use the index to pull up an image of the cart and
36put that in the list. User picks from a graphical image of the cart.
37
38Ideally, the label will go into the archive along with the ROM image, but that's
39for the future...
40Maybe box art, screenshots will go as well...
41
42I'm thinking compatibility info should be displayed as well... Just to stop the
43inevitable stupid questions from people too lazy to do basic research for themselves.
44
45
46Data strategy:
47
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)?
51
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
55*/
56
57//could use Window as well...
58//FilePickerWindow::FilePickerWindow(QWidget * parent/*= 0*/): QWidget(parent, Qt::Dialog)
59FilePickerWindow::FilePickerWindow(QWidget * parent/*= 0*/): QWidget(parent, Qt::Window),
60 currentFile("")
61{
62 if (!vjs.softTypeDebugger)
63 {
64 setWindowTitle(tr("Insert Cartridge..."));
65 }
66 else
67 {
68 setWindowTitle(tr("Load executable file..."));
69 }
70
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());
78#if 0
79 //nope.
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...
83QSize sbSize = fileList->verticalScrollBar()->minimumSizeHint();
84printf("VSB minimumSizeHint: %u, %u\n", sbSize.width(), sbSize.height());
85QSize sbSize2 = fileList->verticalScrollBar()->sizeHint();
86printf("VSB sizeHint: %u, %u\n", sbSize2.width(), sbSize2.height());
87QRect sbRect = fileList->verticalScrollBar()->normalGeometry();
88printf("VSB normalGeometry: %u, %u\n", sbRect.width(), sbRect.height());
89QSize sbSize3 = fileList->verticalScrollBar()->size();
90printf("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
94#else
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);
105 delete vsb;
106
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. :-(
120 // 488/4 + 4 = 126
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
124
125// fileList->setSpacing(4);
126 fileList->setUniformItemSizes(true);
127#endif
128
129// QVBoxLayout * layout = new QVBoxLayout;
130 QHBoxLayout * layout = new QHBoxLayout;
131 setLayout(layout);
132 layout->addWidget(fileList);
133
134 // Weird note: This layout has to be added *before* putting anything into it...
135 QVBoxLayout * vLayout = new QVBoxLayout;
136 layout->addLayout(vLayout);
137
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"));
142 painter.end();
143 cartImage->setPixmap(QPixmap::fromImage(cartImg));
144 cartImage->setMargin(4);
145 vLayout->addWidget(cartImage);
146
147 title = new QLabel(QString(tr("<h2>...</h2>")));
148 title->setMargin(6);
149 title->setAlignment(Qt::AlignCenter);
150//no.
151//title->setFixedWidth(cartImage->width());
152//title->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
153//YESH!!!!
154 title->setFixedWidth(cartImage->sizeHint().width());
155 vLayout->addWidget(title);
156
157 QHBoxLayout * dataLayout = new QHBoxLayout;
158 vLayout->addLayout(dataLayout);
159
160 QLabel * labels = new QLabel(QString(tr(
161 "<b>Type: </b><br>"
162 "<b>CRC32: </b><br>"
163 "<b>Compatibility: </b><br>"
164 "<b>Notes:</b>"
165 )));
166 labels->setAlignment(Qt::AlignRight);
167 labels->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
168 dataLayout->addWidget(labels);
169 data = new QLabel(QString(tr(
170 "?MB Cartridge<br>"
171 "00000000<br>"
172 "?<br>"
173 "?"
174 )));
175 data->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
176 dataLayout->addWidget(data);
177
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);
185
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)));
193
194// Let's defer this to the main window, so we can have some control over when this is done.
195// fileThread->Go();
196/*
197New sizes: 373x172 (label), 420x340 (cart)
198*/
199
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 &)));
203
204 connect(insertCart, SIGNAL(clicked()), this, SLOT(LoadButtonPressed()));
205
206 connect(fileList, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(CatchDoubleClick(const QModelIndex &)));
207
208// connect(fileList, SIGNAL(doubleClicked()), this, SLOT(LoadButtonPressed()));
209// This returns:
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());
213}
214
215void FilePickerWindow::keyPressEvent(QKeyEvent * e)
216{
217 if (e->key() == Qt::Key_Escape)
218 {
219 hide();
220 emit(FilePickerHiding());
221 }
222 else if (e->key() == Qt::Key_Return)
223 LoadButtonPressed();
224}
225
226void FilePickerWindow::CatchDoubleClick(const QModelIndex &)
227{
228 LoadButtonPressed();
229}
230
231QString FilePickerWindow::GetSelectedPrettyName(void)
232{
233 return prettyFilename;
234}
235
236void FilePickerWindow::ScanSoftwareFolder(bool allow/*= false*/)
237{
238 // "allow" is whether or not to allow scanning for unknown software.
239 model->ClearData();
240 fileThread->Go(allow);
241}
242
243//
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.
246//
247void FilePickerWindow::AddFileToList(unsigned long index)
248{
249printf("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);
253}
254
255void FilePickerWindow::AddFileToList2(unsigned long index, QString str, QImage * img, unsigned long size)
256{
257if (index != 0xFFFFFFFF)
258 printf("FilePickerWindow(2): Found match [%s]...\n", romList[index].name);
259
260 if (img)
261 {
262 model->AddData(index, str, *img, size);
263//It would be better to pass the pointer into the model though...
264 delete img;
265 }
266 else
267 model->AddData(index, str, QImage(), size);
268}
269
270void FilePickerWindow::AddFileToList3(unsigned long index, QString str, QImage * img, unsigned long size, bool haveUniversalHeader, unsigned long fileType, unsigned long crc)
271{
272//if (index != 0xFFFFFFFF)
273// printf("FilePickerWindow(3): Found match [%s]...\n", romList[index].name);
274
275 if (img)
276 {
277 model->AddData(index, str, *img, size, haveUniversalHeader, fileType, crc);
278//It would be better to pass the pointer into the model though...
279 delete img;
280 }
281 else
282 model->AddData(index, str, QImage(), size, haveUniversalHeader, fileType, crc);
283}
284
285void FilePickerWindow::LoadButtonPressed(void)
286{
287 // TODO: Get the text of the current selection, call the MainWin slot for loading
288 emit(RequestLoad(currentFile));
289 hide();
290}
291
292//
293// This slot gets called when the QListView gets clicked on. Updates
294// the cart graphic and accompanying text.
295//
296void FilePickerWindow::UpdateSelection(const QModelIndex & current, const QModelIndex &/*previous*/)
297{
298#if 0
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();
304#else
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);
315#endif
316
317 // Disallow loading completely unknown files, but allow all others.
318 insertCart->setEnabled(haveUnknown && (fileType == JST_NONE) ? false : true);
319//hm.
320//currentFile = s;
321
322//373x172 is label size...
323//365x168 now...
324 if (!label.isNull())
325 {
326/*
327 QImage cartImg(":/res/cart-blank.png");
328 QPainter painter(&cartImg);
329 painter.drawPixmap(23, 87, QPixmap(":/res/label-blank.png"));
330 painter.end();
331 cartSmall = cartImg.scaled(488/4, 395/4, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
332*/
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));
342
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")));
346
347 painter.end();
348 cartImage->setPixmap(QPixmap::fromImage(cart));
349 }
350 else
351 {
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.
355 QImage cart;
356
357// We now have to sources of data for the passed in files:
358// - The file DB
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
362// should be valid.
363// The DB takes precedence over the fileType.
364 if ((!haveUnknown && (romList[i].flags & FF_ROM))
365 || (haveUnknown && (fileType == JST_ROM) && !haveUniversalHeader))
366 {
367 cart = QImage(":/res/cart-blank.png");
368 QPainter painter(&cart);
369 painter.drawPixmap(27, 89, QPixmap::fromImage(QImage(":/res/label-blank.png")));
370 painter.end();
371 }
372 else if ((!haveUnknown && (romList[i].flags & FF_ALPINE))
373 || (haveUnknown
374 && ((fileType == JST_ALPINE) || ((fileType == JST_ROM) && haveUniversalHeader))))
375 {
376 if (haveUniversalHeader)
377 cart = QImage(":/res/skunkboard-file.png");
378 else
379 cart = QImage(":/res/alpine-file.png");
380 }
381 else if (haveUnknown && (fileType == JST_ELF32))
382 {
383 cart = QImage(":/res/ELF-file.png");
384 }
385 else if (haveUnknown && (fileType == JST_ABS_TYPE1 || fileType == JST_ABS_TYPE2
386 || fileType == JST_JAGSERVER) || fileType == JST_WTFOMGBBQ)
387 {
388 cart = QImage(":/res/homebrew-file.png");
389 }
390 else
391 cart = QImage(":/res/unknown-file.png");
392
393 cartImage->setPixmap(QPixmap::fromImage(cart));
394 }
395
396//1048576
397//2097152
398//4194304
399 if (!haveUnknown)
400 prettyFilename = romList[i].name;
401 else
402 {
403 int lastSlashPos = currentFile.lastIndexOf('/');
404 prettyFilename = "\"" + currentFile.mid(lastSlashPos + 1) + "\"";
405 }
406
407 // Ensure that the title isn't longer than the width of the dialog...
408#if 1
409 title->setText(QString("<h2>%1</h2>").arg(prettyFilename));
410#else
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);
415#endif
416
417//Kludge for now, we'll have to fix this later...
418// So let's fix it now!
419 QString fileTypeString, crcString, notes, compatibility;
420
421#if 0
422 if (!haveUnknown)
423 {
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);
428 else
429 fileTypeString = QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize);
430 }
431#else
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))
436 || (haveUnknown
437 && ((fileType == JST_ALPINE) || ((fileType == JST_ROM) && haveUniversalHeader))))
438 {
439 if (haveUniversalHeader)
440 fileTypeString = QString(tr("%1MB Alpine ROM w/Universal Header"));
441 else
442 fileTypeString = QString(tr("%1MB Alpine ROM"));
443
444 fileTypeString = fileTypeString.arg((fileSize + 8192) / 1048576);
445 }
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);
452 else
453 fileTypeString = QString(tr("*** UNKNOWN *** (%1 bytes)")).arg(fileSize);
454#endif
455
456// crcString = QString("%1").arg(romList[i].crc32, 8, 16, QChar('0')).toUpper();
457 crcString = QString("%1").arg(crc, 8, 16, QChar('0')).toUpper();
458
459 if (!haveUnknown && (romList[i].flags & FF_NON_WORKING))
460 compatibility = "DOES NOT WORK";
461 else
462 compatibility = "Unknown";
463
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>";
467
468// if (haveUniversalHeader)
469// notes += " Universal Header detected";
470
471 if (!haveUnknown && (romList[i].flags & FF_REQ_BIOS))
472 notes += " Requires BIOS";
473
474 if (!haveUnknown && (romList[i].flags & FF_REQ_DSP))
475 notes += " Requires DSP";
476
477 if (!haveUnknown && (romList[i].flags & FF_VERIFIED))
478 notes += " <i>(Verified)</i>";
479
480 data->setText(QString("%1<br>%2<br>%3<br>%4")
481 .arg(fileTypeString).arg(crcString).arg(compatibility).arg(notes));
482}
483
484/*
485 Super Duper Awesome Guy (World)
486
487 Type: 4MB Cartridge
488 CRC32: FEDCBA98
489Compatibility: DOES NOT WORK
490 Notes: Universal Header detected; Requires DSP
491
492
493 Stupid Homebrew Game That Sux
494
495 Type: ABS/COF Executable (43853 bytes)
496 CRC32: 76543210
497Compatibility: Unknown
498 Notes: $4000 Load, $4000 Run
499
500
501 Action Hopscotch Plus (Prototype)
502
503 Type: 2MB Alpine ROM
504 CRC32: 44889921
505Compatibility: 80% (or ****)
506 Notes: EEPROM available
507*/