Qt Select At Most 1 Marker on Map - qt

In my code every marker that I clicked are selected(turn into green from red). I want just 1 can change. When I click another marker the marker I clicked before turns red again. Or When I click an empty area the marker I clicked before turns red again.
In qml my Item's code:
Component {
id: hazardous_img
MapQuickItem {
id: hazardousitem
anchorPoint.x: image.width/4
anchorPoint.y: image.height
coordinate: position
property bool isClicked: false
MouseArea {
anchors.fill: parent
onDoubleClicked: {
mainwindow.hazardousIconClicked(mapview.toCoordinate(Qt.point(mouse.x,mouse.y)))
}
onClicked: {
if (isClicked === false) {
image.source = "qrc:/grn-pushpin.png"
isClicked = true
} else {
image.source = "qrc:/red-pushpin.png"
isClicked = false
}
}
}
sourceItem: Image {
id: image
source: "qrc:/red-pushpin.png"
}
}
}

In QML this is usually done with using a ButtonGroup, but as you're not using AbstractButtons you need to write it yourself. Here is my solution for it.
I've used the ListModel to not only store the coordinates of each marker, but also a selected flag which is set to false by default. In the delegate I'm using the selected data role to show if a marker is selected or not.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtLocation 5.15
import QtPositioning 5.15
ApplicationWindow {
id: window
width: 640
height: 480
visible: true
title: qsTr("Map")
ListModel { id: markerModel }
Plugin {
id: mapPlugin
name: "osm"
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
zoomLevel: 14
MouseArea {
anchors.fill: parent
onDoubleClicked: {
var coordinate = map.toCoordinate(Qt.point(mouse.x, mouse.y))
var jsonObject = JSON.parse(JSON.stringify(coordinate))
jsonObject["selected"] = false
markerModel.append(jsonObject)
}
onClicked: map.deselectAll()
}
MapItemView {
model: markerModel
delegate: markerDelegate
}
function deselectAll() {
for (var i = 0; i < markerModel.count; ++i)
markerModel.setProperty(i, "selected", false)
}
Component {
id: markerDelegate
MapQuickItem {
id: markerItem
required property int index
required property real latitude
required property real longitude
required property bool selected
anchorPoint.x: waypointMarker.width / 2
anchorPoint.y: waypointMarker.height / 2
coordinate: QtPositioning.coordinate(latitude, longitude)
sourceItem: Rectangle {
id: waypointMarker
width: 20
height: 20
radius: 20
border.width: 1
border.color: mouseArea.containsMouse ? "red" : "black"
color: markerItem.selected ? "red" : "gray"
}
MouseArea {
id: mouseArea
hoverEnabled: true
anchors.fill: parent
onClicked: {
map.deselectAll()
markerModel.setProperty(markerItem.index, "selected", true)
}
}
}
}
}
}

I came up with yet another solution without looping over all items in the model. It just stores the index of the selected marker in a dedicated property. This has the drawback that if the model order changes the index can become invalid, also potential multi selection is hard to handle, but on the other hand it is faster because it doesn't need to iterate over all items.
I experimented a lot with DelegateModel, it seems to be a perfect match if one could use it in combination with MapItemView, because of the groups and the attached properties like inGroupName.
After that I've tried ItemSelectionModel, but it seems it is only intended to be used in combination with a view, e.g. TreeView. I couldn't find out how to generate a QModelIndex in QML without a TreeView.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtLocation 5.15
import QtPositioning 5.15
ApplicationWindow {
id: root
width: 640
height: 480
visible: true
title: qsTr("Map")
property int selectedMarker: -1
Map {
id: map
anchors.fill: parent
plugin: Plugin {
id: mapPlugin
name: "osm"
}
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
zoomLevel: 14
MouseArea {
anchors.fill: parent
onDoubleClicked: {
var coordinate = map.toCoordinate(Qt.point(mouse.x, mouse.y))
markerModel.append(JSON.parse(JSON.stringify(coordinate)))
}
onClicked: root.selectedMarker = -1
}
MapItemView {
model: ListModel { id: markerModel }
delegate: markerDelegate
}
Component {
id: markerDelegate
MapQuickItem {
id: markerItem
required property int index
required property real latitude
required property real longitude
anchorPoint.x: waypointMarker.width / 2
anchorPoint.y: waypointMarker.height / 2
coordinate: QtPositioning.coordinate(latitude, longitude)
sourceItem: Rectangle {
id: waypointMarker
width: 20
height: 20
radius: 20
border.width: 1
border.color: mouseArea.containsMouse ? "red" : "black"
color: markerItem.index === root.selectedMarker ? "red" : "gray"
}
MouseArea {
id: mouseArea
hoverEnabled: true
anchors.fill: parent
onClicked: root.selectedMarker = markerItem.index
}
}
}
}
}

I promise this is the last answer on that question.
This one is using an ItemSelectionModel and a few undocumented functions, e.g. ListModel.index(row, col).
itemSelectionModel.hasSelection is used in the color binding to trigger a reevaluation in order to call isRowSelected and set the color accordingly whenever the selection has changed.
If the user clicks on the background the clear() is called to clear the selection.
I think out of the three this is the best solution. It can be easily upgraded to allow multi selection as shown below. Also the ItemSelectionModel can be used by other views to show the data and selection.
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtLocation 5.15
import QtPositioning 5.15
import QtQml.Models 2.15
ApplicationWindow {
id: root
width: 640
height: 480
visible: true
title: qsTr("Map")
Map {
id: map
anchors.fill: parent
plugin: Plugin {
id: mapPlugin
name: "osm"
}
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
zoomLevel: 14
MouseArea {
anchors.fill: parent
onDoubleClicked: function(mouse) {
markerModel.append(map.toCoordinate(Qt.point(mouse.x, mouse.y)))
}
onClicked: itemSelectionModel.clear()
}
MapItemView {
model: ListModel { id: markerModel }
delegate: markerDelegate
}
ItemSelectionModel {
id: itemSelectionModel
model: markerModel
}
Component {
id: markerDelegate
MapQuickItem {
id: markerItem
required property int index
required property real latitude
required property real longitude
anchorPoint.x: waypointMarker.width / 2
anchorPoint.y: waypointMarker.height / 2
coordinate: QtPositioning.coordinate(latitude, longitude)
sourceItem: Rectangle {
id: waypointMarker
width: 20
height: 20
radius: 20
border.width: 1
border.color: mouseArea.containsMouse ? "red" : "black"
color: {
itemSelectionModel.hasSelection
return itemSelectionModel.isRowSelected(markerItem.index) ? "red" : "gray"
}
}
MouseArea {
id: mouseArea
hoverEnabled: true
anchors.fill: parent
onClicked: itemSelectionModel.select(markerModel.index(markerItem.index, 0),
ItemSelectionModel./*ClearAnd*/Select)
}
}
}
}
}

Related

QML osm Plugin draw both path & items

How can I draw both item icons & path between points from model using QML and osm plugin?
XmlListModel {
...
}
Plugin {
id: mapPlugin
objectName: "mapPlugin"
name: "osm"
...
}
Map {
id: map
objectName: "map"
anchors.fill: parent
plugin: mapPlugin
MapItemView {
id: mapItemView
model: mapModel
// draw item icons
delegate: MapQuickItem {
coordinate: QtPositioning.coordinate( model.latitude, model.longitude )
...
}
// could draw lines between points, but unable to use two delegates
/* delegate: MapPolyline {
path: pathRole
line.color: "red"
line.width: 5
} */
}
One trick you could do is create two MapItemViews. One for drawing the icons and the other for drawing line segments connecting the two, i.e.
MapItemView { // for drawing icons
model: mapModel
}
MapItemView { // for drawing line segments
model: mapModel.count - 1
}
Here's a sample of how you may do this:
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtLocation 5.15
import QtPositioning 5.15
Page {
Plugin {
id: mapPlugin
name: "osm"
}
Map {
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(43.0896, -79.0849)
zoomLevel: 12
MapItemView {
model: mapModel
delegate: MapQuickItem {
coordinate: QtPositioning.coordinate(lat, lon)
anchorPoint.x: 5
anchorPoint.y: 5
sourceItem: Rectangle {
width: 10
height: 10
color: "red"
border.color: "black"
}
}
}
MapItemView {
model: mapModel.count - 1
delegate: MapPolyline {
line.width: 3
line.color: "green"
path: [
{ latitude: mapModel.get(index).lat, longitude: mapModel.get(index).lon },
{ latitude: mapModel.get(index + 1).lat, longitude: mapModel.get(index + 1).lon }
]
}
}
}
ListModel {
id: mapModel
function appendCoordinate(lat, lon) {
append( {lat, lon} );
}
Component.onCompleted: {
appendCoordinate(43.0896, -79.0849);
appendCoordinate(43.0796, -79.0849);
appendCoordinate(43.0796, -79.0949);
}
}
}

How to declare a property of type EventHandler (slot?) in Custom Component and assign function for that in its usage

For my Custom window, shown in the answer, I've created a Component named PathButton
import QtQuick 2.0
import QtQuick.Shapes 1.12
Item {
property alias pathData: svg.path
width: 24
height: 24
Shape {
ShapePath {
fillColor: "black"
PathSvg {
id: svg
path: pathData
}
}
}
}
and used it to minimize, maximize and close the window like this:
RowLayout{
Layout.preferredWidth: 100
anchors.right: title.right
PathButton{
pathData: ViewModel.minimizeIcon
MouseArea{
anchors.fill: parent
onClicked: mainWindow.showMinimized()
}
}
PathButton{
pathData: ViewModel.maximizeIcon
MouseArea{
anchors.fill: parent
//this doesn't restore the window to its Normal state, showMaximized() is called always
onClicked: mainWindow.Maximized? mainWindow.showNormal() : mainWindow.showMaximized()
}
}
PathButton{
pathData: ViewModel.closeIcon
MouseArea{
anchors.fill: parent
onClicked: mainWindow.close()
}
}
}
in all those I've a MouseArea. What I want is to have that MouseArea in my Custom Component and declare a property of type eventhandler/signal/slot and assign that handler for onClicked like this:
import QtQuick 2.0
import QtQuick.Shapes 1.12
Item {
property alias pathData: svg.path
property alias handler: mouse.onClicked
width: 24
height: 24
Shape {
ShapePath {
fillColor: "black"
PathSvg {
id: svg
path: pathData
}
}
}
MouseArea{
id: mouse
anchors.fill: parent
onClicked: handler
}
}
and in its usage I want to assign the relevant function like this:
PathButton{
pathData: ViewModel.minimizeIcon
handler: mainWindow.showMinimized()
}
EDIT
Here's what I've in PathButton.qml:
import QtQuick 2.0
import QtQuick.Shapes 1.12
Item {
id: root
property alias pathData: svg.path
signal onClicked()
width: 24
height: 24
Shape {
ShapePath {
fillColor: "black"
PathSvg {
id: svg
path: pathData
}
}
}
MouseArea{
anchors.fill: parent
onClicked: root.onClicked()
}
}
and in main.qml I get that error and when I hit run with that error, in Application Output I get another error:
I think what you're looking for is simply a signal:
// PathButton.qml
Item {
id: root
signal clicked()
...
MouseArea {
onClicked: root.clicked()
}
}
Then you would use it like this:
PathButton {
onClicked: mainWindow.showMinimized()
}

QML append new PathCurve elements to List<PathElements> in ShapePath

To be specific: I want to connect several Points on a Map with a spline curve. New points can be added with a Mouseclick and should also be connected to the existing Path. The points are stored within a model, so I can access them also in C++.
Unfortunately I can't figure out, how I can append new PathCurve elements to the existing List in the Shape::ShapePath Object.
I expected that something like this should work:
...
MapQuickItem {
coordinate: QtPositioning.coordinate(0.0000, 0.0000)
sourceItem: Shape {
id: myShape
anchors.fill: parent
vendorExtensionsEnabled: false
ShapePath {
id: myPath
strokeColor: "black"
strokeWidth: 2
capStyle: ShapePath.RoundCap
fillColor: "transparent"
startX: 0; startY: 0
}
}
zoomLevel: 15
}
MouseArea {
anchors.fill: parent
onClicked: {
var coord = parent.toCoordinate(Qt.point(mouse.x,mouse.y))
myPath.pathElements.push( new PathCurve(mouse.x, mouse.y) ) //does not work
}
}
I also tried to fill the PathElements from C++, but the PathCurve class seems to be private and is only usable from within QML. Hardcoding PathCurve Elements works just fine like in every online example, but I want to dynamically modify the list of pathelements.
Any help would be appreciated very much!
You must dynamically components using the function createQmlObject, but for this you must bear in mind that it depends on the zoomLevel that you apply to the MapQuickItem the relation of sizes since that depends on the painting, the PathCurve does not use coordinates of the window but the coordinates of the Shape, and the Shape is painted according to how it is configured. So for in this case the zoomLevel of the MapQuickItem must be 0 or the same as the map. Considering the above, the solution is:
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Shapes 1.12
import QtPositioning 5.9
import QtLocation 5.3
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Plugin {
id: mapPlugin
name: "osm" // "mapboxgl" "osm" "esri"
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
zoomLevel: 14
center: QtPositioning.coordinate(59.91, 10.75) // Oslo
MapQuickItem {
id: map_item
coordinate: QtPositioning.coordinate(59.91, 10.75)
anchorPoint.x : 0
anchorPoint.y : 0
zoomLevel: map.zoomLevel
sourceItem: Shape {
id: myShape
anchors.fill: parent
vendorExtensionsEnabled: false
ShapePath {
id: myPath
strokeColor: "black"
strokeWidth: 2
capStyle: ShapePath.RoundCap
fillColor: "transparent"
startX: 0; startY: 0
}
}
}
}
MouseArea {
id: mousearea
anchors.fill: map
onClicked: {
var item_pos = map.fromCoordinate(map_item.coordinate, false)
var pathcurve = Qt.createQmlObject('import QtQuick 2.12; PathCurve {}',
myPath);
pathcurve.x = mouse.x - item_pos.x
pathcurve.y = mouse.y - item_pos.y
myPath.pathElements.push(pathcurve)
}
}
}

How can I assign an ordering number to dynamically created component in qml?

My code (actually an official example) can draw markers and polylines on the point which I clicked. And I want that every marker has their own Text which represents its order. Text "1" for the first marker, and Text "2" for the second marker, for example. But markerCount(declared in componentCreation.js) for the Text does not increase, so all of the Text of the marker is "1" which is a default.
In the code, Rectangle which is MapQuickItem's child represents a marker, and it is dynamically created by createElements() (componentCreation.js). markerCount++ is implemented in Component.onCompleted.
The code is:
componentCreation.js
var arrayLines = []
var lineComplete = false
var markerCount = 1
function createElements(point) {
console.log(markerCount)
var componentMarker = Qt.createComponent("Marker.qml");
if (componentMarker.status === Component.Ready) {
var markerFirstCorner = componentMarker.createObject(map);
markerFirstCorner.coordinate = map.toCoordinate(point)
map.addMapItem(markerFirstCorner)
} else {
console.log("Marker not created")
}
var theLine
if (arrayLines.length === 0) {
createLine(point)
} else {
theLine = arrayLines[arrayLines.length-1]
theLine.mainPolyline.addCoordinate(map.toCoordinate(point))
}
}
function createLine(point){
var componentLine = Qt.createComponent("Line.qml")
if (componentLine.status === Component.Ready) {
var lineFirstCorner = componentLine.createObject(map);
lineFirstCorner.mainPolyline.addCoordinate(map.toCoordinate(point))
map.addMapItem(lineFirstCorner)
arrayLines.push(lineFirstCorner)
} else {
console.log("Line not created")
}
}
main.qml
import QtQuick 2.11
import QtQuick.Window 2.11
import QtLocation 5.11
import QtPositioning 5.8
import QtQuick.Controls 2.1
import "componentCreation.js" as MyScript
ApplicationWindow {
id: applicationWindow
visible: true
width: 640
height: 480
Plugin {
id: mapPlugin
name: "googlemaps"
}
Map {
id: map
anchors.fill: parent
zoomLevel: 12
plugin: mapPlugin
center: QtPositioning.coordinate(35.8926195, 128.6000172)
MouseArea{
id: mouseArea
anchors.fill: parent
z: 1
onClicked: {
console.log("Before creation : " + MyScript.markerCount)
var point = Qt.point(mouse.x, mouse.y)
console.log()
console.log("You clicked : " + map.toCoordinate(point))
MyScript.createElements(Qt.point(mouse.x,mouse.y))
}
}
}
}
Marker.qml
import QtQuick 2.0
import QtLocation 5.11
import "componentCreation.js" as MyScript
MapQuickItem {
property alias marker: marker
id: marker
sourceItem: Rectangle {
width: 50
height: 50
color: "transparent"
Image {
anchors.fill: parent
source: "images/drone.svg" // Ignore warnings from this
sourceSize: Qt.size(parent.width, parent.height)
}
Text {
anchors.fill: parent
text: { MyScript.markerCount }
}
Component.onCompleted: {
MyScript.markerCount++
console.log("markerCount: " + MyScript.markerCount)
}
}
opacity: 1.0
anchorPoint: Qt.point(sourceItem.width/2, sourceItem.height/2)
}
Line.qml
import QtQuick 2.0
import QtLocation 5.8
MapPolyline {
property alias mainPolyline: mainPolyline
id: mainPolyline
line.width: 3
line.color: 'black'
}
I'm new to Qt and Qml. I don't know why markerCount does not increase. Please tell me why or give me another way to order the markers.
Thank you for your help.
You are complicating yourself too much, in case you want to store a lot of information the correct thing is to use a model, in this case ListModel, and a view, in this case MapItemView, that has as a delegate the Marker, then use a property to save the index that it is obtained by using the count property of the model:
Marker.qml
import QtQuick 2.0
import QtLocation 5.11
MapQuickItem {
id: marker
property alias text: txt.text
sourceItem: Rectangle {
width: 50
height: 50
color: "transparent"
Image {
anchors.fill: parent
source: "images/drone.svg" // Ignore warnings from this
sourceSize: Qt.size(parent.width, parent.height)
}
Text {
id: txt
anchors.fill: parent
}
}
opacity: 1.0
anchorPoint: Qt.point(sourceItem.width/2, sourceItem.height/2)
}
main.qml
import QtQuick 2.11
import QtQuick.Window 2.11
import QtLocation 5.11
import QtPositioning 5.8
import QtQuick.Controls 2.1
ApplicationWindow {
id: applicationWindow
visible: true
width: 640
height: 480
Plugin {
id: mapPlugin
name: "googlemaps"
}
ListModel{
id: md
}
Map {
id: map
anchors.fill: parent
zoomLevel: 12
plugin: mapPlugin
center: QtPositioning.coordinate(35.8926195, 128.6000172)
MapItemView{
model: md
delegate: Marker{
text: title
coordinate: QtPositioning.coordinate(coords.latitude, coords.longitude)
}
}
Line{
id: li
}
MouseArea{
id: mouseArea
anchors.fill: parent
z: 1
onClicked: {
var point = Qt.point(mouse.x, mouse.y)
var coord = map.toCoordinate(point);
var text = md.count + 1;
md.append({"coords": coord, "title": text})
li.addCoordinate(coord)
}
}
}
}
Line.qml
import QtQuick 2.0
import QtLocation 5.8
MapPolyline {
id: mainPolyline
line.width: 3
line.color: 'black'
}

Qt - change property from a component in a different qml file

Update 1
The idea is to be able to change the front and back of CardForm from main.qml because i want to be able to use multiple CardForm instances. I tried to do what they did here but it doesnt work.
Here is the code:
CardForm.qml
import QtQuick 2.0
Flipable {
id: sCard
width: 75
height: 200
property bool flipped: false
property string front: "Front"
property string back: "Back"
property alias callFront : front
property alias callBack : back
front: Rectangle{
id: front
anchors.fill: sCard
border.width: 2
border.color: "black"
radius: 5
Text{
anchors.centerIn: parent
text: sCard.front
}
}
back: Column{
Rectangle{
id: back
anchors.fill: sCard
radius: 5
border.width: 2
border.color: "black"
Text{
anchors.centerIn: parent
text: sCard.front
}
Text{
anchors.centerIn: parent
text: sCard.front
}
}
}
transform: Rotation{
id: flip
origin.x: sCard.width
origin.y: sCard.height/2
axis.x: 0; axis.y: 1; axis.z: 0 // set axis.y to 1 to rotate around y-axis
angle: 0 // the default angle
}
states: State {
name: "back"
PropertyChanges {
target: flip
angle: 180
}
when: sCard.flipped
}
transitions: Transition{
NumberAnimation {
target: flip
property: "angle"
duration: 200
}
}
MouseArea{
anchors.fill: parent
onClicked: sCard.flipped = !sCard.flipped
}
}
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("Neuro Seed")
SwipeView {
id: swipeView
anchors.fill: parent
currentIndex: tabBar.currentIndex
Column {
CardForm{
id: test
anchors.centerIn: parent
test.callFront: "Hello World!"
test.callBack: "Bonjour le Monde!
}
}
}
}
Here are the error messages:
SHGetSpecialFolderPath() failed for standard location "Shared Configuration", clsid=0x1c. ()
qrc:/main.qml:17:13: QML CardForm: back is a write-once property
qrc:/main.qml:17:13: QML CardForm: front is a write-once property
qrc:/main.qml:16:9: QML Column: Cannot specify top, bottom, verticalCenter, fill or centerIn anchors for items inside Column. Column will not function.
the c1.getFront() and getBack() were from a C++ class that I made. I changed these to "Hello World!" and "Bonjour le Monde!"
So after many hours of struggling I figured out that to create a property which is accessible by other .qml files you must create a property alias name: id.property. The id must point towards an existing instance of a object in your code and the property of this instance that you wish to be able to change from the outside. So in my case it would be like so:
CardForm.qml
Flipable {
id: sCard
width: 75
height: 200
property bool flipped: false
property alias frontText : front.text
front: Rectangle{
id: front
anchors.fill: sCard
border.width: 2
border.color: "black"
radius: 5
Text{
anchors.centerIn: parent
text: frontText
}
}
}
and in the 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("Neuro Seed")
Rectangle {
anchors.fill: parent
CardForm{
id: test
anchors.centerIn: parent
frontText: "Hello World!"
}
}
}
}

Resources