<commit_before>//
// This file is part of the Marble Virtual Globe.
//
// This program is free software licensed under the GNU LGPL. You can
// find a copy of this license in LICENSE.txt in the top directory of
// the source code.
//
// Copyright 2008 Henry de Valence <hdevalence@gmail.com>
// Copyright 2010 Dennis Nienhüser <earthwings@gentoo.org>
// Copyright 2011 Thibaut Gridel <tgridel@free.fr>

#include "MarbleRunnerManager.h"

#include "MarblePlacemarkModel.h"
#include "MarbleDebug.h"
#include "MarbleModel.h"
#include "Planet.h"
#include "GeoDataDocument.h"
#include "GeoDataPlacemark.h"
#include "PluginManager.h"
#include "RunnerPlugin.h"
#include "RunnerTask.h"
#include "routing/RouteRequest.h"
#include "routing/RoutingProfilesModel.h"

#include <QtCore/QObject>
#include <QtCore/QString>
#include <QtCore/QVector>
#include <QtCore/QThreadPool>
#include <QtCore/QTimer>

namespace Marble
{

class MarbleModel;

class MarbleRunnerManagerPrivate
{
public:
    MarbleRunnerManager* q;
    QString m_lastSearchTerm;
    QMutex m_modelMutex;
    MarblePlacemarkModel *m_model;
    QVector<GeoDataPlacemark*> m_placemarkContainer;
    QList<GeoDataCoordinates> m_reverseGeocodingResults;
    QString m_reverseGeocodingResult;
    QVector<GeoDataDocument*> m_routingResult;
    GeoDataDocument* m_fileResult;
    MarbleModel * m_marbleModel;
    const PluginManager* m_pluginManager;

    MarbleRunnerManagerPrivate( MarbleRunnerManager* parent, const PluginManager* pluginManager );

    ~MarbleRunnerManagerPrivate();

    QList<RunnerPlugin*> plugins( RunnerPlugin::Capability capability );

    QList<RunnerTask*> m_searchTasks;
    QList<RunnerTask*> m_reverseTasks;
    QList<RunnerTask*> m_routingTasks;
    QList<RunnerTask*> m_parsingTasks;
    int m_watchdogTimer;

    void addSearchResult( QVector<GeoDataPlacemark*> result );
    void addReverseGeocodingResult( const GeoDataCoordinates &coordinates, const GeoDataPlacemark &placemark );
    void addRoutingResult( GeoDataDocument* route );
    void addParsingResult( GeoDataDocument* document, const QString& error = QString() );

    void cleanupSearchTask( RunnerTask* task );
    void cleanupReverseGeocodingTask( RunnerTask* task );
    void cleanupRoutingTask( RunnerTask* task );
    void cleanupParsingTask( RunnerTask* task );

};

MarbleRunnerManagerPrivate::MarbleRunnerManagerPrivate( MarbleRunnerManager* parent, const PluginManager* pluginManager ) :
        q( parent ),
        m_model( new MarblePlacemarkModel( parent ) ),
        m_fileResult( 0 ),
        m_marbleModel( 0 ),
        m_pluginManager( pluginManager ),
        m_watchdogTimer( 30000 )
{
    m_model->setPlacemarkContainer( &m_placemarkContainer );
    qRegisterMetaType<GeoDataDocument*>( "GeoDataDocument*" );
    qRegisterMetaType<GeoDataPlacemark>( "GeoDataPlacemark" );
    qRegisterMetaType<GeoDataCoordinates>( "GeoDataCoordinates" );
    qRegisterMetaType<QVector<GeoDataPlacemark*> >( "QVector<GeoDataPlacemark*>" );
}

MarbleRunnerManagerPrivate::~MarbleRunnerManagerPrivate()
{
    // nothing to do
}

QList<RunnerPlugin*> MarbleRunnerManagerPrivate::plugins( RunnerPlugin::Capability capability )
{
    QList<RunnerPlugin*> result;
    QList<RunnerPlugin*> plugins = m_pluginManager->runnerPlugins();
    foreach( RunnerPlugin* plugin, plugins ) {
        if ( !plugin->supports( capability ) ) {
            continue;
        }

        if ( ( m_marbleModel && m_marbleModel->workOffline() && !plugin->canWorkOffline() ) ) {
            continue;
        }

        if ( !plugin->canWork( capability ) ) {
            continue;
        }

        if ( m_marbleModel && !plugin->supportsCelestialBody( m_marbleModel->planet()->id() ) )
        {
            continue;
        }

        result << plugin;
    }

    return result;
}

void MarbleRunnerManagerPrivate::cleanupSearchTask( RunnerTask* task )
{
    m_searchTasks.removeAll( task );
    mDebug() << "removing search task" << m_searchTasks.size() << (long)task;
    if ( m_searchTasks.isEmpty() ) {
        if( m_placemarkContainer.isEmpty() ) {
            emit q->searchResultChanged( m_model );
            emit q->searchResultChanged( m_placemarkContainer );
        }
        emit q->searchFinished( m_lastSearchTerm );
        emit q->placemarkSearchFinished();
    }
}

void MarbleRunnerManagerPrivate::cleanupReverseGeocodingTask( RunnerTask* task )
{
    m_reverseTasks.removeAll( task );
    mDebug() << "removing task " << m_reverseTasks.size() << " " << (long)task;
    if ( m_reverseTasks.isEmpty() ) {
        emit q->reverseGeocodingFinished();
    }
}

void MarbleRunnerManagerPrivate::cleanupRoutingTask( RunnerTask* task )
{
    m_routingTasks.removeAll( task );
    mDebug() << "removing task " << m_routingTasks.size() << " " << (long)task;
    if ( m_routingTasks.isEmpty() ) {
        if ( m_routingResult.isEmpty() ) {
            emit q->routeRetrieved( 0 );
        }

        emit q->routingFinished();
    }
}

void MarbleRunnerManagerPrivate::cleanupParsingTask( RunnerTask* task )
{
    m_parsingTasks.removeAll( task );
    mDebug() << "removing task " << m_parsingTasks.size() << " " << (long)task;

    if ( m_parsingTasks.isEmpty() ) {
        emit q->parsingFinished();
    }
}

MarbleRunnerManager::MarbleRunnerManager( const PluginManager* pluginManager, QObject *parent )
    : QObject( parent ), d( new MarbleRunnerManagerPrivate( this, pluginManager ) )
{
    if ( QThreadPool::globalInstance()->maxThreadCount() < 4 ) {
        QThreadPool::globalInstance()->setMaxThreadCount( 4 );
    }
}

MarbleRunnerManager::~MarbleRunnerManager()
{
    delete d;
}

void MarbleRunnerManager::findPlacemarks( const QString &searchTerm )
{
    if ( searchTerm == d->m_lastSearchTerm ) {
      emit searchResultChanged( d->m_model );
      emit searchResultChanged( d->m_placemarkContainer );
      emit searchFinished( searchTerm );
      emit placemarkSearchFinished();
      return;
    }

    d->m_lastSearchTerm = searchTerm;

    d->m_searchTasks.clear();

    d->m_modelMutex.lock();
    d->m_model->removePlacemarks( "MarbleRunnerManager", 0, d->m_placemarkContainer.size() );
    qDeleteAll( d->m_placemarkContainer );
    d->m_placemarkContainer.clear();
    d->m_modelMutex.unlock();
    emit searchResultChanged( d->m_model );

    if ( searchTerm.trimmed().isEmpty() ) {
        emit searchFinished( searchTerm );
        emit placemarkSearchFinished();
        return;
    }

    QList<RunnerPlugin*> plugins = d->plugins( RunnerPlugin::Search );
    foreach( RunnerPlugin* plugin, plugins ) {
        SearchTask* task = new SearchTask( plugin, this, d->m_marbleModel, searchTerm );
        connect( task, SIGNAL( finished( RunnerTask* ) ), this, SLOT( cleanupSearchTask( RunnerTask* ) ) );
        d->m_searchTasks << task;
        mDebug() << "search task " << plugin->nameId() << " " << (long)task;
        QThreadPool::globalInstance()->start( task );
    }

    if ( plugins.isEmpty() ) {
        d->cleanupSearchTask( 0 );
    }
}

void MarbleRunnerManagerPrivate::addSearchResult( QVector<GeoDataPlacemark*> result )
{
    mDebug() << "Runner reports" << result.size() << " search results";
    if( result.isEmpty() )
        return;

    m_modelMutex.lock();
    int start = m_placemarkContainer.size();
    m_placemarkContainer << result;
    m_model->addPlacemarks( start, result.size() );
    m_modelMutex.unlock();
    emit q->searchResultChanged( m_model );
    emit q->searchResultChanged( m_placemarkContainer );
}

QVector<GeoDataPlacemark*> MarbleRunnerManager::searchPlacemarks( const QString &searchTerm ) {
    QEventLoop localEventLoop;
    QTimer watchdog;
    watchdog.setSingleShot(true);
    connect( &watchdog, SIGNAL(timeout()),
             &localEventLoop, SLOT(quit()));
    connect(this, SIGNAL(placemarkSearchFinished()),
            &localEventLoop, SLOT(quit()), Qt::QueuedConnection );

    watchdog.start( d->m_watchdogTimer );
    findPlacemarks( searchTerm );
    localEventLoop.exec();
    return d->m_placemarkContainer;
}

void MarbleRunnerManager::setModel( MarbleModel * model )
{
    // TODO: Terminate runners which are making use of the map.
    d->m_marbleModel = model;
}

void MarbleRunnerManager::reverseGeocoding( const GeoDataCoordinates &coordinates )
{
    d->m_reverseTasks.clear();
    d->m_reverseGeocodingResult.clear();
    d->m_reverseGeocodingResults.removeAll( coordinates );
    QList<RunnerPlugin*> plugins = d->plugins( RunnerPlugin::ReverseGeocoding );
    foreach( RunnerPlugin* plugin, plugins ) {
        ReverseGeocodingTask* task = new ReverseGeocodingTask( plugin, this, d->m_marbleModel, coordinates );
        connect( task, SIGNAL( finished( RunnerTask* ) ), this, SLOT( cleanupReverseGeocodingTask(RunnerTask*) ) );
        mDebug() << "reverse task " << plugin->nameId() << " " << (long)task;
        d->m_reverseTasks << task;
        QThreadPool::globalInstance()->start( task );
    }

    if ( plugins.isEmpty() ) {
        emit reverseGeocodingFinished( coordinates, GeoDataPlacemark() );
        d->cleanupReverseGeocodingTask( 0 );
    }
}

void MarbleRunnerManagerPrivate::addReverseGeocodingResult( const GeoDataCoordinates &coordinates, const GeoDataPlacemark &placemark )
{
    if ( !m_reverseGeocodingResults.contains( coordinates ) && !placemark.address().isEmpty() ) {
        m_reverseGeocodingResults.push_back( coordinates );
        m_reverseGeocodingResult = placemark.address();
        emit q->reverseGeocodingFinished( coordinates, placemark );
    }

    if ( m_reverseTasks.isEmpty() ) {
        emit q->reverseGeocodingFinished();
    }
}

QString MarbleRunnerManager::searchReverseGeocoding( const GeoDataCoordinates &coordinates ) {
    QEventLoop localEventLoop;
    QTimer watchdog;
    watchdog.setSingleShot(true);
    connect( &watchdog, SIGNAL(timeout()),
             &localEventLoop, SLOT(quit()));
    connect(this, SIGNAL(reverseGeocodingFinished()),
            &localEventLoop, SLOT(quit()), Qt::QueuedConnection );

    watchdog.start( d->m_watchdogTimer );
    reverseGeocoding( coordinates );
    localEventLoop.exec();
    return d->m_reverseGeocodingResult;
}

void MarbleRunnerManager::retrieveRoute( const RouteRequest *request )
{
    RoutingProfile profile = request->routingProfile();

    d->m_routingTasks.clear();
    d->m_routingResult.clear();

    QList<RunnerPlugin*> plugins = d->plugins( RunnerPlugin::Routing );
    foreach( RunnerPlugin* plugin, plugins ) {
        if ( !profile.name().isEmpty() && !profile.pluginSettings().contains( plugin->nameId() ) ) {
            continue;
        }

        RoutingTask* task = new RoutingTask( plugin, this, d->m_marbleModel, request );
        connect( task, SIGNAL( finished( RunnerTask* ) ), this, SLOT( cleanupRoutingTask( RunnerTask* ) ) );
        mDebug() << "route task " << plugin->nameId() << " " << (long)task;
        d->m_routingTasks << task;
        QThreadPool::globalInstance()->start( task );
    }

    if ( plugins.isEmpty() ) {
        mDebug() << "No routing plugins found, cannot retrieve a route";
        d->cleanupRoutingTask( 0 );
    }
}

void MarbleRunnerManagerPrivate::addRoutingResult( GeoDataDocument* route )
{
    if ( route ) {
        mDebug() << "route retrieved";
        m_routingResult.push_back( route );
        emit q->routeRetrieved( route );
    }
}

QVector<GeoDataDocument*> MarbleRunnerManager::searchRoute( const RouteRequest *request ) {
    QEventLoop localEventLoop;
    QTimer watchdog;
    watchdog.setSingleShot(true);
    connect( &watchdog, SIGNAL(timeout()),
             &localEventLoop, SLOT(quit()));
    connect(this, SIGNAL(routingFinished()),
            &localEventLoop, SLOT(quit()), Qt::QueuedConnection );

    watchdog.start( d->m_watchdogTimer );
    retrieveRoute( request );
    localEventLoop.exec();
    return d->m_routingResult;
}

void MarbleRunnerManager::parseFile( const QString &fileName, DocumentRole role )
{
    QList<RunnerPlugin*> plugins = d->plugins( RunnerPlugin::Parsing );
    foreach( RunnerPlugin *plugin, plugins ) {
        ParsingTask *task = new ParsingTask( plugin, this, fileName, role );
        connect( task, SIGNAL( finished( RunnerTask* ) ), this, SLOT( cleanupParsingTask(RunnerTask*) ) );
        mDebug() << "parse task " << plugin->nameId() << " " << (long)task;
        d->m_parsingTasks << task;
        QThreadPool::globalInstance()->start( task );
    }

    if ( plugins.isEmpty() ) {
        emit parsingFinished( 0, "No plugin found");
        d->cleanupParsingTask( 0 );
    }
}

void MarbleRunnerManagerPrivate::addParsingResult( GeoDataDocument *document, const QString& error )
{
    if ( document || !error.isEmpty() ) {
        m_fileResult = document;
        emit q->parsingFinished( document, error );
    }
}

GeoDataDocument* MarbleRunnerManager::openFile( const QString &fileName, DocumentRole role ) {
    QEventLoop localEventLoop;
    QTimer watchdog;
    watchdog.setSingleShot(true);
    connect( &watchdog, SIGNAL(timeout()),
             &localEventLoop, SLOT(quit()));
    connect(this, SIGNAL(parsingFinished()),
            &localEventLoop, SLOT(quit()), Qt::QueuedConnection );

    watchdog.start( d->m_watchdogTimer );
    parseFile( fileName, role);
    localEventLoop.exec();
    return d->m_fileResult;
}

}

#include "MarbleRunnerManager.moc"
<commit_msg>MarbleRunnerManager: no warning popup when no plugin found<commit_after>//
// This file is part of the Marble Virtual Globe.
//
// This program is free software licensed under the GNU LGPL. You can
// find a copy of this license in LICENSE.txt in the top directory of
// the source code.
//
// Copyright 2008 Henry de Valence <hdevalence@gmail.com>
// Copyright 2010 Dennis Nienhüser <earthwings@gentoo.org>
// Copyright 2011 Thibaut Gridel <tgridel@free.fr>

#include "MarbleRunnerManager.h"

#include "MarblePlacemarkModel.h"
#include "MarbleDebug.h"
#include "MarbleModel.h"
#include "Planet.h"
#include "GeoDataDocument.h"
#include "GeoDataPlacemark.h"
#include "PluginManager.h"
#include "RunnerPlugin.h"
#include "RunnerTask.h"
#include "routing/RouteRequest.h"
#include "routing/RoutingProfilesModel.h"

#include <QtCore/QObject>
#include <QtCore/QString>
#include <QtCore/QVector>
#include <QtCore/QThreadPool>
#include <QtCore/QTimer>

namespace Marble
{

class MarbleModel;

class MarbleRunnerManagerPrivate
{
public:
    MarbleRunnerManager* q;
    QString m_lastSearchTerm;
    QMutex m_modelMutex;
    MarblePlacemarkModel *m_model;
    QVector<GeoDataPlacemark*> m_placemarkContainer;
    QList<GeoDataCoordinates> m_reverseGeocodingResults;
    QString m_reverseGeocodingResult;
    QVector<GeoDataDocument*> m_routingResult;
    GeoDataDocument* m_fileResult;
    MarbleModel * m_marbleModel;
    const PluginManager* m_pluginManager;

    MarbleRunnerManagerPrivate( MarbleRunnerManager* parent, const PluginManager* pluginManager );

    ~MarbleRunnerManagerPrivate();

    QList<RunnerPlugin*> plugins( RunnerPlugin::Capability capability );

    QList<RunnerTask*> m_searchTasks;
    QList<RunnerTask*> m_reverseTasks;
    QList<RunnerTask*> m_routingTasks;
    QList<RunnerTask*> m_parsingTasks;
    int m_watchdogTimer;

    void addSearchResult( QVector<GeoDataPlacemark*> result );
    void addReverseGeocodingResult( const GeoDataCoordinates &coordinates, const GeoDataPlacemark &placemark );
    void addRoutingResult( GeoDataDocument* route );
    void addParsingResult( GeoDataDocument* document, const QString& error = QString() );

    void cleanupSearchTask( RunnerTask* task );
    void cleanupReverseGeocodingTask( RunnerTask* task );
    void cleanupRoutingTask( RunnerTask* task );
    void cleanupParsingTask( RunnerTask* task );

};

MarbleRunnerManagerPrivate::MarbleRunnerManagerPrivate( MarbleRunnerManager* parent, const PluginManager* pluginManager ) :
        q( parent ),
        m_model( new MarblePlacemarkModel( parent ) ),
        m_fileResult( 0 ),
        m_marbleModel( 0 ),
        m_pluginManager( pluginManager ),
        m_watchdogTimer( 30000 )
{
    m_model->setPlacemarkContainer( &m_placemarkContainer );
    qRegisterMetaType<GeoDataDocument*>( "GeoDataDocument*" );
    qRegisterMetaType<GeoDataPlacemark>( "GeoDataPlacemark" );
    qRegisterMetaType<GeoDataCoordinates>( "GeoDataCoordinates" );
    qRegisterMetaType<QVector<GeoDataPlacemark*> >( "QVector<GeoDataPlacemark*>" );
}

MarbleRunnerManagerPrivate::~MarbleRunnerManagerPrivate()
{
    // nothing to do
}

QList<RunnerPlugin*> MarbleRunnerManagerPrivate::plugins( RunnerPlugin::Capability capability )
{
    QList<RunnerPlugin*> result;
    QList<RunnerPlugin*> plugins = m_pluginManager->runnerPlugins();
    foreach( RunnerPlugin* plugin, plugins ) {
        if ( !plugin->supports( capability ) ) {
            continue;
        }

        if ( ( m_marbleModel && m_marbleModel->workOffline() && !plugin->canWorkOffline() ) ) {
            continue;
        }

        if ( !plugin->canWork( capability ) ) {
            continue;
        }

        if ( m_marbleModel && !plugin->supportsCelestialBody( m_marbleModel->planet()->id() ) )
        {
            continue;
        }

        result << plugin;
    }

    return result;
}

void MarbleRunnerManagerPrivate::cleanupSearchTask( RunnerTask* task )
{
    m_searchTasks.removeAll( task );
    mDebug() << "removing search task" << m_searchTasks.size() << (long)task;
    if ( m_searchTasks.isEmpty() ) {
        if( m_placemarkContainer.isEmpty() ) {
            emit q->searchResultChanged( m_model );
            emit q->searchResultChanged( m_placemarkContainer );
        }
        emit q->searchFinished( m_lastSearchTerm );
        emit q->placemarkSearchFinished();
    }
}

void MarbleRunnerManagerPrivate::cleanupReverseGeocodingTask( RunnerTask* task )
{
    m_reverseTasks.removeAll( task );
    mDebug() << "removing task " << m_reverseTasks.size() << " " << (long)task;
    if ( m_reverseTasks.isEmpty() ) {
        emit q->reverseGeocodingFinished();
    }
}

void MarbleRunnerManagerPrivate::cleanupRoutingTask( RunnerTask* task )
{
    m_routingTasks.removeAll( task );
    mDebug() << "removing task " << m_routingTasks.size() << " " << (long)task;
    if ( m_routingTasks.isEmpty() ) {
        if ( m_routingResult.isEmpty() ) {
            emit q->routeRetrieved( 0 );
        }

        emit q->routingFinished();
    }
}

void MarbleRunnerManagerPrivate::cleanupParsingTask( RunnerTask* task )
{
    m_parsingTasks.removeAll( task );
    mDebug() << "removing task " << m_parsingTasks.size() << " " << (long)task;

    if ( m_parsingTasks.isEmpty() ) {
        emit q->parsingFinished();
    }
}

MarbleRunnerManager::MarbleRunnerManager( const PluginManager* pluginManager, QObject *parent )
    : QObject( parent ), d( new MarbleRunnerManagerPrivate( this, pluginManager ) )
{
    if ( QThreadPool::globalInstance()->maxThreadCount() < 4 ) {
        QThreadPool::globalInstance()->setMaxThreadCount( 4 );
    }
}

MarbleRunnerManager::~MarbleRunnerManager()
{
    delete d;
}

void MarbleRunnerManager::findPlacemarks( const QString &searchTerm )
{
    if ( searchTerm == d->m_lastSearchTerm ) {
      emit searchResultChanged( d->m_model );
      emit searchResultChanged( d->m_placemarkContainer );
      emit searchFinished( searchTerm );
      emit placemarkSearchFinished();
      return;
    }

    d->m_lastSearchTerm = searchTerm;

    d->m_searchTasks.clear();

    d->m_modelMutex.lock();
    d->m_model->removePlacemarks( "MarbleRunnerManager", 0, d->m_placemarkContainer.size() );
    qDeleteAll( d->m_placemarkContainer );
    d->m_placemarkContainer.clear();
    d->m_modelMutex.unlock();
    emit searchResultChanged( d->m_model );

    if ( searchTerm.trimmed().isEmpty() ) {
        emit searchFinished( searchTerm );
        emit placemarkSearchFinished();
        return;
    }

    QList<RunnerPlugin*> plugins = d->plugins( RunnerPlugin::Search );
    foreach( RunnerPlugin* plugin, plugins ) {
        SearchTask* task = new SearchTask( plugin, this, d->m_marbleModel, searchTerm );
        connect( task, SIGNAL( finished( RunnerTask* ) ), this, SLOT( cleanupSearchTask( RunnerTask* ) ) );
        d->m_searchTasks << task;
        mDebug() << "search task " << plugin->nameId() << " " << (long)task;
        QThreadPool::globalInstance()->start( task );
    }

    if ( plugins.isEmpty() ) {
        d->cleanupSearchTask( 0 );
    }
}

void MarbleRunnerManagerPrivate::addSearchResult( QVector<GeoDataPlacemark*> result )
{
    mDebug() << "Runner reports" << result.size() << " search results";
    if( result.isEmpty() )
        return;

    m_modelMutex.lock();
    int start = m_placemarkContainer.size();
    m_placemarkContainer << result;
    m_model->addPlacemarks( start, result.size() );
    m_modelMutex.unlock();
    emit q->searchResultChanged( m_model );
    emit q->searchResultChanged( m_placemarkContainer );
}

QVector<GeoDataPlacemark*> MarbleRunnerManager::searchPlacemarks( const QString &searchTerm ) {
    QEventLoop localEventLoop;
    QTimer watchdog;
    watchdog.setSingleShot(true);
    connect( &watchdog, SIGNAL(timeout()),
             &localEventLoop, SLOT(quit()));
    connect(this, SIGNAL(placemarkSearchFinished()),
            &localEventLoop, SLOT(quit()), Qt::QueuedConnection );

    watchdog.start( d->m_watchdogTimer );
    findPlacemarks( searchTerm );
    localEventLoop.exec();
    return d->m_placemarkContainer;
}

void MarbleRunnerManager::setModel( MarbleModel * model )
{
    // TODO: Terminate runners which are making use of the map.
    d->m_marbleModel = model;
}

void MarbleRunnerManager::reverseGeocoding( const GeoDataCoordinates &coordinates )
{
    d->m_reverseTasks.clear();
    d->m_reverseGeocodingResult.clear();
    d->m_reverseGeocodingResults.removeAll( coordinates );
    QList<RunnerPlugin*> plugins = d->plugins( RunnerPlugin::ReverseGeocoding );
    foreach( RunnerPlugin* plugin, plugins ) {
        ReverseGeocodingTask* task = new ReverseGeocodingTask( plugin, this, d->m_marbleModel, coordinates );
        connect( task, SIGNAL( finished( RunnerTask* ) ), this, SLOT( cleanupReverseGeocodingTask(RunnerTask*) ) );
        mDebug() << "reverse task " << plugin->nameId() << " " << (long)task;
        d->m_reverseTasks << task;
        QThreadPool::globalInstance()->start( task );
    }

    if ( plugins.isEmpty() ) {
        emit reverseGeocodingFinished( coordinates, GeoDataPlacemark() );
        d->cleanupReverseGeocodingTask( 0 );
    }
}

void MarbleRunnerManagerPrivate::addReverseGeocodingResult( const GeoDataCoordinates &coordinates, const GeoDataPlacemark &placemark )
{
    if ( !m_reverseGeocodingResults.contains( coordinates ) && !placemark.address().isEmpty() ) {
        m_reverseGeocodingResults.push_back( coordinates );
        m_reverseGeocodingResult = placemark.address();
        emit q->reverseGeocodingFinished( coordinates, placemark );
    }

    if ( m_reverseTasks.isEmpty() ) {
        emit q->reverseGeocodingFinished();
    }
}

QString MarbleRunnerManager::searchReverseGeocoding( const GeoDataCoordinates &coordinates ) {
    QEventLoop localEventLoop;
    QTimer watchdog;
    watchdog.setSingleShot(true);
    connect( &watchdog, SIGNAL(timeout()),
             &localEventLoop, SLOT(quit()));
    connect(this, SIGNAL(reverseGeocodingFinished()),
            &localEventLoop, SLOT(quit()), Qt::QueuedConnection );

    watchdog.start( d->m_watchdogTimer );
    reverseGeocoding( coordinates );
    localEventLoop.exec();
    return d->m_reverseGeocodingResult;
}

void MarbleRunnerManager::retrieveRoute( const RouteRequest *request )
{
    RoutingProfile profile = request->routingProfile();

    d->m_routingTasks.clear();
    d->m_routingResult.clear();

    QList<RunnerPlugin*> plugins = d->plugins( RunnerPlugin::Routing );
    foreach( RunnerPlugin* plugin, plugins ) {
        if ( !profile.name().isEmpty() && !profile.pluginSettings().contains( plugin->nameId() ) ) {
            continue;
        }

        RoutingTask* task = new RoutingTask( plugin, this, d->m_marbleModel, request );
        connect( task, SIGNAL( finished( RunnerTask* ) ), this, SLOT( cleanupRoutingTask( RunnerTask* ) ) );
        mDebug() << "route task " << plugin->nameId() << " " << (long)task;
        d->m_routingTasks << task;
        QThreadPool::globalInstance()->start( task );
    }

    if ( plugins.isEmpty() ) {
        mDebug() << "No routing plugins found, cannot retrieve a route";
        d->cleanupRoutingTask( 0 );
    }
}

void MarbleRunnerManagerPrivate::addRoutingResult( GeoDataDocument* route )
{
    if ( route ) {
        mDebug() << "route retrieved";
        m_routingResult.push_back( route );
        emit q->routeRetrieved( route );
    }
}

QVector<GeoDataDocument*> MarbleRunnerManager::searchRoute( const RouteRequest *request ) {
    QEventLoop localEventLoop;
    QTimer watchdog;
    watchdog.setSingleShot(true);
    connect( &watchdog, SIGNAL(timeout()),
             &localEventLoop, SLOT(quit()));
    connect(this, SIGNAL(routingFinished()),
            &localEventLoop, SLOT(quit()), Qt::QueuedConnection );

    watchdog.start( d->m_watchdogTimer );
    retrieveRoute( request );
    localEventLoop.exec();
    return d->m_routingResult;
}

void MarbleRunnerManager::parseFile( const QString &fileName, DocumentRole role )
{
    QList<RunnerPlugin*> plugins = d->plugins( RunnerPlugin::Parsing );
    foreach( RunnerPlugin *plugin, plugins ) {
        ParsingTask *task = new ParsingTask( plugin, this, fileName, role );
        connect( task, SIGNAL( finished( RunnerTask* ) ), this, SLOT( cleanupParsingTask(RunnerTask*) ) );
        mDebug() << "parse task " << plugin->nameId() << " " << (long)task;
        d->m_parsingTasks << task;
        QThreadPool::globalInstance()->start( task );
    }

    if ( plugins.isEmpty() ) {
        emit parsingFinished( 0 );
        d->cleanupParsingTask( 0 );
    }
}

void MarbleRunnerManagerPrivate::addParsingResult( GeoDataDocument *document, const QString& error )
{
    if ( document || !error.isEmpty() ) {
        m_fileResult = document;
        emit q->parsingFinished( document, error );
    }
}

GeoDataDocument* MarbleRunnerManager::openFile( const QString &fileName, DocumentRole role ) {
    QEventLoop localEventLoop;
    QTimer watchdog;
    watchdog.setSingleShot(true);
    connect( &watchdog, SIGNAL(timeout()),
             &localEventLoop, SLOT(quit()));
    connect(this, SIGNAL(parsingFinished()),
            &localEventLoop, SLOT(quit()), Qt::QueuedConnection );

    watchdog.start( d->m_watchdogTimer );
    parseFile( fileName, role);
    localEventLoop.exec();
    return d->m_fileResult;
}

}

#include "MarbleRunnerManager.moc"
<|endoftext|>


<commit_before>//
// This file is part of the Marble Virtual Globe.
//
// This program is free software licensed under the GNU LGPL. You can
// find a copy of this license in LICENSE.txt in the top directory of
// the source code.
//
// Copyright 2013   Utku Aydın <utkuaydin34@gmail.com>
//

#include "MergeItem.h"

#include "GeoDataPlacemark.h"

namespace Marble {

MergeItem::MergeItem()
{
}

QString MergeItem::pathA()
{
    return m_pathA;
}

void MergeItem::setPathA( const QString &path )
{
    m_pathA = path;
    pathAChanged();
}

QString MergeItem::pathB()
{
    return m_pathB;
}

void MergeItem::setPathB( const QString &path )
{
    m_pathB = path;
    pathBChanged();
}

GeoDataPlacemark MergeItem::placemarkA()
{
    return m_placemarkA;
}

void MergeItem::setPlacemarkA( const GeoDataPlacemark &placemark )
{
    m_placemarkA = placemark;
    emit placemarkAChanged();
    emit nameAChanged();
    emit descriptionAChanged();
}

GeoDataPlacemark MergeItem::placemarkB()
{
    return m_placemarkB;
}

void MergeItem::setPlacemarkB( const GeoDataPlacemark &placemark )
{
    m_placemarkB = placemark;
    emit placemarkBChanged();
    emit nameBChanged();
    emit descriptionBChanged();
}

QString MergeItem::nameA()
{
    return m_placemarkA.name();
}

QString MergeItem::nameB()
{
    return m_placemarkB.name();
}

QString MergeItem::descriptionA()
{
    return m_placemarkA.description();
}

QString MergeItem::descriptionB()
{
    return m_placemarkB.description();
}

MergeItem::Action MergeItem::actionA()
{
    return m_actionA;
}

void MergeItem::setActionA( MergeItem::Action action )
{
    m_actionA = action;
}

MergeItem::Action MergeItem::actionB()
{
    return m_actionB;
}

void MergeItem::setActionB( MergeItem::Action action )
{
    m_actionB = action;
}

MergeItem::Resolution MergeItem::resolution()
{
    return m_resolution;
}

void MergeItem::setResolution( MergeItem::Resolution resolution )
{
    m_resolution = resolution;
    emit resolutionChanged();
}

}

#include "MergeItem.moc"
<commit_msg>Add missing emit keywords<commit_after>//
// This file is part of the Marble Virtual Globe.
//
// This program is free software licensed under the GNU LGPL. You can
// find a copy of this license in LICENSE.txt in the top directory of
// the source code.
//
// Copyright 2013   Utku Aydın <utkuaydin34@gmail.com>
//

#include "MergeItem.h"

#include "GeoDataPlacemark.h"

namespace Marble {

MergeItem::MergeItem()
{
}

QString MergeItem::pathA()
{
    return m_pathA;
}

void MergeItem::setPathA( const QString &path )
{
    m_pathA = path;
    emit pathAChanged();
}

QString MergeItem::pathB()
{
    return m_pathB;
}

void MergeItem::setPathB( const QString &path )
{
    m_pathB = path;
    emit pathBChanged();
}

GeoDataPlacemark MergeItem::placemarkA()
{
    return m_placemarkA;
}

void MergeItem::setPlacemarkA( const GeoDataPlacemark &placemark )
{
    m_placemarkA = placemark;
    emit placemarkAChanged();
    emit nameAChanged();
    emit descriptionAChanged();
}

GeoDataPlacemark MergeItem::placemarkB()
{
    return m_placemarkB;
}

void MergeItem::setPlacemarkB( const GeoDataPlacemark &placemark )
{
    m_placemarkB = placemark;
    emit placemarkBChanged();
    emit nameBChanged();
    emit descriptionBChanged();
}

QString MergeItem::nameA()
{
    return m_placemarkA.name();
}

QString MergeItem::nameB()
{
    return m_placemarkB.name();
}

QString MergeItem::descriptionA()
{
    return m_placemarkA.description();
}

QString MergeItem::descriptionB()
{
    return m_placemarkB.description();
}

MergeItem::Action MergeItem::actionA()
{
    return m_actionA;
}

void MergeItem::setActionA( MergeItem::Action action )
{
    m_actionA = action;
}

MergeItem::Action MergeItem::actionB()
{
    return m_actionB;
}

void MergeItem::setActionB( MergeItem::Action action )
{
    m_actionB = action;
}

MergeItem::Resolution MergeItem::resolution()
{
    return m_resolution;
}

void MergeItem::setResolution( MergeItem::Resolution resolution )
{
    m_resolution = resolution;
    emit resolutionChanged();
}

}

#include "MergeItem.moc"
<|endoftext|>


<commit_before>//
// This file is part of the Marble Virtual Globe.
//
// This program is free software licensed under the GNU LGPL. You can
// find a copy of this license in LICENSE.txt in the top directory of
// the source code.
//
// Copyright 2006-2007 Torsten Rahn <tackat@kde.org>
// Copyright 2007      Inge Wallin  <ingwa@kde.org>
// Copyright 2008, 2009, 2010 Jens-Michael Hoffmann <jmho@c-xx.com>
// Copyright 2010-2012 Bernhard Beschow <bbeschow@cs.tu-berlin.de>//

#include "TextureLayer.h"

#include <QtCore/qmath.h>
#include <QtCore/QTimer>

#include "SphericalScanlineTextureMapper.h"
#include "EquirectScanlineTextureMapper.h"
#include "MercatorScanlineTextureMapper.h"
#include "TileScalingTextureMapper.h"
#include "GeoPainter.h"
#include "GeoSceneGroup.h"
#include "GeoSceneTypes.h"
#include "MergedLayerDecorator.h"
#include "MarbleDebug.h"
#include "MarbleDirs.h"
#include "StackedTile.h"
#include "StackedTileLoader.h"
#include "SunLocator.h"
#include "TextureColorizer.h"
#include "TileLoader.h"
#include "VectorComposer.h"
#include "ViewportParams.h"

namespace Marble
{

const int REPAINT_SCHEDULING_INTERVAL = 1000;

class TextureLayer::Private
{
public:
    Private( HttpDownloadManager *downloadManager,
             const SunLocator *sunLocator,
             VectorComposer *veccomposer,
             const PluginManager *pluginManager,
             TextureLayer *parent );

    void mapChanged();
    void updateTextureLayers();
    void updateTile( const TileId &tileId, const QImage &tileImage );

public:
    TextureLayer  *const m_parent;
    const SunLocator *const m_sunLocator;
    VectorComposer *const m_veccomposer;
    TileLoader m_loader;
    MergedLayerDecorator m_layerDecorator;
    StackedTileLoader    m_tileLoader;
    int m_tileZoomLevel;
    TextureMapperInterface *m_texmapper;
    TextureColorizer *m_texcolorizer;
    QVector<const GeoSceneTextureTile *> m_textures;
    const GeoSceneGroup *m_textureLayerSettings;
    QString m_runtimeTrace;
    // For scheduling repaints
    QTimer           m_repaintTimer;

};

TextureLayer::Private::Private( HttpDownloadManager *downloadManager,
                                const SunLocator *sunLocator,
                                VectorComposer *veccomposer,
                                const PluginManager *pluginManager,
                                TextureLayer *parent )
    : m_parent( parent )
    , m_sunLocator( sunLocator )
    , m_veccomposer( veccomposer )
    , m_loader( downloadManager, pluginManager )
    , m_layerDecorator( &m_loader, sunLocator )
    , m_tileLoader( &m_layerDecorator )
    , m_tileZoomLevel( -1 )
    , m_texmapper( 0 )
    , m_texcolorizer( 0 )
    , m_textureLayerSettings( 0 )
    , m_repaintTimer()
{
}

void TextureLayer::Private::mapChanged()
{
    if ( m_texmapper ) {
        m_texmapper->setRepaintNeeded();
    }

    if ( !m_repaintTimer.isActive() ) {
        m_repaintTimer.start();
    }
}

void TextureLayer::Private::updateTextureLayers()
{
    QVector<GeoSceneTextureTile const *> result;

    foreach ( const GeoSceneTextureTile *candidate, m_textures ) {
        bool enabled = true;
        if ( m_textureLayerSettings ) {
            const bool propertyExists = m_textureLayerSettings->propertyValue( candidate->name(), enabled );
            enabled |= !propertyExists; // if property doesn't exist, enable texture nevertheless
        }
        if ( enabled ) {
            result.append( candidate );
            mDebug() << "enabling texture" << candidate->name();
        } else {
            mDebug() << "disabling texture" << candidate->name();
        }
    }

    if ( !result.isEmpty() ) {
        const GeoSceneTextureTile *const firstTexture = result.at( 0 );
        m_layerDecorator.setLevelZeroLayout( firstTexture->levelZeroColumns(), firstTexture->levelZeroRows() );
        m_layerDecorator.setThemeId( "maps/" + firstTexture->sourceDir() );
    }
    m_tileLoader.setTextureLayers( result );
}

void TextureLayer::Private::updateTile( const TileId &tileId, const QImage &tileImage )
{
    if ( tileImage.isNull() )
        return; // keep tiles in cache to improve performance

    m_tileLoader.updateTile( tileId, tileImage );

    mapChanged();
}

TextureLayer::TextureLayer( HttpDownloadManager *downloadManager,
                            const SunLocator *sunLocator,
                            VectorComposer *veccomposer ,
                            const PluginManager *pluginManager )
    : QObject()
    , d( new Private( downloadManager, sunLocator, veccomposer, pluginManager, this ) )
{
    connect( &d->m_loader, SIGNAL(tileCompleted(TileId,QImage)),
             this, SLOT(updateTile(TileId,QImage)) );

    // Repaint timer
    d->m_repaintTimer.setSingleShot( true );
    d->m_repaintTimer.setInterval( REPAINT_SCHEDULING_INTERVAL );
    connect( &d->m_repaintTimer, SIGNAL(timeout()),
             this, SIGNAL(repaintNeeded()) );

    connect( d->m_veccomposer, SIGNAL(datasetLoaded()),
             this, SLOT(mapChanged()) );
}

TextureLayer::~TextureLayer()
{
    delete d->m_texmapper;
    delete d->m_texcolorizer;
    delete d;
}

QStringList TextureLayer::renderPosition() const
{
    return QStringList() << "SURFACE";
}

void TextureLayer::addSeaDocument( const GeoDataDocument *seaDocument )
{
    if( d->m_texcolorizer ) {
        d->m_texcolorizer->addSeaDocument( seaDocument );
        reset();
    }
}

void TextureLayer::addLandDocument( const GeoDataDocument *landDocument )
{
    if( d->m_texcolorizer ) {
        d->m_texcolorizer->addLandDocument( landDocument );
        reset();
    }
}

bool TextureLayer::showSunShading() const
{
    return d->m_layerDecorator.showSunShading();
}

bool TextureLayer::showCityLights() const
{
    return d->m_layerDecorator.showCityLights();
}

bool TextureLayer::render( GeoPainter *painter, ViewportParams *viewport,
                           const QString &renderPos, GeoSceneLayer *layer )
{
    Q_UNUSED( renderPos );
    Q_UNUSED( layer );

    // Stop repaint timer if it is already running
    d->m_repaintTimer.stop();

    if ( d->m_textures.isEmpty() )
        return false;

    if ( d->m_tileLoader.textureLayersSize() == 0 )
        return false;

    if ( !d->m_texmapper )
        return false;

    // choose the smaller dimension for selecting the tile level, leading to higher-resolution results
    const int levelZeroWidth = d->m_tileLoader.tileSize().width() * d->m_tileLoader.tileColumnCount( 0 );
    const int levelZeroHight = d->m_tileLoader.tileSize().height() * d->m_tileLoader.tileRowCount( 0 );
    const int levelZeroMinDimension = qMin( levelZeroWidth, levelZeroHight );

    // limit to 1 as dirty fix for invalid entry linearLevel
    const qreal linearLevel = qMax( 1.0, viewport->radius() * 4.0 / levelZeroMinDimension );

    // As our tile resolution doubles with each level we calculate
    // the tile level from tilesize and the globe radius via log(2)
    const qreal tileLevelF = qLn( linearLevel ) / qLn( 2.0 ) * 1.00001;  // snap to the sharper tile level a tiny bit earlier
                                                                         // to work around rounding errors when the radius
                                                                         // roughly equals the global texture width

    const int tileLevel = qMin<int>( d->m_tileLoader.maximumTileLevel(), tileLevelF );

    if ( tileLevel != d->m_tileZoomLevel ) {
        d->m_tileZoomLevel = tileLevel;
        emit tileLevelChanged( d->m_tileZoomLevel );
    }

    const QRect dirtyRect = QRect( QPoint( 0, 0), viewport->size() );
    d->m_texmapper->mapTexture( painter, viewport, d->m_tileZoomLevel, dirtyRect, d->m_texcolorizer );
    d->m_runtimeTrace = QString("Cache: %1 ").arg(d->m_tileLoader.tileCount());
    return true;
}

QString TextureLayer::runtimeTrace() const
{
    return d->m_runtimeTrace;
}

void TextureLayer::setShowRelief( bool show )
{
    if ( d->m_texcolorizer ) {
        d->m_texcolorizer->setShowRelief( show );
    }
}

void TextureLayer::setShowSunShading( bool show )
{
    disconnect( d->m_sunLocator, SIGNAL(positionChanged(qreal,qreal)),
                this, SLOT(reset()) );

    if ( show ) {
        connect( d->m_sunLocator, SIGNAL(positionChanged(qreal,qreal)),
                 this,       SLOT(reset()) );
    }

    d->m_layerDecorator.setShowSunShading( show );

    reset();
}

void TextureLayer::setShowCityLights( bool show )
{
    d->m_layerDecorator.setShowCityLights( show );

    reset();
}

void TextureLayer::setShowTileId( bool show )
{
    d->m_layerDecorator.setShowTileId( show );

    reset();
}

void TextureLayer::setProjection( Projection projection )
{
    // FIXME: replace this with an approach based on the factory method pattern.
    delete d->m_texmapper;

    switch( projection ) {
        case Spherical:
            d->m_texmapper = new SphericalScanlineTextureMapper( &d->m_tileLoader );
            break;
        case Equirectangular:
            d->m_texmapper = new EquirectScanlineTextureMapper( &d->m_tileLoader );
            break;
        case Mercator:
            if ( d->m_tileLoader.tileProjection() == GeoSceneTiled::Mercator ) {
                d->m_texmapper = new TileScalingTextureMapper( &d->m_tileLoader );
            } else {
                d->m_texmapper = new MercatorScanlineTextureMapper( &d->m_tileLoader );
            }
            break;
        default:
            d->m_texmapper = 0;
    }
    Q_ASSERT( d->m_texmapper );
}

void TextureLayer::setNeedsUpdate()
{
    if ( d->m_texmapper ) {
        d->m_texmapper->setRepaintNeeded();
    }
}

void TextureLayer::setVolatileCacheLimit( quint64 kilobytes )
{
    d->m_tileLoader.setVolatileCacheLimit( kilobytes );
}

void TextureLayer::reset()
{
    mDebug() << Q_FUNC_INFO;

    d->m_tileLoader.clear();
    d->mapChanged();
}

void TextureLayer::reload()
{
    d->m_tileLoader.reloadVisibleTiles();
}

void TextureLayer::downloadStackedTile( const TileId &stackedTileId )
{
    d->m_tileLoader.downloadStackedTile( stackedTileId );
}

void TextureLayer::setMapTheme( const QVector<const GeoSceneTextureTile *> &textures, const GeoSceneGroup *textureLayerSettings, const QString &seaFile, const QString &landFile )
{
    delete d->m_texcolorizer;
    d->m_texcolorizer = 0;

    if ( QFileInfo( seaFile ).isReadable() || QFileInfo( landFile ).isReadable() ) {
        d->m_texcolorizer = new TextureColorizer( seaFile, landFile, d->m_veccomposer );
    }

    d->m_textures = textures;
    d->m_textureLayerSettings = textureLayerSettings;

    if ( d->m_textureLayerSettings ) {
        connect( d->m_textureLayerSettings, SIGNAL(valueChanged(QString,bool)),
                 this,                      SLOT(updateTextureLayers()) );
    }

    d->updateTextureLayers();
}

int TextureLayer::tileZoomLevel() const
{
    return d->m_tileZoomLevel;
}

QSize TextureLayer::tileSize() const
{
    return d->m_tileLoader.tileSize();
}

GeoSceneTiled::Projection TextureLayer::tileProjection() const
{
    return d->m_tileLoader.tileProjection();
}

int TextureLayer::tileColumnCount( int level ) const
{
    return d->m_tileLoader.tileColumnCount( level );
}

int TextureLayer::tileRowCount( int level ) const
{
    return d->m_tileLoader.tileRowCount( level );
}

qint64 TextureLayer::volatileCacheLimit() const
{
    return d->m_tileLoader.volatileCacheLimit();
}

int TextureLayer::preferredRadiusCeil( int radius ) const
{
    const int tileWidth = d->m_tileLoader.tileSize().width();
    const int levelZeroColumns = d->m_tileLoader.tileColumnCount( 0 );
    const qreal linearLevel = 4.0 * (qreal)( radius ) / (qreal)( tileWidth * levelZeroColumns );
    const qreal tileLevelF = qLn( linearLevel ) / qLn( 2.0 );
    const int tileLevel = qCeil( tileLevelF );

    if ( tileLevel < 0 )
        return ( tileWidth * levelZeroColumns / 4 ) >> (-tileLevel);

    return ( tileWidth * levelZeroColumns / 4 ) << tileLevel;
}

int TextureLayer::preferredRadiusFloor( int radius ) const
{
    const int tileWidth = d->m_tileLoader.tileSize().width();
    const int levelZeroColumns = d->m_tileLoader.tileColumnCount( 0 );
    const qreal linearLevel = 4.0 * (qreal)( radius ) / (qreal)( tileWidth * levelZeroColumns );
    const qreal tileLevelF = qLn( linearLevel ) / qLn( 2.0 );
    const int tileLevel = qFloor( tileLevelF );

    if ( tileLevel < 0 )
        return ( tileWidth * levelZeroColumns / 4 ) >> (-tileLevel);

    return ( tileWidth * levelZeroColumns / 4 ) << tileLevel;
}

}

#include "TextureLayer.moc"
<commit_msg>Don't crash with texture-less themes (e.g. plain map).<commit_after>//
// This file is part of the Marble Virtual Globe.
//
// This program is free software licensed under the GNU LGPL. You can
// find a copy of this license in LICENSE.txt in the top directory of
// the source code.
//
// Copyright 2006-2007 Torsten Rahn <tackat@kde.org>
// Copyright 2007      Inge Wallin  <ingwa@kde.org>
// Copyright 2008, 2009, 2010 Jens-Michael Hoffmann <jmho@c-xx.com>
// Copyright 2010-2012 Bernhard Beschow <bbeschow@cs.tu-berlin.de>//

#include "TextureLayer.h"

#include <QtCore/qmath.h>
#include <QtCore/QTimer>

#include "SphericalScanlineTextureMapper.h"
#include "EquirectScanlineTextureMapper.h"
#include "MercatorScanlineTextureMapper.h"
#include "TileScalingTextureMapper.h"
#include "GeoPainter.h"
#include "GeoSceneGroup.h"
#include "GeoSceneTypes.h"
#include "MergedLayerDecorator.h"
#include "MarbleDebug.h"
#include "MarbleDirs.h"
#include "StackedTile.h"
#include "StackedTileLoader.h"
#include "SunLocator.h"
#include "TextureColorizer.h"
#include "TileLoader.h"
#include "VectorComposer.h"
#include "ViewportParams.h"

namespace Marble
{

const int REPAINT_SCHEDULING_INTERVAL = 1000;

class TextureLayer::Private
{
public:
    Private( HttpDownloadManager *downloadManager,
             const SunLocator *sunLocator,
             VectorComposer *veccomposer,
             const PluginManager *pluginManager,
             TextureLayer *parent );

    void mapChanged();
    void updateTextureLayers();
    void updateTile( const TileId &tileId, const QImage &tileImage );

public:
    TextureLayer  *const m_parent;
    const SunLocator *const m_sunLocator;
    VectorComposer *const m_veccomposer;
    TileLoader m_loader;
    MergedLayerDecorator m_layerDecorator;
    StackedTileLoader    m_tileLoader;
    int m_tileZoomLevel;
    TextureMapperInterface *m_texmapper;
    TextureColorizer *m_texcolorizer;
    QVector<const GeoSceneTextureTile *> m_textures;
    const GeoSceneGroup *m_textureLayerSettings;
    QString m_runtimeTrace;
    // For scheduling repaints
    QTimer           m_repaintTimer;

};

TextureLayer::Private::Private( HttpDownloadManager *downloadManager,
                                const SunLocator *sunLocator,
                                VectorComposer *veccomposer,
                                const PluginManager *pluginManager,
                                TextureLayer *parent )
    : m_parent( parent )
    , m_sunLocator( sunLocator )
    , m_veccomposer( veccomposer )
    , m_loader( downloadManager, pluginManager )
    , m_layerDecorator( &m_loader, sunLocator )
    , m_tileLoader( &m_layerDecorator )
    , m_tileZoomLevel( -1 )
    , m_texmapper( 0 )
    , m_texcolorizer( 0 )
    , m_textureLayerSettings( 0 )
    , m_repaintTimer()
{
}

void TextureLayer::Private::mapChanged()
{
    if ( m_texmapper ) {
        m_texmapper->setRepaintNeeded();
    }

    if ( !m_repaintTimer.isActive() ) {
        m_repaintTimer.start();
    }
}

void TextureLayer::Private::updateTextureLayers()
{
    QVector<GeoSceneTextureTile const *> result;

    foreach ( const GeoSceneTextureTile *candidate, m_textures ) {
        bool enabled = true;
        if ( m_textureLayerSettings ) {
            const bool propertyExists = m_textureLayerSettings->propertyValue( candidate->name(), enabled );
            enabled |= !propertyExists; // if property doesn't exist, enable texture nevertheless
        }
        if ( enabled ) {
            result.append( candidate );
            mDebug() << "enabling texture" << candidate->name();
        } else {
            mDebug() << "disabling texture" << candidate->name();
        }
    }

    if ( !result.isEmpty() ) {
        const GeoSceneTextureTile *const firstTexture = result.at( 0 );
        m_layerDecorator.setLevelZeroLayout( firstTexture->levelZeroColumns(), firstTexture->levelZeroRows() );
        m_layerDecorator.setThemeId( "maps/" + firstTexture->sourceDir() );
    }
    m_tileLoader.setTextureLayers( result );
}

void TextureLayer::Private::updateTile( const TileId &tileId, const QImage &tileImage )
{
    if ( tileImage.isNull() )
        return; // keep tiles in cache to improve performance

    m_tileLoader.updateTile( tileId, tileImage );

    mapChanged();
}

TextureLayer::TextureLayer( HttpDownloadManager *downloadManager,
                            const SunLocator *sunLocator,
                            VectorComposer *veccomposer ,
                            const PluginManager *pluginManager )
    : QObject()
    , d( new Private( downloadManager, sunLocator, veccomposer, pluginManager, this ) )
{
    connect( &d->m_loader, SIGNAL(tileCompleted(TileId,QImage)),
             this, SLOT(updateTile(TileId,QImage)) );

    // Repaint timer
    d->m_repaintTimer.setSingleShot( true );
    d->m_repaintTimer.setInterval( REPAINT_SCHEDULING_INTERVAL );
    connect( &d->m_repaintTimer, SIGNAL(timeout()),
             this, SIGNAL(repaintNeeded()) );

    connect( d->m_veccomposer, SIGNAL(datasetLoaded()),
             this, SLOT(mapChanged()) );
}

TextureLayer::~TextureLayer()
{
    delete d->m_texmapper;
    delete d->m_texcolorizer;
    delete d;
}

QStringList TextureLayer::renderPosition() const
{
    return QStringList() << "SURFACE";
}

void TextureLayer::addSeaDocument( const GeoDataDocument *seaDocument )
{
    if( d->m_texcolorizer ) {
        d->m_texcolorizer->addSeaDocument( seaDocument );
        reset();
    }
}

void TextureLayer::addLandDocument( const GeoDataDocument *landDocument )
{
    if( d->m_texcolorizer ) {
        d->m_texcolorizer->addLandDocument( landDocument );
        reset();
    }
}

bool TextureLayer::showSunShading() const
{
    return d->m_layerDecorator.showSunShading();
}

bool TextureLayer::showCityLights() const
{
    return d->m_layerDecorator.showCityLights();
}

bool TextureLayer::render( GeoPainter *painter, ViewportParams *viewport,
                           const QString &renderPos, GeoSceneLayer *layer )
{
    Q_UNUSED( renderPos );
    Q_UNUSED( layer );

    // Stop repaint timer if it is already running
    d->m_repaintTimer.stop();

    if ( d->m_textures.isEmpty() )
        return false;

    if ( d->m_tileLoader.textureLayersSize() == 0 )
        return false;

    if ( !d->m_texmapper )
        return false;

    // choose the smaller dimension for selecting the tile level, leading to higher-resolution results
    const int levelZeroWidth = d->m_tileLoader.tileSize().width() * d->m_tileLoader.tileColumnCount( 0 );
    const int levelZeroHight = d->m_tileLoader.tileSize().height() * d->m_tileLoader.tileRowCount( 0 );
    const int levelZeroMinDimension = qMin( levelZeroWidth, levelZeroHight );

    // limit to 1 as dirty fix for invalid entry linearLevel
    const qreal linearLevel = qMax( 1.0, viewport->radius() * 4.0 / levelZeroMinDimension );

    // As our tile resolution doubles with each level we calculate
    // the tile level from tilesize and the globe radius via log(2)
    const qreal tileLevelF = qLn( linearLevel ) / qLn( 2.0 ) * 1.00001;  // snap to the sharper tile level a tiny bit earlier
                                                                         // to work around rounding errors when the radius
                                                                         // roughly equals the global texture width

    const int tileLevel = qMin<int>( d->m_tileLoader.maximumTileLevel(), tileLevelF );

    if ( tileLevel != d->m_tileZoomLevel ) {
        d->m_tileZoomLevel = tileLevel;
        emit tileLevelChanged( d->m_tileZoomLevel );
    }

    const QRect dirtyRect = QRect( QPoint( 0, 0), viewport->size() );
    d->m_texmapper->mapTexture( painter, viewport, d->m_tileZoomLevel, dirtyRect, d->m_texcolorizer );
    d->m_runtimeTrace = QString("Cache: %1 ").arg(d->m_tileLoader.tileCount());
    return true;
}

QString TextureLayer::runtimeTrace() const
{
    return d->m_runtimeTrace;
}

void TextureLayer::setShowRelief( bool show )
{
    if ( d->m_texcolorizer ) {
        d->m_texcolorizer->setShowRelief( show );
    }
}

void TextureLayer::setShowSunShading( bool show )
{
    disconnect( d->m_sunLocator, SIGNAL(positionChanged(qreal,qreal)),
                this, SLOT(reset()) );

    if ( show ) {
        connect( d->m_sunLocator, SIGNAL(positionChanged(qreal,qreal)),
                 this,       SLOT(reset()) );
    }

    d->m_layerDecorator.setShowSunShading( show );

    reset();
}

void TextureLayer::setShowCityLights( bool show )
{
    d->m_layerDecorator.setShowCityLights( show );

    reset();
}

void TextureLayer::setShowTileId( bool show )
{
    d->m_layerDecorator.setShowTileId( show );

    reset();
}

void TextureLayer::setProjection( Projection projection )
{
    if ( d->m_textures.isEmpty() ) {
        return;
    }

    // FIXME: replace this with an approach based on the factory method pattern.
    delete d->m_texmapper;

    switch( projection ) {
        case Spherical:
            d->m_texmapper = new SphericalScanlineTextureMapper( &d->m_tileLoader );
            break;
        case Equirectangular:
            d->m_texmapper = new EquirectScanlineTextureMapper( &d->m_tileLoader );
            break;
        case Mercator:
            if ( d->m_tileLoader.tileProjection() == GeoSceneTiled::Mercator ) {
                d->m_texmapper = new TileScalingTextureMapper( &d->m_tileLoader );
            } else {
                d->m_texmapper = new MercatorScanlineTextureMapper( &d->m_tileLoader );
            }
            break;
        default:
            d->m_texmapper = 0;
    }
    Q_ASSERT( d->m_texmapper );
}

void TextureLayer::setNeedsUpdate()
{
    if ( d->m_texmapper ) {
        d->m_texmapper->setRepaintNeeded();
    }
}

void TextureLayer::setVolatileCacheLimit( quint64 kilobytes )
{
    d->m_tileLoader.setVolatileCacheLimit( kilobytes );
}

void TextureLayer::reset()
{
    mDebug() << Q_FUNC_INFO;

    d->m_tileLoader.clear();
    d->mapChanged();
}

void TextureLayer::reload()
{
    d->m_tileLoader.reloadVisibleTiles();
}

void TextureLayer::downloadStackedTile( const TileId &stackedTileId )
{
    d->m_tileLoader.downloadStackedTile( stackedTileId );
}

void TextureLayer::setMapTheme( const QVector<const GeoSceneTextureTile *> &textures, const GeoSceneGroup *textureLayerSettings, const QString &seaFile, const QString &landFile )
{
    delete d->m_texcolorizer;
    d->m_texcolorizer = 0;

    if ( QFileInfo( seaFile ).isReadable() || QFileInfo( landFile ).isReadable() ) {
        d->m_texcolorizer = new TextureColorizer( seaFile, landFile, d->m_veccomposer );
    }

    d->m_textures = textures;
    d->m_textureLayerSettings = textureLayerSettings;

    if ( d->m_textureLayerSettings ) {
        connect( d->m_textureLayerSettings, SIGNAL(valueChanged(QString,bool)),
                 this,                      SLOT(updateTextureLayers()) );
    }

    d->updateTextureLayers();
}

int TextureLayer::tileZoomLevel() const
{
    return d->m_tileZoomLevel;
}

QSize TextureLayer::tileSize() const
{
    return d->m_tileLoader.tileSize();
}

GeoSceneTiled::Projection TextureLayer::tileProjection() const
{
    return d->m_tileLoader.tileProjection();
}

int TextureLayer::tileColumnCount( int level ) const
{
    return d->m_tileLoader.tileColumnCount( level );
}

int TextureLayer::tileRowCount( int level ) const
{
    return d->m_tileLoader.tileRowCount( level );
}

qint64 TextureLayer::volatileCacheLimit() const
{
    return d->m_tileLoader.volatileCacheLimit();
}

int TextureLayer::preferredRadiusCeil( int radius ) const
{
    const int tileWidth = d->m_tileLoader.tileSize().width();
    const int levelZeroColumns = d->m_tileLoader.tileColumnCount( 0 );
    const qreal linearLevel = 4.0 * (qreal)( radius ) / (qreal)( tileWidth * levelZeroColumns );
    const qreal tileLevelF = qLn( linearLevel ) / qLn( 2.0 );
    const int tileLevel = qCeil( tileLevelF );

    if ( tileLevel < 0 )
        return ( tileWidth * levelZeroColumns / 4 ) >> (-tileLevel);

    return ( tileWidth * levelZeroColumns / 4 ) << tileLevel;
}

int TextureLayer::preferredRadiusFloor( int radius ) const
{
    const int tileWidth = d->m_tileLoader.tileSize().width();
    const int levelZeroColumns = d->m_tileLoader.tileColumnCount( 0 );
    const qreal linearLevel = 4.0 * (qreal)( radius ) / (qreal)( tileWidth * levelZeroColumns );
    const qreal tileLevelF = qLn( linearLevel ) / qLn( 2.0 );
    const int tileLevel = qFloor( tileLevelF );

    if ( tileLevel < 0 )
        return ( tileWidth * levelZeroColumns / 4 ) >> (-tileLevel);

    return ( tileWidth * levelZeroColumns / 4 ) << tileLevel;
}

}

#include "TextureLayer.moc"
<|endoftext|>


<commit_before>/************************************************************************
*
* Flood Project  (2008-201x)
* Licensed under the simplified BSD license. All rights reserved.
*
************************************************************************/

#pragma once

#include "Engine/API.h"
#include "Engine/Texture/TextureAtlas.h"

NAMESPACE_ENGINE_BEGIN

//-----------------------------------//

static const MaxRectsBinPack::FreeRectChoiceHeuristic gs_heuristic =
             MaxRectsBinPack::FreeRectChoiceHeuristic::RectBestAreaFit;

TextureAtlas::TextureAtlas(uint maxSize)
{
    atlasMaxSize = maxSize;
    atlasSize = std::min(maxSize, DefaultSize);
    rectanglePacker.Init(atlasSize,atlasSize);
    atlasImageHandle = ImageCreate(AllocatorGetHeap(),atlasSize,atlasSize,PixelFormat::R8G8B8A8);
}

static const int RBLOCK = 96;

// We use this function to rotate images in-place since sometimes
// the packing algorithm will rotate the image to have a better fit.
static void RotateImage(Image* srcImage, Image* dstImage, Vector2i dstOffset)
{
    assert(srcImage->getPixelFormat() == dstImage->getPixelFormat());

    int bpp = srcImage->getPixelSize();

    int srcWidth = srcImage->getWidth();
    int srcHeight = srcImage->getHeight();
    int dstWidth = srcHeight;
    int dstHeight = srcWidth;

    // get src and dst scan width
    int srcPitch  = srcWidth*bpp;
    int dstPitch  = dstImage->getWidth()*bpp;

    byte* bsrc = srcImage->getBuffer().data();
    byte* bdst = dstImage->getBuffer().data();

    for(int xs = 0; xs < dstWidth; xs += RBLOCK) {    // for all image blocks of RBLOCK*RBLOCK pixels
        for(int ys = 0; ys < dstHeight; ys += RBLOCK) {
            for(int y = ys; y < std::min(dstHeight, ys + RBLOCK); y++) {    // do rotation
                int y2 = dstHeight - y - 1;
                // point to src pixel at (y2, xs)
                byte* src_bits = bsrc + (xs * srcPitch) + (y2 * bpp);
                // point to dst pixel at (xs, y)
                byte* dst_bits = bdst + (( y + dstOffset.y) * dstPitch) + ((xs + dstOffset.x) * bpp);
                for (int x = xs; x < std::min(dstWidth, xs + RBLOCK); x++) {
                    // dst.SetPixel(x, y, src.GetPixel(y2, x));
                    for(int j = 0; j < bpp; j++) {
                        dst_bits[j] = src_bits[j];
                    }
                    dst_bits += bpp;
                    src_bits += srcPitch;
                }
            }
        }
    }
}

bool TextureAtlas::addImage(const ImageHandle& newImageHandle)
{
    printf("%i\n",newImageHandle.id);

    if(imageSubTextures.find( newImageHandle ) != imageSubTextures.end())
        return true;

    Image* newImage = newImageHandle.Resolve();

    Rect newRect = rectanglePacker.Insert(newImage->getWidth(), newImage->getHeight(), gs_heuristic);

    if (newRect.height == 0 || newRect.width == 0){
        if (atlasSize >= atlasMaxSize)
            return false;

        atlasSize = std::min(atlasSize*2, atlasMaxSize);
        resizeAtlas(atlasSize);

        return addImage(newImageHandle);
    }

    addImage(newImageHandle,newRect);

    return true;
}

bool TextureAtlas::getImageSubTexture(const ImageHandle& imageHandle, SubTexture& subTexture)
{
    if(imageSubTextures.find( imageHandle ) == imageSubTextures.end())
        return false;

    subTexture = imageSubTextures[imageHandle];
    return true;
}

ImageHandle TextureAtlas::getAtlasImageHandle() const
{
    return atlasImageHandle;
}

void TextureAtlas::resizeAtlas(uint newSize)
{
    rectanglePacker.Init(newSize,newSize);

    std::vector<RectSize> rectSizes;
    std::vector<Rect> newRects;

    Image* atlasImage = atlasImageHandle.Resolve();

    std::map<ImageHandle, SubTexture>::iterator iter;
    for (iter = imageSubTextures.begin(); iter != imageSubTextures.end(); ++iter) {
        RectSize rectSize;
        rectSize.width = iter->second.rect.width;
        rectSize.height = iter->second.rect.height;
        rectSizes.push_back(rectSize);
    }

    rectanglePacker.Insert(rectSizes, newRects, gs_heuristic);

    assert(newRects.size() == imageSubTextures.size());

    int i;
    for (i = 0, iter = imageSubTextures.begin(); iter != imageSubTextures.end(); ++iter, ++i)
    {
        ImageHandle newImageHandle = iter->first;
        Rect newRect = newRects[i];

        addImage(newImageHandle, newRect);
    }
}

void TextureAtlas::addImage(ImageHandle newImageHandle, Rect newRect)
{
    Image* newImage = newImageHandle.Resolve();

    bool wasRotated = newImage->getWidth() == newRect.height &&
                      newImage->getHeight() == newRect.width;

    Image* atlasImage = atlasImageHandle.Resolve();
    if (newImage->getPixelFormat() == atlasImage->getPixelFormat())
    {
        atlasImage->setBuffer(newImage,Vector2i(newRect.x,newRect.y));
    }
    else
    {
         assert(newImage->getPixelFormat() == PixelFormat::Depth &&
                atlasImage->getPixelFormat() == PixelFormat::R8G8B8A8);

         Image tmpImage(newImage->getWidth(),newImage->getHeight(),PixelFormat::R8G8B8A8);

         int newImageSize = newImage->getSize();
         std::vector<byte>& buffer = tmpImage.getBuffer();
         buffer.resize(newImageSize*4,0);

         for (int i = 0; i < newImageSize; i++)
         {
             buffer[i*4+3] = newImage->getBuffer()[i]; //A
         }

         if (wasRotated)
            RotateImage(&tmpImage,atlasImage,Vector2i(newRect.x,newRect.y));
         else
            atlasImage->setBuffer(&tmpImage,Vector2i(newRect.x,newRect.y));
    }

    SubTexture subTexture;
    subTexture.rect = newRect;
    subTexture.isRotated = wasRotated;

    imageSubTextures[newImageHandle] = subTexture;
}

//-----------------------------------//

NAMESPACE_ENGINE_END<commit_msg>Remove extra print.<commit_after>/************************************************************************
*
* Flood Project  (2008-201x)
* Licensed under the simplified BSD license. All rights reserved.
*
************************************************************************/

#pragma once

#include "Engine/API.h"
#include "Engine/Texture/TextureAtlas.h"

NAMESPACE_ENGINE_BEGIN

//-----------------------------------//

static const MaxRectsBinPack::FreeRectChoiceHeuristic gs_heuristic =
             MaxRectsBinPack::FreeRectChoiceHeuristic::RectBestAreaFit;

TextureAtlas::TextureAtlas(uint maxSize)
{
    atlasMaxSize = maxSize;
    atlasSize = std::min(maxSize, DefaultSize);
    rectanglePacker.Init(atlasSize,atlasSize);
    atlasImageHandle = ImageCreate(AllocatorGetHeap(),atlasSize,atlasSize,PixelFormat::R8G8B8A8);
}

static const int RBLOCK = 96;

// We use this function to rotate images in-place since sometimes
// the packing algorithm will rotate the image to have a better fit.
static void RotateImage(Image* srcImage, Image* dstImage, Vector2i dstOffset)
{
    assert(srcImage->getPixelFormat() == dstImage->getPixelFormat());

    int bpp = srcImage->getPixelSize();

    int srcWidth = srcImage->getWidth();
    int srcHeight = srcImage->getHeight();
    int dstWidth = srcHeight;
    int dstHeight = srcWidth;

    // get src and dst scan width
    int srcPitch  = srcWidth*bpp;
    int dstPitch  = dstImage->getWidth()*bpp;

    byte* bsrc = srcImage->getBuffer().data();
    byte* bdst = dstImage->getBuffer().data();

    for(int xs = 0; xs < dstWidth; xs += RBLOCK) {    // for all image blocks of RBLOCK*RBLOCK pixels
        for(int ys = 0; ys < dstHeight; ys += RBLOCK) {
            for(int y = ys; y < std::min(dstHeight, ys + RBLOCK); y++) {    // do rotation
                int y2 = dstHeight - y - 1;
                // point to src pixel at (y2, xs)
                byte* src_bits = bsrc + (xs * srcPitch) + (y2 * bpp);
                // point to dst pixel at (xs, y)
                byte* dst_bits = bdst + (( y + dstOffset.y) * dstPitch) + ((xs + dstOffset.x) * bpp);
                for (int x = xs; x < std::min(dstWidth, xs + RBLOCK); x++) {
                    // dst.SetPixel(x, y, src.GetPixel(y2, x));
                    for(int j = 0; j < bpp; j++) {
                        dst_bits[j] = src_bits[j];
                    }
                    dst_bits += bpp;
                    src_bits += srcPitch;
                }
            }
        }
    }
}

bool TextureAtlas::addImage(const ImageHandle& newImageHandle)
{
    if(imageSubTextures.find( newImageHandle ) != imageSubTextures.end())
        return true;

    Image* newImage = newImageHandle.Resolve();

    Rect newRect = rectanglePacker.Insert(newImage->getWidth(), newImage->getHeight(), gs_heuristic);

    if (newRect.height == 0 || newRect.width == 0){
        if (atlasSize >= atlasMaxSize)
            return false;

        atlasSize = std::min(atlasSize*2, atlasMaxSize);
        resizeAtlas(atlasSize);

        return addImage(newImageHandle);
    }

    addImage(newImageHandle,newRect);

    return true;
}

bool TextureAtlas::getImageSubTexture(const ImageHandle& imageHandle, SubTexture& subTexture)
{
    if(imageSubTextures.find( imageHandle ) == imageSubTextures.end())
        return false;

    subTexture = imageSubTextures[imageHandle];
    return true;
}

ImageHandle TextureAtlas::getAtlasImageHandle() const
{
    return atlasImageHandle;
}

void TextureAtlas::resizeAtlas(uint newSize)
{
    rectanglePacker.Init(newSize,newSize);

    std::vector<RectSize> rectSizes;
    std::vector<Rect> newRects;

    Image* atlasImage = atlasImageHandle.Resolve();

    std::map<ImageHandle, SubTexture>::iterator iter;
    for (iter = imageSubTextures.begin(); iter != imageSubTextures.end(); ++iter) {
        RectSize rectSize;
        rectSize.width = iter->second.rect.width;
        rectSize.height = iter->second.rect.height;
        rectSizes.push_back(rectSize);
    }

    rectanglePacker.Insert(rectSizes, newRects, gs_heuristic);

    assert(newRects.size() == imageSubTextures.size());

    int i;
    for (i = 0, iter = imageSubTextures.begin(); iter != imageSubTextures.end(); ++iter, ++i)
    {
        ImageHandle newImageHandle = iter->first;
        Rect newRect = newRects[i];

        addImage(newImageHandle, newRect);
    }
}

void TextureAtlas::addImage(ImageHandle newImageHandle, Rect newRect)
{
    Image* newImage = newImageHandle.Resolve();

    bool wasRotated = newImage->getWidth() == newRect.height &&
                      newImage->getHeight() == newRect.width;

    Image* atlasImage = atlasImageHandle.Resolve();
    if (newImage->getPixelFormat() == atlasImage->getPixelFormat())
    {
        atlasImage->setBuffer(newImage,Vector2i(newRect.x,newRect.y));
    }
    else
    {
         assert(newImage->getPixelFormat() == PixelFormat::Depth &&
                atlasImage->getPixelFormat() == PixelFormat::R8G8B8A8);

         Image tmpImage(newImage->getWidth(),newImage->getHeight(),PixelFormat::R8G8B8A8);

         int newImageSize = newImage->getSize();
         std::vector<byte>& buffer = tmpImage.getBuffer();
         buffer.resize(newImageSize*4,0);

         for (int i = 0; i < newImageSize; i++)
         {
             buffer[i*4+3] = newImage->getBuffer()[i]; //A
         }

         if (wasRotated)
            RotateImage(&tmpImage,atlasImage,Vector2i(newRect.x,newRect.y));
         else
            atlasImage->setBuffer(&tmpImage,Vector2i(newRect.x,newRect.y));
    }

    SubTexture subTexture;
    subTexture.rect = newRect;
    subTexture.isRotated = wasRotated;

    imageSubTextures[newImageHandle] = subTexture;
}

//-----------------------------------//

NAMESPACE_ENGINE_END<|endoftext|>


<commit_before>//////////////////////////////////////////////////////////////////////////
//
//  Copyright (c) 2014, Image Engine Design Inc. All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions are
//  met:
//
//      * Redistributions of source code must retain the above
//        copyright notice, this list of conditions and the following
//        disclaimer.
//
//      * Redistributions in binary form must reproduce the above
//        copyright notice, this list of conditions and the following
//        disclaimer in the documentation and/or other materials provided with
//        the distribution.
//
//      * Neither the name of John Haddon nor the names of
//        any other contributors to this software may be used to endorse or
//        promote products derived from this software without specific prior
//        written permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
//  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
//  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
//  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
//  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
//  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
//  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////

#include "GafferSceneUI/SelectionTool.h"

#include "GafferSceneUI/ContextAlgo.h"
#include "GafferSceneUI/SceneView.h"

#include "GafferScene/ScenePlug.h"

#include "GafferUI/Pointer.h"
#include "GafferUI/Style.h"

#include "boost/bind.hpp"

using namespace Imath;
using namespace IECore;
using namespace Gaffer;
using namespace GafferUI;
using namespace GafferScene;
using namespace GafferSceneUI;

//////////////////////////////////////////////////////////////////////////
// DragOverlay implementation
//////////////////////////////////////////////////////////////////////////

class SelectionTool::DragOverlay : public GafferUI::Gadget
{

	public :

		DragOverlay()
			:	Gadget()
		{
		}

		Imath::Box3f bound() const override
		{
			// we draw in raster space so don't have a sensible bound
			return Box3f();
		}

		void setStartPosition( const V3f &p )
		{
			if( m_startPosition == p )
			{
				return;
			}
			m_startPosition = p;
			requestRender();
		}

		const V3f &getStartPosition() const
		{
			return m_startPosition;
		}

		void setEndPosition( const V3f &p )
		{
			if( m_endPosition == p )
			{
				return;
			}
			m_endPosition = p;
			requestRender();
		}

		const V3f &getEndPosition() const
		{
			return m_endPosition;
		}

	protected :

		void doRenderLayer( Layer layer, const Style *style ) const override
		{
			if( layer != Layer::Main )
			{
				return Gadget::doRenderLayer( layer, style );
			}

			if( IECoreGL::Selector::currentSelector() )
			{
				return;
			}

			const ViewportGadget *viewportGadget = ancestor<ViewportGadget>();
			ViewportGadget::RasterScope rasterScope( viewportGadget );

			Box2f b;
			b.extendBy( viewportGadget->gadgetToRasterSpace( m_startPosition, this ) );
			b.extendBy( viewportGadget->gadgetToRasterSpace( m_endPosition, this ) );

			style->renderSelectionBox( b );
		}

	private :

		Imath::V3f m_startPosition;
		Imath::V3f m_endPosition;

};

//////////////////////////////////////////////////////////////////////////
// SelectionTool implementation
//////////////////////////////////////////////////////////////////////////

IE_CORE_DEFINERUNTIMETYPED( SelectionTool );

SelectionTool::ToolDescription<SelectionTool, SceneView> SelectionTool::g_toolDescription;
static IECore::InternedString g_dragOverlayName( "__selectionToolDragOverlay" );

SelectionTool::SelectionTool( SceneView *view, const std::string &name )
	:	Tool( view, name )
{
	SceneGadget *sg = sceneGadget();

	sg->buttonPressSignal().connect( boost::bind( &SelectionTool::buttonPress, this, ::_2 ) );
	sg->dragBeginSignal().connect( boost::bind( &SelectionTool::dragBegin, this, ::_1, ::_2 ) );
	sg->dragEnterSignal().connect( boost::bind( &SelectionTool::dragEnter, this, ::_1, ::_2 ) );
	sg->dragMoveSignal().connect( boost::bind( &SelectionTool::dragMove, this, ::_2 ) );
	sg->dragEndSignal().connect( boost::bind( &SelectionTool::dragEnd, this, ::_2 ) );
}

SelectionTool::~SelectionTool()
{
}

SceneGadget *SelectionTool::sceneGadget()
{
	return runTimeCast<SceneGadget>( view()->viewportGadget()->getPrimaryChild() );
}

SelectionTool::DragOverlay *SelectionTool::dragOverlay()
{
	// All instances of SelectionTool share a single drag overlay - this
	// allows SelectionTool to be subclassed for the creation of other tools.
	DragOverlay *result = view()->viewportGadget()->getChild<DragOverlay>( g_dragOverlayName );
	if( !result )
	{
		result = new DragOverlay;
		view()->viewportGadget()->setChild( g_dragOverlayName, result );
		result->setVisible( false );
	}
	return result;
}

bool SelectionTool::buttonPress( const GafferUI::ButtonEvent &event )
{
	if( event.buttons != ButtonEvent::Left )
	{
		return false;
	}

	if( !activePlug()->getValue() )
	{
		return false;
	}

	SceneGadget *sg = sceneGadget();
	ScenePlug::ScenePath objectUnderMouse;
	sg->objectAt( event.line, objectUnderMouse );

	PathMatcher selection = sg->getSelection();

	const bool shiftHeld = event.modifiers && ButtonEvent::Shift;
	if( !objectUnderMouse.size() )
	{
		// background click - clear the selection unless
		// shift is held in which case we might be starting
		// a drag to add more.
		if( !shiftHeld )
		{
			ContextAlgo::setSelectedPaths( view()->getContext(), IECore::PathMatcher() );
		}
	}
	else
	{
		const bool objectSelectedAlready = selection.match( objectUnderMouse ) & PathMatcher::ExactMatch;

		if( objectSelectedAlready )
		{
			if( shiftHeld )
			{
				selection.removePath( objectUnderMouse );
				ContextAlgo::setSelectedPaths( view()->getContext(), selection );
			}
		}
		else
		{
			if( !shiftHeld )
			{
				ContextAlgo::setSelectedPaths( view()->getContext(), IECore::PathMatcher() );
			}
			ContextAlgo::setLastSelectedPath( view()->getContext(), objectUnderMouse );
		}
	}

	return true;
}

IECore::RunTimeTypedPtr SelectionTool::dragBegin( GafferUI::Gadget *gadget, const GafferUI::DragDropEvent &event )
{
	SceneGadget *sg = sceneGadget();
	ScenePlug::ScenePath objectUnderMouse;

	if( !sg->objectAt( event.line, objectUnderMouse ) )
	{
		// drag to select
		dragOverlay()->setStartPosition( event.line.p1 );
		dragOverlay()->setEndPosition( event.line.p1 );
		dragOverlay()->setVisible( true );
		return gadget;
	}
	else
	{
		const PathMatcher &selection = sg->getSelection();
		if( selection.match( objectUnderMouse ) & PathMatcher::ExactMatch )
		{
			// drag the selection somewhere
			IECore::StringVectorDataPtr dragData = new IECore::StringVectorData();
			selection.paths( dragData->writable() );
			Pointer::setCurrent( "objects" );
			return dragData;
		}
	}
	return nullptr;
}

bool SelectionTool::dragEnter( const GafferUI::Gadget *gadget, const GafferUI::DragDropEvent &event )
{
	return event.sourceGadget == gadget && event.data == gadget;
}

bool SelectionTool::dragMove( const GafferUI::DragDropEvent &event )
{
	dragOverlay()->setEndPosition( event.line.p1 );
	return true;
}

bool SelectionTool::dragEnd( const GafferUI::DragDropEvent &event )
{
	Pointer::setCurrent( "" );
	if( !dragOverlay()->getVisible() )
	{
		return false;
	}

	dragOverlay()->setVisible( false );

	SceneGadget *sg = sceneGadget();
	PathMatcher selection = sg->getSelection();

	if( sg->objectsAt( dragOverlay()->getStartPosition(), dragOverlay()->getEndPosition(), selection ) )
	{
		ContextAlgo::setSelectedPaths( view()->getContext(), selection );
	}

	return true;
}
<commit_msg>SelectionTool : Fix modifier handling during mouse clicks<commit_after>//////////////////////////////////////////////////////////////////////////
//
//  Copyright (c) 2014, Image Engine Design Inc. All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions are
//  met:
//
//      * Redistributions of source code must retain the above
//        copyright notice, this list of conditions and the following
//        disclaimer.
//
//      * Redistributions in binary form must reproduce the above
//        copyright notice, this list of conditions and the following
//        disclaimer in the documentation and/or other materials provided with
//        the distribution.
//
//      * Neither the name of John Haddon nor the names of
//        any other contributors to this software may be used to endorse or
//        promote products derived from this software without specific prior
//        written permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
//  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
//  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
//  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
//  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
//  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
//  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////

#include "GafferSceneUI/SelectionTool.h"

#include "GafferSceneUI/ContextAlgo.h"
#include "GafferSceneUI/SceneView.h"

#include "GafferScene/ScenePlug.h"

#include "GafferUI/Pointer.h"
#include "GafferUI/Style.h"

#include "boost/bind.hpp"

using namespace Imath;
using namespace IECore;
using namespace Gaffer;
using namespace GafferUI;
using namespace GafferScene;
using namespace GafferSceneUI;

//////////////////////////////////////////////////////////////////////////
// DragOverlay implementation
//////////////////////////////////////////////////////////////////////////

class SelectionTool::DragOverlay : public GafferUI::Gadget
{

	public :

		DragOverlay()
			:	Gadget()
		{
		}

		Imath::Box3f bound() const override
		{
			// we draw in raster space so don't have a sensible bound
			return Box3f();
		}

		void setStartPosition( const V3f &p )
		{
			if( m_startPosition == p )
			{
				return;
			}
			m_startPosition = p;
			requestRender();
		}

		const V3f &getStartPosition() const
		{
			return m_startPosition;
		}

		void setEndPosition( const V3f &p )
		{
			if( m_endPosition == p )
			{
				return;
			}
			m_endPosition = p;
			requestRender();
		}

		const V3f &getEndPosition() const
		{
			return m_endPosition;
		}

	protected :

		void doRenderLayer( Layer layer, const Style *style ) const override
		{
			if( layer != Layer::Main )
			{
				return Gadget::doRenderLayer( layer, style );
			}

			if( IECoreGL::Selector::currentSelector() )
			{
				return;
			}

			const ViewportGadget *viewportGadget = ancestor<ViewportGadget>();
			ViewportGadget::RasterScope rasterScope( viewportGadget );

			Box2f b;
			b.extendBy( viewportGadget->gadgetToRasterSpace( m_startPosition, this ) );
			b.extendBy( viewportGadget->gadgetToRasterSpace( m_endPosition, this ) );

			style->renderSelectionBox( b );
		}

	private :

		Imath::V3f m_startPosition;
		Imath::V3f m_endPosition;

};

//////////////////////////////////////////////////////////////////////////
// SelectionTool implementation
//////////////////////////////////////////////////////////////////////////

IE_CORE_DEFINERUNTIMETYPED( SelectionTool );

SelectionTool::ToolDescription<SelectionTool, SceneView> SelectionTool::g_toolDescription;
static IECore::InternedString g_dragOverlayName( "__selectionToolDragOverlay" );

SelectionTool::SelectionTool( SceneView *view, const std::string &name )
	:	Tool( view, name )
{
	SceneGadget *sg = sceneGadget();

	sg->buttonPressSignal().connect( boost::bind( &SelectionTool::buttonPress, this, ::_2 ) );
	sg->dragBeginSignal().connect( boost::bind( &SelectionTool::dragBegin, this, ::_1, ::_2 ) );
	sg->dragEnterSignal().connect( boost::bind( &SelectionTool::dragEnter, this, ::_1, ::_2 ) );
	sg->dragMoveSignal().connect( boost::bind( &SelectionTool::dragMove, this, ::_2 ) );
	sg->dragEndSignal().connect( boost::bind( &SelectionTool::dragEnd, this, ::_2 ) );
}

SelectionTool::~SelectionTool()
{
}

SceneGadget *SelectionTool::sceneGadget()
{
	return runTimeCast<SceneGadget>( view()->viewportGadget()->getPrimaryChild() );
}

SelectionTool::DragOverlay *SelectionTool::dragOverlay()
{
	// All instances of SelectionTool share a single drag overlay - this
	// allows SelectionTool to be subclassed for the creation of other tools.
	DragOverlay *result = view()->viewportGadget()->getChild<DragOverlay>( g_dragOverlayName );
	if( !result )
	{
		result = new DragOverlay;
		view()->viewportGadget()->setChild( g_dragOverlayName, result );
		result->setVisible( false );
	}
	return result;
}

bool SelectionTool::buttonPress( const GafferUI::ButtonEvent &event )
{
	if( event.buttons != ButtonEvent::Left )
	{
		return false;
	}

	if( !activePlug()->getValue() )
	{
		return false;
	}

	SceneGadget *sg = sceneGadget();
	ScenePlug::ScenePath objectUnderMouse;
	sg->objectAt( event.line, objectUnderMouse );

	PathMatcher selection = sg->getSelection();

	const bool shiftHeld = event.modifiers & ButtonEvent::Shift;
	if( !objectUnderMouse.size() )
	{
		// background click - clear the selection unless
		// shift is held in which case we might be starting
		// a drag to add more.
		if( !shiftHeld )
		{
			ContextAlgo::setSelectedPaths( view()->getContext(), IECore::PathMatcher() );
		}
	}
	else
	{
		const bool objectSelectedAlready = selection.match( objectUnderMouse ) & PathMatcher::ExactMatch;

		if( objectSelectedAlready )
		{
			if( shiftHeld )
			{
				selection.removePath( objectUnderMouse );
				ContextAlgo::setSelectedPaths( view()->getContext(), selection );
			}
		}
		else
		{
			if( !shiftHeld )
			{
				ContextAlgo::setSelectedPaths( view()->getContext(), IECore::PathMatcher() );
			}
			ContextAlgo::setLastSelectedPath( view()->getContext(), objectUnderMouse );
		}
	}

	return true;
}

IECore::RunTimeTypedPtr SelectionTool::dragBegin( GafferUI::Gadget *gadget, const GafferUI::DragDropEvent &event )
{
	SceneGadget *sg = sceneGadget();
	ScenePlug::ScenePath objectUnderMouse;

	if( !sg->objectAt( event.line, objectUnderMouse ) )
	{
		// drag to select
		dragOverlay()->setStartPosition( event.line.p1 );
		dragOverlay()->setEndPosition( event.line.p1 );
		dragOverlay()->setVisible( true );
		return gadget;
	}
	else
	{
		const PathMatcher &selection = sg->getSelection();
		if( selection.match( objectUnderMouse ) & PathMatcher::ExactMatch )
		{
			// drag the selection somewhere
			IECore::StringVectorDataPtr dragData = new IECore::StringVectorData();
			selection.paths( dragData->writable() );
			Pointer::setCurrent( "objects" );
			return dragData;
		}
	}
	return nullptr;
}

bool SelectionTool::dragEnter( const GafferUI::Gadget *gadget, const GafferUI::DragDropEvent &event )
{
	return event.sourceGadget == gadget && event.data == gadget;
}

bool SelectionTool::dragMove( const GafferUI::DragDropEvent &event )
{
	dragOverlay()->setEndPosition( event.line.p1 );
	return true;
}

bool SelectionTool::dragEnd( const GafferUI::DragDropEvent &event )
{
	Pointer::setCurrent( "" );
	if( !dragOverlay()->getVisible() )
	{
		return false;
	}

	dragOverlay()->setVisible( false );

	SceneGadget *sg = sceneGadget();
	PathMatcher selection = sg->getSelection();

	if( sg->objectsAt( dragOverlay()->getStartPosition(), dragOverlay()->getEndPosition(), selection ) )
	{
		ContextAlgo::setSelectedPaths( view()->getContext(), selection );
	}

	return true;
}
<|endoftext|>