Recursevly go trough delegated maps in FolderListModel (QML) - qt

I'm creating a music player for Ubuntu Touch in QML and I have some things I would appreciate some help with since I'm new to QML.
The app are able to play tracks listed in the selected directory, but the directory also shows folders, and I want all the files from all the subdirs to be listed, instead of only the tracks in the root dir.
But I don't really know how to do this. FolderListModel har a isFolder method that perhaps could be used, but I don't know how. Or use some kind of function to go trough the selected dirctory.
I came by this thread qml FolderListModel but it didn't give me anything.
This is the code that currently lists my tracks:
Column {
anchors.centerIn: parent
anchors.fill: parent
ListView {
id: musicFolder
FolderListModel {
id: folderModel
folder: musicDir
nameFilters: ["*.ogg","*.mp3","*.oga","*.wav"]
}
width: parent.width
height: parent.height
model: folderModel
delegate: ListItem.Subtitled {
text: fileName
subText: "Artist: "
onClicked: {
console.debug('Debug: User pressed '+musicDir+fileName)
playMusic.source = musicDir+fileName
playMusic.play()
}
}
}
}

You'll need a proxy model which flattens the nested items recursively. One example of such a proxy model is the KDescendantsProxyModel (I'm also using it).
Alternatively, you could build a list of files matching a particular pattern via recursively walking the filesystem (or letting some existing class do this for you) and feeding the result to some other, simpler model. That would have a disadvantage of not being able to display updates easily.

Related

Sharing data across QML files (properties)

I am writing an App and I already managed to resolve some issues, with the latest one being the Screen.Width/Height for adjusting the window size dynamically on different monitors (I use laptop, phone, PC, it's simply convenient).
To write the code efficiently and nicely, I want to obtain that specific information and put it into a single set of 2 "variables", that would then hold this information.
I tried assigning the ApplicationWindow object an id: mainWindow, in order to call upon it from a different QML file, to obtain the property value as:
mainWindow.height, mainWindow.width
I then was told to use another approach, custom QML properties, that are declared like:
property (type) (name): (value)
I then followed the advice and declared those properties in Main.qml (with AppWindow) and it does work. The properties of AppWindow (ApplicationWindow) contain the width and height of the Screen multiplied by a specific coefficient.
Then those variables are accessed by the object itself, drawing the App Window as I want it to be.
The problem is that this approach was meant to solve the issue of sharing code across .QML files, and it doesn't
[go down]
ApplicationWindow {
id: mainWindow
//Wide screen support
//Screen.desktopAvailableWidth / 4
//Screen.desktopAvailableHeight / 6
//The below is monitor cross-compatible (phone, PC, laptop)
property int globalWidth: Screen.width / 2
property int globalHeight: Screen.height / 3
width: globalWidth
height: globalHeight
visible: true
title: qsTr("Redacted")
//setWindowIcon(QIcon(":/path/to/icon.png"));
The piece of code below resides in main.qml. It is called upon from that file. Just ignoring the IDs (IDs supposedly [as stated by users/docs] can't be accessed outside of local scope...), the properties (especially custom properties) should be accessible inside Page1, Page2, Page3 etc.
SwipeView {
id: swipeView
anchors.fill: parent
currentIndex: tabBar.currentIndex
Page1Form {
}
Page2Form {
}
Page3Form {
}
}
The below is the Page2Form.qml file that is called upon (as class definition) in main.qml.
The properties declared in parent objects in main.qml (imo) should be inherited by child objects (imo).
Page {
id: localPage2
width: globalWidth
height: globalHeight
Rectangle {
id: rectangle
x: (localPage2.width / 2) - (width / 2)
y: (localPage2.height / 4) - (height / 2)
width: localPage2.width / 3
height: localPage2.height / 6
color: "#ffffff"
border.color: "#a45c5c"
border.width: 2
TextInput {
id: textInput
x: 0
y: 0
width: localPage2.width / 3
height: localPage2.height / 6
text: qsTr("Redacted")
font.pixelSize: 12
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.weight: Font.Normal
focus: true
}
}
Ok, so what is the problem?
I can try calling those properties as:
mainWindow.globalHeight
globalHeight
mainWindow.height
etc.
They won't be accessed. The form editor will provide me preview of Page object that has 0 size.
The page does render eventually (when compiled and ran), but there is an issue in passing (accessing) the value of that property.
As you can notice, both IDs and custom properties seem to work just fine locally.
Update:
I haven't fixed that issue, I also tried using aliases (references) and the "foreign" QML file will still fail to be assigned proper size (Page width and height).
I then put another custom property of string type with some text in it, I then managed to access that property in Page2.qml and the property is originally in main.qml.
It's bugged, or I have no idea what it is.
I tried 3 approaches:
ID (it's not global as it turns out)
Custom property (kind of works, just not with Screen size...)
Aliases on object's default properties
(property alias globalWidth: mainWindow.width
property alias globalHeight: mainWindow.height)

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.

QtQuick - FolderListModel not showing all the files

I am facing troubles using FolderListModel. I am trying to filter only XML files of a specific directory and display them in a ListView. The problem is, it only displays one file whereas I have several XML files in this directory.
I tried with other types of files (txt, pdf) and it never displays the correct amount of files in the ListView.
Here is my code, what am I doing wrong?
ListView {
id: listView1
x: 0
width: 288
height: 256
anchors.top: parent.top
anchors.topMargin: 16
anchors.horizontalCenter: parent.horizontalCenter
delegate: listviewdelegate
model: listviewmodel
clip: true;
}
FolderListModel{
id:listviewmodel
nameFilters: ["*.xml"]
showDirs: false
showDotAndDotDot: false
folder:"C:/Users/bg/Documents"//serializationpath
}
Component{
id:listviewdelegate
Text {
text: fileName
color: m_colorDefault
font.pixelSize: m_iFontSizeMin
anchors.verticalCenter: parent.verticalCenter
}
}
Can't we use a FolderListModel inside a ListView?
Thanks for your help,
Regards
Edit:
As I am trying to solve my issue, I have noticed that the Qt documentation
isn't correct for the folder property. It says it is an invalid URL by default, but if I don't set the folder, it uses the application's folder.
I tried to set the folder property with absolute path:
FolderListModel {
id: listviewmodel
folder: "F:/QtDev/Sources.ScenarioEditor"
}
But it keeps using the application's folder, without yelling about a wrong path. So I am a bit confused here...
Edit 2:
I finally succeeded in targeting the right folder, but now I am facing a stupide behavior of the nameFilters property...
Here is a snippet:
FolderListModel {
id: listviewmodel
showDirs: false
//works fine and filters XML
// folder:"file:/F:/QtDev/Sources.ScenarioEditor"
// nameFilters: ["*.xml"]
//works fine but doesn't filter XML
folder:"file:/"+scenario.serializationPath
nameFilters: ["*.xml"]
}
The scenario.serializationPath targets my user folder, which is the one I truly need to use. But in that case, the file filtering doesn't not work :/
Any help will be much appreciated, as I am stuck on this problem for a while.
Regards
I stumbled across a similar issue while trying to use a FolderListModel with a dynamic folder name and nameFilter. I was trying to update the folder and/or filter, then refresh a view that is attached to the model using Component.onCompleted. Populating the model seems to be an asynchronous operation and the data isn't ready by the Component.onCompleted call, so as a workaround I triggered the view update on the FolderListModel count value:
property int totalFileCount: folderListModel.count
onTotalFileCountChanged: {
console.log("total files: " + totalFileCount);
// **** Refresh your view here ****
}
FolderListModel {
id: folderListModel
folder: "" // This gets updated by another function
nameFilters: [ "*.png" ]
}

Unable to set the parent of a dynamically created QML component

I am creating dynamic component in QML as follows:
var component = Qt.createComponent("PlayerWindow.qml")
if (component.status != component.errorString())
console.log(component.errorString())
var playerWin = component.createObject(rootWindow);
Here rootWindow is my main application window. Now, the PlayerWindow is quite simple as:
Window {
id: playerWindow
width: parent.width
height: parent.height
Component.onCompleted: {
console.log(parent.width)
console.log(rootWindow.height)
}
}
The thing is that the values for parent.width and rootWindow.width are really different and this is also evident when the window is displayed. However, rootWindow is set as the parent in the createObject call. So, I am not sure what is happening there but I wanted to know if this is the correct way to set the component parent when they are being dynamically created.
Try to add console.log(parent) in the code. You will see something like qml: QQuickRootItem(0x1e3e4e0). If you check the Qt doc you will find that Item.parent() returns Item but Windows is not Itemdescendant but QQuickWindow. Also from documentation:
A QQuickWindow always has a single invisible root item ...
So, in your case parent and rootWindow are different objects.
P.S. The dynamic object creation in your code can produce an error since component.createObject will be executed although Qt.createComponent returns error. Just copy the code from Qt documentation.

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.

Resources