diff --git a/applets/devicenotifier/package/contents/ui/FullRepresentation.qml b/applets/devicenotifier/package/contents/ui/FullRepresentation.qml index 42c67bdf8..ca0a3f716 100644 --- a/applets/devicenotifier/package/contents/ui/FullRepresentation.qml +++ b/applets/devicenotifier/package/contents/ui/FullRepresentation.qml @@ -1,183 +1,210 @@ /* * Copyright 2011 Viranch Mehta * Copyright 2012 Jacopo De Simoi * Copyright 2014 David Edmundson * Copyright 2014 Marco Martin * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 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 General Public License for more details * * You should have received a copy of the GNU Library 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.2 +import QtQuick.Window 2.2 import QtQuick.Layouts 1.1 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras -Item { +MouseArea { + id: fullRep + property bool spontaneousOpen: false + + hoverEnabled: true Layout.minimumWidth: units.gridUnit * 12 Layout.minimumHeight: units.gridUnit * 12 PlasmaExtras.Heading { width: parent.width level: 3 opacity: 0.6 text: i18n("No Devices Available") visible: notifierDialog.count === 0 && !devicenotifier.pendingDelegateRemoval } + PlasmaCore.DataSource { + id: userActivitySource + engine: "powermanagement" + connectedSources: "UserActivity" + property int polls: 0 + //poll only on plasmoid expanded + interval: !fullRep.containsMouse && !fullRep.Window.active && spontaneousOpen && plasmoid.expanded ? 3000 : 0 + onIntervalChanged: polls = 0; + onDataChanged: { + //only do when polling + if (interval == 0 || polls++ < 1) { + return; + } + + if (userActivitySource.data["UserActivity"]["IdleTime"] < interval) { + plasmoid.expanded = false; + spontaneousOpen = false; + } + } + } + + // this item is reparented to a delegate that is showing a message to draw focus to it PlasmaComponents.Highlight { id: messageHighlight visible: false OpacityAnimator { id: messageHighlightAnimator target: messageHighlight from: 1 to: 0 duration: 3000 easing.type: Easing.InOutQuad } Connections { target: statusSource onLastChanged: { if (!statusSource.last) { messageHighlightAnimator.stop() messageHighlight.visible = false } } } function highlight(item) { parent = item width = Qt.binding(function() { return item.width }) height = Qt.binding(function() { return item.height }) opacity = 1 // Animator is threaded so the old opacity might be visible for a frame or two visible = true messageHighlightAnimator.start() } } Connections { target: plasmoid onExpandedChanged: { if (!plasmoid.expanded) { statusSource.clearMessage() } } } ColumnLayout { anchors.fill: parent PlasmaExtras.ScrollArea { Layout.fillWidth: true Layout.fillHeight: true ListView { id: notifierDialog focus: true boundsBehavior: Flickable.StopAtBounds model: filterModel delegate: deviceItem highlight: PlasmaComponents.Highlight { } highlightMoveDuration: 0 highlightResizeDuration: 0 spacing: units.smallSpacing currentIndex: devicenotifier.currentIndex //this is needed to make SectionScroller actually work //acceptable since one doesn't have a billion of devices cacheBuffer: 1000 section { property: "Type Description" delegate: Item { height: childrenRect.height width: notifierDialog.width PlasmaExtras.Heading { level: 3 opacity: 0.6 text: section } } } } } } Component { id: deviceItem DeviceItem { width: notifierDialog.width udi: DataEngineSource icon: sdSource.data[udi].Icon deviceName: sdSource.data[udi].Description emblemIcon: Emblems[0] state: sdSource.data[udi].State percentUsage: { if (!sdSource.data[udi]) { return 0 } var freeSpace = new Number(sdSource.data[udi]["Free Space"]); var size = new Number(sdSource.data[udi]["Size"]); var used = size-freeSpace; return used*100/size; } freeSpaceText: sdSource.data[udi] && sdSource.data[udi]["Free Space Text"] ? sdSource.data[udi]["Free Space Text"] : "" actionIcon: mounted ? "media-eject" : "media-mount" actionVisible: model["Device Types"].indexOf("Portable Media Player") == -1 actionToolTip: { if (!mounted) { return i18n("Click to access this device from other applications.") } else if (model["Device Types"].indexOf("OpticalDisc") != -1) { return i18n("Click to eject this disc.") } else { return i18n("Click to safely remove this device.") } } mounted: devicenotifier.isMounted(udi) onActionTriggered: { var operationName = mounted ? "unmount" : "mount"; var service = sdSource.serviceForSource(udi); var operation = service.operationDescription(operationName); service.startOperationCall(operation); } property int operationResult: (model["Operation result"]) onOperationResultChanged: { if (operationResult == 1) { devicenotifier.popupIcon = "dialog-ok" popupIconTimer.restart() } else if (operationResult == 2) { devicenotifier.popupIcon = "dialog-error" popupIconTimer.restart() } } Behavior on height { NumberAnimation { duration: units.shortDuration * 3 } } } } } diff --git a/applets/devicenotifier/package/contents/ui/devicenotifier.qml b/applets/devicenotifier/package/contents/ui/devicenotifier.qml index 946a18758..f2266b791 100644 --- a/applets/devicenotifier/package/contents/ui/devicenotifier.qml +++ b/applets/devicenotifier/package/contents/ui/devicenotifier.qml @@ -1,273 +1,274 @@ /* * Copyright 2011 Viranch Mehta * Copyright 2012 Jacopo De Simoi * Copyright 2014 David Edmundson * Copyright 2016 Kai Uwe Broulik * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 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 General Public License for more details * * You should have received a copy of the GNU Library 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.0 import QtQuick.Layouts 1.1 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.kquickcontrolsaddons 2.0 Item { id: devicenotifier readonly property string automounterKcmName: "device_automounter_kcm" property string devicesType: { if (plasmoid.configuration.allDevices) { return "all" } else if (plasmoid.configuration.removableDevices) { return "removable" } else { return "nonRemovable" } } property string expandedDevice property string popupIcon: "device-notifier" property bool itemClicked: false property int currentIndex: -1 // QTBUG-50380: As soon as the item gets removed from the model, all of ListView's // properties (count, contentHeight) pretend the delegate doesn't exist anymore // causing our "No devices" heading to overlap with the remaining device property int pendingDelegateRemoval: 0 Plasmoid.switchWidth: units.gridUnit * 10 Plasmoid.switchHeight: units.gridUnit * 10 Plasmoid.toolTipMainText: filterModel.count > 0 && filterModel.get(0) ? i18n("Most Recent Device") : i18n("No Devices Available") Plasmoid.toolTipSubText: { if (filterModel.count > 0) { var data = filterModel.get(0) if (data && data.Description) { return data.Description } } return "" } Plasmoid.icon: { if (filterModel.count > 0) { var data = filterModel.get(0) if (data && data.Icon) { return data.Icon } } return "device-notifier" } Plasmoid.status: (filterModel.count > 0 || pendingDelegateRemoval > 0) ? PlasmaCore.Types.ActiveStatus : PlasmaCore.Types.PassiveStatus PlasmaCore.DataSource { id: hpSource engine: "hotplug" connectedSources: sources interval: 0 onSourceAdded: { disconnectSource(source); connectSource(source); } onSourceRemoved: { disconnectSource(source); } } Plasmoid.compactRepresentation: PlasmaCore.IconItem { source: devicenotifier.popupIcon width: units.iconSizes.medium; height: units.iconSizes.medium; MouseArea { anchors.fill: parent onClicked: plasmoid.expanded = !plasmoid.expanded } } Plasmoid.fullRepresentation: FullRepresentation {} PlasmaCore.DataSource { id: sdSource engine: "soliddevice" connectedSources: hpSource.sources interval: plasmoid.expanded ? 5000 : 0 property string last onSourceAdded: { disconnectSource(source); connectSource(source); last = source; processLastDevice(true); } onSourceRemoved: { if (expandedDevice == source) { expandedDevice = ""; } disconnectSource(source); } onDataChanged: { processLastDevice(true); } onNewData: { last = sourceName; processLastDevice(false); } function isViableDevice(udi) { if (devicesType === "all") { return true; } var device = data[udi]; return (devicesType === "removable" && device.Removable) || (devicesType === "nonRemovable" && !device.Removable); } function processLastDevice(expand) { if (last) { if (isViableDevice(last)) { if (expand && hpSource.data[last].added) { expandDevice(last); } last = ""; } } } } PlasmaCore.SortFilterModel { id: filterModel sourceModel: PlasmaCore.DataModel { dataSource: sdSource } filterRole: "Removable" filterRegExp: { if (devicesType === "removable") { return "true" } else if (devicesType === "nonRemovable") { return "false" } else { return "" } } sortRole: "Timestamp" sortOrder: Qt.DescendingOrder } PlasmaCore.DataSource { id: statusSource engine: "devicenotifications" property string last property string lastUdi onSourceAdded: { last = source; disconnectSource(source); connectSource(source); } onSourceRemoved: disconnectSource(source) onDataChanged: { if (last) { lastUdi = data[last].udi if (sdSource.isViableDevice(lastUdi)) { plasmoid.expanded = true } } } function clearMessage() { last = "" lastUdi = "" } } Component.onCompleted: { if (sdSource.connectedSources.count == 0) { Plasmoid.status = PlasmaCore.Types.PassiveStatus; } if (KCMShell.authorize(devicenotifier.automounterKcmName + ".desktop").length > 0) { plasmoid.setAction("openAutomounterKcm", i18nc("Open auto mounter kcm", "Configure Removable Devices"), "drive-removable-media") } } function action_openAutomounterKcm() { KCMShell.open([devicenotifier.automounterKcmName]) } Plasmoid.onExpandedChanged: { popupEventSlot(plasmoid.expanded); } function popupEventSlot(popped) { if (!popped) { // reset the property that lets us remember if an item was clicked // (versus only hovered) for autohide purposes devicenotifier.itemClicked = true; expandedDevice = ""; devicenotifier.currentIndex = -1; } } function expandDevice(udi) { if (hpSource.data[udi]["actions"].length > 1) { expandedDevice = udi } // reset the property that lets us remember if an item was clicked // (versus only hovered) for autohide purposes devicenotifier.itemClicked = false; devicenotifier.popupIcon = "preferences-desktop-notification"; //plasmoid.expanded = true; expandTimer.restart(); popupIconTimer.restart() } function isMounted(udi) { var types = sdSource.data[udi]["Device Types"]; if (types.indexOf("Storage Access") >= 0) { return sdSource.data[udi]["Accessible"]; } return (types.indexOf("Storage Volume") >= 0 && types.indexOf("OpticalDisc") >= 0) } Timer { id: popupIconTimer interval: 3000 onTriggered: devicenotifier.popupIcon = "device-notifier"; } Timer { id: expandTimer interval: 250 onTriggered: { if (plasmoid.configuration.popupOnNewDevice) { // Bug 351592 plasmoid.expanded = true; + plasmoid.fullRepresentationItem.spontaneousOpen = true; } } } }