QML Map visible region - c++

In my application, I am using QtLocation to display a map. Since there is only QML API to render the map, here is my QML file:
import QtQuick 2.0
import QtPositioning 5.5
import QtLocation 5.5
Item{
anchors.fill: parent
Plugin{
id: osmplugin
name: "osm"
}
Map {
anchors.fill: parent
id: map
plugin: osmplugin;
zoomLevel: (maximumZoomLevel - minimumZoomLevel)/2
center {
// The Qt Company in Oslo
latitude: 59.9485
longitude: 10.7686
}
}
function bbox(){
return map.visibleRegion;
}
}
In the C++ code, I need to know the currently visible region in the map widget, QML Map has the property visibleRegion http://doc.qt.io/qt-5/qml-qtlocation-map.html#visibleRegion-prop
But I don't understand how to get it from C++ code, since QGeoShape is abstract;
I tried this:
QQuickItem* map = mMap->rootObject();
QGeoRectangle rect;
bool ok = QMetaObject::invokeMethod( map, "bbox", Qt::DirectConnection, Q_RETURN_ARG( QGeoRectangle, rect ) );
if ( !ok )
qDebug() << " Shit happens!";
qDebug() << rect.isValid();
But it did not help. Please tell me how do I get visible rectangle from QML Map.

The correct syntax is:
QQuickItem* map = mMap->rootObject();
QVariant ret;
bool ok = QMetaObject::invokeMethod( map, "bbox", Qt::DirectConnection, Q_RETURN_ARG( QVariant, ret ) );
if ( !ok ){
qWarning( "Fail to call qml method" );
}
QGeoRectangle rect = qvariant_cast<QGeoRectangle>( ret );
mNorth = rect.topLeft().latitude();
mSouth = rect.bottomLeft().latitude();
mWest = rect.topLeft().longitude();
mEast = rect.topRight().longitude();

Related

How to access QML delegate children of a ListView from C++

I have a QML ListView and I want to access to the delegate children in c++
main.qml
ApplicationWindow {
objectName: "wnd1"
ListView {
objectName: "mediaPlayerListView"
model: provider.mediaPlayerItems
delegate: MediaPlayerDelegate
}
}
MediaPlayerDelegate.qml
Row {
objectName: "mainRow"
VideoOutput {
objectName: "videoOutput" + modelData.id
}
Here is the c++ code I've tried
QString currentId = "1";
QQuickWindow *mainWindow = _qmlAppEngine->rootObjects()[0]->findChild<QQuickWindow *>("wnd1");
QObject *mediaPlayerListView = mainWindow->findChild<QObject *>("mediaPlayerListView");
QObject *mediaPlayerListViewDelegate = mediaPlayerListView->property("delegate").value<QObject *>();
I want to be able to get in cpp the videoOutput, doing something like this
QObject *videoOutput = mediaPlayerListViewDelegate->findChild<QObject *>("videoOutput" + currentId)
But I can't access further than the delegate, it has no children.

Qt control OSM location from C++

In my Qt5.9 widget application project (Windows), I added a QQuickWidget in the ui and set the source file to a QML file.
My itention is to display open street maps in the QQuickWidget. By clicking a button, the center location of the map should change to specific lat/long coordinates.
The map gets displayed in the QQuickWidget as expected, however, I can't get the location change by button click to work.
I am using this QML file content to display the map:
//================================
// map.qml
//================================
import QtQuick 2.0
import QtQuick.Window 2.0
import QtLocation 5.6
import QtPositioning 5.6
Item {
id: qmlMap
Plugin {
id: osmPlugin
name: "osm"
}
Map {
id: map
anchors.fill: parent
plugin: osmPlugin
center: QtPositioning.coordinate(59.91, 10.75)
zoomLevel: 10
objectName: "mainMap"
MapQuickItem {
id: marker
coordinate {latitude: 59.91
longitude: 10.75}
anchorPoint.x: image.width * 0.5
anchorPoint.y: image.height
sourceItem: Image {
id: image
height: 35
width: 35
source: "geotag.png"
}
function recenter(lat,lng) {
map.clearMapItems();
marker.coordinate.latitude = lat;
marker.coordinate.longitude = lng;
map.addMapItem(marker);
map.center.latitude = lat;
map.center.longitude = lng;
map.update();
}
}
}
}
On application start up, I can see the OSM centered on my specified location and I also can see the marker at the right location.
Loaded map on start up
However, when I click my button to call the function recenter(lat,lng) from C++, nothing seems to happen (no location change on map visible).
My C++ button code for location change is:
void mapproject::on_btnUpdatePos_clicked()
{
QQmlEngine engine;
QQmlComponent component(&engine, "qrc:/map.qml");
QObject *object = component.create();
QVariant returnedValue;
QVariant pos = QVariant(0);
if(object != NULL){
QMetaObject::invokeMethod(object, "recenter",
Q_RETURN_ARG(QVariant, returnedValue),
Q_ARG(QVariant, pos),
Q_ARG(QVariant, pos));
}
}
Why does the location change not work? Is there a mistake in my QML file or in my C++ code?
Assuming that the QQuickWidget has been added through Qt Designer and it is called quickWidget, so you can access it using ui->quickWidget.
To do a simple search you can set an objectName in the MapQuickItem:
MapQuickItem {
id: marker
objectName: "mapItem"
coordinate {latitude: 59.91
[...]
You should not create a new component, you should use the QQuickWidget, the first thing is to get the item that shows the QQuickWidget through the rootObject() method, then look for the child named mapItem and invoke the recenter method:
void MainWindow::on_btnUpdatePos_clicked()
{
QQuickItem *item = ui->quickWidget->rootObject();
QObject *object = item->findChild<QObject*>("mapItem");
QVariant posx = QVariant(-12.0464);
QVariant posy = QVariant(-77.0428);
if(object != NULL){
QMetaObject::invokeMethod(object, "recenter",
Q_ARG(QVariant, posx),
Q_ARG(QVariant, posy));
}
}
The complete example can be found in the following link

Qt Create a QML Slider Sensitive to Touch Events

I am creating a game for touch screens that requires 2-4 players to each have access to a pair of slider controls. The problem is that the QML Slider control responds to touch as a mouse event and seizes the focus. Then only one player can access a single control at a time. I need multiple sliders to respond to touch events simultaneously. My question is how to do that?
With the help of a variety of stack overflow posts, I have been able to create my own answer that so far seems to work. I detail the answer below in the answer section to save other newbies like me the trouble.
There is a pure qml solution to this problem. The TouchSlider C++ object in my first answer (elsewhere in this thread) was unnecessary. Here I have modified the code to the TouchSlider qml code to eliminate references to touchslider (the TouchSlider C++ object).
TouchPoint.qml:
import QtQuick 2.0
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
Item {
property string sliderTitle
property real sliderMin
property real sliderMax
property real sliderVal
ColumnLayout{
id: column1
Label {
text: qsTr(sliderTitle)
font.pointSize: 10
}
Slider {
id: touchSlider1
minimumValue: sliderMin
maximumValue: sliderMax
orientation: Qt.Vertical
value: sliderVal
onValueChanged: function(){
sliderVal = Math.round(this.value);
labelSliderValue.text = qsTr(JSON.stringify(sliderVal));
}
}
Label {
id: labelSliderValue
text: qsTr(JSON.stringify(sliderVal))
font.pointSize: 10
}
function sliderSetValueFromTouch(position){
// Assume qs a vertical slider
var minvalue = touchSlider1.minimumValue;
var maxvalue = touchSlider1.maximumValue;
// Since this is a vertical slider, by assumption, get the height
var height = touchSlider1.height;
// Compute the new value based on position coordinate
var newvalue = (height-position)/height * (maxvalue-minvalue);
if (newvalue<minvalue) newvalue = minvalue;
if (newvalue>maxvalue) newvalue = maxvalue;
//qDebug() << newvalue;
// Set the value of the slider
touchSlider1.value = newvalue;
}
MultiPointTouchArea{
anchors.fill: touchSlider1
touchPoints: [
TouchPoint {
id: point1
onPressedChanged: function(){
if(pressed){
//console.log("pressed");
//console.log(touchslider.testStringReturn());
//touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
column1.sliderSetValueFromTouch(point1.y);
}
}
}
]
onTouchUpdated: function(){
//touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
column1.sliderSetValueFromTouch(point1.y);
}
}
}
}
The touchslider.h and touchslider.cpp files add no value.
I could not find a pure QML way to solve the problem but I wanted to minimize the use of C++. Using C++, I create an object TouchSlider and add it to my qml scene. The TouchSlider object has a simple function to update the value of a vertical slider according to a position argument. Then in the QML code, I add a MultiPointTouchArea on top of a regular slider and respond to the touch events by calling C++ function.
Here are all my files for a project called SliderPair.
SliderPair.pro:
QT += qml quick widgets
QT += quickcontrols2
QT += core
CONFIG += c++11
SOURCES += main.cpp \
touchslider.cpp
RESOURCES += \
qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH += qml
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
HEADERS += \
touchslider.h
DISTFILES +=
main.cpp:
#include <QApplication>
#include <QQmlApplicationEngine>
// add following includes for exposing new class TouchSlider to QML
#include <QQmlEngine>
#include <QQmlContext>
#include "touchslider.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
//Create an object of type TouchSlider
//When a scoped pointer goes out of scope the object is deleted from memory. Good housekeeping:
QScopedPointer<TouchSlider> touchslider (new TouchSlider);
QQmlApplicationEngine engine;
engine.addImportPath(QStringLiteral("qml"));
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
//QML can now refer to the TouchSlider object using the handle "touchslider":
engine.rootContext()->setContextProperty("touchslider",touchslider.data());
return app.exec();
}
touchslider.h:
#ifndef TOUCHSLIDER_H
#define TOUCHSLIDER_H
#include <QObject>
#include <QDebug>
#include <QtQuickControls2>
class TouchSlider : public QObject
{
Q_OBJECT
public:
explicit TouchSlider(QObject *parent = 0);
//call Q_INVOKABLE macro to set up functions for QML
Q_INVOKABLE void testDebug(); //hello world from C++
Q_INVOKABLE QString testStringReturn(); //hello world to javascript
Q_INVOKABLE void sliderSetValueFromTouch(QQuickItem *qs,int position );//use touch event to set slider value
signals:
public slots:
};
#endif // TOUCHSLIDER_H
touchslider.cpp:
#include "touchslider.h"
TouchSlider::TouchSlider(QObject *parent) : QObject(parent)
{
}
void TouchSlider::testDebug()
{
qDebug() << "Hello from C++";
}
QString TouchSlider::testStringReturn()
{
QString message = "Hi from C++";
return message;
}
void TouchSlider::sliderSetValueFromTouch(QQuickItem *qs, int position)
{
// Assume qs a vertical slider
// Get its properties (its slider properties are accessible even though it is declared as QQuickItem)
// minimumValue and maximumValue are of type QVariant so we need to cast them as double
double minvalue = qs->property("minimumValue").value<double>();
double maxvalue = qs->property("maximumValue").value<double>();
// Since this is a vertical slider, by assumption, get the height
double height = qs->property("height").value<double>();
// Compute the new value based on position coordinate
double newvalue = (height-position)/height * (maxvalue-minvalue);
if (newvalue<minvalue) newvalue = minvalue;
if (newvalue>maxvalue) newvalue = maxvalue;
//qDebug() << newvalue;
// Set the value of the slider
qs->setProperty("value",newvalue);
}
TouchSlider.qml:
import QtQuick 2.0
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
Item {
property string sliderTitle
property real sliderMin
property real sliderMax
property real sliderVal
ColumnLayout{
Label {
text: qsTr(sliderTitle)
font.pointSize: 10
}
Slider {
id: touchSlider1
minimumValue: sliderMin
maximumValue: sliderMax
orientation: Qt.Vertical
value: sliderVal
onValueChanged: function(){
sliderVal = Math.round(this.value);
labelSliderValue.text = qsTr(JSON.stringify(sliderVal));
}
}
Label {
id: labelSliderValue
text: qsTr(JSON.stringify(sliderVal))
font.pointSize: 10
}
MultiPointTouchArea{
anchors.fill: touchSlider1
touchPoints: [
TouchPoint {
id: point1
onPressedChanged: function(){
if(pressed){
//console.log("pressed");
//console.log(touchslider.testStringReturn());
touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
}
}
}
]
onTouchUpdated: function(){
touchslider.sliderSetValueFromTouch(touchSlider1,point1.y);
}
}
}
}
PlayerControls.qml:
import QtQuick 2.0
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2
Item {
// These properties act as constants, useable outside this QML file
property string playerName
property real priceMin
property real priceMax
property real qualityMin
property real qualityMax
property real priceValue
property real qualityValue
property int sliderWidth
ColumnLayout{
id: columnLayout1
width: 640
height: 480
Layout.minimumWidth: 640
Layout.fillWidth: true
anchors.fill: parent
spacing: 10.2
Label {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
id: labelPlayer1
text: qsTr(playerName)
font.pointSize: 10
}
RowLayout{
ColumnLayout{
Label {
text: qsTr("")
font.pointSize: 10
width: 50
}
}
TouchSlider {
width: sliderWidth
sliderTitle: "Price"
sliderMin: priceMin
sliderMax: priceMax
sliderVal: priceValue
}
TouchSlider {
width: sliderWidth
sliderTitle: "Quality"
sliderMin: qualityMin
sliderMax: qualityMax
sliderVal: qualityValue
}
}
}
}
main.qml:
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("SliderPair Test")
Item {
PlayerControls{
playerName: "Player 1"
priceMin: 0
priceMax: 200
priceValue: 100
qualityMin: 0
qualityMax: 50
qualityValue: 25
sliderWidth: 200
}
}
}
The result should look like this:
On a touch screen like my Surface Pro, I can control each slider simultaneously with two fingers. Since Windows supports up to 10 simultaneous touches that should mean I can have 2-4 players without a problem. We shall see.

Closing a ApplicationWindow and open a new one?

I'm facing this problem while trying to switch from a ApplicationWindow to another.
I have this first Window called login.qml which opens the main.qml through the following signal:
onIdAutenticadoChanged: {
if(idAutenticado > 0){
console.log("login.qml: Autenticado ID: " + idAutenticado);
LoginController.abrirMain();
close();
} else if(idAutenticado == 0){
senhaInput.text = "";
console.log("Falha na autenticação: Usuário e/ou senha inválidos.");
lblMsgErro.text = "Usuário e/ou senha inválidos.";
lblMsgErro.visible = true;
loginInput.focus = true;
}
}
The slot LoginController.abrirMain() will load and show the main.qml
void QLoginController::abrirMain()
{
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("MainController", new QMainController(0,m_autenticado));
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
QQuickWindow* window = qobject_cast<QQuickWindow*>(engine.rootObjects().at(0));
window->showFullScreen();
}
The problem is: main.qml is not being shown.
//main.qml
ApplicationWindow {
id: mainWindow
visible: true
modality: "WindowModal"
visibility: "FullScreen"
color: "#09435b"
}
Question: Am I using the right approach to open this new window? If not, how should I do this?
Because you create the engine on stack :), which will be deleted automatically before abrirMain() returns and hence destroying the window too.
You should create engine on heap. Something like this:
// 'this' will become engine's parent and will automaticlaly delete engine when 'this' is deleted
QQmlApplicationEngine *engine = new QQmlApplicationEngine(this);
engine->rootContext()->setContextProperty("MainController", new QMainController(0,m_autenticado));
engine->load(QUrl(QStringLiteral("qrc:///main.qml")));
QQuickWindow* window = qobject_cast<QQuickWindow*>(engine->rootObjects().at(0));
window->showFullScreen();

QML c++ signal & slots

I've got following code:
main.cpp
QDeclarativeView *qmlView = new QDeclarativeView();
qmlView->setSource(QUrl("qrc:/nav.qml"));
ui->nav->addWidget(qmlView);
Blockschaltbild bild;
QObject *value = qmlView->rootObject();
QObject::connect(value, SIGNAL(testSig()), &bild, SLOT(BlockSlot()));
The signals and slots connect correctly. (QObject::connect returns "true")
qml file:
Rectangle {
id: rectangle1
....
signal testSig()
MouseArea{
id: mousearea
anchors.fill: parent
onEntered: parent.color = onHoverColor
onExited: parent.color = parent.buttonColor
onClicked: {
rectangle1.testSig()
console.log("Button clicked")
}
}
}
This is where the slot is located:
Blockschaltbild.h
class Blockschaltbild: public QObject
{
Q_OBJECT
public slots:
void BlockSlot(){
cout << "Slot is working" << endl;
}
public:
....
}
If I click on the mouse area, the console shows "Button clicked" but not "Slot is working".
I use Qt 4.8.4 with QtQuick 1.1. Where is my mistake?
If you simply require to work with your Blockschaltbild object in QML, you can also decide against a loose coupling with signal and slots and simply pass your object as a context parameter, to make it available in QML.
QDeclarativeView *qmlView = new QDeclarativeView();
qmlView->setSource(QUrl("qrc:/nav.qml"));
ui->nav->addWidget(qmlView);
Blockschaltbild* bild;
QObject *value = qmlView->engine().rootContext()->setContextProperty("bild", bild);
You can then call the BlockSlot() slot of your object from QML with:
Rectangle {
id: rectangle1
....
MouseArea{
id: mousearea
anchors.fill: parent
onEntered: parent.color = onHoverColor
onExited: parent.color = parent.buttonColor
onClicked: {
bild.BlockSlot() // call BlockSlot of "bild" context property
console.log("Button clicked")
}
}
}
It is also possible to use qmlRegisterType, which allows you to create instances of your Blockschaltbild class with QML. See here for more information.