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