File selector in QML and PySide
Today I wrote a file selector in QML. This was not trivial because QML has no standard element for drilling down in a tree model. So I wrote one. A bit of Python was needed to expose the file system to the QML as a data model.
I've played with Bup a bit lately and wanted to write a GUI for it. Normal Qt widgets would do, but when the bup developers asked if it would run on MeeGo, I had a look at QML.

Update: check the comments for a new version.
The Python part of the code is simple and short:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PySide import QtCore, QtGui, QtDeclarative
app = QtGui.QApplication(sys.argv)
model = QtGui.QDirModel()
view = QtDeclarative.QDeclarativeView()
view.rootContext().setContextProperty("dirModel", model)
view.setSource(QtCore.QUrl.fromLocalFile("list.qml"))
view.show()
sys.exit(app.exec_())
The QML is rather long. I post it here so other QML developers can easily find it and experiment with it. The selector can be navigated with arrow keys and mouse. The lists can be flicked. Save this QML as 'list.qml' so that the Python code can find it.
import QtQuick 1.0
Rectangle {
id: page
width: 400; height: 240;
anchors.fill: parent
VisualDataModel {
id: listModel
model: dirModel
Item {
id: itemDelegate
width: listView.width; height: 25
Rectangle {
id: content
anchors.fill: parent
color: "transparent"
Text { text: fileName }
}
states: State {
name: "active"; when: itemDelegate.activeFocus
PropertyChanges { target: content; color: "#FFDDDD" }
}
MouseArea {
anchors.fill: parent
onClicked: {
listView.currentIndex = index
itemDelegate.forceActiveFocus()
if (model.hasModelChildren) {
animModel.rootIndex = listModel.modelIndex(index)
animation.running = true
}
}
}
Keys.onRightPressed: {
if (model.hasModelChildren) {
animModel.rootIndex = listModel.modelIndex(index)
animation.running = true
}
}
Keys.onLeftPressed: {
// if statement does not work as intended
if (listModel.parentModelIndex() != listModel.rootIndex) {
listView.x = -listView.width
listModel.rootIndex = listModel.parentModelIndex()
leftAnimation.running = true
}
}
Keys.onUpPressed: {
if (index > 0) {
listView.currentIndex = index - 1
} else if (listView.keyNavigationWraps) {
listView.currentIndex = listView.count - 1
}
animModel.rootIndex = listModel.modelIndex(listView.currentIndex)
}
Keys.onDownPressed: {
if (listModel.count > index + 1) {
listView.currentIndex = index + 1
} else if (listView.keyNavigationWraps) {
listView.currentIndex = 0
}
animModel.rootIndex = listModel.modelIndex(listView.currentIndex)
}
}
}
VisualDataModel {
id: animModel
model: dirModel
Rectangle {
width: listView.width; height: 25
Text { text: fileName }
}
rootIndex: listModel.modelIndex(0)
}
SequentialAnimation {
id: animation
NumberAnimation {
target: listView
property: "x"
to: -listView.width
duration: 100
}
ScriptAction {
script: {
listView.x = 0
listModel.rootIndex = animModel.rootIndex
animModel.rootIndex = listModel.modelIndex(0)
}
}
}
SequentialAnimation {
id: leftAnimation
NumberAnimation {
target: listView
property: "x"
to: 0
duration: 100
}
ScriptAction {
script: {
animModel.rootIndex = listModel.modelIndex(0)
}
}
}
ListView {
id: listView
x: 0; y: 0
width: page.width * 0.8
height: page.height
model: listModel
focus: true
keyNavigationWraps: true
}
ListView {
id: animBox
x: listView.width; y: listView.y
width: listView.width
height: listView.height
model: animModel
}
}