1 #include <QPixmap>
2 #include <QImage>
3 #include <QImageReader>
4 #include <QDir>
5 #include <QByteArray>
6 #include <QBuffer>
7 #include <QTimer>
8 #include <QMap>
9 #include <QClipboard>
10 #include <QCache>
11 #include <QTreeWidgetItem>
12 #include <QPainterPath>
13
14 #include "settings.h"
15 #include "options.h"
16 #include "imagewidget.h"
17 #include "customartwork.h"
18 #include "qmc2main.h"
19 #include "machinelist.h"
20 #include "macros.h"
21
22 // external global variables
23 extern MainWindow *qmc2MainWindow;
24 extern Settings *qmc2Config;
25 extern Options *qmc2Options;
26 extern bool qmc2SmoothScaling;
27 extern bool qmc2RetryLoadingImages;
28 extern bool qmc2ParentImageFallback;
29 extern bool qmc2ShowMachineName;
30 extern bool qmc2ShowMachineNameOnlyWhenRequired;
31 extern QTreeWidgetItem *qmc2CurrentItem;
32 extern QHash<QString, QString> qmc2ParentHash;
33 extern QCache<QString, ImagePixmap> qmc2ImagePixmapCache;
34
35 QStringList ImageWidget::formatNames;
36 QStringList ImageWidget::formatExtensions;
37 QStringList ImageWidget::formatDescriptions;
38 QHash<int, ImageWidget *> ImageWidget::artworkHash;
39
ImageWidget(QWidget * parent)40 ImageWidget::ImageWidget(QWidget *parent)
41 : QWidget(parent)
42 {
43 if ( formatNames.isEmpty() )
44 formatNames << "PNG" << "BMP" << "GIF" << "JPG" << "PBM" << "PGM" << "PPM" << "TIFF" << "XBM" << "XPM" << "SVG" << "TGA";
45 if ( formatExtensions.isEmpty() )
46 formatExtensions << "png" << "bmp" << "gif" << "jpg, jpeg" << "pbm" << "pgm" << "ppm" << "tif, tiff" << "xbm" << "xpm" << "svg" << "tga";
47 if ( formatDescriptions.isEmpty() )
48 formatDescriptions << tr("Portable Network Graphics") << tr("Windows Bitmap") << tr("Graphic Interchange Format") << tr("Joint Photographic Experts Group") << tr("Portable Bitmap")
49 << tr("Portable Graymap") << tr("Portable Pixmap") << tr("Tagged Image File Format") << tr("X11 Bitmap") << tr("X11 Pixmap") << tr("Scalable Vector Graphics") << tr("Targa Image Format");
50 QTimer::singleShot(0, this, SLOT(init()));
51 }
52
~ImageWidget()53 ImageWidget::~ImageWidget()
54 {
55 closeSource();
56 }
57
customArtworkWidget(const QString & name)58 ImageWidget *ImageWidget::customArtworkWidget(const QString &name)
59 {
60 foreach (ImageWidget *imw, artworkHash)
61 if ( imw->customArtwork() )
62 if ( ((CustomArtwork *)imw)->name() == name )
63 return imw;
64 return 0;
65 }
66
init()67 void ImageWidget::init()
68 {
69 contextMenu = new QMenu(this);
70 contextMenu->hide();
71
72 QString s;
73 QAction *action;
74
75 s = tr("Copy image to clipboard");
76 action = contextMenu->addAction(s);
77 action->setToolTip(s); action->setStatusTip(s);
78 action->setIcon(QIcon(QString::fromUtf8(":/data/img/editcopy.png")));
79 connect(action, SIGNAL(triggered()), this, SLOT(copyToClipboard()));
80
81 s = tr("Copy file path to clipboard");
82 action = contextMenu->addAction(s);
83 action->setToolTip(s); action->setStatusTip(s);
84 action->setIcon(QIcon(QString::fromUtf8(":/data/img/editcopy.png")));
85 connect(action, SIGNAL(triggered()), this, SLOT(copyPathToClipboard()));
86 actionCopyPathToClipboard = action;
87
88 contextMenu->addSeparator();
89
90 s = tr("Refresh cache slot");
91 action = contextMenu->addAction(s);
92 action->setToolTip(s); action->setStatusTip(s);
93 action->setIcon(QIcon(QString::fromUtf8(":/data/img/reload.png")));
94 connect(action, SIGNAL(triggered()), this, SLOT(refresh()));
95
96 openSource();
97 }
98
updateArtwork()99 void ImageWidget::updateArtwork()
100 {
101 QHashIterator<int, ImageWidget *> it(artworkHash);
102 while ( it.hasNext() ) {
103 it.next();
104 it.value()->update();
105 }
106 }
107
reloadArtworkFormats()108 void ImageWidget::reloadArtworkFormats()
109 {
110 QHashIterator<int, ImageWidget *> it(artworkHash);
111 while ( it.hasNext() ) {
112 it.next();
113 it.value()->reloadActiveFormats();
114 }
115 }
116
openSource()117 void ImageWidget::openSource()
118 {
119 if ( useZip() ) {
120 foreach (QString filePath, imageZip().split(';', QString::SkipEmptyParts)) {
121 unzFile imageFile = unzOpen(filePath.toUtf8().constData());
122 if ( imageFile == 0 )
123 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("FATAL: can't open %1 file, please check access permissions for %2").arg(imageType()).arg(imageZip()));
124 else
125 imageFileMap.insert(filePath, imageFile);
126 }
127 } else if ( useSevenZip() ) {
128 foreach (QString filePath, imageZip().split(';', QString::SkipEmptyParts)) {
129 SevenZipFile *imageFile = new SevenZipFile(filePath);
130 if ( !imageFile->open() )
131 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("FATAL: can't open %1 file, please check access permissions for %2").arg(imageType()).arg(imageZip()));
132 else {
133 connect(imageFile, SIGNAL(dataReady()), this, SLOT(sevenZipDataReady()));
134 imageFileMap7z.insert(filePath, imageFile);
135 }
136 }
137 }
138 #if defined(QMC2_LIBARCHIVE_ENABLED)
139 else if ( useArchive() ) {
140 foreach (QString filePath, imageZip().split(';', QString::SkipEmptyParts)) {
141 ArchiveFile *imageFile = new ArchiveFile(filePath);
142 if ( !imageFile->open() )
143 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("FATAL: can't open %1 file, please check access permissions for %2").arg(imageType()).arg(imageZip()));
144 else
145 imageArchiveMap.insert(filePath, imageFile);
146 }
147 }
148 #endif
149 reloadActiveFormats();
150 }
151
closeSource()152 void ImageWidget::closeSource()
153 {
154 QMapIterator<QString, unzFile> itZip(imageFileMap);
155 while ( itZip.hasNext() ) {
156 itZip.next();
157 unzClose(itZip.value());
158 }
159 imageFileMap.clear();
160 QMapIterator<QString, SevenZipFile*> it7z(imageFileMap7z);
161 while ( it7z.hasNext() ) {
162 it7z.next();
163 it7z.value()->close();
164 delete it7z.value();
165 }
166 imageFileMap7z.clear();
167 #if defined(QMC2_LIBARCHIVE_ENABLED)
168 QMapIterator<QString, ArchiveFile*> itArchive(imageArchiveMap);
169 while ( itArchive.hasNext() ) {
170 itArchive.next();
171 itArchive.value()->close();
172 delete itArchive.value();
173 }
174 imageArchiveMap.clear();
175 #endif
176 }
177
parentFallback()178 bool ImageWidget::parentFallback()
179 {
180 return qmc2ParentImageFallback && qmc2Config->value(fallbackSettingsKey(), 0).toInt() == 0;
181 }
182
reloadActiveFormats()183 void ImageWidget::reloadActiveFormats()
184 {
185 activeFormats.clear();
186 QStringList imgFmts;
187 if ( customArtwork() )
188 imgFmts = qmc2Config->value(QString("Artwork/%1/ActiveFormats").arg(imageType()), QStringList()).toStringList();
189 else
190 imgFmts = qmc2Config->value(QMC2_FRONTEND_PREFIX + QString("ActiveImageFormats/%1").arg(cachePrefix()), QStringList()).toStringList();
191 if ( imgFmts.isEmpty() )
192 activeFormats << QMC2_IMAGE_FORMAT_INDEX_PNG;
193 else for (int i = 0; i < imgFmts.count(); i++)
194 activeFormats << imgFmts.at(i).toInt();
195 }
196
cleanDir(QString dirs)197 QString ImageWidget::cleanDir(QString dirs)
198 {
199 QStringList dirList;
200 foreach (QString dir, dirs.split(';', QString::SkipEmptyParts)) {
201 if ( !dir.endsWith('/') )
202 dir += '/';
203 dirList << dir;
204 }
205 return dirList.join(";");
206 }
207
paintEvent(QPaintEvent * e)208 void ImageWidget::paintEvent(QPaintEvent *e)
209 {
210 QPainter p(this);
211
212 if ( !qmc2CurrentItem ) {
213 drawCenteredImage(0, &p); // clear image widget
214 return;
215 }
216
217 if ( qmc2CurrentItem->text(QMC2_MACHINELIST_COLUMN_MACHINE) == MachineList::trWaitingForData ) {
218 drawCenteredImage(0, &p); // clear image widget
219 return;
220 }
221
222 QTreeWidgetItem *topLevelItem = qmc2CurrentItem;
223 while ( topLevelItem->parent() )
224 topLevelItem = topLevelItem->parent();
225
226 QString machineName(topLevelItem->text(QMC2_MACHINELIST_COLUMN_NAME));
227 cacheKey = cachePrefix() + '_' + machineName;
228 ImagePixmap *cpm = qmc2ImagePixmapCache.object(cacheKey);
229 if ( !cpm ) {
230 qmc2CurrentItem = topLevelItem;
231 loadImage(machineName, machineName);
232 cpm = qmc2ImagePixmapCache.object(cacheKey);
233 }
234 if ( cpm ) {
235 currentPixmap = *cpm;
236 currentPixmap.imagePath = cpm->imagePath;
237 }
238 if ( scaledImage() || currentPixmap.isGhost )
239 drawScaledImage(¤tPixmap, &p);
240 else
241 drawCenteredImage(¤tPixmap, &p);
242 }
243
refresh()244 void ImageWidget::refresh()
245 {
246 if ( !cacheKey.isEmpty() ) {
247 qmc2ImagePixmapCache.remove(cacheKey);
248 update();
249 }
250 }
251
sevenZipDataReady()252 void ImageWidget::sevenZipDataReady()
253 {
254 update();
255 enableWidgets(true);
256 }
257
enableWidgets(bool enable)258 void ImageWidget::enableWidgets(bool enable)
259 {
260 if ( customArtwork() )
261 return;
262 switch ( imageTypeNumeric() ) {
263 case QMC2_IMGTYPE_PREVIEW:
264 qmc2Options->radioButtonPreviewSelect->setEnabled(enable);
265 qmc2Options->lineEditPreviewFile->setEnabled(enable);
266 qmc2Options->comboBoxPreviewFileType->setEnabled(enable);
267 qmc2Options->toolButtonBrowsePreviewFile->setEnabled(enable);
268 break;
269 case QMC2_IMGTYPE_FLYER:
270 qmc2Options->radioButtonFlyerSelect->setEnabled(enable);
271 qmc2Options->lineEditFlyerFile->setEnabled(enable);
272 qmc2Options->comboBoxFlyerFileType->setEnabled(enable);
273 qmc2Options->toolButtonBrowseFlyerFile->setEnabled(enable);
274 break;
275 case QMC2_IMGTYPE_CABINET:
276 qmc2Options->radioButtonCabinetSelect->setEnabled(enable);
277 qmc2Options->lineEditCabinetFile->setEnabled(enable);
278 qmc2Options->comboBoxCabinetFileType->setEnabled(enable);
279 qmc2Options->toolButtonBrowseCabinetFile->setEnabled(enable);
280 break;
281 case QMC2_IMGTYPE_CONTROLLER:
282 qmc2Options->radioButtonControllerSelect->setEnabled(enable);
283 qmc2Options->lineEditControllerFile->setEnabled(enable);
284 qmc2Options->comboBoxControllerFileType->setEnabled(enable);
285 qmc2Options->toolButtonBrowseControllerFile->setEnabled(enable);
286 break;
287 case QMC2_IMGTYPE_MARQUEE:
288 qmc2Options->radioButtonMarqueeSelect->setEnabled(enable);
289 qmc2Options->lineEditMarqueeFile->setEnabled(enable);
290 qmc2Options->comboBoxMarqueeFileType->setEnabled(enable);
291 qmc2Options->toolButtonBrowseMarqueeFile->setEnabled(enable);
292 break;
293 case QMC2_IMGTYPE_TITLE:
294 qmc2Options->radioButtonTitleSelect->setEnabled(enable);
295 qmc2Options->lineEditTitleFile->setEnabled(enable);
296 qmc2Options->comboBoxTitleFileType->setEnabled(enable);
297 qmc2Options->toolButtonBrowseTitleFile->setEnabled(enable);
298 break;
299 case QMC2_IMGTYPE_PCB:
300 qmc2Options->radioButtonPCBSelect->setEnabled(enable);
301 qmc2Options->lineEditPCBFile->setEnabled(enable);
302 qmc2Options->comboBoxPCBFileType->setEnabled(enable);
303 qmc2Options->toolButtonBrowsePCBFile->setEnabled(enable);
304 break;
305 }
306 }
307
loadImage(const QString & machineName,const QString & onBehalfOf,bool checkOnly,QString * fileName,bool loadImages)308 bool ImageWidget::loadImage(const QString &machineName, const QString &onBehalfOf, bool checkOnly, QString *fileName, bool loadImages)
309 {
310 ImagePixmap pm;
311 char imageBuffer[QMC2_ZIP_BUFFER_SIZE];
312 if ( fileName )
313 *fileName = "";
314 bool fileOk = true;
315 QString cacheKey(cachePrefix() + '_' + onBehalfOf);
316 if ( useZip() ) {
317 // try loading image from (semicolon-separated) ZIP archive(s)
318 QByteArray imageData;
319 int len;
320 foreach (int format, activeFormats) {
321 QString formatName(formatNames.value(format));
322 foreach (QString extension, formatExtensions.value(format).split(", ", QString::SkipEmptyParts)) {
323 QString machineFile(machineName + '.' + extension);
324 if ( fileName )
325 *fileName = machineFile;
326 foreach (unzFile imageFile, imageFileMap) {
327 if ( unzLocateFile(imageFile, machineFile.toUtf8().constData(), 0) == UNZ_OK ) {
328 if ( unzOpenCurrentFile(imageFile) == UNZ_OK ) {
329 while ( (len = unzReadCurrentFile(imageFile, &imageBuffer, QMC2_ZIP_BUFFER_SIZE)) > 0 )
330 imageData.append(imageBuffer, len);
331 fileOk = true;
332 unzCloseCurrentFile(imageFile);
333 } else
334 fileOk = false;
335 } else
336 fileOk = false;
337
338 if ( fileOk )
339 break;
340 else
341 imageData.clear();
342 }
343 if ( fileOk )
344 fileOk = pm.loadFromData(imageData, formatName.toUtf8().constData());
345 if ( !checkOnly ) {
346 if ( fileOk ) {
347 #if defined(QMC2_DEBUG)
348 QMC2_PRINT_STRTXT(QString("ZIP: Image loaded for %1").arg(cacheKey));
349 #endif
350 qmc2ImagePixmapCache.insert(cacheKey, new ImagePixmap(pm), pm.toImage().byteCount());
351 currentPixmap = pm;
352 } else {
353 QString parentName(qmc2ParentHash.value(machineName));
354 if ( parentFallback() && !parentName.isEmpty() ) {
355 fileOk = loadImage(parentName, onBehalfOf);
356 } else {
357 currentPixmap = qmc2MainWindow->qmc2GhostImagePixmap;
358 if ( !qmc2RetryLoadingImages )
359 qmc2ImagePixmapCache.insert(cacheKey, new ImagePixmap(currentPixmap), currentPixmap.toImage().byteCount());
360 #if defined(QMC2_DEBUG)
361 QMC2_PRINT_STRTXT(QString("ZIP: Using ghost image for %1").arg(cacheKey));
362 #endif
363 }
364 }
365 }
366 if ( fileOk )
367 break;
368 }
369 if ( fileOk )
370 break;
371 }
372 } else if ( useSevenZip() ) {
373 // try loading image from (semicolon-separated) 7z archive(s)
374 QByteArray imageData;
375 foreach (int format, activeFormats) {
376 QString formatName(formatNames.value(format));
377 foreach (QString extension, formatExtensions.value(format).split(", ", QString::SkipEmptyParts)) {
378 QString machineFile(machineName + '.' + extension);
379 if ( fileName )
380 *fileName = machineFile;
381 bool isFillingDictionary = false;
382 foreach (SevenZipFile *imageFile, imageFileMap7z) {
383 int index = imageFile->indexOfName(machineFile);
384 if ( index >= 0 ) {
385 m_async = true;
386 quint64 readLength = imageFile->read(index, &imageData, &m_async);
387 if ( readLength == 0 && m_async ) {
388 currentPixmap = qmc2MainWindow->qmc2GhostImagePixmap;
389 qmc2ImagePixmapCache.remove(cacheKey);
390 isFillingDictionary = true;
391 fileOk = true;
392 } else
393 fileOk = !imageFile->hasError();
394 } else
395 fileOk = false;
396
397 if ( fileOk )
398 break;
399 else
400 imageData.clear();
401 }
402 if ( fileOk )
403 fileOk = pm.loadFromData(imageData, formatName.toUtf8().constData());
404 if ( !checkOnly ) {
405 if ( fileOk ) {
406 #if defined(QMC2_DEBUG)
407 QMC2_PRINT_STRTXT(QString("7z: Image loaded for %1").arg(cacheKey));
408 #endif
409 qmc2ImagePixmapCache.insert(cacheKey, new ImagePixmap(pm), pm.toImage().byteCount());
410 currentPixmap = pm;
411 } else {
412 QString parentName = qmc2ParentHash.value(machineName);
413 if ( parentFallback() && !parentName.isEmpty() ) {
414 fileOk = loadImage(parentName, onBehalfOf);
415 } else {
416 currentPixmap = qmc2MainWindow->qmc2GhostImagePixmap;
417 if ( !qmc2RetryLoadingImages && !isFillingDictionary )
418 qmc2ImagePixmapCache.insert(cacheKey, new ImagePixmap(currentPixmap), currentPixmap.toImage().byteCount());
419 else {
420 QPainter p;
421 QString message = tr("Decompressing archive, please wait...");
422 p.begin(¤tPixmap);
423 p.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::HighQualityAntialiasing | QPainter::SmoothPixmapTransform);
424 QFont f(qApp->font());
425 f.setWeight(QFont::Bold);
426 f.setPointSize(f.pointSize() * 2);
427 QFontMetrics fm(f);
428 int adjustment = fm.height() / 2;
429 p.setFont(f);
430 QRect outerRect = p.boundingRect(currentPixmap.rect(), Qt::AlignCenter | Qt::TextWordWrap, message).adjusted(-adjustment, -adjustment, adjustment, adjustment);
431 QPainterPath pp;
432 pp.addRoundedRect(outerRect, 5, 5);
433 p.fillPath(pp, QBrush(QColor(0, 0, 0, 128), Qt::SolidPattern));
434 p.setPen(QColor(255, 255, 0, 255));
435 p.drawText(currentPixmap.rect(), Qt::AlignCenter | Qt::TextWordWrap, message);
436 p.end();
437 enableWidgets(false);
438 }
439 #if defined(QMC2_DEBUG)
440 QMC2_PRINT_STRTXT(QString("7z: Using ghost image for %1%2").arg(cacheKey).arg(isFillingDictionary ? " (filling up dictionary)" : ""));
441 #endif
442 if ( isFillingDictionary )
443 QTimer::singleShot(QMC2_IMG_7Z_DICT_FILL_DELAY, this, SLOT(update()));
444 }
445 }
446 }
447 if ( fileOk )
448 break;
449 }
450 if ( fileOk )
451 break;
452 }
453 }
454 #if defined(QMC2_LIBARCHIVE_ENABLED)
455 else if ( useArchive() ) {
456 // try loading image from (semicolon-separated) archive(s)
457 QByteArray imageData;
458 foreach (int format, activeFormats) {
459 QString formatName(formatNames.value(format));
460 foreach (QString extension, formatExtensions.value(format).split(", ", QString::SkipEmptyParts)) {
461 QString machineFile(machineName + '.' + extension);
462 if ( fileName )
463 *fileName = machineFile;
464 foreach (ArchiveFile *imageFile, imageArchiveMap) {
465 if ( imageFile->seekEntry(machineFile) )
466 fileOk = imageFile->readEntry(imageData) > 0;
467 else
468 fileOk = false;
469 if ( fileOk )
470 break;
471 }
472 if ( fileOk )
473 fileOk = pm.loadFromData(imageData, formatName.toUtf8().constData());
474 if ( !checkOnly ) {
475 if ( fileOk ) {
476 #if defined(QMC2_DEBUG)
477 QMC2_PRINT_STRTXT(QString("Archive: Image loaded for %1").arg(cacheKey));
478 #endif
479 qmc2ImagePixmapCache.insert(cacheKey, new ImagePixmap(pm), pm.toImage().byteCount());
480 currentPixmap = pm;
481 } else {
482 QString parentName(qmc2ParentHash.value(machineName));
483 if ( parentFallback() && !parentName.isEmpty() ) {
484 fileOk = loadImage(parentName, onBehalfOf);
485 } else {
486 currentPixmap = qmc2MainWindow->qmc2GhostImagePixmap;
487 if ( !qmc2RetryLoadingImages )
488 qmc2ImagePixmapCache.insert(cacheKey, new ImagePixmap(currentPixmap), currentPixmap.toImage().byteCount());
489 #if defined(QMC2_DEBUG)
490 QMC2_PRINT_STRTXT(QString("Archive: Using ghost image for %1").arg(cacheKey));
491 #endif
492 }
493 }
494 }
495 if ( fileOk )
496 break;
497 }
498 if ( fileOk )
499 break;
500 }
501 }
502 #endif
503 else {
504 // try loading image from (semicolon-separated) folder(s)
505 foreach (QString baseDirectory, imageDir().split(';', QString::SkipEmptyParts)) {
506 QString imgDir(QDir::cleanPath(baseDirectory + '/' + machineName));
507 foreach (int format, activeFormats) {
508 QString formatName(formatNames.value(format));
509 foreach (QString extension, formatExtensions.value(format).split(", ", QString::SkipEmptyParts)) {
510 QString imagePath(imgDir + '.' + extension);
511 if ( fileName )
512 *fileName = imagePath;
513 QFile f(imagePath);
514 if ( !f.exists() ) {
515 QDir dir(imgDir);
516 if ( dir.exists() ) {
517 QStringList nameFilter;
518 nameFilter << "*." + extension;
519 QStringList dirEntries = dir.entryList(nameFilter, QDir::Files | QDir::NoDotAndDotDot | QDir::Readable | QDir::CaseSensitive, QDir::Name | QDir::Reversed);
520 if ( dirEntries.count() > 0 ) {
521 imagePath = imgDir + '/' + dirEntries.first();
522 if ( fileName )
523 *fileName = imagePath;
524 }
525 }
526 }
527 if ( checkOnly ) {
528 if ( loadImages )
529 fileOk = pm.load(imagePath, formatName.toUtf8().constData());
530 else {
531 QFile f(imagePath);
532 fileOk = f.exists();
533 if ( !fileOk ) {
534 QString parentName(qmc2ParentHash.value(machineName));
535 if ( parentFallback() && !parentName.isEmpty() )
536 fileOk = loadImage(parentName, onBehalfOf, checkOnly, fileName, false);
537 }
538 }
539 } else {
540 if ( pm.load(imagePath, formatName.toUtf8().constData()) ) {
541 pm.imagePath = imagePath;
542 #if defined(QMC2_DEBUG)
543 QMC2_PRINT_STRTXT(QString("Folder: Image loaded for %1").arg(cacheKey));
544 #endif
545 qmc2ImagePixmapCache.insert(cacheKey, new ImagePixmap(pm), pm.toImage().byteCount());
546 currentPixmap = pm;
547 fileOk = true;
548 } else {
549 QString parentName(qmc2ParentHash.value(machineName));
550 if ( parentFallback() && !parentName.isEmpty() ) {
551 fileOk = loadImage(parentName, onBehalfOf);
552 } else {
553 currentPixmap = qmc2MainWindow->qmc2GhostImagePixmap;
554 if ( !qmc2RetryLoadingImages )
555 qmc2ImagePixmapCache.insert(cacheKey, new ImagePixmap(currentPixmap), currentPixmap.toImage().byteCount());
556 #if defined(QMC2_DEBUG)
557 QMC2_PRINT_STRTXT(QString("Folder: Using ghost image for %1").arg(cacheKey));
558 #endif
559 fileOk = false;
560 }
561 }
562 }
563 if ( fileOk )
564 break;
565 }
566 if ( fileOk )
567 break;
568 }
569 if ( fileOk )
570 break;
571 }
572 }
573 return fileOk;
574 }
575
primaryPathFor(QString machineName)576 QString ImageWidget::primaryPathFor(QString machineName)
577 {
578 if ( !useZip() && !useSevenZip() ) {
579 QStringList fl(imageDir().split(';', QString::SkipEmptyParts));
580 QString baseDirectory;
581 if ( !fl.isEmpty() )
582 baseDirectory = fl.first();
583 return QDir::toNativeSeparators(QDir::cleanPath(baseDirectory + '/' + machineName + ".png"));
584 } else // we don't support on-the-fly image replacement for zipped images yet!
585 return QString();
586 }
587
replaceImage(QString machineName,QPixmap & pixmap)588 bool ImageWidget::replaceImage(QString machineName, QPixmap &pixmap)
589 {
590 if ( !useZip() && !useSevenZip() ) {
591 QString savePath = primaryPathFor(machineName);
592 if ( !savePath.isEmpty() ) {
593 bool goOn = true;
594 if ( QFile::exists(savePath) ) {
595 QString backupPath = savePath + ".bak";
596 if ( QFile::exists(backupPath) )
597 QFile::remove(backupPath);
598 if ( !QFile::copy(savePath, backupPath) ) {
599 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("FATAL: can't create backup of existing image file '%1' as '%2'").arg(savePath).arg(backupPath));
600 goOn = false;
601 }
602 }
603 if ( goOn ) {
604 QString primaryPath = QFileInfo(savePath).absoluteDir().absolutePath();
605 QDir ppDir(primaryPath);
606 if ( !ppDir.exists() )
607 ppDir.mkpath(primaryPath);
608 if ( pixmap.save(savePath, "PNG") ) {
609 currentPixmap = pixmap;
610 currentPixmap.imagePath = savePath;
611 update();
612 return true;
613 } else {
614 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("FATAL: can't create image file '%1'").arg(savePath));
615 return false;
616 }
617 } else
618 return false;
619 } else {
620 qmc2MainWindow->log(QMC2_LOG_FRONTEND, tr("FATAL: can't determine primary path for image-type '%1'").arg(imageType()));
621 return false;
622 }
623 } else // we don't support on-the-fly image replacement for zipped and 7-zipped images yet!
624 return false;
625 }
626
627 #if defined(QMC2_LIBARCHIVE_ENABLED)
checkImage(QString machineName,unzFile zip,SevenZipFile * sevenZip,ArchiveFile * archiveFile,QSize * sizeReturn,int * bytesUsed,QString * fileName,QString * readerError,bool * async,bool * isFillingDict)628 bool ImageWidget::checkImage(QString machineName, unzFile zip, SevenZipFile *sevenZip, ArchiveFile *archiveFile, QSize *sizeReturn, int *bytesUsed, QString *fileName, QString *readerError, bool *async, bool *isFillingDict)
629 #else
630 bool ImageWidget::checkImage(QString machineName, unzFile zip, SevenZipFile *sevenZip, QSize *sizeReturn, int *bytesUsed, QString *fileName, QString *readerError, bool *async, bool *isFillingDict)
631 #endif
632 {
633 QImage image;
634 char imageBuffer[QMC2_ZIP_BUFFER_SIZE];
635
636 if ( fileName )
637 fileName->clear();
638 bool fileOk = true;
639 if ( useZip() ) {
640 // try loading image from (semicolon-separated) ZIP archive(s)
641 QByteArray imageData;
642 int len;
643 foreach (int format, activeFormats) {
644 QString formatName(formatNames.value(format));
645 foreach (QString extension, formatExtensions.value(format).split(", ", QString::SkipEmptyParts)) {
646 QString machineFile(machineName + '.' + extension);
647 if ( fileName )
648 *fileName = machineFile;
649 if ( zip == 0 ) {
650 foreach (unzFile imageFile, imageFileMap) {
651 if ( unzLocateFile(imageFile, machineFile.toUtf8().constData(), 0) == UNZ_OK ) {
652 if ( unzOpenCurrentFile(imageFile) == UNZ_OK ) {
653 while ( (len = unzReadCurrentFile(imageFile, &imageBuffer, QMC2_ZIP_BUFFER_SIZE)) > 0 )
654 imageData.append(imageBuffer, len);
655 fileOk = true;
656 unzCloseCurrentFile(imageFile);
657 } else
658 fileOk = false;
659 } else
660 fileOk = false;
661 if ( fileOk )
662 break;
663 else
664 imageData.clear();
665 }
666 } else {
667 if ( unzLocateFile(zip, machineFile.toUtf8().constData(), 0) == UNZ_OK ) {
668 if ( unzOpenCurrentFile(zip) == UNZ_OK ) {
669 while ( (len = unzReadCurrentFile(zip, &imageBuffer, QMC2_ZIP_BUFFER_SIZE)) > 0 )
670 imageData.append(imageBuffer, len);
671 fileOk = true;
672 unzCloseCurrentFile(zip);
673 } else
674 fileOk = false;
675 } else
676 fileOk = false;
677 }
678 if ( fileOk ) {
679 QBuffer buffer(&imageData);
680 QImageReader imageReader(&buffer, formatName.toUtf8().constData());
681 fileOk = imageReader.read(&image);
682 if ( fileOk ) {
683 if ( sizeReturn )
684 *sizeReturn = image.size();
685 if ( bytesUsed )
686 *bytesUsed = image.byteCount();
687 } else if ( readerError != 0 && imageReader.error() != QImageReader::FileNotFoundError )
688 *readerError = imageReader.errorString();
689 }
690 if ( fileOk )
691 break;
692 }
693 if ( fileOk )
694 break;
695 }
696 } else if ( useSevenZip() ) {
697 // try loading image from (semicolon-separated) 7z archive(s)
698 QByteArray imageData;
699 foreach (int format, activeFormats) {
700 QString formatName(formatNames.value(format));
701 foreach (QString extension, formatExtensions.value(format).split(", ", QString::SkipEmptyParts)) {
702 QString machineFile(machineName + '.' + extension);
703 if ( fileName )
704 *fileName = machineFile;
705 if ( isFillingDict )
706 *isFillingDict = false;
707 if ( sevenZip == 0 ) {
708 foreach (SevenZipFile *imageFile, imageFileMap7z) {
709 int index = imageFile->indexOfName(machineFile);
710 if ( index >= 0 ) {
711 m_async = true;
712 quint64 readLength = imageFile->read(index, &imageData, &m_async);
713 if ( readLength == 0 && m_async ) {
714 if ( isFillingDict )
715 *isFillingDict = true;
716 fileOk = true;
717 } else
718 fileOk = !imageFile->hasError();
719 } else
720 fileOk = false;
721 if ( fileOk )
722 break;
723 else
724 imageData.clear();
725 }
726 } else {
727 int index = sevenZip->indexOfName(machineFile);
728 if ( index >= 0 ) {
729 if ( async )
730 *async = true;
731 quint64 readLength = sevenZip->read(index, &imageData, async);
732 if ( readLength == 0 && (async && *async) ) {
733 if ( isFillingDict )
734 *isFillingDict = true;
735 fileOk = true;
736 } else
737 fileOk = !sevenZip->hasError();
738 } else
739 fileOk = false;
740 if ( !fileOk )
741 imageData.clear();
742 }
743 bool ifd = isFillingDict ? *isFillingDict : false;
744 if ( fileOk && !ifd ) {
745 QBuffer buffer(&imageData);
746 QImageReader imageReader(&buffer, formatName.toUtf8().constData());
747 fileOk = imageReader.read(&image);
748 if ( fileOk ) {
749 if ( sizeReturn )
750 *sizeReturn = image.size();
751 if ( bytesUsed )
752 *bytesUsed = image.byteCount();
753 } else if ( readerError != 0 && imageReader.error() != QImageReader::FileNotFoundError )
754 *readerError = imageReader.errorString();
755 }
756 if ( fileOk )
757 break;
758 }
759 if ( fileOk )
760 break;
761 }
762 }
763 #if defined(QMC2_LIBARCHIVE_ENABLED)
764 else if ( useArchive() ) {
765 // try loading image from (semicolon-separated) archive(s)
766 QByteArray imageData;
767 foreach (int format, activeFormats) {
768 QString formatName(formatNames.value(format));
769 foreach (QString extension, formatExtensions.value(format).split(", ", QString::SkipEmptyParts)) {
770 QString machineFile(machineName + '.' + extension);
771 if ( fileName )
772 *fileName = machineFile;
773 if ( archiveFile == 0 ) {
774 foreach (ArchiveFile *imageFile, imageArchiveMap) {
775 if ( imageFile->seekEntry(machineFile) )
776 fileOk = imageFile->readEntry(imageData) > 0;
777 else
778 fileOk = false;
779 if ( fileOk )
780 break;
781 }
782 } else {
783 if ( archiveFile->seekEntry(machineFile) )
784 fileOk = archiveFile->readEntry(imageData) > 0;
785 else
786 fileOk = false;
787 }
788 if ( fileOk ) {
789 QBuffer buffer(&imageData);
790 QImageReader imageReader(&buffer, formatName.toUtf8().constData());
791 fileOk = imageReader.read(&image);
792 if ( fileOk ) {
793 if ( sizeReturn )
794 *sizeReturn = image.size();
795 if ( bytesUsed )
796 *bytesUsed = image.byteCount();
797 } else if ( readerError != 0 && imageReader.error() != QImageReader::FileNotFoundError )
798 *readerError = imageReader.errorString();
799 }
800 if ( fileOk )
801 break;
802 }
803 if ( fileOk )
804 break;
805 }
806 }
807 #endif
808 else {
809 // try loading image from (semicolon-separated) folder(s)
810 foreach (QString baseDirectory, imageDir().split(';', QString::SkipEmptyParts)) {
811 QString imgDir(baseDirectory + machineName);
812 foreach (int format, activeFormats) {
813 QString formatName(formatNames.value(format));
814 foreach (QString extension, formatExtensions.value(format).split(", ", QString::SkipEmptyParts)) {
815 QString localImagePath(imgDir + '.' + extension);
816 if ( fileName )
817 *fileName = QDir::toNativeSeparators(localImagePath);
818 QImageReader imageReader(localImagePath, formatName.toUtf8().constData());
819 fileOk = imageReader.read(&image);
820 if ( fileOk ) {
821 if ( sizeReturn )
822 *sizeReturn = image.size();
823 if ( bytesUsed )
824 *bytesUsed = image.byteCount();
825 break;
826 } else if ( readerError != 0 && imageReader.error() != QImageReader::FileNotFoundError )
827 *readerError = imageReader.errorString();
828 if ( fileOk )
829 break;
830 }
831 if ( fileOk )
832 break;
833 }
834 if ( fileOk )
835 break;
836 }
837 }
838 return fileOk;
839 }
840
drawCenteredImage(QPixmap * pm,QPainter * p)841 void ImageWidget::drawCenteredImage(QPixmap *pm, QPainter *p)
842 {
843 p->eraseRect(rect());
844
845 if ( pm == 0 ) {
846 p->end();
847 return;
848 }
849
850 // last resort if pm->load() retrieved a null pixmap...
851 if ( pm->isNull() )
852 pm = &qmc2MainWindow->qmc2GhostImagePixmap;
853
854 int posx = (rect().width() - pm->width()) / 2;
855 int posy = (rect().height() - pm->height()) / 2;
856
857 p->drawPixmap(posx, posy, *pm);
858
859 bool drawMachineName = false;
860 if ( qmc2ShowMachineName ) {
861 if ( qmc2ShowMachineNameOnlyWhenRequired ) {
862 if ( qmc2MainWindow->hSplitter->sizes()[0] == 0 || qmc2MainWindow->tabWidgetMachineList->currentIndex() != QMC2_MACHINELIST_INDEX ) {
863 drawMachineName = true;
864 } else {
865 drawMachineName = false;
866 }
867 } else
868 drawMachineName = true;
869 } else
870 drawMachineName = false;
871
872 if ( drawMachineName ) {
873 // draw game/machine title
874 p->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::HighQualityAntialiasing | QPainter::SmoothPixmapTransform);
875 QString title = qmc2CurrentItem->text(QMC2_MACHINELIST_COLUMN_MACHINE);
876 QFont f(qApp->font());
877 f.setWeight(QFont::Bold);
878 p->setFont(f);
879 QFontMetrics fm(f);
880 QRect r = rect();
881 int adjustment = fm.height() / 2;
882 r = r.adjusted(+adjustment, +adjustment, -adjustment, -adjustment);
883 QRect outerRect = p->boundingRect(r, Qt::AlignCenter | Qt::TextWordWrap, title);
884 r.setTop(r.bottom() - outerRect.height());
885 r = p->boundingRect(r, Qt::AlignCenter | Qt::TextWordWrap, title);
886 r = r.adjusted(-adjustment, -adjustment, +adjustment, +adjustment);
887 r.setBottom(rect().bottom());
888 QPainterPath pp;
889 pp.addRoundedRect(r, 5, 5);
890 p->fillPath(pp, QBrush(QColor(0, 0, 0, 128), Qt::SolidPattern));
891 p->setPen(QPen(QColor(255, 255, 255, 255)));
892 p->drawText(r, Qt::AlignCenter | Qt::TextWordWrap, title);
893 }
894
895 p->end();
896 }
897
drawScaledImage(QPixmap * pm,QPainter * p)898 void ImageWidget::drawScaledImage(QPixmap *pm, QPainter *p)
899 {
900 if ( pm == 0 ) {
901 p->eraseRect(rect());
902 p->end();
903 return;
904 }
905
906 // last resort if pm->load() retrieved a null pixmap...
907 if ( pm->isNull() )
908 pm = &qmc2MainWindow->qmc2GhostImagePixmap;
909
910 double desired_width;
911 double desired_height;
912
913 if ( pm->width() > pm->height() ) {
914 desired_width = contentsRect().width();
915 desired_height = (double)pm->height() * (desired_width / (double)pm->width());
916 if ( desired_height > contentsRect().height() ) {
917 desired_height = contentsRect().height();
918 desired_width = (double)pm->width() * (desired_height / (double)pm->height());
919 }
920 } else {
921 desired_height = contentsRect().height();
922 desired_width = (double)pm->width() * (desired_height / (double)pm->height());
923 if ( desired_width > contentsRect().width() ) {
924 desired_width = contentsRect().width();
925 desired_height = (double)pm->height() * (desired_width / (double)pm->width());
926 }
927 }
928
929 QPixmap pmScaled;
930
931 if ( qmc2SmoothScaling )
932 pmScaled = pm->scaled((int)desired_width, (int)desired_height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
933 else
934 pmScaled = pm->scaled((int)desired_width, (int)desired_height, Qt::KeepAspectRatio, Qt::FastTransformation);
935
936 drawCenteredImage(&pmScaled, p);
937 }
938
copyToClipboard()939 void ImageWidget::copyToClipboard()
940 {
941 if ( !currentPixmap.isNull() )
942 qApp->clipboard()->setPixmap(currentPixmap);
943 }
944
copyPathToClipboard()945 void ImageWidget::copyPathToClipboard()
946 {
947 if ( !absoluteImagePath().isEmpty() )
948 qApp->clipboard()->setText(absoluteImagePath());
949 }
950
contextMenuEvent(QContextMenuEvent * e)951 void ImageWidget::contextMenuEvent(QContextMenuEvent *e)
952 {
953 actionCopyPathToClipboard->setVisible(!absoluteImagePath().isEmpty());
954 contextMenu->move(qmc2MainWindow->adjustedWidgetPosition(mapToGlobal(e->pos()), contextMenu));
955 contextMenu->show();
956 }
957
toBase64()958 QString ImageWidget::toBase64()
959 {
960 ImagePixmap pm;
961 if ( !currentPixmap.isNull() )
962 pm = currentPixmap;
963 else
964 pm = qmc2MainWindow->qmc2GhostImagePixmap;
965 QByteArray imageData;
966 QBuffer buffer(&imageData);
967 pm.save(&buffer, "PNG");
968 return QString(imageData.toBase64());
969 }
970