Skip to content

File selector in QML and PySide

Sunday, 29 May 2011  |  oever

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.

QML File Selector

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