diff --git a/Modules/energy/kcm.cpp b/Modules/energy/kcm.cpp --- a/Modules/energy/kcm.cpp +++ b/Modules/energy/kcm.cpp @@ -48,17 +48,12 @@ KCMEnergyInfo::KCMEnergyInfo(QObject *parent, const QVariantList &args) : ConfigModule(parent, args) { - //This flag seems to be needed in order for QQuickWidget to work - //see https://bugreports.qt-project.org/browse/QTBUG-40765 - //also, it seems to work only if set in the kcm, not in the systemsettings' main - qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); - qmlRegisterType(); qmlRegisterType(); qmlRegisterType("org.kde.kinfocenter.energy.private", 1, 0, "HistoryModel"); KAboutData *about = new KAboutData(QStringLiteral("kcm_energyinfo"), i18n("Energy Consumption Statistics"), - QStringLiteral("0.1"), QString(), KAboutLicense::GPL); + QStringLiteral("0.2"), QString(), KAboutLicense::GPL); about->addAuthor(i18n("Kai Uwe Broulik"), QString(), QStringLiteral("kde@privat.broulik.de")); setAboutData(about); diff --git a/Modules/energy/package/contents/ui/Graph.qml b/Modules/energy/package/contents/ui/Graph.qml --- a/Modules/energy/package/contents/ui/Graph.qml +++ b/Modules/energy/package/contents/ui/Graph.qml @@ -37,7 +37,7 @@ antialiasing: true property int xPadding: 45 - property int yPadding: 25 + property int yPadding: 10 property var data //expect an array of QPointF @@ -52,7 +52,7 @@ //internal - property real plotWidth: width - xPadding *2 + property real plotWidth: width - xPadding property real plotHeight: height - yPadding *2 onDataChanged: { diff --git a/Modules/energy/package/contents/ui/main.qml b/Modules/energy/package/contents/ui/main.qml --- a/Modules/energy/package/contents/ui/main.qml +++ b/Modules/energy/package/contents/ui/main.qml @@ -17,22 +17,24 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ -import QtQuick 2.2 -import QtQuick.Controls 1.3 -import QtQuick.Controls 2.0 as QQC2 +import QtQuick 2.5 +import QtQuick.Controls 2.5 as QQC2 import QtQuick.Layouts 1.1 +import org.kde.kirigami 2.5 as Kirigami import org.kde.kquickcontrolsaddons 2.0 import org.kde.kinfocenter.energy.private 1.0 -//We need units from it import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.plasma.workspace.components 2.0 as WorkspaceComponents +import org.kde.kcm 1.1 as KCM -Item { +KCM.SimpleKCM { id: root + + KCM.ConfigModule.quickHelp: i18n("This module lets you see energy information and statistics.") + property QtObject currentBattery: null property string currentUdi: "" property bool compact: (root.width / units.gridUnit) < 25 @@ -44,8 +46,6 @@ } } - SystemPalette { id: sysPal; colorGroup: SystemPalette.Active } - property bool showWakeUps: true property int historyType: HistoryModel.ChargeType @@ -104,390 +104,360 @@ currentUdi = kcm.batteries.udi(0) } - implicitWidth: units.gridUnit * 25 - implicitHeight: !!currentBattery ? units.gridUnit * 25 : units.gridUnit * 12 - - SystemPalette { - id: syspal - } - - ScrollView { - id: scrollView - anchors.fill: parent - - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - - ColumnLayout { - id: column + implicitWidth: units.gridUnit * 30 + implicitHeight: !!currentBattery ? units.gridUnit * 30 : units.gridUnit * 12 - width: scrollView.viewport.width - spacing: units.largeSpacing + readonly property var timespanComboChoices: [i18n("Last hour"),i18n("Last 2 hours"),i18n("Last 12 hours"),i18n("Last 24 hours"),i18n("Last 48 hours"), i18n("Last 7 days")] + readonly property var timespanComboDurations: [3600, 7200, 43200, 86400, 172800, 604800] - ScrollView { - id: tabView - Layout.fillWidth: true - Layout.minimumHeight: units.gridUnit * 3 - Layout.maximumHeight: Layout.minimumHeight + ColumnLayout { + id: column + QQC2.ScrollView { + id: tabView + Layout.fillWidth: true + Layout.minimumHeight: units.gridUnit * 3 + Layout.maximumHeight: Layout.minimumHeight + visible: kcm.batteries.count > 1 - frameVisible: true - visible: kcm.batteries.count > 1 + Row { + spacing: Kirigami.Units.smallSpacing + Repeater { + model: kcm.batteries - verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff + QQC2.Button { + id: button + width: height + height: tabView.height + checked: model.battery == root.currentBattery + checkable: true + onClicked: { + root.currentUdi = model.udi + root.currentBattery = model.battery + // override checked property + checked = Qt.binding(function() { + return model.battery == root.currentBattery + }) + + showWakeUps = (index === 0) + } - Row { - Repeater { - model: kcm.batteries - - Button { - id: button - width: height - height: tabView.viewport.height - checked: model.battery == root.currentBattery - checkable: true - onClicked: { - root.currentUdi = model.udi - root.currentBattery = model.battery - // override checked property - checked = Qt.binding(function() { - return model.battery == root.currentBattery - }) - - showWakeUps = (index === 0) + ColumnLayout { + anchors { + fill: parent + margins: units.smallSpacing } + spacing: 0 - ColumnLayout { - anchors { - fill: parent - margins: units.smallSpacing - } - spacing: 0 - - WorkspaceComponents.BatteryIcon { - Layout.fillWidth: true - Layout.fillHeight: true - hasBattery: true - batteryType: { - switch(model.battery.type) { - case 3: return "Battery" - case 2: return "Ups" - case 9: return "Monitor" - case 4: return "Mouse" - case 5: return "Keyboard" - case 1: return "Pda" - case 7: return "Phone" - default: return "Unknown" - } + WorkspaceComponents.BatteryIcon { + Layout.fillWidth: true + Layout.fillHeight: true + hasBattery: true + batteryType: { + switch(model.battery.type) { + case 3: return "Battery" + case 2: return "Ups" + case 9: return "Monitor" + case 4: return "Mouse" + case 5: return "Keyboard" + case 1: return "Pda" + case 7: return "Phone" + default: return "Unknown" } - percent: model.battery.chargePercent - //pluggedIn: model.battery.chargeState === 1 // Makes it hard to see } + percent: model.battery.chargePercent + //pluggedIn: model.battery.chargeState === 1 // Makes it hard to see + } - ProgressBar { // TODO make progress bar not eat mouse events - Layout.fillWidth: true - minimumValue: 0 - maximumValue: 100 - value: model.battery.chargePercent - enabled: button.checked ? false : true - } + QQC2.ProgressBar { // TODO make progress bar not eat mouse events + Layout.fillWidth: true + from: 0 + to: 100 + value: model.battery.chargePercent + enabled: button.checked ? false : true } } } } } + } - ColumnLayout { - Layout.fillWidth: true - spacing: units.smallSpacing - visible: !!currentBattery - - GridLayout { - Layout.fillWidth: true - columns: !compact ? 5 : 3 - - Button { - id: chargeButton - checked: true - checkable: true - text: i18n("Charge Percentage") - onClicked: { - historyType = HistoryModel.ChargeType - rateButton.checked = false - } - } + ColumnLayout { + Layout.fillWidth: true + spacing: units.smallSpacing + visible: !!currentBattery - Button { - id: rateButton - checkable: true - text: i18n("Energy Consumption") - onClicked: { - historyType = HistoryModel.RateType - chargeButton.checked = false - } + GridLayout { + Layout.fillWidth: true + columns: !compact ? 5 : 3 + + QQC2.Button { + id: chargeButton + checked: true + checkable: true + text: i18n("Charge Percentage") + onClicked: { + historyType = HistoryModel.ChargeType + rateButton.checked = false } + } - Item { - Layout.fillWidth: true + QQC2.Button { + id: rateButton + checkable: true + text: i18n("Energy Consumption") + onClicked: { + historyType = HistoryModel.RateType + chargeButton.checked = false } + } - ComboBox { - id: timespanCombo - Layout.minimumWidth: units.gridUnit * 6 - model: [ - {text: i18n("Last hour"), value: 3600}, - {text: i18n("Last 2 hours"), value: 7200}, - {text: i18n("Last 12 hours"), value: 43200}, - {text: i18n("Last 24 hours"), value: 86400}, - {text: i18n("Last 48 hours"), value: 172800}, - {text: i18n("Last 7 days"), value: 604800} - ] - Accessible.name: i18n("Timespan") - Accessible.description: i18n("Timespan of data to display") - } + Item { + Layout.fillWidth: true + } - Button { - iconName: "view-refresh" - tooltip: i18n("Refresh") - Accessible.name: tooltip - onClicked: history.refresh() - } + QQC2.ComboBox { + id: timespanCombo + Layout.minimumWidth: units.gridUnit * 6 + model: timespanComboChoices + Accessible.name: i18n("Timespan") + Accessible.description: i18n("Timespan of data to display") } - HistoryModel { - id: history - duration: timespanCombo.model[timespanCombo.currentIndex].value - device: currentUdi - type: root.historyType + QQC2.Button { + icon.name: "view-refresh" + hoverEnabled: true + QQC2.ToolTip.text: i18n("Refresh") + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + Accessible.name: ToolTip.text + onClicked: history.refresh() } + } - Graph { - id: graph - Layout.fillWidth: true - Layout.minimumHeight: column.width / 3 - Layout.maximumHeight: column.width / 3 - data: history.points - - xMin: history.firstDataPointTime - xMax: history.lastDataPointTime - - yUnits: root.historyType == HistoryModel.RateType ? i18nc("Shorthand for Watts","W") : i18nc("literal percent sign","%") - yMax: { - if (root.historyType == HistoryModel.RateType) { - var max = history.largestValue - var modulo = max % 10 - if (modulo > 0) { - max = max - modulo + 10 // ceil to nearest 10s - } - return max; - } else { - return 100; + HistoryModel { + id: history + duration: timespanComboDurations[timespanCombo.currentIndex] + device: currentUdi + type: root.historyType + } + + Graph { + id: graph + Layout.fillWidth: true + Layout.minimumHeight: column.width / 3 + Layout.maximumHeight: column.width / 3 + Layout.topMargin: units.largeSpacing + + data: history.points + + xMin: history.firstDataPointTime + xMax: history.lastDataPointTime + + yUnits: root.historyType == HistoryModel.RateType ? i18nc("Shorthand for Watts","W") : i18nc("literal percent sign","%") + yMax: { + if (root.historyType == HistoryModel.RateType) { + var max = history.largestValue + var modulo = max % 10 + if (modulo > 0) { + max = max - modulo + 10 // ceil to nearest 10s } + return max; + } else { + return 100; } - yStep: root.historyType == HistoryModel.RateType ? 10 : 20 - visible: history.count > 1 - } - - QQC2.Label { - Layout.fillWidth: true - Layout.minimumHeight: column.width / 3 - Layout.maximumHeight: column.width / 3 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text: i18n("This type of history is currently not available for this device.") - wrapMode: Text.WordWrap - visible: !graph.visible } + yStep: root.historyType == HistoryModel.RateType ? 10 : 20 + visible: history.count > 1 } - ColumnLayout { + Kirigami.InlineMessage { Layout.fillWidth: true - spacing: units.smallSpacing - visible: showWakeUps && kcm.wakeUps.count > 0 + Layout.topMargin: units.smallSpacing + showCloseButton: true + text: i18n("This type of history is currently not available for this device.") + visible: !graph.visible + } + } - RowLayout { + ColumnLayout { + Layout.fillWidth: true + spacing: units.smallSpacing + visible: showWakeUps && kcm.wakeUps.count > 0 + + RowLayout { + Layout.fillWidth: true + Kirigami.Heading { Layout.fillWidth: true - PlasmaExtras.Heading { - Layout.fillWidth: true - Layout.columnSpan: 2 - color: sysPal.text - level: 4 - text: i18n("Application Energy Consumption") - } + Layout.columnSpan: 2 + level: 4 + text: i18n("Application Energy Consumption") } + } - GridLayout { - id: wakeUpsGrid - Layout.fillWidth: true - rows: compact ? kcm.wakeUps.count : kcm.wakeUps.count / 2 - flow: GridLayout.TopToBottom - rowSpacing: units.smallSpacing - columnSpacing: units.smallSpacing - property int barWidth: (compact ? root.width - units.gridUnit: root.width / 2) - units.smallSpacing * 2 + GridLayout { + id: wakeUpsGrid + Layout.fillWidth: true + rows: compact ? kcm.wakeUps.count : kcm.wakeUps.count / 2 + flow: GridLayout.TopToBottom + rowSpacing: units.smallSpacing + columnSpacing: units.smallSpacing + property int barWidth: (compact ? root.width - units.gridUnit: root.width / 2) - units.smallSpacing * 2 - Repeater { - model: showWakeUps ? kcm.wakeUps : null - - PlasmaCore.ToolTipArea { // FIXME use widget style tooltip - Layout.minimumWidth: wakeUpsGrid.barWidth - Layout.maximumWidth: wakeUpsGrid.barWidth - height: childrenRect.height - z: 2 // since the progress bar eats mouse events - mainText: model.prettyName || model.name - subText: { - var text = "" - if (model.prettyName && model.name !== model.prettyName) { - text += i18n("Path: %1", model.name) + "\n" - } - if (model.pid) { - text += i18n("PID: %1", model.pid) + "\n" - } - // FIXME format decimals - text += i18n("Wakeups per second: %1 (%2%)", Math.round(model.wakeUps * 100) / 100, Math.round(model.wakeUps / kcm.wakeUps.total * 100)) + "\n" - if (model.details) { - text += i18n("Details: %1", model.details) - } - return text + Repeater { + model: showWakeUps ? kcm.wakeUps : null + + PlasmaCore.ToolTipArea { // FIXME use widget style tooltip + Layout.minimumWidth: wakeUpsGrid.barWidth + Layout.maximumWidth: wakeUpsGrid.barWidth + height: childrenRect.height + z: 2 // since the progress bar eats mouse events + mainText: model.prettyName || model.name + subText: { + var text = "" + if (model.prettyName && model.name !== model.prettyName) { + text += i18n("Path: %1", model.name) + "\n" + } + if (model.pid) { + text += i18n("PID: %1", model.pid) + "\n" + } + // FIXME format decimals + text += i18n("Wakeups per second: %1 (%2%)", Math.round(model.wakeUps * 100) / 100, Math.round(model.wakeUps / kcm.wakeUps.total * 100)) + "\n" + if (model.details) { + text += i18n("Details: %1", model.details) } - icon: model.iconName + return text + } + icon: model.iconName - RowLayout { - id: wakeUpItemRow - width: parent.width + RowLayout { + id: wakeUpItemRow + width: parent.width - QIconItem { - width: units.iconSizes.medium - height: width - icon: model.iconName - } + QIconItem { + width: units.iconSizes.medium + height: width + icon: model.iconName + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 - ColumnLayout { + RowLayout { Layout.fillWidth: true - spacing: 0 - RowLayout { + QQC2.Label { Layout.fillWidth: true - - QQC2.Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: model.prettyName || model.name - } - - /*Label { - text: i18n("System Service") - visible: !model.userSpace - opacity: 0.6 - }*/ + elide: Text.ElideRight + text: model.prettyName || model.name } - ProgressBar { - Layout.fillWidth: true - minimumValue: 0 - maximumValue: 100 - value: model.wakeUps / kcm.wakeUps.total * 100 - } + /*QQC2.Label { + text: i18n("System Service") + visible: !model.userSpace + opacity: 0.6 + }*/ + } + + QQC2.ProgressBar { + Layout.fillWidth: true + from: 0 + to: 100 + value: model.wakeUps / kcm.wakeUps.total * 100 } } } } } } + } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#ccc" // FIXME palette - //Layout.topMargin: (compact ? units.gridUnit * 2 : 0) - visible: wakeUpsGrid.visible - } + Rectangle { + Layout.fillWidth: true + height: 1 + color: Kirigami.Theme.textColor // FIXME palette + //Layout.topMargin: (compact ? units.gridUnit * 2 : 0) + visible: wakeUpsGrid.visible + } - ColumnLayout { - id: detailsColumn - property int legendWidth: 10 + ColumnLayout { + id: detailsColumn + spacing: 0 - Layout.fillWidth: true - spacing: 0 - visible: !!currentBattery + Layout.fillWidth: true + visible: !!currentBattery - Repeater { - model: root.details - - ColumnLayout { - spacing: 0//units.smallSpacing - - PlasmaExtras.Heading { - level: 4 - color: sysPal.text - text: modelData.title - // HACK hide section header if all labels are invisible - visible: { - for (var i = 0, length = detailsRepeater.count; i < length; ++i) { - var item = detailsRepeater.itemAt(i) - if (item && item.visible) { - return true - } - } + Repeater { + id: titleRepeater + model: root.details + property list layouts - return false - } + delegate: Kirigami.FormLayout { + id: currentLayout + + Component.onCompleted: { + // ensure that all visible FormLayout share the same set of twinFormLayouts + titleRepeater.layouts.push(currentLayout); + for (var i = 0, length = titleRepeater.layouts.length; i < length; ++i) { + titleRepeater.layouts[i].twinFormLayouts = titleRepeater.layouts; } + } - Repeater { - id: detailsRepeater - model: modelData.data || [] + Kirigami.Heading { + text: modelData.title + Kirigami.FormData.isSection: true + level: 2 + // HACK hide section header if all labels are invisible + visible: { + for (var i = 0, length = detailsRepeater.count; i < length; ++i) { + var item = detailsRepeater.itemAt(i) + if (item && item.visible) { + return true + } + } - RowLayout { - Layout.fillWidth: true - spacing: units.smallSpacing * 2 - visible: valueLabel.text !== "" - - QQC2.Label { - Layout.minimumWidth: detailsColumn.legendWidth + units.gridUnit - horizontalAlignment: Text.AlignRight - text: i18n("%1:", modelData.label) - wrapMode: Text.NoWrap - opacity: 0.8 - onPaintedWidthChanged: { - if (paintedWidth > detailsColumn.legendWidth) { - detailsColumn.legendWidth = paintedWidth - } + return false + } + } + + Repeater { + id: detailsRepeater + model: modelData.data || [] + + QQC2.Label { + id: valueLabel + Kirigami.FormData.label: i18n("%1:", modelData.label) + text: { + var value = currentBattery[modelData.value] + + if (typeof value === "boolean") { + if (value) { + return i18n("Yes") + } else { + return i18n("No") } } - QQC2.Label { - id: valueLabel - Layout.fillWidth: true - text: { - var value = currentBattery[modelData.value] - - if (typeof value === "boolean") { - if (value) { - return i18n("Yes") - } else { - return i18n("No") - } - } - - if (!value) { - return "" - } - - var precision = modelData.precision - if (typeof precision === "number") { // round to decimals - value = Number(value).toLocaleString(Qt.locale(), "f", precision) - } - - if (modelData.modifier && root["modifier_" + modelData.modifier]) { - value = root["modifier_" + modelData.modifier](value) - } - - if (modelData.unit) { - value = i18nc("%1 is value, %2 is unit", "%1 %2", value, modelData.unit) - } - - return value - } + if (!value) { + return "" } + + var precision = modelData.precision + if (typeof precision === "number") { // round to decimals + value = Number(value).toLocaleString(Qt.locale(), "f", precision) + } + + if (modelData.modifier && root["modifier_" + modelData.modifier]) { + value = root["modifier_" + modelData.modifier](value) + } + + if (modelData.unit) { + value = i18nc("%1 is value, %2 is unit", "%1 %2", value, modelData.unit) + } + + return value } + visible: valueLabel.text !== "" } } }