diff options
-rw-r--r-- | desktop-widgets/divepicturewidget.cpp | 14 | ||||
-rw-r--r-- | desktop-widgets/divepicturewidget.h | 2 | ||||
-rw-r--r-- | desktop-widgets/tab-widgets/TabDivePhotos.cpp | 9 | ||||
-rw-r--r-- | desktop-widgets/tab-widgets/TabDivePhotos.h | 1 | ||||
-rw-r--r-- | desktop-widgets/tab-widgets/TabDivePhotos.ui | 36 | ||||
-rw-r--r-- | profile-widget/profilewidget2.cpp | 2 | ||||
-rw-r--r-- | qt-models/divepicturemodel.cpp | 68 | ||||
-rw-r--r-- | qt-models/divepicturemodel.h | 5 |
8 files changed, 120 insertions, 17 deletions
diff --git a/desktop-widgets/divepicturewidget.cpp b/desktop-widgets/divepicturewidget.cpp index a6864a5d9..46de16d09 100644 --- a/desktop-widgets/divepicturewidget.cpp +++ b/desktop-widgets/divepicturewidget.cpp @@ -59,3 +59,17 @@ void DivePictureWidget::mousePressEvent(QMouseEvent *event) QListView::mousePressEvent(event); } } + +void DivePictureWidget::wheelEvent(QWheelEvent *event) +{ + if (event->modifiers() == Qt::ControlModifier) { + // Angle delta is given in eighth parts of a degree. A classical mouse + // wheel click is 15 degrees. Each click should correspond to one zoom step. + // Therefore, divide by 15*8=120. To also support touch pads and finer-grained + // mouse wheels, take care to always round away from zero. + int delta = event->angleDelta().y(); + int carry = delta > 0 ? 119 : -119; + emit zoomLevelChanged((delta + carry) / 120); + } else + QListView::wheelEvent(event); +} diff --git a/desktop-widgets/divepicturewidget.h b/desktop-widgets/divepicturewidget.h index 09d4608c4..bf48a9d1b 100644 --- a/desktop-widgets/divepicturewidget.h +++ b/desktop-widgets/divepicturewidget.h @@ -14,9 +14,11 @@ public: protected: void mouseDoubleClickEvent(QMouseEvent *event) Q_DECL_OVERRIDE; void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; + void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE; signals: void photoDoubleClicked(const QString filePath); + void zoomLevelChanged(int delta); }; class DivePictureThumbnailThread : public QThread { diff --git a/desktop-widgets/tab-widgets/TabDivePhotos.cpp b/desktop-widgets/tab-widgets/TabDivePhotos.cpp index 86749e770..571e0a3ac 100644 --- a/desktop-widgets/tab-widgets/TabDivePhotos.cpp +++ b/desktop-widgets/tab-widgets/TabDivePhotos.cpp @@ -29,6 +29,10 @@ TabDivePhotos::TabDivePhotos(QWidget *parent) QDesktopServices::openUrl(QUrl::fromLocalFile(path)); } ); + connect(ui->photosView, &DivePictureWidget::zoomLevelChanged, + this, &TabDivePhotos::changeZoomLevel); + connect(ui->zoomSlider, &QAbstractSlider::valueChanged, + DivePictureModel::instance(), &DivePictureModel::setZoomLevel); } TabDivePhotos::~TabDivePhotos() @@ -98,3 +102,8 @@ void TabDivePhotos::updateData() divePictureModel->updateDivePictures(); } +void TabDivePhotos::changeZoomLevel(int delta) +{ + // We count on QSlider doing bound checks + ui->zoomSlider->setValue(ui->zoomSlider->value() + delta); +} diff --git a/desktop-widgets/tab-widgets/TabDivePhotos.h b/desktop-widgets/tab-widgets/TabDivePhotos.h index 9b711595c..e2e7aa05c 100644 --- a/desktop-widgets/tab-widgets/TabDivePhotos.h +++ b/desktop-widgets/tab-widgets/TabDivePhotos.h @@ -26,6 +26,7 @@ private slots: void addPhotosFromURL(); void removeAllPhotos(); void removeSelectedPhotos(); + void changeZoomLevel(int delta); private: Ui::TabDivePhotos *ui; diff --git a/desktop-widgets/tab-widgets/TabDivePhotos.ui b/desktop-widgets/tab-widgets/TabDivePhotos.ui index 35cfd375a..21e4544e6 100644 --- a/desktop-widgets/tab-widgets/TabDivePhotos.ui +++ b/desktop-widgets/tab-widgets/TabDivePhotos.ui @@ -21,6 +21,42 @@ </property> </widget> </item> + <item> + <layout class="QHBoxLayout" name="zoomLayout"> + <item> + <widget class="QLabel" name="zoomLabel"> + <property name="text"> + <string>Zoom level</string> + </property> + </widget> + </item> + <item> + <widget class="QSlider" name="zoomSlider"> + <property name="minimum"> + <number>-10</number> + </property> + <property name="maximum"> + <number>10</number> + </property> + <property name="singleStep"> + <number>1</number> + </property> + <property name="pageStep"> + <number>1</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="tickPosition"> + <enum>QSlider::TicksBelow</enum> + </property> + <property name="tickInterval"> + <number>1</number> + </property> + </widget> + </item> + </layout> + </item> </layout> </widget> <customwidgets> diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index b7b6057fe..55eb40000 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -2007,7 +2007,7 @@ void ProfileWidget2::plotPictures() if (!offsetSeconds) continue; DivePictureItem *item = new DivePictureItem(); - item->setPixmap(m->index(i, 0).data(Qt::DecorationRole).value<QPixmap>()); + item->setPixmap(m->index(i, 0).data(Qt::UserRole).value<QPixmap>()); item->setFileUrl(m->index(i, 1).data().toString()); // let's put the picture at the correct time, but at a fixed "depth" on the profile // not sure this is ideal, but it seems to look right. diff --git a/qt-models/divepicturemodel.cpp b/qt-models/divepicturemodel.cpp index ff4e187ae..47e64492b 100644 --- a/qt-models/divepicturemodel.cpp +++ b/qt-models/divepicturemodel.cpp @@ -9,25 +9,28 @@ extern QHash <QString, QImage> thumbnailCache; static QMutex thumbnailMutex; +static const int maxZoom = 3; // Maximum zoom: thrice of standard size -static void scaleImages(PictureEntry &entry) +static QImage getThumbnailFromCache(const PictureEntry &entry) { QMutexLocker l(&thumbnailMutex); - if (thumbnailCache.contains(entry.filename) && !thumbnailCache.value(entry.filename).isNull()) { - entry.image = thumbnailCache.value(entry.filename); - return; - } - l.unlock(); + return thumbnailCache.value(entry.filename); +} - int dim = defaultIconMetrics().sz_pic; - QImage p = SHashedImage(entry.picture); - if(!p.isNull()) { - p = p.scaled(dim, dim, Qt::KeepAspectRatio); +static void scaleImages(PictureEntry &entry, int size, int maxSize) +{ + QImage thumbnail = getThumbnailFromCache(entry); + // If thumbnails were written by an earlier version, they might be smaller than needed. + // Rescale in such a case to avoid resizing artifacts. + if (thumbnail.isNull() || (thumbnail.size().width() < maxSize && thumbnail.size().height() < maxSize)) { + thumbnail = SHashedImage(entry.picture).scaled(maxSize, maxSize, Qt::KeepAspectRatio); QMutexLocker l(&thumbnailMutex); - if (!thumbnailCache.contains(entry.filename)) - thumbnailCache.insert(entry.filename, p); + thumbnailCache.insert(entry.filename, thumbnail); } - entry.image = p; + + entry.imageProfile = thumbnail.scaled(maxSize / maxZoom, maxSize / maxZoom, Qt::KeepAspectRatio); + entry.image = size == maxSize ? thumbnail + : thumbnail.scaled(size, size, Qt::KeepAspectRatio); } DivePictureModel *DivePictureModel::instance() @@ -36,7 +39,9 @@ DivePictureModel *DivePictureModel::instance() return self; } -DivePictureModel::DivePictureModel() : rowDDStart(0), rowDDEnd(0) +DivePictureModel::DivePictureModel() : rowDDStart(0), + rowDDEnd(0), + zoomLevel(0.0) { } @@ -48,6 +53,33 @@ void DivePictureModel::updateDivePicturesWhenDone(QList<QFuture<void>> futures) updateDivePictures(); } +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; + updateThumbnails(); + layoutChanged(); +} + +void DivePictureModel::updateThumbnails() +{ + // Calculate size of thumbnails. The standard size is defaultIconMetrics().sz_pic. + // We use exponential scaling so that the central point is the standard + // size and the minimum and maximum extreme points are a third respectively + // three times the standard size. + // Naturally, these three zoom levels are then represented by + // -1.0 (minimum), 0 (standard) and 1.0 (maximum). The actual size is + // calculated as standard_size*3.0^zoomLevel. + int defaultSize = defaultIconMetrics().sz_pic; + int maxSize = defaultSize * maxZoom; + int size = static_cast<int>(round(defaultSize * pow(maxZoom, zoomLevel))); + QtConcurrent::blockingMap(pictures, [size, maxSize](PictureEntry &entry){scaleImages(entry, size, maxSize);}); +} + void DivePictureModel::updateDivePictures() { if (!pictures.isEmpty()) { @@ -68,12 +100,13 @@ void DivePictureModel::updateDivePictures() if (dive->id == displayed_dive.id) rowDDStart = pictures.count(); FOR_EACH_PICTURE(dive) - pictures.push_back({picture, picture->filename, QImage(), picture->offset.seconds}); + pictures.push_back({picture, picture->filename, {}, {}, picture->offset.seconds}); if (dive->id == displayed_dive.id) rowDDEnd = pictures.count(); } } - QtConcurrent::blockingMap(pictures, scaleImages); + + updateThumbnails(); beginInsertRows(QModelIndex(), 0, pictures.count() - 1); endInsertRows(); @@ -100,6 +133,9 @@ QVariant DivePictureModel::data(const QModelIndex &index, int role) const case Qt::DecorationRole: ret = entry.image; break; + case Qt::UserRole: // Used by profile widget to access bigger thumbnails + ret = entry.imageProfile; + break; case Qt::DisplayRole: ret = QFileInfo(entry.filename).fileName(); break; diff --git a/qt-models/divepicturemodel.h b/qt-models/divepicturemodel.h index 53a72076a..74f92f449 100644 --- a/qt-models/divepicturemodel.h +++ b/qt-models/divepicturemodel.h @@ -10,6 +10,7 @@ struct PictureEntry { struct picture *picture; QString filename; QImage image; + QImage imageProfile; // For the profile widget keep a copy of a constant sized image int offsetSeconds; }; @@ -24,9 +25,13 @@ public: void updateDivePicturesWhenDone(QList<QFuture<void>>); void removePicture(const QString& fileUrl, bool last); int rowDDStart, rowDDEnd; +public slots: + void setZoomLevel(int level); private: DivePictureModel(); QList<PictureEntry> pictures; + double zoomLevel; // -1.0: minimum, 0.0: standard, 1.0: maximum + void updateThumbnails(); }; #endif |