Marble/OnlineServices: Difference between revisions

From KDE TechBase
No edit summary
No edit summary
Line 25: Line 25:


The class based on AbstractDataPlugin is the representation class of our plugin. It provides the name and the idea of the plugin. You also have to set your model there.
The class based on AbstractDataPlugin is the representation class of our plugin. It provides the name and the idea of the plugin. You also have to set your model there.
== Item ==
We will write our item first as it depends on nothing else. As you should already know the item stores the data and paints it on the screen. One item has an exact position on the globe as longitude, latitude and altitude.
The item we will write shows a text at the specific position.
We will now look at the header file to see what functions we have to implement.
'''TutorialItem.h'''
<code cppqt>
// This is to prevent this header to be included multiple times
#ifndef TUTORIALITEM_H
#define TUTORIALITEM_H
#include "AbstractDataPluginItem.h"
class QFont;
namespace Marble {
class TutorialItem : public AbstractDataPluginItem {
    Q_OBJECT
   
public:
    TutorialItem( QObject *parent );
   
    ~TutorialItem();
   
    // Returns the item type of the item.
    QString itemType() const;
   
    // Returns true if the item is paintable
    bool initialized();
   
    // Here the item gets painted
    void paint( GeoPainter *painter, ViewportParams *viewport,
                const QString& renderPos, GeoSceneLayer * layer = 0 );
   
    bool operator<( const AbstractDataPluginItem *other ) const;
   
    // The text we want to show on the map
    QString text() const;   
    void setText( const QString& text );
   
private:
    QString m_text;
   
    static QFont s_font;
};
   
}
#endif // TUTORIALITEM_H
</code>
As you have probably seen, it will be very easy to implement this as we have only one function (paint()) besides some getters and setters.
Let's have a look at the implementation.
'''TutorialItem.cpp'''
<code cppqt>
// Self
#include "TutorialItem.h"
// Marble
#include "GeoPainter.h"
#include "ViewportParams.h"
// Qt
#include <QtGui/QFontMetrics>
using namespace Marble;
// That's the font we will use to paint.
QFont TutorialItem::s_font = QFont( "Sans Serif", 8 );
TutorialItem::TutorialItem( QObject *parent )
    : AbstractDataPluginItem( parent )
{
    // The size of an item without a text is 0
    setSize( QSize( 0, 0 ) );
}
TutorialItem::~TutorialItem() {
}
QString TutorialItem::itemType() const {
    // Our itemType:
    return "tutorialItem";
}
   
bool TutorialItem::initialized() {
    // The item is initialized if it has a text
    if ( m_text.isEmpty() )
        return false;
    else
        return true;
}
bool TutorialItem::operator<( const AbstractDataPluginItem *other ) const {
    // That's not a very nice priority estimation, you'll hopefully find
    // a better one for your plugin.
    return this->id() < other->id();
}
QString TutorialItem::text() const {
    return m_text;
}
void TutorialItem::setText( const QString& text ) {
    // On a text change our size may also change, so we have to set the new
    // item size. Marble needs to know how large an item is to find the correct
    // bounding rect. The given position will always be in the middle of the
    // item for now.
    QFontMetrics metrics( s_font );
    setSize( metrics.size( 0, text ) );
    m_text = text;
}
 
void TutorialItem::paint( GeoPainter *painter, ViewportParams *viewport,
                          const QString& renderPos, GeoSceneLayer * layer )
{
    Q_UNUSED( renderPos )
    Q_UNUSED( layer )
   
    // Save the old painter state.
    painter->save();
    // We want to paint a black string.
    painter->setPen( QPen( QColor( Qt::black ) ) );
    // We will use our standard font.
    painter->setFont( s_font );
    // Draw the text into the given rect.
    painter->drawText( QRect( QPoint( 0, 0 ), size() ), 0, m_text );
    // Restore the old painter state.
    painter->restore();
}
// This is needed for all QObjects (see MOC)
#include "TutorialItem.moc"
</code>
Wasn't that easy? Continue with the next section.


== Model ==
== Model ==
We will start developing the core component, the model. Let's first look at the header file:
We will continue developing the core component, the model. Let's first look at the header file:
'''TutorialModel.h'''
'''TutorialModel.h'''
<code cppqt>
<code cppqt>
// This prevents from including the header multiple times
#ifndef TUTORIALMODEL_H
#ifndef TUTORIALMODEL_H
#define TUTORIALMODEL_H
#define TUTORIALMODEL_H
 
#include "AbstractDataPluginModel.h"
#include "AbstractDataPluginModel.h"
 
#include <QtGui/QIcon>
#include <QtGui/QPixmap>
 
namespace Marble {
namespace Marble {
 
class MarbleDataFacade;
class MarbleDataFacade;
 
// The maximum number of items we want to show on the screen.
// The maximum number of items we want to show on the screen.
const quint32 numberOfItemsOnScreen = 20;
const quint32 numberOfItemsOnScreen = 20;
 
class TutorialModel : public AbstractDataPluginModel
class TutorialModel : public AbstractDataPluginModel
{
{
     Q_OBJECT
     Q_OBJECT
   
  public:
  public:
     TutorialModel( QObject *parent = 0 );
     TutorialModel( QObject *parent = 0 );
Line 62: Line 196:
                             MarbleDataFacade *facade,
                             MarbleDataFacade *facade,
                             qint32 number = 10 );
                             qint32 number = 10 );
     
    /**
    * The reimplementation has to parse the @p file and should generate widgets. This widgets
    * have to be scheduled to downloadItemData or could be directly added to the list,
    * depending on if they have to download information to be shown.
    **/
    void parseFile( const QByteArray& file );
};
};
 
}
}
 
#endif // TUTORIALMODEL_H
#endif // TUTORIALMODEL_H
</code>
</code>


You see that we have to implement the functions parseFile() and getAdditionalItems(). getAdditionalItems() is called when the viewport has been changed significantly, so new items have to be generated. parseFile() is called when a description file has been downloaded which probably needs parsing.
You see that we have to implement the function getAdditionalItems(). getAdditionalItems() is called when the viewport has been changed significantly, so new items have to be generated.
 
Let's continue with the interesting part, the implementation:
Let's continue with the interesting part, the implementation:


Line 83: Line 209:
// Self
// Self
#include "TutorialModel.h"
#include "TutorialModel.h"
 
// Plugin
#include "TutorialItem.h"
// Marble
// Marble
#include "global.h"
#include "global.h"
 
#include "MarbleDataFacade.h"
#include "GeoDataCoordinates.h"
// Qt
// Qt
#include <QtCore/QString>
#include <QtCore/QString>
 
using namespace Marble;
using namespace Marble;
 
 
TutorialModel::TutorialModel( QObject *parent )
TutorialModel::TutorialModel( QObject *parent )
     : AbstractDataPluginModel( "tutorial", parent )  
     : AbstractDataPluginModel( "tutorial", parent )  
{
{
}
}
 
TutorialModel::~TutorialModel() {
TutorialModel::~TutorialModel() {
}
}
 
void TutorialModel::getAdditionalItems( const GeoDataLatLonAltBox& box,
void TutorialModel::getAdditionalItems( const GeoDataLatLonAltBox& box,
                                         MarbleDataFacade *facade,
                                         MarbleDataFacade *facade,
Line 110: Line 241:
     }
     }
      
      
     // Generate your items here
     // We will only create one item in our first tutorial.
 
    // Every item has to get an id. We have to check if the item already exists.
    if ( !itemExists( "tutorial1" ) ) {
        // If it does not exists, create it
        GeoDataCoordinates coor( 10.22 * DEG2RAD, 54.4 * DEG2RAD );
        // The parent of the new item is this object
        TutorialItem *item = new TutorialItem( this );
        item->setCoordinate( coor );
        item->setTarget( "earth" );
        item->setId( "tutorial1" );
        item->setText( "Hello World!" );
        // Add the item to the list of items, so it will be displayed
        addItemToList( item );
    }
}
}
 
void TutorialModel::parseFile( const QByteArray& file ) {
}
 
#include "TutorialModel.moc"
#include "TutorialModel.moc"
</code>
</code>

Revision as of 15:37, 26 July 2009

Creating your first Marble Online Services Plugin
Tutorial Series   Marble Tutorial
Previous   C++, Qt, KDE4 development environment
What's Next  
Further Reading   CMake

This Tutorial is unfinished, I'll finish it in a few days.

Abstract

You will here learn to create your own online service plugins. If you don't know what is meant by online service, you will see these in the Marble menu at "View"->"Online Services". You'll need KDE 4.3 to build this tutorial. If you want to write plugins for Marble, it could be useful to do this in KDE's subversion. Please contact the Marble developers for this (IRC, E-Mail).

Structure

An online services plugin or data plugin consist of three classes at least. The class to display the information is the Data Item (the base class is AbstractDataPluginItem). It stores the information for single places on the map and displays them.

The Model (base class AbstractDataPluginModel) stores all items. Storing the items will be done by AbstractDataPluginModel itself. Your only job is to get information for new items when the displayed part of the earth changes. This can include downloading so called "Description files" from the servers of an online service and parsing them. These "Description files" contain lists of items in a specific part of the earth (specified by a LatLonAltBox).

The class based on AbstractDataPlugin is the representation class of our plugin. It provides the name and the idea of the plugin. You also have to set your model there.

Item

We will write our item first as it depends on nothing else. As you should already know the item stores the data and paints it on the screen. One item has an exact position on the globe as longitude, latitude and altitude. The item we will write shows a text at the specific position.

We will now look at the header file to see what functions we have to implement. TutorialItem.h // This is to prevent this header to be included multiple times

  1. ifndef TUTORIALITEM_H
  2. define TUTORIALITEM_H
  1. include "AbstractDataPluginItem.h"

class QFont;

namespace Marble {

class TutorialItem : public AbstractDataPluginItem {

   Q_OBJECT
   
public:
   TutorialItem( QObject *parent );
   
   ~TutorialItem();
   
   // Returns the item type of the item.
   QString itemType() const;
   
   // Returns true if the item is paintable
   bool initialized();
   
   // Here the item gets painted
   void paint( GeoPainter *painter, ViewportParams *viewport,
               const QString& renderPos, GeoSceneLayer * layer = 0 );
   
   bool operator<( const AbstractDataPluginItem *other ) const;
   
   // The text we want to show on the map
   QString text() const;    
   void setText( const QString& text );
   
private:
   QString m_text;
   
   static QFont s_font;

};

}

  1. endif // TUTORIALITEM_H

As you have probably seen, it will be very easy to implement this as we have only one function (paint()) besides some getters and setters.

Let's have a look at the implementation. TutorialItem.cpp // Self

  1. include "TutorialItem.h"

// Marble

  1. include "GeoPainter.h"
  2. include "ViewportParams.h"

// Qt

  1. include <QtGui/QFontMetrics>

using namespace Marble;

// That's the font we will use to paint. QFont TutorialItem::s_font = QFont( "Sans Serif", 8 );

TutorialItem::TutorialItem( QObject *parent )

   : AbstractDataPluginItem( parent )

{

   // The size of an item without a text is 0
   setSize( QSize( 0, 0 ) );

}

TutorialItem::~TutorialItem() { }

QString TutorialItem::itemType() const {

   // Our itemType:
   return "tutorialItem";

}

bool TutorialItem::initialized() {

   // The item is initialized if it has a text
   if ( m_text.isEmpty() ) 
       return false;
   else
       return true;

}

bool TutorialItem::operator<( const AbstractDataPluginItem *other ) const {

   // That's not a very nice priority estimation, you'll hopefully find
   // a better one for your plugin.
   return this->id() < other->id();

}

QString TutorialItem::text() const {

   return m_text;

}

void TutorialItem::setText( const QString& text ) {

   // On a text change our size may also change, so we have to set the new
   // item size. Marble needs to know how large an item is to find the correct
   // bounding rect. The given position will always be in the middle of the 
   // item for now.
   QFontMetrics metrics( s_font );
   setSize( metrics.size( 0, text ) );
   m_text = text;

}

void TutorialItem::paint( GeoPainter *painter, ViewportParams *viewport,

                          const QString& renderPos, GeoSceneLayer * layer )

{

   Q_UNUSED( renderPos )
   Q_UNUSED( layer )
   
   // Save the old painter state.
   painter->save();
   // We want to paint a black string.
   painter->setPen( QPen( QColor( Qt::black ) ) );
   // We will use our standard font.
   painter->setFont( s_font );
   // Draw the text into the given rect.
   painter->drawText( QRect( QPoint( 0, 0 ), size() ), 0, m_text );
   // Restore the old painter state.
   painter->restore();

}

// This is needed for all QObjects (see MOC)

  1. include "TutorialItem.moc"

Wasn't that easy? Continue with the next section.

Model

We will continue developing the core component, the model. Let's first look at the header file: TutorialModel.h

  1. ifndef TUTORIALMODEL_H
  2. define TUTORIALMODEL_H
  1. include "AbstractDataPluginModel.h"

namespace Marble {

class MarbleDataFacade;

// The maximum number of items we want to show on the screen. const quint32 numberOfItemsOnScreen = 20;

class TutorialModel : public AbstractDataPluginModel {

   Q_OBJECT

public:
   TutorialModel( QObject *parent = 0 );
   ~TutorialModel();

protected:
   /**
    * Generates the download url for the description file from the web service depending on
    * the @p box surrounding the view and the @p number of files to show.
    **/
   void getAdditionalItems( const GeoDataLatLonAltBox& box,
                            MarbleDataFacade *facade,
                            qint32 number = 10 );

};

}

  1. endif // TUTORIALMODEL_H

You see that we have to implement the function getAdditionalItems(). getAdditionalItems() is called when the viewport has been changed significantly, so new items have to be generated. Let's continue with the interesting part, the implementation:

// Self

  1. include "TutorialModel.h"

// Plugin

  1. include "TutorialItem.h"

// Marble

  1. include "global.h"
  2. include "MarbleDataFacade.h"
  3. include "GeoDataCoordinates.h"

// Qt

  1. include <QtCore/QString>

using namespace Marble;


TutorialModel::TutorialModel( QObject *parent )

   : AbstractDataPluginModel( "tutorial", parent ) 

{ }

TutorialModel::~TutorialModel() { }

void TutorialModel::getAdditionalItems( const GeoDataLatLonAltBox& box,

                                        MarbleDataFacade *facade,
                                        qint32 number )

{

   // This plugin only supports Tutorial items for earth
   if( facade->target() != "earth" ) {
       return;
   }
   
   // We will only create one item in our first tutorial.
   // Every item has to get an id. We have to check if the item already exists.
   if ( !itemExists( "tutorial1" ) ) {
       // If it does not exists, create it
       GeoDataCoordinates coor( 10.22 * DEG2RAD, 54.4 * DEG2RAD );
       // The parent of the new item is this object
       TutorialItem *item = new TutorialItem( this );
       item->setCoordinate( coor );
       item->setTarget( "earth" );
       item->setId( "tutorial1" );
       item->setText( "Hello World!" );
       // Add the item to the list of items, so it will be displayed
       addItemToList( item );
   }

}

  1. include "TutorialModel.moc"