Qt Location Editing a MapPolyLine - qt

My objective is to allow a user to edit a MapPolyLine using the mouse to drag and drop points on the line. I have implemented this by creating a C++ model that exposes a path() method for dumping all points to draw the line.
I then used this model to display a set of MapCircles for each vertex on the line. These Mapcircles have drag events which update the MapPolyLine model after drag has finished.
This works a treat for lines with < 500 vertices. Then performance suffers really badly. Some of my lines (from GPX files) have 10,000 vertices.
So I tried to only expose MapCircles on the line close to the mouse hover point. But (unless I am mistaken) the MouseArea for a MapPolyLine appears to be the bounding box of the line and hover can be triggered in strange situations.
I found that the onEntered event worked well enough for me to work out the position on the line and then display a set of MapCircles close to that position. It is a sub-optimal solution though as the user has to actively click the line to get these MapCircles to show.
My question is: "am I using the right strategy to allow a MapPolyLine to be edited?". I cannot see any other way of doing it in the current implementation of QtLocation.
The code is now quite complex. It's structure is:-
GPXModel {
id: gpxModel
}
MapPolyLine {
path: gpxModel.path
MouseArea {
onEntered: {
var mapCoord = gpxLine.mapToItem(mapView,mouseX,mouseY);
var coord = mapView.toCoordinate(Qt.point(mapCoord.x,mapCoord.y));
//tell the model where we are in the array of points so we can display MapCircles around this point
gpxModel.setEditLocationFromCoordinate(coord);
}
}
}
MapItemView {
model: gpxModel
MapCircle {
//this is a custom role I use to return a sub-set of points set by setEditLocationFromCoordinate
center: positionRole
MouseArea{
anchors.fill: parent
drag.target: parent
onDragActiveChanged: {
if(!drag.active){
gpxModel.updatePositionOfMarker();
}
}
}
}
}
The project is open sourced at https://github.com/citizenfish/servalan_2020 with the caveat that I am using this as a learning exercise so it's all a bit hacked together.
EDIT: I do not want to use webview based solutions. The point of the project was to do this in native Qt with minimal reliance on third party javascript libraries/browsers.

Related

What are these glitches in my listview when scrolling?

I'm writing a file conversion software in QT for Python where you will be able to drag files into the software, click a convert button, and drag out the output files. When a user drags files into the software, all the names of the files are added into a ListView.
When a user drags files into the ListView, it calls the backend.addToPaths(url) function with each of the file paths that were dragged into it. This function then appends all of the file paths into an internal list of file paths, then updates the listview, calling the updateList (paths) function in the main.qml file with all of the file names, which clears the listview and then appends back all of the file names, the old ones, and the new ones dragged in.
All of the file names are added to the list view correctly, but then when I scroll around inside of the list view, it produces these very strange rendering bugs, as shown in this video, or in these images:
Image 1:
Image 2:
Image 3:
At first I thought it could be due to clipping, so I removed that, and nothing changed. I then tried increasing the listview's display margin, and then its cacheBuffer, but it still didn't help. I also tried setting pixelAligned to true, but that still didn't work. I'm guessing it's probably because QT isn't very good at handling listmodels being updated or changed, but I don't really know.
Here's a simplified version of my main.qml file if needed:
ListView {
id: inputFileView
// #disable-check M16
objectName: "inputFileView"
clip: true
boundsBehavior: Flickable.StopAtBounds
flickableDirection: Flickable.VerticalFlick
displayMarginBeginning: 100
displayMarginEnd: 100
function updateList(paths) {
console.log("updating list");
inputFileModel.clear();
paths.forEach( function (item) {
inputFileModel.append({
'name': item
});
});
}
model: ListModel {
id: inputFileModel
}
delegate: Item {
Row {
id: row1
Text {
text: name
}
}
}
DropArea {
anchors.fill: parent
onDropped: {
drop.urls.forEach( function (url) {
backend.addToPaths(url)
});
}
}
}
I guess it was a bug with PySide2? I noticed someone replaced the Pyside tag with the PySide2 tag, and I didn't even show the python code that would've clarified that I was using that version, so I wondered if it meant that the issue was only in PySide2, so I switched from that to PySide6, and it worked. I have no idea what was causing it, but I guess it's fine now.

Qt/QML: Trying to create a log file view with TextEdit, result is really slow

I am working on an Android App. I need to display a console like type of log, which will be written to by the C++ back end. I have tried doing this combining a TextEdit and ScrollView, but the result is really slow. As soon as my log goes beyond ~50 lines, adding a few lines slows down (locks) the interface for a few seconds.
Trimming down the source code, this is the log view section:
property int logMaxLines: 50
ScrollView {
id: logScrollView
anchors.fill: parent
clip: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
TextEdit {
id: logTextEdit
anchors.fill: parent
readOnly: true
color: "darkgreen"
property int linesTrimmed: 0
}
}
Connections{
target: gate
onNewMessageLineAdded :
{
logTextEdit.append(gate.newMessageLine)
if (logTextEdit.lineCount > logMaxLines) {
while (logTextEdit.lineCount >= logMaxLines) {
logTextEdit.text = logTextEdit.text.slice(logTextEdit.text.indexOf('\n')+2)
logTextEdit.linesTrimmed++
}
logTextEdit.insert(0, "[... trimmed " + logTextEdit.linesTrimmed + " lines ...]\n")
}
}
}
I picked a ScrollView as I'd like to have the vertical scroll bar. Lines are added one at a time by the C++ code, when it emits the newMessageLineAdded signal. This is coming from a class which includes this Q_PROPERTY, used to pass the new line content:
Q_PROPERTY(QString newMessageLine READ newMessageLine NOTIFY newMessageLineAdded)
the signal is declared as:
void newMessageLineAdded();
I have added the small bit of java to trim the log when it grows too long, as the issue is there even when this trimming code is not present.
Am I doing something very clunky here? Should I use another type of object to replace the TextEdit, knowing that it is not used at all to edit text, but only as a display?
Thanks.
I recommend you to use ListView instead of TextEdit. And use QStringListModel as model declared in C++ code and added to QML as context property. Read Embedding C++ Objects into QML with Context Properties. It is recommended for better perfomance to have most of logic in C++ code.

Catching mouse events from QML

I want to create a QML item which disappears when the mouse moves outside of it. Here is my code:
Item {
id: disappearing_element
ListView { ... }
MouseArea {
id: collapser
anchors.fill: parent
propagateComposedEvents: true
hoverEnabled: true
onExited: {
disappearing_element.visible = false
}
}
}
It works well, but MouseArea propagates events like onClicked() onDoubleClicked() only (as said in Qt docs).
Is there a way to notify disappearing_element's childrens about mouse enter and mouse exit events (without using a Popup element)?
I think this is one of the common needs when developing QtQuick apps. One solution we currently use quite often is to add MouseArea in each of the children that need check mouse containment, and emit signals (and catch these signals in your main item) when the mouse enters or exits.
Things go a bit complicated when the children items also need such mechanism to manage their children. However, for common usage, this approach is enough for us right now.

Qt 5 QML app with lots of Windows or complex UIs

In QtQuick 2 using the QtQuick Controls you can create complex desktop apps. However it seems to me that the entire UI must be declared and create all at once at the start of the app. Any parts that you don't want to use yet (for example the File->Open dialog) must still be created but they are hidden, like this:
ApplicationWindow {
FileDialog {
id: fileOpenDialog
visible: false
// ...
}
FileDialog {
id: fileSaveDialog
visible: false
// ...
}
// And so on for every window in your app and every piece of UI.
Now, this may be fine for simple apps, but for complex ones or apps with many dialogs surely this is a crazy thing to do? In the traditional QtWidgets model you would dynamically create your dialog when needed.
I know there are some workarounds for this, e.g. you can use a Loader or even create QML objects dynamically directly in javascript, but they are very ugly and you lose all the benefits of the nice QML syntax. Also you can't really "unload" the components. Well Loader claims you can but I tried it and my app crashed.
Is there an elegant solution to this problem? Or do I simply have to bite the bullet and create all the potential UI for my app at once and then hide most of it?
Note: this page has information about using Loaders to get around this, but as you can see it is not a very nice solution.
Edit 1 - Why is Loader suboptimal?
Ok, to show you why Loader is not really that pleasant, consider this example which starts some complex task and waits for a result. Suppose that - unlike all the trivial examples people usually give - the task has many inputs and several outputs.
This is the Loader solution:
Window {
Loader {
id: task
source: "ComplexTask.qml"
active: false
}
TextField {
id: input1
}
TextField {
id: output1
}
Button {
text: "Begin complex task"
onClicked: {
// Show the task.
if (task.active === false)
{
task.active = true;
// Connect completed signal if it hasn't been already.
task.item.taskCompleted.connect(onTaskCompleted)
}
view.item.input1 = input1.text;
// And several more lines of that...
}
}
}
function onTaskCompleted()
{
output1.text = view.item.output1
// And several more lines...
// This actually causes a crash in my code:
// view.active = false;
}
}
If I was doing it without Loader, I could have something like this:
Window {
ComplexTask {
id: task
taskInput1: input1.text
componentLoaded: false
onCompleted: componentLoaded = false
}
TextField {
id: input1
}
TextField {
id: output1
text: task.taskOutput1
}
Button {
text: "Begin complex task"
onClicked: task.componentLoaded = true
}
}
That is obviously way simpler. What I clearly want is some way for the ComplexTask to be loaded and have all its declarative relationships activated when componentLoaded is set to true, and then have the relationships disconnected and unload the component when componentLoaded is set to false. I'm pretty sure there is no way to make something like this in Qt currently.
Creating QML components from JS dynamically is just as ugly as creating widgets from C++ dynamically (if not less so, as it is actually more flexible). There is nothing ugly about it, you can implement your QML components in separate files, use every assistance Creator provides in their creation, and instantiate those components wherever you need them as much as you need them. It is far uglier to have everything hidden from the get go, it is also a lot heavier and it could not possibly anticipate everything that might happen as well dynamic component instantiation can.
Here is a minimalistic self-contained example, it doesn't even use a loader, since the dialog is locally available QML file.
Dialog.qml
Rectangle {
id: dialog
anchors.fill: parent
color: "lightblue"
property var target : null
Column {
TextField {
id: name
text: "new name"
}
Button {
text: "OK"
onClicked: {
if (target) target.text = name.text
dialog.destroy()
}
}
Button {
text: "Cancel"
onClicked: dialog.destroy()
}
}
}
main.qml
ApplicationWindow {
visible: true
width: 200
height: 200
Button {
id: button
text: "rename me"
width: 200
onClicked: {
var component = Qt.createComponent("Dialog.qml")
var obj = component.createObject(overlay)
obj.target = button
}
}
Item {
id: overlay
anchors.fill: parent
}
}
Also, the above example is very barebone and just for the sake of illustration, consider using a stack view, either your own implementation or the available since 5.1 stock StackView.
Here's a slight alternative to ddriver's answer that doesn't call Qt.createComponent() every time you create an instance of that component (which will be quite slow):
// Message dialog box component.
Component {
id: messageBoxFactory
MessageDialog {
}
}
// Create and show a new message box.
function showMessage(text, title, modal)
{
if (typeof modal === 'undefined')
modal = true;
// mainWindow is the parent. We can also specify initial property values.
var messageDialog = messageBoxFactory.createObject(mainWindow, {
text: text,
title: title,
visible: true,
modality: modal ? Qt.ApplicationModal : Qt.NonModal
} );
messageDialog.accepted.connect(messageDialog.destroy);
messageDialog.rejected.connect(messageDialog.destroy);
}
I think loading and unloading elements is not actual any more because every user have more than 2GB RAM.
And do you think your app can take more than even 512 MB ram? I doubt it.
You should load qml elements and don't unload them, no crashes will happens, just store all pointers and manipulate qml frames.
If you just keep all your QML elements in RAM and store their states, it will works faster and looks better.
Example is my project that developed in that way: https://youtube.com/watch?v=UTMOd2s9Vkk
I have made base frame that inherited by all windows. This frame does have methods hide/show and resetState. Base window does contains all child frames, so via signal/slots other frames show/hide next required frame.

asynchronous (kind of) animation in qml

let's say i have the following QML Components:
Foo.qml
import Qt 4.7
Rectangle {
Repeater {
model: myModel
delegate: Bar {
barProp: elemProp
}
}
}
Bar.qml
import Qt 4.7
Rectangle {
property string barProp: ""
Text {
text: barProp
NumberAnimation on x {
from: 0; to: 100
duration: 1000
loops: Animation.Infinite
}
}
}
I maintain myModel from C++, it has the following Q_PROPERTY declaration:
Q_PROPERTY (QDeclarativeListProperty <Bar> myModel READ myModel
NOTIFY myModelChanged)
Now, my problem is that every time I add a new element to the underlying QList, the animation specified in Bar resets, so in practice, the elements always completely overlap. What I want is that the element animations are not synchronous, and each can continue seamlessly regardless of the rest. Is this possible to do?
Cheers
You should use a QAbstractItemModel (QStandardItemModel may be easiest) rather than a QList. QAbstractItemModel notifies the view when new items are inserted/removed/moved and the view reacts appropriately by modifying its content. In contrast, the view knows nothing about the changes made to a QList; only that something has changed. This means that the list has no choice but to destroy and recreate all delegates.

Resources