diff --git a/demo/qml/DataAndChart.qml b/demo/qml/DataAndChart.qml index 7dd31c1..9e2ab5e 100644 --- a/demo/qml/DataAndChart.qml +++ b/demo/qml/DataAndChart.qml @@ -1,90 +1,110 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import QtQuick 2.6 import QtQml.Models 2.2 import QtQuick.Controls 1.5 import QtQuick.Layouts 1.3 RowLayout { Component { id: editableDelegate Item { function round(v) { return Math.round(1000 * v) / 1000 } Text { text: round(styleData.value) visible: !styleData.selected } Loader { id: loaderEditor anchors.fill: parent Connections { target: loaderEditor.item onEditingFinished: { if (!demo) { return } var val = loaderEditor.item.text var row = styleData.row if (styleData.column === 0) { demo.timeSeries.setTime(row, val) } else if (styleData.column === 1) { demo.timeSeries.setSin(row, val) } else { demo.timeSeries.setCos(row, val) } } } sourceComponent: styleData.selected ? editor : null } Component { id: editor TextInput { id: textInput text: round(styleData.value) MouseArea { anchors.fill: parent hoverEnabled: true onClicked: textInput.forceActiveFocus() } } } } } SplitView { anchors.fill: parent TableView { model: demo.timeSeries Layout.fillHeight: true TableViewColumn { role: "time" title: qsTr("time") } TableViewColumn { role: "sin" title: qsTr("sin") } TableViewColumn { role: "cos" title: qsTr("cos") } itemDelegate: { return editableDelegate } } Item { Layout.fillWidth: true Layout.fillHeight: true Text { anchors.centerIn: parent text: qsTr("QtChart is not available.") visible: chartLoader.status !== Loader.Ready } Loader { anchors.fill: parent id: chartLoader source: "chart.qml" } } } } diff --git a/demo/qml/Fibonacci.qml b/demo/qml/Fibonacci.qml index d31e7cd..70db04c 100644 --- a/demo/qml/Fibonacci.qml +++ b/demo/qml/Fibonacci.qml @@ -1,28 +1,48 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import QtQuick 2.6 import QtQuick.Controls 1.5 import QtQuick.Layouts 1.3 ColumnLayout { Layout.fillHeight: true Text { text: qsTr("Calculate the nth Fibonacci number") } TextField { id: fibonacciInput placeholderText: qsTr("Your number") validator: IntValidator { bottom: 0 top: 100 } text: demo.fibonacci.input onAccepted: { var val = parseInt(text, 10) if (val !== demo.fibonacci.input) { demo.fibonacci.input = val } } } Text { text: qsTr("The Fibonacci number: ") + demo.fibonacci.result } } diff --git a/demo/qml/Fibonacci2.qml b/demo/qml/Fibonacci2.qml index a81b2a6..63c7c69 100644 --- a/demo/qml/Fibonacci2.qml +++ b/demo/qml/Fibonacci2.qml @@ -1,27 +1,47 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import QtQuick 2.6 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.3 ColumnLayout { Text { text: qsTr("Calculate the nth Fibonacci number") } TextField { id: fibonacciInput placeholderText: qsTr("Your number") validator: IntValidator { bottom: 0 top: 100 } text: demo.fibonacci.input onAccepted: { var val = parseInt(text, 10) if (val !== demo.fibonacci.input) { demo.fibonacci.input = val } } } Text { text: qsTr("The Fibonacci number: ") + demo.fibonacci.result } } diff --git a/demo/qml/FibonacciList.qml b/demo/qml/FibonacciList.qml index 6da1f51..a447dde 100644 --- a/demo/qml/FibonacciList.qml +++ b/demo/qml/FibonacciList.qml @@ -1,18 +1,38 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import QtQuick 2.6 import QtQml.Models 2.2 import QtQuick.Controls 1.5 import QtQuick.Layouts 1.3 TableView { model: demo.fibonacciList TableViewColumn { role: "row" title: qsTr("Row") width: 100 } TableViewColumn { role: "fibonacciNumber" title: qsTr("Fibonacci number") width: 100 } } diff --git a/demo/qml/FibonacciList2.qml b/demo/qml/FibonacciList2.qml index 7845422..1b0ca1f 100644 --- a/demo/qml/FibonacciList2.qml +++ b/demo/qml/FibonacciList2.qml @@ -1,24 +1,44 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import QtQuick 2.6 import QtQuick.Controls 2.2 ListView { model: demo.fibonacciList header: Row { Text { width: 100 text: qsTr("Row") } Text { text: qsTr("Fibonacci number") } } delegate: Row { Text { width: 100 text: row } Text { text: fibonacciNumber } } } diff --git a/demo/qml/FibonacciListKirigami.qml b/demo/qml/FibonacciListKirigami.qml index 4d5bf5c..56a718a 100644 --- a/demo/qml/FibonacciListKirigami.qml +++ b/demo/qml/FibonacciListKirigami.qml @@ -1,15 +1,35 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import QtQuick 2.6 import QtQuick.Controls 2.0 as QQC2 import org.kde.kirigami 2.0 as Kirigami Kirigami.ScrollablePage { ListView { model: demo.fibonacciList header: Kirigami.Heading { text: qsTr("Row") + "\t" + qsTr("Fibonacci number") } delegate: Kirigami.BasicListItem { label: row + "\t" + fibonacciNumber } } } diff --git a/demo/qml/FileTreeView.qml b/demo/qml/FileTreeView.qml index 46b51ef..d1b419d 100644 --- a/demo/qml/FileTreeView.qml +++ b/demo/qml/FileTreeView.qml @@ -1,31 +1,51 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import QtQuick 2.6 import QtQml.Models 2.2 import QtQuick.Controls 1.5 import QtQuick.Layouts 1.3 TreeView { id: treeView model: sortedFileSystem sortIndicatorVisible: true alternatingRowColors: true selection: selectionModel TableViewColumn { title: "Name" role: "fileName" } TableViewColumn { title: "Size" role: "fileSize" } Component.onCompleted: { var root = treeView.rootIndex var first = sortedFileSystem.index(0, 0, root) treeView.expand(first) } onSortIndicatorColumnChanged: sort() onSortIndicatorOrderChanged: sort() function sort() { var role = getColumn(treeView.sortIndicatorColumn).role model.sortByRole(role, treeView.sortIndicatorOrder) } } diff --git a/demo/qml/FileTreeView2.qml b/demo/qml/FileTreeView2.qml index 475bb65..66bce2d 100644 --- a/demo/qml/FileTreeView2.qml +++ b/demo/qml/FileTreeView2.qml @@ -1,78 +1,98 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import QtQuick 2.6 import QtQml.Models 2.2 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 ListView { id: view property string title header: Column { width: parent.width ToolBar { width: parent.width RowLayout { anchors.fill: parent ToolButton { text: qsTr("‹") enabled: dirModel.rootIndex.valid onClicked: { dirModel.rootIndex = dirModel.rootIndex.parent } } Label { text: view.title elide: Label.ElideMiddle horizontalAlignment: Qt.AlignHCenter verticalAlignment: Qt.AlignVCenter Layout.fillWidth: true } } } Row { Text { width: 200 text: qsTr("Name") } Text { text: qsTr("Size") } } } model: DelegateModel { id: dirModel model: sortedFileSystem onRootIndexChanged: { var index = sortedFileSystem.mapToSource(rootIndex); view.title = demo.fileSystemTree.filePath(index) || ""; } delegate: Item { width: parent.width height: row.height Row { id: row Connections { target: sortedFileSystem onRowsInserted: { // enable the button if children were found when 'model' // was created or if they were just inserted button.enabled = model.hasModelChildren || dirModel.modelIndex(index) === parent } } Button { id: button width: 200 text: fileName enabled: model.hasModelChildren onClicked: { view.model.rootIndex = view.model.modelIndex(index) } } Label { text: fileSize padding: button.padding } } } } } diff --git a/demo/qml/FileTreeViewKirigami.qml b/demo/qml/FileTreeViewKirigami.qml index e1d9221..0f45992 100644 --- a/demo/qml/FileTreeViewKirigami.qml +++ b/demo/qml/FileTreeViewKirigami.qml @@ -1,79 +1,99 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import QtQuick 2.6 import QtQml.Models 2.2 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import org.kde.kirigami 2.0 as Kirigami ListView { id: view property string title header: Column { width: parent.width ToolBar { width: parent.width RowLayout { anchors.fill: parent ToolButton { text: qsTr("‹") enabled: dirModel.rootIndex.valid onClicked: { dirModel.rootIndex = dirModel.rootIndex.parent } } Kirigami.Heading { text: view.title elide: Label.ElideMiddle horizontalAlignment: Qt.AlignHCenter verticalAlignment: Qt.AlignVCenter Layout.fillWidth: true } } } Row { Text { width: 200 text: qsTr("Name") } Text { text: qsTr("Size") } } } model: DelegateModel { id: dirModel model: sortedFileSystem onRootIndexChanged: { var index = sortedFileSystem.mapToSource(rootIndex); view.title = demo.fileSystemTree.filePath(index) || ""; } delegate: Item { width: parent.width height: row.height Row { id: row Connections { target: sortedFileSystem onRowsInserted: { // enable the button if children were found when 'model' // was created or if they were just inserted button.enabled = model.hasModelChildren || dirModel.modelIndex(index) === parent } } Button { id: button width: 200 text: fileName enabled: model.hasModelChildren onClicked: { view.model.rootIndex = view.model.modelIndex(index) } } Label { text: fileSize padding: button.padding } } } } } diff --git a/demo/qml/ProcessesTree.qml b/demo/qml/ProcessesTree.qml index 1c105b4..c87572b 100644 --- a/demo/qml/ProcessesTree.qml +++ b/demo/qml/ProcessesTree.qml @@ -1,69 +1,89 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import QtQuick 2.6 import QtQml.Models 2.2 import QtQuick.Controls 1.5 import QtQuick.Layouts 1.3 Item { TextField { id: processFilterInput focus: true width: parent.width placeholderText: "Filter processes" onTextChanged: { processes.filterRegExp = new RegExp(processFilterInput.text) } } TreeView { onClicked: { processSelection.select( index, ItemSelectionModel.ToggleCurrent) } Binding { target: demo.processes property: "active" value: visible } width: parent.width anchors.top: processFilterInput.bottom anchors.bottom: parent.bottom id: processView model: processes selection: processSelection selectionMode: SelectionMode.ExtendedSelection // selectionMode: SelectionMode.SingleSelection sortIndicatorVisible: true alternatingRowColors: true TableViewColumn { title: "name" role: "name" } TableViewColumn { title: "cpu" role: "cpuUsage" } TableViewColumn { title: "memory" role: "memory" } onSortIndicatorColumnChanged: sort() onSortIndicatorOrderChanged: sort() function sort() { var role = getColumn( processView.sortIndicatorColumn).role model.sortByRole(role, processView.sortIndicatorOrder) } Timer { interval: 100 running: true repeat: true onTriggered: { var root = processView.rootIndex var systemd = processes.index(1, 0, root) if (processes.data(systemd) === "systemd") { processView.expand(systemd) running = false } } } } } diff --git a/demo/qml/StyleSwitcher.qml b/demo/qml/StyleSwitcher.qml index 191f39a..7518be2 100644 --- a/demo/qml/StyleSwitcher.qml +++ b/demo/qml/StyleSwitcher.qml @@ -1,23 +1,43 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import QtQuick 2.6 import QtQuick.Controls 1.5 import QtQuick.Layouts 1.3 RowLayout { Image { sourceSize.height: 2* box.height fillMode: Image.PreserveAspectFit source: "../rust_qt_binding_generator.svg" } ComboBox { id: box currentIndex: qtquickIndex model: styles textRole: "display" onActivated: { if (index !== qtquickIndex) { widgets.currentIndex = index; application.close(); } } } } diff --git a/demo/qml/StyleSwitcher2.qml b/demo/qml/StyleSwitcher2.qml index 57894c9..a4c523d 100644 --- a/demo/qml/StyleSwitcher2.qml +++ b/demo/qml/StyleSwitcher2.qml @@ -1,24 +1,44 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import QtQuick 2.6 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 RowLayout { Image { sourceSize.height: 2 * box.height fillMode: Image.PreserveAspectFit source: "../rust_qt_binding_generator.svg" } ComboBox { id: box Layout.fillWidth: true currentIndex: qtquickIndex model: styles textRole: "display" onActivated: { if (index !== qtquickIndex) { widgets.currentIndex = index application.close() } } } } diff --git a/demo/qml/chart.qml b/demo/qml/chart.qml index 557603d..2c4c19e 100644 --- a/demo/qml/chart.qml +++ b/demo/qml/chart.qml @@ -1,54 +1,74 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import QtQuick 2.6 import QtCharts 2.2 Item { ChartView { id: chart anchors.fill: parent Component.onCompleted: { legend.alignment = Qt.AlignBottom } antialiasing: true ValueAxis { id: axisX titleText: qsTr("time [s]") labelFormat: "%.1f" } ValueAxis { id: axisY titleText: qsTr("electric potential [V]") labelFormat: "%.1f" } LineSeries { id: sin name: "sin" axisX: axisX axisY: axisY } VXYModelMapper { model: demo.timeSeries xColumn: 0 yColumn: 1 series: sin } LineSeries { id: cos name: "cos" axisX: axisX axisY: axisY } VXYModelMapper { model: demo.timeSeries xColumn: 0 yColumn: 2 series: cos } } } diff --git a/demo/qml/demo-kirigami2.qml b/demo/qml/demo-kirigami2.qml index 9d23052..f511ae5 100644 --- a/demo/qml/demo-kirigami2.qml +++ b/demo/qml/demo-kirigami2.qml @@ -1,42 +1,62 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import QtQuick 2.1 import QtQuick.Controls 2.0 as QQC2 import org.kde.kirigami 2.0 as Kirigami import QtQuick.Layouts 1.3 Kirigami.ApplicationWindow { id: application property string initialTab: "style" property int qtquickIndex: 0 visible: true width: 800 height: 640 header: QQC2.TabBar { id: bar width: parent.width QQC2.TabButton { text: "object" } QQC2.TabButton { text: "list" } QQC2.TabButton { text: "tree" } } footer: Rectangle { height: statusBar.height width: parent.width StyleSwitcher2 { id: statusBar width: parent.width } } StackLayout { anchors.fill: parent currentIndex: bar.currentIndex Fibonacci2 {} FibonacciListKirigami {} FileTreeViewKirigami {} } } diff --git a/demo/qml/demo-qtquick2.qml b/demo/qml/demo-qtquick2.qml index 50956d8..86570e3 100644 --- a/demo/qml/demo-qtquick2.qml +++ b/demo/qml/demo-qtquick2.qml @@ -1,41 +1,61 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import QtQuick 2.6 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.3 ApplicationWindow { id: application property string initialTab: "style" property int qtquickIndex: 0 visible: true width: 800 height: 640 header: TabBar { id: bar width: parent.width TabButton { text: "object" } TabButton { text: "list" } TabButton { text: "tree" } } footer: Rectangle { height: statusBar.height width: parent.width StyleSwitcher2 { id: statusBar width: parent.width } } StackLayout { anchors.fill: parent currentIndex: bar.currentIndex Fibonacci2 {} FibonacciList2 {} FileTreeView2 {} } } diff --git a/demo/qml/demo.qml b/demo/qml/demo.qml index 966d7a8..5d31e0d 100644 --- a/demo/qml/demo.qml +++ b/demo/qml/demo.qml @@ -1,63 +1,83 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import QtQuick 2.6 import QtQml.Models 2.2 import QtQuick.Controls 1.5 import QtQuick.Layouts 1.3 ApplicationWindow { id: application property string initialTab: "style" property int qtquickIndex: 0 property var processes: ListModel { ListElement { name: "init" } } onInitialTabChanged: { for (var i = 0; i < tabView.count; ++i) { if (tabView.getTab(i).title === initialTab) { tabView.currentIndex = i } } } width: 640 height: 480 visible: true ItemSelectionModel { id: selectionModel model: sortedFileSystem } ItemSelectionModel { id: processSelection model: processes } statusBar: StatusBar { StyleSwitcher { anchors.right: parent.right } } TabView { id: tabView anchors.fill: parent Tab { title: "object" Fibonacci {} } Tab { title: "list" FibonacciList {} } Tab { title: "tree" FileTreeView {} } Tab { title: "processes" ProcessesTree {} } Tab { id: chartTab title: "chart" DataAndChart {} } } } diff --git a/demo/rust/src/implementation/demo.rs b/demo/rust/src/implementation/demo.rs index 495c1e7..6891058 100644 --- a/demo/rust/src/implementation/demo.rs +++ b/demo/rust/src/implementation/demo.rs @@ -1,62 +1,80 @@ +// Copyright 2017 Jos van den Oever +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License or (at your option) version 3 or any later version +// accepted by the membership of KDE e.V. (or its successor approved +// by the membership of KDE e.V.), which shall act as a proxy +// defined in Section 14 of version 3 of the license. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + use interface::*; use super::*; pub struct Demo { emit: DemoEmitter, fibonacci: Fibonacci, fibonacci_list: FibonacciList, file_system_tree: FileSystemTree, processes: Processes, time_series: TimeSeries } impl DemoTrait for Demo { fn new(emit: DemoEmitter, fibonacci: Fibonacci, fibonacci_list: FibonacciList, file_system_tree: FileSystemTree, processes: Processes, time_series: TimeSeries) -> Self { Demo { emit: emit, fibonacci: fibonacci, fibonacci_list: fibonacci_list, file_system_tree: file_system_tree, processes: processes, time_series: time_series } } fn emit(&self) -> &DemoEmitter { &self.emit } fn fibonacci(&self) -> &Fibonacci { &self.fibonacci } fn fibonacci_mut(&mut self) -> &mut Fibonacci { &mut self.fibonacci } fn fibonacci_list(&self) -> &FibonacciList { &self.fibonacci_list } fn fibonacci_list_mut(&mut self) -> &mut FibonacciList { &mut self.fibonacci_list } fn file_system_tree(&self) -> &FileSystemTree { &self.file_system_tree } fn file_system_tree_mut(&mut self) -> &mut FileSystemTree { &mut self.file_system_tree } fn processes(&self) -> &Processes { &self.processes } fn processes_mut(&mut self) -> &mut Processes { &mut self.processes } fn time_series(&self) -> &TimeSeries { &self.time_series } fn time_series_mut(&mut self) -> &mut TimeSeries { &mut self.time_series } } diff --git a/demo/rust/src/implementation/fibonacci.rs b/demo/rust/src/implementation/fibonacci.rs index cec0422..bc2a37d 100644 --- a/demo/rust/src/implementation/fibonacci.rs +++ b/demo/rust/src/implementation/fibonacci.rs @@ -1,84 +1,102 @@ +// Copyright 2017 Jos van den Oever +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License or (at your option) version 3 or any later version +// accepted by the membership of KDE e.V. (or its successor approved +// by the membership of KDE e.V.), which shall act as a proxy +// defined in Section 14 of version 3 of the license. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + use std::thread; use interface::*; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use std::sync::Arc; fn fibonacci(input: u32) -> usize { if input <= 1 { return input as usize; } let mut i = 0; let mut sum = 0; let mut last = 0; let mut cur = 1; while i < input - 1 { sum = last + cur; last = cur; cur = sum; i += 1; } sum } pub struct Fibonacci { emit: FibonacciEmitter, input: u32, result: Arc, } impl FibonacciTrait for Fibonacci { fn new(emit: FibonacciEmitter) -> Fibonacci { Fibonacci { emit: emit, input: 0, result: Arc::new(AtomicUsize::new(0)), } } fn emit(&self) -> &FibonacciEmitter { &self.emit } fn input(&self) -> u32 { self.input } fn set_input(&mut self, value: u32) { self.input = value; self.emit.input_changed(); let emit = self.emit.clone(); let result = self.result.clone(); result.swap(0, Ordering::SeqCst); emit.result_changed(); thread::spawn(move || { let r = fibonacci(value); result.swap(r, Ordering::SeqCst); emit.result_changed(); }); } fn result(&self) -> u64 { self.result.fetch_add(0, Ordering::SeqCst) as u64 } } pub struct FibonacciList { emit: FibonacciListEmitter, } impl FibonacciListTrait for FibonacciList { fn new(emit: FibonacciListEmitter, _: FibonacciListList) -> FibonacciList { FibonacciList { emit: emit, } } fn emit(&self) -> &FibonacciListEmitter { &self.emit } fn row_count(&self) -> usize { 93 } fn row(&self, row: usize) -> u64 { row as u64 + 1 } fn fibonacci_number(&self, row: usize) -> u64 { fibonacci(row as u32 + 1) as u64 } } diff --git a/demo/rust/src/implementation/file_system_tree.rs b/demo/rust/src/implementation/file_system_tree.rs index 9e1c3c9..a2116a4 100644 --- a/demo/rust/src/implementation/file_system_tree.rs +++ b/demo/rust/src/implementation/file_system_tree.rs @@ -1,284 +1,302 @@ +// Copyright 2017 Jos van den Oever +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License or (at your option) version 3 or any later version +// accepted by the membership of KDE e.V. (or its successor approved +// by the membership of KDE e.V.), which shall act as a proxy +// defined in Section 14 of version 3 of the license. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + use interface::*; use std::fs::*; use std::fs::read_dir; use std::path::PathBuf; use std::ffi::OsString; use std::default::Default; use std::thread; use std::sync::{Arc, Mutex}; use std::marker::Sync; use std::collections::HashMap; pub struct DirEntry { name: OsString, metadata: Option, path: Option, icon: Vec, } type Incoming = Arc>>>; impl Item for DirEntry { fn new(name: &str) -> DirEntry { DirEntry { name: OsString::from(name), metadata: metadata(name).ok(), path: None, icon: Vec::new() } } fn can_fetch_more(&self) -> bool { self.metadata.as_ref().map_or(false, |m| m.is_dir()) } fn file_name(&self) -> String { self.name.to_string_lossy().to_string() } fn file_path(&self) -> Option { self.path.as_ref().map(|p| p.to_string_lossy().into()) } fn file_permissions(&self) -> i32 { 42 } fn file_type(&self) -> i32 { 0 } fn file_size(&self) -> Option { self.metadata.as_ref().map(|m| m.len()) } fn icon(&self) -> &[u8] { &self.icon } fn retrieve(id: usize, parents: Vec<&DirEntry>, q: Incoming, emit: FileSystemTreeEmitter) { let mut v = Vec::new(); let path: PathBuf = parents.into_iter().map(|e| &e.name).collect(); thread::spawn(move || { if let Ok(it) = read_dir(&path) { for i in it.filter_map(|v| v.ok()) { let de = DirEntry { name: i.file_name(), metadata: i.metadata().ok(), path: Some(i.path()), icon: Vec::new(), }; v.push(de); } } v.sort_by(|a, b| a.name.cmp(&b.name)); let mut map = q.lock().unwrap(); if !map.contains_key(&id) { map.insert(id, v); emit.new_data_ready(Some(id)); } }); } } impl Default for DirEntry { fn default() -> DirEntry { DirEntry { name: OsString::new(), metadata: None, path: None, icon: Vec::new(), } } } pub trait Item: Default { fn new(name: &str) -> Self; fn can_fetch_more(&self) -> bool; fn retrieve(id: usize, parents: Vec<&Self>, q: Incoming, emit: FileSystemTreeEmitter); fn file_name(&self) -> String; fn file_path(&self) -> Option; fn file_permissions(&self) -> i32; fn file_type(&self) -> i32; fn file_size(&self) -> Option; fn icon(&self) -> &[u8]; } pub type FileSystemTree = RGeneralItemModel; struct Entry { parent: Option, row: usize, children: Option>, data: T, } pub struct RGeneralItemModel { emit: FileSystemTreeEmitter, model: FileSystemTreeTree, entries: Vec>, path: Option, incoming: Incoming, } impl RGeneralItemModel where T: Sync + Send, { fn reset(&mut self) { self.model.begin_reset_model(); self.entries.clear(); if let Some(ref path) = self.path { let root = Entry { parent: None, row: 0, children: None, data: T::new(path), }; self.entries.push(root); } self.model.end_reset_model(); } fn get(&self, item: usize) -> &Entry { &self.entries[item] } fn retrieve(&mut self, item: usize) { let parents = self.get_parents(item); let incoming = self.incoming.clone(); T::retrieve(item, parents, incoming, self.emit.clone()); } fn process_incoming(&mut self) { if let Ok(ref mut incoming) = self.incoming.try_lock() { for (id, entries) in incoming.drain() { if self.entries[id].children.is_some() { continue; } let mut new_entries = Vec::new(); let mut children = Vec::new(); { for (r, d) in entries.into_iter().enumerate() { let e = Entry { parent: Some(id), row: r, children: None, data: d, }; children.push(self.entries.len() + r); new_entries.push(e); } if !new_entries.is_empty() { self.model.begin_insert_rows( Some(id), 0, (new_entries.len() - 1), ); } } self.entries[id].children = Some(children); if !new_entries.is_empty() { self.entries.append(&mut new_entries); self.model.end_insert_rows(); } } } } fn get_parents(&self, id: usize) -> Vec<&T> { let mut pos = Some(id); let mut e = Vec::new(); while let Some(p) = pos { e.push(p); pos = self.entries[p].parent; } e.into_iter().rev().map(|i| &self.entries[i].data).collect() } } impl FileSystemTreeTrait for RGeneralItemModel where T: Sync + Send, { fn new(emit: FileSystemTreeEmitter, model: FileSystemTreeTree) -> Self { let mut tree = RGeneralItemModel { emit: emit, model: model, entries: Vec::new(), path: None, incoming: Arc::new(Mutex::new(HashMap::new())), }; tree.reset(); tree } fn emit(&self) -> &FileSystemTreeEmitter { &self.emit } fn path(&self) -> Option<&str> { self.path.as_ref().map(|s|&s[..]) } fn set_path(&mut self, value: Option) { if self.path != value { self.path = value; self.emit.path_changed(); self.reset(); } } fn can_fetch_more(&self, item: Option) -> bool { if let Some(item) = item { let entry = self.get(item); entry.children.is_none() && entry.data.can_fetch_more() } else { false } } fn fetch_more(&mut self, item: Option) { self.process_incoming(); if !self.can_fetch_more(item) { return; } if let Some(item) = item { self.retrieve(item); } } fn row_count(&self, item: Option) -> usize { if self.entries.is_empty() { return 0; } if let Some(i) = item { let entry = self.get(i); if let Some(ref children) = entry.children { children.len() } else { // model does lazy loading, signal that data may be available if self.can_fetch_more(item) { self.emit.new_data_ready(item); } 0 } } else { 1 } } fn index(&self, item: Option, row: usize) -> usize { if let Some(item) = item { self.get(item).children.as_ref().unwrap()[row] } else { 0 } } fn parent(&self, item: usize) -> Option { self.entries[item].parent } fn row(&self, item: usize) -> usize { self.entries[item].row } fn file_name(&self, item: usize) -> String { self.get(item).data.file_name() } fn file_permissions(&self, item: usize) -> i32 { self.get(item).data.file_permissions() } #[allow(unused_variables)] fn file_icon(&self, item: usize) -> &[u8] { self.get(item).data.icon() } fn file_path(&self, item: usize) -> Option { self.get(item).data.file_path() } fn file_type(&self, item: usize) -> i32 { self.get(item).data.file_type() } fn file_size(&self, item: usize) -> Option { self.get(item).data.file_size() } } diff --git a/demo/rust/src/implementation/mod.rs b/demo/rust/src/implementation/mod.rs index a7935b5..69ce8db 100644 --- a/demo/rust/src/implementation/mod.rs +++ b/demo/rust/src/implementation/mod.rs @@ -1,11 +1,29 @@ +// Copyright 2017 Jos van den Oever +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License or (at your option) version 3 or any later version +// accepted by the membership of KDE e.V. (or its successor approved +// by the membership of KDE e.V.), which shall act as a proxy +// defined in Section 14 of version 3 of the license. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + mod demo; mod fibonacci; mod file_system_tree; mod processes; mod time_series; pub use self::demo::*; pub use self::fibonacci::*; pub use self::file_system_tree::*; pub use self::processes::*; pub use self::time_series::*; diff --git a/demo/rust/src/implementation/processes.rs b/demo/rust/src/implementation/processes.rs index c31c1dd..7d4a188 100644 --- a/demo/rust/src/implementation/processes.rs +++ b/demo/rust/src/implementation/processes.rs @@ -1,416 +1,434 @@ +// Copyright 2017 Jos van den Oever +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License or (at your option) version 3 or any later version +// accepted by the membership of KDE e.V. (or its successor approved +// by the membership of KDE e.V.), which shall act as a proxy +// defined in Section 14 of version 3 of the license. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + use interface::*; use sysinfo::*; use std::sync::{Arc, Mutex}; use std::collections::HashMap; use libc::pid_t; use std::thread; use std::time::Duration; use std::sync::mpsc::{channel, Sender, Receiver, RecvTimeoutError}; struct ProcessItem { row: usize, tasks: Vec, process: Process, } #[derive(Default)] struct ProcessTree { top: Vec, processes: HashMap, cpusum: f32, } enum ChangeState { Active, Inactive, Quit } pub struct Processes { emit: ProcessesEmitter, model: ProcessesTree, p: ProcessTree, incoming: Arc>>, active: bool, channel: Sender } fn check_process_hierarchy(parent: Option, processes: &HashMap) { for (pid, process) in processes { assert_eq!(process.pid, *pid); if !parent.is_none() { assert_eq!(process.parent, parent); } check_process_hierarchy(Some(*pid), &process.tasks); } } fn collect_processes( tasks: &HashMap, mut processes: &mut HashMap, ) -> f32 { let mut cpusum = 0.0; for process in tasks.values() { processes.insert( process.pid, ProcessItem { row: 0, tasks: Vec::new(), process: process.clone(), }, ); let s = collect_processes(&process.tasks, &mut processes); cpusum += process.cpu_usage + s; } cpusum } // reconstruct process hierarchy fn handle_tasks(processes: &mut HashMap) -> Vec { let mut top = Vec::new(); let pids: Vec = processes.keys().cloned().collect(); for pid in pids { if let Some(parent) = processes[&pid].process.parent { let p = processes.get_mut(&parent).unwrap(); p.tasks.push(pid); } else { top.push(pid); } } top } fn update_rows(list: &[pid_t], processes: &mut HashMap) { for (row, pid) in list.iter().enumerate() { processes.get_mut(pid).unwrap().row = row; let l = processes[pid].tasks.clone(); update_rows(&l, processes); } } fn sort_tasks(p: &mut ProcessTree) { for process in p.processes.values_mut() { process.tasks.sort(); } p.top.sort(); update_rows(&p.top, &mut p.processes); } fn update() -> ProcessTree { let mut p = ProcessTree::default(); let mut sysinfo = System::new(); sysinfo.refresh_processes(); let list = sysinfo.get_process_list(); check_process_hierarchy(None, list); p.cpusum = collect_processes(list, &mut p.processes); p.top = handle_tasks(&mut p.processes); sort_tasks(&mut p); p } fn update_thread( emit: ProcessesEmitter, incoming: Arc>>, mut active: bool, status_channel: Receiver, ) { thread::spawn(move || { loop { let timeout = if active { *incoming.lock().unwrap() = Some(update()); emit.new_data_ready(None); Duration::from_secs(1) } else { Duration::from_secs(10_000) }; match status_channel.recv_timeout(timeout) { Err(RecvTimeoutError::Timeout) => {}, Err(RecvTimeoutError::Disconnected) | Ok(ChangeState::Quit) => { return; }, Ok(ChangeState::Active) => { active = true; }, Ok(ChangeState::Inactive) => { active = false; }, } } }); } impl Processes { fn get(&self, item: usize) -> &ProcessItem { let pid = item as pid_t; &self.p.processes[&pid] } fn process(&self, item: usize) -> &Process { let pid = item as pid_t; &self.p.processes[&pid].process } } fn move_process( pid: pid_t, amap: &mut HashMap, bmap: &mut HashMap, ) { if let Some(e) = bmap.remove(&pid) { amap.insert(pid, e); let ts = amap[&pid].tasks.clone(); for t in ts { move_process(t, amap, bmap); } } } fn remove_row( model: &ProcessesTree, parent: pid_t, row: usize, map: &mut HashMap, ) { let pid = map[&parent].tasks[row]; println!( "removing {} '{}' {}", pid, map[&pid].process.exe, map[&pid].process.cmd.join(" ") ); model.begin_remove_rows(Some(parent as usize), row, row); map.remove(&pid); let len = { let tasks = &mut map.get_mut(&parent).unwrap().tasks; tasks.remove(row); tasks.len() }; for r in row..len { let pid = map.get_mut(&parent).unwrap().tasks[r]; map.get_mut(&pid).unwrap().row = r; } model.end_remove_rows(); } fn insert_row( model: &ProcessesTree, parent: pid_t, row: usize, map: &mut HashMap, pid: pid_t, source: &mut HashMap, ) { println!( "adding {} '{}' {}", pid, source[&pid].process.exe, source[&pid].process.cmd.join(" ") ); model.begin_insert_rows(Some(parent as usize), row, row); move_process(pid, map, source); let len = { let tasks = &mut map.get_mut(&parent).unwrap().tasks; tasks.insert(row, pid); tasks.len() }; for r in row..len { let pid = map.get_mut(&parent).unwrap().tasks[r]; map.get_mut(&pid).unwrap().row = r; } model.end_insert_rows(); } fn cmp_f32(a: f32, b: f32) -> bool { ((a - b) / a).abs() < 0.01 } fn sync_row(model: &ProcessesTree, pid: pid_t, a: &mut Process, b: &Process) -> f32 { let mut changed = a.name != b.name; if changed { a.name.clone_from(&b.name); } if !cmp_f32(a.cpu_usage, b.cpu_usage) { a.cpu_usage = b.cpu_usage; changed = true; } if a.cmd != b.cmd { a.cmd.clone_from(&b.cmd); changed = true; } if a.exe != b.exe { a.exe.clone_from(&b.exe); changed = true; } if a.memory != b.memory { a.memory = b.memory; changed = true; } if changed { model.data_changed(pid as usize, pid as usize); } b.cpu_usage } fn sync_tree( model: &ProcessesTree, parent: pid_t, amap: &mut HashMap, bmap: &mut HashMap, ) -> f32 { let mut a = 0; let mut b = 0; let mut alen = amap[&parent].tasks.len(); let blen = bmap[&parent].tasks.len(); let mut cpu_total = bmap[&parent].process.cpu_usage; while a < alen && b < blen { let apid = amap[&parent].tasks[a]; let bpid = bmap[&parent].tasks[b]; if apid < bpid { // a process has disappeared remove_row(model, parent, a, amap); alen -= 1; } else if apid > bpid { // a process has appeared insert_row(model, parent, a, amap, bpid, bmap); cpu_total += amap[&bpid].process.cpu_usage; a += 1; alen += 1; b += 1; } else { cpu_total += sync_row( model, apid, &mut amap.get_mut(&apid).unwrap().process, &bmap[&apid].process, ); cpu_total += sync_tree(model, apid, amap, bmap); a += 1; b += 1; } } while a < blen { let bpid = bmap[&parent].tasks[b]; insert_row(model, parent, a, amap, bpid, bmap); a += 1; alen += 1; b += 1; } while b < alen { remove_row(model, parent, a, amap); alen -= 1; } if !cmp_f32(cpu_total, bmap[&parent].process.cpu_usage) { amap.get_mut(&parent).unwrap().process.cpu_usage = cpu_total; model.data_changed(parent as usize, parent as usize); } assert_eq!(a, b); cpu_total } impl ProcessesTrait for Processes { fn new(emit: ProcessesEmitter, model: ProcessesTree) -> Processes { let (tx, rx) = channel(); let p = Processes { emit: emit.clone(), model: model, p: ProcessTree::default(), incoming: Arc::new(Mutex::new(None)), active: false, channel: tx, }; update_thread(emit, p.incoming.clone(), p.active, rx); p } fn emit(&self) -> &ProcessesEmitter { &self.emit } fn row_count(&self, item: Option) -> usize { if let Some(item) = item { self.get(item).tasks.len() } else { self.p.top.len() } } fn index(&self, item: Option, row: usize) -> usize { if let Some(item) = item { self.get(item).tasks[row] as usize } else { self.p.top[row] as usize } } fn parent(&self, item: usize) -> Option { self.get(item).process.parent.map(|pid| pid as usize) } fn can_fetch_more(&self, item: Option) -> bool { if item.is_some() || !self.active { return false; } if let Ok(ref incoming) = self.incoming.try_lock() { incoming.is_some() } else { false } } fn fetch_more(&mut self, item: Option) { if item.is_some() || !self.active { return; } let new = if let Ok(ref mut incoming) = self.incoming.try_lock() { incoming.take() } else { None }; if let Some(mut new) = new { // alert! at the top level, only adding is supported! if self.p.top.is_empty() { self.model.begin_reset_model(); self.p = new; self.model.end_reset_model(); } else { let top = self.p.top.clone(); for pid in top { sync_tree(&self.model, pid, &mut self.p.processes, &mut new.processes); } } } } fn row(&self, item: usize) -> usize { self.get(item).row } fn pid(&self, item: usize) -> u32 { self.process(item).pid as u32 } fn uid(&self, item: usize) -> u32 { self.process(item).uid as u32 } fn cpu_usage(&self, item: usize) -> f32 { self.process(item).cpu_usage } fn cpu_percentage(&self, item: usize) -> u8 { let cpu = self.process(item).cpu_usage / self.p.cpusum; (cpu * 100.0) as u8 } fn memory(&self, item: usize) -> u64 { self.process(item).memory } fn name(&self, item: usize) -> &str { &self.process(item).name } fn cmd(&self, item: usize) -> String { self.process(item).cmd.join(" ") } fn active(&self) -> bool { self.active } fn set_active(&mut self, active: bool) { if self.active != active { self.active = active; if active { self.channel.send(ChangeState::Active) } else { self.channel.send(ChangeState::Inactive) }.expect("Process thread died."); } } } impl Drop for Processes { fn drop(&mut self) { self.channel.send(ChangeState::Quit).expect("Process thread died."); } } diff --git a/demo/rust/src/implementation/time_series.rs b/demo/rust/src/implementation/time_series.rs index 9752ece..95318bf 100644 --- a/demo/rust/src/implementation/time_series.rs +++ b/demo/rust/src/implementation/time_series.rs @@ -1,58 +1,76 @@ +// Copyright 2017 Jos van den Oever +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License or (at your option) version 3 or any later version +// accepted by the membership of KDE e.V. (or its successor approved +// by the membership of KDE e.V.), which shall act as a proxy +// defined in Section 14 of version 3 of the license. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + use interface::*; #[derive(Default, Clone)] struct TimeSeriesItem { time: f32, sin: f32, cos: f32, } pub struct TimeSeries { emit: TimeSeriesEmitter, list: Vec, } impl TimeSeriesTrait for TimeSeries { fn new(emit: TimeSeriesEmitter, _: TimeSeriesList) -> TimeSeries { let mut series = TimeSeries { emit: emit, list: Vec::new(), }; for i in 0..101 { let x = i as f32 / 10.; series.list.push(TimeSeriesItem { time: x, sin: x.sin(), cos: x.cos(), }); } series } fn emit(&self) -> &TimeSeriesEmitter { &self.emit } fn row_count(&self) -> usize { self.list.len() as usize } fn time(&self, row: usize) -> f32 { self.list[row as usize].time } fn set_time(&mut self, row: usize, v: f32) -> bool { self.list[row as usize].time = v; true } fn sin(&self, row: usize) -> f32 { self.list[row as usize].sin } fn set_sin(&mut self, row: usize, v: f32) -> bool { self.list[row as usize].sin = v; true } fn cos(&self, row: usize) -> f32 { self.list[row as usize].cos } fn set_cos(&mut self, row: usize, v: f32) -> bool { self.list[row as usize].cos = v; true } } diff --git a/demo/rust/src/lib.rs b/demo/rust/src/lib.rs index 78763bf..d9f65a6 100644 --- a/demo/rust/src/lib.rs +++ b/demo/rust/src/lib.rs @@ -1,5 +1,23 @@ +// Copyright 2017 Jos van den Oever +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License or (at your option) version 3 or any later version +// accepted by the membership of KDE e.V. (or its successor approved +// by the membership of KDE e.V.), which shall act as a proxy +// defined in Section 14 of version 3 of the license. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + extern crate libc; extern crate sysinfo; mod implementation; pub mod interface; diff --git a/demo/src/SortedModel.cpp b/demo/src/SortedModel.cpp index 9b967d2..370bbec 100644 --- a/demo/src/SortedModel.cpp +++ b/demo/src/SortedModel.cpp @@ -1,15 +1,35 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "SortedModel.h" bool SortedModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { if (QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent)) { return true; } QModelIndex source_index = sourceModel()->index(source_row, 0, source_parent); for (int i = 0 ; i < sourceModel()->rowCount(source_index); ++i) { if (filterAcceptsRow(i, source_index)) return true; } return false; } diff --git a/demo/src/SortedModel.h b/demo/src/SortedModel.h index f488b63..d712c59 100644 --- a/demo/src/SortedModel.h +++ b/demo/src/SortedModel.h @@ -1,28 +1,48 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #ifndef SORTED_MODEL #define SORTED_MODEL #include #include class SortedModel : public QSortFilterProxyModel { Q_OBJECT public: SortedModel() :QSortFilterProxyModel() {} bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const; Q_INVOKABLE QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const { return QSortFilterProxyModel::data(index, role); } public slots: void sortByRole(const QString& role, Qt::SortOrder order) { QHashIterator i(roleNames()); while (i.hasNext()) { i.next(); if (i.value() == role) { setSortRole(i.key()); } } sort(0, order); } }; #endif diff --git a/demo/src/main.cpp b/demo/src/main.cpp index 3d74c88..a704891 100644 --- a/demo/src/main.cpp +++ b/demo/src/main.cpp @@ -1,388 +1,408 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "Bindings.h" #include "SortedModel.h" #ifdef QT_CHARTS_LIB #include #endif #ifdef QT_QUICK_LIB #include #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct Model { QStringListModel styles; Demo demo; SortedModel sortedFileSystem; SortedModel sortedProcesses; }; void setStyle(QWidget* w, QStyle* style) { for (QObject* o: w->children()) { QWidget* c = dynamic_cast(o); if (c) { setStyle(c, style); } } w->setStyle(style); } QWindow* getWindow(QWidget* w) { QWidget* top = w; while (top && top->parentWidget()) { top = top->parentWidget(); } return top->windowHandle(); } #ifdef QT_QUICK_LIB void copyWindowGeometry(const QRect& rect, QObject* c) { if (rect.width() && rect.height()) { c->setProperty("x", rect.x()); c->setProperty("y", rect.y()); c->setProperty("width", rect.width()); c->setProperty("heigth", rect.height()); } } void createQtQuick(const QString& name, const QString& qml, Model* model, QWidget* widgets, const QString& initialTab) { QQmlApplicationEngine* engine = new QQmlApplicationEngine(); QQmlContext* c = engine->rootContext(); c->setContextProperty("styles", &model->styles); c->setContextProperty("demo", &model->demo); c->setContextProperty("sortedFileSystem", &model->sortedFileSystem); c->setContextProperty("widgets", widgets); QRect geometry; QWindow* window = getWindow(widgets); if (window) { geometry = window->geometry(); } else { geometry = QRect(0, 0, 800, 640); } engine->load(QUrl(qml)); QObject* root = engine->rootObjects().first(); copyWindowGeometry(geometry, root); root->setProperty("initialTab", initialTab); root->setProperty("qtquickIndex", QVariant(model->styles.stringList().indexOf(name))); root->setProperty("processes", QVariant::fromValue(&model->sortedProcesses)); } #endif QComboBox* createStyleComboBox(Model* model) { QComboBox* box = new QComboBox(); box->setModel(&model->styles); auto styles = QStyleFactory::keys(); QString currentStyle = QApplication::style()->objectName().toLower(); for (auto v: styles) { box->addItem("QWidgets " + v); if (v.toLower() == currentStyle) { box->setCurrentText(v); } } #ifdef QT_QUICK_LIB box->addItem("QtQuick"); #endif #ifdef QTQUICKCONTROLS2 box->addItem("QtQuick Controls 2"); #endif #ifdef KIRIGAMI2 box->addItem("Kirigami 2"); #endif return box; } QStatusBar* createStatusBar(Model* model, QWidget* main, QComboBox* box, const QString& initialTab) { QRect windowRect; auto f = [windowRect, box, main, model, initialTab](const QString &text) mutable { QWindow* window = getWindow(main); bool visible = main->isVisible(); if (text.startsWith("QWidgets ")) { main->setVisible(true); if (window && !visible) { window->setX(windowRect.x()); window->setY(windowRect.y()); window->setWidth(windowRect.width()); window->setHeight(windowRect.height()); } setStyle(main, QStyleFactory::create(text.mid(9))); #ifdef QT_QUICK_LIB } else { if (window) { windowRect.setX(window->x()); windowRect.setY(window->y()); windowRect.setWidth(window->width()); windowRect.setHeight(window->height()); } main->setVisible(false); #ifdef QTQUICKCONTROLS2 if (text == "QtQuick Controls 2") { createQtQuick("QtQuick Controls 2", "qrc:///qml/demo-qtquick2.qml", model, box, initialTab); } else #endif #ifdef KIRIGAMI2 if (text == "Kirigami 2") { createQtQuick("Kirigami 2", "qrc:///qml/demo-kirigami2.qml", model, box, initialTab); } else #endif createQtQuick("QtQuick", "qrc:///qml/demo.qml", model, box, initialTab); #endif } }; box->connect(box, &QComboBox::currentTextChanged, box, f); QSvgWidget* rust_qt_binding_generator = new QSvgWidget(":/rust_qt_binding_generator.svg"); rust_qt_binding_generator->setFixedSize(rust_qt_binding_generator->renderer()->defaultSize() / 4); QStatusBar* statusBar = new QStatusBar; statusBar->addPermanentWidget(rust_qt_binding_generator); statusBar->addPermanentWidget(box); return statusBar; } QWidget* createObjectTab(Model* model) { QWidget* view = new QWidget; Fibonacci* fibonacci = model->demo.fibonacci(); QLabel* label = new QLabel; label->setText(QCoreApplication::translate("main", "Calculate the nth Fibonacci number")); QLineEdit* input = new QLineEdit; input->setPlaceholderText("Your number"); input->setValidator(new QIntValidator(0, 100)); input->connect(input, &QLineEdit::textChanged, fibonacci, [fibonacci](const QString& text) { fibonacci->setInput(text.toInt()); }); fibonacci->connect(fibonacci, &Fibonacci::inputChanged, input, [input, fibonacci]() { input->setText(QString::number(fibonacci->input())); }); QLabel* result = new QLabel; fibonacci->connect(fibonacci, &Fibonacci::resultChanged, result, [result, fibonacci]() { result->setText(QCoreApplication::translate("main", "The Fibonacci number: ") + QString::number(fibonacci->result())); }); input->setText(QString::number(model->demo.fibonacci()->input())); QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(label); layout->addWidget(input); layout->addWidget(result); view->setLayout(layout); return view; } QWidget* createListTab(Model* model) { QTableView* view = new QTableView(); model->demo.fibonacciList()->setHeaderData(0, Qt::Horizontal, QCoreApplication::translate("main", "Row"), Qt::DisplayRole); model->demo.fibonacciList()->setHeaderData(1, Qt::Horizontal, QCoreApplication::translate("main", "Fibonacci number"), Qt::DisplayRole); view->setModel(model->demo.fibonacciList()); view->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); return view; } QWidget* createTreeTab(Model* model) { QTreeView* view = new QTreeView(); model->demo.fileSystemTree()->setHeaderData(0, Qt::Horizontal, QCoreApplication::translate("main", "Name"), Qt::DisplayRole); model->demo.fileSystemTree()->setHeaderData(1, Qt::Horizontal, QCoreApplication::translate("main", "Size"), Qt::DisplayRole); view->setUniformRowHeights(true); view->setSortingEnabled(true); view->setModel(&model->sortedFileSystem); view->header()->setSectionHidden(2, true); view->header()->setSectionHidden(3, true); view->header()->setSectionHidden(4, true); auto root = model->sortedFileSystem.index(0, 0); view->expand(root); view->sortByColumn(0, Qt::AscendingOrder); view->header()->setSectionResizeMode(QHeaderView::ResizeToContents); return view; } QWidget* createProcessesTab(Model* model) { QTreeView* view = new QTreeView(); view->setUniformRowHeights(true); view->setSortingEnabled(true); view->setModel(&model->sortedProcesses); // expand when the model is populated view->connect(&model->sortedProcesses, &QAbstractItemModel::rowsInserted, view, [view](const QModelIndex& index) { if (!index.isValid()) { view->expandAll(); } }); view->expandAll(); view->sortByColumn(0, Qt::AscendingOrder); return view; } #ifdef QT_CHARTS_LIB using namespace QtCharts; QWidget* createChartTab(Model* model) { QLineSeries *sin = new QLineSeries(); sin->setName("sin"); QVXYModelMapper *sinMapper = new QVXYModelMapper(sin); sinMapper->setXColumn(0); sinMapper->setYColumn(1); sinMapper->setSeries(sin); sinMapper->setModel(model->demo.timeSeries()); QLineSeries *cos = new QLineSeries(); cos->setName("cos"); QVXYModelMapper *cosMapper = new QVXYModelMapper(cos); cosMapper->setXColumn(0); cosMapper->setYColumn(2); cosMapper->setSeries(cos); cosMapper->setModel(model->demo.timeSeries()); QChart* chart = new QChart; chart->addSeries(sin); chart->addSeries(cos); QValueAxis *axisX = new QValueAxis; axisX->setLabelFormat("%.1f"); axisX->setTitleText("time [s]"); chart->addAxis(axisX, Qt::AlignBottom); sin->attachAxis(axisX); cos->attachAxis(axisX); QValueAxis *axisY = new QValueAxis; axisY->setLabelFormat("%.1f"); axisY->setTitleText("electric potential [V]"); chart->addAxis(axisY, Qt::AlignLeft); sin->attachAxis(axisY); cos->attachAxis(axisY); QWidget* view = new QWidget; QTableView* data = new QTableView; data->setModel(model->demo.timeSeries()); QChartView *chartView = new QChartView(chart); QHBoxLayout *layout = new QHBoxLayout; layout->addWidget(data); layout->addWidget(chartView); view->setLayout(layout); return view; } #endif QTabWidget* createTabs(Model* model) { QTabWidget* tabs = new QTabWidget(); tabs->addTab(createObjectTab(model), "object"); tabs->addTab(createListTab(model), "list"); tabs->addTab(createTreeTab(model), "tree"); const int procTab = tabs->addTab(createProcessesTab(model), "processes"); tabs->connect(tabs, &QTabWidget::currentChanged, model->demo.processes(), [model, procTab](int current) { model->demo.processes()->setActive(current == procTab); }); #ifdef QT_CHARTS_LIB tabs->addTab(createChartTab(model), "chart"); #endif return tabs; } void createMainWindow(Model* model, const QString& initialStyle, const QString& initialTab) { QMainWindow* main = new QMainWindow(); main->setMinimumSize(QSize(800, 640)); QComboBox* box = createStyleComboBox(model); QStatusBar* statusBar = createStatusBar(model, main, box, initialTab); main->setStatusBar(statusBar); QTabWidget* tabs = createTabs(model); main->setCentralWidget(tabs); main->show(); box->setCurrentText(initialStyle); for (int i = 0; i < tabs->count(); ++i) { if (tabs->tabText(i) == initialTab) { tabs->setCurrentIndex(i); } } } int main (int argc, char *argv[]) { QApplication app(argc, argv); app.setWindowIcon(QIcon(":/rust_qt_binding_generator.svg")); #ifdef QT_QUICK_LIB qmlRegisterType("org.qtproject.example", 1, 0, "SortFilterProxyModel"); qmlRegisterType("rust", 1, 0, "Fibonacci"); qmlRegisterType("rust", 1, 0, "FibonacciList"); #endif QCommandLineParser parser; parser.setApplicationDescription("Demo application for Qt and RUst"); parser.addHelpOption(); parser.addVersionOption(); // --initial-style QCommandLineOption initialStyleOption(QStringList() << "initial-style", QCoreApplication::translate("main", "Initial widget style."), QCoreApplication::translate("main", "style")); parser.addOption(initialStyleOption); // --initial-tab QCommandLineOption initialTabOption(QStringList() << "initial-tab", QCoreApplication::translate("main", "Initial tab."), QCoreApplication::translate("main", "tab")); parser.addOption(initialTabOption); parser.process(app); Model model; model.demo.fibonacci()->setInput(1); model.demo.fileSystemTree()->setPath("/"); model.sortedFileSystem.setSourceModel(model.demo.fileSystemTree()); model.sortedFileSystem.setDynamicSortFilter(true); model.sortedProcesses.setSourceModel(model.demo.processes()); model.sortedProcesses.setDynamicSortFilter(true); createMainWindow(&model, parser.value(initialStyleOption), parser.value(initialTabOption)); return app.exec(); } diff --git a/src/cpp.cpp b/src/cpp.cpp index 2341369..441209b 100644 --- a/src/cpp.cpp +++ b/src/cpp.cpp @@ -1,893 +1,913 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "structs.h" #include "cpp.h" #include "helper.h" #include template QString cppSetType(const T& p) { if (p.optional) { return "option_" + p.type.cppSetType; } return p.type.cppSetType; } QString upperInitial(const QString& name) { return name.left(1).toUpper() + name.mid(1); } QString lowerInitial(const QString& name) { return name.left(1).toLower() + name.mid(1); } QString writeProperty(const QString& name) { return "WRITE set" + upperInitial(name) + " "; } QString baseType(const Object& o) { if (o.type != ObjectType::Object) { return "QAbstractItemModel"; } return "QObject"; } QString cGetType(const BindingTypeProperties& type) { return type.name + "*, " + type.name.toLower() + "_set"; } bool modelIsWritable(const Object& o) { bool write = false; for (auto ip: o.itemProperties) { write |= ip.write; } return write; } void writeHeaderItemModel(QTextStream& h, const Object& o) { h << QString(R"( int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &index) const override; bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; bool canFetchMore(const QModelIndex &parent) const override; void fetchMore(const QModelIndex &parent) override; Qt::ItemFlags flags(const QModelIndex &index) const override; void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; QHash roleNames() const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override; )"); if (modelIsWritable(o)) { h << " bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;\n"; } for (auto ip: o.itemProperties) { if (o.type == ObjectType::List) { h << QString(" Q_INVOKABLE QVariant %1(int row) const;\n").arg(ip.name); if (ip.write) { h << QString(" Q_INVOKABLE bool set%1(int row, const QVariant& value);\n").arg(upperInitial(ip.name)); } } else { h << QString(" Q_INVOKABLE QVariant %1(const QModelIndex& index) const;\n").arg(ip.name); if (ip.write) { h << QString(" Q_INVOKABLE bool set%1(const QModelIndex& index, const QVariant& value);\n").arg(upperInitial(ip.name)); } } } h << R"( signals: // new data is ready to be made available to the model with fetchMore() void newDataReady(const QModelIndex &parent) const; private: QHash, QVariant> m_headerData; void initHeaderData(); )"; } bool isColumnWrite(const Object& o, int col) { for (auto ip: o.itemProperties) { if (ip.write && ip.roles.size() > col && ip.roles[col].size() > 0) { return true; } } return false; } void writeModelGetterSetter(QTextStream& cpp, const QString& index, const ItemProperty& ip, const Object& o) { const QString lcname(snakeCase(o.name)); QString idx = index; // getter if (o.type == ObjectType::List) { idx = ", row"; cpp << QString("QVariant %1::%2(int row) const\n{\n") .arg(o.name, ip.name); } else { cpp << QString("QVariant %1::%2(const QModelIndex& index) const\n{\n") .arg(o.name, ip.name); } cpp << " QVariant v;\n"; if (ip.type.name == "QString") { cpp << " QString s;\n"; cpp << QString(" %1_data_%2(m_d%4, &s, set_%3);\n") .arg(lcname, snakeCase(ip.name), ip.type.name.toLower(), idx); cpp << " if (!s.isNull()) v.setValue(s);\n"; } else if (ip.type.name == "QByteArray") { cpp << " QByteArray b;\n"; cpp << QString(" %1_data_%2(m_d%4, &b, set_%3);\n") .arg(lcname, snakeCase(ip.name), ip.type.name.toLower(), idx); cpp << " if (!b.isNull()) v.setValue(b);\n"; } else { cpp << QString(" v = %1_data_%2(m_d%3);\n") .arg(lcname, snakeCase(ip.name), idx); } cpp << " return v;\n"; cpp << "}\n\n"; if (!ip.write) { return; } // setter if (o.type == ObjectType::List) { idx = ", row"; cpp << QString("bool %1::set%2(int row, const QVariant& value)\n{\n") .arg(o.name, upperInitial(ip.name)); } else { cpp << QString("bool %1::set%2(const QModelIndex& index, const QVariant& value)\n{\n") .arg(o.name, upperInitial(ip.name)); } cpp << " bool set = false;\n"; if (ip.optional) { QString test = "!value.isValid()"; if (ip.type.isComplex()) { test += " || value.isNull()"; } cpp << " if (" << test << ") {\n"; cpp << QString(" set = %1_set_data_%2_none(m_d%3);") .arg(lcname, snakeCase(ip.name), idx) << endl; cpp << " } else\n"; } QString val = QString("value.value<%1>()").arg(ip.type.name); cpp << QString(" set = %1_set_data_%2(m_d%3, %4);") .arg(lcname, snakeCase(ip.name), idx, val) << endl; if (o.type == ObjectType::List) { cpp << R"( if (set) { QModelIndex index = createIndex(row, 0, row); emit dataChanged(index, index); } return set; } )"; } else { cpp << R"( if (set) { emit dataChanged(index, index); } return set; } )"; } } void writeCppModel(QTextStream& cpp, const Object& o) { const QString lcname(snakeCase(o.name)); QString indexDecl = ", int"; QString index = ", index.row()"; if (o.type == ObjectType::Tree) { indexDecl = ", quintptr"; index = ", index.internalId()"; } cpp << "extern \"C\" {\n"; for (auto ip: o.itemProperties) { if (ip.type.isComplex()) { cpp << QString(" void %2_data_%3(const %1::Private*%5, %4);\n") .arg(o.name, lcname, snakeCase(ip.name), cGetType(ip.type), indexDecl); } else { cpp << QString(" %4 %2_data_%3(const %1::Private*%5);\n") .arg(o.name, lcname, snakeCase(ip.name), cppSetType(ip), indexDecl); } if (ip.write) { cpp << QString(" bool %2_set_data_%3(%1::Private*%5, %4);") .arg(o.name, lcname, snakeCase(ip.name), ip.type.cSetType, indexDecl) << endl; if (ip.optional) { cpp << QString(" bool %2_set_data_%3_none(%1::Private*%4);") .arg(o.name, lcname, snakeCase(ip.name), indexDecl) << endl; } } } cpp << QString(" void %2_sort(%1::Private*, unsigned char column, Qt::SortOrder order = Qt::AscendingOrder);\n").arg(o.name, lcname); if (o.type == ObjectType::List) { cpp << QString(R"( int %2_row_count(const %1::Private*); bool %2_can_fetch_more(const %1::Private*); void %2_fetch_more(%1::Private*); } int %1::columnCount(const QModelIndex &parent) const { return (parent.isValid()) ? 0 : %3; } bool %1::hasChildren(const QModelIndex &parent) const { return rowCount(parent) > 0; } int %1::rowCount(const QModelIndex &parent) const { return (parent.isValid()) ? 0 : %2_row_count(m_d); } QModelIndex %1::index(int row, int column, const QModelIndex &parent) const { if (!parent.isValid() && row >= 0 && row < rowCount(parent) && column >= 0 && column < %3) { return createIndex(row, column, (quintptr)row); } return QModelIndex(); } QModelIndex %1::parent(const QModelIndex &) const { return QModelIndex(); } bool %1::canFetchMore(const QModelIndex &parent) const { return (parent.isValid()) ? 0 : %2_can_fetch_more(m_d); } void %1::fetchMore(const QModelIndex &parent) { if (!parent.isValid()) { %2_fetch_more(m_d); } } )").arg(o.name, lcname, QString::number(o.columnCount)); } else { cpp << QString(R"( int %2_row_count(const %1::Private*, quintptr, bool); bool %2_can_fetch_more(const %1::Private*, quintptr, bool); void %2_fetch_more(%1::Private*, quintptr, bool); quintptr %2_index(const %1::Private*, quintptr, bool, int); qmodelindex_t %2_parent(const %1::Private*, quintptr); int %2_row(const %1::Private*, quintptr); } int %1::columnCount(const QModelIndex &) const { return %3; } bool %1::hasChildren(const QModelIndex &parent) const { return rowCount(parent) > 0; } int %1::rowCount(const QModelIndex &parent) const { if (parent.isValid() && parent.column() != 0) { return 0; } return %2_row_count(m_d, parent.internalId(), parent.isValid()); } QModelIndex %1::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column < 0 || column >= %3) { return QModelIndex(); } if (parent.isValid() && parent.column() != 0) { return QModelIndex(); } if (row >= rowCount(parent)) { return QModelIndex(); } const quintptr id = %2_index(m_d, parent.internalId(), parent.isValid(), row); return createIndex(row, column, id); } QModelIndex %1::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } const qmodelindex_t parent = %2_parent(m_d, index.internalId()); return parent.row >= 0 ?createIndex(parent.row, 0, parent.id) :QModelIndex(); } bool %1::canFetchMore(const QModelIndex &parent) const { if (parent.isValid() && parent.column() != 0) { return false; } return %2_can_fetch_more(m_d, parent.internalId(), parent.isValid()); } void %1::fetchMore(const QModelIndex &parent) { %2_fetch_more(m_d, parent.internalId(), parent.isValid()); } )").arg(o.name, lcname, QString::number(o.columnCount)); } cpp << QString(R"( void %1::sort(int column, Qt::SortOrder order) { %2_sort(m_d, column, order); } Qt::ItemFlags %1::flags(const QModelIndex &i) const { auto flags = QAbstractItemModel::flags(i); )").arg(o.name, lcname); for (int col = 0; col < o.columnCount; ++col) { if (isColumnWrite(o, col)) { cpp << " if (i.column() == " << col << ") {\n"; cpp << " flags |= Qt::ItemIsEditable;\n }\n"; } } cpp << " return flags;\n}\n\n"; for (auto ip: o.itemProperties) { writeModelGetterSetter(cpp, index, ip, o); } cpp << QString(R"(QVariant %1::data(const QModelIndex &index, int role) const { Q_ASSERT(rowCount(index.parent()) > index.row()); switch (index.column()) { )").arg(o.name); auto metaRoles = QMetaEnum::fromType(); for (int col = 0; col < o.columnCount; ++col) { cpp << QString(" case %1:\n").arg(col); cpp << QString(" switch (role) {\n"); for (int i = 0; i < o.itemProperties.size(); ++i) { auto ip = o.itemProperties[i]; auto roles = ip.roles.value(col); if (col > 0 && roles.size() == 0) { continue; } for (auto role: roles) { cpp << QString(" case Qt::%1:\n").arg(metaRoles.valueToKey(role)); } cpp << QString(" case Qt::UserRole + %1:\n").arg(i); if (o.type == ObjectType::List) { cpp << QString(" return %1(index.row());\n").arg(ip.name); } else { cpp << QString(" return %1(index);\n").arg(ip.name); } } cpp << " }\n"; } cpp << " }\n return QVariant();\n}\n\n"; cpp << "QHash " << o.name << "::roleNames() const {\n"; cpp << " QHash names = QAbstractItemModel::roleNames();\n"; for (int i = 0; i < o.itemProperties.size(); ++i) { auto ip = o.itemProperties[i]; cpp << " names.insert(Qt::UserRole + " << i << ", \"" << ip.name << "\");\n"; } cpp << " return names;\n"; cpp << QString(R"(} QVariant %1::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal) { return QVariant(); } return m_headerData.value(qMakePair(section, (Qt::ItemDataRole)role), role == Qt::DisplayRole ?QString::number(section + 1) :QVariant()); } bool %1::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (orientation != Qt::Horizontal) { return false; } m_headerData.insert(qMakePair(section, (Qt::ItemDataRole)role), value); return true; } )").arg(o.name); if (modelIsWritable(o)) { cpp << QString("bool %1::setData(const QModelIndex &index, const QVariant &value, int role)\n{\n").arg(o.name); for (int col = 0; col < o.columnCount; ++col) { if (!isColumnWrite(o, col)) { continue; } cpp << " if (index.column() == " << col << ") {\n"; for (int i = 0; i < o.itemProperties.size(); ++i) { auto ip = o.itemProperties[i]; if (!ip.write) { continue; } auto roles = ip.roles.value(col); if (col > 0 && roles.size() == 0) { continue; } cpp << " if ("; for (auto role: roles) { cpp << QString("role == Qt::%1 || ").arg(metaRoles.valueToKey(role)); } cpp << "role == Qt::UserRole + " << i << ") {\n"; if (o.type == ObjectType::List) { cpp << QString(" return set%1(index.row(), value);\n") .arg(upperInitial(ip.name)); } else { cpp << QString(" return set%1(index, value);\n") .arg(upperInitial(ip.name)); } cpp << " }\n"; } cpp << " }\n"; } cpp << " return false;\n}\n\n"; } } void writeHeaderObject(QTextStream& h, const Object& o, const Configuration& conf) { h << QString(R"( class %1 : public %3 { Q_OBJEC%2 )").arg(o.name, "T", baseType(o)); for (auto object: conf.objects) { if (object.containsObject() && o.name != object.name) { h << " friend class " << object.name << ";\n"; } } h << R"(public: class Private; private: )"; for (auto p: o.properties) { if (p.type.type == BindingType::Object) { h << " " << p.type.name << "* const m_" << p.name << ";\n"; } } h << R"( Private * m_d; bool m_ownsPrivate; )"; for (auto p: o.properties) { bool obj = p.type.type == BindingType::Object; h << QString(" Q_PROPERTY(%1 %2 READ %2 %3NOTIFY %2Changed FINAL)") .arg(p.type.name + (obj ?"*" :""), p.name, p.write ? writeProperty(p.name) :"") << endl; } h << QString(R"( explicit %1(bool owned, QObject *parent); public: explicit %1(QObject *parent = nullptr); ~%1(); )").arg(o.name); for (auto p: o.properties) { if (p.type.type == BindingType::Object) { h << " const " << p.type.name << "* " << p.name << "() const;" << endl; h << " " << p.type.name << "* " << p.name << "();" << endl; } else { h << " " << p.type.name << " " << p.name << "() const;" << endl; if (p.write) { h << " void set" << upperInitial(p.name) << "(" << p.type.cppSetType << " v);" << endl; } } } if (baseType(o) == "QAbstractItemModel") { writeHeaderItemModel(h, o); } h << "signals:" << endl; for (auto p: o.properties) { h << " void " << p.name << "Changed();" << endl; } h << "};" << endl; } void constructorArgsDecl(QTextStream& cpp, const Object& o, const Configuration& conf) { cpp << o.name << "*"; for (auto p: o.properties) { if (p.type.type == BindingType::Object) { cpp << QString(", "); constructorArgsDecl(cpp, conf.findObject(p.type.name), conf); } else { cpp << QString(", void (*)(%1*)").arg(o.name); } } if (o.type == ObjectType::List) { cpp << QString(R"(, void (*)(const %1*), void (*)(%1*, quintptr, quintptr), void (*)(%1*), void (*)(%1*), void (*)(%1*, int, int), void (*)(%1*), void (*)(%1*, int, int), void (*)(%1*))").arg(o.name); } if (o.type == ObjectType::Tree) { cpp << QString(R"(, void (*)(const %1*, quintptr, bool), void (*)(%1*, quintptr, quintptr), void (*)(%1*), void (*)(%1*), void (*)(%1*, option_quintptr, int, int), void (*)(%1*), void (*)(%1*, option_quintptr, int, int), void (*)(%1*))").arg(o.name); } } QString changedF(const Object& o, const Property& p) { return lowerInitial(o.name) + upperInitial(p.name) + "Changed"; } void constructorArgs(QTextStream& cpp, const QString& prefix, const Object& o, const Configuration& conf) { const QString lcname(snakeCase(o.name)); for (const Property& p: o.properties) { if (p.type.type == BindingType::Object) { cpp << ", " << prefix << "m_" << p.name; constructorArgs(cpp, "m_" + p.name + "->", conf.findObject(p.type.name), conf); } else { cpp << ",\n " << changedF(o, p); } } if (o.type == ObjectType::List) { cpp << QString(R"(, [](const %1* o) { emit o->newDataReady(QModelIndex()); }, [](%1* o, quintptr first, quintptr last) { o->dataChanged(o->createIndex(first, 0, first), o->createIndex(last, %2, last)); }, [](%1* o) { o->beginResetModel(); }, [](%1* o) { o->endResetModel(); }, [](%1* o, int first, int last) { o->beginInsertRows(QModelIndex(), first, last); }, [](%1* o) { o->endInsertRows(); }, [](%1* o, int first, int last) { o->beginRemoveRows(QModelIndex(), first, last); }, [](%1* o) { o->endRemoveRows(); } )").arg(o.name, QString::number(o.columnCount - 1)); } if (o.type == ObjectType::Tree) { cpp << QString(R"(, [](const %1* o, quintptr id, bool valid) { if (valid) { int row = %2_row(o->m_d, id); emit o->newDataReady(o->createIndex(row, 0, id)); } else { emit o->newDataReady(QModelIndex()); } }, [](%1* o, quintptr first, quintptr last) { quintptr frow = %2_row(o->m_d, first); quintptr lrow = %2_row(o->m_d, first); o->dataChanged(o->createIndex(frow, 0, first), o->createIndex(lrow, %3, last)); }, [](%1* o) { o->beginResetModel(); }, [](%1* o) { o->endResetModel(); }, [](%1* o, option_quintptr id, int first, int last) { if (id.some) { int row = %2_row(o->m_d, id.value); o->beginInsertRows(o->createIndex(row, 0, id.value), first, last); } else { o->beginInsertRows(QModelIndex(), first, last); } }, [](%1* o) { o->endInsertRows(); }, [](%1* o, option_quintptr id, int first, int last) { if (id.some) { int row = %2_row(o->m_d, id.value); o->beginRemoveRows(o->createIndex(row, 0, id.value), first, last); } else { o->beginRemoveRows(QModelIndex(), first, last); } }, [](%1* o) { o->endRemoveRows(); } )").arg(o.name, lcname, QString::number(o.columnCount - 1)); } } void writeObjectCDecl(QTextStream& cpp, const Object& o, const Configuration& conf) { const QString lcname(snakeCase(o.name)); cpp << QString(" %1::Private* %2_new(").arg(o.name, lcname); constructorArgsDecl(cpp, o, conf); cpp << ");" << endl; cpp << QString(" void %2_free(%1::Private*);").arg(o.name, lcname) << endl; for (const Property& p: o.properties) { const QString base = QString("%1_%2").arg(lcname, snakeCase(p.name)); if (p.type.type == BindingType::Object) { cpp << QString(" %3::Private* %2_get(const %1::Private*);") .arg(o.name, base, p.type.name) << endl; } else if (p.type.isComplex()) { cpp << QString(" void %2_get(const %1::Private*, %3);") .arg(o.name, base, cGetType(p.type)) << endl; } else { cpp << QString(" %3 %2_get(const %1::Private*);") .arg(o.name, base, p.type.name) << endl; } if (p.write) { cpp << QString(" void %2_set(%1::Private*, %3);") .arg(o.name, base, p.type.cSetType) << endl; if (p.optional) { cpp << QString(" void %2_set_none(%1::Private*);") .arg(o.name, base) << endl; } } } } void initializeMembersEmpty(QTextStream& cpp, const Object& o, const Configuration& conf) { for (const Property& p: o.properties) { if (p.type.type == BindingType::Object) { initializeMembersEmpty(cpp, conf.findObject(p.type.name), conf); cpp << QString(" %1m_%2(new %3(false, this)),\n") .arg(p.name, p.type.name); } } } void initializeMembersZero(QTextStream& cpp, const Object& o) { for (const Property& p: o.properties) { if (p.type.type == BindingType::Object) { cpp << QString(" m_%1(new %2(false, this)),\n") .arg(p.name, p.type.name); } } } void initializeMembers(QTextStream& cpp, const QString& prefix, const Object& o, const Configuration& conf) { for (const Property& p: o.properties) { if (p.type.type == BindingType::Object) { cpp << QString(" %1m_%2->m_d = %3_%4_get(%1m_d);\n") .arg(prefix, p.name, snakeCase(o.name), snakeCase(p.name)); initializeMembers(cpp, "m_" + p.name + "->", conf.findObject(p.type.name), conf); } } } void connect(QTextStream& cpp, const QString& d, const Object& o, const Configuration& conf) { for (auto p: o.properties) { if (p.type.type == BindingType::Object) { connect(cpp, d + "->m_" + p.name, conf.findObject(p.type.name), conf); } } if (o.type != ObjectType::Object) { cpp << QString(R"( connect(%2, &%1::newDataReady, %2, [this](const QModelIndex& i) { %2->fetchMore(i); }, Qt::QueuedConnection); )").arg(o.name, d); } } void writeCppObject(QTextStream& cpp, const Object& o, const Configuration& conf) { const QString lcname(snakeCase(o.name)); cpp << QString("%1::%1(bool /*owned*/, QObject *parent):\n %2(parent),") .arg(o.name, baseType(o)) << endl; for (const Property& p: o.properties) { if (p.type.type == BindingType::Object) { cpp << QString(" m_%1(new %2(false, this)),\n") .arg(p.name, p.type.name); } } cpp << " m_d(0),\n m_ownsPrivate(false)\n{\n"; if (o.type != ObjectType::Object) { cpp << " initHeaderData();\n"; } cpp << QString("}\n\n%1::%1(QObject *parent):\n %2(parent),") .arg(o.name, baseType(o)) << endl; initializeMembersZero(cpp, o); cpp << QString(" m_d(%1_new(this").arg(lcname); constructorArgs(cpp, "", o, conf); cpp << ")),\n m_ownsPrivate(true)\n{\n"; initializeMembers(cpp, "", o, conf); connect(cpp, "this", o, conf); if (o.type != ObjectType::Object) { cpp << " initHeaderData();\n"; } cpp << QString(R"(} %1::~%1() { if (m_ownsPrivate) { %2_free(m_d); } } )").arg(o.name, lcname); if (o.type != ObjectType::Object) { cpp << QString("void %1::initHeaderData() {\n").arg(o.name); for (int col = 0; col < o.columnCount; ++col) { for (auto ip: o.itemProperties) { auto roles = ip.roles.value(col); if (roles.contains(Qt::DisplayRole)) { cpp << QString(" m_headerData.insert(qMakePair(%1, Qt::DisplayRole), QVariant(\"%2\"));\n").arg(QString::number(col), ip.name); } } } cpp << "}\n"; } for (const Property& p: o.properties) { const QString base = QString("%1_%2").arg(lcname, snakeCase(p.name)); if (p.type.type == BindingType::Object) { cpp << QString(R"(const %3* %1::%2() const { return m_%2; } %3* %1::%2() { return m_%2; } )").arg(o.name, p.name, p.type.name); } else if (p.type.isComplex()) { cpp << QString("%3 %1::%2() const\n{\n").arg(o.name, p.name, p.type.name); cpp << " " << p.type.name << " v;\n"; cpp << " " << base << "_get(m_d, &v, set_" << p.type.name.toLower() << ");\n"; cpp << " return v;\n}\n"; } else { cpp << QString("%3 %1::%2() const\n{\n").arg(o.name, p.name, p.type.name); cpp << QString(" return %1_get(m_d);\n}\n").arg(base); } if (p.write) { cpp << "void " << o.name << "::set" << upperInitial(p.name) << "(" << p.type.cppSetType << " v) {" << endl; if (p.optional) { cpp << QString(" if (v.isNull()) {") << endl; cpp << QString(" %1_set_none(m_d);").arg(base) << endl; cpp << QString(" } else {") << endl; cpp << QString(" %1_set(m_d, v);").arg(base) << endl; cpp << QString(" }") << endl; } else { cpp << QString(" %1_set(m_d, v);").arg(base) << endl; } cpp << "}" << endl; } } } void writeHeader(const Configuration& conf) { DifferentFileWriter w(conf.hFile.absoluteFilePath()); QTextStream h(&w.buffer); const QString guard(conf.hFile.fileName().replace('.', '_').toUpper()); h << QString(R"(/* generated by rust_qt_binding_generator */ #ifndef %1 #define %1 #include #include )").arg(guard); for (auto object: conf.objects) { h << "class " << object.name << ";\n"; } for (auto object: conf.objects) { writeHeaderObject(h, object, conf); } h << QString("#endif // %1\n").arg(guard); } void writeCpp(const Configuration& conf) { DifferentFileWriter w(conf.cppFile.absoluteFilePath()); QTextStream cpp(&w.buffer); cpp << QString(R"(/* generated by rust_qt_binding_generator */ #include "%1" namespace { )").arg(conf.hFile.fileName()); for (auto option: conf.optionalTypes()) { cpp << QString(R"( struct option_%1 { public: %1 value; bool some; operator QVariant() const { if (some) { return QVariant(value); } return QVariant(); } }; )").arg(option); } if (conf.types().contains("QString")) { cpp << R"( struct qstring_t { private: const void* data; int len; public: qstring_t(const QString& v): data(static_cast(v.utf16())), len(v.size()) { } operator QString() const { return QString::fromUtf8(static_cast(data), len); } }; typedef void (*qstring_set)(QString*, qstring_t*); void set_qstring(QString* v, qstring_t* val) { *v = *val; } )"; } if (conf.types().contains("QByteArray")) { cpp << R"( struct qbytearray_t { private: const char* data; int len; public: qbytearray_t(const QByteArray& v): data(v.data()), len(v.size()) { } operator QByteArray() const { return QByteArray(data, len); } }; typedef void (*qbytearray_set)(QByteArray*, qbytearray_t*); void set_qbytearray(QByteArray* v, qbytearray_t* val) { *v = *val; } )"; } if (conf.hasListOrTree()) { cpp << R"( struct qmodelindex_t { int row; quintptr id; }; )"; } for (auto o: conf.objects) { for (auto p: o.properties) { if (p.type.type == BindingType::Object) { continue; } cpp << " inline void " << changedF(o, p) << "(" << o.name << "* o)\n"; cpp << " {\n emit o->" << p.name << "Changed();\n }\n"; } } cpp << "}\n"; for (auto object: conf.objects) { if (object.type != ObjectType::Object) { writeCppModel(cpp, object); } cpp << "extern \"C\" {\n"; writeObjectCDecl(cpp, object, conf); cpp << "};\n" << endl; } for (auto object: conf.objects) { writeCppObject(cpp, object, conf); } } diff --git a/src/cpp.h b/src/cpp.h index 68750ce..1a27c8d 100644 --- a/src/cpp.h +++ b/src/cpp.h @@ -1,3 +1,23 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + struct Configuration; void writeHeader(const Configuration& conf); void writeCpp(const Configuration& conf); diff --git a/src/helper.cpp b/src/helper.cpp index b432776..a194a22 100644 --- a/src/helper.cpp +++ b/src/helper.cpp @@ -1,3 +1,23 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include QTextStream err(stderr); diff --git a/src/helper.h b/src/helper.h index 10ca642..508c480 100644 --- a/src/helper.h +++ b/src/helper.h @@ -1,46 +1,66 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include #include #include #include inline QString snakeCase(const QString& name) { return name.left(1).toLower() + name.mid(1) .replace(QRegExp("([A-Z])"), "_\\1").toLower(); } // Only write a file if it is different class DifferentFileWriter { public: const QString path; QByteArray buffer; bool overwrite; DifferentFileWriter(const QString& p, bool o = true) :path(p), overwrite(o) { } ~DifferentFileWriter() { const QByteArray old = read(); if (old != buffer && (old.isNull() || overwrite)) { write(); } } QByteArray read() const { QByteArray content; QFile file(path); if (file.open(QIODevice::ReadOnly)) { content = file.readAll(); } return content; } void write() const { QFile file(path); if (!file.open(QIODevice::WriteOnly)) { QTextStream err(stderr); err << QCoreApplication::translate("main", "Cannot write %1.\n").arg(file.fileName()); err.flush(); exit(1); } file.write(buffer); file.close(); } }; diff --git a/src/main.cpp b/src/main.cpp index 3d8f689..fd20700 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,48 +1,68 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "parseJson.h" #include "cpp.h" #include "rust.h" #include "helper.h" #include int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QCoreApplication::setApplicationName(argv[0]); QCoreApplication::setApplicationVersion("0.1"); QCommandLineParser parser; parser.setApplicationDescription("Generates bindings between Qt and Rust"); parser.addHelpOption(); parser.addVersionOption(); parser.addPositionalArgument("configuration", QCoreApplication::translate("main", "Configuration file(s)")); // A boolean option (--overwrite-implementation) QCommandLineOption overwriteOption(QStringList() << "overwrite-implementation", QCoreApplication::translate("main", "Overwrite existing implementation.")); parser.addOption(overwriteOption); parser.process(app); const QStringList args = parser.positionalArguments(); if (args.isEmpty()) { QTextStream err(stderr); err << QCoreApplication::translate("main", "Configuration file is missing.\n"); return 1; } for (auto path: args) { const QString configurationFile(path); Configuration configuration = parseConfiguration(configurationFile); configuration.overwriteImplementation = parser.isSet(overwriteOption); writeHeader(configuration); writeCpp(configuration); writeRustInterface(configuration); writeRustImplementation(configuration); } return 0; } diff --git a/src/parseJson.cpp b/src/parseJson.cpp index e4d2f1a..09af681 100644 --- a/src/parseJson.cpp +++ b/src/parseJson.cpp @@ -1,231 +1,251 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "parseJson.h" #include "helper.h" #include #include #include #include BindingTypeProperties simpleType(BindingType type, const char* name, const char* init) { return { .type = type, .name = name, .cppSetType = name, .cSetType = name, .rustType = name, .rustTypeInit = init }; } QList& bindingTypeProperties() { static QList p; if (p.empty()) { QList f; f.append(simpleType(BindingType::Bool, "bool", "true")); f.append({ .type = BindingType::UChar, .name = "quint8", .cppSetType = "quint8", .cSetType = "quint8", .rustType = "u8", .rustTypeInit = "0", }); f.append({ .type = BindingType::Int, .name = "qint32", .cppSetType = "qint32", .cSetType = "qint32", .rustType = "i32", .rustTypeInit = "0", }); f.append({ .type = BindingType::UInt, .name = "quint32", .cppSetType = "uint", .cSetType = "uint", .rustType = "u32", .rustTypeInit = "0" }); f.append({ .type = BindingType::ULongLong, .name = "quint64", .cppSetType = "quint64", .cSetType = "quint64", .rustType = "u64", .rustTypeInit = "0" }); f.append({ .type = BindingType::Float, .name = "float", .cppSetType = "float", .cSetType = "float", .rustType = "f32", .rustTypeInit = "0.0" }); f.append({ .type = BindingType::QString, .name = "QString", .cppSetType = "const QString&", .cSetType = "qstring_t", .rustType = "String", .rustTypeInit = "String::new()" }); f.append({ .type = BindingType::QByteArray, .name = "QByteArray", .cppSetType = "const QByteArray&", .cSetType = "qbytearray_t", .rustType = "Vec", .rustTypeInit = "Vec::new()" }); p = f; } return p; } BindingTypeProperties parseBindingType(const QString& value) { for (auto type: bindingTypeProperties()) { if (value == type.name) { return type; } } QTextStream err(stderr); err << QCoreApplication::translate("main", "'%1' is not a supported type. Use one of\n").arg(value); for (auto i: bindingTypeProperties()) { if (i.name == i.rustType) { err << " " << i.rustType << "\n"; } else { err << " " << i.name << " (" << i.rustType << ")\n"; } } err.flush(); exit(1); } Property parseProperty(const QString& name, const QJsonObject& json) { Property p; p.name = name; p.type = parseBindingType(json.value("type").toString()); p.write = json.value("write").toBool(); p.optional = json.value("optional").toBool(); p.rustByValue = json.value("rustByValue").toBool(); return p; } Qt::ItemDataRole parseItemDataRole(const QString& s) { const QString name = s.left(1).toUpper() + s.mid(1) + "Role"; int v = QMetaEnum::fromType() .keyToValue(name.toUtf8()); if (v >= 0) { return (Qt::ItemDataRole)v; } QTextStream err(stderr); err << QCoreApplication::translate("main", "%1 is not a valid role name.\n").arg(s); err.flush(); exit(1); } ItemProperty parseItemProperty(const QString& name, const QJsonObject& json) { ItemProperty ip; ip.name = name; ip.type = parseBindingType(json.value("type").toString()); ip.write = json.value("write").toBool(); ip.optional = json.value("optional").toBool(); ip.rustByValue = json.value("rustByValue").toBool(); QJsonArray roles = json.value("roles").toArray(); for (auto r: roles) { QList l; for (auto v: r.toArray()) { l.append(parseItemDataRole(v.toString())); } ip.roles.append(l); } return ip; } Object parseObject(const QString& name, const QJsonObject& json) { Object o; o.name = name; QString type = json.value("type").toString(); if (type == "List") { o.type = ObjectType::List; } else if (type == "Tree") { o.type = ObjectType::Tree; } else { o.type = ObjectType::Object; } const QJsonObject& properties = json.value("properties").toObject(); for (const QString& key: properties.keys()) { o.properties.append(parseProperty(key, properties[key].toObject())); } QTextStream err(stderr); const QJsonObject& itemProperties = json.value("itemProperties").toObject(); if (o.type != ObjectType::Object && itemProperties.size() == 0) { err << QCoreApplication::translate("main", "No item properties are defined for %1.\n").arg(o.name); err.flush(); exit(1); } else if (o.type == ObjectType::Object && itemProperties.size() > 0) { err << QCoreApplication::translate("main", "%1 is an Object and should not have itemProperties.").arg(o.name); err.flush(); exit(1); } o.columnCount = 0; for (const QString& key: itemProperties.keys()) { ItemProperty p = parseItemProperty(key, itemProperties[key].toObject()); o.columnCount = qMax(o.columnCount, p.roles.size()); o.itemProperties.append(p); } return o; } Configuration parseConfiguration(const QString& path) { QFile configurationFile(path); const QDir base = QFileInfo(configurationFile).dir(); QTextStream err(stderr); if (!configurationFile.open(QIODevice::ReadOnly)) { err << QCoreApplication::translate("main", "Cannot read %1.\n").arg(configurationFile.fileName()); err.flush(); exit(1); } const QByteArray data(configurationFile.readAll()); QJsonParseError error; const QJsonDocument doc(QJsonDocument::fromJson(data, &error)); if (error.error != QJsonParseError::NoError) { err << error.errorString(); err.flush(); exit(1); } const QJsonObject o = doc.object(); Configuration c; c.cppFile = QFileInfo(base, o.value("cppFile").toString()); QDir(c.cppFile.dir()).mkpath("."); c.hFile = QFileInfo(c.cppFile.dir(), c.cppFile.completeBaseName() + ".h"); const QJsonObject& object = o.value("objects").toObject(); for (const QString& key: object.keys()) { bindingTypeProperties().append({ .type = BindingType::Object, .name = key, .cppSetType = key, .cSetType = key, .rustType = key, .rustTypeInit = "", }); } for (const QString& key: object.keys()) { Object o = parseObject(key, object[key].toObject()); c.objects.append(o); } const QJsonObject rust = o.value("rust").toObject(); c.rustdir = QDir(base.filePath(rust.value("dir").toString())); c.interfaceModule = rust.value("interfaceModule").toString(); c.implementationModule = rust.value("implementationModule").toString(); return c; } diff --git a/src/parseJson.h b/src/parseJson.h index 7f23d38..42e4d0c 100644 --- a/src/parseJson.h +++ b/src/parseJson.h @@ -1,3 +1,23 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "structs.h" Configuration parseConfiguration(const QString& path); diff --git a/src/rust.cpp b/src/rust.cpp index 1ed4730..2bd0030 100644 --- a/src/rust.cpp +++ b/src/rust.cpp @@ -1,885 +1,905 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "structs.h" #include "helper.h" template QString rustType(const T& p) { if (p.optional) { return "Option<" + p.type.rustType + ">"; } return p.type.rustType; } template QString rustReturnType(const T& p) { QString type = p.type.rustType; if (type == "String" && !p.rustByValue) { type = "str"; } if (type == "Vec" && !p.rustByValue) { type = "[u8]"; } if (p.type.isComplex() && !p.rustByValue) { type = "&" + type; } if (p.optional) { return "Option<" + type + ">"; } return type; } template QString rustCType(const T& p) { if (p.optional) { return "COption<" + p.type.rustType + ">"; } return p.type.rustType; } template QString rustTypeInit(const T& p) { if (p.optional) { return "None"; } return p.type.rustTypeInit; } void rConstructorArgsDecl(QTextStream& r, const QString& name, const Object& o, const Configuration& conf) { r << QString(" %2: *mut %1QObject").arg(o.name, snakeCase(name)); for (const Property& p: o.properties) { if (p.type.type == BindingType::Object) { r << QString(",\n"); rConstructorArgsDecl(r, p.name, conf.findObject(p.type.name), conf); } else { r << QString(",\n %2_changed: fn(*const %1QObject)") .arg(o.name, snakeCase(p.name)); } } if (o.type == ObjectType::List) { r << QString(",\n %2_new_data_ready: fn(*const %1QObject)") .arg(o.name, snakeCase(o.name)); } else if (o.type == ObjectType::Tree) { r << QString(",\n %2_new_data_ready: fn(*const %1QObject, item: usize, valid: bool)") .arg(o.name, snakeCase(o.name)); } if (o.type != ObjectType::Object) { QString indexDecl; if (o.type == ObjectType::Tree) { indexDecl = " item: usize, valid: bool,"; } r << QString(R"(, %3_data_changed: fn(*const %1QObject, usize, usize), %3_begin_reset_model: fn(*const %1QObject), %3_end_reset_model: fn(*const %1QObject), %3_begin_insert_rows: fn(*const %1QObject,%2 usize, usize), %3_end_insert_rows: fn(*const %1QObject), %3_begin_remove_rows: fn(*const %1QObject,%2 usize, usize), %3_end_remove_rows: fn(*const %1QObject))").arg(o.name, indexDecl, snakeCase(o.name)); } } void rConstructorArgs(QTextStream& r, const QString& name, const Object& o, const Configuration& conf) { const QString lcname(snakeCase(o.name)); for (const Property& p: o.properties) { if (p.type.type == BindingType::Object) { rConstructorArgs(r, p.name, conf.findObject(p.type.name), conf); } } r << QString(R"( let %2_emit = %1Emitter { qobject: Arc::new(Mutex::new(%2)), )").arg(o.name, snakeCase(name)); for (const Property& p: o.properties) { if (p.type.type == BindingType::Object) continue; r << QString(" %1_changed: %1_changed,\n").arg(snakeCase(p.name)); } if (o.type != ObjectType::Object) { r << QString(" new_data_ready: %1_new_data_ready,\n") .arg(snakeCase(o.name)); } QString model = ""; if (o.type != ObjectType::Object) { const QString type = o.type == ObjectType::List ? "List" : "Tree"; model = ", model"; r << QString(R"( }; let model = %1%2 { qobject: %3, data_changed: %4_data_changed, begin_reset_model: %4_begin_reset_model, end_reset_model: %4_end_reset_model, begin_insert_rows: %4_begin_insert_rows, end_insert_rows: %4_end_insert_rows, begin_remove_rows: %4_begin_remove_rows, end_remove_rows: %4_end_remove_rows, )").arg(o.name, type, snakeCase(name), snakeCase(o.name)); } r << QString(" };\n let d_%3 = %1::new(%3_emit%2") .arg(o.name, model, snakeCase(name)); for (const Property& p: o.properties) { if (p.type.type == BindingType::Object) { r << ",\n d_" << snakeCase(p.name); } } r << ");\n"; } void writeRustInterfaceObject(QTextStream& r, const Object& o, const Configuration& conf) { const QString lcname(snakeCase(o.name)); r << QString(R"( pub struct %1QObject {} #[derive(Clone)] pub struct %1Emitter { qobject: Arc>, )").arg(o.name); for (const Property& p: o.properties) { if (p.type.type == BindingType::Object) { continue; } r << QString(" %2_changed: fn(*const %1QObject),\n") .arg(o.name, snakeCase(p.name)); } if (o.type == ObjectType::List) { r << QString(" new_data_ready: fn(*const %1QObject),\n") .arg(o.name); } else if (o.type == ObjectType::Tree) { r << QString(" new_data_ready: fn(*const %1QObject, item: usize, valid: bool),\n") .arg(o.name); } r << QString(R"(} unsafe impl Send for %1Emitter {} impl %1Emitter { fn clear(&self) { *self.qobject.lock().unwrap() = null(); } )").arg(o.name); for (const Property& p: o.properties) { if (p.type.type == BindingType::Object) { continue; } r << QString(R"( pub fn %1_changed(&self) { let ptr = *self.qobject.lock().unwrap(); if !ptr.is_null() { (self.%1_changed)(ptr); } } )").arg(snakeCase(p.name)); } if (o.type == ObjectType::List) { r << R"( pub fn new_data_ready(&self) { let ptr = *self.qobject.lock().unwrap(); if !ptr.is_null() { (self.new_data_ready)(ptr); } } )"; } else if (o.type == ObjectType::Tree) { r << R"( pub fn new_data_ready(&self, item: Option) { let ptr = *self.qobject.lock().unwrap(); if !ptr.is_null() { (self.new_data_ready)(ptr, item.unwrap_or(13), item.is_some()); } } )"; } QString modelStruct = ""; if (o.type != ObjectType::Object) { QString type = o.type == ObjectType::List ? "List" : "Tree"; modelStruct = ", model: " + o.name + type; QString index; QString indexDecl; QString indexCDecl; if (o.type == ObjectType::Tree) { indexDecl = " item: Option,"; indexCDecl = " item: usize, valid: bool,"; index = " item.unwrap_or(13), item.is_some(),"; } r << QString(R"(} pub struct %1%2 { qobject: *const %1QObject, data_changed: fn(*const %1QObject, usize, usize), begin_reset_model: fn(*const %1QObject), end_reset_model: fn(*const %1QObject), begin_insert_rows: fn(*const %1QObject,%5 usize, usize), end_insert_rows: fn(*const %1QObject), begin_remove_rows: fn(*const %1QObject,%5 usize, usize), end_remove_rows: fn(*const %1QObject), } impl %1%2 { pub fn data_changed(&self, first: usize, last: usize) { (self.data_changed)(self.qobject, first, last); } pub fn begin_reset_model(&self) { (self.begin_reset_model)(self.qobject); } pub fn end_reset_model(&self) { (self.end_reset_model)(self.qobject); } pub fn begin_insert_rows(&self,%3 first: usize, last: usize) { (self.begin_insert_rows)(self.qobject,%4 first, last); } pub fn end_insert_rows(&self) { (self.end_insert_rows)(self.qobject); } pub fn begin_remove_rows(&self,%3 first: usize, last: usize) { (self.begin_remove_rows)(self.qobject,%4 first, last); } pub fn end_remove_rows(&self) { (self.end_remove_rows)(self.qobject); } )").arg(o.name, type, indexDecl, index, indexCDecl); } r << QString(R"(} pub trait %1Trait { fn new(emit: %1Emitter%2)").arg(o.name, modelStruct); for (const Property& p: o.properties) { if (p.type.type == BindingType::Object) { r << ",\n " << snakeCase(p.name) << ": " << p.type.name; } } r << QString(R"() -> Self; fn emit(&self) -> &%1Emitter; )").arg(o.name); for (const Property& p: o.properties) { const QString lc(snakeCase(p.name)); if (p.type.type == BindingType::Object) { r << QString(" fn %1(&self) -> &%2;\n").arg(lc, rustType(p)); r << QString(" fn %1_mut(&mut self) -> &mut %2;\n").arg(lc, rustType(p)); } else { r << QString(" fn %1(&self) -> %2;\n").arg(lc, rustReturnType(p)); if (p.write) { r << QString(" fn set_%1(&mut self, value: %2);\n").arg(lc, rustType(p)); } } } if (o.type == ObjectType::List) { r << R"( fn row_count(&self) -> usize; fn can_fetch_more(&self) -> bool { false } fn fetch_more(&mut self) {} fn sort(&mut self, u8, SortOrder) {} )"; } else if (o.type == ObjectType::Tree) { r << R"( fn row_count(&self, Option) -> usize; fn can_fetch_more(&self, Option) -> bool { false } fn fetch_more(&mut self, Option) {} fn sort(&mut self, u8, SortOrder) {} fn index(&self, item: Option, row: usize) -> usize; fn parent(&self, item: usize) -> Option; fn row(&self, item: usize) -> usize; )"; } if (o.type != ObjectType::Object) { for (auto ip: o.itemProperties) { r << QString(" fn %1(&self, item: usize) -> %2;\n") .arg(snakeCase(ip.name), rustReturnType(ip)); if (ip.write) { r << QString(" fn set_%1(&mut self, item: usize, %2) -> bool;\n") .arg(snakeCase(ip.name), rustType(ip)); } } } r << QString(R"(} #[no_mangle] pub extern "C" fn %1_new( )").arg(lcname); rConstructorArgsDecl(r, lcname, o, conf); r << QString(",\n) -> *mut %1 {\n").arg(o.name); rConstructorArgs(r, lcname, o, conf); r << QString(R"( Box::into_raw(Box::new(d_%2)) } #[no_mangle] pub unsafe extern "C" fn %2_free(ptr: *mut %1) { Box::from_raw(ptr).emit().clear(); } )").arg(o.name, lcname); for (const Property& p: o.properties) { const QString base = QString("%1_%2").arg(lcname, snakeCase(p.name)); QString ret = ") -> " + rustType(p); if (p.type.type == BindingType::Object) { r << QString(R"( #[no_mangle] pub unsafe extern "C" fn %2_get(ptr: *mut %1) -> *mut %4 { (&mut *ptr).%3_mut() } )").arg(o.name, base, snakeCase(p.name), rustType(p)); } else if (p.type.isComplex() && !p.optional) { r << QString(R"( #[no_mangle] pub unsafe extern "C" fn %2_get( ptr: *const %1, p: *mut c_void, set: fn(*mut c_void, %4), ) { let data = (&*ptr).%3(); set(p, %5data.into()); } )").arg(o.name, base, snakeCase(p.name), p.type.name, p.rustByValue ?"&" :""); if (p.write) { const QString type = p.type.name == "QString" ? "QStringIn" : p.type.name; r << QString(R"( #[no_mangle] pub unsafe extern "C" fn %2_set(ptr: *mut %1, v: %4) { (&mut *ptr).set_%3(v.convert()); } )").arg(o.name, base, snakeCase(p.name), type); } } else if (p.type.isComplex()) { r << QString(R"( #[no_mangle] pub unsafe extern "C" fn %2_get( ptr: *const %1, p: *mut c_void, set: fn(*mut c_void, %4), ) { let data = (&*ptr).%3(); if let Some(data) = data { set(p, %5data.into()); } } )").arg(o.name, base, snakeCase(p.name), p.type.name, p.rustByValue ?"&" :""); if (p.write) { const QString type = p.type.name == "QString" ? "QStringIn" : p.type.name; r << QString(R"( #[no_mangle] pub unsafe extern "C" fn %2_set(ptr: *mut %1, v: %4) { (&mut *ptr).set_%3(Some(v.convert())); } #[no_mangle] pub unsafe extern "C" fn %2_set_none(ptr: *mut %1) { (&mut *ptr).set_%3(None); } )").arg(o.name, base, snakeCase(p.name), type); } } else { r << QString(R"( #[no_mangle] pub unsafe extern "C" fn %2_get(ptr: *const %1) -> %4 { (&*ptr).%3() } )").arg(o.name, base, snakeCase(p.name), rustType(p)); if (p.write) { r << QString(R"( #[no_mangle] pub unsafe extern "C" fn %2_set(ptr: *mut %1, v: %4) { (&mut *ptr).set_%3(v); } )").arg(o.name, base, snakeCase(p.name), rustType(p)); } } } if (o.type == ObjectType::List) { r << QString(R"( #[no_mangle] pub unsafe extern "C" fn %2_row_count(ptr: *const %1) -> c_int { (&*ptr).row_count() as c_int } #[no_mangle] pub unsafe extern "C" fn %2_can_fetch_more(ptr: *const %1) -> bool { (&*ptr).can_fetch_more() } #[no_mangle] pub unsafe extern "C" fn %2_fetch_more(ptr: *mut %1) { (&mut *ptr).fetch_more() } #[no_mangle] pub unsafe extern "C" fn %2_sort( ptr: *mut %1, column: u8, order: SortOrder, ) { (&mut *ptr).sort(column, order) } )").arg(o.name, lcname); } else if (o.type == ObjectType::Tree) { r << QString(R"( #[no_mangle] pub unsafe extern "C" fn %2_row_count( ptr: *const %1, item: usize, valid: bool, ) -> c_int { if valid { (&*ptr).row_count(Some(item)) as c_int } else { (&*ptr).row_count(None) as c_int } } #[no_mangle] pub unsafe extern "C" fn %2_can_fetch_more( ptr: *const %1, item: usize, valid: bool, ) -> bool { if valid { (&*ptr).can_fetch_more(Some(item)) } else { (&*ptr).can_fetch_more(None) } } #[no_mangle] pub unsafe extern "C" fn %2_fetch_more(ptr: *mut %1, item: usize, valid: bool) { if valid { (&mut *ptr).fetch_more(Some(item)) } else { (&mut *ptr).fetch_more(None) } } #[no_mangle] pub unsafe extern "C" fn %2_sort( ptr: *mut %1, column: u8, order: SortOrder ) { (&mut *ptr).sort(column, order) } #[no_mangle] pub unsafe extern "C" fn %2_index( ptr: *const %1, item: usize, valid: bool, row: c_int, ) -> usize { if !valid { (&*ptr).index(None, row as usize) } else { (&*ptr).index(Some(item), row as usize) } } #[no_mangle] pub unsafe extern "C" fn %2_parent(ptr: *const %1, index: usize) -> QModelIndex { if let Some(parent) = (&*ptr).parent(index) { QModelIndex { row: (&*ptr).row(parent) as c_int, internal_id: parent, } } else { QModelIndex { row: -1, internal_id: 0, } } } #[no_mangle] pub unsafe extern "C" fn %2_row(ptr: *const %1, item: usize) -> c_int { (&*ptr).row(item) as c_int } )").arg(o.name, lcname); } if (o.type != ObjectType::Object) { QString indexDecl = ", row: c_int"; QString index = "row as usize"; if (o.type == ObjectType::Tree) { indexDecl = ", item: usize"; index = "item"; } for (auto ip: o.itemProperties) { if (ip.type.isComplex() && !ip.optional) { r << QString(R"( #[no_mangle] pub unsafe extern "C" fn %2_data_%3( ptr: *const %1%5, d: *mut c_void, set: fn(*mut c_void, %4), ) { let data = (&*ptr).%3(%6); set(d, (%7data).into()); } )").arg(o.name, lcname, snakeCase(ip.name), ip.type.name, indexDecl, index, ip.rustByValue ?"&" :""); } else if (ip.type.isComplex()) { r << QString(R"( #[no_mangle] pub unsafe extern "C" fn %2_data_%3( ptr: *const %1%5, d: *mut c_void, set: fn(*mut c_void, %4), ) { let data = (&*ptr).%3(%6); if let Some(data) = data { set(d, %4::from(&data)); } } )").arg(o.name, lcname, snakeCase(ip.name), ip.type.name, indexDecl, index); } else { r << QString(R"( #[no_mangle] pub unsafe extern "C" fn %2_data_%3(ptr: *const %1%5) -> %4 { (&*ptr).%3(%6).into() } )").arg(o.name, lcname, snakeCase(ip.name), rustCType(ip), indexDecl, index); } if (ip.write) { QString val = "v"; QString type = ip.type.rustType; if (ip.type.isComplex()) { val = val + ".convert()"; type = ip.type.name == "QString" ? "QStringIn" : ip.type.name; } if (ip.optional) { val = "Some(" + val + ")"; } r << QString(R"( #[no_mangle] pub unsafe extern "C" fn %2_set_data_%3( ptr: *mut %1%4, v: %6, ) -> bool { (&mut *ptr).set_%3(%5, %7) } )").arg(o.name, lcname, snakeCase(ip.name), indexDecl, index, type, val); } if (ip.write && ip.optional) { r << QString(R"( #[no_mangle] pub unsafe extern "C" fn %2_set_data_%3_none(ptr: *mut %1, row: c_int%4) -> bool { (&mut *ptr).set_%3(%5row as usize, None) } )").arg(o.name, lcname, snakeCase(ip.name), indexDecl, index); } } } } QString rustFile(const QDir rustdir, const QString& module) { QDir src(rustdir.absoluteFilePath("src")); QString modulePath = src.absoluteFilePath(module + "/mod.rs"); if (QFile::exists(modulePath)) { return modulePath; } return src.absoluteFilePath(module + ".rs"); } void writeRustTypes(const Configuration& conf, QTextStream& r) { bool hasOption = false; bool hasString = false; bool hasStringWrite = false; bool hasByteArray = false; bool hasListOrTree = false; for (auto o: conf.objects) { hasListOrTree |= o.type != ObjectType::Object; for (auto p: o.properties) { hasOption |= p.optional; hasString |= p.type.type == BindingType::QString; hasStringWrite |= p.type.type == BindingType::QString && p.write; hasByteArray |= p.type.type == BindingType::QByteArray; } for (auto p: o.itemProperties) { hasOption |= p.optional; hasString |= p.type.type == BindingType::QString; hasByteArray |= p.type.type == BindingType::QByteArray; } } if (hasOption || hasListOrTree) { r << R"( #[repr(C)] pub struct COption { data: T, some: bool, } impl From> for COption where T: Default, { fn from(t: Option) -> COption { if let Some(v) = t { COption { data: v, some: true, } } else { COption { data: T::default(), some: false, } } } } )"; } if (hasString) { r << R"( #[repr(C)] pub struct QString { data: *const uint8_t, len: c_int, } #[repr(C)] pub struct QStringIn { data: *const uint16_t, len: c_int, } impl QStringIn { fn convert(&self) -> String { let data = unsafe { slice::from_raw_parts(self.data, self.len as usize) }; String::from_utf16_lossy(data) } } impl<'a> From<&'a str> for QString { fn from(string: &'a str) -> QString { QString { len: string.len() as c_int, data: string.as_ptr(), } } } impl<'a> From<&'a String> for QString { fn from(string: &'a String) -> QString { QString { len: string.len() as c_int, data: string.as_ptr(), } } } )"; } if (hasByteArray) { r << R"( #[repr(C)] pub struct QByteArray { data: *const uint8_t, len: c_int, } impl QByteArray { fn convert(&self) -> Vec { let data = unsafe { slice::from_raw_parts(self.data, self.len as usize) }; Vec::from(data) } } impl<'a> From<&'a [u8]> for QByteArray { fn from(value: &'a [u8]) -> QByteArray { QByteArray { len: value.len() as c_int, data: value.as_ptr(), } } } )"; } if (hasListOrTree) { r << R"( #[repr(C)] pub enum SortOrder { Ascending = 0, Descending = 1, } #[repr(C)] pub struct QModelIndex { row: c_int, internal_id: usize, } )"; } } void writeRustInterface(const Configuration& conf) { DifferentFileWriter w(rustFile(conf.rustdir, conf.interfaceModule)); QTextStream r(&w.buffer); r << QString(R"(/* generated by rust_qt_binding_generator */ #![allow(unknown_lints)] #![allow(mutex_atomic, needless_pass_by_value)] use libc::{c_int, c_void, uint8_t, uint16_t}; use std::slice; use std::sync::{Arc, Mutex}; use std::ptr::null; use %1::*; )").arg(conf.implementationModule); writeRustTypes(conf, r); for (auto object: conf.objects) { writeRustInterfaceObject(r, object, conf); } } void writeRustImplementationObject(QTextStream& r, const Object& o) { const QString lcname(snakeCase(o.name)); if (o.type != ObjectType::Object) { r << "#[derive(Default, Clone)]\n"; r << QString("struct %1Item {\n").arg(o.name); for (auto ip: o.itemProperties) { const QString lc(snakeCase(ip.name)); r << QString(" %1: %2,\n").arg(lc, ip.type.rustType); } r << "}\n\n"; } QString modelStruct = ""; r << QString("pub struct %1 {\n emit: %1Emitter,\n").arg((o.name)); if (o.type == ObjectType::List) { modelStruct = ", model: " + o.name + "List"; r << QString(" model: %1List,\n").arg(o.name); } else if (o.type == ObjectType::Tree) { modelStruct = ", model: " + o.name + "Tree"; r << QString(" model: %1Tree,\n").arg(o.name); } for (const Property& p: o.properties) { const QString lc(snakeCase(p.name)); r << QString(" %1: %2,\n").arg(lc, rustType(p)); } if (o.type != ObjectType::Object) { r << QString(" list: Vec<%1Item>,\n").arg(o.name); } r << "}\n\n"; for (const Property& p: o.properties) { if (p.type.type == BindingType::Object) { modelStruct += ", " + p.name + ": " + p.type.name; } } r << QString(R"(impl %1Trait for %1 { fn new(emit: %1Emitter%2) -> %1 { %1 { emit: emit, )").arg(o.name, modelStruct); if (o.type != ObjectType::Object) { r << QString(" model: model,\n"); } for (const Property& p: o.properties) { const QString lc(snakeCase(p.name)); if (p.type.type == BindingType::Object) { r << QString(" %1: %1,\n").arg(lc); } else { r << QString(" %1: %2,\n").arg(lc, rustTypeInit(p)); } } if (o.type != ObjectType::Object) { r << QString(" list: vec![%1Item::default(); 10],\n") .arg(o.name); } r << QString(R"( } } fn emit(&self) -> &%1Emitter { &self.emit } )").arg(o.name); for (const Property& p: o.properties) { const QString lc(snakeCase(p.name)); if (p.type.type == BindingType::Object) { r << QString(R"( fn %1(&self) -> &%2 { &self.%1 } fn %1_mut(&mut self) -> &mut %2 { &mut self.%1 } )").arg(lc, rustReturnType(p)); } else { r << QString(" fn %1(&self) -> %2 {\n").arg(lc, rustReturnType(p)); if (p.type.isComplex()) { if (p.optional) { /* if (rustType(p) == "Option") { r << QString(" self.%1.as_ref().map(|p|p.as_str())\n").arg(lc); } else { } */ r << QString(" self.%1.as_ref().map(|p|&p[..])\n").arg(lc); } else { r << QString(" &self.%1\n").arg(lc); } } else { r << QString(" self.%1\n").arg(lc); } r << " }\n"; if (p.write) { r << QString(R"( fn set_%1(&mut self, value: %2) { self.%1 = value; self.emit.%1_changed(); } )").arg(lc, rustType(p)); } } } if (o.type == ObjectType::List) { r << " fn row_count(&self) -> usize {\n self.list.len()\n }\n"; } else if (o.type == ObjectType::Tree) { r << R"( fn row_count(&self, item: Option) -> usize { self.list.len() } fn index(&self, item: Option, row: usize) -> usize { 0 } fn parent(&self, item: usize) -> Option { None } fn row(&self, item: usize) -> usize { item } )"; } if (o.type != ObjectType::Object) { QString index; if (o.type == ObjectType::Tree) { index = ", item: usize"; } for (auto ip: o.itemProperties) { const QString lc(snakeCase(ip.name)); r << QString(" fn %1(&self, item: usize) -> %2 {\n") .arg(lc, rustReturnType(ip)); if (ip.type.isComplex()) { r << " &self.list[item]." << lc << "\n"; } else { r << " self.list[item]." << lc << "\n"; } r << " }\n"; if (ip.write) { r << QString(" fn set_%1(&mut self, item: usize, v: %2) -> bool {\n") .arg(snakeCase(ip.name), rustType(ip)); r << " self.list[item]." << lc << " = v;\n"; r << " true\n"; r << " }\n"; } } } r << "}\n\n"; } void writeRustImplementation(const Configuration& conf) { DifferentFileWriter w(rustFile(conf.rustdir, conf.implementationModule), conf.overwriteImplementation); QTextStream r(&w.buffer); r << QString(R"(#![allow(unused_imports)] #![allow(unused_variables)] #![allow(dead_code)] use %1::*; )").arg(conf.interfaceModule); for (auto object: conf.objects) { writeRustImplementationObject(r, object); } } diff --git a/src/rust.h b/src/rust.h index d4d11d5..779f379 100644 --- a/src/rust.h +++ b/src/rust.h @@ -1,3 +1,23 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + class Configuration; void writeRustInterface(const Configuration& conf); void writeRustImplementation(const Configuration& conf); diff --git a/src/structs.h b/src/structs.h index 8536ad5..58c5fb9 100644 --- a/src/structs.h +++ b/src/structs.h @@ -1,139 +1,159 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include #include #include #include #include #include #include enum class ObjectType { Object, List, Tree }; enum class BindingType { Bool, UChar, Int, UInt, ULongLong, Float, QString, QByteArray, Object, }; struct BindingTypeProperties { BindingType type; QString name; QString cppSetType; QString cSetType; QString rustType; QString rustTypeInit; bool isComplex() const { return name.startsWith("Q"); } bool operator==(const BindingTypeProperties& other) { return type == other.type && name == other.name && cppSetType == other.cppSetType && cSetType == other.cSetType && rustType == other.rustType && rustTypeInit == other.rustTypeInit; } }; struct Property { QString name; BindingTypeProperties type; bool write; bool optional; bool rustByValue; }; struct ItemProperty { QString name; BindingTypeProperties type; bool write; bool optional; bool rustByValue; QList> roles; }; struct Object { QString name; ObjectType type; QList properties; QList itemProperties; int columnCount; bool containsObject() { for (auto p: properties) { if (p.type.type == BindingType::Object) { return true; } } return false; } }; struct Configuration { QFileInfo hFile; QFileInfo cppFile; QDir rustdir; QString interfaceModule; QString implementationModule; QList objects; bool overwriteImplementation; const Object& findObject(const QString& name) const { for (auto& o: objects) { if (o.name == name) { return o; } } QTextStream err(stderr); err << QCoreApplication::translate("main", "Cannot find type %1.\n").arg(name); err.flush(); exit(1); } QList types() const { QList ops; for (auto o: objects) { for (auto ip: o.properties) { if (!ops.contains(ip.type.name)) { ops.append(ip.type.name); } } for (auto ip: o.itemProperties) { if (!ops.contains(ip.type.name)) { ops.append(ip.type.name); } } } return ops; } QList optionalTypes() const { QList ops; for (auto o: objects) { for (auto ip: o.itemProperties) { if (ip.optional && !ops.contains(ip.type.name)) { ops.append(ip.type.name); } } if (o.type != ObjectType::Object && !ops.contains("quintptr")) { ops.append("quintptr"); } } return ops; } bool hasListOrTree() const { for (auto o: objects) { if (o.type == ObjectType::List || o.type == ObjectType::Tree) { return true; } } return false; } }; diff --git a/tests/test_list.cpp b/tests/test_list.cpp index d7a7cfb..e1f06a3 100644 --- a/tests/test_list.cpp +++ b/tests/test_list.cpp @@ -1,49 +1,69 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "test_list_rust.h" #include #include class TestRustList : public QObject { Q_OBJECT private slots: void testConstructor(); void testStringGetter(); void testStringSetter(); }; void TestRustList::testConstructor() { Persons persons; } void TestRustList::testStringGetter() { Persons persons; QCOMPARE(persons.rowCount(), 10); QVariant value = persons.data(persons.index(0,0)); // value should be empty string in default implementation QVERIFY(value.isValid()); QCOMPARE(value.type(), QVariant::String); QCOMPARE(value.toString(), QString()); } void TestRustList::testStringSetter() { // GIVEN Persons persons; QSignalSpy spy(&persons, &Persons::dataChanged); // WHEN const QModelIndex index(persons.index(0,0)); const bool set = persons.setData(index, "Konqi"); // THEN QVERIFY(set); QVERIFY(spy.isValid()); QCOMPARE(spy.count(), 1); QVariant value = persons.data(persons.index(0,0)); QCOMPARE(value.toString(), QString("Konqi")); } QTEST_MAIN(TestRustList) #include "test_list.moc" diff --git a/tests/test_object.cpp b/tests/test_object.cpp index 2a3bec2..24d7ac6 100644 --- a/tests/test_object.cpp +++ b/tests/test_object.cpp @@ -1,41 +1,61 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "test_object_rust.h" #include #include class TestRustObject : public QObject { Q_OBJECT private slots: void testConstructor(); void testStringGetter(); void testStringSetter(); }; void TestRustObject::testConstructor() { Person person; } void TestRustObject::testStringGetter() { Person person; person.setUserName("Konqi"); } void TestRustObject::testStringSetter() { // GIVEN Person person; QSignalSpy spy(&person, &Person::userNameChanged); // WHEN person.setUserName("Konqi"); // THEN QVERIFY(spy.isValid()); QCOMPARE(spy.count(), 1); QCOMPARE(person.userName(), QString("Konqi")); } QTEST_MAIN(TestRustObject) #include "test_object.moc" diff --git a/tests/test_objects.cpp b/tests/test_objects.cpp index 9e9bb31..5213b46 100644 --- a/tests/test_objects.cpp +++ b/tests/test_objects.cpp @@ -1,70 +1,90 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "test_objects_rust.h" #include #include class TestRustObjects : public QObject { Q_OBJECT private slots: void testOneLevelConstructor(); void testOneLevelStringGetter(); void testOneLevelStringSetter(); void testTwoLevelsConstructor(); void testTwoLevelsStringGetter(); void testTwoLevelsStringSetter(); }; void TestRustObjects::testOneLevelConstructor() { Person person; } void TestRustObjects::testOneLevelStringGetter() { Person person; person.object()->setDescription("Konqi"); } void TestRustObjects::testOneLevelStringSetter() { // GIVEN Person person; QSignalSpy spy(person.object(), &InnerObject::descriptionChanged); // WHEN person.object()->setDescription("Konqi"); // THEN QVERIFY(spy.isValid()); QCOMPARE(spy.count(), 1); QCOMPARE(person.object()->description(), QString("Konqi")); } void TestRustObjects::testTwoLevelsConstructor() { Group group; } void TestRustObjects::testTwoLevelsStringGetter() { Group group; group.person()->object()->setDescription("Konqi"); } void TestRustObjects::testTwoLevelsStringSetter() { // GIVEN Group group; QSignalSpy spy(group.person()->object(), &InnerObject::descriptionChanged); // WHEN group.person()->object()->setDescription("Konqi"); // THEN QVERIFY(spy.isValid()); QCOMPARE(spy.count(), 1); QCOMPARE(group.person()->object()->description(), QString("Konqi")); } QTEST_MAIN(TestRustObjects) #include "test_objects.moc" diff --git a/tests/test_tree.cpp b/tests/test_tree.cpp index 1bed36d..ed6becc 100644 --- a/tests/test_tree.cpp +++ b/tests/test_tree.cpp @@ -1,49 +1,69 @@ +/* + * Copyright 2017 Jos van den Oever + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "test_list_rust.h" #include #include class TestRustTree : public QObject { Q_OBJECT private slots: void testConstructor(); void testStringGetter(); void testStringSetter(); }; void TestRustTree::testConstructor() { Persons persons; } void TestRustTree::testStringGetter() { Persons persons; QCOMPARE(persons.rowCount(), 10); QVariant value = persons.data(persons.index(0,0)); // value should be empty string in default implementation QVERIFY(value.isValid()); QCOMPARE(value.type(), QVariant::String); QCOMPARE(value.toString(), QString()); } void TestRustTree::testStringSetter() { // GIVEN Persons persons; QSignalSpy spy(&persons, &Persons::dataChanged); // WHEN const QModelIndex index(persons.index(0,0)); const bool set = persons.setData(index, "Konqi"); // THEN QVERIFY(set); QVERIFY(spy.isValid()); QCOMPARE(spy.count(), 1); QVariant value = persons.data(persons.index(0,0)); QCOMPARE(value.toString(), QString("Konqi")); } QTEST_MAIN(TestRustTree) #include "test_tree.moc"