~ubuntu-branches/ubuntu/saucy/digikam/saucy

« back to all changes in this revision

Viewing changes to extra/kipi-plugins/gpssync/kipiimageitem.cpp

  • Committer: Package Import Robot
  • Author(s): Felix Geyer, Rohan Garg, Philip Muškovac, Felix Geyer
  • Date: 2011-09-23 18:18:55 UTC
  • mfrom: (1.2.36 upstream)
  • Revision ID: package-import@ubuntu.com-20110923181855-ifs67wxkugshev9k
Tags: 2:2.1.1-0ubuntu1
[ Rohan Garg ]
* New upstream release (LP: #834190)
  - debian/control
    + Build with libqtwebkit-dev
 - debian/kipi-plugins-common
    + Install libkvkontakte required by kipi-plugins
 - debian/digikam
    + Install panoramagui

[ Philip Muškovac ]
* New upstream release
  - debian/control:
    + Add libcv-dev, libcvaux-dev, libhighgui-dev, libboost-graph1.46-dev,
      libksane-dev, libxml2-dev, libxslt-dev, libqt4-opengl-dev, libqjson-dev,
      libgpod-dev and libqca2-dev to build-deps
    + Add packages for kipi-plugins, libmediawiki, libkface, libkgeomap and
      libkvkontakte
  - debian/rules:
    + Don't build with gphoto2 since it doesn't build with it.
  - Add kubuntu_fix_test_linking.diff to fix linking of the dngconverter test
  - update install files
  - update kubuntu_01_mysqld_executable_name.diff for new cmake layout
    and rename to kubuntu_mysqld_executable_name.diff
* Fix typo in digikam-data description (LP: #804894)
* Fix Vcs links

[ Felix Geyer ]
* Move library data files to the new packages libkface-data, libkgeomap-data
  and libkvkontakte-data.
* Override version of the embedded library packages to 1.0~digikam<version>.
* Exclude the library packages from digikam-dbg to prevent file conflicts in
  the future.
* Call dh_install with --list-missing.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/** ===========================================================
 
2
 * @file
 
3
 *
 
4
 * This file is a part of kipi-plugins project
 
5
 * <a href="http://www.kipi-plugins.org">http://www.kipi-plugins.org</a>
 
6
 *
 
7
 * @date   2010-03-21
 
8
 * @brief  An item to hold information about an image.
 
9
 *
 
10
 * @author Copyright (C) 2010 by Michael G. Hansen
 
11
 *         <a href="mailto:mike at mghansen dot de">mike at mghansen dot de</a>
 
12
 *
 
13
 * This program is free software; you can redistribute it
 
14
 * and/or modify it under the terms of the GNU General
 
15
 * Public License as published by the Free Software Foundation;
 
16
 * either version 2, or (at your option) any later version.
 
17
 *
 
18
 * This program is distributed in the hope that it will be useful,
 
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 
21
 * GNU General Public License for more details.
 
22
 *
 
23
 * ============================================================ */
 
24
 
 
25
#include "kipiimageitem.h"
 
26
 
 
27
// Qt includes
 
28
 
 
29
#include <QBrush>
 
30
#include <QScopedPointer>
 
31
 
 
32
// KDE includes
 
33
 
 
34
#include <kdebug.h>
 
35
#include <kglobal.h>
 
36
#include <klocale.h>
 
37
 
 
38
// LibKExiv2 includes
 
39
 
 
40
#include <libkexiv2/version.h>
 
41
#include <libkexiv2/kexiv2.h>
 
42
 
 
43
// local includes
 
44
 
 
45
#include "kipiimagemodel.h"
 
46
 
 
47
using namespace KExiv2Iface;
 
48
 
 
49
namespace KIPIGPSSyncPlugin
 
50
{
 
51
 
 
52
bool KExiv2SetExifXmpTagDataVariant(KExiv2Iface::KExiv2* const exiv2Iface, const char* const exifTagName, const char* const xmpTagName, const QVariant& value)
 
53
{
 
54
    bool success = exiv2Iface->setExifTagVariant(exifTagName, value);
 
55
 
 
56
    if (success)
 
57
    {
 
58
        /** @todo Here we save all data types as XMP Strings. Is that okay or do we have to store them as some other type?
 
59
         */
 
60
        switch (value.type())
 
61
        {
 
62
            case QVariant::Int:
 
63
            case QVariant::UInt:
 
64
            case QVariant::Bool:
 
65
            case QVariant::LongLong:
 
66
            case QVariant::ULongLong:
 
67
                success = exiv2Iface->setXmpTagString(xmpTagName, QString::number(value.toInt()));
 
68
                break;
 
69
 
 
70
            case QVariant::Double:
 
71
            {
 
72
                long num, den;
 
73
                exiv2Iface->convertToRationalSmallDenominator(value.toDouble(), &num, &den);
 
74
                success = exiv2Iface->setXmpTagString(xmpTagName, QString("%1/%2").arg(num).arg(den));
 
75
                break;
 
76
            }
 
77
            case QVariant::List:
 
78
            {
 
79
                long num = 0, den = 1;
 
80
                QList<QVariant> list = value.toList();
 
81
                if (list.size() >= 1)
 
82
                    num = list[0].toInt();
 
83
                if (list.size() >= 2)
 
84
                    den = list[1].toInt();
 
85
                success = exiv2Iface->setXmpTagString(xmpTagName, QString("%1/%2").arg(num).arg(den));
 
86
                break;
 
87
            }
 
88
 
 
89
            case QVariant::Date:
 
90
            case QVariant::DateTime:
 
91
            {
 
92
                QDateTime dateTime = value.toDateTime();
 
93
                if(!dateTime.isValid())
 
94
                {
 
95
                    success = false;
 
96
                    break;
 
97
                }
 
98
 
 
99
                success = exiv2Iface->setXmpTagString(xmpTagName, dateTime.toString(QString("yyyy:MM:dd hh:mm:ss")));
 
100
                break;
 
101
            }
 
102
 
 
103
            case QVariant::String:
 
104
            case QVariant::Char:
 
105
                success = exiv2Iface->setXmpTagString(xmpTagName, value.toString());
 
106
                break;
 
107
 
 
108
            case QVariant::ByteArray:
 
109
                /// @todo I don't know a straightforward way to convert a byte array to XMP
 
110
                success = false;
 
111
                break;
 
112
 
 
113
            default:
 
114
                success = false;
 
115
                break;
 
116
        }
 
117
    }
 
118
 
 
119
    return success;
 
120
}
 
121
 
 
122
KipiImageItem::KipiImageItem(KIPI::Interface* const interface, const KUrl& url)
 
123
             : m_interface(interface),
 
124
               m_model(0),
 
125
               m_url(url),
 
126
               m_dateTime(),
 
127
               m_dirty(false),
 
128
               m_gpsData(),
 
129
               m_savedState(),
 
130
               m_tagListDirty(false),
 
131
               m_tagList(),
 
132
               m_savedTagList(),
 
133
               m_writeXmpTags(true)
 
134
{
 
135
}
 
136
 
 
137
KipiImageItem::~KipiImageItem()
 
138
{
 
139
}
 
140
 
 
141
KExiv2Iface::KExiv2* KipiImageItem::getExiv2ForFile()
 
142
{
 
143
    QScopedPointer<KExiv2Iface::KExiv2> exiv2Iface(new KExiv2Iface::KExiv2);
 
144
 
 
145
    if (m_interface)
 
146
    {
 
147
        exiv2Iface->setWriteRawFiles(m_interface->hostSetting("WriteMetadataToRAW").toBool());
 
148
        exiv2Iface->setUpdateFileTimeStamp(m_interface->hostSetting("WriteMetadataUpdateFiletimeStamp").toBool());
 
149
        exiv2Iface->setUseXMPSidecar4Reading(m_interface->hostSetting("UseXMPSidecar4Reading").toBool());
 
150
        exiv2Iface->setMetadataWritingMode(m_interface->hostSetting("MetadataWritingMode").toInt());
 
151
    }
 
152
    else
 
153
    {
 
154
        exiv2Iface->setUseXMPSidecar4Reading(true);
 
155
        exiv2Iface->setMetadataWritingMode(KExiv2::WRITETOSIDECARONLY4READONLYFILES);
 
156
    }
 
157
 
 
158
    if (!exiv2Iface->load(m_url.path()))
 
159
    {
 
160
        return 0;
 
161
    }
 
162
 
 
163
    return exiv2Iface.take();
 
164
}
 
165
 
 
166
int getWarningLevelFromGPSDataContainer(const GPSDataContainer& data)
 
167
{
 
168
    if (data.hasDop())
 
169
    {
 
170
        const int dopValue = data.getDop();
 
171
        if (dopValue<2)
 
172
            return 1;
 
173
        if (dopValue<4)
 
174
            return 2;
 
175
        if (dopValue<10)
 
176
            return 3;
 
177
        return 4;
 
178
    }
 
179
    else if (data.hasFixType())
 
180
    {
 
181
        if (data.getFixType()<3)
 
182
            return 4;
 
183
    }
 
184
    else if (data.hasNSatellites())
 
185
    {
 
186
        if (data.getNSatellites()<4)
 
187
            return 4;
 
188
    }
 
189
 
 
190
    // no warning level
 
191
    return -1;
 
192
}
 
193
 
 
194
bool KipiImageItem::loadImageData(const bool fromInterface, const bool fromFile)
 
195
{
 
196
    if (fromInterface && m_interface)
 
197
    {
 
198
        // try to load the GPS data from the KIPI interface:
 
199
        QMap<QString, QVariant> attributes;
 
200
        KIPI::ImageInfo info = m_interface->info(m_url);
 
201
        attributes = info.attributes();
 
202
 
 
203
        if (attributes.contains("latitude") &&
 
204
            attributes.contains("longitude"))
 
205
        {
 
206
            m_gpsData.setLatLon(attributes["latitude"].toDouble(), attributes["longitude"].toDouble());
 
207
            if (attributes.contains("altitude"))
 
208
            {
 
209
                m_gpsData.setAltitude(attributes["altitude"].toDouble());
 
210
            }
 
211
        }
 
212
 
 
213
        m_dateTime = info.time(KIPI::FromInfo);
 
214
    }
 
215
 
 
216
    if (fromFile)
 
217
    {
 
218
        QScopedPointer<KExiv2Iface::KExiv2> exiv2Iface(getExiv2ForFile());
 
219
 
 
220
        if (!exiv2Iface)
 
221
            return false;
 
222
 
 
223
        if (!m_dateTime.isValid())
 
224
        {
 
225
            m_dateTime = exiv2Iface->getImageDateTime();
 
226
        }
 
227
 
 
228
        m_gpsData.clear();
 
229
 
 
230
        if (!m_gpsData.hasCoordinates())
 
231
        {
 
232
            // could not load the coordinates from the interface,
 
233
            // read them directly from the file
 
234
 
 
235
            double lat, lng;
 
236
            bool haveCoordinates = exiv2Iface->getGPSLatitudeNumber(&lat) && exiv2Iface->getGPSLongitudeNumber(&lng);
 
237
            if (haveCoordinates)
 
238
            {
 
239
                KGeoMap::GeoCoordinates coordinates(lat, lng);
 
240
                double alt;
 
241
                if (exiv2Iface->getGPSAltitude(&alt))
 
242
                {
 
243
                    coordinates.setAlt(alt);
 
244
                }
 
245
                m_gpsData.setCoordinates(coordinates);
 
246
            }
 
247
        }
 
248
 
 
249
        /** @todo It seems that exiv2 provides EXIF entries if XMP sidecar entries exist,
 
250
         *  therefore no need to read XMP as well?
 
251
         */
 
252
        // read the remaining GPS information from the file:
 
253
        const QByteArray speedRef = exiv2Iface->getExifTagData("Exif.GPSInfo.GPSSpeedRef");
 
254
        bool success = !speedRef.isEmpty();
 
255
        long num, den;
 
256
        success&= exiv2Iface->getExifTagRational("Exif.GPSInfo.GPSSpeed", num, den);
 
257
        if (success)
 
258
        {
 
259
            // be relaxed about 0/0
 
260
            if ((num==0.0)&&(den==0.0))
 
261
                den = 1.0;
 
262
 
 
263
            const qreal speedInRef = qreal(num)/qreal(den);
 
264
 
 
265
            qreal FactorToMetersPerSecond;
 
266
            if (speedRef.startsWith('K'))
 
267
            {
 
268
                // km/h = 1000 * 3600
 
269
                FactorToMetersPerSecond = 1.0/3.6;
 
270
            }
 
271
            else if (speedRef.startsWith('M'))
 
272
            {
 
273
                // TODO: someone please check that this is the 'right' mile
 
274
                // miles/hour = 1609.344 meters / hour = 1609.344 meters / 3600 seconds
 
275
                FactorToMetersPerSecond = 1.0 / (1609.344 / 3600.0);
 
276
            }
 
277
            else if (speedRef.startsWith('N'))
 
278
            {
 
279
                // speed is in knots.
 
280
                // knot = one nautic mile / hour = 1852 meters / hour = 1852 meters / 3600 seconds
 
281
                FactorToMetersPerSecond = 1.0 / (1852.0 / 3600.0);
 
282
            }
 
283
            else
 
284
            {
 
285
                success = false;
 
286
            }
 
287
 
 
288
            if (success)
 
289
            {
 
290
                const qreal speedInMetersPerSecond = speedInRef * FactorToMetersPerSecond;
 
291
                m_gpsData.setSpeed(speedInMetersPerSecond);
 
292
            }
 
293
        }
 
294
 
 
295
        // number of satellites
 
296
        const QString gpsSatellitesString = exiv2Iface->getExifTagString("Exif.GPSInfo.GPSSatellites");
 
297
        bool satellitesOkay = !gpsSatellitesString.isEmpty();
 
298
        if (satellitesOkay)
 
299
        {
 
300
            /**
 
301
             * @todo Here we only accept a single integer denoting the number of satellites used
 
302
             *       but not detailed information about all satellites.
 
303
             */
 
304
            const int nSatellites = gpsSatellitesString.toInt(&satellitesOkay);
 
305
            if (satellitesOkay)
 
306
            {
 
307
                m_gpsData.setNSatellites(nSatellites);
 
308
            }
 
309
        }
 
310
 
 
311
        // fix type / measure mode
 
312
        const QByteArray gpsMeasureModeByteArray = exiv2Iface->getExifTagData("Exif.GPSInfo.GPSMeasureMode");
 
313
        bool measureModeOkay = !gpsMeasureModeByteArray.isEmpty();
 
314
        if (measureModeOkay)
 
315
        {
 
316
            const int measureMode = gpsMeasureModeByteArray.toInt(&measureModeOkay);
 
317
            if (measureModeOkay)
 
318
            {
 
319
                if ((measureMode==2)||(measureMode==3))
 
320
                {
 
321
                    m_gpsData.setFixType(measureMode);
 
322
                }
 
323
            }
 
324
        }
 
325
 
 
326
        // read the DOP value:
 
327
        success= exiv2Iface->getExifTagRational("Exif.GPSInfo.GPSDOP", num, den);
 
328
        if (success)
 
329
        {
 
330
            // be relaxed about 0/0
 
331
            if ((num==0.0)&&(den==0.0))
 
332
                den = 1.0;
 
333
 
 
334
            const qreal dop = qreal(num)/qreal(den);
 
335
 
 
336
            m_gpsData.setDop(dop);
 
337
        }
 
338
 
 
339
    }
 
340
 
 
341
    // mark us as not-dirty, because the data was just loaded:
 
342
    m_dirty = false;
 
343
    m_savedState = m_gpsData;
 
344
 
 
345
    emitDataChanged();
 
346
 
 
347
    return true;
 
348
}
 
349
 
 
350
QVariant KipiImageItem::data(const int column, const int role) const
 
351
{
 
352
    if ((column==ColumnFilename)&&(role==Qt::DisplayRole))
 
353
    {
 
354
        return m_url.fileName();
 
355
    }
 
356
    else if ((column==ColumnDateTime)&&(role==Qt::DisplayRole))
 
357
    {
 
358
        if (m_dateTime.isValid())
 
359
        {
 
360
            return m_dateTime.toString(Qt::LocalDate);
 
361
        }
 
362
        return i18n("Not available");
 
363
    }
 
364
    else if (role==RoleCoordinates)
 
365
    {
 
366
        return QVariant::fromValue(m_gpsData.getCoordinates());
 
367
    }
 
368
    else if ((column==ColumnLatitude)&&(role==Qt::DisplayRole))
 
369
    {
 
370
        if (!m_gpsData.getCoordinates().hasLatitude())
 
371
            return QString();
 
372
 
 
373
        return KGlobal::locale()->formatNumber(m_gpsData.getCoordinates().lat(), 7);
 
374
    }
 
375
    else if ((column==ColumnLongitude)&&(role==Qt::DisplayRole))
 
376
    {
 
377
        if (!m_gpsData.getCoordinates().hasLongitude())
 
378
            return QString();
 
379
 
 
380
        return KGlobal::locale()->formatNumber(m_gpsData.getCoordinates().lon(), 7);
 
381
    }
 
382
    else if ((column==ColumnAltitude)&&(role==Qt::DisplayRole))
 
383
    {
 
384
        if (!m_gpsData.getCoordinates().hasAltitude())
 
385
            return QString();
 
386
 
 
387
        return KGlobal::locale()->formatNumber(m_gpsData.getCoordinates().alt());
 
388
    }
 
389
    else if (column==ColumnAccuracy)
 
390
    {
 
391
        if (role==Qt::DisplayRole)
 
392
        {
 
393
            if (m_gpsData.hasDop())
 
394
            {
 
395
                return i18n("DOP: %1", m_gpsData.getDop());
 
396
            }
 
397
 
 
398
            if (m_gpsData.hasFixType())
 
399
            {
 
400
                return i18n("Fix: %1d", m_gpsData.getFixType());
 
401
            }
 
402
 
 
403
            if (m_gpsData.hasNSatellites())
 
404
            {
 
405
                return i18n("#Sat: %1", m_gpsData.getNSatellites());
 
406
            }
 
407
        }
 
408
        else if (role==Qt::BackgroundRole)
 
409
        {
 
410
            const int warningLevel = getWarningLevelFromGPSDataContainer(m_gpsData);
 
411
            switch (warningLevel)
 
412
            {
 
413
            case 1:
 
414
                return QBrush(Qt::green);
 
415
            case 2:
 
416
                return QBrush(Qt::yellow);
 
417
            case 3:
 
418
                // orange
 
419
                return QBrush(QColor(0xff, 0x80, 0x00));
 
420
            case 4:
 
421
                return QBrush(Qt::red);
 
422
            default:
 
423
                break;
 
424
            }
 
425
        }
 
426
    }
 
427
    else if ((column==ColumnDOP)&&(role==Qt::DisplayRole))
 
428
    {
 
429
        if (!m_gpsData.hasDop())
 
430
            return QString();
 
431
 
 
432
        return KGlobal::locale()->formatNumber(m_gpsData.getDop());
 
433
    }
 
434
    else if ((column==ColumnFixType)&&(role==Qt::DisplayRole))
 
435
    {
 
436
        if (!m_gpsData.hasFixType())
 
437
            return QString();
 
438
 
 
439
        return i18n("%1d", m_gpsData.getFixType());
 
440
    }
 
441
    else if ((column==ColumnNSatellites)&&(role==Qt::DisplayRole))
 
442
    {
 
443
        if (!m_gpsData.hasNSatellites())
 
444
            return QString();
 
445
 
 
446
        return KGlobal::locale()->formatNumber(m_gpsData.getNSatellites(), 0);
 
447
    }
 
448
    else if ((column==ColumnSpeed)&&(role==Qt::DisplayRole))
 
449
    {
 
450
        if (!m_gpsData.hasSpeed())
 
451
            return QString();
 
452
 
 
453
        return KGlobal::locale()->formatNumber(m_gpsData.getSpeed());
 
454
    }
 
455
    else if ((column==ColumnStatus)&&(role==Qt::DisplayRole))
 
456
    {
 
457
        if (m_dirty || m_tagListDirty)
 
458
        {
 
459
            return i18n("Modified");
 
460
        }
 
461
 
 
462
        return QString();
 
463
    }
 
464
    else if ((column==ColumnTags)&&(role==Qt::DisplayRole))
 
465
    {
 
466
        if (!m_tagList.isEmpty())
 
467
        {
 
468
 
 
469
            QString myTagsList;
 
470
            for (int i=0; i<m_tagList.count(); ++i)
 
471
            {
 
472
                QString myTag;
 
473
                for (int j=0; j<m_tagList[i].count(); ++j)
 
474
                {
 
475
                    myTag.append(QString("/") + m_tagList[i].at(j).tagName);
 
476
                    if (j == 0)
 
477
                        myTag.remove(0,1);
 
478
                }
 
479
 
 
480
                if (!myTagsList.isEmpty())
 
481
                    myTagsList.append(", ");
 
482
                myTagsList.append(myTag);
 
483
            }
 
484
 
 
485
            return myTagsList;
 
486
        }
 
487
 
 
488
        return QString();
 
489
    }
 
490
 
 
491
 
 
492
    return QVariant();
 
493
}
 
494
 
 
495
void KipiImageItem::setCoordinates(const KGeoMap::GeoCoordinates& newCoordinates)
 
496
{
 
497
    m_gpsData.setCoordinates(newCoordinates);
 
498
    m_dirty = true;
 
499
    emitDataChanged();
 
500
}
 
501
 
 
502
void KipiImageItem::setModel(KipiImageModel* const model)
 
503
{
 
504
    m_model = model;
 
505
}
 
506
 
 
507
void KipiImageItem::emitDataChanged()
 
508
{
 
509
    if (m_model)
 
510
    {
 
511
        m_model->itemChanged(this);
 
512
    }
 
513
}
 
514
 
 
515
void KipiImageItem::setHeaderData(KipiImageModel* const model)
 
516
{
 
517
    model->setColumnCount(ColumnGPSImageItemCount);
 
518
    model->setHeaderData(ColumnThumbnail, Qt::Horizontal, i18n("Thumbnail"), Qt::DisplayRole);
 
519
    model->setHeaderData(ColumnFilename, Qt::Horizontal, i18n("Filename"), Qt::DisplayRole);
 
520
    model->setHeaderData(ColumnDateTime, Qt::Horizontal, i18n("Date and time"), Qt::DisplayRole);
 
521
    model->setHeaderData(ColumnLatitude, Qt::Horizontal, i18n("Latitude"), Qt::DisplayRole);
 
522
    model->setHeaderData(ColumnLongitude, Qt::Horizontal, i18n("Longitude"), Qt::DisplayRole);
 
523
    model->setHeaderData(ColumnAltitude, Qt::Horizontal, i18n("Altitude"), Qt::DisplayRole);
 
524
    model->setHeaderData(ColumnAccuracy, Qt::Horizontal, i18n("Accuracy"), Qt::DisplayRole);
 
525
    model->setHeaderData(ColumnDOP, Qt::Horizontal, i18n("DOP"), Qt::DisplayRole);
 
526
    model->setHeaderData(ColumnFixType, Qt::Horizontal, i18n("Fix type"), Qt::DisplayRole);
 
527
    model->setHeaderData(ColumnNSatellites, Qt::Horizontal, i18n("# satellites"), Qt::DisplayRole);
 
528
    model->setHeaderData(ColumnSpeed, Qt::Horizontal, i18n("Speed"), Qt::DisplayRole);
 
529
    model->setHeaderData(ColumnStatus, Qt::Horizontal, i18n("Status"), Qt::DisplayRole);
 
530
    model->setHeaderData(ColumnTags, Qt::Horizontal, i18n("Tags"), Qt::DisplayRole);
 
531
}
 
532
 
 
533
bool KipiImageItem::lessThan(const KipiImageItem* const otherItem, const int column) const
 
534
{
 
535
    switch (column)
 
536
    {
 
537
    case ColumnThumbnail:
 
538
        return false;
 
539
 
 
540
    case ColumnFilename:
 
541
        return m_url < otherItem->m_url;
 
542
 
 
543
    case ColumnDateTime:
 
544
        return m_dateTime < otherItem->m_dateTime;
 
545
 
 
546
        case ColumnAltitude:
 
547
    {
 
548
        if (!m_gpsData.hasAltitude())
 
549
            return false;
 
550
 
 
551
        if (!otherItem->m_gpsData.hasAltitude())
 
552
            return true;
 
553
 
 
554
        return m_gpsData.getCoordinates().alt() < otherItem->m_gpsData.getCoordinates().alt();
 
555
    }
 
556
 
 
557
    case ColumnNSatellites:
 
558
    {
 
559
        if (!m_gpsData.hasNSatellites())
 
560
            return false;
 
561
 
 
562
        if (!otherItem->m_gpsData.hasNSatellites())
 
563
            return true;
 
564
 
 
565
        return m_gpsData.getNSatellites() < otherItem->m_gpsData.getNSatellites();
 
566
    }
 
567
 
 
568
    case ColumnAccuracy:
 
569
    {
 
570
        const int myWarning = getWarningLevelFromGPSDataContainer(m_gpsData);
 
571
        const int otherWarning = getWarningLevelFromGPSDataContainer(otherItem->m_gpsData);
 
572
 
 
573
        if (myWarning<0)
 
574
            return false;
 
575
 
 
576
        if (otherWarning<0)
 
577
            return true;
 
578
 
 
579
        if (myWarning!=otherWarning)
 
580
            return myWarning < otherWarning;
 
581
 
 
582
        // TODO: this may not be the best way to sort images with equal warning levels
 
583
        //       but it works for now
 
584
 
 
585
        if (m_gpsData.hasDop()!=otherItem->m_gpsData.hasDop())
 
586
            return !m_gpsData.hasDop();
 
587
        if (m_gpsData.hasDop()&&otherItem->m_gpsData.hasDop())
 
588
        {
 
589
            return m_gpsData.getDop()<otherItem->m_gpsData.getDop();
 
590
        }
 
591
 
 
592
        if (m_gpsData.hasFixType()!=otherItem->m_gpsData.hasFixType())
 
593
            return m_gpsData.hasFixType();
 
594
        if (m_gpsData.hasFixType()&&otherItem->m_gpsData.hasFixType())
 
595
        {
 
596
            return m_gpsData.getFixType()>otherItem->m_gpsData.getFixType();
 
597
        }
 
598
 
 
599
        if (m_gpsData.hasNSatellites()!=otherItem->m_gpsData.hasNSatellites())
 
600
            return m_gpsData.hasNSatellites();
 
601
        if (m_gpsData.hasNSatellites()&&otherItem->m_gpsData.hasNSatellites())
 
602
        {
 
603
            return m_gpsData.getNSatellites()>otherItem->m_gpsData.getNSatellites();
 
604
        }
 
605
 
 
606
        return false;
 
607
    }
 
608
 
 
609
    case ColumnDOP:
 
610
    {
 
611
        if (!m_gpsData.hasDop())
 
612
            return false;
 
613
 
 
614
        if (!otherItem->m_gpsData.hasDop())
 
615
            return true;
 
616
 
 
617
        return m_gpsData.getDop() < otherItem->m_gpsData.getDop();
 
618
    }
 
619
 
 
620
    case ColumnFixType:
 
621
    {
 
622
        if (!m_gpsData.hasFixType())
 
623
            return false;
 
624
 
 
625
        if (!otherItem->m_gpsData.hasFixType())
 
626
            return true;
 
627
 
 
628
        return m_gpsData.getFixType() < otherItem->m_gpsData.getFixType();
 
629
    }
 
630
 
 
631
    case ColumnSpeed:
 
632
    {
 
633
        if (!m_gpsData.hasSpeed())
 
634
            return false;
 
635
 
 
636
        if (!otherItem->m_gpsData.hasSpeed())
 
637
            return true;
 
638
 
 
639
        return m_gpsData.getSpeed() < otherItem->m_gpsData.getSpeed();
 
640
    }
 
641
 
 
642
    case ColumnLatitude:
 
643
    {
 
644
        if (!m_gpsData.hasCoordinates())
 
645
            return false;
 
646
 
 
647
        if (!otherItem->m_gpsData.hasCoordinates())
 
648
            return true;
 
649
 
 
650
        return m_gpsData.getCoordinates().lat() < otherItem->m_gpsData.getCoordinates().lat();
 
651
    }
 
652
 
 
653
    case ColumnLongitude:
 
654
    {
 
655
        if (!m_gpsData.hasCoordinates())
 
656
            return false;
 
657
 
 
658
        if (!otherItem->m_gpsData.hasCoordinates())
 
659
            return true;
 
660
 
 
661
        return m_gpsData.getCoordinates().lon() < otherItem->m_gpsData.getCoordinates().lon();
 
662
    }
 
663
 
 
664
    case ColumnStatus:
 
665
    {
 
666
        return m_dirty && !otherItem->m_dirty;
 
667
    }
 
668
 
 
669
    default:
 
670
        return false;
 
671
    }
 
672
}
 
673
 
 
674
QString KipiImageItem::saveChanges(const bool toInterface, const bool toFile)
 
675
{
 
676
    Q_UNUSED(toInterface);
 
677
    Q_UNUSED(toFile);
 
678
 
 
679
    // determine what is to be done first
 
680
    bool shouldRemoveCoordinates = false;
 
681
    bool shouldRemoveAltitude    = false;
 
682
    bool shouldWriteCoordinates  = false;
 
683
    bool shouldWriteAltitude     = false;
 
684
    qreal altitude  = 0;
 
685
    qreal latitude  = 0;
 
686
    qreal longitude = 0;
 
687
 
 
688
    // do we have gps information?
 
689
    if (m_gpsData.hasCoordinates())
 
690
    {
 
691
        shouldWriteCoordinates = true;
 
692
        latitude = m_gpsData.getCoordinates().lat();
 
693
        longitude = m_gpsData.getCoordinates().lon();
 
694
 
 
695
        if (m_gpsData.hasAltitude())
 
696
        {
 
697
            shouldWriteAltitude = true;
 
698
            altitude = m_gpsData.getCoordinates().alt();
 
699
        }
 
700
        else
 
701
        {
 
702
            shouldRemoveAltitude = true;
 
703
        }
 
704
    }
 
705
    else
 
706
    {
 
707
        shouldRemoveCoordinates = true;
 
708
        shouldRemoveAltitude = true;
 
709
    }
 
710
 
 
711
    QString returnString;
 
712
 
 
713
    // first try to write the information to the image file
 
714
    bool success = false;
 
715
    QScopedPointer<KExiv2Iface::KExiv2> exiv2Iface(getExiv2ForFile());
 
716
    if (!exiv2Iface)
 
717
    {
 
718
        // TODO: more verbosity!
 
719
        returnString = i18n("Failed to open file.");
 
720
    }
 
721
    else
 
722
    {
 
723
        if (shouldWriteCoordinates)
 
724
        {
 
725
            if (shouldWriteAltitude)
 
726
            {
 
727
                success = exiv2Iface->setGPSInfo(altitude, latitude, longitude);
 
728
            }
 
729
            else
 
730
            {
 
731
                success = exiv2Iface->setGPSInfo(static_cast<const double* const>(0), latitude, longitude);
 
732
            }
 
733
 
 
734
            // write all other GPS information here too
 
735
            if (success && m_gpsData.hasSpeed())
 
736
            {
 
737
                success = KExiv2SetExifXmpTagDataVariant(exiv2Iface.data(), "Exif.GPSInfo.GPSSpeedRef", "Xmp.exif.GPSSpeedRef", QVariant(QString("K")));
 
738
 
 
739
                if (success)
 
740
                {
 
741
                    const qreal speedInMetersPerSecond = m_gpsData.getSpeed();
 
742
 
 
743
                    // km/h = 0.001 * m / ( s * 1/(60*60) ) = 3.6 * m/s
 
744
                    const qreal speedInKilometersPerHour = 3.6 * speedInMetersPerSecond;
 
745
                    success = KExiv2SetExifXmpTagDataVariant(exiv2Iface.data(), "Exif.GPSInfo.GPSSpeed", "Xmp.exif.GPSSpeed", QVariant(speedInKilometersPerHour));
 
746
                }
 
747
            }
 
748
 
 
749
            if (success && m_gpsData.hasNSatellites())
 
750
            {
 
751
                /**
 
752
                 * @todo According to the EXIF 2.2 spec, GPSSatellites is a free form field which can either hold only the
 
753
                 * number of satellites or more details about each satellite used. For now, we just write
 
754
                 * the number of satellites. Are we using the correct format for the number of satellites here?
 
755
                 */
 
756
                success = KExiv2SetExifXmpTagDataVariant(exiv2Iface.data(),
 
757
                                                         "Exif.GPSInfo.GPSSatellites", "Xmp.exif.GPSSatellites",
 
758
                                                         QVariant(QString::number(m_gpsData.getNSatellites())));
 
759
            }
 
760
 
 
761
            if (success && m_gpsData.hasFixType())
 
762
            {
 
763
                success = KExiv2SetExifXmpTagDataVariant(exiv2Iface.data(),
 
764
                                                         "Exif.GPSInfo.GPSMeasureMode", "Xmp.exif.GPSMeasureMode",
 
765
                                                         QVariant(QString::number(m_gpsData.getFixType())));
 
766
            }
 
767
 
 
768
            // write DOP
 
769
            if (success && m_gpsData.hasDop())
 
770
            {
 
771
                success = KExiv2SetExifXmpTagDataVariant(
 
772
                        exiv2Iface.data(),
 
773
                        "Exif.GPSInfo.GPSDOP",
 
774
                        "Xmp.exif.GPSDOP",
 
775
                        QVariant(m_gpsData.getDop())
 
776
                    );
 
777
            }
 
778
 
 
779
 
 
780
            if (!success)
 
781
            {
 
782
                returnString = i18n("Failed to add GPS info to image.");
 
783
            }
 
784
        }
 
785
        if (shouldRemoveCoordinates)
 
786
        {
 
787
            // TODO: remove only the altitude if requested
 
788
            success = exiv2Iface->removeGPSInfo();
 
789
            if (!success)
 
790
            {
 
791
                returnString = i18n("Failed to remove GPS info from image");
 
792
            }
 
793
        }
 
794
 
 
795
        if (!m_tagList.isEmpty() && m_writeXmpTags)
 
796
        {
 
797
 
 
798
            QStringList tagSeq;
 
799
 
 
800
            for (int i=0; i<m_tagList.count(); ++i)
 
801
            {
 
802
                QList<TagData> currentTagList = m_tagList[i];
 
803
                QString tag;
 
804
 
 
805
                for (int j=0; j<currentTagList.count(); ++j)
 
806
                {
 
807
                    tag.append(QString("/") + currentTagList[j].tagName);
 
808
                }
 
809
                tag.remove(0,1);
 
810
 
 
811
                tagSeq.append(tag);
 
812
            }
 
813
 
 
814
            bool success = exiv2Iface->setXmpTagStringSeq("Xmp.digiKam.TagsList", tagSeq, true);
 
815
            if (!success)
 
816
            {
 
817
                returnString = i18n("Failed to save tags to file.");
 
818
            }
 
819
            success = exiv2Iface->setXmpTagStringSeq("Xmp.dc.subject", tagSeq, true);
 
820
            if (!success)
 
821
            {
 
822
                returnString = i18n("Failed to save tags to file.");
 
823
            }
 
824
        }
 
825
    }
 
826
 
 
827
    if (success)
 
828
    {
 
829
        success = exiv2Iface->save(m_url.path());
 
830
        if (!success)
 
831
        {
 
832
            returnString = i18n("Unable to save changes to file");
 
833
        }
 
834
        else
 
835
        {
 
836
            m_dirty = false;
 
837
            m_savedState = m_gpsData;
 
838
            m_tagListDirty = false;
 
839
            m_savedTagList = m_tagList;
 
840
        }
 
841
    }
 
842
 
 
843
    // now tell the interface about the changes
 
844
    // TODO: remove the altitude if it is not available
 
845
    if (m_interface)
 
846
    {
 
847
        if (shouldWriteCoordinates)
 
848
        {
 
849
            QMap<QString, QVariant> attributes;
 
850
            attributes.insert("latitude", latitude);
 
851
            attributes.insert("longitude", longitude);
 
852
            if (shouldWriteAltitude)
 
853
            {
 
854
                attributes.insert("altitude", altitude);
 
855
            }
 
856
 
 
857
            KIPI::ImageInfo info = m_interface->info(m_url);
 
858
            info.addAttributes(attributes);
 
859
        }
 
860
 
 
861
        if (shouldRemoveCoordinates)
 
862
        {
 
863
            QStringList listToRemove;
 
864
            listToRemove << "gpslocation";
 
865
            KIPI::ImageInfo info = m_interface->info(m_url);
 
866
            info.delAttributes(listToRemove);
 
867
        }
 
868
 
 
869
        if (!m_tagList.isEmpty())
 
870
        {
 
871
            QMap<QString, QVariant> attributes;
 
872
            QStringList tagsPath;
 
873
 
 
874
            for (int i=0; i<m_tagList.count(); ++i)
 
875
            {
 
876
 
 
877
                QString singleTagPath;
 
878
                QList<TagData> currentTagPath = m_tagList[i];
 
879
                for (int j=0; j<currentTagPath.count(); ++j)
 
880
                {
 
881
                    singleTagPath.append(QString("%1").arg("/") + currentTagPath[j].tagName);
 
882
                    if (j == 0)
 
883
                    {
 
884
                        singleTagPath.remove(0,1);
 
885
                    }
 
886
                }
 
887
 
 
888
                tagsPath.append(singleTagPath);
 
889
            }
 
890
 
 
891
            attributes.insert("tagspath", tagsPath);
 
892
            KIPI::ImageInfo info = m_interface->info(m_url);
 
893
            info.addAttributes(attributes);
 
894
        }
 
895
    }
 
896
 
 
897
    if (returnString.isEmpty())
 
898
    {
 
899
        // mark all changes as not dirty and tell the model:
 
900
        emitDataChanged();
 
901
    }
 
902
 
 
903
    return returnString;
 
904
}
 
905
 
 
906
/**
 
907
 * @brief Restore the gps data to @p container. Sets m_dirty to false if container equals savedState.
 
908
 */
 
909
void KipiImageItem::restoreGPSData(const GPSDataContainer& container)
 
910
{
 
911
    m_dirty = !(container == m_savedState);
 
912
    m_gpsData = container;
 
913
    emitDataChanged();
 
914
}
 
915
 
 
916
void KipiImageItem::restoreRGTagList(const QList<QList<TagData> >& tagList)
 
917
{
 
918
    //TODO: override == operator
 
919
 
 
920
    if (tagList.count() != m_savedTagList.count())
 
921
        m_tagListDirty = true;
 
922
    else
 
923
    {
 
924
        for (int i=0; i<tagList.count(); ++i)
 
925
        {
 
926
            bool foundNotEqual = false;
 
927
 
 
928
            if (tagList[i].count() != m_savedTagList[i].count())
 
929
            {
 
930
                m_tagListDirty = true;
 
931
                break;
 
932
            }
 
933
 
 
934
            for (int j=0; j<tagList[i].count(); ++j)
 
935
            {
 
936
                if (tagList[i].at(j).tagName != m_savedTagList[i].at(j).tagName)
 
937
                {
 
938
                    foundNotEqual = true;
 
939
                    break;
 
940
                }
 
941
            }
 
942
 
 
943
            if (foundNotEqual)
 
944
            {
 
945
                m_tagListDirty = true;
 
946
                break;
 
947
            }
 
948
        }
 
949
    }
 
950
 
 
951
    m_tagList = tagList;
 
952
    emitDataChanged();
 
953
}
 
954
 
 
955
} /* KIPIGPSSyncPlugin */