Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. About the position of engine->rootContext()->setContextProperty() and engine->load() resulting SEGFAULT
Forum Updated to NodeBB v4.3 + New Features

About the position of engine->rootContext()->setContextProperty() and engine->load() resulting SEGFAULT

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
2 Posts 2 Posters 32 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • M Offline
    M Offline
    Morty56
    wrote last edited by
    #1

    I've recently implemented an instrument cluster (since here I will mention it as IC). Meanwhile, something weird happens. The IC has methods named registerModel(), and loadQml(). When I do the two methods separately, it crashes with segmentation fault at the line of _engine->load(url). But when I do these methods in a single method, it works. What's going on?

    What I've done is just move one method to another by integrating two functions to resolve this issue. Nothing else has changed. Here is my code. Which part could bring the result of segmentation fault? I am so confused.

    Thank you all in advance for the replies!

    Main.cpp

    #include "InstrumentCluster.h"
    
    #include <QDebug>
    #include <iostream>
    
    int main(int argc, char *argv[]) {
    	int appExit = EXIT_FAILURE;
    
    	try {
    		InstrumentCluster cluster(argc, argv);
    
    		if (!cluster.openGateway("can1")) {
    			qCritical() << "Failed to open can1";
    			return EXIT_FAILURE;
    		}
    
    		ViewModel model;
    		cluster.connectModel("can1", model);
    
    		/* TWO SEPARATE METHODS: NOT WORKING. PRINT SEGMENTATION FAULT */
    		// cluster.registerModel("ViewModel", model);
    		// cluster.loadQML("qrc:/Main.qml");
    
    		/* INTEGRATED A SINGLE METHOD: WORKING! */
    		cluster.loadQML("qrc:/Main.qml", "ViewModel", model);
    
    		appExit = cluster.run();
    
    	} catch (const std::exception& e) {
    		qCritical() << "Application error:" << e.what();
    	}
    
    	return appExit;
    }
    
    

    InstrumentCluster.cpp

    #include "InstrumentCluster.h"
    
    #include <QQmlContext>
    #include <QFile>
    #include <QDebug>
    
    /* CON & DESTRUCTOR */
    InstrumentCluster::InstrumentCluster(int argc, char** argv)
    : _app(std::make_unique<QGuiApplication>(argc, argv)),
    _engine(std::make_unique<QQmlApplicationEngine>()) {
    }
    
    InstrumentCluster::~InstrumentCluster() {
    	for (const auto& pair : _threads) {
    		closeGateway(pair.first);
    	}
    }
    
    /* METHODS */
    bool InstrumentCluster::openGateway(const std::string& ifname) {
    	try {
    		if (_findGateway(ifname)) {
    			qWarning() << "[IC] Gateway for" << QString::fromStdString(ifname) << "already exists";
    			return true;
    		}
    
    		// Instanciate gateway
    		_gateways.emplace_back(new CANGateway(ifname));
    
    		// Instanciate thread and move the gateway worker to thread
    		_threads[ifname] = QThread_ptr(new QThread);
    		_gateways.back()->moveToThread(_threads[ifname].get());
    
    		// Set signals
    		_openGatewaySetSignals(_gateways.back(), _threads[ifname], ifname);
    
    		// Let the worker begin :)
    		_threads[ifname]->start();
    
    		qInfo() << "[IC] CAN Gateway opened for interface:" << QString::fromStdString(ifname);
    		return true;
    
    	} catch (const std::exception& e) {
    		qCritical() << "[IC] Failed to open gateway for" << QString::fromStdString(ifname) << ":" << e.what();
    
    		auto it = _threads.find(ifname);
    		if (it != _threads.end()) {
    			_threads.erase(it);
    		}
    		return false;
    	}
    }
    
    void InstrumentCluster::_openGatewaySetSignals(InstrumentCluster::CANGateway_ptr& gateway,
    											InstrumentCluster::QThread_ptr& thread, const std::string& ifname) {
    	// Connect thread management signals
    	QObject::connect(thread.get(), &QThread::started, gateway.get(), &CANGateway::start);
    	QObject::connect(gateway.get(), &CANGateway::finished, thread.get(), &QThread::quit);
    
    	// Connect status signals
    	QObject::connect(gateway.get(), &CANGateway::connected, [ifname]() {
    		qInfo() << "[IC] CAN Gateway activated:" << QString::fromStdString(ifname);
    	});
    	QObject::connect(gateway.get(), &CANGateway::disconnected, [ifname]() {
    		qInfo() << "[IC] CAN Gateway deactivated:" << QString::fromStdString(ifname);
    	});
    
    	// Connect error handling
    	QObject::connect(gateway.get(), &CANGateway::error, [ifname](const QString& error) {
    		qCritical() << "[IC] CAN Gateway error on" << QString::fromStdString(ifname) << ":" << error;
    	});
    }
    
    void InstrumentCluster::closeGateway(const std::string& ifname) {
    	const auto& gateway = _findGateway(ifname);
    	if (!gateway) {
    		qWarning() << "[IC] No gateway found for CAN interface:" << QString::fromStdString(ifname);
    		return;
    	}
    
    	// Stop the gateway
    	gateway->stop();
    
    	// Handle thread cleanup
    	auto it = _threads.find(ifname);
    	if (it != _threads.end()) {
    		const auto& thread = it->second;
    
    		if (thread->isRunning()) {
    			thread->quit();
    			thread->wait(CLOSE_WAIT);
    
    			if (thread->isRunning()) {
    				qWarning() << "[IC] Thread for" << QString::fromStdString(ifname)
    						<< "did not stop gracefully, terminating...";
    				thread->terminate();
    				thread->wait(FORCE_WAIT);
    			}
    		}
    		_threads.erase(it);
    	}
    
    	_gateways.remove_if([&ifname](const CANGateway_ptr& ptr) {
    		return ptr && ptr->ifname == ifname;
    	});
    
    	qInfo() << "[IC] CAN Gateway closed for interface:" << QString::fromStdString(ifname);
    }
    
    /* FUTURE: Implement a method to check if a gateway is connected */
    // bool InstrumentCluster::isGatewayConnected(const std::string& interface) const {
    // 	const auto& gateway = _findGateway(interface);
    // 	return gateway && gateway->isConnected();
    // }
    
    // FUTURE: Implement a method to list all connected gateways
    // std::vector<std::string> InstrumentCluster::getConnectedGateways() const {
    // 	std::vector<std::string> connectedGateways;
    // 	for (const auto& gateway : _gateways) {
    // 		if (gateway && gateway->isConnected()) {
    // 			connectedGateways.push_back(gateway->interfaceName());
    // 		}
    // 	}
    // 	return connectedGateways;
    // }
    
    void InstrumentCluster::connectModel(const std::string& interface, ViewModel& model) {
    	const auto& gateway = _findGateway(interface);
    	if (!gateway) {
    		qWarning() << "No gateway found for CAN interface:" << QString::fromStdString(interface);
    		return;
    	}
    
    	// Connect the single newData signal to ViewModel's receiveData slot
    	QObject::connect(gateway.get(), &CANGateway::newData, &model, &ViewModel::receiveData, Qt::QueuedConnection);
    	qInfo() << "[IC] Connected CAN Gateway" << QString::fromStdString(interface) << "to ViewModel.";
    }
    
    void InstrumentCluster::registerModel(const std::string& name, ViewModel& model) {
    	_engine->rootContext()->setContextProperty(QString::fromStdString(name), &model);
    	qInfo() << "[IC] ViewModel registered as:" << QString::fromStdString(name);
    }
    
    // void InstrumentCluster::loadQML(const std::string& qmlEntry) {
    void InstrumentCluster::loadQML(const std::string& qmlEntry, const std::string& name, ViewModel& model) {
    	/* ONLY THE DIFFERENCE IS THAT FUNCTION CALL IS MOVED TO HERE FROM registerModel()  */
    	_engine->rootContext()->setContextProperty(QString::fromStdString(name), &model);
    
    	const QUrl url(QString::fromStdString(qmlEntry));
    	if (!url.isValid() || url.isEmpty()) {
    		qCritical() << "[IC] QML entry path is invalid:" << QString::fromStdString(qmlEntry);
    		return;
    	}
    	if (url.isLocalFile()) {
    		if (!QFile::exists(url.toLocalFile())) {
    			qCritical() << "[IC] QML file does not exist at path:" << url.toLocalFile();
    			return;
    		}
    	}
    
    	QObject::connect(_engine.get(), &QQmlApplicationEngine::objectCreated, _app.get(),
    		[url](QObject *obj, const QUrl &objUrl) {
    			if (!obj && url == objUrl)
    				QCoreApplication::exit(EXIT_FAILURE);
    		},
    		Qt::QueuedConnection);
    
    	_engine->load(url);
    	qInfo() << "[IC] QML file loaded:" << QString::fromStdString(qmlEntry);
    }
    
    int InstrumentCluster::run() {
    	return _app->exec();
    }
    
    const InstrumentCluster::CANGateway_ptr& InstrumentCluster::_findGateway(const std::string& interface) const {
    	for (const auto& gateway : _gateways) {
    		if (gateway && gateway->ifname == interface) {
    			return gateway;
    		}
    	}
    	// Return a null reference - this will be checked by the caller
    	static CANGateway_ptr nullPtr;
    	return nullPtr;
    }
    
    

    ViewModel.hpp

    #ifndef VIEWMODEL_H
    # define VIEWMODEL_H
    
    # include <QObject>
    # include <QTimer>
    # include <QByteArray>
    
    # define WHEEL_DIAMETER 6.8 // CM
    # define PI 3.1415
    
    enum canID_e {
    	ID_RPM = 0x10,
    };
    
    class ViewModel : public QObject {
    	/* QT FRAMEWORK */
    	Q_OBJECT
    	Q_PROPERTY(int speed READ speed NOTIFY updateSpeed)
    
    	public slots:
    		void receiveData(int, const QByteArray&);
    
    	signals:
    		void updateSpeed();
    
    	/* CLASS */
    	public:
    		explicit ViewModel(QObject* parent = nullptr);
    		~ViewModel();
    
    		int speed() const { return _speed; }
    
    	private:
    		int _speed; // Centimeters per second
    
    		int _int(const QByteArray&, int) const;
    		// float _float(const QByteArray&, size_t) const;
    		// bool _bool(const QByteArray&, size_t) const;
    };
    
    #endif
    
    

    ViewModel.cpp

    #include "ViewModel.h"
    
    #include <QDebug>
    #include <cstring>
    
    ViewModel::ViewModel(QObject *parent)
    : QObject(parent), _speed(0) { std::cout << "constructing viewModel\\n"; }
    
    ViewModel::~ViewModel() { std::cout << "constructing viewModel\\n"; }
    
    /* QT METHODS */
    void ViewModel::receiveData(int canID, const QByteArray& data) {
    	qDebug() << "[ViewModel] Received CAN frame - ID:" << Qt::hex << canID
    			<< "Data:" << data.toHex(' ') << "Size:" << data.size();
    
    	switch (canID) {
    		case ID_RPM: {
    			if (data.size() < 2) {
    				qWarning() << "[ViewModel] RPM frame too short:" << data.size();
    				break;
    			}
    
    			// Convert RPM to speed using wheel diameter
    			// Speed = (wheel circumference * RPM) / 60 (to get per second)
    			// Result in cm/s as per your original implementation
    			int val = static_cast<int>(WHEEL_DIAMETER * PI * static_cast<float>(_int(data, 0)) / 60.0f);
    			if (val != _speed) {
    				_speed = val;
    				emit updateSpeed();
    			}
    			break;
    		}
    
    		default: qDebug() << "[ViewModel] Unreserved CAN ID received:" << Qt::hex << canID;
    	}
    }
    
    /* CLASS METHODS */
    int ViewModel::_int(const QByteArray& data, int pos = 0) const {
    	if (pos + 1 >= data.size()) {
    		return 0;
    	}
    
    	// Parse as big-endian 16-bit integer
    	return (static_cast<unsigned char>(data[pos]) << 8) |
    			static_cast<unsigned char>(data[pos + 1]);
    }
    
    
    JKSHJ 1 Reply Last reply
    0
    • M Morty56

      I've recently implemented an instrument cluster (since here I will mention it as IC). Meanwhile, something weird happens. The IC has methods named registerModel(), and loadQml(). When I do the two methods separately, it crashes with segmentation fault at the line of _engine->load(url). But when I do these methods in a single method, it works. What's going on?

      What I've done is just move one method to another by integrating two functions to resolve this issue. Nothing else has changed. Here is my code. Which part could bring the result of segmentation fault? I am so confused.

      Thank you all in advance for the replies!

      Main.cpp

      #include "InstrumentCluster.h"
      
      #include <QDebug>
      #include <iostream>
      
      int main(int argc, char *argv[]) {
      	int appExit = EXIT_FAILURE;
      
      	try {
      		InstrumentCluster cluster(argc, argv);
      
      		if (!cluster.openGateway("can1")) {
      			qCritical() << "Failed to open can1";
      			return EXIT_FAILURE;
      		}
      
      		ViewModel model;
      		cluster.connectModel("can1", model);
      
      		/* TWO SEPARATE METHODS: NOT WORKING. PRINT SEGMENTATION FAULT */
      		// cluster.registerModel("ViewModel", model);
      		// cluster.loadQML("qrc:/Main.qml");
      
      		/* INTEGRATED A SINGLE METHOD: WORKING! */
      		cluster.loadQML("qrc:/Main.qml", "ViewModel", model);
      
      		appExit = cluster.run();
      
      	} catch (const std::exception& e) {
      		qCritical() << "Application error:" << e.what();
      	}
      
      	return appExit;
      }
      
      

      InstrumentCluster.cpp

      #include "InstrumentCluster.h"
      
      #include <QQmlContext>
      #include <QFile>
      #include <QDebug>
      
      /* CON & DESTRUCTOR */
      InstrumentCluster::InstrumentCluster(int argc, char** argv)
      : _app(std::make_unique<QGuiApplication>(argc, argv)),
      _engine(std::make_unique<QQmlApplicationEngine>()) {
      }
      
      InstrumentCluster::~InstrumentCluster() {
      	for (const auto& pair : _threads) {
      		closeGateway(pair.first);
      	}
      }
      
      /* METHODS */
      bool InstrumentCluster::openGateway(const std::string& ifname) {
      	try {
      		if (_findGateway(ifname)) {
      			qWarning() << "[IC] Gateway for" << QString::fromStdString(ifname) << "already exists";
      			return true;
      		}
      
      		// Instanciate gateway
      		_gateways.emplace_back(new CANGateway(ifname));
      
      		// Instanciate thread and move the gateway worker to thread
      		_threads[ifname] = QThread_ptr(new QThread);
      		_gateways.back()->moveToThread(_threads[ifname].get());
      
      		// Set signals
      		_openGatewaySetSignals(_gateways.back(), _threads[ifname], ifname);
      
      		// Let the worker begin :)
      		_threads[ifname]->start();
      
      		qInfo() << "[IC] CAN Gateway opened for interface:" << QString::fromStdString(ifname);
      		return true;
      
      	} catch (const std::exception& e) {
      		qCritical() << "[IC] Failed to open gateway for" << QString::fromStdString(ifname) << ":" << e.what();
      
      		auto it = _threads.find(ifname);
      		if (it != _threads.end()) {
      			_threads.erase(it);
      		}
      		return false;
      	}
      }
      
      void InstrumentCluster::_openGatewaySetSignals(InstrumentCluster::CANGateway_ptr& gateway,
      											InstrumentCluster::QThread_ptr& thread, const std::string& ifname) {
      	// Connect thread management signals
      	QObject::connect(thread.get(), &QThread::started, gateway.get(), &CANGateway::start);
      	QObject::connect(gateway.get(), &CANGateway::finished, thread.get(), &QThread::quit);
      
      	// Connect status signals
      	QObject::connect(gateway.get(), &CANGateway::connected, [ifname]() {
      		qInfo() << "[IC] CAN Gateway activated:" << QString::fromStdString(ifname);
      	});
      	QObject::connect(gateway.get(), &CANGateway::disconnected, [ifname]() {
      		qInfo() << "[IC] CAN Gateway deactivated:" << QString::fromStdString(ifname);
      	});
      
      	// Connect error handling
      	QObject::connect(gateway.get(), &CANGateway::error, [ifname](const QString& error) {
      		qCritical() << "[IC] CAN Gateway error on" << QString::fromStdString(ifname) << ":" << error;
      	});
      }
      
      void InstrumentCluster::closeGateway(const std::string& ifname) {
      	const auto& gateway = _findGateway(ifname);
      	if (!gateway) {
      		qWarning() << "[IC] No gateway found for CAN interface:" << QString::fromStdString(ifname);
      		return;
      	}
      
      	// Stop the gateway
      	gateway->stop();
      
      	// Handle thread cleanup
      	auto it = _threads.find(ifname);
      	if (it != _threads.end()) {
      		const auto& thread = it->second;
      
      		if (thread->isRunning()) {
      			thread->quit();
      			thread->wait(CLOSE_WAIT);
      
      			if (thread->isRunning()) {
      				qWarning() << "[IC] Thread for" << QString::fromStdString(ifname)
      						<< "did not stop gracefully, terminating...";
      				thread->terminate();
      				thread->wait(FORCE_WAIT);
      			}
      		}
      		_threads.erase(it);
      	}
      
      	_gateways.remove_if([&ifname](const CANGateway_ptr& ptr) {
      		return ptr && ptr->ifname == ifname;
      	});
      
      	qInfo() << "[IC] CAN Gateway closed for interface:" << QString::fromStdString(ifname);
      }
      
      /* FUTURE: Implement a method to check if a gateway is connected */
      // bool InstrumentCluster::isGatewayConnected(const std::string& interface) const {
      // 	const auto& gateway = _findGateway(interface);
      // 	return gateway && gateway->isConnected();
      // }
      
      // FUTURE: Implement a method to list all connected gateways
      // std::vector<std::string> InstrumentCluster::getConnectedGateways() const {
      // 	std::vector<std::string> connectedGateways;
      // 	for (const auto& gateway : _gateways) {
      // 		if (gateway && gateway->isConnected()) {
      // 			connectedGateways.push_back(gateway->interfaceName());
      // 		}
      // 	}
      // 	return connectedGateways;
      // }
      
      void InstrumentCluster::connectModel(const std::string& interface, ViewModel& model) {
      	const auto& gateway = _findGateway(interface);
      	if (!gateway) {
      		qWarning() << "No gateway found for CAN interface:" << QString::fromStdString(interface);
      		return;
      	}
      
      	// Connect the single newData signal to ViewModel's receiveData slot
      	QObject::connect(gateway.get(), &CANGateway::newData, &model, &ViewModel::receiveData, Qt::QueuedConnection);
      	qInfo() << "[IC] Connected CAN Gateway" << QString::fromStdString(interface) << "to ViewModel.";
      }
      
      void InstrumentCluster::registerModel(const std::string& name, ViewModel& model) {
      	_engine->rootContext()->setContextProperty(QString::fromStdString(name), &model);
      	qInfo() << "[IC] ViewModel registered as:" << QString::fromStdString(name);
      }
      
      // void InstrumentCluster::loadQML(const std::string& qmlEntry) {
      void InstrumentCluster::loadQML(const std::string& qmlEntry, const std::string& name, ViewModel& model) {
      	/* ONLY THE DIFFERENCE IS THAT FUNCTION CALL IS MOVED TO HERE FROM registerModel()  */
      	_engine->rootContext()->setContextProperty(QString::fromStdString(name), &model);
      
      	const QUrl url(QString::fromStdString(qmlEntry));
      	if (!url.isValid() || url.isEmpty()) {
      		qCritical() << "[IC] QML entry path is invalid:" << QString::fromStdString(qmlEntry);
      		return;
      	}
      	if (url.isLocalFile()) {
      		if (!QFile::exists(url.toLocalFile())) {
      			qCritical() << "[IC] QML file does not exist at path:" << url.toLocalFile();
      			return;
      		}
      	}
      
      	QObject::connect(_engine.get(), &QQmlApplicationEngine::objectCreated, _app.get(),
      		[url](QObject *obj, const QUrl &objUrl) {
      			if (!obj && url == objUrl)
      				QCoreApplication::exit(EXIT_FAILURE);
      		},
      		Qt::QueuedConnection);
      
      	_engine->load(url);
      	qInfo() << "[IC] QML file loaded:" << QString::fromStdString(qmlEntry);
      }
      
      int InstrumentCluster::run() {
      	return _app->exec();
      }
      
      const InstrumentCluster::CANGateway_ptr& InstrumentCluster::_findGateway(const std::string& interface) const {
      	for (const auto& gateway : _gateways) {
      		if (gateway && gateway->ifname == interface) {
      			return gateway;
      		}
      	}
      	// Return a null reference - this will be checked by the caller
      	static CANGateway_ptr nullPtr;
      	return nullPtr;
      }
      
      

      ViewModel.hpp

      #ifndef VIEWMODEL_H
      # define VIEWMODEL_H
      
      # include <QObject>
      # include <QTimer>
      # include <QByteArray>
      
      # define WHEEL_DIAMETER 6.8 // CM
      # define PI 3.1415
      
      enum canID_e {
      	ID_RPM = 0x10,
      };
      
      class ViewModel : public QObject {
      	/* QT FRAMEWORK */
      	Q_OBJECT
      	Q_PROPERTY(int speed READ speed NOTIFY updateSpeed)
      
      	public slots:
      		void receiveData(int, const QByteArray&);
      
      	signals:
      		void updateSpeed();
      
      	/* CLASS */
      	public:
      		explicit ViewModel(QObject* parent = nullptr);
      		~ViewModel();
      
      		int speed() const { return _speed; }
      
      	private:
      		int _speed; // Centimeters per second
      
      		int _int(const QByteArray&, int) const;
      		// float _float(const QByteArray&, size_t) const;
      		// bool _bool(const QByteArray&, size_t) const;
      };
      
      #endif
      
      

      ViewModel.cpp

      #include "ViewModel.h"
      
      #include <QDebug>
      #include <cstring>
      
      ViewModel::ViewModel(QObject *parent)
      : QObject(parent), _speed(0) { std::cout << "constructing viewModel\\n"; }
      
      ViewModel::~ViewModel() { std::cout << "constructing viewModel\\n"; }
      
      /* QT METHODS */
      void ViewModel::receiveData(int canID, const QByteArray& data) {
      	qDebug() << "[ViewModel] Received CAN frame - ID:" << Qt::hex << canID
      			<< "Data:" << data.toHex(' ') << "Size:" << data.size();
      
      	switch (canID) {
      		case ID_RPM: {
      			if (data.size() < 2) {
      				qWarning() << "[ViewModel] RPM frame too short:" << data.size();
      				break;
      			}
      
      			// Convert RPM to speed using wheel diameter
      			// Speed = (wheel circumference * RPM) / 60 (to get per second)
      			// Result in cm/s as per your original implementation
      			int val = static_cast<int>(WHEEL_DIAMETER * PI * static_cast<float>(_int(data, 0)) / 60.0f);
      			if (val != _speed) {
      				_speed = val;
      				emit updateSpeed();
      			}
      			break;
      		}
      
      		default: qDebug() << "[ViewModel] Unreserved CAN ID received:" << Qt::hex << canID;
      	}
      }
      
      /* CLASS METHODS */
      int ViewModel::_int(const QByteArray& data, int pos = 0) const {
      	if (pos + 1 >= data.size()) {
      		return 0;
      	}
      
      	// Parse as big-endian 16-bit integer
      	return (static_cast<unsigned char>(data[pos]) << 8) |
      			static_cast<unsigned char>(data[pos + 1]);
      }
      
      
      JKSHJ Offline
      JKSHJ Offline
      JKSH
      Moderators
      wrote last edited by
      #2

      Hi, and welcome!

      @Morty56 said in About the position of engine->rootContext()->setContextProperty() and engine->load() resulting SEGFAULT:

      What I've done is just move one method to another by integrating two functions to resolve this issue. Nothing else has changed. Here is my code. Which part could bring the result of segmentation fault? I am so confused.

      Your code could contain a race condition, where moving functions around changes the outcome of the race. Check your code using tools like ASan and TSan.

      Qt Doc Search for browsers: forum.qt.io/topic/35616/web-browser-extension-for-improved-doc-searches

      1 Reply Last reply
      1

      • Login

      • Login or register to search.
      • First post
        Last post
      0
      • Categories
      • Recent
      • Tags
      • Popular
      • Users
      • Groups
      • Search
      • Get Qt Extensions
      • Unsolved