/* ============================================================ * * 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 * * 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 #include #include #include #include #include #include #include // KDE includes. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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, Qt::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("

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("

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("

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("

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("

%1\n" "Do you really want to close this window without applying changes?

") .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(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(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(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(it.current()); selItem->eraseGPSInfo(); } ++it; } } void GPSSyncDialog::slotApply() { KURL::List images; TQListViewItemIterator it( d->listView ); while ( it.current() ) { GPSListViewItem *selItem = dynamic_cast(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(it.current()); if (selItem->url() == item->url()) { selItem->setPixmap(0, pix); } ++it; } } } // NameSpace KIPIGPSSyncPlugin