1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2013 Mihail Ivchenko <ematirov@gmail.com>
4 // SPDX-FileCopyrightText: 2014 Sanjiban Bairagya <sanjiban22393@gmail.com>
5 // SPDX-FileCopyrightText: 2014 Illya Kovalevskyy <illya.kovalevskyy@gmail.com>
6 //
7 
8 #include "TourItemDelegate.h"
9 
10 #include <QAbstractTextDocumentLayout>
11 #include <QStyleOptionButton>
12 #include <QPainter>
13 #include <QApplication>
14 #include <QListView>
15 #include <QPointer>
16 
17 #include "MarblePlacemarkModel.h"
18 #include "geodata/data/GeoDataContainer.h"
19 #include "geodata/data/GeoDataFlyTo.h"
20 #include "geodata/data/GeoDataObject.h"
21 #include "geodata/data/GeoDataTourControl.h"
22 #include "geodata/data/GeoDataWait.h"
23 #include "geodata/data/GeoDataCoordinates.h"
24 #include "geodata/data/GeoDataSoundCue.h"
25 #include "geodata/data/GeoDataAnimatedUpdate.h"
26 #include "FlyToEditWidget.h"
27 #include "TourControlEditWidget.h"
28 #include "SoundCueEditWidget.h"
29 #include "WaitEditWidget.h"
30 #include "RemoveItemEditWidget.h"
31 #include "GeoDataPlacemark.h"
32 #include "GeoDataCreate.h"
33 #include "GeoDataUpdate.h"
34 #include "GeoDataDelete.h"
35 #include "GeoDataChange.h"
36 #include "EditPlacemarkDialog.h"
37 #include "MarbleWidget.h"
38 #include "GeoDataPlaylist.h"
39 #include "TourWidget.h"
40 
41 namespace Marble
42 {
43 
TourItemDelegate(QListView * view,MarbleWidget * widget,TourWidget * tour)44 TourItemDelegate::TourItemDelegate( QListView* view, MarbleWidget* widget, TourWidget* tour ):
45                     m_listView( view ), m_widget( widget ), m_editable( true ), m_tourWidget( tour )
46 {
47     QObject::connect( this, SIGNAL(editingChanged(QModelIndex)), m_listView, SLOT(update(QModelIndex)) );
48     m_listView->setEditTriggers( QAbstractItemView::NoEditTriggers );
49 }
50 
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const51 void TourItemDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
52 {
53     QStyleOptionViewItem styleOption = option;
54     styleOption.text = QString();
55     QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &styleOption, painter);
56 
57     QAbstractTextDocumentLayout::PaintContext paintContext;
58     if (styleOption.state & QStyle::State_Selected) {
59         paintContext.palette.setColor(QPalette::Text,
60             styleOption.palette.color(QPalette::Active, QPalette::HighlightedText));
61     }
62 
63     if ( m_listView->currentIndex() == index && m_tourWidget->isPlaying() ) {
64         painter->fillRect( option.rect, paintContext.palette.color( QPalette::Midlight ) );
65         QStyledItemDelegate::paint( painter, option, index );
66     }
67 
68     QTextDocument label;
69     QRect const labelRect = position(Label, option);
70     label.setTextWidth( labelRect.width() );
71     label.setDefaultFont( option.font );
72 
73     QStyleOptionButton button;
74     button.state = option.state;
75     button.palette = option.palette;
76     button.features = QStyleOptionButton::None;
77     button.iconSize = QSize( 16, 16 );
78     button.state &= ~QStyle::State_HasFocus;
79     if( !editable() ) {
80         button.state &= ~QStyle::State_Enabled;
81     }
82 
83     QRect const iconRect = position( GeoDataElementIcon, option );
84 
85     const GeoDataObject *object = qvariant_cast<GeoDataObject*>(index.data(MarblePlacemarkModel::ObjectPointerRole));
86     if (!m_editingIndices.contains(index)) {
87         if (const GeoDataTourControl *tourControl = geodata_cast<GeoDataTourControl>(object)) {
88             GeoDataTourControl::PlayMode const playMode = tourControl->playMode();
89 
90             if ( playMode == GeoDataTourControl::Play ) {
91                 label.setHtml( tr("Play the tour") );
92             } else if ( playMode == GeoDataTourControl::Pause ) {
93                 label.setHtml( tr("Pause the tour") );
94             }
95             painter->save();
96             painter->translate( labelRect.topLeft() );
97             painter->setClipRect( 0, 0, labelRect.width(), labelRect.height() );
98             label.documentLayout()->draw( painter, paintContext );
99             painter->restore();
100             button.icon = QIcon(QStringLiteral(":/marble/document-edit.png"));
101 
102             QRect const buttonRect = position( EditButton, option );
103             button.rect = buttonRect;
104 
105             QIcon const icon = QIcon(QStringLiteral(":/marble/media-playback-pause.png"));
106             painter->drawPixmap( iconRect, icon.pixmap( iconRect.size() ) );
107 
108         } else if (geodata_cast<GeoDataFlyTo>(object)) {
109             GeoDataCoordinates const flyToCoords = index.data( MarblePlacemarkModel::CoordinateRole ).value<GeoDataCoordinates>();
110             label.setHtml( flyToCoords.toString() );
111             button.icon = QIcon(QStringLiteral(":/marble/document-edit.png"));
112 
113             painter->save();
114             painter->translate( labelRect.topLeft() );
115             painter->setClipRect( 0, 0, labelRect.width(), labelRect.height() );
116             label.documentLayout()->draw( painter, paintContext );
117             painter->restore();
118 
119             QRect const buttonRect = position( EditButton, option );
120             button.rect = buttonRect;
121 
122             QIcon const icon = QIcon(QStringLiteral(":/marble/flag.png"));
123             painter->drawPixmap( iconRect, icon.pixmap( iconRect.size() ) );
124         } else if (const GeoDataWait *wait = geodata_cast<GeoDataWait>(object)) {
125             label.setHtml( tr("Wait for %1 seconds").arg( QString::number( wait->duration() ) ) );
126 
127             painter->save();
128             painter->translate( labelRect.topLeft() );
129             painter->setClipRect( 0, 0, labelRect.width(), labelRect.height() );
130             label.documentLayout()->draw( painter, paintContext );
131             painter->restore();
132 
133             button.icon = QIcon(QStringLiteral(":/marble/document-edit.png"));
134 
135             QRect const buttonRect = position( EditButton, option );
136             button.rect = buttonRect;
137 
138             QIcon const icon = QIcon(QStringLiteral(":/marble/player-time.png"));
139             painter->drawPixmap( iconRect, icon.pixmap( iconRect.size() ) );
140         } else if (const GeoDataSoundCue *soundCue = geodata_cast<GeoDataSoundCue>(object)) {
141             label.setHtml(soundCue->href().section(QLatin1Char('/'), -1));
142 
143             painter->save();
144             painter->translate( labelRect.topLeft() );
145             painter->setClipRect( 0, 0, labelRect.width(), labelRect.height() );
146             label.documentLayout()->draw( painter, paintContext );
147             painter->restore();
148 
149             QStyleOptionButton playButton = button;
150 
151             button.icon = QIcon(QStringLiteral(":/marble/document-edit.png"));
152             QRect const buttonRect = position( EditButton, option );
153             button.rect = buttonRect;
154 
155             playButton.icon = QIcon(QStringLiteral(":/marble/playback-play.png"));
156             QRect const playButtonRect = position( ActionButton, option );
157             playButton.rect = playButtonRect;
158             QApplication::style()->drawControl( QStyle::CE_PushButton, &playButton, painter );
159 
160             QIcon const icon = QIcon(QStringLiteral(":/marble/audio-x-generic.png"));
161             painter->drawPixmap( iconRect, icon.pixmap( iconRect.size() ) );
162         } else if (const GeoDataAnimatedUpdate *animUpdate = geodata_cast<GeoDataAnimatedUpdate>(object)) {
163             const GeoDataUpdate *update = animUpdate->update();
164             bool ok = false;
165             QString iconString;
166             if( update && update->create() && update->create()->size() != 0
167                        && (dynamic_cast<const GeoDataContainer *>(&update->create()->first()))) {
168                 const GeoDataContainer *container = static_cast<const GeoDataContainer*>(update->create()->child(0));
169                 if( container->size() > 0 ) {
170                     label.setHtml( tr( "Create item %1" ).arg( container->first().id() ) );
171                     ok = true;
172                     iconString = QStringLiteral(":/icons/add-placemark.png");
173                 }
174             } else if( update && update->getDelete() && update->getDelete()->size() != 0 ){
175                 label.setHtml( tr( "Remove item %1" ).arg( update->getDelete()->first().targetId() ) );
176                 ok = true;
177                 iconString = QStringLiteral(":/icons/remove.png");
178             } else if( update && update->change() && update->change()->size() != 0 ){
179                 label.setHtml( tr( "Change item %1" ).arg( update->change()->first().targetId() ) );
180                 ok = true;
181                 iconString = QStringLiteral(":/marble/document-edit.png");
182             }
183             if( update && !ok ) {
184                 label.setHtml( tr( "Update items" ) );
185                 button.state &= ~QStyle::State_Enabled & ~QStyle::State_Sunken;
186             }
187 
188             painter->save();
189             painter->translate( labelRect.topLeft() );
190             painter->setClipRect( 0, 0, labelRect.width(), labelRect.height() );
191             label.documentLayout()->draw( painter, paintContext );
192             painter->restore();
193 
194             button.icon = QIcon(QStringLiteral(":/marble/document-edit.png"));
195             QRect const buttonRect = position( EditButton, option );
196             button.rect = buttonRect;
197 
198             QIcon const icon = QIcon( iconString );
199             painter->drawPixmap( iconRect, icon.pixmap( iconRect.size() ) );
200         }
201     }
202 
203     QApplication::style()->drawControl( QStyle::CE_PushButton, &button, painter );
204 }
205 
position(Element element,const QStyleOptionViewItem & option)206 QRect TourItemDelegate::position( Element element, const QStyleOptionViewItem &option )
207 {
208     QPoint const topCol1 = option.rect.topLeft() + QPoint(10, 10);
209     QPoint const topCol2 = topCol1 + QPoint(30, 0);
210     QPoint const topCol3 = topCol2 + QPoint(210, 0);
211     QPoint const topCol4 = topCol3 + QPoint(30, 0);
212     QSize const labelSize = QSize(220, 30);
213     QSize const iconsSize = QSize(22, 22);
214 
215     switch(element)
216     {
217     case GeoDataElementIcon:
218         return QRect( topCol1, iconsSize );
219     case Label:
220         return QRect( topCol2, labelSize );
221     case EditButton:
222         return QRect( topCol3, iconsSize );
223     case ActionButton:
224         return QRect( topCol4, iconsSize );
225     }
226     return QRect();
227 }
228 
findIds(const GeoDataPlaylist & playlist,bool onlyFeatures)229 QStringList TourItemDelegate::findIds(const GeoDataPlaylist &playlist, bool onlyFeatures)
230 {
231     QStringList result;
232     for (int i = 0; i < playlist.size(); ++i) {
233         const GeoDataTourPrimitive *primitive = playlist.primitive(i);
234         if( !primitive->id().isEmpty() && !onlyFeatures ) {
235             result << primitive->id();
236         }
237         if (const GeoDataAnimatedUpdate *animatedUpdate = geodata_cast<GeoDataAnimatedUpdate>(primitive)) {
238             if( animatedUpdate->update() != nullptr ) {
239                 const GeoDataUpdate *update = animatedUpdate->update();
240                 if( !update->id().isEmpty() && !onlyFeatures ) {
241                     result << update->id();
242                 }
243                 if( update->create() != nullptr ) {
244                     if( !update->create()->id().isEmpty() && !onlyFeatures ) {
245                         result << update->create()->id();
246                     }
247                     for( int j = 0; j < update->create()->size(); ++j ) {
248                         if( !update->create()->at( j ).id().isEmpty() ) {
249                             result << update->create()->at( j ).id();
250                         }
251                     }
252                 }
253                 if( update->change() != nullptr ) {
254                     if( !update->change()->id().isEmpty() && !onlyFeatures ) {
255                         result << update->change()->id();
256                     }
257                     for( int j = 0; j < update->change()->size(); ++j ) {
258                         if( !update->change()->at( j ).id().isEmpty() ) {
259                             result << update->change()->at( j ).id();
260                         }
261                     }
262                 }
263                 if( update->getDelete() != nullptr ) {
264                     if( !update->getDelete()->id().isEmpty() && !onlyFeatures ) {
265                         result << update->getDelete()->id();
266                     }
267                     for( int j = 0; j < update->getDelete()->size(); ++j ) {
268                         if( !update->getDelete()->at( j ).id().isEmpty() ) {
269                             result << update->getDelete()->at( j ).id();
270                         }
271                     }
272                 }
273             }
274         }
275     }
276     return result;
277 }
278 
playlist() const279 GeoDataPlaylist *TourItemDelegate::playlist() const
280 {
281     QModelIndex const rootIndex = m_listView->rootIndex();
282     if( rootIndex.isValid() ) {
283         GeoDataObject *rootObject = static_cast<GeoDataObject*>( rootIndex.internalPointer() );
284         if (GeoDataPlaylist *playlist = geodata_cast<GeoDataPlaylist>(rootObject)) {
285             return playlist;
286         }
287     }
288     return nullptr;
289 }
290 
291 
sizeHint(const QStyleOptionViewItem & option,const QModelIndex & index) const292 QSize TourItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
293 {
294     Q_UNUSED( option );
295     Q_UNUSED( index );
296     return QSize(290,50);
297 }
298 
createEditor(QWidget * parent,const QStyleOptionViewItem & option,const QModelIndex & index) const299 QWidget* TourItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
300 {
301     Q_UNUSED( option );
302     GeoDataObject *object = qvariant_cast<GeoDataObject*>(index.data( MarblePlacemarkModel::ObjectPointerRole ) );
303     if (geodata_cast<GeoDataFlyTo>(object)) {
304         FlyToEditWidget* widget = new FlyToEditWidget(index, m_widget, parent);
305         widget->setFirstFlyTo( m_firstFlyTo );
306         connect(widget, SIGNAL(editingDone(QModelIndex)), this, SLOT(closeEditor(QModelIndex)));
307         connect( this, SIGNAL(editableChanged(bool)), widget, SLOT(setEditable(bool)) );
308         connect( this, SIGNAL(firstFlyToChanged(QPersistentModelIndex)), widget, SLOT(setFirstFlyTo(QPersistentModelIndex)) );
309         return widget;
310 
311     } else if (geodata_cast<GeoDataTourControl>(object)) {
312         TourControlEditWidget* widget = new TourControlEditWidget(index, parent);
313         connect(widget, SIGNAL(editingDone(QModelIndex)), this, SLOT(closeEditor(QModelIndex)));
314         connect( this, SIGNAL(editableChanged(bool)), widget, SLOT(setEditable(bool)) );
315         return widget;
316 
317     } else if (geodata_cast<GeoDataWait>(object)) {
318         WaitEditWidget* widget = new WaitEditWidget(index, parent);
319         connect(widget, SIGNAL(editingDone(QModelIndex)), this, SLOT(closeEditor(QModelIndex)));
320         connect( this, SIGNAL(editableChanged(bool)), widget, SLOT(setEditable(bool)) );
321         return widget;
322 
323     } else if (geodata_cast<GeoDataSoundCue>(object)) {
324         SoundCueEditWidget* widget = new SoundCueEditWidget(index, parent);
325         connect(widget, SIGNAL(editingDone(QModelIndex)), this, SLOT(closeEditor(QModelIndex)));
326         connect( this, SIGNAL(editableChanged(bool)), widget, SLOT(setEditable(bool)) );
327         return widget;
328 
329     } else if (geodata_cast<GeoDataAnimatedUpdate>(object)) {
330         RemoveItemEditWidget* widget = new RemoveItemEditWidget(index, parent);
331         GeoDataPlaylist *playlistObject = playlist();
332         if( playlistObject != nullptr ) {
333             widget->setFeatureIds(findIds(*playlistObject));
334         }
335         widget->setDefaultFeatureId( m_defaultFeatureId );
336         connect(widget, SIGNAL(editingDone(QModelIndex)), this, SLOT(closeEditor(QModelIndex)));
337         connect( this, SIGNAL(editableChanged(bool)), widget, SLOT(setEditable(bool)) );
338         connect( this, SIGNAL(featureIdsChanged(QStringList)), widget, SLOT(setFeatureIds(QStringList)) );
339         connect( this, SIGNAL(defaultFeatureIdChanged(QString)), widget, SLOT(setDefaultFeatureId(QString)) );
340         return widget;
341 
342     }
343     return nullptr;
344 }
345 
editable() const346 bool TourItemDelegate::editable() const
347 {
348     return m_editable;
349 }
350 
setEditable(bool editable)351 void TourItemDelegate::setEditable( bool editable )
352 {
353     if( m_editable != editable ) {
354         m_editable = editable;
355         emit editableChanged( m_editable );
356     }
357 }
358 
firstFlyTo() const359 QModelIndex TourItemDelegate::firstFlyTo() const
360 {
361     return m_firstFlyTo;
362 }
363 
editAnimatedUpdate(GeoDataAnimatedUpdate * animatedUpdate,bool create)364 bool TourItemDelegate::editAnimatedUpdate(GeoDataAnimatedUpdate *animatedUpdate, bool create)
365 {
366     if( animatedUpdate->update() == nullptr ) {
367         return false;
368     }
369     GeoDataFeature *feature = nullptr;
370     if( create && !( animatedUpdate->update()->create() == nullptr || animatedUpdate->update()->create()->size() == 0 ) ) {
371         GeoDataContainer *container = dynamic_cast<GeoDataContainer*>( animatedUpdate->update()->create()->child( 0 ) );
372         if( container != nullptr && container->size() ) {
373             feature = container->child( 0 );
374         }
375     } else if ( !create && !( animatedUpdate->update()->change() == nullptr || animatedUpdate->update()->change()->size() == 0 ) ) {
376         GeoDataContainer *container = dynamic_cast<GeoDataContainer*>( animatedUpdate->update()->change()->child( 0 ) );
377         if( container != nullptr && container->size() ) {
378             feature = container->child( 0 );
379         }
380     }
381     if( feature == nullptr ) {
382         return false;
383     }
384 
385     QStringList ids;
386 
387     GeoDataPlacemark *placemark = static_cast<GeoDataPlacemark*>( feature );
388 
389     if( !create ) {
390         if( placemark->targetId().isEmpty() && !defaultFeatureId().isEmpty() ) {
391             GeoDataFeature *feature = findFeature( defaultFeatureId() );
392             if (GeoDataPlacemark *targetPlacemark = (feature != nullptr ? geodata_cast<GeoDataPlacemark>(feature) : nullptr)) {
393                 animatedUpdate->update()->change()->placemarkList().remove( 0 );
394                 delete placemark;
395                 placemark = new GeoDataPlacemark( *targetPlacemark );
396                 animatedUpdate->update()->change()->placemarkList().insert( 0, placemark );
397                 placemark->setTargetId( defaultFeatureId() );
398                 placemark->setId(QString());
399             }
400         }
401     }
402 
403     QPointer<EditPlacemarkDialog> dialog = new EditPlacemarkDialog( placemark, nullptr, m_widget );
404     if( create ) {
405         dialog->setWindowTitle( QObject::tr( "Add Placemark to Tour" ) );
406     } else {
407         dialog->setWindowTitle( QObject::tr( "Change Placemark in Tour" ) );
408         dialog->setTargetIdFieldVisible( true );
409         dialog->setIdFieldVisible( false );
410     }
411     GeoDataPlaylist* playlistObject = playlist();
412     if( playlistObject != nullptr ) {
413         ids.append(findIds(*playlistObject, true));
414     }
415     ids.removeOne( placemark->id() );
416     if( create ) {
417         dialog->setIdFilter( ids );
418     } else {
419         dialog->setTargetIds( ids );
420     }
421     bool status = dialog->exec();
422     if( !create ) {
423         placemark->setId(QString());
424     }
425     return status;
426 }
427 
defaultFeatureId() const428 QString TourItemDelegate::defaultFeatureId() const
429 {
430     return m_defaultFeatureId;
431 }
432 
433 
434 
findFeature(const QString & id) const435 GeoDataFeature *TourItemDelegate::findFeature(const QString &id) const
436 {
437     GeoDataPlaylist *playlistObject = playlist();
438     if( playlistObject == nullptr ) {
439         return nullptr;
440     }
441     GeoDataFeature *result = nullptr;
442     for( int i = 0; i < playlistObject->size(); ++i ) {
443         GeoDataTourPrimitive *primitive = playlistObject->primitive( i );
444         if (GeoDataAnimatedUpdate *animatedUpdate = geodata_cast<GeoDataAnimatedUpdate>(primitive)) {
445             if( animatedUpdate->update() != nullptr ) {
446                 GeoDataUpdate *update = animatedUpdate->update();
447                 if( update->create() != nullptr ) {
448                     for( int j = 0; j < update->create()->featureList().size(); ++j ) {
449                         if( update->create()->at( j ).id() == id ) {
450                             result = update->create()->featureList().at( j );
451                         }
452                     }
453                 }
454                 if( update->change() != nullptr ) {
455                     for( int j = 0; j < update->change()->featureList().size(); ++j ) {
456                         if( update->change()->at( j ).id() == id ) {
457                             result = update->change()->featureList().at( j );
458                         }
459                     }
460                 }
461                 if( update->getDelete() != nullptr ) {
462                     for( int j = 0; j < update->getDelete()->featureList().size(); ++j ) {
463                         if( update->getDelete()->at( j ).id() == id ) {
464                             result = update->getDelete()->featureList().at( j );
465                         }
466                     }
467                 }
468             }
469         }
470     }
471     return result;
472 }
473 
setFirstFlyTo(const QPersistentModelIndex & index)474 void TourItemDelegate::setFirstFlyTo(const QPersistentModelIndex &index )
475 {
476     m_firstFlyTo = index;
477     emit firstFlyToChanged( m_firstFlyTo );
478 }
479 
setDefaultFeatureId(const QString & id)480 void TourItemDelegate::setDefaultFeatureId(const QString &id)
481 {
482     m_defaultFeatureId = id;
483     const QStringList ids = playlist() ? findIds(*playlist()) : QStringList();
484     emit featureIdsChanged( ids );
485     emit defaultFeatureIdChanged( id );
486 }
487 
editorEvent(QEvent * event,QAbstractItemModel * model,const QStyleOptionViewItem & option,const QModelIndex & index)488 bool TourItemDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index )
489 {
490     Q_UNUSED( model );
491     if ( ( event->type() == QEvent::MouseButtonRelease ) && editable() ) {
492         QMouseEvent *mouseEvent = static_cast<QMouseEvent*>( event );
493         QRect editRect = position( EditButton, option );
494         if ( editRect.contains( mouseEvent->pos() ) ) {
495             if( m_editingIndices.contains( index ) ) {
496                 m_editingIndices.removeOne( index );
497                 emit editingChanged( index );
498                 return true;
499             }else{
500                 GeoDataObject *object = qvariant_cast<GeoDataObject*>(index.data( MarblePlacemarkModel::ObjectPointerRole ) );
501                 if (GeoDataAnimatedUpdate *animatedUpdate = geodata_cast<GeoDataAnimatedUpdate>(object)) {
502                     if( animatedUpdate->update() && animatedUpdate->update()->create() ) {
503                         if( editAnimatedUpdate( animatedUpdate ) ) {
504                             setDefaultFeatureId( m_defaultFeatureId );
505                         }
506                     } else if( animatedUpdate->update() && animatedUpdate->update()->change() ) {
507                         editAnimatedUpdate( animatedUpdate, false );
508                     } else if ( animatedUpdate->update() && animatedUpdate->update()->getDelete() ) {
509                         m_editingIndices.append( index );
510                         m_listView->openPersistentEditor( index );
511                     }
512                 } else {
513                     m_editingIndices.append( index );
514                     m_listView->openPersistentEditor( index );
515                 }
516             }
517             emit editingChanged( index );
518             return true;
519         }
520     }
521     return false;
522 }
523 
closeEditor(const QModelIndex & index)524 void TourItemDelegate::closeEditor( const QModelIndex &index )
525 {
526     emit edited( index );
527     m_listView->closePersistentEditor( index );
528     m_editingIndices.removeOne( index );
529 }
530 
531 }
532 
533 #include "moc_TourItemDelegate.cpp"
534