how to make an updatable model in QML - qt

I'm trying to make a QML widget that is interactive and will update its model.
Directly modifying the model does not work, so I create a updateModel(int index, real x, real y) signal in InteractiveGraph.
However the change done in onUpdateModel produces no effect on the UI.
I also tried with an onUpdateModel handler such as this: { var newModel = model.slice(); newModel[index].x = x; newModel[index].y = y; model = newModel } but the result is even worse as it breaks the self-dragging of the vertex item, and the model is not updated in the UI anyway.
Is the Array class not a good model for this use-case?
Is there some readymade model that is more appropriate?
InteractiveGraph.qml:
import QtQuick 2.0
/* An interactive undirected graph, whose vertices are draggable */
Item {
id: root
property alias model: repeater.model
property real size: 20
signal updateModel(int index, real x, real y)
Canvas { // render lines connecting the vertices
anchors.fill: parent
onPaint: {
var ctx = getContext("2d")
ctx.beginPath()
for(var i = 0; i < model.length; i++) {
if(i === 0) ctx.moveTo(model[i].x, model[i].y);
else ctx.lineTo(model[i].x, model[i].y);
}
ctx.closePath()
ctx.stroke()
}
}
Repeater { // instantiate items corresponding to vertices
id: repeater
delegate: Rectangle {
id: self
color: "red"
border.color: "#000"
width: size
height: size
x: modelData.x - size/2
y: modelData.y - size/2
Text { anchors.centerIn: parent; text: index }
MouseArea { // make items self-draggable
anchors.fill: parent
drag.target: self
}
// invert the x and y relation, and send back the change to model
onXChanged: root.updateModel(index, x + size/2, y + size/2)
onYChanged: root.updateModel(index, x + size/2, y + size/2)
}
}
}
main.qml:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
InteractiveGraph {
id: interactiveGraph
anchors.fill: parent
model: [
{x: 15, y: 20},
{x: 100, y: 220},
{x: 145, y: 230},
{x: 225, y: 130},
{x: 140, y: 88},
{x: 290, y: 60},
]
onUpdateModel: { model[index].x = x; model[index].y = y; model = model; }
}
}

I should have read Models and Views in Qt Quick, to find out the appropriate model to use is ListModel.
The only caveat is that we can't use x/y property names inside the ListElement child of ListModel, as when using ListElement, the variable modelData is not used anymore, but directly the property name is exposed, and x/y would collide with delegate's properties with the same name.
(In case of name clash, we can use model.fieldName instead of fieldName.)
InteractiveGraph.qml:
Item {
id: root
property alias model: repeater.model
property real size: 20
Canvas {
id: canvas
anchors.fill: parent
onPaint: {
var ctx = getContext("2d")
ctx.fillStyle = "#eee"; ctx.fillRect(0, 0, width, height)
ctx.beginPath()
for(var i = 0; i < model.count; i++) {
var item = model.get(i)
if(i === 0) ctx.moveTo(item.xPos, item.yPos)
else ctx.lineTo(item.xPos, item.yPos)
}
ctx.stroke()
}
}
Repeater {
id: repeater
delegate: Rectangle {
id: self
color: "red"; border.color: "#000"
x: xPos - size/2; y: yPos - size/2; width: size; height: size
onXChanged: canvas.requestPaint(); onYChanged: canvas.requestPaint()
Text { anchors.centerIn: parent; text: index }
MouseArea {
anchors.fill: parent
drag.target: self
onPositionChanged: model.set(index, {xPos: self.x + size/2, yPos: self.y + size/2})
}
}
}
}
main.qml:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ListModel {
id: graphModel
ListElement {xPos: 15; yPos: 20}
ListElement {xPos: 100; yPos: 220}
ListElement {xPos: 145; yPos: 230}
ListElement {xPos: 225; yPos: 130}
ListElement {xPos: 140; yPos: 88}
ListElement {xPos: 290; yPos: 60}
}
InteractiveGraph {
id: interactiveGraph
anchors.fill: parent
model: graphModel
}
}

Related

Qt Select At Most 1 Marker on Map

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)
}
}
}
}
}

How to ensure Popup is visible inside a QML Map

I'm building a Qt 5.11 application which embeds an openstreetmap QML component.
I just wrote a minimal reproduce case. It consists in displaying objects (five blue dots here) on the map. When hovering the object, a small popup is displayed with some text.
When objects are close to the edge, the popup is not displayed correctly.
I though I would use visibleArea check this, but the property was added in Qt 5.12.
I can't find a solution for the popup to be fully visible. Is there a workaround in Qt 5.11 that I can do?
Here the QML file. Just type qmlscene sample.qml and hover blue dots to view it.
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtLocation 5.11
import QtPositioning 5.11
import QtQuick.Window 2.11
Window {
id: root; width: 800; height: 600;
Plugin { id: mapPlugin; name: "osm"; }
ListModel {
id: myModel
ListElement { latitude: 48.2351164; longitude: 6.8986936; name: "The point on the center"; }
ListElement { latitude: 48.235111272600186; longitude: 6.9007217756551995; name: "The point on the right"; }
ListElement { latitude: 48.23512783507458; longitude: 6.896574932520792; name: "The point on the left"; }
ListElement { latitude: 48.23614708436043; longitude: 6.898623901851295; name: "The point on the top"; }
ListElement { latitude: 48.23417574713512; longitude: 6.898641104398024; name: "The point on the bottom"; }
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(48.2351164, 6.8986936)
zoomLevel: 19
MapItemView {
model: myModel
delegate: MapQuickItem {
anchorPoint.x: myRect.width / 2
anchorPoint.y: myRect.height / 2
width: myRect.width
height: myRect.height
coordinate: QtPositioning.coordinate(model.latitude, model.longitude)
sourceItem: Rectangle {
id: myRect
readonly property int radius: 10
width: radius * 2
height: radius * 2
color: "transparent"
Canvas {
id: myCanvas
anchors.fill: parent
property alias textVisible: myPopup.visible
onPaint: {
var width = myRect.width;
var height = myRect.height;
var centreX = width / 2;
var centreY = height / 2;
var ctx = getContext("2d");
ctx.reset();
ctx.fillStyle = "blue";
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.moveTo(centreX, centreY);
ctx.arc(centreX, centreY, myRect.radius, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
}
MouseArea {
x: 0; y: 0;
width: myRect.radius * 2
height: myRect.radius * 2
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onEntered: { myCanvas.textVisible = true }
onExited: { myCanvas.textVisible = false }
}
}
Popup {
id: myPopup
x: myRect.width / 2 - width / 2
y: myRect.height / 2 + 20
visible: false
Label { text: model.name; horizontalAlignment: Text.AlignHCenter; }
}
}
}
}
}
}
Any help is greatly appreciated.
You can check if (popup width+popup x) goes outside screen width then change x,y to adjust popup position. You can look into following modified code for Popup component. adjust X and Y as per your marker position.
Popup {
id: myPopup
x: {
if((mapItem.x+myPopup.width) >= root.width)
return -(myRect.width/2)-(width)
else if((mapItem.x-myPopup.width) < 0)
return (myRect.width)
else
return myRect.width / 2 - width / 2
}
y: {
if((mapItem.y+myPopup.height) >= root.height)
return -(myRect.height/2)-(height)
else if((mapItem.y-myPopup.height) < 0)
return (height)
else
return myRect.height / 2 - height / 2
}
visible: false
Label { text: model.name;anchors.centerIn: parent;horizontalAlignment: Text.AlignHCenter; }
}
After looking for a while, I finally came across these two functions: mapToItem and mapFromItem.
So, I first need to map the current item point to the map item coordinate system. Then, I must check the point is inside the map viewport.
If not, I have to adjust the coordinate, and after that, I map the point back to the current item coordinate system. Popup width and height seem to decrease when approaching bottom and right borders, so I had to use contentHeight, contentWidth and padding properties to get the real popup size.
And I had to initialize the popup x and y to a value different of zero to allow mouse event to pass on the blue dot.
Here is the working code, for those who may need it.
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtLocation 5.11
import QtPositioning 5.11
import QtQuick.Window 2.11
Window {
id: root; width: 800; height: 600;
Plugin { id: mapPlugin; name: "osm"; }
ListModel {
id: myModel
ListElement { latitude: 48.2351164; longitude: 6.8986936; name: "The point on the center"; }
ListElement { latitude: 48.235111272600186; longitude: 6.9007217756551995; name: "The point on the right"; }
ListElement { latitude: 48.23512783507458; longitude: 6.896574932520792; name: "The point on the left"; }
ListElement { latitude: 48.23614708436043; longitude: 6.898623901851295; name: "The point on the top"; }
ListElement { latitude: 48.23417574713512; longitude: 6.898641104398024; name: "The point on the bottom"; }
}
Map {
id: map
anchors.fill: parent
plugin: mapPlugin
center: QtPositioning.coordinate(48.2351164, 6.8986936)
zoomLevel: 19
MapItemView {
model: myModel
delegate: MapQuickItem {
anchorPoint.x: myRect.width / 2
anchorPoint.y: myRect.height / 2
width: myRect.width
height: myRect.height
coordinate: QtPositioning.coordinate(model.latitude, model.longitude)
sourceItem: Rectangle {
id: myRect
readonly property int radius: 10
width: radius * 2
height: radius * 2
color: "transparent"
Canvas {
id: myCanvas
anchors.fill: parent
property alias textVisible: myPopup.visible
onPaint: {
var width = myRect.width;
var height = myRect.height;
var centreX = width / 2;
var centreY = height / 2;
var ctx = getContext("2d");
ctx.reset();
ctx.fillStyle = "blue";
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.moveTo(centreX, centreY);
ctx.arc(centreX, centreY, myRect.radius, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
}
MouseArea {
x: 0; y: 0;
width: myRect.radius * 2
height: myRect.radius * 2
acceptedButtons: Qt.LeftButton
hoverEnabled: true
onPositionChanged: {
myCanvas.textVisible = true;
// absolute position in map coordinate system
var absPos = mapToItem(map, mouse.x, mouse.y);
// margin between mouse pointer and the popup
var cursorMargin = 10;
// extra margin for right and bottom side
var bottomRightSideExtraMargin = 10;
// add the cursor margin to the position
var absPopupX = absPos.x + cursorMargin;
var absPopupY = absPos.y + cursorMargin;
// adjust if the popup is out of view on the bottom or right sides
if (absPos.x + myPopup.contentWidth + myPopup.leftPadding + myRect.radius * 2 + bottomRightSideExtraMargin > root.width) {
absPopupX = root.width - (myPopup.contentWidth + myPopup.leftPadding + cursorMargin + bottomRightSideExtraMargin);
}
if (absPos.y + myPopup.contentHeight + myPopup.topPadding + myRect.radius * 2 + bottomRightSideExtraMargin > root.height) {
absPopupY = root.height - (myPopup.contentHeight + myPopup.topPadding + cursorMargin + bottomRightSideExtraMargin);
}
// convert back to the current item coordinate system
var popupPos = mapFromItem(map, absPopupX, absPopupY);
myPopup.x = popupPos.x;
myPopup.y = popupPos.y;
}
onExited: {
myCanvas.textVisible = false;
}
}
}
Popup {
id: myPopup
// original offset to allow mouse hover
x: 20; y: 20;
visible: false
Label { text: model.name; horizontalAlignment: Text.AlignHCenter; }
}
}
}
}
}
}

ListModel content not set properly

I have the following code. It basically builds a dialog with a TableView in it, in which I can set values manually in the cells:
import QtQuick 2.2
import QtQuick.Window 2.1
import QtQuick.Controls 1.2
import QtQuick.Dialogs 1.2
Window {
visible: true
width: 538
height: 360
MouseArea {
anchors.fill: parent
onClicked: {
dial.open()
}
}
Dialog {
id:dial
width: 300
height: 500
title: "Set Path Parameters"
standardButtons: StandardButton.Ok | StandardButton.Cancel
signal updSig(var content)
ListModel {
id: streetsModel
property int count: 100
Component.onCompleted: {
for (var i=1 ; i<= count ; ++i)
streetsModel.append({"street_alias":"str_"+i , "start_xstart_y": "", "end_xend_y": "", "width": ""})
}
//todo: erste Spalte auch einbeziehen
function getColumnContent(role) {
var cont = ""
for (var i=0 ; i< count ; ++i) {
var cellContent;
cellContent = streetsModel.get(i).role
//streetsModel.get(styleData.row).role
if(!(cellContent === "") && !(cellContent === undefined))
cont += cellContent === "" ? "" : (cellContent + "\n")
}
cont = cont.slice(0,-1)
return cont
}
}
Item {
anchors.fill: parent
Component {
id: editableDelegate
Item {
TextInput {
id: textinput
width: parent.width
anchors.margins: 4
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: styleData.value
color: styleData.textColor
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: textinput.forceActiveFocus()
}
onEditingFinished: {
var role = styleData.role
streetsModel.set(styleData.row, {role: textinput.text})
console.log(streetsModel.get(styleData.row).role)
console.log(styleData.row, role)
}
}
}
}
}
TableView {
id: streetsTab
model: streetsModel
anchors.margins: 12
anchors.fill:parent
TableViewColumn {
id: strt_al_cl
role: "street_alias"
title: "Street Alias"
width: 120
}
TableViewColumn {
id: start_xy_cl
role: "start_xstart_y"
title: "StartX,StartY"
width: 120
}
TableViewColumn {
id: end_xy_cl
role: "end_xend_y"
title: "EndX,EndY"
width: 120
}
TableViewColumn {
id: width_cl
role: "width"
title: "Width"
width: 120
}
itemDelegate: {
return editableDelegate;
}
}
onAccepted: {
var content = [streetsModel.getColumnContent(start_xy_cl.role), streetsModel.getColumnContent(end_xy_cl.role), streetsModel.getColumnContent(width_cl.role)];
updSig(content)
this.close()
}
onRejected: this.close()
}
}
Now, if I set the values 1 in cellnumber (0,0), 2 in cellnumber (0,1) and 3 in cellnumber (0,2) (and click on an arbitrary other cell after that to insert the content), I get as content in my onAccepted-method [3,3,3]. This means the values for the first two roles start_xstart_y and end_xend_y get overwritten by the third role value for the role width. Why is this? If the code is run, on the console, the correct values in the cells are printed (see the console.log(...) parts), which stem from the model directly. So I don't understand why they are finally overwritten

How works ChartView::zoomIn(rect rectangle)

I want to zoom a selected with rectang area in ChartView:
import QtQuick 2.7
import QtCharts 2.1
ChartView{
id: chart
width: 400
height: 400
ScatterSeries{
markerSize: 10
XYPoint{x: 1; y: 1}
XYPoint{x: 2; y: 2}
XYPoint{x: 5; y: 5}
}
Rectangle{
id: rectang
color: "black"
opacity: 0.6
visible: false
}
MouseArea{
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.AllButtons
onPressed: {rectang.x = mouseX; rectang.y = mouseY; rectangle.visible = true}
onMouseXChanged: {rectang.width = mouseX - rectang.x}
onMouseYChanged: {rectang.height = mouseY - rectang.y}
onReleased: {
chart.zoomIn(rectang); // something wrong with that line, it doesn't work
rectang.visible = false
}
}
}
Can you tell how to use ChartView::zoomIn(rect rectangle) properly? I want zoom to work like in Zoom Line Example. Simple ChartView::zoomIn() just zoom a center by factor of 2.
That helpped:
onReleased: {
chart.zoomIn(Qt.rect(rectang.x, rectang.y, rectang.width, rectang.height))
rectang.visible = false
}
I mistakenly thought that rect and Rectangle are the same types.

QML how to get the position of Loader

I want generate a binary tree like flow to show organization relationship info with QML.enter image description here
My way:
Create tree node like flow example.
Line the tree nodes with Canvas like above picture. I want get the
point and draw line after Loader.onLoaded.
My question:
After the first step has been done. In the second step, I can't get the right position point of the tree node.
Details:
According to Qt document, if I don't set the width and height of Loader explicitly, it will automatically set by its item size. However, I can't get the node position point in Loader.onloaded()
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
id: appWindow
visible: true
width: 1066
height: 600
property var jsonData: [{"id":1,"pid":0},{"id":2,"pid":1},{"id":3,"pid":1},
{"id":4,"pid":2},{"id":5,"pid":2},{"id":6,"pid":3},
{"id":7,"pid":3},{"id":8,"pid":4},{"id":9,"pid":4},{"id":10,"pid":6}]
property int maxRectangles: 5
Component.onCompleted: {
var color = Qt.lighter("red", maxRectangles / 7)
pLoader.sourceComponent = rectangleComponent;
var currItem = pLoader.item;
currItem.color = "blue"
currItem.text = 0
var tmp = {};
tmp[jsonData[0]["id"]] = currItem ;
for(var index = 1; index < jsonData.length; index++) {
color = Qt.lighter("red", index / 7)
var sItem = jsonData[index]["pid"];
if(tmp[sItem].rComponent.sourceComponent === null){
tmp[sItem].rComponent.sourceComponent = rectangleComponent
attachData(tmp[sItem].rComponent.item,index, color);
tmp[jsonData[index]["id"]] = tmp[sItem].rComponent.item ;
}
else {
tmp[sItem].lComponent.sourceComponent = rectangleComponent
attachData(tmp[sItem].lComponent.item,index, color);
tmp[jsonData[index]["id"]] = tmp[sItem].lComponent.item ;
}
}
}
function attachData(item, text, color){
item.text = text;
item.color = color
}
Component {
id: rectangleComponent
Column{
property alias rComponent: rightLoader
property alias lComponent: leftLoader
property alias color: rect.color
property alias text: myText.text
spacing: 40
//anchors.horizontalCenter: parent.horizontalCenter
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
id:rect
width: 100
height: 100
Text{
id:myText
font.pixelSize: 18
anchors.centerIn: parent
color:"white"
}
Component.onCompleted: {
console.log("Component.onCompleted", mapToItem(canvas, 0, 0))
}
MouseArea {
anchors.fill: parent
onClicked: {
updateDotPosition(parent)
//here, printed coordinate is my expected
//but i want line them after tree node loaded antomate
console.log("updateDotPosition", mapToItem(canvas, 0, 0))
}
}
}
Row{
spacing: 40
anchors.horizontalCenter: parent.horizontalCenter
Loader {
id:leftLoader
onLoaded: {
console.log("leftLoader", mapToItem(canvas, 0, 0))
}
}
Loader {
id:rightLoader
onLoaded: {
console.log("rightLoader", mapToItem(canvas, 0, 0))
}
}
}
}
}
Canvas{
anchors.fill: parent
id:canvas
Loader {
id:pLoader
anchors.horizontalCenter: parent.horizontalCenter
//sourceComponent: rectangleComponent
}
}
Rectangle {
id: testMapToItemDot
width: 20
height: width
radius: width / 2
z: 1
color: "darkblue"
}
function updateDotPosition(itemToMap) {
var pos = testMapToItemDot.mapFromItem(
itemToMap,
(itemToMap.width - testMapToItemDot.width) / 2, // these parameters are optional - I want to move the dot to
(itemToMap.height - testMapToItemDot.height) / 2) // the center of the object, not upper left corner
testMapToItemDot.x += pos.x
testMapToItemDot.y += pos.y
}
}
Position and size of the loaded item can be accessed like this: Loader.item.x, Loader.item.y, Loader.item.width, Loader.item.height.
If this is not what your are looking for please describe the question more precisely.
Please have a look at this example application. I think it is what you are looking for - have a look at function updateDotPosition(itemToMap). Please note that if you will be "mapping" to an object that is not moving (dot in the example is moving) and this object will be a sibling of the Canvas with the same x, y values then the result of the function will give you exact points where from and where to draw a line. You will not need to use += as I used.
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
id: appWindow
visible: true
width: 1066
height: 600
property int maxRectangles: 5
Component {
id: rectangleComponent
Rectangle {
x: 110
y: 30
width: 100
height: 100
property var loaderSource
Component.onCompleted: {
color = Qt.lighter("red", maxRectangles / 7)
if (maxRectangles-- > 0)
loaderSource = rectangleComponent
else
loaderSource = undefined
}
Loader {
sourceComponent: loaderSource
}
MouseArea {
anchors.fill: parent
onClicked: {
updateDotPosition(parent)
}
}
}
}
Loader {
sourceComponent: rectangleComponent
}
Rectangle {
id: testMapToItemDot
width: 20
height: width
radius: width / 2
z: 1
color: "darkblue"
}
function updateDotPosition(itemToMap) {
var pos = testMapToItemDot.mapFromItem(
itemToMap,
(itemToMap.width - testMapToItemDot.width) / 2, // these parameters are optional - I want to move the dot to
(itemToMap.height - testMapToItemDot.height) / 2) // the center of the object, not upper left corner
testMapToItemDot.x += pos.x
testMapToItemDot.y += pos.y
}
}
Here is working version of your example. This is just an example. Dirty one unfortunately. This is because it was a little hard for me to understand what is going on in the code - I am sure you will not have this problem. Also I have used Timer because the Rectangle objects on creation have x,y equal to 0. I needed to wait a bit. Please do not use this in release code - it is bad design :)
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
id: appWindow
visible: true
width: 1066
height: 600
property var jsonData: [{"id":1,"pid":0},{"id":2,"pid":1},{"id":3,"pid":1},
{"id":4,"pid":2},{"id":5,"pid":2},{"id":6,"pid":3},
{"id":7,"pid":3},{"id":8,"pid":4},{"id":9,"pid":4},{"id":10,"pid":6}]
property int maxRectangles: 5
Component.onCompleted: {
var color = Qt.lighter("red", maxRectangles / 7)
pLoader.sourceComponent = rectangleComponent;
var currItem = pLoader.item;
currItem.color = "blue"
currItem.text = 0
connectedItems.push([currItem, null])
var tmp = {};
tmp[jsonData[0]["id"]] = currItem ;
for(var index = 1; index < jsonData.length; index++) {
color = Qt.lighter("red", index / 7)
var sItem = jsonData[index]["pid"];
if(tmp[sItem].rComponent.sourceComponent === null){
tmp[sItem].rComponent.sourceComponent = rectangleComponent
attachData(tmp[sItem].rComponent.item,index, color);
tmp[jsonData[index]["id"]] = tmp[sItem].rComponent.item ;
connectedItems.push([tmp[sItem].rComponent.item, null])
}
else {
tmp[sItem].lComponent.sourceComponent = rectangleComponent
attachData(tmp[sItem].lComponent.item,index, color);
tmp[jsonData[index]["id"]] = tmp[sItem].lComponent.item ;
connectedItems.push([tmp[sItem].lComponent.item, null])
}
if ( jsonData[index]["pid"] !== 0) {
connectedItems[connectedItems.length - 1][1] = connectedItems[jsonData[index]["pid"] - 1][0]
}
}
}
function attachData(item, text, color){
item.text = text;
item.color = color
}
property var connectedItems: [];
function paintConnection(objectFrom, objectTo) {
var vectorStart = canvas.mapFromItem(
objectFrom, objectFrom.width / 2, objectFrom.height)
var vectorEnd = canvas.mapFromItem(
objectTo, objectTo.width / 2,0)
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(vectorStart.x, vectorStart.y);
ctx.lineTo(vectorEnd.x,vectorEnd.y);
ctx.stroke();
canvas.requestPaint()
}
Timer {
id: paintLines
interval: 100
running: true
onTriggered: {
for (var i = 1 ; i < connectedItems.length ; i++) {
paintConnection(connectedItems[i][1].rect, connectedItems[i][0].rect)
}
}
}
Component {
id: rectangleComponent
Column{
property alias rComponent: rightLoader
property alias lComponent: leftLoader
property alias color: rect.color
property alias text: myText.text
property alias rect: rect
spacing: 40
//anchors.horizontalCenter: parent.horizontalCenter
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
id:rect
width: 100
height: 100
Text{
id:myText
font.pixelSize: 18
anchors.centerIn: parent
color:"white"
}
Component.onCompleted: {
console.log("Component.onCompleted", mapToItem(canvas, 0, 0))
}
MouseArea {
anchors.fill: parent
onClicked: {
updateDotPosition(parent)
//here, printed coordinate is my expected
//but i want line them after tree node loaded antomate
console.log("updateDotPosition", mapToItem(canvas, 0, 0))
}
}
}
Row{
spacing: 40
anchors.horizontalCenter: parent.horizontalCenter
Loader {
id:leftLoader
onLoaded: {
console.log("leftLoader", mapToItem(canvas, 0, 0))
}
}
Loader {
id:rightLoader
onLoaded: {
console.log("rightLoader", mapToItem(canvas, 0, 0))
}
}
}
}
}
Canvas{
anchors.fill: parent
id:canvas
Loader {
id:pLoader
anchors.horizontalCenter: parent.horizontalCenter
//sourceComponent: rectangleComponent
}
}
Rectangle {
id: testMapToItemDot
width: 20
height: width
radius: width / 2
z: 1
color: "darkblue"
}
function updateDotPosition(itemToMap) {
var pos = testMapToItemDot.mapFromItem(
itemToMap,
(itemToMap.width - testMapToItemDot.width) / 2, // these parameters are optional - I want to move the dot to
(itemToMap.height - testMapToItemDot.height) / 2) // the center of the object, not upper left corner
testMapToItemDot.x += pos.x
testMapToItemDot.y += pos.y
}
}

Resources