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(&currentPixmap, &p);
240 	else
241 		drawCenteredImage(&currentPixmap, &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(&currentPixmap);
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