Marble

TourItemDelegate.cpp
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 <QApplication>
12#include <QListView>
13#include <QPainter>
14#include <QPointer>
15#include <QStyleOptionButton>
16
17#include "EditPlacemarkDialog.h"
18#include "FlyToEditWidget.h"
19#include "GeoDataChange.h"
20#include "GeoDataCreate.h"
21#include "GeoDataDelete.h"
22#include "GeoDataPlacemark.h"
23#include "GeoDataPlaylist.h"
24#include "GeoDataUpdate.h"
25#include "MarblePlacemarkModel.h"
26#include "MarbleWidget.h"
27#include "RemoveItemEditWidget.h"
28#include "SoundCueEditWidget.h"
29#include "TourControlEditWidget.h"
30#include "TourWidget.h"
31#include "WaitEditWidget.h"
32#include "geodata/data/GeoDataAnimatedUpdate.h"
33#include "geodata/data/GeoDataContainer.h"
34#include "geodata/data/GeoDataCoordinates.h"
35#include "geodata/data/GeoDataFlyTo.h"
36#include "geodata/data/GeoDataObject.h"
37#include "geodata/data/GeoDataSoundCue.h"
38#include "geodata/data/GeoDataTourControl.h"
39#include "geodata/data/GeoDataWait.h"
40
41namespace Marble
42{
43
44TourItemDelegate::TourItemDelegate(QListView *view, MarbleWidget *widget, TourWidget *tour)
45 : m_listView(view)
46 , m_widget(widget)
47 , m_editable(true)
48 , m_tourWidget(tour)
49{
50 connect(this, SIGNAL(editingChanged(QModelIndex)), m_listView, SLOT(update(QModelIndex)));
51 m_listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
52}
53
54void TourItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
55{
56 QStyleOptionViewItem styleOption = option;
57 styleOption.text = QString();
59
61 if (styleOption.state & QStyle::State_Selected) {
62 paintContext.palette.setColor(QPalette::Text, styleOption.palette.color(QPalette::Active, QPalette::HighlightedText));
63 }
64
65 if (m_listView->currentIndex() == index && m_tourWidget->isPlaying()) {
66 painter->fillRect(option.rect, paintContext.palette.color(QPalette::Midlight));
67 QStyledItemDelegate::paint(painter, option, index);
68 }
69
71 QRect const labelRect = position(Label, option);
72 label.setTextWidth(labelRect.width());
73 label.setDefaultFont(option.font);
74
75 QStyleOptionButton button;
76 button.state = option.state;
77 button.palette = option.palette;
78 button.features = QStyleOptionButton::None;
79 button.iconSize = QSize(16, 16);
80 button.state &= ~QStyle::State_HasFocus;
81 if (!editable()) {
82 button.state &= ~QStyle::State_Enabled;
83 }
84
85 QRect const iconRect = position(GeoDataElementIcon, option);
86
87 const GeoDataObject *object = qvariant_cast<GeoDataObject *>(index.data(MarblePlacemarkModel::ObjectPointerRole));
88 if (!m_editingIndices.contains(index)) {
89 if (const auto tourControl = geodata_cast<GeoDataTourControl>(object)) {
90 GeoDataTourControl::PlayMode const playMode = tourControl->playMode();
91
92 if (playMode == GeoDataTourControl::Play) {
93 label.setHtml(tr("Play the tour"));
94 } else if (playMode == GeoDataTourControl::Pause) {
95 label.setHtml(tr("Pause the tour"));
96 }
97 painter->save();
98 painter->translate(labelRect.topLeft());
99 painter->setClipRect(0, 0, labelRect.width(), labelRect.height());
100 label.documentLayout()->draw(painter, paintContext);
101 painter->restore();
102 button.icon = QIcon(QStringLiteral(":/marble/document-edit.png"));
103
104 QRect const buttonRect = position(EditButton, option);
105 button.rect = buttonRect;
106
107 QIcon const icon = QIcon(QStringLiteral(":/marble/media-playback-pause.png"));
108 painter->drawPixmap(iconRect, icon.pixmap(iconRect.size()));
109
110 } else if (geodata_cast<GeoDataFlyTo>(object)) {
111 auto const flyToCoords = index.data(MarblePlacemarkModel::CoordinateRole).value<GeoDataCoordinates>();
112 label.setHtml(flyToCoords.toString());
113 button.icon = QIcon(QStringLiteral(":/marble/document-edit.png"));
114
115 painter->save();
116 painter->translate(labelRect.topLeft());
117 painter->setClipRect(0, 0, labelRect.width(), labelRect.height());
118 label.documentLayout()->draw(painter, paintContext);
119 painter->restore();
120
121 QRect const buttonRect = position(EditButton, option);
122 button.rect = buttonRect;
123
124 QIcon const icon = QIcon(QStringLiteral(":/marble/flag.png"));
125 painter->drawPixmap(iconRect, icon.pixmap(iconRect.size()));
126 } else if (const auto wait = geodata_cast<GeoDataWait>(object)) {
127 label.setHtml(tr("Wait for %1 seconds").arg(QString::number(wait->duration())));
128
129 painter->save();
130 painter->translate(labelRect.topLeft());
131 painter->setClipRect(0, 0, labelRect.width(), labelRect.height());
132 label.documentLayout()->draw(painter, paintContext);
133 painter->restore();
134
135 button.icon = QIcon(QStringLiteral(":/marble/document-edit.png"));
136
137 QRect const buttonRect = position(EditButton, option);
138 button.rect = buttonRect;
139
140 QIcon const icon = QIcon(QStringLiteral(":/marble/player-time.png"));
141 painter->drawPixmap(iconRect, icon.pixmap(iconRect.size()));
142 } else if (const auto soundCue = geodata_cast<GeoDataSoundCue>(object)) {
143 label.setHtml(soundCue->href().section(QLatin1Char('/'), -1));
144
145 painter->save();
146 painter->translate(labelRect.topLeft());
147 painter->setClipRect(0, 0, labelRect.width(), labelRect.height());
148 label.documentLayout()->draw(painter, paintContext);
149 painter->restore();
150
151 QStyleOptionButton playButton = button;
152
153 button.icon = QIcon(QStringLiteral(":/marble/document-edit.png"));
154 QRect const buttonRect = position(EditButton, option);
155 button.rect = buttonRect;
156
157 playButton.icon = QIcon(QStringLiteral(":/marble/playback-play.png"));
158 QRect const playButtonRect = position(ActionButton, option);
159 playButton.rect = playButtonRect;
160 QApplication::style()->drawControl(QStyle::CE_PushButton, &playButton, painter);
161
162 QIcon const icon = QIcon(QStringLiteral(":/marble/audio-x-generic.png"));
163 painter->drawPixmap(iconRect, icon.pixmap(iconRect.size()));
164 } else if (const auto animUpdate = geodata_cast<GeoDataAnimatedUpdate>(object)) {
165 const GeoDataUpdate *update = animUpdate->update();
166 bool ok = false;
167 QString iconString;
168 if (update && update->create() && update->create()->size() != 0 && (dynamic_cast<const GeoDataContainer *>(&update->create()->first()))) {
169 const auto container = static_cast<const GeoDataContainer *>(update->create()->child(0));
170 if (container->size() > 0) {
171 label.setHtml(tr("Create item %1").arg(container->first().id()));
172 ok = true;
173 iconString = QStringLiteral(":/icons/add-placemark.png");
174 }
175 } else if (update && update->getDelete() && update->getDelete()->size() != 0) {
176 label.setHtml(tr("Remove item %1").arg(update->getDelete()->first().targetId()));
177 ok = true;
178 iconString = QStringLiteral(":/icons/remove.png");
179 } else if (update && update->change() && update->change()->size() != 0) {
180 label.setHtml(tr("Change item %1").arg(update->change()->first().targetId()));
181 ok = true;
182 iconString = QStringLiteral(":/marble/document-edit.png");
183 }
184 if (update && !ok) {
185 label.setHtml(tr("Update items"));
186 button.state &= ~QStyle::State_Enabled & ~QStyle::State_Sunken;
187 }
188
189 painter->save();
190 painter->translate(labelRect.topLeft());
191 painter->setClipRect(0, 0, labelRect.width(), labelRect.height());
192 label.documentLayout()->draw(painter, paintContext);
193 painter->restore();
194
195 button.icon = QIcon(QStringLiteral(":/marble/document-edit.png"));
196 QRect const buttonRect = position(EditButton, option);
197 button.rect = buttonRect;
198
199 QIcon const icon = QIcon(iconString);
200 painter->drawPixmap(iconRect, icon.pixmap(iconRect.size()));
201 }
202 }
203
205}
206
207QRect TourItemDelegate::position(Element element, const QStyleOptionViewItem &option)
208{
209 QPoint const topCol1 = option.rect.topLeft() + QPoint(10, 10);
210 QPoint const topCol2 = topCol1 + QPoint(30, 0);
211 QPoint const topCol3 = topCol2 + QPoint(210, 0);
212 QPoint const topCol4 = topCol3 + QPoint(30, 0);
213 QSize const labelSize = QSize(220, 30);
214 QSize const iconsSize = QSize(22, 22);
215
216 switch (element) {
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 {};
227}
228
229QStringList 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 auto 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
279GeoDataPlaylist *TourItemDelegate::playlist() const
280{
281 QModelIndex const rootIndex = m_listView->rootIndex();
282 if (rootIndex.isValid()) {
283 auto rootObject = static_cast<GeoDataObject *>(rootIndex.internalPointer());
284 if (auto playlist = geodata_cast<GeoDataPlaylist>(rootObject)) {
285 return playlist;
286 }
287 }
288 return nullptr;
289}
290
291QSize TourItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
292{
293 Q_UNUSED(option);
294 Q_UNUSED(index);
295 return {290, 50};
296}
297
298QWidget *TourItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
299{
300 Q_UNUSED(option);
301 auto object = qvariant_cast<GeoDataObject *>(index.data(MarblePlacemarkModel::ObjectPointerRole));
302 if (geodata_cast<GeoDataFlyTo>(object)) {
303 auto widget = new FlyToEditWidget(index, m_widget, parent);
304 widget->setFirstFlyTo(m_firstFlyTo);
305 connect(widget, SIGNAL(editingDone(QModelIndex)), this, SLOT(closeEditor(QModelIndex)));
306 connect(this, SIGNAL(editableChanged(bool)), widget, SLOT(setEditable(bool)));
307 connect(this, SIGNAL(firstFlyToChanged(QPersistentModelIndex)), widget, SLOT(setFirstFlyTo(QPersistentModelIndex)));
308 return widget;
309
310 } else if (geodata_cast<GeoDataTourControl>(object)) {
311 auto widget = new TourControlEditWidget(index, parent);
312 connect(widget, SIGNAL(editingDone(QModelIndex)), this, SLOT(closeEditor(QModelIndex)));
313 connect(this, SIGNAL(editableChanged(bool)), widget, SLOT(setEditable(bool)));
314 return widget;
315
316 } else if (geodata_cast<GeoDataWait>(object)) {
317 auto widget = new WaitEditWidget(index, parent);
318 connect(widget, SIGNAL(editingDone(QModelIndex)), this, SLOT(closeEditor(QModelIndex)));
319 connect(this, SIGNAL(editableChanged(bool)), widget, SLOT(setEditable(bool)));
320 return widget;
321
322 } else if (geodata_cast<GeoDataSoundCue>(object)) {
323 auto widget = new SoundCueEditWidget(index, parent);
324 connect(widget, SIGNAL(editingDone(QModelIndex)), this, SLOT(closeEditor(QModelIndex)));
325 connect(this, SIGNAL(editableChanged(bool)), widget, SLOT(setEditable(bool)));
326 return widget;
327
328 } else if (geodata_cast<GeoDataAnimatedUpdate>(object)) {
329 auto widget = new RemoveItemEditWidget(index, parent);
330 GeoDataPlaylist *playlistObject = playlist();
331 if (playlistObject != nullptr) {
332 widget->setFeatureIds(findIds(*playlistObject));
333 }
334 widget->setDefaultFeatureId(m_defaultFeatureId);
335 connect(widget, SIGNAL(editingDone(QModelIndex)), this, SLOT(closeEditor(QModelIndex)));
336 connect(this, SIGNAL(editableChanged(bool)), widget, SLOT(setEditable(bool)));
337 connect(this, SIGNAL(featureIdsChanged(QStringList)), widget, SLOT(setFeatureIds(QStringList)));
338 connect(this, SIGNAL(defaultFeatureIdChanged(QString)), widget, SLOT(setDefaultFeatureId(QString)));
339 return widget;
340 }
341 return nullptr;
342}
343
344bool TourItemDelegate::editable() const
345{
346 return m_editable;
347}
348
349void TourItemDelegate::setEditable(bool editable)
350{
351 if (m_editable != editable) {
352 m_editable = editable;
353 Q_EMIT editableChanged(m_editable);
354 }
355}
356
357QModelIndex TourItemDelegate::firstFlyTo() const
358{
359 return m_firstFlyTo;
360}
361
362bool TourItemDelegate::editAnimatedUpdate(GeoDataAnimatedUpdate *animatedUpdate, bool create)
363{
364 if (animatedUpdate->update() == nullptr) {
365 return false;
366 }
367 GeoDataFeature *feature = nullptr;
368 if (create && !(animatedUpdate->update()->create() == nullptr || animatedUpdate->update()->create()->size() == 0)) {
369 auto container = dynamic_cast<GeoDataContainer *>(animatedUpdate->update()->create()->child(0));
370 if (container != nullptr && container->size()) {
371 feature = container->child(0);
372 }
373 } else if (!create && !(animatedUpdate->update()->change() == nullptr || animatedUpdate->update()->change()->size() == 0)) {
374 auto container = dynamic_cast<GeoDataContainer *>(animatedUpdate->update()->change()->child(0));
375 if (container != nullptr && container->size()) {
376 feature = container->child(0);
377 }
378 }
379 if (feature == nullptr) {
380 return false;
381 }
382
383 QStringList ids;
384
385 auto placemark = static_cast<GeoDataPlacemark *>(feature);
386
387 if (!create) {
388 if (placemark->targetId().isEmpty() && !defaultFeatureId().isEmpty()) {
389 GeoDataFeature *feature = findFeature(defaultFeatureId());
390 if (GeoDataPlacemark *targetPlacemark = (feature != nullptr ? geodata_cast<GeoDataPlacemark>(feature) : nullptr)) {
391 animatedUpdate->update()->change()->placemarkList().remove(0);
392 delete placemark;
393 placemark = new GeoDataPlacemark(*targetPlacemark);
394 animatedUpdate->update()->change()->placemarkList().insert(0, placemark);
395 placemark->setTargetId(defaultFeatureId());
396 placemark->setId(QString());
397 }
398 }
399 }
400
401 QPointer<EditPlacemarkDialog> dialog = new EditPlacemarkDialog(placemark, nullptr, m_widget);
402 if (create) {
403 dialog->setWindowTitle(QObject::tr("Add Placemark to Tour"));
404 } else {
405 dialog->setWindowTitle(QObject::tr("Change Placemark in Tour"));
406 dialog->setTargetIdFieldVisible(true);
407 dialog->setIdFieldVisible(false);
408 }
409 GeoDataPlaylist *playlistObject = playlist();
410 if (playlistObject != nullptr) {
411 ids.append(findIds(*playlistObject, true));
412 }
413 ids.removeOne(placemark->id());
414 if (create) {
415 dialog->setIdFilter(ids);
416 } else {
417 dialog->setTargetIds(ids);
418 }
419 bool status = dialog->exec();
420 if (!create) {
421 placemark->setId(QString());
422 }
423 return status;
424}
425
426QString TourItemDelegate::defaultFeatureId() const
427{
428 return m_defaultFeatureId;
429}
430
431GeoDataFeature *TourItemDelegate::findFeature(const QString &id) const
432{
433 GeoDataPlaylist *playlistObject = playlist();
434 if (playlistObject == nullptr) {
435 return nullptr;
436 }
437 GeoDataFeature *result = nullptr;
438 for (int i = 0; i < playlistObject->size(); ++i) {
439 GeoDataTourPrimitive *primitive = playlistObject->primitive(i);
440 if (auto animatedUpdate = geodata_cast<GeoDataAnimatedUpdate>(primitive)) {
441 if (animatedUpdate->update() != nullptr) {
442 GeoDataUpdate *update = animatedUpdate->update();
443 if (update->create() != nullptr) {
444 for (int j = 0; j < update->create()->featureList().size(); ++j) {
445 if (update->create()->at(j).id() == id) {
446 result = update->create()->featureList().at(j);
447 }
448 }
449 }
450 if (update->change() != nullptr) {
451 for (int j = 0; j < update->change()->featureList().size(); ++j) {
452 if (update->change()->at(j).id() == id) {
453 result = update->change()->featureList().at(j);
454 }
455 }
456 }
457 if (update->getDelete() != nullptr) {
458 for (int j = 0; j < update->getDelete()->featureList().size(); ++j) {
459 if (update->getDelete()->at(j).id() == id) {
460 result = update->getDelete()->featureList().at(j);
461 }
462 }
463 }
464 }
465 }
466 }
467 return result;
468}
469
470void TourItemDelegate::setFirstFlyTo(const QPersistentModelIndex &index)
471{
472 m_firstFlyTo = index;
473 Q_EMIT firstFlyToChanged(m_firstFlyTo);
474}
475
476void TourItemDelegate::setDefaultFeatureId(const QString &id)
477{
478 m_defaultFeatureId = id;
479 const QStringList ids = playlist() ? findIds(*playlist()) : QStringList();
480 Q_EMIT featureIdsChanged(ids);
481 Q_EMIT defaultFeatureIdChanged(id);
482}
483
484bool TourItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
485{
486 Q_UNUSED(model);
487 if ((event->type() == QEvent::MouseButtonRelease) && editable()) {
488 auto mouseEvent = static_cast<QMouseEvent *>(event);
489 QRect editRect = position(EditButton, option);
490 if (editRect.contains(mouseEvent->pos())) {
491 if (m_editingIndices.contains(index)) {
492 m_editingIndices.removeOne(index);
493 Q_EMIT editingChanged(index);
494 return true;
495 } else {
496 auto object = qvariant_cast<GeoDataObject *>(index.data(MarblePlacemarkModel::ObjectPointerRole));
497 if (auto animatedUpdate = geodata_cast<GeoDataAnimatedUpdate>(object)) {
498 if (animatedUpdate->update() && animatedUpdate->update()->create()) {
499 if (editAnimatedUpdate(animatedUpdate)) {
500 setDefaultFeatureId(m_defaultFeatureId);
501 }
502 } else if (animatedUpdate->update() && animatedUpdate->update()->change()) {
503 editAnimatedUpdate(animatedUpdate, false);
504 } else if (animatedUpdate->update() && animatedUpdate->update()->getDelete()) {
505 m_editingIndices.append(index);
506 m_listView->openPersistentEditor(index);
507 }
508 } else {
509 m_editingIndices.append(index);
510 m_listView->openPersistentEditor(index);
511 }
512 }
513 Q_EMIT editingChanged(index);
514 return true;
515 }
516 }
517 return false;
518}
519
520void TourItemDelegate::closeEditor(const QModelIndex &index)
521{
522 Q_EMIT edited(index);
523 m_listView->closePersistentEditor(index);
524 m_editingIndices.removeOne(index);
525}
526
527}
528
529#include "moc_TourItemDelegate.cpp"
This file contains the headers for MarbleWidget.
Q_SCRIPTABLE CaptureState status()
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
void update(Part *part, const QByteArray &data, qint64 dataSize)
QAction * create(StandardAction id, const Receiver *recvr, Func slot, QObject *parent, std::optional< Qt::ConnectionType > connectionType=std::nullopt)
QString label(StandardShortcut id)
Binds a QML item to a specific geodetic location in screen coordinates.
QStyle * style()
MouseButtonRelease
QPixmap pixmap(QWindow *window, const QSize &size, Mode mode, State state) const const
void append(QList< T > &&value)
bool removeOne(const AT &t)
QVariant data(int role) const const
void * internalPointer() const const
bool isValid() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
void drawPixmap(const QPoint &point, const QPixmap &pixmap)
void fillRect(const QRect &rectangle, QGradient::Preset preset)
void restore()
void save()
void setClipRect(const QRect &rectangle, Qt::ClipOperation operation)
void translate(const QPoint &offset)
bool contains(const QPoint &point, bool proper) const const
int height() const const
QSize size() const const
QPoint topLeft() const const
int width() const const
QString number(double n, char format, int precision)
virtual void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const const=0
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const const override
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:04:14 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.