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 <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
41namespace Marble
42{
43
44TourItemDelegate::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
51void TourItemDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
52{
53 QStyleOptionViewItem styleOption = option;
54 styleOption.text = QString();
56
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
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
204}
205
206QRect 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
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 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
279GeoDataPlaylist *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
292QSize 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
299QWidget* 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
346bool TourItemDelegate::editable() const
347{
348 return m_editable;
349}
350
351void TourItemDelegate::setEditable( bool editable )
352{
353 if( m_editable != editable ) {
354 m_editable = editable;
355 emit editableChanged( m_editable );
356 }
357}
358
359QModelIndex TourItemDelegate::firstFlyTo() const
360{
361 return m_firstFlyTo;
362}
363
364bool 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
428QString TourItemDelegate::defaultFeatureId() const
429{
430 return m_defaultFeatureId;
431}
432
433
434
435GeoDataFeature *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
474void TourItemDelegate::setFirstFlyTo(const QPersistentModelIndex &index )
475{
476 m_firstFlyTo = index;
477 emit firstFlyToChanged( m_firstFlyTo );
478}
479
480void 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
488bool 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
524void 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"
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)
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
QPoint pos() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
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 Tue Mar 26 2024 11:18:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.