/****************************************************************************** * QSkinny - Copyright (C) 2016 Uwe Rathmann * This file may be used under the terms of the QSkinny License, Version 1.0 *****************************************************************************/ #include "QskSkinManager.h" #include "QskSkinFactory.h" #include #include #include #include #include #include #include #include /* We could use QFactoryLoader, but as it is again a "private" class and does a couple of hardcoded things we don't need ( like always resolving from the application library path ) we prefer having our own code. */ namespace { class SkinManager final : public QskSkinManager { }; } Q_GLOBAL_STATIC( SkinManager, qskGlobalSkinManager ) static QStringList qskPathList( const char* envName ) { const auto env = qgetenv( envName ); if ( env.isEmpty() ) return QStringList(); return QFile::decodeName( env ).split( QDir::listSeparator(), QString::SkipEmptyParts ); } static inline QString qskResolvedPath( const QString& path ) { return QDir( path ).canonicalPath(); } namespace { class FactoryLoader final : public QPluginLoader { public: FactoryLoader( QObject* parent = nullptr ): QPluginLoader( parent ) { } bool setPlugin( const QString& fileName ) { QPluginLoader::setFileName( fileName ); /* FactoryId and names of the skins can be found in the metadata without having to load the plugin itself */ const QLatin1String TokenInterfaceId( "IID" ); const QLatin1String TokenData( "MetaData" ); const QLatin1String TokenFactoryId( "FactoryId" ); const QLatin1String TokenSkins( "Skins" ); const QLatin1String InterfaceId( QskSkinFactoryIID ); const auto pluginData = metaData(); if ( pluginData.value( TokenInterfaceId ) == InterfaceId ) { const auto factoryData = pluginData.value( TokenData ).toObject(); m_factoryId = factoryData.value( TokenFactoryId ).toString().toLower(); if ( m_factoryId.isEmpty() ) { // Creating a dummy factory id static int i = 0; m_factoryId = QStringLiteral( "skin_factory_" ) + QString::number( i++ ); } const auto skinNames = factoryData.value( TokenSkins ).toArray(); for ( const auto& name : skinNames ) m_skinNames += name.toString().toLower(); } return !m_skinNames.isEmpty(); } inline QString factoryId() const { return m_factoryId; } inline QskSkinFactory* factory() { auto factory = qobject_cast< QskSkinFactory* >( QPluginLoader::instance() ); if ( factory ) { factory->setParent( nullptr ); factory->setObjectName( m_factoryId ); } return factory; } inline QStringList skinNames() const { return m_skinNames; } private: void setFileName( const QString& ) = delete; QObject* instance() = delete; QString m_factoryId; QStringList m_skinNames; }; class FactoryMap { private: class Data { public: Data(): loader( nullptr ) { } ~Data() { reset(); } void reset() { if ( factory && factory->parent() == nullptr ) delete factory; factory = nullptr; delete loader; loader = nullptr; } FactoryLoader* loader; QPointer< QskSkinFactory > factory; }; public: FactoryMap(): m_isValid( false ) { } void reset() { m_skinNames.clear(); m_skinMap.clear(); m_factoryMap.clear(); } QskSkinFactory* factory( const QString& skinName ) { if ( !m_isValid ) rebuild(); const auto it = m_skinMap.constFind( skinName ); if ( it != m_skinMap.constEnd() ) { auto it2 = m_factoryMap.find( it.value() ); if ( it2 != m_factoryMap.end() ) { auto& data = it2.value(); if ( ( data.factory == nullptr ) && data.loader != nullptr ) data.factory = data.loader->factory(); return data.factory; } } return nullptr; } QStringList skinNames() const { if ( !m_isValid ) const_cast< FactoryMap* >( this )->rebuild(); return m_skinMap.keys(); } QStringList skinNames( const QString& factoryId ) const { const auto it = m_factoryMap.constFind( factoryId ); if ( it != m_factoryMap.constEnd() ) { const auto& data = it.value(); if ( data.factory ) return data.factory->skinNames(); if ( data.loader ) return data.loader->skinNames(); } return QStringList(); } void insertFactory( FactoryLoader* loader ) { auto& data = m_factoryMap[ loader->factoryId() ]; if ( data.loader != loader ) { data.reset(); data.loader = loader; m_skinMap.clear(); m_isValid = false; } } void insertFactory( const QString& factoryId, QskSkinFactory* factory ) { auto& data = m_factoryMap[ factoryId ]; if ( data.factory != factory ) { data.reset(); data.factory = factory; m_skinMap.clear(); m_skinNames.clear(); m_isValid = false; } } void removeFactory( const QString& factoryId ) { const auto it = m_factoryMap.find( factoryId ); if ( it == m_factoryMap.end() ) return; m_factoryMap.erase( it ); if ( m_isValid ) { for ( auto it = m_skinMap.constBegin(); it != m_skinMap.constEnd(); ++it ) { if ( it.key() == factoryId ) { m_isValid = false; break; } } if ( !m_isValid ) { m_skinNames.clear(); m_skinMap.clear(); } } } inline bool hasFactory( const QString& factoryId ) const { return m_factoryMap.contains( factoryId ); } private: void rebuild() { m_skinMap.clear(); // first we try all factories, that have been added manually for ( auto it = m_factoryMap.begin(); it != m_factoryMap.end(); ++it ) { const auto& data = it.value(); if ( data.loader == nullptr && data.factory ) rebuild( it.key(), data.factory->skinNames() ); } // all factories from plugins are following for ( auto it = m_factoryMap.begin(); it != m_factoryMap.end(); ++it ) { const auto& data = it.value(); if ( data.loader ) rebuild( it.key(), data.loader->skinNames() ); } m_skinNames = m_skinMap.keys(); m_isValid = true; } void rebuild( const QString& factoryId, const QStringList& skinNames ) { for ( const auto& name : skinNames ) { if ( !m_skinMap.contains( name ) ) m_skinMap.insert( name, factoryId ); } } QMap< QString, Data > m_factoryMap; // factoryId -> data QMap< QString, QString > m_skinMap; // skinName -> factoryId QStringList m_skinNames; bool m_isValid; }; } class QskSkinManager::PrivateData { public: PrivateData(): pluginsRegistered( false ) { } inline void ensurePlugins() { if ( !pluginsRegistered ) { for ( auto path : pluginPaths ) registerPlugins( path + QStringLiteral( "/skins" ) ); pluginsRegistered = true; } } void registerPlugins( const QString& path ) { /* We only detect plugins, but don't load them before being needed. Static plugins are not supported as QskSkinmanager::registerFactory offers a better solution for this use case. */ QDir dir( path ); FactoryLoader* loader = nullptr; for ( auto fileName : dir.entryList( QDir::Files ) ) { if ( loader == nullptr ) loader = new FactoryLoader(); bool ok = loader->setPlugin( dir.absoluteFilePath( fileName ) ); if ( ok && !factoryMap.hasFactory( loader->factoryId() ) ) { factoryMap.insertFactory( loader ); loader = nullptr; } } delete loader; } public: QStringList pluginPaths; FactoryMap factoryMap; bool pluginsRegistered : 1; }; QskSkinManager* QskSkinManager::instance() { return qskGlobalSkinManager; } QskSkinManager::QskSkinManager(): m_data( new PrivateData() ) { setPluginPaths( qskPathList( "QSK_PLUGIN_PATH" ) + qskPathList( "QT_PLUGIN_PATH" ) ); } QskSkinManager::~QskSkinManager() { } void QskSkinManager::addPluginPath( const QString& path ) { const auto pluginPath = qskResolvedPath( path ); if ( !pluginPath.isEmpty() && !pluginPath.contains( pluginPath ) ) { m_data->pluginPaths += pluginPath; if ( m_data->pluginsRegistered ) m_data->registerPlugins( pluginPath ); } } void QskSkinManager::removePluginPath( const QString& path ) { const auto pluginPath = qskResolvedPath( path ); if ( m_data->pluginPaths.removeOne( pluginPath ) ) { if ( m_data->pluginsRegistered ) { m_data->factoryMap.reset(); m_data->pluginsRegistered = false; } } } void QskSkinManager::setPluginPaths( const QStringList& paths ) { m_data->pluginPaths.clear(); QSet< QString > pathSet; // checking for duplicates QStringList pluginPaths; for ( auto path : paths ) { const auto pluginPath = qskResolvedPath( path ); if ( !pluginPath.isEmpty() && !pathSet.contains( pluginPath ) ) { pathSet += pluginPath; pluginPaths += pluginPath; } } if ( pluginPaths != m_data->pluginPaths ) { m_data->pluginPaths = pluginPaths; m_data->factoryMap.reset(); m_data->pluginsRegistered = false; } } QStringList QskSkinManager::pluginPaths() const { return m_data->pluginPaths; } void QskSkinManager::registerFactory( const QString& factoryId, QskSkinFactory* factory ) { if ( factoryId.isEmpty() || factory == nullptr ) return; /* Manually registered factories always come first, and we don't need to check the plugins here. */ m_data->factoryMap.insertFactory( factoryId.toLower(), factory ); } void QskSkinManager::unregisterFactory( const QString& factoryId ) { if ( factoryId.isEmpty() ) return; /* As this call might be about a factory from a plugin, we need to know about them here. */ m_data->ensurePlugins(); m_data->factoryMap.removeFactory( factoryId.toLower() ); } QStringList QskSkinManager::skinNames() const { m_data->ensurePlugins(); return m_data->factoryMap.skinNames(); } QskSkin* QskSkinManager::createSkin( const QString& skinName ) const { m_data->ensurePlugins(); auto& map = m_data->factoryMap; auto name = skinName; auto factory = map.factory( name ); if ( factory == nullptr ) { /* Once the Fusion skin has been implemented it will be used as fallback. For the moment we implement another stupid fallback. TODO ... */ const auto names = map.skinNames(); if ( !names.isEmpty() ) { name = names.last(); factory = map.factory( name ); } } return factory ? factory->createSkin( name ) : nullptr; } #include "moc_QskSkinManager.cpp"