mirror of
				https://github.com/subsurface/subsurface.git
				synced 2025-02-19 22:16:15 +00:00 
			
		
		
		
	On the profile, the run-length of the videos is visualized by a bar. Add the same information to video-thumbnails in the dive-photo-tab. Though in this case, render it as text on top of the thumbnails. Fixes #359 Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
		
			
				
	
	
		
			242 lines
		
	
	
	
		
			6.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			242 lines
		
	
	
	
		
			6.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| #include "qt-models/divepicturemodel.h"
 | |
| #include "core/dive.h"
 | |
| #include "core/metrics.h"
 | |
| #include "core/divelist.h"
 | |
| #include "core/imagedownloader.h"
 | |
| #include "core/qthelper.h"
 | |
| 
 | |
| #include <QFileInfo>
 | |
| #include <QPainter>
 | |
| 
 | |
| DivePictureModel *DivePictureModel::instance()
 | |
| {
 | |
| 	static DivePictureModel *self = new DivePictureModel();
 | |
| 	return self;
 | |
| }
 | |
| 
 | |
| DivePictureModel::DivePictureModel() : zoomLevel(0.0)
 | |
| {
 | |
| 	connect(Thumbnailer::instance(), &Thumbnailer::thumbnailChanged,
 | |
| 		this, &DivePictureModel::updateThumbnail, Qt::QueuedConnection);
 | |
| }
 | |
| 
 | |
| void DivePictureModel::setZoomLevel(int level)
 | |
| {
 | |
| 	zoomLevel = level / 10.0;
 | |
| 	// zoomLevel is bound by [-1.0 1.0], see comment below.
 | |
| 	if (zoomLevel < -1.0)
 | |
| 		zoomLevel = -1.0;
 | |
| 	if (zoomLevel > 1.0)
 | |
| 		zoomLevel = 1.0;
 | |
| 	updateZoom();
 | |
| 	layoutChanged();
 | |
| }
 | |
| 
 | |
| void DivePictureModel::updateZoom()
 | |
| {
 | |
| 	size = Thumbnailer::thumbnailSize(zoomLevel);
 | |
| }
 | |
| 
 | |
| void DivePictureModel::updateThumbnails()
 | |
| {
 | |
| 	updateZoom();
 | |
| 	for (PictureEntry &entry: pictures)
 | |
| 		entry.image = Thumbnailer::instance()->fetchThumbnail(entry.filename);
 | |
| }
 | |
| 
 | |
| void DivePictureModel::updateDivePictures()
 | |
| {
 | |
| 	beginResetModel();
 | |
| 	if (!pictures.isEmpty()) {
 | |
| 		pictures.clear();
 | |
| 		Thumbnailer::instance()->clearWorkQueue();
 | |
| 	}
 | |
| 
 | |
| 	int i;
 | |
| 	struct dive *dive;
 | |
| 	for_each_dive (i, dive) {
 | |
| 		if (dive->selected) {
 | |
| 			int first = pictures.count();
 | |
| 			FOR_EACH_PICTURE(dive)
 | |
| 				pictures.push_back({ dive->id, picture, picture->filename, {}, picture->offset.seconds });
 | |
| 
 | |
| 			// Sort pictures of this dive by offset.
 | |
| 			// Thus, the list will be sorted by (diveId, offset).
 | |
| 			std::sort(pictures.begin() + first, pictures.end(),
 | |
| 				  [](const PictureEntry &a, const PictureEntry &b) { return a.offsetSeconds < b.offsetSeconds; });
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	updateThumbnails();
 | |
| 	endResetModel();
 | |
| }
 | |
| 
 | |
| int DivePictureModel::columnCount(const QModelIndex&) const
 | |
| {
 | |
| 	return 2;
 | |
| }
 | |
| 
 | |
| QVariant DivePictureModel::data(const QModelIndex &index, int role) const
 | |
| {
 | |
| 	QVariant ret;
 | |
| 	if (!index.isValid())
 | |
| 		return ret;
 | |
| 
 | |
| 	const PictureEntry &entry = pictures.at(index.row());
 | |
| 	if (index.column() == 0) {
 | |
| 		switch (role) {
 | |
| 		case Qt::ToolTipRole:
 | |
| 			ret = entry.filename;
 | |
| 			break;
 | |
| 		case Qt::DecorationRole:
 | |
| 			ret = entry.image.scaled(size, size, Qt::KeepAspectRatio);
 | |
| 			break;
 | |
| 		case Qt::DisplayRole:
 | |
| 			ret = QFileInfo(entry.filename).fileName();
 | |
| 			break;
 | |
| 		case Qt::DisplayPropertyRole:
 | |
| 			ret = QFileInfo(entry.filename).filePath();
 | |
| 			break;
 | |
| 		case Qt::UserRole:
 | |
| 			ret = entry.diveId;
 | |
| 			break;
 | |
| 		}
 | |
| 	} else if (index.column() == 1) {
 | |
| 		switch (role) {
 | |
| 		case Qt::DisplayRole:
 | |
| 			ret = entry.filename;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| // Return true if we actually removed a picture
 | |
| static bool removePictureFromSelectedDive(const char *fileUrl)
 | |
| {
 | |
| 	int i;
 | |
| 	struct dive *dive;
 | |
| 	for_each_dive (i, dive) {
 | |
| 		if (dive->selected && dive_remove_picture(dive, fileUrl))
 | |
| 			return true;
 | |
| 	}
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| void DivePictureModel::removePictures(const QVector<QString> &fileUrls)
 | |
| {
 | |
| 	bool removed = false;
 | |
| 	for (const QString &fileUrl: fileUrls)
 | |
| 		removed |= removePictureFromSelectedDive(qPrintable(fileUrl));
 | |
| 	if (!removed)
 | |
| 		return;
 | |
| 	copy_dive(current_dive, &displayed_dive);
 | |
| 	mark_divelist_changed(true);
 | |
| 
 | |
| 	for (int i = 0; i < pictures.size(); ++i) {
 | |
| 		// Find range [i j) of pictures to remove
 | |
| 		if (std::find(fileUrls.begin(), fileUrls.end(), pictures[i].filename) == fileUrls.end())
 | |
| 			continue;
 | |
| 		int j;
 | |
| 		for (j = i + 1; j < pictures.size(); ++j) {
 | |
| 			if (std::find(fileUrls.begin(), fileUrls.end(), pictures[j].filename) == fileUrls.end())
 | |
| 				break;
 | |
| 		}
 | |
| 
 | |
| 		// Qt's model-interface is surprisingly idiosyncratic: you don't pass [first last), but [first last] ranges.
 | |
| 		// For example, an empty list would be [0 -1].
 | |
| 		beginRemoveRows(QModelIndex(), i, j - 1);
 | |
| 		pictures.erase(pictures.begin() + i, pictures.begin() + j);
 | |
| 		endRemoveRows();
 | |
| 	}
 | |
| 	emit picturesRemoved(fileUrls);
 | |
| }
 | |
| 
 | |
| int DivePictureModel::rowCount(const QModelIndex&) const
 | |
| {
 | |
| 	return pictures.count();
 | |
| }
 | |
| 
 | |
| int DivePictureModel::findPictureId(const QString &filename)
 | |
| {
 | |
| 	for (int i = 0; i < pictures.size(); ++i)
 | |
| 		if (pictures[i].filename == filename)
 | |
| 			return i;
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| static void addDurationToThumbnail(QImage &img, duration_t duration)
 | |
| {
 | |
| 	int seconds = duration.seconds;
 | |
| 	if (seconds < 0)
 | |
| 		return;
 | |
| 	QString s = seconds >= 3600 ?
 | |
| 		QStringLiteral("%1:%2:%3").arg(seconds / 3600, 2, 10, QChar('0'))
 | |
| 					  .arg((seconds % 3600) / 60, 2, 10, QChar('0'))
 | |
| 					  .arg(seconds % 60, 2, 10, QChar('0')) :
 | |
| 		QStringLiteral("%1:%2").arg(seconds / 60, 2, 10, QChar('0'))
 | |
| 				       .arg(seconds % 60, 2, 10, QChar('0'));
 | |
| 
 | |
| 	QFont font(system_divelist_default_font, 30);
 | |
| 	QFontMetrics metrics(font);
 | |
| 	QSize size = metrics.size(Qt::TextSingleLine, s);
 | |
| 	QSize imgSize = img.size();
 | |
| 	int x = imgSize.width() - size.width();
 | |
| 	int y = imgSize.height() - size.height() + metrics.descent();
 | |
| 	QPainter painter(&img);
 | |
| 	painter.setBrush(Qt::white);
 | |
| 	painter.setPen(Qt::NoPen);
 | |
| 	painter.drawRect(x, y, size.width(), size.height() - metrics.descent());
 | |
| 	painter.setFont(font);
 | |
| 	painter.setPen(Qt::black);
 | |
| 	painter.drawText(x, imgSize.height(), s);
 | |
| }
 | |
| 
 | |
| void DivePictureModel::updateThumbnail(QString filename, QImage thumbnail, duration_t duration)
 | |
| {
 | |
| 	int i = findPictureId(filename);
 | |
| 	if (i >= 0) {
 | |
| 		if (duration.seconds > 0)
 | |
| 			addDurationToThumbnail(thumbnail, duration);	// If we know the duration paint it on top of the thumbnail
 | |
| 		pictures[i].image = thumbnail;
 | |
| 		emit dataChanged(createIndex(i, 0), createIndex(i, 1));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void DivePictureModel::updateDivePictureOffset(int diveId, const QString &filename, int offsetSeconds)
 | |
| {
 | |
| 	// Find the pictures of the given dive.
 | |
| 	auto from = std::find_if(pictures.begin(), pictures.end(), [diveId](const PictureEntry &e) { return e.diveId == diveId; });
 | |
| 	auto to = std::find_if(from, pictures.end(), [diveId](const PictureEntry &e) { return e.diveId != diveId; });
 | |
| 
 | |
| 	// Find picture with the given filename
 | |
| 	auto oldPos = std::find_if(from, to, [filename](const PictureEntry &e) { return e.filename == filename; });
 | |
| 	if (oldPos == to)
 | |
| 		return;
 | |
| 
 | |
| 	// Find new position
 | |
| 	auto newPos = std::find_if(from, to, [offsetSeconds](const PictureEntry &e) { return e.offsetSeconds > offsetSeconds; });
 | |
| 
 | |
| 	// Update the offset here and in the backend
 | |
| 	oldPos->offsetSeconds = offsetSeconds;
 | |
| 	if (struct dive *dive = get_dive_by_uniq_id(diveId)) {
 | |
| 		FOR_EACH_PICTURE(dive) {
 | |
| 			if (picture->filename == filename) {
 | |
| 				picture->offset.seconds = offsetSeconds;
 | |
| 				mark_divelist_changed(true);
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		copy_dive(current_dive, &displayed_dive);
 | |
| 	}
 | |
| 
 | |
| 	// Henceforth we will work with indices instead of iterators
 | |
| 	int oldIndex = oldPos - pictures.begin();
 | |
| 	int newIndex = newPos - pictures.begin();
 | |
| 	if (oldIndex == newIndex || oldIndex + 1 == newIndex)
 | |
| 		return;
 | |
| 	beginMoveRows(QModelIndex(), oldIndex, oldIndex, QModelIndex(), newIndex);
 | |
| 	moveInVector(pictures, oldIndex, oldIndex + 1, newIndex);
 | |
| 	endMoveRows();
 | |
| }
 |