Browse Source

Massive GUI overhaul. Ported everything to Kirigami2. Also fixed profiles.

Malte Veerman 6 years ago
parent
commit
0554d6c785

+ 2 - 2
CMakeLists.txt

@@ -44,13 +44,13 @@ include(FindPkgConfig)
 
 
 #Find Qt5
 #Find Qt5
 find_package(Qt5Core REQUIRED)
 find_package(Qt5Core REQUIRED)
-find_package(Qt5 5.10 COMPONENTS Quick QuickControls2)
+find_package(Qt5 5.8 COMPONENTS Quick QuickControls2)
 set_package_properties(Qt5Quick PROPERTIES TYPE RUNTIME PURPOSE "Needed by the QML parts")
 set_package_properties(Qt5Quick PROPERTIES TYPE RUNTIME PURPOSE "Needed by the QML parts")
 set_package_properties(Qt5QuickControls2 PROPERTIES TYPE RUNTIME PURPOSE "Needed by the QML parts")
 set_package_properties(Qt5QuickControls2 PROPERTIES TYPE RUNTIME PURPOSE "Needed by the QML parts")
 
 
 #Find KF5
 #Find KF5
 find_package(KF5 COMPONENTS I18n REQUIRED)
 find_package(KF5 COMPONENTS I18n REQUIRED)
-find_package(KF5 COMPONENTS Kirigami2)
+find_package(KF5 5.37 COMPONENTS Kirigami2)
 set_package_properties(KF5Kirigami2 PROPERTIES TYPE RUNTIME PURPOSE "Needed by the QML parts")
 set_package_properties(KF5Kirigami2 PROPERTIES TYPE RUNTIME PURPOSE "Needed by the QML parts")
 
 
 if(${KF5_VERSION} VERSION_GREATER_EQUAL 5.33.0)
 if(${KF5_VERSION} VERSION_GREATER_EQUAL 5.33.0)

+ 2 - 2
README.md

@@ -14,11 +14,11 @@ To compile the additional KCM set the cmake option -DBUILD_KCM=on.
 The KCM is only build, if the -DNO_SYSTEMD option is unset or set to false.
 The KCM is only build, if the -DNO_SYSTEMD option is unset or set to false.
 
 
 # Requirements
 # Requirements
-* Qt5: Base/Core, Widgets, GUI, QML, Quick
+* Qt5: Base/Core, Widgets, GUI, QML, Quick, QuickControls2
 * KF5: I18n, Auth, Config, Package, Declarative, CoreAddons, DBusAddons, KCMUtils, Extra-Cmake-Modules, Kirigami2, Notifications
 * KF5: I18n, Auth, Config, Package, Declarative, CoreAddons, DBusAddons, KCMUtils, Extra-Cmake-Modules, Kirigami2, Notifications
 
 
 ## Debian/Ubuntu command to install the build requirements:
 ## Debian/Ubuntu command to install the build requirements:
-`sudo apt-get install libkf5config-dev libkf5auth-dev libkf5package-dev libkf5declarative-dev libkf5coreaddons-dev libkf5dbusaddons-dev libkf5kcmutils-dev libkf5i18n-dev libqt5core5a libqt5widgets5 libqt5gui5 libqt5qml5 extra-cmake-modules qtbase5-dev kirigami2-dev libkf5kirigami2-5 libkf5notifications-dev qml-module-org-kde-kirigami2 qml-module-qtquick-dialogs qml-module-qtquick-controls cmake build-essential`
+`sudo apt-get install libkf5config-dev libkf5auth-dev libkf5package-dev libkf5declarative-dev libkf5coreaddons-dev libkf5dbusaddons-dev libkf5kcmutils-dev libkf5i18n-dev libqt5core5a libqt5widgets5 libqt5gui5 libqt5qml5 extra-cmake-modules qtbase5-dev kirigami2-dev libkf5kirigami2-5 libkf5notifications-dev qml-module-org-kde-kirigami2 qml-module-qtquick-dialogs qml-module-qtquick-controls2 cmake build-essential`
 
 
 **Note:** This was tested on KDE Neon User Edition 5.13, which is based on Ubuntu 18.04 LTS (Debian 9 Stretch/Sid).
 **Note:** This was tested on KDE Neon User Edition 5.13, which is based on Ubuntu 18.04 LTS (Debian 9 Stretch/Sid).
 
 

+ 76 - 105
fancontrol-gui/package/contents/ui/Application.qml

@@ -18,10 +18,10 @@
  */
  */
 
 
 
 
-import QtQuick 2.4
-import QtQuick.Controls 2.3
-import QtQuick.Layouts 1.10
-import org.kde.kirigami 2.0 as Kirigami
+import QtQuick 2.6
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.3 as Kirigami
 import Fancontrol.Gui 1.0 as Gui
 import Fancontrol.Gui 1.0 as Gui
 import Fancontrol.Qml 1.0 as Fancontrol
 import Fancontrol.Qml 1.0 as Fancontrol
 
 
@@ -29,6 +29,9 @@ import Fancontrol.Qml 1.0 as Fancontrol
 Kirigami.ApplicationWindow {
 Kirigami.ApplicationWindow {
     id: window
     id: window
 
 
+    property string leftPage
+    property QtObject fan: Fancontrol.Base.pwmFanModel.fans.length > 0 ? Fancontrol.Base.pwmFanModel.fans[0] : null
+
     function showWindow() {
     function showWindow() {
         window.show()
         window.show()
         window.raise()
         window.raise()
@@ -36,9 +39,16 @@ Kirigami.ApplicationWindow {
     }
     }
 
 
     title: i18n("Fancontrol-GUI")
     title: i18n("Fancontrol-GUI")
-    width: 1024
-    height: 768
-    color: palette.window
+    reachableModeEnabled: false
+    pageStack.defaultColumnWidth: Kirigami.Units.gridUnit * 30
+
+    onLeftPageChanged: {
+        window.pageStack.clear();
+        if (leftPage)
+            window.pageStack.push(Qt.resolvedUrl(leftPage));
+    }
+
+    onWideScreenChanged: drawer.drawerOpen = wideScreen
 
 
     onClosing: {
     onClosing: {
         windowConfig.save(window);
         windowConfig.save(window);
@@ -53,82 +63,81 @@ Kirigami.ApplicationWindow {
         Fancontrol.Base.load();
         Fancontrol.Base.load();
         windowConfig.restore(window);
         windowConfig.restore(window);
         window.visible = !Fancontrol.Base.startMinimized;
         window.visible = !Fancontrol.Base.startMinimized;
+        leftPage = "SensorsTab.qml";
     }
     }
 
 
-    header: ToolBar {
-        RowLayout {
-            anchors.fill: parent
+    globalDrawer: Kirigami.GlobalDrawer {
+        id: drawer
 
 
-            ToolButton {
-                action: applyAction
-                display: AbstractButton.IconOnly
-            }
-            ToolButton {
-                action: resetAction
-                display: AbstractButton.IconOnly
-            }
-            Loader {
-                active: !!Fancontrol.Base.systemdCom
-
-                sourceComponent: ToolButton {
-                    action: startAction
-                    display: AbstractButton.IconOnly
-                }
-            }
-            Loader {
-                active: !!Fancontrol.Base.systemdCom
+        width: Kirigami.Units.gridUnit * 10
+        modal: !window.wideScreen
+        handleVisible: !window.wideScreen
+        resetMenuOnTriggered: false
 
 
-                sourceComponent: ToolButton {
-                    action: stopAction
-                    display: AbstractButton.IconOnly
-                }
+        function populateFans(fans) {
+            for (var i=fansAction.children.length-1; i>=0; i--) {
+                fansAction.children[i].destroy();
             }
             }
-            Item {
-                Layout.fillWidth: true
+            var actions = [];
+            for (var i=0; i<fans.length; i++) {
+                var action = fanActionComponent.createObject(fansAction, { "fan": fans[i] });
+                actions.push(action);
             }
             }
+            fansAction.children = actions;
         }
         }
-    }
 
 
-    ColumnLayout {
-        anchors.fill: parent
-        spacing: 5
+        Component {
+            id: fanActionComponent
 
 
-        TabBar {
-            id: tabBar
+            Kirigami.Action {
+                property QtObject fan
 
 
-            Layout.fillWidth: true
+                text: fan.name
+                checked: window.fan === fan
 
 
-            TabButton {
-                text: i18n("Sensors")
-                width: implicitWidth
-            }
-            TabButton {
-                text: i18n("PwmFans")
-                width: implicitWidth
-            }
-            TabButton {
-                text: i18n("Configfile")
-                width: implicitWidth
-            }
-            TabButton {
-                text: i18n("Settings")
-                width: implicitWidth
+                onTriggered: window.fan = fan
             }
             }
         }
         }
 
 
-        StackLayout {
-            Layout.fillWidth: true
-            Layout.fillHeight: true
-            currentIndex: tabBar.currentIndex
-            anchors.margins: Kirigami.Units.smallSpacing
+        Component.onCompleted: populateFans(Fancontrol.Base.pwmFanModel.fans)
 
 
-            SensorsTab {}
-            PwmFansTab {}
-            ConfigfileTab {}
-            SettingsTab {}
+        Connections {
+            target: Fancontrol.Base.pwmFanModel
+            onFansChanged: populateFans(Fancontrol.Base.pwmFanModel.fans)
         }
         }
+
+        actions: [
+            Kirigami.Action {
+                text: i18n("Sensors")
+                checked: window.leftPage === "SensorsTab.qml"
+
+                onTriggered: window.leftPage = "SensorsTab.qml"
+            },
+            Kirigami.Action {
+                id: fansAction
+
+                text: i18n("Fans")
+                checked: window.leftPage === "PwmFansTab.qml"
+
+                onTriggered: window.leftPage = "PwmFansTab.qml"
+            },
+            Kirigami.Action {
+                text: i18n("Configfile")
+                checked: window.leftPage === "ConfigfileTab.qml"
+
+                onTriggered: window.leftPage = "ConfigfileTab.qml"
+            },
+            Kirigami.Action {
+                text: i18n("Settings")
+                checked: window.leftPage === "SettingsTab.qml"
+
+                onTriggered: window.leftPage = "SettingsTab.qml"
+            }
+        ]
     }
     }
 
 
+    contextDrawer: Kirigami.ContextDrawer {}
+
     Loader {
     Loader {
         id: trayLoader
         id: trayLoader
 
 
@@ -153,6 +162,7 @@ Kirigami.ApplicationWindow {
         id: errorDialog
         id: errorDialog
 
 
         visible: false
         visible: false
+        anchors.centerIn: window.contentItem
     }
     }
 
 
     Dialog {
     Dialog {
@@ -164,6 +174,7 @@ Kirigami.ApplicationWindow {
         modal: true
         modal: true
         title: i18n("Unsaved changes")
         title: i18n("Unsaved changes")
         standardButtons: Dialog.Cancel | Dialog.Discard | Dialog.Apply
         standardButtons: Dialog.Cancel | Dialog.Discard | Dialog.Apply
+        anchors.centerIn: window.contentItem
 
 
         onRejected: close()
         onRejected: close()
         onDiscarded: {
         onDiscarded: {
@@ -184,44 +195,4 @@ Kirigami.ApplicationWindow {
             text: i18n("There are unsaved changes.\nDo you want to apply these changes?")
             text: i18n("There are unsaved changes.\nDo you want to apply these changes?")
         }
         }
     }
     }
-
-    Action {
-        id: applyAction
-//         text: i18n("Apply")
-        enabled: Fancontrol.Base.needsApply
-        onTriggered: Fancontrol.Base.apply()
-        icon.name: "dialog-ok-apply"
-//         tooltip: i18n("Apply changes")
-        shortcut: StandardKey.Apply
-    }
-    Action {
-        id: resetAction
-//         text: i18n("Reset")
-        enabled: Fancontrol.Base.needsApply
-        onTriggered: Fancontrol.Base.reset()
-        icon.name: "edit-undo"
-//         tooltip: i18n("Revert changes")
-    }
-    Action {
-        id: startAction
-//         text: i18n("Start")
-        enabled: !!Fancontrol.Base.systemdCom && !Fancontrol.Base.systemdCom.serviceActive
-        icon.name: "media-playback-start"
-//         tooltip: i18n("Enable manual control")
-
-        onTriggered: Fancontrol.Base.systemdCom.serviceActive = true
-    }
-    Action {
-        id: stopAction
-//         text: i18n("Stop")
-        enabled: !!Fancontrol.Base.systemdCom && Fancontrol.Base.systemdCom.serviceActive
-        icon.name: "media-playback-stop"
-//         tooltip: i18n("Disable manual control")
-
-        onTriggered: Fancontrol.Base.systemdCom.serviceActive = false
-    }
-
-    SystemPalette {
-        id: palette
-    }
 }
 }

+ 14 - 30
fancontrol-gui/package/contents/ui/ConfigfileTab.qml

@@ -18,43 +18,27 @@
  */
  */
 
 
 
 
-import QtQuick 2.4
-import QtQuick.Controls 2.3
-import QtQuick.Layouts 1.10
-import org.kde.kirigami 2.0 as Kirigami
+import QtQuick 2.6
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.3 as Kirigami
 import Fancontrol.Qml 1.0 as Fancontrol
 import Fancontrol.Qml 1.0 as Fancontrol
 
 
 
 
-ColumnLayout {
+Kirigami.ScrollablePage {
+    id: root
+
     property QtObject loader: Fancontrol.Base.loader
     property QtObject loader: Fancontrol.Base.loader
 
 
-    Label {
-        Layout.alignment: Qt.AlignTop
+    header: Label {
         text: !!loader && loader.configEqualToLoadedFile ? loader.configPath : i18n("New config")
         text: !!loader && loader.configEqualToLoadedFile ? loader.configPath : i18n("New config")
     }
     }
 
 
-    Rectangle {
-        Layout.fillHeight: true
-        Layout.fillWidth: true
-        color: palette.light
-        border.width: 1
-        radius: Kirigami.Units.smallSpacing
-
-        ScrollView {
-            id: scrollView
-
-            anchors.fill: parent
-            anchors.margins: Kirigami.Units.smallSpacing
-
-            TextEdit {
-                text: !!loader ? loader.config : ""
-                readOnly: true
-                color: palette.text
-            }
-        }
-    }
-
-    SystemPalette {
-        id: palette
+    TextEdit {
+        width: root.width - root.leftPadding - root.rightPadding
+        text: !!loader ? loader.config : ""
+        readOnly: true
+        color: Kirigami.Theme.textColor
+        wrapMode: TextEdit.Wrap
     }
     }
 }
 }

+ 81 - 107
fancontrol-gui/package/contents/ui/PwmFansTab.qml

@@ -18,143 +18,117 @@
  */
  */
 
 
 
 
-import QtQuick 2.4
-import QtQuick.Controls 2.3
-import QtQuick.Layouts 1.10
-import org.kde.kirigami 2.0 as Kirigami
+import QtQuick 2.6
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.3 as Kirigami
 import Fancontrol.Qml 1.0 as Fancontrol
 import Fancontrol.Qml 1.0 as Fancontrol
 
 
 
 
-Item {
+Kirigami.Page {
     property QtObject loader: Fancontrol.Base.loader
     property QtObject loader: Fancontrol.Base.loader
     property QtObject systemdCom: Fancontrol.Base.hasSystemdCommunicator() ? Fancontrol.Base.systemdCom : null
     property QtObject systemdCom: Fancontrol.Base.hasSystemdCommunicator() ? Fancontrol.Base.systemdCom : null
     property QtObject pwmFanModel: Fancontrol.Base.pwmFanModel
     property QtObject pwmFanModel: Fancontrol.Base.pwmFanModel
     property QtObject tempModel: Fancontrol.Base.tempModel
     property QtObject tempModel: Fancontrol.Base.tempModel
     property QtObject profileModel: Fancontrol.Base.profileModel
     property QtObject profileModel: Fancontrol.Base.profileModel
     property var pwmFans: pwmFanModel.fans
     property var pwmFans: pwmFanModel.fans
+    property QtObject fan: applicationWindow().fan
 
 
     id: root
     id: root
 
 
-    RowLayout {
-        id: profileRow
-
-        anchors.top: parent.top
-        width: parent.width
-        height: childrenRect.height
-
-        Label {
-            text: i18n("Profile:")
-            Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
-            renderType: Text.NativeRendering
-        }
-        ComboBox {
-            id: profileComboBox
-
-            property string saveText: editText.length > 0 ? editText : currentText
-
-            editable: true
-            model: profileModel
-            textRole: "display"
-            Layout.fillWidth: true
-            Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+    header: Fancontrol.FanHeader {
+        fan: root.fan
+    }
 
 
-            onActivated: Fancontrol.Base.applyProfile(index)
+    contextualActions: [
+        Kirigami.Action {
+            text: i18n("Manage profiles")
+            onTriggered: profilesDialog.open()
+        },
+        Kirigami.Action {
+            text: i18n("Service")
+            visible: Fancontrol.Base.hasSystemdCommunicator
+            tooltip: i18n("Control the systemd service")
+
+            Kirigami.Action {
+                text: !!systemdCom && systemdCom.serviceActive ? i18n("Stop service") : i18n("Start service")
+                icon.name: !!systemdCom && systemdCom.serviceActive ? "media-playback-stop" : "media-playback-start"
+
+                onTriggered: systemdCom.serviceActive = !systemdCom.serviceActive
+            }
+            Kirigami.Action {
+                text: !!systemdCom && systemdCom.serviceEnabled ? i18n("Disable service") : i18n("Enable service")
+                tooltip: !!systemdCom && systemdCom.serviceEnabled ? i18n("Disable service autostart at boot") : i18n("Enable service autostart at boot")
 
 
-            Connections {
-                target: Fancontrol.Base
-                onProfileChanged: profileComboBox.currentIndex = profile
+                onTriggered: systemdCom.serviceEnabled = !systemdCom.serviceEnabled
+            }
+        },
+        Kirigami.Action {
+            text: loader.sensorsDetected ? i18n("Detect fans again") : i18n("Detect fans")
+            icon.name: "dialog-password"
+            onTriggered: loader.detectSensors()
+        },
+        Kirigami.Action {
+            visible: !!systemdCom && !!fan
+            text: !!fan ? fan.testing ? i18n("Abort test") : i18n("Test start and stop values") : ""
+            icon.name: "dialog-password"
+            onTriggered: {
+                if (fan.testing) {
+                    fan.abortTest();
+                } else {
+                    fan.test();
+                }
             }
             }
         }
         }
-        Button {
-            Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
-            action: saveProfileAction
-        }
-        Button {
-            Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
-            action: deleteProfileAction
-        }
-    }
+    ]
 
 
-    RowLayout {
-        id: fanRow
-
-        anchors.top: profileRow.bottom
-        width: parent.width
-        height: childrenRect.height
-        visible: pwmFans.length > 0
-
-        Label {
-            text: i18n("Fan:")
-            Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
-            renderType: Text.NativeRendering
-        }
-        ComboBox {
-            id: fanComboBox
+    mainAction: Kirigami.Action {
+        text: i18n("Apply")
+        enabled: Fancontrol.Base.needsApply
+        icon.name: "dialog-ok-apply"
+        tooltip: i18n("Apply changes")
+        shortcut: StandardKey.Apply
 
 
-            model: pwmFanModel
-            textRole: "display"
-            Layout.fillWidth: true
-            Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
-        }
-        Button {
-            Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
-            action: detectFansAction
-        }
+        onTriggered: Fancontrol.Base.apply()
     }
     }
+    rightAction: Kirigami.Action {
+        text: i18n("Reset")
+        enabled: Fancontrol.Base.needsApply
+        icon.name: "edit-undo"
+        tooltip: i18n("Revert changes")
 
 
-    Loader {
-        width: parent.width
-        anchors.top: fanRow.bottom
-        anchors.bottom: parent.bottom
-        anchors.topMargin: parent.anchors.margins
-        active: pwmFans.length > fanComboBox.currentIndex && fanComboBox.currentIndex >= 0
-
-        sourceComponent: Fancontrol.FanItem {
-            fan: pwmFans[fanComboBox.currentIndex]
-            tempModel: root.tempModel
-            systemdCom: root.systemdCom
-        }
+        onTriggered: Fancontrol.Base.reset()
     }
     }
 
 
     ColumnLayout {
     ColumnLayout {
-        id: noFansInfo
+        anchors.fill: parent
 
 
-        anchors.centerIn: parent
-        spacing: Kirigami.Units.smallSpacing * 2
-        visible: pwmFans.length === 0
-
-        Label {
-            Layout.alignment: Qt.AlignCenter
-            text: i18n("There are no pwm capable fans in your system.")
-            font.pointSize: 14
-            font.bold: true
-        }
+        Loader {
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+            active: !!root.fan
 
 
-        Button {
-            Layout.alignment: Qt.AlignCenter
-            action: detectFansAction
+            sourceComponent: Fancontrol.FanItem {
+                fan: root.fan
+                tempModel: root.tempModel
+                systemdCom: root.systemdCom
+            }
         }
         }
     }
     }
 
 
-    Action {
-        id: saveProfileAction
-
-        text: i18n("Save profile")
-        icon.name: "document-save"
-        onTriggered: Fancontrol.Base.saveProfile(profileComboBox.saveText)
+    Label {
+        anchors.centerIn: parent
+        visible: pwmFans.length === 0
+        text: i18n("There are no pwm capable fans in your system.")
+        font.pointSize: 14
+        font.bold: true
     }
     }
-    Action {
-        id: deleteProfileAction
 
 
-        text: i18n("Delete profile")
-        icon.name: "edit-delete"
-        onTriggered: Fancontrol.Base.deleteProfile(profileComboBox.currentIndex)
-    }
-    Action {
-        id: detectFansAction
+    Fancontrol.ProfilesDialog {
+        id: profilesDialog
 
 
-        text: loader.sensorsDetected ? i18n("Detect fans again") : i18n("Detect fans")
-        icon.name: "dialog-password"
-        onTriggered: loader.detectSensors()
+        visible: false
+        modal: true
+        anchors.centerIn: parent
     }
     }
 }
 }

+ 29 - 29
fancontrol-gui/package/contents/ui/SensorsTab.qml

@@ -18,40 +18,44 @@
  */
  */
 
 
 
 
-import QtQuick 2.4
-import QtQuick.Controls 2.3
+import QtQuick 2.6
+import QtQuick.Controls 2.1
 import QtQuick.Layouts 1.2
 import QtQuick.Layouts 1.2
-import org.kde.kirigami 2.0 as Kirigami
+import org.kde.kirigami 2.3 as Kirigami
 import Fancontrol.Qml 1.0 as Fancontrol
 import Fancontrol.Qml 1.0 as Fancontrol
 
 
 
 
-RowLayout {
+Kirigami.ScrollablePage {
     id: root
     id: root
 
 
-    property int padding: 10
     property QtObject loader: Fancontrol.Base.loader
     property QtObject loader: Fancontrol.Base.loader
 
 
-    anchors.fill: parent
+    spacing: Kirigami.Units.smallSpacing
+
+    ListView {
+        id: listView
+
+        width: root.width
+        topMargin: spacing
+        spacing: Kirigami.Units.largeSpacing * 2
+        headerPositioning: ListView.OverlayHeader
 
 
-    Repeater {
         model: loader.hwmons.length
         model: loader.hwmons.length
 
 
-        Rectangle {
+        delegate: Rectangle {
             property QtObject hwmon: loader.hwmons[index]
             property QtObject hwmon: loader.hwmons[index]
 
 
-            Layout.preferredWidth: root.width / loader.hwmons.length - root.spacing
-            Layout.minimumWidth: Kirigami.Units.gridUnit * 10
-            Layout.fillHeight: true
-            color: palette.light
-            border.width: 1
-            border.color: "black"
-            radius: Kirigami.Units.smallSpacing
+            height: childrenRect.height
+            width: listView.width - listView.spacing * 2
+            x: listView.spacing
             clip: true
             clip: true
+            color: Kirigami.Theme.backgroundColor
 
 
             Column {
             Column {
                 id: column
                 id: column
-                anchors.fill: parent
-                anchors.margins: Kirigami.Units.smallSpacing
+
+                width: parent.width
+                padding: root.spacing
 
 
                 Label {
                 Label {
                     anchors.horizontalCenter: parent.horizontalCenter
                     anchors.horizontalCenter: parent.horizontalCenter
@@ -64,11 +68,11 @@ RowLayout {
                     model: hwmon.fans.length
                     model: hwmon.fans.length
 
 
                     RowLayout {
                     RowLayout {
-                        width: parent.width
+                        width: parent.width - parent.padding * 2
 
 
                         Label {
                         Label {
-                            anchors.leftMargin: root.padding
-                            Layout.maximumWidth: parent.width - rpmValue.width - root.padding*2
+                            anchors.leftMargin: root.spacing
+                            Layout.maximumWidth: parent.width - rpmValue.width - root.spacing*2
                             Layout.alignment: Qt.AlignLeft
                             Layout.alignment: Qt.AlignLeft
                             clip: true
                             clip: true
                             text: "Fan " + (index+1) + ":"
                             text: "Fan " + (index+1) + ":"
@@ -76,7 +80,7 @@ RowLayout {
                         Label {
                         Label {
                             id: rpmValue
                             id: rpmValue
                             Layout.alignment: Qt.AlignRight
                             Layout.alignment: Qt.AlignRight
-                            anchors.rightMargin: root.padding
+                            anchors.rightMargin: root.spacing
                             text: hwmon.fans[index].rpm + " " + i18n("rpm")
                             text: hwmon.fans[index].rpm + " " + i18n("rpm")
                         }
                         }
                     }
                     }
@@ -85,19 +89,19 @@ RowLayout {
                     model: hwmon.temps.length
                     model: hwmon.temps.length
 
 
                     RowLayout {
                     RowLayout {
-                        width: parent.width
+                        width: parent.width - parent.padding * 2
 
 
                         Label {
                         Label {
-                            anchors.leftMargin: root.padding
+                            anchors.leftMargin: root.spacing
                             text: hwmon.temps[index].name + ": "
                             text: hwmon.temps[index].name + ": "
-                            Layout.maximumWidth: parent.width - tempValue.width - root.padding*2
+                            Layout.maximumWidth: parent.width - tempValue.width - root.spacing*2
                             Layout.alignment: Qt.AlignLeft
                             Layout.alignment: Qt.AlignLeft
                             clip: true
                             clip: true
                         }
                         }
                         Label {
                         Label {
                             id: tempValue
                             id: tempValue
                             Layout.alignment: Qt.AlignRight
                             Layout.alignment: Qt.AlignRight
-                            anchors.rightMargin: root.padding
+                            anchors.rightMargin: root.spacing
                             text: Units.fromCelsius(hwmon.temps[index].value, Fancontrol.Base.unit) + " " + i18n(Fancontrol.Base.unit)
                             text: Units.fromCelsius(hwmon.temps[index].value, Fancontrol.Base.unit) + " " + i18n(Fancontrol.Base.unit)
                         }
                         }
                     }
                     }
@@ -105,8 +109,4 @@ RowLayout {
             }
             }
         }
         }
     }
     }
-
-    SystemPalette {
-        id: palette
-    }
 }
 }

+ 4 - 255
fancontrol-gui/package/contents/ui/SettingsTab.qml

@@ -18,264 +18,13 @@
  */
  */
 
 
 
 
-import QtQuick 2.4
-import QtQuick.Layouts 1.10
-import QtQuick.Controls 2.3
-import QtQuick.Dialogs 1.2
-import org.kde.kirigami 2.0 as Kirigami
+import QtQuick 2.6
+import org.kde.kirigami 2.3 as Kirigami
 import Fancontrol.Qml 1.0 as Fancontrol
 import Fancontrol.Qml 1.0 as Fancontrol
 
 
 
 
-Item {
-    property QtObject systemdCom: Fancontrol.Base.hasSystemdCommunicator() ? Fancontrol.Base.systemdCom : null
-    property QtObject loader: Fancontrol.Base.loader
-    property int padding: Kirigami.Units.smallSpacing
-    property real textWidth: 0
-    property var locale: Qt.locale()
-
-    id: root
-
-    Column {
-        id: column
+Kirigami.Page {
+    Fancontrol.SettingsForm {
         anchors.fill: parent
         anchors.fill: parent
-        anchors.margins: padding
-        spacing: Kirigami.Units.smallSpacing
-
-        RowLayout {
-            width: parent.width
-
-            Label {
-                Layout.preferredWidth: root.textWidth
-                clip: true
-                text: i18n("Interval:")
-                horizontalAlignment: Text.AlignRight
-                Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
-            }
-            SpinBox {
-                id: intervalSpinBox
-
-                Layout.minimumWidth: implicitWidth
-                Layout.fillWidth: true
-                value: loader.interval
-                from: 1.0
-                editable: true
-                textFromValue: function(value, locale) { return Number(value).toLocaleString(locale, 'f', 0) + ' ' + i18np("second", "seconds", value) }
-                onValueModified: loader.interval = value
-
-                Connections {
-                    target: loader
-                    onIntervalChanged: if (loader.interval != intervalSpinBox.value) intervalSpinBox.value = loader.interval
-                }
-            }
-        }
-        RowLayout {
-            width: parent.width
-
-            Label {
-                Layout.preferredWidth: root.textWidth
-                clip: true
-                text: i18n("Minimum temperature for fan graphs:")
-                horizontalAlignment: Text.AlignRight
-                Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
-            }
-            SpinBox {
-                id: minTempBox
-
-                Layout.minimumWidth: implicitWidth
-                Layout.fillWidth: true
-//                 decimals: 2
-                from: Fancontrol.Units.fromKelvin(0, Fancontrol.Base.unit)
-                inputMethodHints: Qt.ImhFormattedNumbersOnly
-                editable: true
-                value: Fancontrol.Units.fromCelsius(Fancontrol.Base.minTemp, Fancontrol.Base.unit)
-                textFromValue: function(value, locale) { return Number(value).toLocaleString(locale, 'f', 2) + Fancontrol.Base.unit }
-                onValueModified: {
-                    Fancontrol.Base.minTemp = Fancontrol.Units.toCelsius(value, Fancontrol.Base.unit);
-                    if (value >= maxTempBox.value) maxTempBox.value = value + 1;
-                }
-
-                Connections {
-                    target: Fancontrol.Base
-                    onMinTempChanged: {
-                        if (Fancontrol.Units.fromCelsius(Fancontrol.Base.minTemp, Fancontrol.Base.unit) != minTempBox.value) {
-                            minTempBox.value = Fancontrol.Units.fromCelsius(Fancontrol.Base.minTemp, Fancontrol.Base.unit);
-                        }
-                    }
-                }
-            }
-        }
-        RowLayout {
-            width: parent.width
-
-            Label {
-                Layout.preferredWidth: root.textWidth
-                clip: true
-                text: i18n("Maximum temperature for fan graphs:")
-                horizontalAlignment: Text.AlignRight
-                Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
-            }
-            SpinBox {
-                id: maxTempBox
-
-                Layout.minimumWidth: implicitWidth
-                Layout.fillWidth: true
-//                 decimals: 2
-                from: Fancontrol.Units.fromKelvin(0, Fancontrol.Base.unit)
-                inputMethodHints: Qt.ImhFormattedNumbersOnly
-                editable: true
-                value: Fancontrol.Units.fromCelsius(Fancontrol.Base.maxTemp, Fancontrol.Base.unit)
-                textFromValue: function(value, locale) { return Number(value).toLocaleString(locale, 'f', 2) + Fancontrol.Base.unit }
-                onValueModified: {
-                    Fancontrol.Base.maxTemp = Fancontrol.Units.toCelsius(value, Fancontrol.Base.unit);
-                    if (value <= minTempBox.value) minTempBox.value = value - 1;
-                }
-
-                Connections {
-                    target: Fancontrol.Base
-                    onMaxTempChanged: {
-                        if (Fancontrol.Units.fromCelsius(Fancontrol.Base.maxTemp, Fancontrol.Base.unit) != maxTempBox.value) {
-                            maxTempBox.value = Fancontrol.Units.fromCelsius(Fancontrol.Base.maxTemp, Fancontrol.Base.unit);
-                        }
-                    }
-                }
-            }
-        }
-        RowLayout {
-            width: parent.width
-
-            Label {
-                Layout.preferredWidth: root.textWidth
-                clip: true
-                text: i18n("Path to the fancontrol config file:")
-                horizontalAlignment: Text.AlignRight
-                Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
-            }
-            Fancontrol.OptionInput {
-                id: fileInput
-
-                Layout.fillWidth: true
-                value: Fancontrol.Base.configUrl.toString().replace("file://", "")
-                onTextChanged: Fancontrol.Base.configUrl = text;
-            }
-            Button {
-                icon.name: "document-open"
-//                 tooltip: i18n("Open config file")
-                onClicked: openFileDialog.open();
-            }
-        }
-        Loader {
-            active: !!systemdCom
-            sourceComponent: RowLayout {
-                width: column.width
-
-                Label {
-                    Layout.preferredWidth: root.textWidth
-                    clip: true
-                    text: i18n("Name of the fancontrol systemd service:")
-                    horizontalAlignment: Text.AlignRight
-                    Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
-                }
-                Fancontrol.OptionInput {
-                    id: serviceNameInput
-
-                    Layout.minimumWidth: implicitWidth
-                    Layout.fillWidth: true
-                    color: !!systemdCom && systemdCom.serviceExists ? "green" : "red"
-                    value: Fancontrol.Base.serviceName
-                    onTextChanged: Fancontrol.Base.serviceName = text
-                }
-            }
-        }
-        Loader {
-            active: !!systemdCom
-            sourceComponent: RowLayout {
-                width: column.width
-
-                Label {
-                    Layout.preferredWidth: root.textWidth
-                    clip: true
-                    text: i18n("Enable service at boot:")
-                    horizontalAlignment: Text.AlignRight
-                    Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
-                }
-                CheckBox {
-                    id: autostartBox
-
-                    Layout.minimumWidth: implicitWidth
-                    Layout.fillWidth: true
-                    checked: !!systemdCom ? systemdCom.serviceEnabled : false
-                    onCheckedChanged: {
-                        if (!!systemdCom) {
-                            systemdCom.serviceEnabled = checked;
-                        }
-                    }
-
-                    Connections {
-                        target: systemdCom
-                        onServiceEnabledChanged: if (systemdCom.serviceEnabled != autostartBox.checked) autostartBox.checked = systemdCom.serviceEnabled
-                    }
-                }
-            }
-        }
-        RowLayout {
-            width: column.width
-
-            Label {
-                Layout.preferredWidth: root.textWidth
-                clip: true
-                text: i18n("Show tray icon:")
-                horizontalAlignment: Text.AlignRight
-                Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
-            }
-            CheckBox {
-                id: trayBox
-
-                Layout.minimumWidth: implicitWidth
-                Layout.fillWidth: true
-                checked: Fancontrol.Base.showTray
-                onCheckedChanged: Fancontrol.Base.showTray = checked
-
-                Connections {
-                    target: Fancontrol.Base
-                    onShowTrayChanged: if (Fancontrol.Base.showTray != trayBox.checked) trayBox.checked = Fancontrol.Base.showTray
-                }
-            }
-        }
-        RowLayout {
-            width: column.width
-            enabled: Fancontrol.Base.showTray
-
-            Label {
-                Layout.preferredWidth: root.textWidth
-                clip: true
-                text: i18n("Start minimized:")
-                horizontalAlignment: Text.AlignRight
-                Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
-            }
-            CheckBox {
-                id: startMinimizedBox
-
-                Layout.minimumWidth: implicitWidth
-                Layout.fillWidth: true
-                checked: Fancontrol.Base.startMinimized
-                onCheckedChanged: Fancontrol.Base.startMinimized = checked
-
-                Connections {
-                    target: Fancontrol.Base
-                    onStartMinimizedChanged: if (Fancontrol.Base.startMinimized != startMinimizedBox.checked) startMinimizedBox.checked = Fancontrol.Base.startMinimized
-                }
-            }
-        }
-
-        FileDialog {
-            id: openFileDialog
-            title: i18n("Please choose a configuration file")
-            folder: "file:///etc"
-            selectExisting: true
-            selectMultiple: false
-            modality: Qt.NonModal
-
-            onAccepted: fileInput.text = fileUrl;
-        }
     }
     }
 }
 }

+ 1 - 1
fancontrol-gui/src/systemtrayicon.cpp

@@ -27,7 +27,7 @@ SystemTrayIcon::SystemTrayIcon(QObject *parent) : KStatusNotifierItem(QStringLit
 {
 {
     setCategory(KStatusNotifierItem::ApplicationStatus);
     setCategory(KStatusNotifierItem::ApplicationStatus);
 
 
-    m_profilesMenu = contextMenu()->addMenu(i18n("Profiles"));
+    m_profilesMenu = contextMenu()->addMenu(i18n("Apply profile"));
 }
 }
 
 
 void SystemTrayIcon::setProfileModel(QStringListModel* model)
 void SystemTrayIcon::setProfileModel(QStringListModel* model)

+ 3 - 1
import/CMakeLists.txt

@@ -12,9 +12,11 @@ set(LIB_SRCS src/hwmon.cpp
 
 
 set(QML_FILES qml/qmldir
 set(QML_FILES qml/qmldir
               qml/ErrorDialog.qml
               qml/ErrorDialog.qml
-              qml/OptionInput.qml
+              qml/FanHeader.qml
               qml/FanItem.qml
               qml/FanItem.qml
               qml/PwmPoint.qml
               qml/PwmPoint.qml
+              qml/ProfilesDialog.qml
+              qml/SettingsForm.qml
               qml/StatusPoint.qml
               qml/StatusPoint.qml
               qml/colors.js
               qml/colors.js
               qml/math.js
               qml/math.js

+ 3 - 3
import/qml/ErrorDialog.qml

@@ -18,9 +18,9 @@
  */
  */
 
 
 
 
-import QtQuick 2.4
-import QtQuick.Controls 2.3
-import org.kde.kirigami 2.0 as Kirigami
+import QtQuick 2.6
+import QtQuick.Controls 2.1
+import org.kde.kirigami 2.3 as Kirigami
 import Fancontrol.Qml 1.0 as Fancontrol
 import Fancontrol.Qml 1.0 as Fancontrol
 
 
 
 

+ 70 - 0
import/qml/FanHeader.qml

@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019  Malte Veerman <malte.veerman@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+import QtQuick 2.6
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.3 as Kirigami
+
+
+Item {
+    property QtObject fan
+
+    implicitHeight: layout.childrenRect.height
+
+    RowLayout {
+        id: layout
+
+        anchors.fill: parent
+        anchors.leftMargin: Kirigami.Units.smallSpacing
+        anchors.rightMargin: Kirigami.Units.smallSpacing
+
+        TextEdit {
+            id: nameField
+
+            Layout.alignment: Qt.AlignLeft
+            text: !!fan ? fan.name : ""
+            color: Kirigami.Theme.textColor
+            horizontalAlignment: TextEdit.AlignLeft
+            wrapMode: TextEdit.Wrap
+            font.bold: true
+            font.pointSize: Kirigami.Theme.defaultFont.pointSize + 2
+            selectByMouse: true
+
+            onTextChanged: if (!!fan && fan.name != text) fan.name = text
+
+            Connections {
+                target: !!fan ? fan : null
+                onNameChanged: if (fan.name != nameField.text) nameField.text = fan.name
+            }
+
+            MouseArea {
+                anchors.fill: parent
+                cursorShape: Qt.IBeamCursor
+                acceptedButtons: Qt.NoButton
+            }
+        }
+        Label {
+            Layout.alignment: Qt.AlignRight
+            text: !!fan ? fan.path : ""
+            horizontalAlignment: Text.AlignRight
+        }
+    }
+}

+ 20 - 96
import/qml/FanItem.qml

@@ -18,17 +18,17 @@
  */
  */
 
 
 
 
-import QtQuick 2.4
-import QtQuick.Controls 2.3
-import QtQuick.Layouts 1.10
-import org.kde.kirigami 2.0 as Kirigami
+import QtQuick 2.6
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.2 as Kirigami
 import Fancontrol.Qml 1.0 as Fancontrol
 import Fancontrol.Qml 1.0 as Fancontrol
 import "math.js" as MoreMath
 import "math.js" as MoreMath
 import "units.js" as Units
 import "units.js" as Units
 import "colors.js" as Colors
 import "colors.js" as Colors
 
 
 
 
-Rectangle {
+Item {
     property QtObject fan
     property QtObject fan
     property QtObject systemdCom
     property QtObject systemdCom
     property QtObject tempModel
     property QtObject tempModel
@@ -40,10 +40,7 @@ Rectangle {
     property real convertedMaxTemp: Units.fromCelsius(maxTemp, unit)
     property real convertedMaxTemp: Units.fromCelsius(maxTemp, unit)
 
 
     id: root
     id: root
-    color: "transparent"
-    border.color: palette.windowText
-    border.width: 2
-    radius: Kirigami.Units.smallSpacing
+
     clip: false
     clip: false
 
 
     onConvertedMinTempChanged: {
     onConvertedMinTempChanged: {
@@ -56,61 +53,17 @@ Rectangle {
     }
     }
     onFanChanged: curveCanvas.requestPaint()
     onFanChanged: curveCanvas.requestPaint()
 
 
-    SystemPalette {
-        id: palette
-        colorGroup: SystemPalette.Active
-    }
-    SystemPalette {
-        id: disabledPalette
-        colorGroup: SystemPalette.Disabled
-    }
-
-    TextEdit {
-        id: nameField
-
-        anchors {
-            left: parent.left
-            leftMargin: margin
-            right: parent.right
-            rightMargin: margin
-            top: parent.top
-            topMargin: margin
-        }
-        visible: root.height >= height + margin * 2
-        text: !!fan ? fan.name : ""
-        color: palette.text
-        horizontalAlignment: TextEdit.AlignLeft
-        wrapMode: TextEdit.Wrap
-        font.bold: true
-        font.pointSize: 14
-        selectByMouse: true
-
-        onTextChanged: if (!!fan && fan.name != text) fan.name = text
-
-        Connections {
-            target: fan
-            onNameChanged: if (fan.name != nameField.text) nameField.text = fan.name
-        }
-
-        MouseArea {
-            anchors.fill: parent
-            cursorShape: Qt.IBeamCursor
-            acceptedButtons: Qt.NoButton
-        }
-    }
-
     Item {
     Item {
         id: graph
         id: graph
 
 
         property int fontSize: MoreMath.bound(8, height / 20 + 1, 16)
         property int fontSize: MoreMath.bound(8, height / 20 + 1, 16)
-        property QtObject pal: !!fan ? fan.hasTemp ? palette : disabledPalette : disabledPalette
         property int verticalScalaCount: 6
         property int verticalScalaCount: 6
         property var horIntervals: MoreMath.intervals(root.convertedMinTemp, root.convertedMaxTemp, 10)
         property var horIntervals: MoreMath.intervals(root.convertedMinTemp, root.convertedMaxTemp, 10)
 
 
         anchors {
         anchors {
             left: parent.left
             left: parent.left
             right: parent.right
             right: parent.right
-            top: nameField.bottom
+            top: parent.top
             bottom: settingsArea.top
             bottom: settingsArea.top
         }
         }
         visible: graphBackground.height > 0 && graphBackground.width > 0
         visible: graphBackground.height > 0 && graphBackground.width > 0
@@ -130,12 +83,12 @@ Rectangle {
 
 
                 model: graph.verticalScalaCount
                 model: graph.verticalScalaCount
 
 
-                Label {
+                Text {
                     x: verticalScala.width - implicitWidth - graph.fontSize / 3
                     x: verticalScala.width - implicitWidth - graph.fontSize / 3
                     y: graphBackground.height - graphBackground.height / (graph.verticalScalaCount - 1) * index - graph.fontSize * 2 / 3
                     y: graphBackground.height - graphBackground.height / (graph.verticalScalaCount - 1) * index - graph.fontSize * 2 / 3
                     horizontalAlignment: Text.AlignRight
                     horizontalAlignment: Text.AlignRight
-                    color: graph.pal.text
-                    text: i18n("%1\%", index * (100 / (graph.verticalScalaCount - 1)))
+                    color: Kirigami.Theme.textColor
+                    text: Number(index * (100 / (graph.verticalScalaCount - 1))).toLocaleString(locale, 'f', 0) + locale.percent
                     font.pixelSize: graph.fontSize
                     font.pixelSize: graph.fontSize
                 }
                 }
             }
             }
@@ -154,11 +107,11 @@ Rectangle {
             Repeater {
             Repeater {
                 model: graph.horIntervals.length;
                 model: graph.horIntervals.length;
 
 
-                Label {
+                Text {
                     x: graphBackground.scaleX(Units.toCelsius(graph.horIntervals[index], unit)) - width/2
                     x: graphBackground.scaleX(Units.toCelsius(graph.horIntervals[index], unit)) - width/2
                     y: horizontalScala.height / 2 - implicitHeight / 2
                     y: horizontalScala.height / 2 - implicitHeight / 2
-                    color: graph.pal.text
-                    text: i18n("%1" + unit, graph.horIntervals[index])
+                    color: Kirigami.Theme.textColor
+                    text: Number(graph.horIntervals[index]).toLocaleString() + i18n(unit)
                     font.pixelSize: graph.fontSize
                     font.pixelSize: graph.fontSize
                 }
                 }
             }
             }
@@ -167,10 +120,8 @@ Rectangle {
         Rectangle {
         Rectangle {
             id: graphBackground
             id: graphBackground
 
 
-            property alias pal: graph.pal
-
-            color: pal.light
-            border.color: pal.text
+            color: Kirigami.Theme.backgroundColor
+            border.color: Kirigami.Theme.textColor
             border.width: 2
             border.width: 2
             radius: 1
             radius: 1
 
 
@@ -205,8 +156,6 @@ Rectangle {
                 anchors.margins: parent.border.width
                 anchors.margins: parent.border.width
                 renderStrategy: Canvas.Cooperative
                 renderStrategy: Canvas.Cooperative
 
 
-                property alias pal: graphBackground.pal
-
                 onPaint: {
                 onPaint: {
                     var c = curveCanvas.getContext("2d");
                     var c = curveCanvas.getContext("2d");
                     c.clearRect(0, 0, width, height);
                     c.clearRect(0, 0, width, height);
@@ -256,15 +205,13 @@ Rectangle {
                 anchors.margins: parent.border.width
                 anchors.margins: parent.border.width
                 renderStrategy: Canvas.Cooperative
                 renderStrategy: Canvas.Cooperative
 
 
-                property alias pal: graphBackground.pal
-
                 onPaint: {
                 onPaint: {
                     var c = meshCanvas.getContext("2d");
                     var c = meshCanvas.getContext("2d");
                     c.clearRect(0, 0, width, height);
                     c.clearRect(0, 0, width, height);
 
 
                     //draw mesh
                     //draw mesh
                     c.beginPath();
                     c.beginPath();
-                    c.strokeStyle = Colors.setAlpha(pal.text, 0.3);
+                    c.strokeStyle = Colors.setAlpha(Kirigami.Theme.textColor, 0.3);
 
 
                     //horizontal lines
                     //horizontal lines
                     for (var i=0; i<=100; i+=20) {
                     for (var i=0; i<=100; i+=20) {
@@ -298,7 +245,7 @@ Rectangle {
             }
             }
             PwmPoint {
             PwmPoint {
                 id: stopPoint
                 id: stopPoint
-                color: !!fan ? fan.hasTemp ? "blue" : Qt.tint(graph.pal.light, Qt.rgba(0, 0, 1, 0.5)) : "transparent"
+                color: !!fan ? fan.hasTemp ? "blue" : Qt.tint(Kirigami.Theme.disabledTextColor, Qt.rgba(0, 0, 1, 0.5)) : "transparent"
                 size: graph.fontSize
                 size: graph.fontSize
                 visible: !!fan ? fan.hasTemp : false
                 visible: !!fan ? fan.hasTemp : false
                 drag.maximumX: Math.min(graphBackground.scaleX(graphBackground.scaleTemp(maxPoint.x)-1), maxPoint.x-1)
                 drag.maximumX: Math.min(graphBackground.scaleX(graphBackground.scaleTemp(maxPoint.x)-1), maxPoint.x-1)
@@ -323,7 +270,7 @@ Rectangle {
             }
             }
             PwmPoint {
             PwmPoint {
                 id: maxPoint
                 id: maxPoint
-                color: !!fan ? fan.hasTemp ? "red" : Qt.tint(graph.pal.light, Qt.rgba(1, 0, 0, 0.5)) : "transparent"
+                color: !!fan ? fan.hasTemp ? "red" : Qt.tint(Kirigami.Theme.disabledTextColor, Qt.rgba(1, 0, 0, 0.5)) : "transparent"
                 size: graph.fontSize
                 size: graph.fontSize
                 visible: !!fan ? fan.hasTemp : false
                 visible: !!fan ? fan.hasTemp : false
                 drag.minimumX: Math.max(graphBackground.scaleX(graphBackground.scaleTemp(stopPoint.x)+1), stopPoint.x+1)
                 drag.minimumX: Math.max(graphBackground.scaleX(graphBackground.scaleTemp(stopPoint.x)+1), stopPoint.x+1)
@@ -359,7 +306,7 @@ Rectangle {
             bottom: parent.bottom
             bottom: parent.bottom
             bottomMargin: padding
             bottomMargin: padding
         }
         }
-        visible: root.height >= nameField.height + height + 2*margin
+        visible: root.height >= height + 2*margin
         clip: true
         clip: true
         spacing: 2
         spacing: 2
 
 
@@ -450,7 +397,7 @@ Rectangle {
                 to: 100
                 to: 100
                 editable: true
                 editable: true
                 value: !!fan ? Math.round(fan.minStart / 2.55) : 0
                 value: !!fan ? Math.round(fan.minStart / 2.55) : 0
-                textFromValue: function(value, locale) { return Number(value).toLocaleString(locale, 'f', 1) + i18n("%") }
+                textFromValue: function(value, locale) { return Number(value).toLocaleString(locale, 'f', 1) + locale.percent }
                 onValueModified: {
                 onValueModified: {
                     if (!!fan) {
                     if (!!fan) {
                         fan.minStart = Math.round(value * 2.55)
                         fan.minStart = Math.round(value * 2.55)
@@ -467,28 +414,5 @@ Rectangle {
                 }
                 }
             }
             }
         }
         }
-
-        RowLayout {
-            visible: !!systemdCom
-
-            Item {
-                Layout.fillWidth: true
-            }
-            Button {
-                id: testButton
-
-                text: !!fan ? fan.testing ? i18n("Abort test") : i18n("Test start and stop values") : ""
-                icon.name: "dialog-password"
-                Layout.alignment: Qt.AlignRight
-                onClicked: {
-                    if (fan.testing) {
-                        fan.abortTest();
-                    } else {
-                        minStartInput.value = Qt.binding(function() { return Math.round(fan.minStart / 2.55) });
-                        fan.test();
-                    }
-                }
-            }
-        }
     }
     }
 }
 }

+ 0 - 81
import/qml/OptionInput.qml

@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2015  Malte Veerman <malte.veerman@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * 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 Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- */
-
-
-import QtQuick 2.4
-import QtQuick.Layouts 1.1
-import org.kde.kirigami 2.0 as Kirigami
-
-
-FocusScope {
-    property alias text: textField.text
-    property alias font: textField.font
-    property alias inputMethodHints: textField.inputMethodHints
-    property alias validator: textField.validator
-    property alias color: textField.color
-    property real margin: Kirigami.Units.smallSpacing
-    property var value
-    property var locale: Qt.locale()
-
-    id: root
-    implicitHeight: textField.implicitHeight + margin * 2
-    implicitWidth: textField.implicitWidth + margin * 2
-
-    onValueChanged: {
-        if (textField.text != value) {
-            textField.text = value;
-        }
-    }
-
-    Rectangle {
-        id: rect
-        anchors.fill: parent
-        border.width: 1
-        radius: 2
-        color: enabled ? palette.base : disabledPalette.base
-        border.color: enabled ? mouseArea.containsMouse || root.activeFocus ? palette.highlight : palette.windowText : disabledPalette.windowText
-
-        TextInput {
-            id: textField
-            anchors.fill: parent
-            anchors.leftMargin: margin
-            horizontalAlignment: TextEdit.AlignLeft
-            verticalAlignment: TextEdit.AlignVCenter
-            renderType: Text.NativeRendering
-            selectByMouse: true
-            color: enabled ? palette.text : disabledPalette.text
-
-            MouseArea {
-                id: mouseArea
-                anchors.fill: parent
-                hoverEnabled: true
-                cursorShape: Qt.IBeamCursor
-                acceptedButtons: Qt.NoButton
-            }
-        }
-
-        SystemPalette {
-            id: palette
-        }
-        SystemPalette {
-            id: disabledPalette
-            colorGroup: SystemPalette.Disabled
-        }
-    }
-}

+ 126 - 0
import/qml/ProfilesDialog.qml

@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <malte.veerman@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+import QtQuick 2.6
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.2
+import org.kde.kirigami 2.3 as Kirigami
+import Fancontrol.Qml 1.0 as Fancontrol
+
+
+Dialog {
+    title: i18n("Manage profiles")
+    width: Kirigami.Units.gridUnit * 20
+    height: Kirigami.Units.gridUnit * 20
+    standardButtons: Dialog.Close
+
+    RowLayout {
+        anchors.fill: parent
+
+        Rectangle {
+            Layout.fillWidth: true
+            Layout.fillHeight: true
+            Kirigami.Theme.colorSet: Kirigami.Theme.View
+            color: Kirigami.Theme.backgroundColor
+            border.width: 1
+            border.color: Kirigami.Theme.textColor
+
+            ListView {
+                id: profilesListView
+
+                anchors.fill: parent
+                anchors.margins: parent.border.width
+                model: Fancontrol.Base.profileModel
+                clip: true
+                currentIndex: Fancontrol.Base.currentProfileIndex
+                boundsBehavior: Flickable.StopAtBounds
+                flickableDirection: Flickable.AutoFlickIfNeeded
+                header: Kirigami.BasicListItem {
+                    label: '<b>' + i18n("Profiles") + '</b>'
+                    reserveSpaceForIcon: false
+                    hoverEnabled: false
+                    separatorVisible: false
+                    leftPadding: Kirigami.Units.smallSpacing
+                }
+                delegate: Kirigami.BasicListItem {
+                    label: {
+                        if (Fancontrol.Base.currentProfileIndex === index)
+                            return display + ' ' + i18n("(current profile)")
+                        else
+                            return display
+                    }
+                    reserveSpaceForIcon: false
+                    hoverEnabled: true
+                    highlighted: ListView.isCurrentItem
+                    separatorVisible: false
+
+                    onPressedChanged: if (pressed) profilesListView.currentIndex = index;
+                }
+            }
+        }
+
+        ColumnLayout {
+            Layout.fillHeight: true
+
+            Button {
+                text: i18n("Apply profile")
+                enabled: Fancontrol.Base.currentProfileIndex != profilesListView.currentIndex
+                onClicked: Fancontrol.Base.applyProfile(profilesListView.currentIndex)
+            }
+            Button {
+                text: i18n("Create new profile")
+                enabled: Fancontrol.Base.currentProfileIndex == -1
+                onClicked: newProfileNameDialog.open()
+            }
+            Button {
+                text: i18n("Save to profile")
+                enabled: Fancontrol.Base.currentProfileIndex != profilesListView.currentIndex && profilesListView.currentIndex >= 0
+                onClicked: Fancontrol.Base.saveProfile(profilesListView.currentItem.label)
+            }
+            Button {
+                text: i18n("Delete profile")
+                enabled: profilesListView.currentIndex >= 0
+                onClicked: Fancontrol.Base.deleteProfile(profilesListView.currentIndex)
+            }
+            Item {
+                Layout.fillHeight: true
+            }
+        }
+    }
+
+    Dialog {
+        id: newProfileNameDialog
+
+        title: i18n("New profile's name")
+        standardButtons: Dialog.Ok | Dialog.Cancel
+        visible: false
+        anchors.centerIn: parent
+
+        onAccepted: {
+            Fancontrol.Base.saveProfile(newProfileNameField.text);
+            newProfileNameField.text = "";
+        }
+        onRejected: newProfileNameField.text = ""
+
+        TextField {
+            id: newProfileNameField
+        }
+    }
+}

+ 17 - 12
import/qml/PwmPoint.qml

@@ -18,9 +18,9 @@
  */
  */
 
 
 
 
-import QtQuick 2.4
-import QtQuick.Controls 2.3
-import org.kde.kirigami 2.0 as Kirigami
+import QtQuick 2.6
+import QtQuick.Controls 2.1
+import org.kde.kirigami 2.3 as Kirigami
 import Fancontrol.Qml 1.0 as Fancontrol
 import Fancontrol.Qml 1.0 as Fancontrol
 import "units.js" as Units
 import "units.js" as Units
 
 
@@ -68,24 +68,29 @@ Rectangle {
 
 
     Rectangle {
     Rectangle {
         id: tooltip
         id: tooltip
+
         x: parent.width
         x: parent.width
         y: - height
         y: - height
-        width: Math.max(pwm.width, temp.width)
-        height: pwm.height + temp.height
-        radius: 4
+        width: column.width
+        height: column.height
+        radius: Kirigami.Units.smallSpacing / 2
         color: Qt.rgba(parent.color.r, parent.color.g, parent.color.b, 0.5)
         color: Qt.rgba(parent.color.r, parent.color.g, parent.color.b, 0.5)
         visible: root.enabled && (pwmMouse.containsMouse || drag.active)
         visible: root.enabled && (pwmMouse.containsMouse || drag.active)
 
 
         Column {
         Column {
-            Label {
-                id: pwm
+            id: column
+
+            padding: Kirigami.Units.smallSpacing
+
+            Text {
                 font.pixelSize: root.size * 1.5
                 font.pixelSize: root.size * 1.5
-                text: Number(Math.round(root.pwm / 2.55)).toLocaleString(locale, 'f', 1) + i18n('%')
+                text: Number(Units.fromCelsius(root.temp, unit)).toLocaleString(locale, 'f', 0) + i18n(unit)
+                color: Kirigami.Theme.textColor
             }
             }
-            Label {
-                id: temp
+            Text {
                 font.pixelSize: root.size * 1.5
                 font.pixelSize: root.size * 1.5
-                text: Math.round(Units.fromCelsius(root.temp, unit)) + i18n(unit)
+                text: Number(root.pwm / 2.55).toLocaleString(locale, 'f', 1) + locale.percent
+                color: Kirigami.Theme.textColor
             }
             }
         }
         }
     }
     }

+ 174 - 0
import/qml/SettingsForm.qml

@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2015  Malte Veerman <malte.veerman@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+import QtQuick 2.6
+import QtQuick.Layouts 1.2
+import QtQuick.Controls 2.1
+import QtQuick.Dialogs 1.2
+import org.kde.kirigami 2.3 as Kirigami
+import Fancontrol.Qml 1.0 as Fancontrol
+
+
+Kirigami.FormLayout {
+    id: root
+
+    property QtObject systemdCom: Fancontrol.Base.hasSystemdCommunicator() ? Fancontrol.Base.systemdCom : null
+    property QtObject loader: Fancontrol.Base.loader
+    property bool showAll: true
+
+    SpinBox {
+        id: intervalSpinBox
+
+        Kirigami.FormData.label: i18n("Interval:")
+        Layout.fillWidth: true
+        value: loader.interval
+        from: 1.0
+        editable: true
+        textFromValue: function(value, locale) { return Number(value).toLocaleString(locale, 'f', 0) + ' ' + i18np("second", "seconds", value) }
+        onValueModified: loader.interval = value
+
+        Connections {
+            target: loader
+            onIntervalChanged: if (loader.interval != intervalSpinBox.value) intervalSpinBox.value = loader.interval
+        }
+    }
+    SpinBox {
+        id: minTempBox
+
+        Kirigami.FormData.label: i18n("Minimum temperature for fan graphs:")
+        Layout.fillWidth: true
+        from: Fancontrol.Units.fromKelvin(0, Fancontrol.Base.unit)
+        inputMethodHints: Qt.ImhFormattedNumbersOnly
+        editable: true
+        value: Fancontrol.Units.fromCelsius(Fancontrol.Base.minTemp, Fancontrol.Base.unit)
+        textFromValue: function(value, locale) { return Number(value).toLocaleString(locale, 'f', 2) + Fancontrol.Base.unit }
+        onValueModified: {
+            Fancontrol.Base.minTemp = Fancontrol.Units.toCelsius(value, Fancontrol.Base.unit);
+            if (value >= maxTempBox.value) maxTempBox.value = value + 1;
+        }
+
+        Connections {
+            target: Fancontrol.Base
+            onMinTempChanged: {
+                if (Fancontrol.Units.fromCelsius(Fancontrol.Base.minTemp, Fancontrol.Base.unit) != minTempBox.value) {
+                    minTempBox.value = Fancontrol.Units.fromCelsius(Fancontrol.Base.minTemp, Fancontrol.Base.unit);
+                }
+            }
+        }
+    }
+    SpinBox {
+        id: maxTempBox
+
+        Kirigami.FormData.label: i18n("Maximum temperature for fan graphs:")
+        Layout.fillWidth: true
+        from: Fancontrol.Units.fromKelvin(0, Fancontrol.Base.unit)
+        inputMethodHints: Qt.ImhFormattedNumbersOnly
+        editable: true
+        value: Fancontrol.Units.fromCelsius(Fancontrol.Base.maxTemp, Fancontrol.Base.unit)
+        textFromValue: function(value, locale) { return Number(value).toLocaleString(locale, 'f', 2) + Fancontrol.Base.unit }
+        onValueModified: {
+            Fancontrol.Base.maxTemp = Fancontrol.Units.toCelsius(value, Fancontrol.Base.unit);
+            if (value <= minTempBox.value) minTempBox.value = value - 1;
+        }
+
+        Connections {
+            target: Fancontrol.Base
+            onMaxTempChanged: {
+                if (Fancontrol.Units.fromCelsius(Fancontrol.Base.maxTemp, Fancontrol.Base.unit) != maxTempBox.value) {
+                    maxTempBox.value = Fancontrol.Units.fromCelsius(Fancontrol.Base.maxTemp, Fancontrol.Base.unit);
+                }
+            }
+        }
+    }
+    RowLayout {
+        Kirigami.FormData.label: i18n("Path to the fancontrol config file:")
+        Layout.fillWidth: true
+
+        TextField {
+            id: fileInput
+
+            Layout.fillWidth: true
+            text: Fancontrol.Base.configUrl.toString().replace("file://", "")
+            onTextChanged: Fancontrol.Base.configUrl = text;
+
+            Connections {
+                target: Fancontrol.Base
+                onConfigUrlChanged: if(Fancontrol.Base.configUrl.toString().replace("file://", "") != fileInput.text) fileInput.text = Fancontrol.Base.configUrl.toString().replace("file://", "")
+            }
+        }
+        Button {
+            icon.name: "document-open"
+            //                 tooltip: i18n("Open config file")
+            onClicked: openFileDialog.open();
+        }
+    }
+    TextField {
+        id: serviceNameInput
+
+        visible: !!systemdCom
+        Kirigami.FormData.label: i18n("Name of the fancontrol systemd service:")
+        Layout.fillWidth: true
+        color: !!systemdCom && systemdCom.serviceExists ? "green" : "red"
+        text: Fancontrol.Base.serviceName
+        onTextChanged: Fancontrol.Base.serviceName = text
+
+        Connections {
+            target: Fancontrol.Base
+            onServiceNameChanged: if(Fancontrol.Base.serviceName != serviceNameInput.text) serviceNameInput.text = Fancontrol.Base.serviceName
+        }
+    }
+    CheckBox {
+        id: trayBox
+
+        Kirigami.FormData.label: i18n("Show tray icon:")
+        checked: Fancontrol.Base.showTray
+        visible: showAll
+        onCheckedChanged: Fancontrol.Base.showTray = checked
+
+        Connections {
+            target: Fancontrol.Base
+            onShowTrayChanged: if (Fancontrol.Base.showTray != trayBox.checked) trayBox.checked = Fancontrol.Base.showTray
+        }
+    }
+    CheckBox {
+        id: startMinimizedBox
+
+        Kirigami.FormData.label: i18n("Start minimized:")
+        checked: Fancontrol.Base.startMinimized
+        visible: showAll
+        onCheckedChanged: Fancontrol.Base.startMinimized = checked
+
+        Connections {
+            target: Fancontrol.Base
+            onStartMinimizedChanged: if (Fancontrol.Base.startMinimized != startMinimizedBox.checked) startMinimizedBox.checked = Fancontrol.Base.startMinimized
+        }
+    }
+
+    FileDialog {
+        id: openFileDialog
+        title: i18n("Please choose a configuration file")
+        folder: "file:///etc"
+        selectExisting: true
+        selectMultiple: false
+        modality: Qt.NonModal
+
+        onAccepted: fileInput.text = fileUrl;
+    }
+}

+ 30 - 23
import/qml/StatusPoint.qml

@@ -18,9 +18,9 @@
  */
  */
 
 
 
 
-import QtQuick 2.4
-import QtQuick.Controls 2.3
-import org.kde.kirigami 2.0 as Kirigami
+import QtQuick 2.6
+import QtQuick.Controls 2.1
+import org.kde.kirigami 2.3 as Kirigami
 import Fancontrol.Qml 1.0 as Fancontrol
 import Fancontrol.Qml 1.0 as Fancontrol
 import "units.js" as Units
 import "units.js" as Units
 import "math.js" as MoreMath
 import "math.js" as MoreMath
@@ -33,6 +33,7 @@ Rectangle {
     property Item background: parent
     property Item background: parent
     property real unscaledTemp: !!fan && fan.hasTemp && !!fan.temp ? fan.temp.value : 0
     property real unscaledTemp: !!fan && fan.hasTemp && !!fan.temp ? fan.temp.value : 0
     property real unscaledPwm: !!fan ? fan.pwm : 0
     property real unscaledPwm: !!fan ? fan.pwm : 0
+    property real unscaledRpm: !!fan ? fan.rpm : 0
     property var locale: Qt.locale()
     property var locale: Qt.locale()
     readonly property real centerX: x + width / 2
     readonly property real centerX: x + width / 2
     readonly property real centerY: y + height / 2
     readonly property real centerY: y + height / 2
@@ -51,17 +52,23 @@ Rectangle {
         SpringAnimation {
         SpringAnimation {
             epsilon: 0.1
             epsilon: 0.1
             spring: 1.0
             spring: 1.0
-            damping: 0.4
+            damping: 0.6
         }
         }
     }
     }
     Behavior on unscaledPwm {
     Behavior on unscaledPwm {
         SpringAnimation {
         SpringAnimation {
             epsilon: 0.1
             epsilon: 0.1
             spring: 1.0
             spring: 1.0
-            damping: 0.4
+            damping: 0.6
+        }
+    }
+    Behavior on unscaledRpm {
+        SpringAnimation {
+            epsilon: 0.1
+            spring: 1.0
+            damping: 0.6
         }
         }
     }
     }
-
     MouseArea {
     MouseArea {
         id: pwmMouse
         id: pwmMouse
 
 
@@ -74,31 +81,31 @@ Rectangle {
 
 
         x: parent.width
         x: parent.width
         y: - height
         y: - height
-        width: Math.max(pwm.width, rpm.width)
-        height: temp.height + pwm.height + rpm.height
-        radius: 4
+        width: column.width
+        height: column.height
+        radius: Kirigami.Units.smallSpacing / 2
         color: Qt.rgba(parent.color.r, parent.color.g, parent.color.b, 0.5)
         color: Qt.rgba(parent.color.r, parent.color.g, parent.color.b, 0.5)
         visible: root.enabled && pwmMouse.containsMouse
         visible: root.enabled && pwmMouse.containsMouse
 
 
         Column {
         Column {
-            Label {
-                id: temp
+            id: column
 
 
-                font.pixelSize: root.height * 1.5
-                text: (!!fan && fan.hasTemp ? Math.round(Units.fromCelsius(root.unscaledTemp, unit)) : "0") + i18n(unit)
+            padding: Kirigami.Units.smallSpacing
 
 
+            Text {
+                font.pixelSize: root.size * 1.5
+                text: Number(Units.fromCelsius(root.unscaledTemp, unit)).toLocaleString(locale, 'f', 1) + i18n(unit)
+                color: Kirigami.Theme.textColor
             }
             }
-            Label {
-                id: pwm
-
-                font.pixelSize: root.height * 1.5
-                text: Number(Math.round(unscaledPwm / 2.55)).toLocaleString(locale, 'f', 1) + i18n('%')
+            Text {
+                font.pixelSize: root.size * 1.5
+                text: Number(unscaledPwm / 2.55).toLocaleString(locale, 'f', 1) + locale.percent
+                color: Kirigami.Theme.textColor
             }
             }
-            Label {
-                id: rpm
-
-                font.pixelSize: root.height * 1.5
-                text: (!!fan ? fan.rpm : "0") + i18n("rpm")
+            Text {
+                font.pixelSize: root.size * 1.5
+                text: Number(unscaledRpm).toLocaleString(locale, 'f', 0) + i18n("rpm")
+                color: Kirigami.Theme.textColor
             }
             }
         }
         }
     }
     }

+ 3 - 1
import/qml/qmldir

@@ -2,8 +2,10 @@ module Fancontrol.Qml
 plugin fancontrol_qml_plugin
 plugin fancontrol_qml_plugin
 internal StatusPoint StatusPoint.qml
 internal StatusPoint StatusPoint.qml
 internal PwmPoint PwmPoint.qml
 internal PwmPoint PwmPoint.qml
+FanHeader 1.0 FanHeader.qml
 FanItem 1.0 FanItem.qml
 FanItem 1.0 FanItem.qml
 ErrorDialog 1.0 ErrorDialog.qml
 ErrorDialog 1.0 ErrorDialog.qml
-OptionInput 1.0 OptionInput.qml
+ProfilesDialog 1.0 ProfilesDialog.qml
+SettingsForm 1.0 SettingsForm.qml
 Units 1.0 units.js
 Units 1.0 units.js
 
 

+ 52 - 20
import/src/guibase.cpp

@@ -48,6 +48,7 @@ GUIBase::GUIBase(QObject *parent) : QObject(parent),
 {
 {
     connect(this, &GUIBase::unitChanged, m_tempModel, &TempModel::setUnit);
     connect(this, &GUIBase::unitChanged, m_tempModel, &TempModel::setUnit);
     connect(m_loader, &Loader::needsSaveChanged, this, &GUIBase::needsApplyChanged);
     connect(m_loader, &Loader::needsSaveChanged, this, &GUIBase::needsApplyChanged);
+    connect(m_loader, &Loader::configChanged, this, &GUIBase::currentProfileChanged);
 
 
 #ifndef NO_SYSTEMD
 #ifndef NO_SYSTEMD
     connect(m_loader, &Loader::requestSetServiceActive, m_com, &SystemdCommunicator::setServiceActive);
     connect(m_loader, &Loader::requestSetServiceActive, m_com, &SystemdCommunicator::setServiceActive);
@@ -77,20 +78,30 @@ GUIBase::~GUIBase()
 void GUIBase::load()
 void GUIBase::load()
 {
 {
     Config::instance()->load();
     Config::instance()->load();
-    m_configValid = m_loader->load(configUrl());
 
 
+    // Check profiles
+    auto profiles = Config::instance()->findItem(QStringLiteral("Profiles"))->property().toStringList();
     auto profileNames = Config::instance()->findItem(QStringLiteral("ProfileNames"))->property().toStringList();
     auto profileNames = Config::instance()->findItem(QStringLiteral("ProfileNames"))->property().toStringList();
+
+    while (profiles.size() > profileNames.size())
+        profiles.removeLast();
+
+    while (profiles.size() < profileNames.size())
+        profileNames.removeLast();
+
+    Config::instance()->findItem(QStringLiteral("Profiles"))->setProperty(profiles);
+    Config::instance()->findItem(QStringLiteral("ProfileNames"))->setProperty(profileNames);
+
     m_profileModel->setStringList(profileNames);
     m_profileModel->setStringList(profileNames);
 
 
-    Config::instance()->setCurrentGroup(QStringLiteral("preferences"));
-    int currentProfile = Config::instance()->findItem(QStringLiteral("CurrentProfile"))->property().toInt();
-    emit profileChanged(currentProfile);
+    m_configValid = m_loader->load(configUrl());
 
 
 #ifndef NO_SYSTEMD
 #ifndef NO_SYSTEMD
     m_com->setServiceName(serviceName());
     m_com->setServiceName(serviceName());
     m_com->reset();
     m_com->reset();
 #endif
 #endif
 
 
+    emit currentProfileChanged();
     emit serviceNameChanged();
     emit serviceNameChanged();
     emit minTempChanged();
     emit minTempChanged();
     emit maxTempChanged();
     emit maxTempChanged();
@@ -272,15 +283,15 @@ void GUIBase::handleInfo(const QString &info)
         qInfo() << info;
         qInfo() << info;
 }
 }
 
 
-void GUIBase::applyProfile(const QString& profile)
+void GUIBase::applyProfile(const QString& profileName)
 {
 {
-    if (!Config::instance()->findItem(QStringLiteral("ProfileNames"))->property().toStringList().contains(profile))
+    if (!Config::instance()->findItem(QStringLiteral("ProfileNames"))->property().toStringList().contains(profileName))
     {
     {
-        handleError(i18n("Unable to apply unknown profile: %1", profile));
+        handleError(i18n("Unable to apply unknown profile: %1", profileName));
         return;
         return;
     }
     }
 
 
-    int index = Config::instance()->findItem(QStringLiteral("ProfileNames"))->property().toStringList().indexOf(profile);
+    int index = Config::instance()->findItem(QStringLiteral("ProfileNames"))->property().toStringList().indexOf(profileName);
 
 
     applyProfile(index);
     applyProfile(index);
 }
 }
@@ -306,22 +317,19 @@ void GUIBase::applyProfile(int index)
         return;
         return;
 
 
     m_loader->load(newConfig);
     m_loader->load(newConfig);
-
-    Config::instance()->findItem(QStringLiteral("CurrentProfile"))->setProperty(index);
-    emit profileChanged(index);
 }
 }
 
 
-void GUIBase::saveProfile(const QString& profile, bool updateModel)
+void GUIBase::saveProfile(const QString& profileName, bool updateModel)
 {
 {
     auto profileNames = Config::instance()->findItem(QStringLiteral("ProfileNames"))->property().toStringList();
     auto profileNames = Config::instance()->findItem(QStringLiteral("ProfileNames"))->property().toStringList();
-    int index = profileNames.indexOf(profile);
+    int index = profileNames.indexOf(profileName);
 
 
     if (index < 0)
     if (index < 0)
     {
     {
         index = profileNames.size();
         index = profileNames.size();
 
 
         auto profileNames = Config::instance()->findItem(QStringLiteral("ProfileNames"))->property().toStringList();
         auto profileNames = Config::instance()->findItem(QStringLiteral("ProfileNames"))->property().toStringList();
-        Config::instance()->findItem(QStringLiteral("ProfileNames"))->setProperty(profileNames << profile);
+        Config::instance()->findItem(QStringLiteral("ProfileNames"))->setProperty(profileNames << profileName);
 
 
         if (updateModel)
         if (updateModel)
             m_profileModel->insertRow(index);
             m_profileModel->insertRow(index);
@@ -331,13 +339,15 @@ void GUIBase::saveProfile(const QString& profile, bool updateModel)
     profiles.insert(index, m_loader->config());
     profiles.insert(index, m_loader->config());
     Config::instance()->findItem(QStringLiteral("Profiles"))->setProperty(profiles);
     Config::instance()->findItem(QStringLiteral("Profiles"))->setProperty(profiles);
 
 
+    emit currentProfileChanged();
+
     if (updateModel)
     if (updateModel)
-        m_profileModel->setData(m_profileModel->index(index, 0), profile);
+        m_profileModel->setData(m_profileModel->index(index, 0), profileName);
 }
 }
 
 
-void GUIBase::deleteProfile(const QString& profile, bool updateModel)
+void GUIBase::deleteProfile(const QString& profileName, bool updateModel)
 {
 {
-    int index = Config::instance()->findItem(QStringLiteral("ProfileNames"))->property().toStringList().indexOf(profile);
+    int index = Config::instance()->findItem(QStringLiteral("ProfileNames"))->property().toStringList().indexOf(profileName);
 
 
     deleteProfile(index, updateModel);
     deleteProfile(index, updateModel);
 }
 }
@@ -345,18 +355,40 @@ void GUIBase::deleteProfile(const QString& profile, bool updateModel)
 void GUIBase::deleteProfile(int index, bool updateModel)
 void GUIBase::deleteProfile(int index, bool updateModel)
 {
 {
     auto profileNames = Config::instance()->findItem(QStringLiteral("ProfileNames"))->property().toStringList();
     auto profileNames = Config::instance()->findItem(QStringLiteral("ProfileNames"))->property().toStringList();
+    auto profiles = Config::instance()->findItem(QStringLiteral("Profiles"))->property().toStringList();
 
 
-    if (index < 0 || index >= profileNames.size())
+    if (index < 0 || index >= profileNames.size() || index >= profiles.size())
+    {
+        qDebug() << "Index out of bounds";
         return;
         return;
+    }
 
 
     profileNames.removeAt(index);
     profileNames.removeAt(index);
     Config::instance()->findItem(QStringLiteral("ProfileNames"))->setProperty(profileNames);
     Config::instance()->findItem(QStringLiteral("ProfileNames"))->setProperty(profileNames);
-    auto profiles = Config::instance()->findItem(QStringLiteral("Profiles"))->property().toStringList();
-    profileNames.removeAt(index);
+    profiles.removeAt(index);
     Config::instance()->findItem(QStringLiteral("Profiles"))->setProperty(profiles);
     Config::instance()->findItem(QStringLiteral("Profiles"))->setProperty(profiles);
 
 
+    emit currentProfileChanged();
+
     if (updateModel)
     if (updateModel)
         m_profileModel->removeRow(index);
         m_profileModel->removeRow(index);
 }
 }
 
 
+QString GUIBase::currentProfile() const
+{
+    const int index = currentProfileIndex();
+
+    if (index == -1)
+        return i18n("No profile");
+
+    const auto profileNames = Config::instance()->findItem(QStringLiteral("ProfileNames"))->property().toStringList();
+    return profileNames.value(index);
+}
+
+int GUIBase::currentProfileIndex() const
+{
+    const auto profiles = Config::instance()->findItem(QStringLiteral("Profiles"))->property().toStringList();
+    return profiles.indexOf(m_loader->config());
+}
+
 }
 }

+ 8 - 4
import/src/guibase.h

@@ -52,6 +52,8 @@ class GUIBase : public QObject
     Q_PROPERTY(PwmFanModel *pwmFanModel READ pwmFanModel CONSTANT)
     Q_PROPERTY(PwmFanModel *pwmFanModel READ pwmFanModel CONSTANT)
     Q_PROPERTY(TempModel *tempModel READ tempModel CONSTANT)
     Q_PROPERTY(TempModel *tempModel READ tempModel CONSTANT)
     Q_PROPERTY(QStringListModel *profileModel READ profileModel CONSTANT)
     Q_PROPERTY(QStringListModel *profileModel READ profileModel CONSTANT)
+    Q_PROPERTY(QString currentProfile READ currentProfile NOTIFY currentProfileChanged)
+    Q_PROPERTY(int currentProfileIndex READ currentProfileIndex NOTIFY currentProfileChanged)
     Q_PROPERTY(Loader* loader READ loader CONSTANT)
     Q_PROPERTY(Loader* loader READ loader CONSTANT)
     Q_PROPERTY(qreal minTemp READ minTemp WRITE setMinTemp NOTIFY minTempChanged)
     Q_PROPERTY(qreal minTemp READ minTemp WRITE setMinTemp NOTIFY minTempChanged)
     Q_PROPERTY(qreal maxTemp READ maxTemp WRITE setMaxTemp NOTIFY maxTempChanged)
     Q_PROPERTY(qreal maxTemp READ maxTemp WRITE setMaxTemp NOTIFY maxTempChanged)
@@ -95,14 +97,16 @@ public:
     PwmFanModel *pwmFanModel() const { return m_pwmFanModel; }
     PwmFanModel *pwmFanModel() const { return m_pwmFanModel; }
     TempModel *tempModel() const { return m_tempModel; }
     TempModel *tempModel() const { return m_tempModel; }
     QStringListModel *profileModel() const { return m_profileModel; }
     QStringListModel *profileModel() const { return m_profileModel; }
+    QString currentProfile() const;
+    int currentProfileIndex() const;
 
 
     Q_INVOKABLE bool hasSystemdCommunicator() const;
     Q_INVOKABLE bool hasSystemdCommunicator() const;
     Q_INVOKABLE void apply();
     Q_INVOKABLE void apply();
     Q_INVOKABLE void reset();
     Q_INVOKABLE void reset();
-    Q_INVOKABLE void applyProfile(const QString &profile);
+    Q_INVOKABLE void applyProfile(const QString &profileName);
     Q_INVOKABLE void applyProfile(int);
     Q_INVOKABLE void applyProfile(int);
-    Q_INVOKABLE void saveProfile(const QString &profile, bool updateModel = true);
-    Q_INVOKABLE void deleteProfile(const QString &profile, bool updateModel = true);
+    Q_INVOKABLE void saveProfile(const QString &profileName, bool updateModel = true);
+    Q_INVOKABLE void deleteProfile(const QString &profileName, bool updateModel = true);
     Q_INVOKABLE void deleteProfile(int, bool updateModel = true);
     Q_INVOKABLE void deleteProfile(int, bool updateModel = true);
 
 
 public slots:
 public slots:
@@ -123,7 +127,7 @@ signals:
     void needsApplyChanged();
     void needsApplyChanged();
     void showTrayChanged();
     void showTrayChanged();
     void startMinimizedChanged();
     void startMinimizedChanged();
-    void profileChanged(int profile);
+    void currentProfileChanged();
 
 
 private:
 private:
 
 

+ 1 - 0
kcm/fancontrol-kcm.desktop

@@ -7,6 +7,7 @@ X-KDE-ServiceTypes=KCModule
 X-KDE-Library=kcm_fancontrol
 X-KDE-Library=kcm_fancontrol
 X-KDE-PluginKeyword=Fans
 X-KDE-PluginKeyword=Fans
 X-KDE-System-Settings-Parent-Category=hardware
 X-KDE-System-Settings-Parent-Category=hardware
+X-KDE-ParentApp=kcontrol
 
 
 Name=Fancontrol
 Name=Fancontrol
 Comment=Control PWM-fans
 Comment=Control PWM-fans

+ 169 - 272
kcm/package/contents/ui/KCM.qml

@@ -18,28 +18,31 @@
  */
  */
 
 
 
 
-import QtQuick 2.4
-import QtQuick.Controls 2.3
-import QtQuick.Layouts 1.10
+import QtQuick 2.6
+import QtQuick.Controls 2.1
+import QtQuick.Layouts 1.2
 import QtQuick.Dialogs 1.2
 import QtQuick.Dialogs 1.2
-import org.kde.kirigami 2.0 as Kirigami
-import org.kde.kcm 1.0
+import org.kde.kirigami 2.3 as Kirigami
+import org.kde.kcm 1.0 as KCM
 import Fancontrol.Qml 1.0 as Fancontrol
 import Fancontrol.Qml 1.0 as Fancontrol
 
 
 
 
-Item {
+Kirigami.Page {
     property QtObject loader: Fancontrol.Base.loader
     property QtObject loader: Fancontrol.Base.loader
     property QtObject systemdCom: Fancontrol.Base.systemdCom
     property QtObject systemdCom: Fancontrol.Base.systemdCom
     property QtObject pwmFanModel: Fancontrol.Base.pwmFanModel
     property QtObject pwmFanModel: Fancontrol.Base.pwmFanModel
     property QtObject tempModel: Fancontrol.Base.tempModel
     property QtObject tempModel: Fancontrol.Base.tempModel
     property QtObject profileModel: Fancontrol.Base.profileModel
     property QtObject profileModel: Fancontrol.Base.profileModel
-    property var locale: Qt.locale()
     property real textWidth: 0
     property real textWidth: 0
     property var pwmFans: pwmFanModel.fans
     property var pwmFans: pwmFanModel.fans
+    property QtObject fan: pwmFans[fansListView.currentIndex]
 
 
     id: root
     id: root
-    implicitWidth: 1024
-    implicitHeight: 768
+
+    implicitWidth: Kirigami.Units.gridUnit * 50
+    implicitHeight: Kirigami.Units.gridUnit * 40
+
+    KCM.ConfigModule.quickHelp: i18n("This module lets you configure your PWM fans.")
 
 
     Connections {
     Connections {
         target: Fancontrol.Base
         target: Fancontrol.Base
@@ -58,11 +61,50 @@ Item {
         onAboutToDefault: enabledBox.checked = false
         onAboutToDefault: enabledBox.checked = false
     }
     }
 
 
+//     contextualActions: [
+//         Kirigami.Action {
+//             text: i18n("Profiles")
+//
+//             Kirigami.Action {
+//                 id: loadProfilesAction
+//
+//                 text: i18n("Load profile")
+//             }
+//             Kirigami.Action {
+//                 text: i18n("Save profile")
+//                 icon.name: "document-save"
+//                 onTriggered: Fancontrol.Base.saveProfile(profileComboBox.saveText)
+//             }
+//             Kirigami.Action {
+//                 text: i18n("Delete profile")
+//                 icon.name: "edit-delete"
+//                 onTriggered: Fancontrol.Base.deleteProfile(profileComboBox.currentIndex)
+//             }
+//         },
+//         Kirigami.Action {
+//             text: loader.sensorsDetected ? i18n("Detect fans again") : i18n("Detect fans")
+//             icon.name: "dialog-password"
+//             onTriggered: loader.detectSensors()
+//         },
+//         Kirigami.Action {
+//             visible: !!systemdCom && !!fan
+//             text: !!fan ? fan.testing ? i18n("Abort test") : i18n("Test start and stop values") : ""
+//             icon.name: "dialog-password"
+//             onTriggered: {
+//                 if (fan.testing) {
+//                     fan.abortTest();
+//                 } else {
+//                     fan.test();
+//                 }
+//             }
+//         }
+//     ]
+
     ColumnLayout {
     ColumnLayout {
         id: noFansInfo
         id: noFansInfo
 
 
-        width: parent.width
-        anchors.verticalCenter: parent.verticalCenter
+        width: root.width
+        y: root.height / 2 - height / 2
         spacing: Kirigami.Units.smallSpacing * 2
         spacing: Kirigami.Units.smallSpacing * 2
         visible: pwmFans.length === 0
         visible: pwmFans.length === 0
 
 
@@ -81,17 +123,14 @@ Item {
         }
         }
     }
     }
 
 
-    CheckBox {
+    header: CheckBox {
         id: enabledBox
         id: enabledBox
 
 
-        anchors.top: parent.top
-        visible: pwmFans.length > 0
         text: i18n("Control fans manually")
         text: i18n("Control fans manually")
         checked: systemdCom.serviceEnabled && systemdCom.serviceActive;
         checked: systemdCom.serviceEnabled && systemdCom.serviceActive;
         onCheckedChanged: {
         onCheckedChanged: {
             systemdCom.serviceActive = enabledBox.checked;
             systemdCom.serviceActive = enabledBox.checked;
             loader.restartServiceAfterTesting = checked;
             loader.restartServiceAfterTesting = checked;
-            autostartBox.checked = true;
         }
         }
 
 
         Connections {
         Connections {
@@ -100,303 +139,161 @@ Item {
         }
         }
     }
     }
 
 
-    ColumnLayout {
-        id: bodyLayout
-
-        width: parent.width
-        anchors.bottom: advancedButton.top
-        anchors.top: enabledBox.bottom
-        anchors.bottomMargin: advancedButton.height / 4
-        visible: enabledBox.checked
-
-        CheckBox {
-            id: autostartBox
-
-            text: i18n("Enable service at boot")
-            checked: systemdCom.serviceEnabled
-            onCheckedChanged: systemdCom.serviceEnabled = checked
-
-            Connections {
-                target: systemdCom
-                onServiceEnabledChanged: if (systemdCom.serviceEnabled != autostartBox.checked) autostartBox.checked = systemdCom.serviceEnabled
-            }
-        }
-
-        RowLayout {
-            id: profileRow
-
-            Label {
-                text: i18n("Profile:")
-                Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
-                renderType: Text.NativeRendering
-            }
-            ComboBox {
-                id: profileComboBox
-
-                property string saveText: editText.length > 0 ? editText : currentText
+    Rectangle {
+        id: fansListViewBackground
 
 
-                editable: true
-                model: profileModel
-                textRole: "display"
-                Layout.fillWidth: true
-                Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+        Kirigami.Theme.colorSet: Kirigami.Theme.View
 
 
-                onActivated: Fancontrol.Base.applyProfile(index)
-            }
-            Button {
-                Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
-                action: saveProfileAction
-            }
-            Button {
-                Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
-                action: deleteProfileAction
-            }
+        anchors {
+            top: parent.top
+            bottom: parent.bottom
+            left: parent.left
         }
         }
+        width: root.width / 5
+        color: Kirigami.Theme.backgroundColor
+        visible: enabledBox.checked
+        border.width: 1
+        border.color: Kirigami.Theme.textColor
 
 
-        RowLayout {
-            visible: enabledBox.checked && pwmFans.length > 0
-
-            Label {
-                text: i18n("Fan:")
-                Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
-                renderType: Text.NativeRendering
-            }
-            ComboBox {
-                id: fanComboBox
+        ListView {
+            id: fansListView
 
 
-                model: pwmFanModel
-                textRole: "display"
-                Layout.fillWidth: true
-                Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
-            }
-            Button {
-                Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
-                text: i18n("Detect fans")
-                icon.name: kcm.needsAuthorization ? "dialog-password" : ""
-                onClicked: loader.detectSensors()
+            anchors.fill: parent
+            anchors.margins: fansListViewBackground.border.width
+            clip: true
+            boundsBehavior: Flickable.StopAtBounds
+            flickableDirection: Flickable.AutoFlickIfNeeded
+            model: pwmFanModel
+            header: Kirigami.BasicListItem {
+                label: '<b>' + i18n("Fans") + '</b>'
+                reserveSpaceForIcon: false
+                hoverEnabled: false
+                separatorVisible: false
+                leftPadding: Kirigami.Units.smallSpacing
             }
             }
-        }
-
-        Loader {
-            Layout.fillWidth: true
-            Layout.fillHeight: true
-            active: pwmFans.length > fanComboBox.currentIndex && fanComboBox.currentIndex >= 0
-            sourceComponent: Fancontrol.FanItem {
-                unit: Fancontrol.Base.unit
-                fan: pwmFans[fanComboBox.currentIndex]
-                systemdCom: root.systemdCom
-                tempModel: root.tempModel
-                minTemp: Fancontrol.Base.minTemp
-                maxTemp: Fancontrol.Base.maxTemp
+            delegate: Kirigami.BasicListItem {
+                label: display
+                reserveSpaceForIcon: false
+                hoverEnabled: true
+                highlighted: ListView.isCurrentItem
+                separatorVisible: false
+
+                onPressedChanged: if (pressed) fansListView.currentIndex = index;
             }
             }
         }
         }
     }
     }
 
 
-    Item {
-        id: advancedButton
-
-        property bool expanded: false
+    Fancontrol.FanHeader {
+        id: fanHeader
 
 
-        anchors.bottom: settingsArea.top
-        width: parent.width
-        height: advancedArrow.height
+        fan: root.fan
         visible: enabledBox.checked
         visible: enabledBox.checked
-
-        Image {
-            id: advancedArrow
-
-            source: parent.expanded ? "image://icon/go-down" : "image://icon/go-next"
-            fillMode: Image.PreserveAspectFit
-            height: advancedLabel.implicitHeight
+        anchors {
+            top: parent.top
+            left: fansListViewBackground.right
+            right: parent.right
+            margins: Kirigami.Units.smallSpacing
         }
         }
-        Label {
-            id: advancedLabel
+    }
 
 
-            anchors.left: advancedArrow.right
-            text: i18n("Advanced settings")
-            font.bold: true
+    Loader {
+        anchors {
+            top: fanHeader.bottom
+            left: fansListViewBackground.right
+            right: parent.right
+            bottom: settingsColumn.top
         }
         }
-        MouseArea {
-            anchors.fill: parent
-            onClicked: parent.expanded = !parent.expanded
+        active: !!root.fan
+        visible: enabledBox.checked
+        sourceComponent: Fancontrol.FanItem {
+            unit: Fancontrol.Base.unit
+            fan: root.fan
+            systemdCom: root.systemdCom
+            tempModel: root.tempModel
+            minTemp: Fancontrol.Base.minTemp
+            maxTemp: Fancontrol.Base.maxTemp
         }
         }
     }
     }
 
 
-    ColumnLayout {
-        id: settingsArea
-
-        visible: enabledBox.checked && parent.height - enabledBox.height - bodyLayout.height - advancedButton.height > height
-        width: parent.width
-        anchors.bottom: parent.bottom
-        clip: true
-        state: advancedButton.expanded ? "VISIBLE" : "HIDDEN"
+    Column {
+        id: settingsColumn
 
 
-        states: [
-            State {
-                name: "VISIBLE"
-
-                PropertyChanges {
-                    target: settingsArea
-                    height: settingsArea.implicitHeight
-                }
-            },
-            State {
-                name: "HIDDEN"
+        anchors {
+            left: fansListViewBackground.right
+            right: parent.right
+            bottom: parent.bottom
+        }
+        height: childrenRect.height
+        visible: enabledBox.checked
 
 
-                PropertyChanges {
-                    target: settingsArea
-                    height: 0
-                }
-            }
-        ]
+        Item {
+            id: advancedButton
 
 
-        transitions: Transition {
-            NumberAnimation {
-                properties: "height"
-                easing.type: Easing.InOutQuad
-            }
-        }
+            property bool expanded: false
 
 
-        RowLayout {
             width: parent.width
             width: parent.width
+            height: childrenRect.height
 
 
-            Label {
-                Layout.preferredWidth: root.textWidth
-                clip: true
-                text: i18n("Interval:")
-                horizontalAlignment: Text.AlignRight
-                Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
-            }
-            SpinBox {
-                Layout.minimumWidth: implicitWidth
-                Layout.fillWidth: true
-                value: loader.interval
-                editable: true
-                textFromValue: function(value, locale) { return Number(value).toLocaleString(locale, 'f', 0) + ' ' + i18np("second", "seconds", value) }
-                from: 1.0
-                onValueModified: loader.interval = value
-            }
-        }
-        RowLayout {
-            width: parent.width
+            Image {
+                id: advancedArrow
 
 
-            Label {
-                Layout.preferredWidth: root.textWidth
-                clip: true
-                text: i18n("Minimum temperature for fan graphs:")
-                horizontalAlignment: Text.AlignRight
-                Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
-            }
-            SpinBox {
-                id: minTempBox
-                Layout.minimumWidth: implicitWidth
-                Layout.fillWidth: true
-                to: maxTempBox.value
-                from: Units.fromKelvin(0, Fancontrol.Base.unit)
-                value: Units.fromCelsius(Fancontrol.Base.minTemp, Fancontrol.Base.unit)
-                textFromValue: function(value, locale) { return Number(value).toLocaleString(locale, 'f', 2) + Fancontrol.Base.unit }
-                onValueChanged: Fancontrol.Base.minTemp = Units.toCelsius(value, Fancontrol.Base.unit)
+                source: parent.expanded ? "image://icon/go-down" : "image://icon/go-next"
+                fillMode: Image.PreserveAspectFit
+                height: advancedLabel.implicitHeight
             }
             }
-        }
-        RowLayout {
-            width: parent.width
-
             Label {
             Label {
-                Layout.preferredWidth: root.textWidth
-                clip: true
-                text: i18n("Maximum temperature for fan graphs:")
-                horizontalAlignment: Text.AlignRight
-                Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
-            }
-            SpinBox {
-                id: maxTempBox
-                Layout.minimumWidth: implicitWidth
-                Layout.fillWidth: true
-                from: minTempBox.value
-                value: Units.fromCelsius(Fancontrol.Base.maxTemp, Fancontrol.Base.unit)
-                editable: true
-                textFromValue: function(value, locale) { return Number(value).toLocaleString(locale, 'f', 2) + Fancontrol.Base.unit }
-                onValueChanged: Fancontrol.Base.maxTemp = Units.toCelsius(value, Fancontrol.Base.unit)
-            }
-        }
-        RowLayout {
-            width: parent.width
+                id: advancedLabel
 
 
-            Label {
-                Layout.preferredWidth: root.textWidth
-                clip: true
-                text: i18n("Name of the fancontrol systemd service:")
-                horizontalAlignment: Text.AlignRight
-                Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
+                anchors.left: advancedArrow.right
+                text: i18n("Advanced settings")
+                font.bold: true
             }
             }
-            Fancontrol.OptionInput {
-                Layout.minimumWidth: implicitWidth
-                Layout.fillWidth: true
-                color: systemdCom.serviceExists ? "green" : "red"
-                value: Fancontrol.Base.serviceName
-                onTextChanged: Fancontrol.Base.serviceName = text
+            MouseArea {
+                anchors.fill: parent
+                onClicked: parent.expanded = !parent.expanded
             }
             }
         }
         }
-        RowLayout {
+
+        Fancontrol.SettingsForm {
+            id: settingsArea
+
             width: parent.width
             width: parent.width
+            showAll: false
+            clip: true
+            state: advancedButton.expanded ? "VISIBLE" : "HIDDEN"
+
+            states: [
+                State {
+                    name: "VISIBLE"
+
+                    PropertyChanges {
+                        target: settingsArea
+                        height: implicitHeight
+                    }
+                },
+                State {
+                    name: "HIDDEN"
+
+                    PropertyChanges {
+                        target: settingsArea
+                        height: 0
+                    }
+                }
+            ]
 
 
-            Label {
-                Layout.preferredWidth: root.textWidth
-                clip: true
-                text: i18n("Path to the fancontrol config file:")
-                horizontalAlignment: Text.AlignRight
-                Component.onCompleted: root.textWidth = Math.max(root.textWidth, contentWidth)
-            }
-            Fancontrol.OptionInput {
-                Layout.minimumWidth: implicitWidth
-                Layout.fillWidth: true
-                value: Fancontrol.Base.configUrl.toString().replace("file://", "")
-                color: Fancontrol.Base.configValid ? "green" : "red"
-                onTextChanged: Fancontrol.Base.configUrl = text
-            }
-            Button {
-                action: loadAction
+            transitions: Transition {
+                NumberAnimation {
+                    properties: "height"
+                    easing.type: Easing.InOutQuad
+                }
             }
             }
         }
         }
     }
     }
 
 
-    Action {
-        id: saveProfileAction
-
-        text: i18n("Save profile")
-        icon.name: "document-save"
-        onTriggered: Fancontrol.Base.saveProfile(profileComboBox.saveText)
-    }
-    Action {
-        id: deleteProfileAction
-
-        text: i18n("Delete profile")
-        icon.name: "edit-delete"
-        onTriggered: Fancontrol.Base.deleteProfile(profileComboBox.currentIndex)
-    }
-    Action {
-        id: loadAction
-
-        icon.name: "document-open"
-        onTriggered: openFileDialog.open()
-//         tooltip: i18n("Load configuration file")
-        shortcut: StandardKey.Open
-    }
-
-    FileDialog {
-        id: openFileDialog
-
-        title: i18n("Please choose a configuration file")
-        folder: "file:///etc"
-        selectExisting: true
-        selectMultiple: false
-
-        onAccepted: Fancontrol.Base.configUrl = fileUrl;
-    }
-
     Fancontrol.ErrorDialog {
     Fancontrol.ErrorDialog {
         id: errorDialog
         id: errorDialog
 
 
         modal: true
         modal: true
+        anchors.centerIn: root
     }
     }
 }
 }