/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the documentation of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:FDL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Free Documentation License Usage
** Alternatively, this file may be used under the terms of the GNU Free
** Documentation License version 1.3 as published by the Free Software
** Foundation and appearing in the file included in the packaging of
** this file.  Please review the following information to ensure
** the GNU Free Documentation License version 1.3 requirements
** will be met: http://www.gnu.org/copyleft/fdl.html.
** $QT_END_LICENSE$
**
****************************************************************************/

/*!
    \page tutorials-mapsdemo.html


    \contentspage Maps Demo Tutorial
    \startpage Maps Demo Tutorial

    \title Maps Demo Tutorial

    \brief An introduction to the Location API, showing how to develop a simple
           map search and navigation application.

    In this tutorial, you will learn about the basic components of the Location
    API, including

    \list
    \o Drawing and interacting with maps;
    \o Managing map objects;
    \o Search and routing services; and
    \o Receiving position updates.
    \endlist

    We will be developing the map search and navigation application shown below:

    \image mapsdemo-finished.png

    Tutorial contents:

    \list 1
    \o \l{Part 1 - The Map Widget}
    \o \l{Part 2 - Searching for locations}
    \o \l{Part 3 - Listening to satellites}
    \o \l{Part 4 - Stopping for directions}
    \o \l{Part 5 - Tuning for mobile devices}
    \endlist
*/

/*!
    \page tutorials-mapsdemo-part1.html

    \previouspage {Maps Demo Tutorial}
    \contentspage {Maps Demo Tutorial} {Contents}
    \nextpage Part 2 - Searching for locations
    \startpage Maps Demo Tutorial

    \title Part 1 - The Map Widget

    To begin with, we will start defining the map widget, which is the central part of
    the application's user interface. Enough of the map widget will be defined here to
    work satisfactorily on most desktop platforms -- full consideration for mobile use
    will be made later along with other parts of the application.

    \section2 The very basics

    The Location module provides the QGraphicsGeoMap which is a simple, easy way to insert
    maps into a QGraphicsScene. Since we're going to be extending the map later, we'll
    create a subclass of QGraphicsGeoMap called \c GeoMap, as below:

    \code
class GeoMap : public QGraphicsGeoMap
{
    Q_OBJECT

public:
    GeoMap(QGeoMappingManager *manager, MapsWidget *mapsWidget);
    ~GeoMap();

private:
    MapsWidget *mapsWidget;
};

GeoMap::GeoMap(QGeoMappingManager *manager, MapsWidget *mapsWidget) :
    QGraphicsGeoMap(manager), mapsWidget(mapsWidget)
{
}
    \endcode

    And next we define a QWidget subclass, MapsWidget, which handles the creation of
    QGraphicsView and QGraphicsScene to put the GeoMap into. We make use of the Pimpl
    idiom on this class, since (as we will see) it will grow later to have a large
    complement of private data members, and some of these have naming conflicts with
    public methods.

    \code
class MapsWidgetPrivate;
class MapsWidget : public QWidget
{
    Q_OBJECT

public:
    MapsWidget(QWidget *parent = 0);
    ~MapsWidget();

public slots:
    void initialize(QGeoMappingManager *manager);

private:
    MapsWidgetPrivate *d;
};
    \endcode

    We perform the creation of the QGraphicsScene and GeoMap in the initialize() method:

    \code
class MapsWidgetPrivate
{
public:
    GeoMap *map;
    QGraphicsView *view;
};

void MapsWidget::initialize(QGeoMappingManager *manager)
{
    d->map = new GeoMap(manager, this);

    QGraphicsScene *sc = new QGraphicsScene;
    sc->addItem(d->map);

    d->map->resize(300, 480);

    d->view = new QGraphicsView(sc, this);
    d->view->setVisible(true);
    d->view->setInteractive(true);

    d->map->setCenter(QGeoCoordinate(-27.5796, 153.1));
    d->map->setZoomLevel(15);
}
    \endcode

    Doing this in the constructor, while possible, is not the preferred approach, as
    the QGeoMappingManager may not be available until the user has chosen it, or until a
    network connection is available. This is especially important in mobile
    environments, as we'll see later.

    To get an instance of QGeoMappingManager we use the list of service providers
    available in QGeoServiceProvider::availableServiceProviders(). Service providers
    provide the ability to fetch and draw maps, search for locations, get directions,
    and a variety of other tasks.

    To test out the MapsWidget we just wrote, we can simply get the first available
    service provider in the main() function, as follows:

    \code
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    MapsWidget w;
    w.show();

    QList<QString> providers = QGeoServiceProvider::availableServiceProviders();
    QGeoServiceProvider *serviceProvider = new QGeoServiceProvider(providers[0]);

    w.initialize(serviceProvider->mappingManager());

    return a.exec();
}
    \endcode

    If you compile and run the code so far, you should see a window appear containing
    a street map of Eight Mile Plains, in Queensland, Australia, rendered by your
    platform's default geo service provider.

    \image mapsdemo-verybasic.png

    \section2 Pan & zoom

    Next we'll add some basic pan and zoom capability to the map widget. Like most other
    classes in Qt, QGraphicsGeoMap allows mouse and keyboard events to be handled by
    private methods.

    Into the private section of the GeoMap declaration we add:
    \code
bool panActive;

void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
    \endcode

    And their definitions:

    \code
void GeoMap::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    panActive = true;
    event->accept();
}

void GeoMap::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    panActive = false;
    event->accept();
}

void GeoMap::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if (panActive) {
        QPointF delta = event->lastPos() - event->pos();
        pan(delta.x(), delta.y());
    }
    event->accept();
}
    \endcode

    These three short methods are enough to add basic panning support to the map.
    The panning method is a simple mouse-locked one, and moving long distances on
    a touch screen with it can get quite tedious. Many map applications now make
    use of "kinetic" panning for a better user experience, especially on touch
    devices, but in the interests of simplicity, we'll save that for other examples.

    Next, to add zoom support on the mouse scrollwheel:

    \code
void GeoMap::wheelEvent(QGraphicsSceneWheelEvent *event)
{
    qreal panx = event->pos().x() - size().width() / 2.0;
    qreal pany = event->pos().y() - size().height() / 2.0;
    pan(panx, pany);
    if (event->delta() > 0) {   // zoom in
        if (zoomLevel() < maximumZoomLevel()) {
            setZoomLevel(zoomLevel() + 1);
        }
    } else {                    // zoom out
        if (zoomLevel() > minimumZoomLevel()) {
            setZoomLevel(zoomLevel() - 1);
        }
    }
    pan(-panx, -pany);
    event->accept();
}
    \endcode

    This method is a little more complicated. To provide a suitable zoom feel, we
    have to actually combine panning with zooming, so that the user's point of
    interest (the mouse cursor) remains in the same part of the view. So, we
    actually pan the mouse cursor's location into the center, then adjust
    the zoom level, then pan back at the end.

    \section2 Map icons

    Another important basic feature is the ability to render icons on the map to
    represent points of interest. The QGeoMapPixmapObject class provides most of
    the functionality necessary to achieve this, and we'll use a subclass of it
    in similar vein to our GeoMap, above.

    For our application, we want to deal with 6 different kinds of icons:
    \list
    \o A "my location" icon
    \o "Search" icons for search results
    \o User waypoints for direction routes
    \o Start points for directions
    \o End points for directions
    \o "Path" markers for individual steps in the direction route
    \endlist

    Once again we make use of the Pimpl idiom to separate the private data
    members from the interface:

    \code
class MarkerPrivate;
class Marker : public QGeoMapPixmapObject
{
    Q_OBJECT
public:
    enum MarkerType {
        MyLocationMarker,
        SearchMarker,
        WaypointMarker,
        StartMarker,
        EndMarker,
        PathMarker
    };

    explicit Marker(MarkerType type);

    inline MarkerType markerType() const { return m_type; }
    void setMarkerType(MarkerType type);

private:
    MarkerPrivate *d;

};
    \endcode

    So we can construct Marker instances of different types, but we need QPixmaps
    to represent each one. In our implementation we will simply use a \c switch
    statement to map MarkerTypes to QPixmaps.

    \code
class MarkerPrivate
{
public:
    Marker::MarkerType type;
};

Marker::Marker(MarkerType type) :
    QGeoMapPixmapObject()
{
    setMarkerType(type);
}

void Marker::setMarkerType(MarkerType type)
{
    QString filename;
    QPoint offset;
    int scale;

    d->type = type;

    switch (d->type) {
    case MyLocationMarker:
        filename = ":/icons/mylocation.png";
        break;
    case SearchMarker:
        filename = ":/icons/searchmarker.png";
        break;
    case WaypointMarker:
        filename = ":/icons/waypointmarker.png";
        break;
    case StartMarker:
        filename = ":/icons/startmarker.png";
        break;
    case EndMarker:
        filename = ":/icons/endmarker.png";
        break;
    case PathMarker:
        filename = ":/icons/pathmarker.png";
        break;
    }

    if (d->type == MyLocationMarker) {
        offset = QPoint(-13,-13);
        scale = 25;
    } else {
        offset = QPoint(-15, -36);
        scale = 30;
    }

    setOffset(offset);
    setPixmap(QPixmap(filename).scaledToWidth(scale, Qt::SmoothTransformation));
}
    \endcode

    The icon PNG images can be found in the \c examples/mapsdemo/icons
    directory in the QtMobility distribution. All we have to do to have this
    working is simply add the PNG icons to a \c .qrc file and add it to the
    project.

    The QGraphicsGeoMap::addMapObject method is used to add markers to a map.
    We can add a call to create a marker at our starting point into
    MapsWidget::initialize() as a demonstration:

    \code
// in MapsWidget::initialize()
Marker *me = new Marker(Marker::MyLocationMarker);
me->setCoordinate(QGeoCoordinate(-27.5796, 153.1));
geoMap->addMapObject(me);
    \endcode

    Build and start the application, and we now have a "My Location" icon in
    the centre of the initial view.

    This now concludes the basic functionality of the map widget. We'll be
    making a few modifications and improvements to it as we go along, but
    the basic skeleton will remain the same.

    Next, we'll add a basic GUI around the map widget, and the ability
    to search for locations like addresses.
*/

/*!
    \page tutorials-mapsdemo-part2.html

    \previouspage Part 1 - The Map Widget
    \contentspage {Maps Demo Tutorial} {Contents}
    \nextpage Part 3 - Listening to satellites
    \startpage Maps Demo Tutorial

    \title Part 2 - Searching for locations

    Now that we have a basic map widget, we want to add the capability
    to search for addresses and locations and create markers for them
    on the map.

    \section2 Search classes

    Searching in the Location API is handled by use of the QGeoSearchManager,
    which we obtain in similar fashion to the MappingManager (in main() in
    part 1). As we want to create markers for search results and then
    be able to remove them for the next search (or perhaps other operations),
    we need some way to organise collections of markers.

    To do this, we introduce a new class, MarkerManager:

    \code
class MarkerManagerPrivate;
class MarkerManager : public QObject
{
    Q_OBJECT
public:
    explicit MarkerManager(QGeoSearchManager *sm, QObject *parent=0);
    ~MarkerManager();

public slots:
    void setMap(QGraphicsGeoMap *map);
    void setMyLocation(QGeoCoordinate coord);
    void search(QString query);
    void removeSearchMarkers();

signals:
    void searchError(QGeoSearchReply::Error error, QString errorString);
    void searchFinished();

private:
    MarkerManagerPrivate *d;

private slots:
    void replyFinished(QGeoSearchReply *reply);
};
    \endcode

    The MarkerManager tracks both the "My Location" marker and a list of search
    result markers. Implementing the My Location portion is nothing new:

    \code
class MarkerManagerPrivate
{
public:
    Marker *myLocation;
    QList<Marker*> searchMarkers;

    QGraphicsGeoMap *map;
    QGeoSearchManager *searchManager;

    QSet<QGeoSearchReply*> forwardReplies;
};

MarkerManager::MarkerManager(QGeoSearchManager *searchManager, QObject *parent) :
    QObject(parent),
    d(new MarkerManagerPrivate)
{
    d->myLocation = new Marker(Marker::MyLocationMarker);
    d->searchManager = searchManager;
}

MarkerManager::~MarkerManager()
{
    if (d->map)
        d->map->removeMapObject(m_myLocation);
    delete d->myLocation;
    ...
}

void MarkerManager::setMap(QGraphicsGeoMap *map)
{
    ...
    d->map = map;
    d->map->addMapObject(d->myLocation);
    ...
}

void MarkerManager::setMyLocation(QGeoCoordinate coord)
{
    d->myLocation->setCoordinate(coord);
}
    \endcode

    To implement searching, we call the QGeoSearchManager::search method, which
    returns a QGeoSearchReply. This reply object emits a signal finished() when
    the search results are available. It can also be constructed already
    finished, and we need to check for this first before connecting the signals.

    We make use of the searchManager's version of the \a finished() signal, as
    it gives out the necessary QGeoSearchReply* parameter so that we can have
    one slot to handle both the case where the reply is constructed already
    finished, and the case where the signal fires later.

    \code
MarkerManager::MarkerManager(QGeoSearchManager *searchManager, QObject *parent) :
    ...
{
    ...
    connect(d->searchManager, SIGNAL(finished(QGeoSearchReply*)),
            this, SLOT(replyFinished(QGeoSearchReply*)));
}

void MarkerManager::search(QString query)
{
    QGeoSearchReply *reply = d->searchManager->search(query);

    d->forwardReplies.insert(reply);
    if (reply->isFinished()) {
        replyFinished(reply);
    } else {
        connect(reply, SIGNAL(error(QGeoSearchReply::Error,QString)),
                this, SIGNAL(searchError(QGeoSearchReply::Error,QString)));
    }
}
    \endcode

    The QGeoSearchReply yields its results as a list of QGeoPlace instances.
    While these hold quite a bit of information, for now we'll just be using
    them for their coordinates.

    \code
void MarkerManager::replyFinished(QGeoSearchReply *reply)
{
    if (!d->forwardReplies.contains(reply))
        return;

    // generate the markers and add them to the map
    foreach (QGeoPlace place, reply->places()) {
        Marker *m = new Marker(Marker::SearchMarker);
        m->setCoordinate(place.coordinate());
        d->searchMarkers.append(m);

        if (d->map) {
            d->map->addMapObject(m);
            // also zoom out until marker is visible
            while (!d->map->viewport().contains(place.coordinate()))
                d->map->setZoomLevel(d->map->zoomLevel()-1);
        }
    }

    d->forwardReplies.remove(reply);
    reply->deleteLater();

    emit searchFinished();
}
    \endcode

    Next, we add two methods to MapsWidget to keep track of a MarkerManager
    instance associated with its map:

    \code
class MapsWidget : public QWidget
{
    ...
public:
    void setMarkerManager(MarkerManager *markerManager);
    MarkerManager *markerManager() const;

    ...
};

class MapsWidgetPrivate
{
public:
    MarkerManager *markerManager;
    ...
};
    \endcode

    And then add two small sections of code to connect them together:

    \code
void MapsWidget::initialize(QGeoMappingManager *manager)
{
    d->map = new GeoMap(manager, this);
    if (d->markerManager)
        d->markerManager->setMap(d->map);
    ...
}

void MapsWidget::setMarkerManager(MarkerManager *markerManager)
{
    d->markerManager = markerManager;
    if (d->map)
        d->markerManager->setMap(d->map);
}
    \endcode

    Now we have basic search capability added to our code. But we still
    have no GUI to drive it, and so we'll focus on that in the next section.

    \section2 GUI with search dialog

    Next we'll build a GUI around our map widget and add a search dialog to
    make use of the code we just wrote. Our finished GUI looks like this:

    \image mapsdemo-searchgui.png

    We won't cover building the GUI in too much detail (that being the
    subject of other tutorials), but the complete code is in the finished
    MapsDemo example in the QtMobility distribution.

    Our GUI consists of a QMainWindow containing our MapsWidget and a QMenuBar.
    On the QMenuBar is an option for zooming to the current "My Location", and
    a menu for performing search operations.

    Also part of the GUI is the dialog box displayed when selecting "Search
    for address or name" -- this is a simple QDialog subclass with a QFormLayout
    and a QDialogButtonBox.

    In the MainWindow constructor, we simply set up the menubar and MapsWidget
    and other UI details. All initialization of Location-based details are in
    the MainWindow::initialize() slot. For the moment, we will simply assume
    that initialize() is called directly from the constructor (the purpose of
    this decoupling will be explained later).

    \code
void MainWindow::initialize()
{
    if (serviceProvider)
        delete serviceProvider;

    QList<QString> providers = QGeoServiceProvider::availableServiceProviders();
    if (providers.size() < 1) {
        QMessageBox::information(this, tr("Maps Demo"),
                                 tr("No service providers are available"));
        QCoreApplication::quit();
        return;
    }

    serviceProvider = new QGeoServiceProvider(providers[0]);
    if (serviceProvider->error() != QGeoServiceProvider::NoError) {
        QMessageBox::information(this, tr("Maps Demo"),
                                 tr("Error loading geoservice plugin: %1").arg(providers[0]));
        QCoreApplication::quit();
        return;
    }

    mapsWidget->initialize(serviceProvider->mappingManager());
    markerManager = new MarkerManager(serviceProvider->searchManager());
    mapsWidget->setMarkerManager(markerManager);

    connect(markerManager, SIGNAL(searchError(QGeoSearchReply::Error,QString)),
            this, SLOT(showErrorMessage(QGeoSearchReply::Error,QString)));

    mapsWidget->setMyLocation(QGeoCoordinate(-27.5796, 153.1));
}
    \endcode

    As you can see, this performs more or less the same actions as our old
    code in main() from part 1 of the tutorial did. It fetches the first
    available service provider, then initializes the MapsWidget and
    MarkerManager using the appropriate Manager instances.

    Additionally, we've added a setMyLocation() method to MapsWidget which
    simply calls the current MarkerManager's method of the same name, plus
    centreing the view on the marker.

    The "Search for address or name" menu item sets off the showSearchDialog()
    slot:

    \code
void MainWindow::showSearchDialog()
{
    SearchDialog sd;
    if (sd.exec() == QDialog::Accepted) {
        if (markerManager) {
            markerManager->removeSearchMarkers();
            markerManager->search(sd.searchTerms());
        }
    }
}
    \endcode

    Which uses the methods on MarkerManager that we defined previously. So now
    we have a basic searchable mapping application. However, there is one big
    piece of functionality missing for a searchable map: consider if we had
    a provider that allowed us to search for local businesses. We might type
    in a business name in the Search dialog and press OK, and then be presented
    with tens or hundreds of businesses that match the name we typed from all
    around the world. Some of these results might not even be the kind of
    business we were looking for (partial text matches etc).

    This can be solved with the addition of two key features: viewing the extra
    details about search results that we're currently throwing away; and adding
    the ability to limit the search area.

    \section2 Adding details to search markers

    First up, we'll add some additional properties to the Marker class:

    \code
class Marker : public QGeoMapPixmapObject
{
    ....
public:
    QString name() const;
    void setName(QString name);

    QGeoAddress address() const;
    void setAddress(QGeoAddress addr);

    bool moveable() const;
    void setMoveable(bool moveable);
    ...
};

class MarkerPrivate
{
public:
    ...
    QString name;
    bool moveable;
    QGeoAddress address;
};
    \endcode

    And add code to MarkerManager to set them from search results:

    \code
void MarkerManager::replyFinished(QGeoSearchReply *reply)
{
    ...
    foreach (QGeoPlace place, reply->places()) {
        Marker *m = new Marker(Marker::SearchMarker);

        m->setCoordinate(place.coordinate());
        if (place.isLandmark()) {
            QLandmark lm(place);
            m->setName(lm.name());
        } else {
            m->setName(QString("%1, %2").arg(place.address().street())
                                        .arg(place.address().city()));
        }
        m->setAddress(place.address());
        m->setMoveable(false);
    ...
    \endcode

    So now the data is available from the Marker objects. We want to show
    this to the user somehow, though, and the best means of doing this is
    probably a dialog box. We're going to do a dialog box that appears when
    the user clicks a marker, so we'll have to add click detection to
    MapsWidget and GeoMap, first.

    We already have methods for handling mouse presses and releases over
    the map widget, so we will modify these. Add two private fields and a
    signal to GeoMap:

    \code
class GeoMap : public QGraphicsGeoMap
{
    ...
signals:
    void clicked(Marker *marker);

private:
    ...
    bool markerPressed;
    QGeoMapObject *pressed;
    ...
};
    \endcode

    We set the \c markerPressed flag when the mouse has been pressed over a
    map object, and set \c pressed to the map object in question.

    \code
void GeoMap::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    panActive = true;

    markerPressed = false;
    QList<QGeoMapObject*> objects = mapObjectsAtScreenPosition(event->pos());
    if (objects.size() > 0) {
        pressed = objects.first();
        markerPressed = true;
    }

    event->accept();
}

void GeoMap::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    panActive = false;

    if (markerPressed) {
        // check if we're still over the object
        QList<QGeoMapObject*> objects = mapObjectsAtScreenPosition(event->pos());
        if (objects.contains(pressed)) {
            Marker *m = dynamic_cast<Marker*>(pressed);
            if (m)
                emit clicked(m);
        }

        markerPressed = false;
    }

    event->accept();
}
    \endcode

    Finally, we need to pass this clicked() signal up through MapsWidget so
    that we can use it from outside. We do this by adding a signal and
    connecting GeoMap's signal to the signal on MapsWidget with the same
    name.

    \code
class MapsWidget : public QWidget
{
    ...
signals:
    void markerClicked(Marker *m);
    ...
};

void MapsWidget::initialize(QGeoMappingManager *manager)
{
    ...
    connect(d->map, SIGNAL(clicked(Marker*)),
            this, SIGNAL(markerClicked(Marker*)));
}
    \endcode

    Now that's done, creating a dialog box to display the address information
    is relatively trivial. The MarkerDialog class contains a QLineEdit for the
    name field, a readonly QLabel for the address, and two QDoubleSpinBoxes
    for latitude and longitude.

    We connect up the MapsWidget's markerClicked() signal to a slot in
    MainWindow:

    \code
void MainWindow::showMarkerDialog(Marker *marker)
{
    MarkerDialog md(marker);
    if (md.exec() == QDialog::Accepted) {
        marker->setName(md.markerName());
        QGeoCoordinate c(md.latitude(), md.longitude());
        marker->setCoordinate(c);
    }
}
    \endcode

    And now clicking on markers on the map yields a simple editing dialog box, so
    our first task is complete.

    \section2 Limiting search area

    The QGeoSearchManager's search() method already comes with support for
    limited search areas -- by setting up a QGeoBoundingArea we can take
    advantage of this functionality.

    Firstly, we'll modify the MarkerManager's search() method:

    \code
// declaration
void search(QString query, qreal radius=-1);

// implementation
void MarkerManager::search(QString query, qreal radius)
{
    QGeoSearchReply *reply;
    if (radius > 0) {
        QGeoBoundingCircle boundingCircle(m_myLocation->coordinate(), radius);
        reply = d->searchManager->search(query,
                                         QGeoSearchManager::SearchAll,
                                         -1, 0,
                                         boundingCircle);
    } else {
        reply = d->searchManager->search(query);
    }

    if (reply->isFinished()) {
      ...
    \endcode

    And now we need to modify the UI to expose this to the user. There are
    a few ways of doing this, but the option we'll take is to expose a
    QComboBox with some preset distances. This is easier to use on
    touch screen devices, especially, where entering numbers often takes
    much more effort from the user than selecting an option.

    \code
// in SearchDialog constructor
whereCombo = new QComboBox();
whereCombo->addItem(tr("Nearby (<10km)"), 10000);
whereCombo->addItem(tr("Within 30 mins drive of me (<25km)"), 25000);
whereCombo->addItem(tr("Within 100km of me"), 100000);
whereCombo->addItem(tr("Anywhere in the world"), -1);
whereCombo->setCurrentIndex(1);
formLayout->addRow(tr("Where"), whereCombo);
    \endcode

    Then to get the radius value to put into search, we simply take the user
    data from the QComboBox, convert it to a qreal and pass it through.

    So we now have a searchable map, with clickable markers and the ability
    to limit the search radius. The last feature we'll cover that relates to
    searching is the so-called "reverse geocode" technique.

    \section2 Reverse geocode

    Currently, if you click the My Location icon on our map application, a
    blank address is displayed. We can add the capability here to turn the
    current coordinates of the marker into an approximate address, and the
    technique is known as "reverse geocode" searching.

    To implement this, we'll hook into the coordinateChanged() signal of
    the Marker object:

    \code
MarkerManager::MarkerManager(QGeoSearchManager *searchManager, QObject *parent) :
    QObject(parent),
    d(new MarkerManagerPrivate)
{
    d->searchManager = searchManager;
    d->myLocation = new Marker(Marker::MyLocationMarker);
    d->myLocation->setName("Me");

    ...

    // hook the coordinateChanged() signal for reverse geocoding
    connect(d->myLocation, SIGNAL(coordinateChanged(QGeoCoordinate)),
            this, SLOT(myLocationChanged(QGeoCoordinate)));
}
    \endcode

    Then we perform the reverse lookup in the myLocationChanged() slot. This
    looks quite similar to the original search() method, with good reason, as
    the reverse geocode lookup is simply a special kind of search call.

    \code
void MarkerManager::myLocationChanged(QGeoCoordinate location)
{
    QGeoSearchReply *reply = d->searchManager->reverseGeocode(location);

    d->reverseReplies.insert(reply);
    if (reply->isFinished())
        reverseReplyFinished(reply);
}

void MarkerManager::reverseReplyFinished(QGeoSearchReply *reply)
{
    if (!d->reverseReplies.contains(reply))
        return;

    if (reply->places().size() > 0) {
        QGeoPlace place = reply->places().first();
        d->myLocation->setAddress(place.address());
    }

    reply->deleteLater();
}
    \endcode

    However, this isn't going to work very well with a GPS updating myLocation
    on a regular basis and a slow network connection, as the requests
    will pile up and the geocoded coordinates will lag behind the reported
    ones by quite a margin. A simple scheme to solve this relies only
    on two boolean flags:

    \code
class MarkerManagerPrivate
{
public:
    ...
    // a reverse geocode request is currently running
    bool revGeocodeRunning;
    // a request is currently running, and my location has changed
    // since it started (ie, the request is stale)
    bool myLocHasMoved;
};

void MarkerManager::myLocationChanged(QGeoCoordinate location)
{
    if (d->revGeocodeRunning) {
        d->myLocHasMoved = true;
    } else {
        QGeoSearchReply *reply = d->searchManager->reverseGeocode(location);
        d->reverseReplies.insert(reply);

        d->myLocHasMoved = false;

        if (reply->isFinished()) {
            d->revGeocodeRunning = false;
            reverseReplyFinished(reply);
        } else {
            d->revGeocodeRunning = true;
        }
    }
}

void MarkerManager::reverseReplyFinished(QGeoSearchReply *reply)
{
    if (!d->reverseReplies.contains(reply))
        return;

    // set address, as before

    d->revGeocodeRunning = false;
    if (d->myLocHasMoved)
        myLocationChanged(d->myLocation->coordinate());

    d->reverseReplies.remove(reply);
    reply->deleteLater();
}
    \endcode

    A reverse geocode request is only sent if the previous one has
    finished -- if it hasn't finished, a flag is set so that the location
    will be refreshed at the conclusion of the previous request. This is
    far from a perfect scheme, but in practise it works quite well.

    At the end of part 2 now, we have a searchable map with a simple GUI,
    clickable markers, the ability to limit search radius about our location,
    and reverse geocoding to work out the address of where we are. This is
    already quite a bit of useful functionality, but we will continue to
    extend it further.

    In part 3, we will add support for using platform positioning methods such
    as GPS, and in part 4 we will add the ability to fetch directions to a given
    destination. Finally, in part 5 we will cover a number of points about
    means for achieving a better user experience on mobile platforms.
*/

/*!
    \page tutorials-mapsdemo-part3.html

    \previouspage Part 2 - Searching for locations
    \contentspage {Maps Demo Tutorial} {Contents}
    \nextpage Part 4 - Stopping for directions
    \startpage Maps Demo Tutorial

    \title Part 3 - Listening to satellites

    Another useful part of the Location API is the ability to receive updates
    of the user's present geographic location from methods such as GPS or
    network positioning. We're going to add support to our MapsDemo for
    using these methods to update the "my location" marker we've already
    added in parts 1 and 2 of this tutorial.

    But first we need an attractive way to present status messages to the user
    while they are busy looking at the map. We're going to do this using an
    animated translucent rectangle at the bottom of the display.

    \section2 Animated status bar

    First, set up the map to resize automatically:

    \code
class MapsWidget : public QWidget
{
    ...
private:
    void resizeEvent(QResizeEvent *event);
    void showEvent(QShowEvent *event);
};

void MapsWidget::resizeEvent(QResizeEvent *event)
{
    if (d->view && d->map) {
        d->view->resize(size());
        d->map->resize(size());
        d->view->centerOn(d->map);
    }
}

void MapsWidget::showEvent(QShowEvent *event)
{
    if (d->view && d->map) {
        d->view->resize(size());
        d->map->resize(size());
        d->view->centerOn(d->map);
    }
}
    \endcode

    And now we add our new StatusBarItem class:

    \code
class StatusBarItemPrivate;
class StatusBarItem : public QObject, public QGraphicsRectItem
{
    Q_OBJECT
    Q_PROPERTY(int offset READ offset WRITE setOffset)

public:
    StatusBarItem();
    ~StatusBarItem();

    int offset() const;
    void setRect(qreal x, qreal y, qreal w, qreal h);

public slots:
    void setText(QString text);

    void showText(QString text, quint32 timeout=3000);
    void show();
    void hide();

    void setOffset(int offset);

private:
    StatusBarItemPrivate *d;
};
    \endcode

    Note that the order of base classes here is very important: QObject
    and then QGraphicsRectItem. Re-ordering the base classes will cause
    the code not to compile, as QGraphicsRectItem does not have a
    meta-object (for more details consult the documentation in Qt).

    The \a offset property here is added so that when we come to animating
    our status bar, we can handle the case where the bar is sliding in
    and the window is being resized simultaneously. If we simply animated
    the \a y property of the GraphicsItem instead we would have difficulty
    handling this case.

    Now add a pointer to one of these in MapsWidgetPrivate (and matching
    accessor methods):

    \code
class MapsWidgetPrivate
{
public:
    ...
    StatusBarItem *statusBarItem;
};
    \endcode

    And we're ready for the implementation. The constructor is not terribly
    exciting, but sets the defaults for everything:

    \code
class StatusBarItemPrivate
{
public:
    int offset;
    QGraphicsSimpleTextItem *textItem;
};

StatusBarItem::StatusBarItem() :
    d(new StatusBarItemPrivate)
{
    d->offset = 0;

    setPen(QPen(QBrush(), 0));
    setBrush(QBrush(QColor(0,0,0,120)));

    d->textItem = new QGraphicsSimpleTextItem(this);
    d->textItem->setBrush(QBrush(Qt::white));

    setText("");
}
    \endcode

    The \a setText function, however, is more interesting;

    \code
void StatusBarItem::setText(QString text)
{
    d->textItem->setText(text);
    QRectF rect = d->textItem->boundingRect();
    QPointF delta = this->rect().center() - rect.center();
    d->textItem->setPos(delta.x(), delta.y());
}
    \endcode

    This re-centers the \a textItem inside its parent (the StatusBarItem)
    every time the text changes.

    Also, the \a setRect method is used to update the size and position of
    the status bar:

    \code
void StatusBarItem::setRect(qreal x, qreal y, qreal w, qreal h)
{
    QGraphicsRectItem::setRect(x, y + d->offset, w, h);
    setText(d->textItem->text());
}
    \endcode

    Here we see the use of the \a offset property for the first time. The
    idea is to call \a setRect to specify a rectangle that is below
    the bottom of the visible area in the QGraphicsView. Then \a offset
    is used to bump the status bar up into the visible area when needed.

    Whenever we change the offset we should re-calculate our own \a y
    value using the rect and the offset together:

    \code
void StatusBarItem::setOffset(int offset)
{
    this->setY(this->y() - d->offset + offset);
    d->offset = offset;
}
    \endcode

    And now finally, the animations:

    \code
void StatusBarItem::show()
{
    QPropertyAnimation *anim = new QPropertyAnimation(this, "offset");
    anim->setStartValue(0);
    anim->setEndValue(-1 * rect().height());
    anim->setDuration(500);
    anim->start(QAbstractAnimation::DeleteWhenStopped);
}

void StatusBarItem::hide()
{
    QPropertyAnimation *anim = new QPropertyAnimation(this, "offset");
    anim->setStartValue(d->offset);
    anim->setEndValue(0);
    anim->setDuration(500);
    anim->start(QAbstractAnimation::DeleteWhenStopped);
}
    \endcode

    You can see here that we simply use QPropertyAnimations on the \a offset
    property we just defined. This produces a nice linear slide in and out
    whenever \a show() or \a hide() are called.

    Lastly, one convenience method:

    \code
void StatusBarItem::showText(QString text, quint32 timeout)
{
    setText(text);
    show();
    QTimer::singleShot(timeout, this, SLOT(hide()));
}
    \endcode

    This lets us more easily display a status message when we only want it
    to appear and disappear soon afterwards.

    Then we have only to add this into our MapsWidget:

    \code
void MapsWidget::initialize(QGeoMappingManager *manager)
{
    QGraphicsScene *sc;
    ...
    d->statusBarItem = new StatusBarItem;
    sc->addItem(d->statusBarItem);
}

void MapsWidget::resizeEvent(QResizeEvent *event)
{
    if (d->view && d->map) {
        ...
        d->statusBarItem->setRect(0, height(), width(), 20);
    }
}

// and similarly in MapsWidget::showEvent()
    \endcode

    \section2 Getting GPS data

    Now we move on to the focus of this section: GPS data and how to get it.
    The QGeoPositionInfoSource class gives a convenient interface to
    receive position updates. We're going to add one to our MainWindow:

    \code
class MainWindow : public QMainWindow
{
private:
    QGeoPositionInfoSource *positionSource;

private slots:
    // slot to receive updates
    void updateMyPosition(QGeoPositionInfo info);
};
    \endcode

    And in \a initialize() we'll set it up. We're just using whatever the
    default position source for the platform happens to be, at an update
    interval of 1000ms, which is plenty for a basic maps application. Once
    set up, we call the source's \a startUpdates() method to begin receiving
    position updates.

    \code
void MainWindow::initialize()
{
    ...
    if (positionSource)
        delete positionSource;

    positionSource = QGeoPositionInfoSource::createDefaultSource(this);

    if (!positionSource) {
        mapsWidget->statusBar()->showText("Could not open GPS", 5000);
        mapsWidget->setMyLocation(QGeoCoordinate(-27.5796, 153.1));
    } else {
        positionSource->setUpdateInterval(1000);
        connect(positionSource, SIGNAL(positionUpdated(QGeoPositionInfo)),
                this, SLOT(updateMyPosition(QGeoPositionInfo)));
        positionSource->startUpdates();
        mapsWidget->statusBar()->showText("Opening GPS...");
    }
}
    \endcode

    Here we also make use of the StatusBarItem to display a message when
    we are able or unable to open the QGeoPositionInfoSource.

    And then in the slot \a updateMyPosition, we use this to set the
    myLocation marker.

    \code
void MainWindow::updateMyPosition(QGeoPositionInfo info)
{
    if (mapsWidget) {
        mapsWidget->setMyLocation(info.coordinate());
    }
}
    \endcode

    So, running the code as is, we have a moving marker for "My Location"
    that follows our actual GPS or network-sourced location. If you start
    driving your car with this app running however, you'll quickly notice
    the fact that the viewport does not pan to follow you as you leave
    the map area.

    We could simply add a call to \a setCenter() on the map object in the
    \a updateMyPosition slot, but in the interests of prettiness, we are
    going to make a nice smoothly animated transition instead.

    \section2 Following and animated panning

    First, add a new boolean member variable to MainWindow, called
    \a tracking, to keep track of whether the viewport is currently
    following the My Location marker:

    \code
class MainWindow : public QMainWindow
{
private:
    bool tracking;
    ...
};
    \endcode

    Our intended design is that initially, the viewport will be in tracking
    mode. It will continue this way until the view is manually panned by
    the user, at which point tracking will stop. Then, if the user clicks
    the "My Location" menu option to re-center the map, we resume tracking
    once again.

    So we will need a way to notify the MainWindow that the user has panned
    the view. Add a new signal \a mapPanned() to MapsWidget, and a
    corresponding signal \a panned() to GeoMap, as we did for \a clicked().

    \code
class MapsWidget : public QWidget
{
signals:
    void mapPanned();
    ...
};

class GeoMap : public QGraphicsGeoMap
{
signals:
    void panned();
    ...
};

void MapsWidget::initialize(QGeoMappingManager *manager)
{
    ...
    connect(geoMap, SIGNAL(panned()),
            this, SIGNAL(mapPanned()));
    ...
}
    \endcode

    And now we simply emit it when a user pan takes place:

    \code
void GeoMap::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if (panActive) {
        ...
        emit panned();
    }
    ...
}
    \endcode

    Back up in MainWindow, we create a slot \a disableTracking and hook up the
    new signal to it:

    \code
class MainWindow : public QMainWindow
{
    ...
private slots:
    ...
    void disableTracking();
    ...
};

void MainWindow::initialize()
{
    ...
    connect(mapsWidget, SIGNAL(mapPanned()),
            this, SLOT(disableTracking()));
    ...
}
    \endcode

    And finally in the slot itself we simply set the flag we created earlier:

    \code
void MainWindow::disableTracking()
{
    tracking = false;
}
    \endcode

    Next we want animated panning to be available. Add a new method on
    MapsWidget:

    \code
class MapsWidget : public QWidget
{
public:
    ...
    void animatedPanTo(QGeoCoordinate center);
    ...
};
    \endcode

    To do animations in Qt, it's always easiest if we can make use of a
    QPropertyAnimation, and to do this you need a Q_PROPERTY to act upon.
    We'll use two animations in parallel, one moving latitude and one moving
    longitude, so we need two Q_PROPERTIES:

    \code
class GeoMap : public QGraphicsGeoMap
{
    Q_OBJECT

    Q_PROPERTY(double centerLatitude READ centerLatitude WRITE setCenterLatitude)
    Q_PROPERTY(double centerLongitude READ centerLongitude WRITE setCenterLongitude)

public:
    ...
    double centerLatitude() const;
    void setCenterLatitude(double lat);
    double centerLongitude() const;
    void setCenterLongitude(double lon);
    ...
};
    \endcode

    These functions simply adjust the corresponding value on \a center() and
    then call \a setCenter() with the new \a QGeoCoordinate.

    Now we can implement our \a animatedPanTo() method:

    \code
void MapsWidget::animatedPanTo(QGeoCoordinate center)
{
    if (!d->map)
        return;

    QPropertyAnimation *latAnim = new QPropertyAnimation(d->map, "centerLatitude");
    latAnim->setEndValue(center.latitude());
    latAnim->setDuration(200);
    QPropertyAnimation *lonAnim = new QPropertyAnimation(d->map, "centerLongitude");
    lonAnim->setEndValue(center.longitude());
    lonAnim->setDuration(200);

    QParallelAnimationGroup *group = new QParallelAnimationGroup;
    group->addAnimation(latAnim);
    group->addAnimation(lonAnim);
    group->start(QAbstractAnimation::DeleteWhenStopped);
}
    \endcode

    To bring it all together, we make the last few changes in MainWindow:

    \code
void MainWindow::goToMyLocation()
{
    mapsWidget->animatedPanTo(markerManager->myLocation());
    tracking = true;
}

void MainWindow::updateMyPosition(QGeoPositionInfo info)
{
    if (mapsWidget) {
        mapsWidget->setMyLocation(info.coordinate());
        if (tracking)
            mapsWidget->animatedPanTo(info.coordinate());
    }
}
    \endcode

    And now we have the simple location tracking functionality we set out to
    implement.
*/

/*!
    \page tutorials-mapsdemo-part4.html

    \previouspage Part 3 - Listening to satellites
    \contentspage {Maps Demo Tutorial} {Contents}
    \nextpage Part 5 - Tuning for mobile devices
    \startpage Maps Demo Tutorial

    \title Part 4 - Stopping for directions

    To complete our tour of the Maps API, we're going to add some very basic
    support for finding transport routes across a map. There is much more
    functionality available in the routing and navigation API than we are
    going to use, though some backend plugins may place restrictions on its
    use to develop, for example, voice-aided navigation applications (such
    as the Nokia Ovi maps plugin).

    \image mapsdemo-routing.png

    We are going to add support for a simple dialog that can be used to search
    for a destination point and display a line on the map giving the route from
    the current GPS "My Location" (which we implemented in part 3) to that
    destination.

    First, we implement the dialog along similar lines to the SearchDialog we
    created earlier:

    \code
class NavigateDialog : public QDialog
{
    Q_OBJECT
public:
    NavigateDialog(QWidget *parent=0);
    ~NavigateDialog();

    QString destinationAddress() const;
    QGeoRouteRequest::TravelModes travelMode() const;

private:
    QLineEdit *addressEdit;
    QComboBox *modeCombo;
};
    \endcode

    Once again we make use of a QFormLayout inside the dialog to align the
    widgets together. We have a QLineEdit for the address of the destination,
    and a QComboBox listing possible travel modes.

    In MainWindow, we create a new slot for showing the navigate dialog:

    \code
void MainWindow::showNavigateDialog()
{
    NavigateDialog nd;
    if (nd.exec() == QDialog::Accepted) {
        if (markerManager) {
            // will fill this out later
        }
    }
}
    \endcode

    And we hook it up to a Menu action:

    \code
MainWindow::MainWindow() :
    ...
{
    ...
    QMenu *navigateMenu = new QMenu("Directions");
    mbar->addMenu(navigateMenu);

    navigateMenu->addAction("From here to address", this, SLOT(showNavigateDialog()));
    ....
}
    \endcode

    Now we need a new class to manage routing. Finding a route to an address
    is a two-stage process: first, a geocode search is performed on the
    address to get a lat/lon coordinate. Then this coordinate is used in
    a route request which finally returns the desired route.

    Our new class is called Navigator, and includes private slots to handle
    each of these events:

    \code
class Navigator : public QObject
{
    Q_OBJECT
public:
    Navigator(QGeoRoutingManager *routingManager, QGeoSearchManager *searchManager,
              MapsWidget *mapsWidget, const QString &address,
              const QGeoRouteRequest &requestTemplate);
    ~Navigator();

    void start();
    QGeoRoute route() const;

signals:
    void finished();
    void searchError(QGeoSearchReply::Error error, QString errorString);
    void routingError(QGeoRouteReply::Error error, QString errorString);

private slots:
    void on_addressSearchFinished();
    void on_routingFinished();

private:
    QString address;
    QGeoRouteRequest request;

    QGeoRoutingManager *routingManager;
    QGeoSearchManager *searchManager;
    MapsWidget *mapsWidget;

    QGeoSearchReply *addressReply;
    QGeoRouteReply *routeReply;

    QGeoMapRouteObject *routeObject;
    Marker *endMarker;
    Marker *startMarker;

    QGeoRoute firstRoute;
};
    \endcode

    The intended lifecycle of a Navigator is to be created when the dialog
    is accepted, then \a start() is called to begin the requests. The
    requests will either error out or complete, emitting one of \a finished(),
    \a searchError(), or \a routingError() signals. If the request is
    successful, the Navigator creates the appropriate markers and draws
    the route on the map (using a QGeoMapRouteObject). It then owns these
    map objects and will remove them when deleted.

    Now for the Navigator's implementation: first, the \a start() method,
    which begins the process by launching the search request.

    A QGeoRouteRequest is specified first and foremost by the points the
    route must pass through (the \a waypoints). In our case we only
    wish two have two waypoints, the user's starting location, and the
    destination. We add the first of these in \a start() and the second
    after the search request returns.

    \code
void Navigator::start()
{
    QList<QGeoCoordinate> waypoints = request.waypoints();
    waypoints.append(mapsWidget->markerManager()->myLocation());
    request.setWaypoints(waypoints);

    startMarker = new Marker(Marker::StartMarker);
    startMarker->setCoordinate(mapsWidget->markerManager()->myLocation());
    startMarker->setName("Start point");
    mapsWidget->map()->addMapObject(startMarker);

    addressReply = searchManager->search(address);
    if (addressReply->isFinished()) {
        on_addressSearchFinished();
    } else {
        connect(addressReply, SIGNAL(error(QGeoSearchReply::Error,QString)),
                this, SIGNAL(searchError(QGeoSearchReply::Error,QString)));
        connect(addressReply, SIGNAL(finished()),
                this, SLOT(on_addressSearchFinished()));
    }
}
    \endcode

    After the request finishes, the \a on_addressSearchFinished() slot will
    be invoked, which finishes off the routing request and sends it in a
    similar fashion:

    \code
void Navigator::on_addressSearchFinished()
{
    if (addressReply->places().size() <= 0) {
        addressReply->deleteLater();
        return;
    }

    QGeoPlace place = addressReply->places().at(0);

    QList<QGeoCoordinate> waypoints = request.waypoints();
    waypoints.append(place.coordinate());
    request.setWaypoints(waypoints);

    routeReply = routingManager->calculateRoute(request);
    if (routeReply->isFinished()) {
        on_routingFinished();
    } else {
        connect(routeReply, SIGNAL(error(QGeoRouteReply::Error,QString)),
                this, SIGNAL(routingError(QGeoRouteReply::Error,QString)));
        connect(routeReply, SIGNAL(finished()),
                this, SLOT(on_routingFinished()));
    }

    endMarker = new Marker(Marker::EndMarker);
    endMarker->setCoordinate(place.coordinate());
    endMarker->setAddress(place.address());
    endMarker->setName("Destination");
    mapsWidget->map()->addMapObject(endMarker);

    addressReply->deleteLater();
}
    \endcode

    And then finally, when the routing request returns we can create
    the route object on the map and emit \a finished():

    \code
void Navigator::on_routingFinished()
{
    if (routeReply->routes().size() <= 0) {
        emit routingError(QGeoRouteReply::NoError, "No valid routes returned");
        routeReply->deleteLater();
        return;
    }

    QGeoRoute route = routeReply->routes().at(0);
    firstRoute = route;

    routeObject = new QGeoMapRouteObject;
    routeObject->setRoute(route);
    routeObject->setPen(QPen(Qt::blue, 2.0));

    mapsWidget->map()->addMapObject(routeObject);

    emit finished();
    routeReply->deleteLater();
}
    \endcode

    Now in MainWindow we have to create a new Navigator instance after
    the dialog returns. We store the Navigator instance in a member
    variable so that we can delete the last one in order to remove
    its map objects before the new one is constructed:

    \code
class MainWindow : public QMainWindow
{
private:
    Navigator *lastNavigator;
    ...
};

void MainWindow::showNavigateDialog()
{
    NavigateDialog nd;
    if (nd.exec() == QDialog::Accepted) {
        if (markerManager) {
            QGeoRouteRequest req;

            req.setTravelModes(nd.travelMode());

            if (lastNavigator)
                lastNavigator->deleteLater();

            Navigator *nvg = new Navigator(serviceProvider->routingManager(),
                                           serviceProvider->searchManager(),
                                           mapsWidget, nd.destinationAddress(),
                                           req);

            lastNavigator = nvg;

            connect(nvg, SIGNAL(searchError(QGeoSearchReply::Error,QString)),
                    this, SLOT(showErrorMessage(QGeoSearchReply::Error,QString)));
            connect(nvg, SIGNAL(routingError(QGeoRouteReply::Error,QString)),
                    this, SLOT(showErrorMessage(QGeoRouteReply::Error,QString)));

            mapsWidget->statusBar()->setText("Routing...");
            mapsWidget->statusBar()->show();

            nvg->start();

            connect(nvg, SIGNAL(finished()),
                    mapsWidget->statusBar(), SLOT(hide()));
        }
    }
}
    \endcode

    And now we have basic support for calculating and displaying
    routes on the map. In addition to this, we could quite easily
    use the QGeoRoute object to show a list of directions and
    overall statistics about the journey. For more information see
    the documentation about QGeoRoute.

    In the final part of this tutorial, we will optimise the maps
    demo so far for mobile platforms in order to deploy it to a
    phone.
*/

/*!
    \page tutorials-mapsdemo-part5.html

    \previouspage Part 4 - Stopping for directions
    \contentspage {Maps Demo Tutorial} {Contents}
    \startpage Maps Demo Tutorial

    \title Part 5 - Tuning for mobile devices

    So far in this tutorial we've been mainly aiming at desktop use
    of the application. If you attempted to build it as is for
    a mobile platform you would quite quickly notice a number of
    issues that prevent it from being really usable, and we will
    now address these one by one.

    \section2 Network connection management

    Most mobile platforms have multiple network connections, which
    are not connected all the time, and change regularly in
    operation. Qt provides the Bearer API for managing these
    and the events that occur with them.

    At present when our application starts it simply assumes that
    a network link is available and that a mapping plugin will
    load. This could quite easily not be the case, so we will use
    the Bearer API to verify the state of the network and ensure
    that it is running before we call \a initialize().

    First up, we need a QNetworkConfigurationManager in order to
    get at the default configuration of our environment:

    \code
class MainWindow : public QMainWindow
{
    ...
private:
    QNetworkConfigurationManager *netConfigManager;
};
    \endcode

    We create it in the constructor of \a MainWindow. As mentioned
    in the documentation for QNetworkConfigurationManager, we should
    make a call to \a updateConfigurations() before actually making
    use of the instance. So we'll also need a slot to be called
    when this completes (we name this \a openNetworkSession()).

    \code
MainWindow::MainWindow() :
    ...
{
    ...
    netConfigManager = new QNetworkConfigurationManager;
    connect(netConfigManager, SIGNAL(updateCompleted()),
            this, SLOT(openNetworkSession()));
    netConfigManager->updateConfigurations();
}
    \endcode

    And in the slot itself we use the \a defaultConfiguration() method
    as the parameter to construct a new QNetworkSession to represent
    our default connection to the network.

    We first check to see if this session is open, and if so, call
    initialize() right away. Otherwise, we hook up an appropriate
    signal and wait for the network to be available.

    \code
void MainWindow::openNetworkSession()
{
    session = new QNetworkSession(netConfigManager->defaultConfiguration());
    if (session->isOpen()) {
        initialize();
    } else {
        connect(session, SIGNAL(opened()),
                this, SLOT(initialize()));
        session->open();
    }
}
    \endcode

    So now our \a initialize() method will be called once a network
    connection is available.

    \section2 Zoom buttons and panning keys

    In our current implementation we depend upon the presence of a mouse
    wheel in order to zoom in and out on the map. This is not terribly
    useful in environments that lack a mouse (ie, anything except a desktop
    or laptop computer). To address this, we will implement a simple pair
    of zoom buttons on the right-hand side of the map display.

    We also currently assume that panning the map using a mouse or touch
    screen is possible, which is not the case on, for example, many
    Symbian devices, which lack touch input. To rectify this, we will
    add support for handling arrow key events in \a GeoMap.

    First up, our zoom buttons. We're going to use a very similar setup
    to that which we used for the sliding status bar previously, and
    create a new subclass of QGraphicsRectItem:

    \code
class ZoomButtonItemPrivate;
class ZoomButtonItem : public QGraphicsRectItem
{
public:
    explicit ZoomButtonItem(GeoMap *map);

    void setRect(qreal x, qreal y, qreal w, qreal h);

private:
    ZoomButtonItemPrivate *d;

    bool isTopHalf(const QPointF &point);
    bool isBottomHalf(const QPointF &point);

protected:
    void mousePressEvent(QGraphicsSceneMouseEvent *event);
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
};
    \endcode

    Our button is going to simply be a translucent rectangle, with the top
    half containing a "+" symbol, which zooms in when clicked, and the
    bottom half containing a "-" symbol, which zooms out. In the constructor
    we create the two text items:

    \code
class ZoomButtonItemPrivate
{
public:
    GeoMap *map;

    QGraphicsSimpleTextItem *plusText;
    QGraphicsSimpleTextItem *minusText;

    bool pressedOverTopHalf;
    bool pressedOverBottomHalf;
};

ZoomButtonItem::ZoomButtonItem(GeoMap *map) :
    d(new ZoomButtonItemPrivate)
{
    d->map = map;
    d->pressedOverBottomHalf = false;
    d->pressedOverTopHalf = false;

    setPen(QPen(QBrush(), 0));
    setBrush(QBrush(QColor(0,0,0,150)));

    d->plusText = new QGraphicsSimpleTextItem(this);
    d->plusText->setText("+");
    d->plusText->setBrush(QBrush(Qt::white));

    d->minusText = new QGraphicsSimpleTextItem(this);
    d->minusText->setText("-");
    d->minusText->setBrush(QBrush(Qt::white));
}
    \endcode

    And in \a setRect() we manage sizing and aligning the text items so
    that they each occupy roughly half the space.

    \code
void ZoomButtonItem::setRect(qreal x, qreal y, qreal w, qreal h)
{
    QGraphicsRectItem::setRect(x, y, w, h);

    QFont f;
    f.setFixedPitch(true);
    f.setPixelSize(h/3.0);
    d->plusText->setFont(f);
    d->minusText->setFont(f);

    QRectF plusBound = d->plusText->boundingRect();
    QPointF plusCenter(x+w/2.0, y+h/4.0);
    QPointF plusDelta = plusCenter - plusBound.center();
    d->plusText->setPos(plusDelta);

    QRectF minusBound = d->minusText->boundingRect();
    QPointF minusCenter(x+w/2.0, y+3.0*h/4.0);
    QPointF minusDelta = minusCenter - minusBound.center();
    d->minusText->setPos(minusDelta);
}
    \endcode

    Finally, we use the boolean flags in ZoomButtonItemPrivate, above, to
    manage click detection in the \a mousePressEvent and \a mouseReleaseEvent
    functions:

    \code
void ZoomButtonItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    const QPointF pos = event->pos();
    if (!d->pressedOverTopHalf && !d->pressedOverBottomHalf) {
        if (isTopHalf(pos)) {
            d->pressedOverTopHalf = true;
        } else if (isBottomHalf(pos)) {
            d->pressedOverBottomHalf = true;
        }
    }
    event->accept();
}

void ZoomButtonItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    const QPointF pos = event->pos();
    if (isTopHalf(pos) && d->pressedOverTopHalf) {
        d->map->setZoomLevel(d->map->zoomLevel() + 1.0);
    } else if (isBottomHalf(pos) && d->pressedOverBottomHalf) {
        d->map->setZoomLevel(d->map->zoomLevel() - 1.0);
    }
    d->pressedOverBottomHalf = false;
    d->pressedOverTopHalf = false;
    event->accept();
}
    \endcode

    In this way, if the mouse (or finger for touch screens) is pressed
    and then released over the same half of the ZoomButtonItem, we
    perform the zoom action appropriately. We could have simply hooked
    the corresponding events on the children items, \a plusText and
    \a minusText, but as they occupy less space and their size/shape vary
    depending on the default font, users may find it difficult to target
    the active portion of the button (especially in a touch environment).

    Adding the new button item to the MapsWidget also happens similarly
    to before:

    \code
void MapsWidget::initialize(QGeoMappingManager *manager)
{
    ...
    d->zoomButtonItem = new ZoomButtonItem(d->map);
    sc->addItem(d->zoomButtonItem);

    resizeEvent(0);
    ...
}

void MapsWidget::resizeEvent(QResizeEvent *event)
{
    if (d->view && d->map) {
        ...
        d->zoomButtonItem->setRect(width()-30, height()/2.0 - 35, 25, 70);
    }
}
    \endcode

    And now we can zoom in and out properly on touch devices. Next we'll
    address the need to pan and zoom on devices with neither touch
    nor mouse, which we can do through handling key events.

    To do this we override the \a keyPressEvent() method on \a GeoMap:

    \code
void GeoMap::keyPressEvent(QKeyEvent *event)
{
    QGeoCoordinate center;
    QPropertyAnimation *anim;
    const qreal width = size().width();
    const qreal height = size().height();

    switch (event->key()) {
    case Qt::Key_4:
    case Qt::Key_Left:
        center = screenPositionToCoordinate(
                    QPointF(width/2 - width/5, height/2));
        anim = new QPropertyAnimation(this, "centerLongitude");
        anim->setEndValue(center.longitude());
        anim->setDuration(200);
        anim->start(QAbstractAnimation::DeleteWhenStopped);
        break;
    case Qt::Key_6:
    case Qt::Key_Right:
        center = screenPositionToCoordinate(
                    QPointF(width/2 + width/5, height/2));
        anim = new QPropertyAnimation(this, "centerLongitude");
        anim->setEndValue(center.longitude());
        anim->setDuration(200);
        anim->start(QAbstractAnimation::DeleteWhenStopped);
        break;
    case Qt::Key_2:
    case Qt::Key_Up:
        center = screenPositionToCoordinate(
                    QPointF(width/2, height/2 - height/5));
        anim = new QPropertyAnimation(this, "centerLatitude");
        anim->setEndValue(center.latitude());
        anim->setDuration(200);
        anim->start(QAbstractAnimation::DeleteWhenStopped);
        break;
    case Qt::Key_8:
    case Qt::Key_Down:
        center = screenPositionToCoordinate(
                    QPointF(width/2, height/2 + height/5));
        anim = new QPropertyAnimation(this, "centerLatitude");
        anim->setEndValue(center.latitude());
        anim->setDuration(200);
        anim->start(QAbstractAnimation::DeleteWhenStopped);
        break;
    case Qt::Key_1:
        if (zoomLevel() > minimumZoomLevel()) {
            setZoomLevel(zoomLevel() - 1);
        }
        break;
    case Qt::Key_3:
        if (zoomLevel() < maximumZoomLevel()) {
            setZoomLevel(zoomLevel() + 1);
        }
        break;
    }
    this->setFocus();
    event->accept();
}
    \endcode

    We allow both the arrow keys (which map to the sides of the D-pad
    on some devices), and the numbers 2, 8, 6 and 4 to pan the map,
    which some users may find more comfortable.

    In addition, the 1 and 3 keys allow zooming in and out. This key
    mapping is very similar to that used by the majority of maps
    applications on Symbian, and should be familiar to most users.

    \section2 Conclusion

    In summary, in this tutorial we have built a simple maps and
    navigation application from scratch using the Qt Location API.

    We first built the basic maps widget, then added a UI and search
    capability, followed by basic routing and some tuning for use
    on mobile platforms.

    The full code as at the end of the tutorial is available in the
    Qt Mobility examples, named "mapsdemo".
*/
