From bf23aaecbce0deb2c0fbb72b1da4ba0580667744 Mon Sep 17 00:00:00 2001 From: "Robert C. Helling" Date: Fri, 24 Apr 2015 17:10:55 +0200 Subject: [PATCH 3/3] Allow images to be added via the web This adds a new divelist context menu entry which asks for a URL. The file is retrieved and if it is an image it is added to the cache and the url is associated to dives as with local files. NB this currently only works with URLs pointing directly to images. But it should not be too hard to add the possibility to add a direction via an html file and its image tags. To test: open dives/test43.xml and delete the image and then add the URL http://euve10195.vserver.de/~robert/wreck.jpg Signed-off-by: Robert C. Helling --- dive.c | 8 ++-- dive.h | 2 +- qt-ui/divelistview.cpp | 67 ++++++++++++++++++++++++++++++++- qt-ui/divelistview.h | 5 +++ qt-ui/divepicturewidget.cpp | 4 ++ qt-ui/simplewidgets.cpp | 14 +++++++ qt-ui/simplewidgets.h | 10 +++++ qt-ui/urldialog.ui | 91 +++++++++++++++++++++++++++++++++++++++++++++ qthelper.cpp | 44 ++++++++++++---------- 9 files changed, 219 insertions(+), 26 deletions(-) create mode 100644 qt-ui/urldialog.ui diff --git a/dive.c b/dive.c index b8adaac..ea3dbb4 100644 --- a/dive.c +++ b/dive.c @@ -2885,7 +2885,7 @@ static bool new_picture_for_dive(struct dive *d, char *filename) // only add pictures that have timestamps between 30 minutes before the dive and // 30 minutes after the dive ends #define D30MIN (30 * 60) -bool dive_check_picture_time(struct dive *d, char *filename, int shift_time, timestamp_t timestamp) +bool dive_check_picture_time(struct dive *d, int shift_time, timestamp_t timestamp) { offset_t offset; if (timestamp) { @@ -2905,7 +2905,7 @@ bool picture_check_valid(char *filename, int shift_time) timestamp_t timestamp = picture_get_timestamp(filename); for_each_dive (i, dive) - if (dive->selected && dive_check_picture_time(dive, filename, shift_time, timestamp)) + if (dive->selected && dive_check_picture_time(dive, shift_time, timestamp)) return true; return false; } @@ -2915,11 +2915,11 @@ void dive_create_picture(struct dive *dive, char *filename, int shift_time) timestamp_t timestamp = picture_get_timestamp(filename); if (!new_picture_for_dive(dive, filename)) return; - if (!dive_check_picture_time(dive, filename, shift_time, timestamp)) + if (!dive_check_picture_time(dive, shift_time, timestamp)) return; struct picture *picture = alloc_picture(); - picture->filename = filename; + picture->filename = strdup(filename); picture->offset.seconds = timestamp - dive->when + shift_time; picture_load_exif_data(picture); diff --git a/dive.h b/dive.h index e418e0e..b90947d 100644 --- a/dive.h +++ b/dive.h @@ -382,7 +382,7 @@ struct picture { for (struct picture *picture = (_divestruct).picture_list; picture; picture = picture->next) extern struct picture *alloc_picture(); -extern bool dive_check_picture_time(struct dive *d, char *filename, int shift_time, timestamp_t timestamp); +extern bool dive_check_picture_time(struct dive *d, int shift_time, timestamp_t timestamp); extern void dive_create_picture(struct dive *d, char *filename, int shift_time); extern void dive_add_picture(struct dive *d, struct picture *newpic); extern void dive_remove_picture(char *filename); diff --git a/qt-ui/divelistview.cpp b/qt-ui/divelistview.cpp index 92ae60b..fda170c 100644 --- a/qt-ui/divelistview.cpp +++ b/qt-ui/divelistview.cpp @@ -4,17 +4,21 @@ * classes for the divelist of Subsurface * */ -#include "divelistview.h" #include "filtermodels.h" #include "modeldelegates.h" #include "mainwindow.h" #include "divepicturewidget.h" #include "display.h" +#include #include #include #include +#include +#include +#include #include "qthelper.h" #include "undocommands.h" +#include "divelistview.h" // # Date Rtg Dpth Dur Tmp Wght Suit Cyl Gas SAC OTU CNS Loc static int defaultWidth[] = { 70, 140, 90, 50, 50, 50, 50, 70, 50, 50, 70, 50, 50, 500}; @@ -834,6 +838,7 @@ void DiveListView::contextMenuEvent(QContextMenuEvent *event) popup.addAction(tr("Renumber dive(s)"), this, SLOT(renumberDives())); popup.addAction(tr("Shift times"), this, SLOT(shiftTimes())); popup.addAction(tr("Load images"), this, SLOT(loadImages())); + popup.addAction(tr("Load image(s) from web"), this, SLOT(loadWebImages())); } // "collapse all" really closes all trips, @@ -859,8 +864,12 @@ void DiveListView::loadImages() QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Open image files"), lastUsedImageDir(), tr("Image files (*.jpg *.jpeg *.pnm *.tif *.tiff)")); if (fileNames.isEmpty()) return; - updateLastUsedImageDir(QFileInfo(fileNames[0]).dir().path()); + matchImagesToDives(fileNames); +} + +void DiveListView::matchImagesToDives(QStringList fileNames) +{ ShiftImageTimesDialog shiftDialog(this, fileNames); shiftDialog.setOffset(lastImageTimeOffset()); if (!shiftDialog.exec()) @@ -882,6 +891,60 @@ void DiveListView::loadImages() DivePictureModel::instance()->updateDivePictures(); } +void DiveListView::loadWebImages() +{ + URLDialog urlDialog(this); + if (!urlDialog.exec()) + return; + loadImageFromURL(QUrl::fromUserInput(urlDialog.url())); + +} + +void DiveListView::loadImageFromURL(QUrl url) +{ + if (url.isValid()) { + QEventLoop loop; + QNetworkRequest request(url); + QNetworkReply *reply = manager.get(request); + while (reply->isRunning()) { + loop.processEvents(); + sleep(1); + } + QByteArray imageData = reply->readAll(); + + QImage image = QImage(); + image.loadFromData(imageData); + if (image.isNull()) + // If this is not an image, maybe it's an html file and Miika can provide some xslr magic to extract images. + // In this case we would call the function recursively on the list of image source urls; + return; + + // Since we already downloaded the image we can cache it as well. + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(imageData); + QString path = QStandardPaths::standardLocations(QStandardPaths::CacheLocation).first(); + QDir dir(path); + if (!dir.exists()) + dir.mkpath(path); + QFile imageFile(path.append("/").append(hash.result().toHex())); + if (imageFile.open(QIODevice::WriteOnly)) { + QDataStream stream(&imageFile); + stream.writeRawData(imageData.data(), imageData.length()); + imageFile.waitForBytesWritten(-1); + imageFile.close(); + add_hash(imageFile.fileName(), hash.result()); + struct picture picture; + picture.hash = NULL; + picture.filename = strdup(url.toString().toUtf8().data()); + learnHash(&picture, hash.result()); + matchImagesToDives(QStringList(url.toString())); + } + } + + +} + + QString DiveListView::lastUsedImageDir() { QSettings settings; diff --git a/qt-ui/divelistview.h b/qt-ui/divelistview.h index a6522fa..6e9a182 100644 --- a/qt-ui/divelistview.h +++ b/qt-ui/divelistview.h @@ -13,6 +13,7 @@ #include #include +#include #include "models.h" class DiveListView : public QTreeView { @@ -51,6 +52,7 @@ slots: void renumberDives(); void shiftTimes(); void loadImages(); + void loadWebImages(); static QString lastUsedImageDir(); signals: @@ -78,6 +80,9 @@ private: void updateLastImageTimeOffset(int offset); int lastImageTimeOffset(); void addToTrip(int delta); + void matchImagesToDives(QStringList fileNames); + void loadImageFromURL(QUrl url); + QNetworkAccessManager manager; }; #endif // DIVELISTVIEW_H diff --git a/qt-ui/divepicturewidget.cpp b/qt-ui/divepicturewidget.cpp index ce997fe..8c1cb35 100644 --- a/qt-ui/divepicturewidget.cpp +++ b/qt-ui/divepicturewidget.cpp @@ -63,6 +63,10 @@ void ImageDownloader::load(){ void ImageDownloader::saveImage(QNetworkReply *reply) { QByteArray imageData = reply->readAll(); + QImage image = QImage(); + image.loadFromData(imageData); + if (image.isNull()) + return; QCryptographicHash hash(QCryptographicHash::Sha1); hash.addData(imageData); QString path = QStandardPaths::standardLocations(QStandardPaths::CacheLocation).first(); diff --git a/qt-ui/simplewidgets.cpp b/qt-ui/simplewidgets.cpp index 2b8605a..a074ad1 100644 --- a/qt-ui/simplewidgets.cpp +++ b/qt-ui/simplewidgets.cpp @@ -361,6 +361,20 @@ void ShiftImageTimesDialog::timeEditChanged(const QTime &time) updateInvalid(); } +URLDialog::URLDialog(QWidget *parent) : QDialog(parent) +{ + ui.setupUi(this); + QShortcut *close = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_W), this); + connect(close, SIGNAL(activated()), this, SLOT(close())); + QShortcut *quit = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), this); + connect(quit, SIGNAL(activated()), parent, SLOT(close())); +} + +QString URLDialog::url() const +{ + return ui.urlField->toPlainText(); +} + bool isGnome3Session() { #if defined(QT_OS_WIW) || defined(QT_OS_MAC) diff --git a/qt-ui/simplewidgets.h b/qt-ui/simplewidgets.h index 5f2402a..dde3033 100644 --- a/qt-ui/simplewidgets.h +++ b/qt-ui/simplewidgets.h @@ -14,6 +14,7 @@ class QNetworkReply; #include "ui_setpoint.h" #include "ui_shifttimes.h" #include "ui_shiftimagetimes.h" +#include "ui_urldialog.h" #include "ui_divecomponentselection.h" #include "ui_listfilter.h" #include "ui_filterwidget.h" @@ -115,6 +116,15 @@ private: time_t dcImageEpoch; }; +class URLDialog : public QDialog { + Q_OBJECT +public: + explicit URLDialog(QWidget *parent); + QString url() const; +private: + Ui::URLDialog ui; +}; + class QCalendarWidget; class DateWidget : public QWidget { diff --git a/qt-ui/urldialog.ui b/qt-ui/urldialog.ui new file mode 100644 index 0000000..397f90a --- /dev/null +++ b/qt-ui/urldialog.ui @@ -0,0 +1,91 @@ + + + URLDialog + + + + 0 + 0 + 397 + 103 + + + + Dialog + + + + + 40 + 60 + 341 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 10 + 30 + 371 + 21 + + + + + + + 10 + 10 + 151 + 16 + + + + Enter URL for images + + + + + + + buttonBox + accepted() + URLDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + URLDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/qthelper.cpp b/qthelper.cpp index a5597e3..030ed94 100644 --- a/qthelper.cpp +++ b/qthelper.cpp @@ -337,22 +337,6 @@ extern "C" xsltStylesheetPtr get_stylesheet(const char *name) return xslt; } -extern "C" void picture_load_exif_data(struct picture *p) -{ - EXIFInfo exif; - memblock mem; - - if (readfile(p->filename, &mem) <= 0) - goto picture_load_exit; - if (exif.parseFrom((const unsigned char *)mem.buffer, (unsigned)mem.size) != PARSE_EXIF_SUCCESS) - goto picture_load_exit; - p->longitude.udeg= lrint(1000000.0 * exif.GeoLocation.Longitude); - p->latitude.udeg = lrint(1000000.0 * exif.GeoLocation.Latitude); - -picture_load_exit: - free(mem.buffer); - return; -} extern "C" timestamp_t picture_get_timestamp(char *filename) { @@ -360,7 +344,8 @@ extern "C" timestamp_t picture_get_timestamp(char *filename) memblock mem; int retval; - if (readfile(filename, &mem) <= 0) + // filename might not be the actual filename, so let's go via the hash. + if (readfile(localFilePath(QString(filename)).toUtf8().data(), &mem) <= 0) return 0; retval = exif.parseFrom((const unsigned char *)mem.buffer, (unsigned)mem.size); free(mem.buffer); @@ -853,7 +838,8 @@ QByteArray hashFile(const QString filename) void learnHash(struct picture *picture, QByteArray hash) { - free(picture->hash); + if (picture->hash) + free(picture->hash); QMutexLocker locker(&hashOfMutex); hashOf[QString(picture->filename)] = hash; picture->hash = strdup(hash.toHex()); @@ -861,7 +847,10 @@ void learnHash(struct picture *picture, QByteArray hash) QString localFilePath(const QString originalFilename) { - return localFilenameOf[hashOf[originalFilename]]; + if (hashOf.contains(originalFilename) && localFilenameOf.contains(hashOf[originalFilename])) + return localFilenameOf[hashOf[originalFilename]]; + else + return originalFilename; } QString fileFromHash(char *hash) @@ -899,3 +888,20 @@ void learnImages(const QDir dir, int max_recursions, bool recursed) QtConcurrent::blockingMap(files, hashFile); } + +extern "C" void picture_load_exif_data(struct picture *p) +{ + EXIFInfo exif; + memblock mem; + + if (readfile(localFilePath(QString(p->filename)).toUtf8().data(), &mem) <= 0) + goto picture_load_exit; + if (exif.parseFrom((const unsigned char *)mem.buffer, (unsigned)mem.size) != PARSE_EXIF_SUCCESS) + goto picture_load_exit; + p->longitude.udeg= lrint(1000000.0 * exif.GeoLocation.Longitude); + p->latitude.udeg = lrint(1000000.0 * exif.GeoLocation.Latitude); + +picture_load_exit: + free(mem.buffer); + return; +} -- 2.3.2 (Apple Git-55)