You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kipi-plugins/kipi-plugins/gpssync/gpssyncdialog.cpp

549 lines
19 KiB

/* ============================================================
*
* This file is a part of kipi-plugins project
* http://www.kipi-plugins.org
*
* Date : 2006-05-16
* Description : a plugin to synchronize pictures with
* a GPS device.
*
* Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
// TQt includes.
#include <tqcombobox.h>
#include <tqlayout.h>
#include <tqlabel.h>
#include <tqvgroupbox.h>
#include <tqgrid.h>
#include <tqpushbutton.h>
#include <tqwhatsthis.h>
#include <tqcheckbox.h>
// KDE includes.
#include <kdebug.h>
#include <tdelocale.h>
#include <tdeapplication.h>
#include <khelpmenu.h>
#include <ksqueezedtextlabel.h>
#include <kiconloader.h>
#include <tdepopupmenu.h>
#include <kstandarddirs.h>
#include <tdelistview.h>
#include <tdefiledialog.h>
#include <tdeconfig.h>
#include <tdemessagebox.h>
#include <tdeglobalsettings.h>
#include <knuminput.h>
#include <kseparator.h>
#include <tdeio/previewjob.h>
// Local includes.
#include "kpaboutdata.h"
#include "pluginsversion.h"
#include "gpslistviewitem.h"
#include "gpsdataparser.h"
#include "gpseditdialog.h"
#include "gpssyncdialog.h"
#include "gpssyncdialog.moc"
namespace KIPIGPSSyncPlugin
{
class GPSSyncDialogPriv
{
public:
GPSSyncDialogPriv()
{
listView = 0;
interface = 0;
maxGapInput = 0;
gpxFileName = 0;
gpxPointsLabel = 0;
timeZoneCB = 0;
interpolateBox = 0;
maxTimeInput = 0;
maxTimeLabel = 0;
about = 0;
}
TQLabel *gpxPointsLabel;
TQLabel *maxTimeLabel;
TQComboBox *timeZoneCB;
TQCheckBox *interpolateBox;
TDEListView *listView;
KIntSpinBox *maxGapInput;
KIntSpinBox *maxTimeInput;
KSqueezedTextLabel *gpxFileName;
KIPI::Interface *interface;
KIPIPlugins::KPAboutData *about;
GPSDataParser gpxParser;
};
GPSSyncDialog::GPSSyncDialog( KIPI::Interface* interface, TQWidget* parent)
: KDialogBase(Plain, i18n("Geolocation"),
Help|User1|User2|User3|Apply|Close, Close,
parent, 0, true, false)
{
d = new GPSSyncDialogPriv;
d->interface = interface;
setButtonText(User1, i18n("Correlate"));
setButtonText(User2, i18n("Edit..."));
setButtonText(User3, i18n("Remove"));
setButtonTip(User1, i18n("Correlate in time and interpolate distance of data from GPX file with all images on the list."));
setButtonTip(User2, i18n("Manually edit GPS coordinates of selected images from the list."));
setButtonTip(User3, i18n("Remove GPS coordinates of selected images from the list."));
enableButton(User1, false);
enableButton(User2, true);
enableButton(User3, true);
TQGridLayout *mainLayout = new TQGridLayout(plainPage(), 3, 1, 0, marginHint());
// --------------------------------------------------------------
d->listView = new TDEListView(plainPage());
d->listView->addColumn( i18n("Thumbnail") );
d->listView->addColumn( i18n("File Name") );
d->listView->addColumn( i18n("Camera time stamp") );
d->listView->addColumn( i18n("Latitude") );
d->listView->addColumn( i18n("Longitude") );
d->listView->addColumn( i18n("Altitude") );
d->listView->addColumn( i18n("Status") );
d->listView->setResizeMode(TQListView::AllColumns);
d->listView->setAllColumnsShowFocus(true);
d->listView->setSorting(-1);
d->listView->setSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Expanding);
d->listView->setSelectionMode(TQListView::Extended);
d->listView->setMinimumWidth(450);
// ---------------------------------------------------------------
TQGroupBox *settingsBox = new TQGroupBox(0, TQt::Vertical, i18n("Settings"), plainPage());
TQGridLayout *settingsBoxLayout = new TQGridLayout(settingsBox->layout(), 8, 1,
KDialog::spacingHint());
TQPushButton *loadGPXButton = new TQPushButton(i18n("Load GPX File..."), settingsBox);
TQLabel *gpxFileLabel = new TQLabel(i18n("Current GPX file:"), settingsBox);
d->gpxFileName = new KSqueezedTextLabel(i18n("No GPX file"), settingsBox);
d->gpxPointsLabel = new TQLabel(settingsBox);
KSeparator *line = new KSeparator(Horizontal, settingsBox);
TQLabel *maxGapLabel = new TQLabel(i18n("Max. time gap (sec.):"), settingsBox);
d->maxGapInput = new KIntSpinBox(0, 1000000, 1, 30, 10, settingsBox);
TQWhatsThis::add(d->maxGapInput, i18n("<p>Sets the maximum difference in "
"seconds from a GPS track point to the image time to be matched. "
"If the time difference exceeds this setting, no match will be attempted."));
TQLabel *timeZoneLabel = new TQLabel(i18n("Time zone:"), settingsBox);
d->timeZoneCB = new TQComboBox( false, settingsBox );
// See list of time zomes over the world :
// http://en.wikipedia.org/wiki/List_of_time_zones
// NOTE: Combo box strings are not i18n.
d->timeZoneCB->insertItem("GMT-12:00");
d->timeZoneCB->insertItem("GMT-11:00");
d->timeZoneCB->insertItem("GMT-10:00");
d->timeZoneCB->insertItem("GMT-09:30");
d->timeZoneCB->insertItem("GMT-09:00");
d->timeZoneCB->insertItem("GMT-08:00");
d->timeZoneCB->insertItem("GMT-07:00");
d->timeZoneCB->insertItem("GMT-06:00");
d->timeZoneCB->insertItem("GMT-05:30");
d->timeZoneCB->insertItem("GMT-05:00");
d->timeZoneCB->insertItem("GMT-04:30");
d->timeZoneCB->insertItem("GMT-04:00");
d->timeZoneCB->insertItem("GMT-03:30");
d->timeZoneCB->insertItem("GMT-03:00");
d->timeZoneCB->insertItem("GMT-02:00");
d->timeZoneCB->insertItem("GMT-01:00");
d->timeZoneCB->insertItem("GMT+00:00");
d->timeZoneCB->insertItem("GMT+01:00");
d->timeZoneCB->insertItem("GMT+02:00");
d->timeZoneCB->insertItem("GMT+03:00");
d->timeZoneCB->insertItem("GMT+03:30");
d->timeZoneCB->insertItem("GMT+04:00");
d->timeZoneCB->insertItem("GMT+05:00");
d->timeZoneCB->insertItem("GMT+05:30"); // See B.K.O # 149491
d->timeZoneCB->insertItem("GMT+05:45");
d->timeZoneCB->insertItem("GMT+06:00");
d->timeZoneCB->insertItem("GMT+06:30");
d->timeZoneCB->insertItem("GMT+07:00");
d->timeZoneCB->insertItem("GMT+08:00");
d->timeZoneCB->insertItem("GMT+08:45");
d->timeZoneCB->insertItem("GMT+09:00");
d->timeZoneCB->insertItem("GMT+09:30");
d->timeZoneCB->insertItem("GMT+10:00");
d->timeZoneCB->insertItem("GMT+10:30");
d->timeZoneCB->insertItem("GMT+11:00");
d->timeZoneCB->insertItem("GMT+11:30");
d->timeZoneCB->insertItem("GMT+12:00");
d->timeZoneCB->insertItem("GMT+12:45");
d->timeZoneCB->insertItem("GMT+13:00");
d->timeZoneCB->insertItem("GMT+14:00");
TQWhatsThis::add(d->timeZoneCB, i18n("<p>Sets the time zone the camera was set to "
"during photo shooting, so that the time stamps of the images "
"can be converted to GMT to match the GPS time reference.\n"
"Note: positive offsets count eastwards from zero longitude (GMT), "
"they are 'ahead of time'."));
d->interpolateBox = new TQCheckBox(i18n("Interpolate"), settingsBox);
TQWhatsThis::add(d->interpolateBox, i18n("<p>Set this option to interpolate GPS track points "
"which are not closely matched to the GPX data file."));
d->maxTimeLabel = new TQLabel(i18n("Difference in min.:"), settingsBox);
d->maxTimeInput = new KIntSpinBox(0, 240, 1, 15, 10, settingsBox);
TQWhatsThis::add(d->maxTimeInput, i18n("<p>Sets the maximum time difference in minutes (240 max.)"
" to interpolate GPX file points to image time data."));
settingsBoxLayout->addMultiCellWidget(loadGPXButton, 0, 0, 0, 1);
settingsBoxLayout->addMultiCellWidget(gpxFileLabel, 1, 1, 0, 1);
settingsBoxLayout->addMultiCellWidget(d->gpxFileName, 2, 2, 0, 1);
settingsBoxLayout->addMultiCellWidget(d->gpxPointsLabel, 3, 3, 0, 1);
settingsBoxLayout->addMultiCellWidget(line, 4, 4, 0, 1);
settingsBoxLayout->addMultiCellWidget(maxGapLabel, 5, 5, 0, 0);
settingsBoxLayout->addMultiCellWidget(d->maxGapInput, 5, 5, 1, 1);
settingsBoxLayout->addMultiCellWidget(timeZoneLabel, 6, 6, 0, 0);
settingsBoxLayout->addMultiCellWidget(d->timeZoneCB, 6, 6, 1, 1);
settingsBoxLayout->addMultiCellWidget(d->interpolateBox, 7, 7, 0, 1);
settingsBoxLayout->addMultiCellWidget(d->maxTimeLabel, 8, 8, 0, 0);
settingsBoxLayout->addMultiCellWidget(d->maxTimeInput, 8, 8, 1, 1);
// ---------------------------------------------------------------
mainLayout->addMultiCellWidget(d->listView, 0, 2, 0, 1);
mainLayout->addMultiCellWidget(settingsBox, 0, 1, 2, 2);
mainLayout->setColStretch(1, 10);
mainLayout->setRowStretch(2, 10);
// ---------------------------------------------------------------
// About data and help button.
d->about = new KIPIPlugins::KPAboutData(I18N_NOOP("GPS Sync"),
0,
TDEAboutData::License_GPL,
I18N_NOOP("A plugin to synchronize images' metadata with a GPS device"),
"(c) 2006-2008, Gilles Caulier");
d->about->addAuthor("Gilles Caulier", I18N_NOOP("Author and Maintainer"),
"caulier dot gilles at gmail dot com");
KHelpMenu* helpMenu = new KHelpMenu(this, d->about, false);
helpMenu->menu()->removeItemAt(0);
helpMenu->menu()->insertItem(i18n("Plugin Handbook"),
this, TQT_SLOT(slotHelp()), 0, -1, 0);
actionButton(Help)->setPopup( helpMenu->menu() );
// ---------------------------------------------------------------
connect(loadGPXButton, TQT_SIGNAL(released()),
this, TQT_SLOT(slotLoadGPXFile()));
connect(d->interpolateBox, TQT_SIGNAL(toggled(bool)),
d->maxTimeLabel, TQT_SLOT(setEnabled(bool)));
connect(d->interpolateBox, TQT_SIGNAL(toggled(bool)),
d->maxTimeInput, TQT_SLOT(setEnabled(bool)));
readSettings();
}
GPSSyncDialog::~GPSSyncDialog()
{
delete d->about;
delete d;
}
void GPSSyncDialog::slotHelp()
{
TDEApplication::kApplication()->invokeHelp("gpssync", "kipi-plugins");
}
void GPSSyncDialog::setImages( const KURL::List& images )
{
for( KURL::List::ConstIterator it = images.begin(); it != images.end(); ++it )
new GPSListViewItem(d->listView, d->listView->lastItem(), *it);
TDEIO::PreviewJob *thumbnailJob = TDEIO::filePreview(images, 64);
connect(thumbnailJob, TQT_SIGNAL(gotPreview(const KFileItem*, const TQPixmap&)),
this, TQT_SLOT(slotGotThumbnail(const KFileItem*, const TQPixmap&)));
}
void GPSSyncDialog::slotLoadGPXFile()
{
KURL loadGPXFile = KFileDialog::getOpenURL(TDEGlobalSettings::documentPath(),
i18n("%1|GPS Exchange Format").arg("*.gpx"), this,
i18n("Select GPX File to Load") );
if( loadGPXFile.isEmpty() )
return;
d->gpxParser.clear();
bool ret = d->gpxParser.loadGPXFile(loadGPXFile);
if (!ret)
{
KMessageBox::error(this, i18n("Cannot parse %1 GPX file!")
.arg(loadGPXFile.fileName()), i18n("GPS Sync"));
enableButton(User1, false);
return;
}
if (d->gpxParser.numPoints() <= 0)
{
KMessageBox::sorry(this, i18n("The %1 GPX file do not have a date-time track to use!")
.arg(loadGPXFile.fileName()), i18n("GPS Sync"));
enableButton(User1, false);
return;
}
d->gpxFileName->setText(loadGPXFile.fileName());
d->gpxPointsLabel->setText(i18n("Points parsed: %1").arg(d->gpxParser.numPoints()));
enableButton(User1, true);
slotUser1();
}
void GPSSyncDialog::closeEvent(TQCloseEvent *e)
{
if (!e) return;
if (!promptUserClose())
{
e->ignore();
return;
}
saveSettings();
e->accept();
}
void GPSSyncDialog::slotClose()
{
if (!promptUserClose()) return;
saveSettings();
KDialogBase::slotClose();
}
bool GPSSyncDialog::promptUserClose()
{
// Check if one item is dirty in the list.
TQListViewItemIterator it( d->listView );
int dirty = 0;
while ( it.current() )
{
GPSListViewItem *item = (GPSListViewItem*) it.current();
if (item->isDirty())
dirty++;
++it;
}
if (dirty > 0)
{
TQString msg = i18n("1 image from the list is not updated.",
"%n images from the list are not updated.", dirty);
if (KMessageBox::No == KMessageBox::warningYesNo(this,
i18n("<p>%1\n"
"Do you really want to close this window without applying changes?</p>")
.arg(msg)))
return false;
}
return true;
}
void GPSSyncDialog::readSettings()
{
TDEConfig config("kipirc");
config.setGroup("GPS Sync Settings");
d->maxGapInput->setValue(config.readNumEntry("Max Gap Time", 30));
d->timeZoneCB->setCurrentItem(config.readNumEntry("Time Zone", 16)); // GMT+00:00
d->interpolateBox->setChecked(config.readBoolEntry("Interpolate", false));
d->maxTimeInput->setValue(config.readNumEntry("Max Inter Dist Time", 15));
d->maxTimeLabel->setEnabled(d->interpolateBox->isChecked());
d->maxTimeInput->setEnabled(d->interpolateBox->isChecked());
resize(configDialogSize(config, TQString("GPS Sync Dialog")));
}
void GPSSyncDialog::saveSettings()
{
TDEConfig config("kipirc");
config.setGroup("GPS Sync Settings");
config.writeEntry("Max Gap Time", d->maxGapInput->value() );
config.writeEntry("Time Zone", d->timeZoneCB->currentItem() );
config.writeEntry("Interpolate", d->interpolateBox->isChecked() );
config.writeEntry("Max Inter Dist Time", d->maxTimeInput->value() );
saveDialogSize(config, TQString("GPS Sync Dialog"));
config.sync();
}
// Correlate the GPS positions from Pictures using a GPX file data.
void GPSSyncDialog::slotUser1()
{
int itemsUpdated = 0;
TQListViewItemIterator it( d->listView );
while ( it.current() )
{
GPSListViewItem *item = dynamic_cast<GPSListViewItem*>(it.current());
GPSDataContainer gpsData;
TQString tz = d->timeZoneCB->currentText();
int hh = TQString(TQString(tz[4])+TQString(tz[5])).toInt();
int mm = TQString(TQString(tz[7])+TQString(tz[8])).toInt();
int offset = hh*3600 + mm*60;
if (tz[3] == TQChar('-'))
offset = (-1)*offset;
if (d->gpxParser.matchDate(item->dateTime(),
d->maxGapInput->value(),
offset,
d->interpolateBox->isChecked(),
d->maxTimeInput->value()*60,
gpsData))
{
item->setGPSInfo(gpsData);
itemsUpdated++;
}
++it;
}
if (itemsUpdated == 0)
{
KMessageBox::sorry(this, i18n("Cannot find pictures to correlate with GPX file data."),
i18n("GPS Sync"));
return;
}
TQString msg = i18n("GPS data of 1 image have been updated on the list using the GPX data file.",
"GPS data of %n images have been updated on the list using the GPX data file.",
itemsUpdated);
msg += '\n';
msg += i18n("Press Apply button to update images metadata.");
KMessageBox::information(this, msg, i18n("GPS Sync"));
}
// Start the GPS coordinates editor dialog.
void GPSSyncDialog::slotUser2()
{
if (!d->listView->currentItem())
{
KMessageBox::information(this, i18n("Please, select at least one image from "
"the list to edit GPS coordinates manually."), i18n("GPS Sync"));
return;
}
GPSListViewItem *item = dynamic_cast<GPSListViewItem*>(d->listView->currentItem());
GPSEditDialog dlg(this, item->GPSInfo(),
item->url().fileName(),
item->hasGPSInfo());
if (dlg.exec() == KDialogBase::Accepted)
{
TQListViewItemIterator it(d->listView);
while (it.current())
{
if (it.current()->isSelected())
{
GPSListViewItem *selItem = dynamic_cast<GPSListViewItem*>(it.current());
selItem->setGPSInfo(dlg.getGPSInfo(), true, true);
}
++it;
}
}
}
// Remove GPS coordinates from pictures.
void GPSSyncDialog::slotUser3()
{
if (!d->listView->currentItem())
{
KMessageBox::information(this, i18n("Please, select at least one image from "
"the list to remove GPS coordinates."), i18n("GPS Sync"));
return;
}
TQListViewItemIterator it(d->listView);
while (it.current())
{
if (it.current()->isSelected())
{
GPSListViewItem *selItem = dynamic_cast<GPSListViewItem*>(it.current());
selItem->eraseGPSInfo();
}
++it;
}
}
void GPSSyncDialog::slotApply()
{
KURL::List images;
TQListViewItemIterator it( d->listView );
while ( it.current() )
{
GPSListViewItem *selItem = dynamic_cast<GPSListViewItem*>(it.current());
d->listView->setSelected(selItem, true);
d->listView->ensureItemVisible(selItem);
selItem->writeGPSInfoToFile();
images.append(selItem->url());
++it;
kapp->processEvents();
}
// We use kipi interface refreshImages() method to tell to host than
// metadata from pictures have changed and need to be re-readed.
d->interface->refreshImages(images);
}
void GPSSyncDialog::slotGotThumbnail(const KFileItem *item, const TQPixmap& pix)
{
TQListViewItemIterator it(d->listView);
while (it.current())
{
GPSListViewItem *selItem = dynamic_cast<GPSListViewItem*>(it.current());
if (selItem->url() == item->url())
{
selItem->setPixmap(0, pix);
}
++it;
}
}
} // NameSpace KIPIGPSSyncPlugin