File indexing completed on 2024-04-21 03:49:27

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2009 Bastian Holst <bastianholst@gmx.de>
0004 //
0005 
0006 // Self
0007 #include "AbstractDataPluginModel.h"
0008 
0009 // Qt
0010 #include <QUrl>
0011 #include <QTimer>
0012 #include <QPointF>
0013 #include <QRectF>
0014 #include <QtAlgorithms>
0015 #include <QVariant>
0016 #include <QAbstractListModel>
0017 #include <QMetaProperty>
0018 
0019 // Marble
0020 #include "MarbleDebug.h"
0021 #include "AbstractDataPluginItem.h"
0022 #include "CacheStoragePolicy.h"
0023 #include "GeoDataCoordinates.h"
0024 #include "GeoDataLatLonAltBox.h"
0025 #include "HttpDownloadManager.h"
0026 #include "MarbleModel.h"
0027 #include "MarbleDirs.h"
0028 #include "ViewportParams.h"
0029 
0030 #include <cmath>
0031 
0032 namespace Marble
0033 {
0034 
0035 const QString descriptionPrefix( "description_" );
0036 
0037 // Time between two tried description file downloads (we decided not to download anything) in ms
0038 const int timeBetweenTriedDownloads = 500;
0039 // Time between two real description file downloads in ms
0040 const int timeBetweenDownloads = 1500;
0041 
0042 // The factor describing how much the box has to be changed to download a new description file.
0043 // A higher factor means more downloads.
0044 const qreal boxComparisonFactor = 16.0;
0045 
0046 // Separator to separate the id of the item from the file type
0047 const QChar fileIdSeparator = QLatin1Char('_');
0048 
0049 class FavoritesModel;
0050 
0051 class AbstractDataPluginModelPrivate
0052 {
0053 public:
0054     AbstractDataPluginModelPrivate( const QString& name,
0055                                     const MarbleModel *marbleModel,
0056                                     AbstractDataPluginModel * parent );
0057     
0058     ~AbstractDataPluginModelPrivate();
0059 
0060     static QString generateFilename(const QString &id, const QString &type);
0061     QString generateFilepath( const QString& id, const QString& type ) const;
0062 
0063     void updateFavoriteItems();
0064 
0065     AbstractDataPluginModel *m_parent;
0066     const QString m_name;
0067     const MarbleModel *const m_marbleModel;
0068     GeoDataLatLonAltBox m_lastBox;
0069     GeoDataLatLonAltBox m_downloadedBox;
0070     qint32 m_lastNumber;
0071     qint32 m_downloadedNumber;
0072     QString m_currentPlanetId;
0073     QList<AbstractDataPluginItem*> m_itemSet;
0074     QHash<QString, AbstractDataPluginItem*> m_downloadingItems;
0075     QList<AbstractDataPluginItem*> m_displayedItems;
0076     QTimer m_downloadTimer;
0077     quint32 m_descriptionFileNumber;
0078     QHash<QString, QVariant> m_itemSettings;
0079     QStringList m_favoriteItems;
0080     bool m_favoriteItemsOnly;
0081 
0082     CacheStoragePolicy m_storagePolicy;
0083     HttpDownloadManager m_downloadManager;
0084     FavoritesModel* m_favoritesModel;
0085     QMetaObject m_metaObject;
0086     bool m_hasMetaObject;
0087     bool m_needsSorting;
0088 };
0089 
0090 class FavoritesModel : public QAbstractListModel
0091 {
0092 public:
0093     AbstractDataPluginModelPrivate* d;
0094 
0095     explicit FavoritesModel( AbstractDataPluginModelPrivate* d, QObject* parent = nullptr );
0096 
0097     int rowCount ( const QModelIndex & parent = QModelIndex() ) const override;
0098 
0099     QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const override;
0100 
0101     void reset();
0102 
0103     QHash<int, QByteArray> roleNames() const override;
0104 
0105 private:
0106     QHash<int, QByteArray> m_roleNames;
0107 };
0108 
0109 AbstractDataPluginModelPrivate::AbstractDataPluginModelPrivate( const QString& name,
0110                                                                 const MarbleModel *marbleModel,
0111                                 AbstractDataPluginModel * parent )
0112     : m_parent( parent ),
0113       m_name( name ),
0114       m_marbleModel( marbleModel ),
0115       m_lastBox(),
0116       m_downloadedBox(),
0117       m_lastNumber( 0 ),
0118       m_downloadedNumber( 0 ),
0119       m_currentPlanetId( marbleModel->planetId() ),
0120       m_downloadTimer( m_parent ),
0121       m_descriptionFileNumber( 0 ),
0122       m_itemSettings(),
0123       m_favoriteItemsOnly( false ),
0124       m_storagePolicy(MarbleDirs::localPath() + QLatin1String("/cache/") + m_name + QLatin1Char('/')),
0125       m_downloadManager( &m_storagePolicy ),
0126       m_favoritesModel( nullptr ),
0127       m_hasMetaObject( false ),
0128       m_needsSorting( false )
0129 {
0130 }
0131 
0132 AbstractDataPluginModelPrivate::~AbstractDataPluginModelPrivate() {
0133     QList<AbstractDataPluginItem*>::iterator lIt = m_itemSet.begin();
0134     QList<AbstractDataPluginItem*>::iterator const lItEnd = m_itemSet.end();
0135     for (; lIt != lItEnd; ++lIt ) {
0136         (*lIt)->deleteLater();
0137     }
0138 
0139     QHash<QString,AbstractDataPluginItem*>::iterator hIt = m_downloadingItems.begin();
0140     QHash<QString,AbstractDataPluginItem*>::iterator const hItEnd = m_downloadingItems.end();
0141     for (; hIt != hItEnd; ++hIt ) {
0142         (*hIt)->deleteLater();
0143     }
0144 
0145     m_storagePolicy.clearCache();
0146 }
0147 
0148 void AbstractDataPluginModelPrivate::updateFavoriteItems()
0149 {
0150     if ( m_favoriteItemsOnly ) {
0151         for( const QString &id: m_favoriteItems ) {
0152             if ( !m_parent->findItem( id ) ) {
0153                 m_parent->getItem( id );
0154             }
0155         }
0156     }
0157 }
0158 
0159 void AbstractDataPluginModel::themeChanged()
0160 {
0161     if ( d->m_currentPlanetId != d->m_marbleModel->planetId() ) {
0162         clear();
0163         d->m_currentPlanetId = d->m_marbleModel->planetId();
0164     }
0165 }
0166 
0167 static bool lessThanByPointer( const AbstractDataPluginItem *item1,
0168                                const AbstractDataPluginItem *item2 )
0169 {
0170     if( item1 && item2 ) {
0171         // Compare by sticky and favorite status (sticky first, then favorites), last by operator<
0172         bool const sticky1 = item1->isSticky();
0173         bool const favorite1 = item1->isFavorite();
0174         if ( sticky1 != item2->isSticky() ) {
0175             return sticky1;
0176         } else if ( favorite1 != item2->isFavorite() ) {
0177             return favorite1;
0178         } else {
0179             return item1->operator<( item2 );
0180         }
0181     }
0182     else {
0183         return false;
0184     }
0185 }
0186 
0187 FavoritesModel::FavoritesModel( AbstractDataPluginModelPrivate *_d, QObject* parent ) :
0188     QAbstractListModel( parent ), d(_d)
0189 {
0190     QHash<int,QByteArray> roles;
0191     int const size = d->m_hasMetaObject ? d->m_metaObject.propertyCount() : 0;
0192     for ( int i=0; i<size; ++i ) {
0193         QMetaProperty property = d->m_metaObject.property( i );
0194         roles[Qt::UserRole+i] = property.name();
0195     }
0196     roles[Qt::DisplayRole] = "display";
0197     roles[Qt::DecorationRole] = "decoration";
0198     m_roleNames = roles;
0199 }
0200 
0201 int FavoritesModel::rowCount ( const QModelIndex &parent ) const
0202 {
0203     if ( parent.isValid() ) {
0204         return 0;
0205     }
0206 
0207     int count = 0;
0208     for( AbstractDataPluginItem* item: d->m_itemSet ) {
0209         if ( item->initialized() && item->isFavorite() ) {
0210             ++count;
0211         }
0212     }
0213 
0214     return count;
0215 }
0216 
0217 QVariant FavoritesModel::data( const QModelIndex &index, int role ) const
0218 {
0219     int const row = index.row();
0220     if ( row >= 0 && row < rowCount() ) {
0221         int count = 0;
0222         for( AbstractDataPluginItem* item: d->m_itemSet ) {
0223             if ( item->initialized() && item->isFavorite() ) {
0224                 if ( count == row ) {
0225                     QString const roleName = roleNames().value( role );
0226                     return item->property(roleName.toLatin1().constData());
0227                 }
0228                 ++count;
0229             }
0230         }
0231     }
0232 
0233     return QVariant();
0234 }
0235 
0236 void FavoritesModel::reset()
0237 {
0238     beginResetModel();
0239     endResetModel();
0240 }
0241 
0242 QHash<int, QByteArray> FavoritesModel::roleNames() const
0243 {
0244     return m_roleNames;
0245 }
0246 
0247 AbstractDataPluginModel::AbstractDataPluginModel( const QString &name, const MarbleModel *marbleModel, QObject *parent )
0248     : QObject(  parent ),
0249       d( new AbstractDataPluginModelPrivate( name, marbleModel, this ) )
0250 {
0251     Q_ASSERT( marbleModel != nullptr );
0252 
0253     // Initializing file and download System
0254     connect( &d->m_downloadManager, SIGNAL(downloadComplete(QString,QString)),
0255              this ,                 SLOT(processFinishedJob(QString,QString)) );
0256 
0257     connect( marbleModel, SIGNAL(themeChanged(QString)),
0258              this, SLOT(themeChanged()) );
0259 
0260     // We want to download a new description file every timeBetweenDownloads ms
0261     connect( &d->m_downloadTimer, SIGNAL(timeout()),
0262              this,               SLOT(handleChangedViewport()),
0263              Qt::QueuedConnection );
0264     d->m_downloadTimer.start( timeBetweenDownloads );
0265 }
0266 
0267 AbstractDataPluginModel::~AbstractDataPluginModel()
0268 {
0269     delete d;
0270 }
0271 
0272 const MarbleModel *AbstractDataPluginModel::marbleModel() const
0273 {
0274     return d->m_marbleModel;
0275 }
0276 
0277 QList<AbstractDataPluginItem*> AbstractDataPluginModel::items( const ViewportParams *viewport,
0278                                                                qint32 number )
0279 {
0280     GeoDataLatLonAltBox currentBox = viewport->viewLatLonAltBox();
0281     QList<AbstractDataPluginItem*> list;
0282     
0283     Q_ASSERT( !d->m_displayedItems.contains( 0 ) && "Null item in m_displayedItems. Please report a bug to marble-devel@kde.org" );
0284     Q_ASSERT( !d->m_itemSet.contains( 0 ) && "Null item in m_itemSet. Please report a bug to marble-devel@kde.org" );
0285 
0286     QList<AbstractDataPluginItem*> candidates = d->m_displayedItems + d->m_itemSet;
0287 
0288     if ( d->m_needsSorting ) {
0289         // Both the candidates list and the list of all items need to be sorted
0290         std::sort( candidates.begin(), candidates.end(), lessThanByPointer );
0291         std::sort( d->m_itemSet.begin(), d->m_itemSet.end(), lessThanByPointer );
0292         d->m_needsSorting =  false;
0293     }
0294 
0295     QList<AbstractDataPluginItem*>::const_iterator i = candidates.constBegin();
0296     QList<AbstractDataPluginItem*>::const_iterator end = candidates.constEnd();
0297 
0298     // Items that are already shown have the highest priority
0299     for (; i != end && list.size() < number; ++i ) {
0300         // Only show items that are initialized
0301         if( !(*i)->initialized() ) {
0302             continue;
0303         }
0304 
0305         // Hide non-favorite items if necessary
0306         if( d->m_favoriteItemsOnly && !(*i)->isFavorite() ) {
0307             continue;
0308         }
0309         
0310         (*i)->setProjection( viewport );
0311         if( (*i)->positions().isEmpty() ) {
0312             continue;
0313         }
0314 
0315         if ( list.contains( *i ) ) {
0316             continue;
0317         }
0318 
0319         // If the item was added initially at a nearer position, they don't have priority,
0320         // because we zoomed out since then.
0321         bool const alreadyDisplayed = d->m_displayedItems.contains( *i );
0322         if ( !alreadyDisplayed || (*i)->addedAngularResolution() >= viewport->angularResolution() || (*i)->isSticky() ) {
0323             bool collides = false;
0324             int const length = list.length();
0325             for ( int j=0; !collides && j<length; ++j ) {
0326                 for( const QRectF &rect: list[j]->boundingRects() ) {
0327                     for( const QRectF &itemRect: (*i)->boundingRects() ) {
0328                         if ( rect.intersects( itemRect ) )
0329                             collides = true;
0330                     }
0331                 }
0332             }
0333 
0334             if ( !collides ) {
0335                 list.append( *i );
0336                 (*i)->setSettings( d->m_itemSettings );
0337 
0338                 // We want to save the angular resolution of the first time the item got added.
0339                 if( !alreadyDisplayed ) {
0340                     (*i)->setAddedAngularResolution( viewport->angularResolution() );
0341                 }
0342             }
0343         }
0344         // TODO: Do we have to cleanup at some point? The list of all items keeps growing
0345     }
0346 
0347     d->m_lastBox = currentBox;
0348     d->m_lastNumber = number;
0349     d->m_displayedItems = list;
0350     return list;
0351 }
0352 
0353 QList<AbstractDataPluginItem *> AbstractDataPluginModel::whichItemAt( const QPoint& curpos )
0354 {
0355     QList<AbstractDataPluginItem *> itemsAt;
0356 
0357     const QPointF curposF(curpos);
0358     for( AbstractDataPluginItem* item: d->m_displayedItems ) {
0359         if (item && item->contains(curposF)) {
0360             itemsAt.append( item );
0361         }
0362     }
0363     
0364     return itemsAt;
0365 }
0366 
0367 void AbstractDataPluginModel::parseFile( const QByteArray& file )
0368 {
0369     Q_UNUSED( file );
0370 }
0371 
0372 void AbstractDataPluginModel::downloadItem( const QUrl& url,
0373                                                 const QString& type,
0374                                                 AbstractDataPluginItem *item )
0375 {
0376     if( !item ) {
0377         return;
0378     }
0379 
0380     QString id = d->generateFilename( item->id(), type );
0381 
0382     d->m_downloadManager.addJob( url, id, id, DownloadBrowse );
0383     d->m_downloadingItems.insert( id, item );
0384 }
0385 
0386 void AbstractDataPluginModel::downloadDescriptionFile( const QUrl& url )
0387 {
0388     if( !url.isEmpty() ) {
0389         QString name( descriptionPrefix );
0390         name += QString::number( d->m_descriptionFileNumber );
0391         
0392         d->m_downloadManager.addJob( url, name, name, DownloadBrowse );
0393         d->m_descriptionFileNumber++;
0394     }
0395 }
0396 
0397 void AbstractDataPluginModel::addItemToList( AbstractDataPluginItem *item )
0398 {
0399     addItemsToList( QList<AbstractDataPluginItem*>() << item );
0400 }
0401 
0402 void AbstractDataPluginModel::addItemsToList( const QList<AbstractDataPluginItem *> &items )
0403 {
0404     bool needsUpdate = false;
0405     bool favoriteChanged = false;
0406     for( AbstractDataPluginItem *item: items ) {
0407         if( !item ) {
0408             continue;
0409         }
0410 
0411         // If the item is already in our list, don't add it.
0412         if ( d->m_itemSet.contains( item ) ) {
0413             continue;
0414         }
0415 
0416         if( itemExists( item->id() ) ) {
0417             item->deleteLater();
0418             continue;
0419         }
0420 
0421         mDebug() << "New item " << item->id();
0422 
0423         // This find the right position in the sorted to insert the new item
0424         QList<AbstractDataPluginItem*>::iterator i = std::lower_bound( d->m_itemSet.begin(),
0425                                                                   d->m_itemSet.end(),
0426                                                                   item,
0427                                                                   lessThanByPointer );
0428         // Insert the item on the right position in the list
0429         d->m_itemSet.insert( i, item );
0430 
0431         connect( item, SIGNAL(stickyChanged()), this, SLOT(scheduleItemSort()) );
0432         connect( item, SIGNAL(destroyed(QObject*)), this, SLOT(removeItem(QObject*)) );
0433         connect( item, SIGNAL(updated()), this, SIGNAL(itemsUpdated()) );
0434         connect( item, SIGNAL(favoriteChanged(QString,bool)), this,
0435                  SLOT(favoriteItemChanged(QString,bool)) );
0436 
0437         if ( !needsUpdate && item->initialized() ) {
0438             needsUpdate = true;
0439         }
0440 
0441         if ( !favoriteChanged && item->initialized() && item->isFavorite() ) {
0442             favoriteChanged = true;
0443         }
0444     }
0445 
0446     if ( favoriteChanged && d->m_favoritesModel ) {
0447         d->m_favoritesModel->reset();
0448     }
0449 
0450     if ( needsUpdate ) {
0451         emit itemsUpdated();
0452     }
0453 }
0454 
0455 void AbstractDataPluginModel::getItem( const QString & )
0456 {
0457     qWarning() << "Retrieving items by identifier is not implemented by this plugin";
0458 }
0459 
0460 void AbstractDataPluginModel::setFavoriteItems( const QStringList& list )
0461 {
0462     if ( d->m_favoriteItems != list) {
0463         d->m_favoriteItems = list;
0464         d->updateFavoriteItems();
0465         if ( d->m_favoritesModel ) {
0466             d->m_favoritesModel->reset();
0467         }
0468         emit favoriteItemsChanged( d->m_favoriteItems );
0469     }
0470 }
0471 
0472 QStringList AbstractDataPluginModel::favoriteItems() const
0473 {
0474     return d->m_favoriteItems;
0475 }
0476 
0477 void AbstractDataPluginModel::setFavoriteItemsOnly( bool favoriteOnly )
0478 {
0479     if ( isFavoriteItemsOnly() != favoriteOnly ) {
0480         d->m_favoriteItemsOnly = favoriteOnly;
0481         d->updateFavoriteItems();
0482         emit favoriteItemsOnlyChanged();
0483     }
0484 }
0485 
0486 bool AbstractDataPluginModel::isFavoriteItemsOnly() const
0487 {
0488     return d->m_favoriteItemsOnly;
0489 }
0490 
0491 QObject *AbstractDataPluginModel::favoritesModel()
0492 {
0493     if ( !d->m_favoritesModel ) {
0494         d->m_favoritesModel = new FavoritesModel( d, this );
0495         d->updateFavoriteItems();
0496     }
0497 
0498     return d->m_favoritesModel;
0499 }
0500 
0501 void AbstractDataPluginModel::favoriteItemChanged( const QString& id, bool isFavorite )
0502 {
0503     QStringList favorites = d->m_favoriteItems;
0504 
0505     if ( isFavorite ) {
0506         if ( !favorites.contains(id) )
0507             favorites.append( id );
0508     } else {
0509         favorites.removeOne( id );
0510     }
0511 
0512     setFavoriteItems( favorites );
0513     scheduleItemSort();
0514 }
0515 
0516 void AbstractDataPluginModel::scheduleItemSort()
0517 {
0518     d->m_needsSorting = true;
0519 }
0520 
0521 QString AbstractDataPluginModelPrivate::generateFilename(const QString &id, const QString &type)
0522 {
0523     QString name;
0524     name += id;
0525     name += fileIdSeparator;
0526     name += type;
0527     
0528     return name;
0529 }
0530 
0531 QString AbstractDataPluginModelPrivate::generateFilepath( const QString& id, const QString& type ) const
0532 {
0533     return MarbleDirs::localPath() + QLatin1String("/cache/") + m_name + QLatin1Char('/') + generateFilename(id, type);
0534 }
0535 
0536 AbstractDataPluginItem *AbstractDataPluginModel::findItem( const QString& id ) const
0537 {
0538     for ( AbstractDataPluginItem *item: d->m_itemSet ) {
0539         if( item->id() == id ) {
0540             return item;
0541         }
0542     }
0543     
0544     return nullptr;
0545 }
0546 
0547 bool AbstractDataPluginModel::itemExists( const QString& id ) const
0548 {
0549     return findItem( id );
0550 }
0551 
0552 void AbstractDataPluginModel::setItemSettings(const QHash<QString, QVariant> &itemSettings)
0553 {
0554     d->m_itemSettings = itemSettings;
0555 }
0556 
0557 void AbstractDataPluginModel::handleChangedViewport()
0558 {
0559     if( d->m_favoriteItemsOnly ) {
0560         return;
0561     }
0562     
0563     // All this is to prevent to often downloads
0564     if( d->m_lastNumber != 0
0565             // We don't need to download if nothing changed
0566             && ( !( d->m_downloadedBox == d->m_lastBox )
0567                  || d->m_downloadedNumber != d->m_lastNumber )
0568             // We try to filter little changes of the bounding box
0569             && ( fabs( d->m_downloadedBox.east() - d->m_lastBox.east() ) * boxComparisonFactor
0570                  > d->m_lastBox.width()
0571                  || fabs( d->m_downloadedBox.south() - d->m_lastBox.south() ) * boxComparisonFactor
0572                  > d->m_lastBox.height()
0573                  || fabs( d->m_downloadedBox.north() - d->m_lastBox.north() ) * boxComparisonFactor
0574                  > d->m_lastBox.height()
0575                  || fabs( d->m_downloadedBox.west() - d->m_lastBox.west() ) * boxComparisonFactor
0576                  > d->m_lastBox.width() ) )
0577     {
0578         // We will wait a little bit longer to start the
0579         // next download as we will really download something now.
0580         d->m_downloadTimer.setInterval( timeBetweenDownloads );
0581         
0582         // Save the download parameter
0583         d->m_downloadedBox = d->m_lastBox;
0584         d->m_downloadedNumber = d->m_lastNumber;
0585 
0586         // Get items
0587         getAdditionalItems( d->m_lastBox, d->m_lastNumber );
0588     }
0589     else {
0590         // Don't wait to long to start the next download as we decided not to download anything.
0591         // This will enhance response.
0592         d->m_downloadTimer.setInterval( timeBetweenTriedDownloads );
0593     }
0594 }
0595 
0596 void AbstractDataPluginModel::processFinishedJob( const QString& relativeUrlString,
0597                                                   const QString& id )
0598 {
0599     Q_UNUSED( relativeUrlString );
0600     
0601     if( id.startsWith( descriptionPrefix ) ) {
0602         parseFile( d->m_storagePolicy.data( id ) );
0603     }
0604     else {
0605         // The downloaded file contains item data.
0606         
0607         // Splitting the id in itemId and fileType
0608         QStringList fileInformation = id.split( fileIdSeparator );
0609         
0610         if( fileInformation.size() < 2) {
0611             mDebug() << "Strange file information " << id;
0612             return;
0613         }
0614         QString itemId = fileInformation.at( 0 );
0615         fileInformation.removeAt( 0 );
0616         QString fileType = fileInformation.join( QString( fileIdSeparator ) );
0617         
0618         // Searching for the right item in m_downloadingItems
0619         QHash<QString, AbstractDataPluginItem *>::iterator i = d->m_downloadingItems.find( id );
0620         if( i != d->m_downloadingItems.end() ) {
0621             if( itemId != (*i)->id() ) {
0622                 return;
0623             }
0624             
0625             (*i)->addDownloadedFile( d->generateFilepath( itemId, fileType ),
0626                                      fileType );
0627 
0628             d->m_downloadingItems.erase( i );
0629         }
0630     }
0631 }
0632 
0633 void AbstractDataPluginModel::removeItem( QObject *item )
0634 {
0635     AbstractDataPluginItem * pluginItem = qobject_cast<AbstractDataPluginItem*>( item );
0636     d->m_itemSet.removeAll( pluginItem );
0637     QHash<QString, AbstractDataPluginItem *>::iterator i;
0638     for( i = d->m_downloadingItems.begin(); i != d->m_downloadingItems.end(); ++i ) {
0639         if( *i == pluginItem ) {
0640             i = d->m_downloadingItems.erase( i );
0641         }
0642     }
0643 }
0644 
0645 void AbstractDataPluginModel::clear()
0646 {
0647     d->m_displayedItems.clear();
0648     QList<AbstractDataPluginItem*>::iterator iter = d->m_itemSet.begin();
0649     QList<AbstractDataPluginItem*>::iterator const end = d->m_itemSet.end();
0650     for (; iter != end; ++iter ) {
0651         (*iter)->deleteLater();
0652     }
0653     d->m_itemSet.clear();
0654     d->m_lastBox = GeoDataLatLonAltBox();
0655     d->m_downloadedBox = GeoDataLatLonAltBox();
0656     d->m_downloadedNumber = 0;
0657     emit itemsUpdated();
0658 }
0659 
0660 void AbstractDataPluginModel::registerItemProperties( const QMetaObject &item )
0661 {
0662     d->m_metaObject = item;
0663     d->m_hasMetaObject = true;
0664 }
0665 
0666 } // namespace Marble
0667 
0668 #include "moc_AbstractDataPluginModel.cpp"