mirror of
https://github.com/subsurface/subsurface.git
synced 2024-11-30 14:10:19 +00:00
a76433279e
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();
|
|
}
|