diff --git a/applets/notifications/package/contents/ui/FullRepresentation.qml b/applets/notifications/package/contents/ui/FullRepresentation.qml index 6f39218f9..c932ef5e5 100644 --- a/applets/notifications/package/contents/ui/FullRepresentation.qml +++ b/applets/notifications/package/contents/ui/FullRepresentation.qml @@ -71,6 +71,7 @@ ColumnLayout{ RowLayout { Layout.fillWidth: true + spacing: 0 RowLayout { id: dndRow @@ -190,9 +191,19 @@ ColumnLayout{ Layout.fillWidth: true } + PlasmaComponents.ToolButton { + iconName: "edit-clear-history" + tooltip: i18n("Clear History") + visible: plasmoid.action("clearHistory").visible + onClicked: action_clearHistory() + } + PlasmaComponents.ToolButton { iconName: "configure" - tooltip: plasmoid.action("openKcm").text + // remove mnemonics + tooltip: plasmoid.action("openKcm").text.replace(/([^&]*)&(.)([^&]*)/g, function (match, p1, p2, p3) { + return p1.concat(p2, p3); + }); visible: plasmoid.action("openKcm").enabled onClicked: plasmoid.action("openKcm").trigger() } @@ -337,24 +348,6 @@ ColumnLayout{ prefix: "pressed" } - header: RowLayout { - width: list.width - - PlasmaExtras.Heading { - Layout.fillWidth: true - level: 3 - opacity: 0.6 - text: list.count === 0 ? i18n("No unread notifications.") : i18n("Notifications") - } - - PlasmaComponents.ToolButton { - iconName: "edit-clear-history" - tooltip: i18n("Clear History") - visible: plasmoid.action("clearHistory").visible - onClicked: action_clearHistory() - } - } - add: Transition { SequentialAnimation { PauseAnimation { duration: units.longDuration } @@ -554,6 +547,14 @@ ColumnLayout{ } } } + + PlasmaExtras.Heading { + width: list.width + level: 3 + opacity: 0.6 + text: i18n("No unread notifications.") + visible: list.count === 0 + } } } } diff --git a/applets/notifications/package/contents/ui/global/Globals.qml b/applets/notifications/package/contents/ui/global/Globals.qml index 45763cc19..86cd1894d 100644 --- a/applets/notifications/package/contents/ui/global/Globals.qml +++ b/applets/notifications/package/contents/ui/global/Globals.qml @@ -134,13 +134,7 @@ QtObject { // How much vertical screen real estate the notification popups may consume readonly property real popupMaximumScreenFill: 0.75 - property var screenRect: plasmoid ? Qt.rect(plasmoid.screenGeometry.x + plasmoid.availableScreenRect.x, - plasmoid.screenGeometry.y + plasmoid.availableScreenRect.y, - plasmoid.availableScreenRect.width, - plasmoid.availableScreenRect.height) : undefined - onPopupLocationChanged: Qt.callLater(positionPopups) - onScreenRectChanged: Qt.callLater(positionPopups) Component.onCompleted: checkInhibition() @@ -215,8 +209,15 @@ QtObject { } function positionPopups() { - var rect = screenRect; - if (!rect || rect.width <= 0 || rect.height <= 0) { + if (!plasmoid) { + return; + } + + var screenRect = Qt.rect(plasmoid.screenGeometry.x + plasmoid.availableScreenRect.x, + plasmoid.screenGeometry.y + plasmoid.availableScreenRect.y, + plasmoid.availableScreenRect.width, + plasmoid.availableScreenRect.height); + if (screenRect.width <= 0 || screenRect.height <= 0) { return; } @@ -269,7 +270,7 @@ QtObject { } property QtObject popupNotificationsModel: NotificationManager.Notifications { - limit: globals.screenRect ? (Math.ceil(globals.screenRect.height / (theme.mSize(theme.defaultFont).height * 4))) : 0 + limit: plasmoid ? (Math.ceil(plasmoid.availableScreenRect.height / (theme.mSize(theme.defaultFont).height * 4))) : 0 showExpired: false showDismissed: false blacklistedDesktopEntries: notificationSettings.popupBlacklistedApplications @@ -428,4 +429,16 @@ QtObject { property QtObject pulseAudio: Loader { source: "PulseAudio.qml" } + + property Connections screenWatcher: Connections { + target: plasmoid + onAvailableScreenRectChanged: repositionTimer.start() + onScreenGeometryChanged: repositionTimer.start() + } + + // Normally popups are repositioned through Qt.callLater but in case of e.g. screen geometry changes we want to compress that + property Timer repositionTimer: Timer { + interval: 250 + onTriggered: positionPopups() + } } diff --git a/libnotificationmanager/jobsmodel_p.cpp b/libnotificationmanager/jobsmodel_p.cpp index b1db074a3..e07069d2e 100644 --- a/libnotificationmanager/jobsmodel_p.cpp +++ b/libnotificationmanager/jobsmodel_p.cpp @@ -288,6 +288,7 @@ QDBusObjectPath JobsModelPrivate::requestView(const QString &desktopEntry, scheduleUpdate(job, Notifications::ClosableRole); if (job->state() == Notifications::JobStateStopped) { + unwatchJob(job); updateApplicationPercentage(job->desktopEntry()); emitJobUrlsChanged(); } @@ -374,14 +375,7 @@ void JobsModelPrivate::remove(Job *job) const QString desktopEntry = jobToBeRemoved->desktopEntry(); - const QString serviceName = m_jobServices.take(jobToBeRemoved); - // Check if there's any jobs left for this service, otherwise stop watching it - auto it = std::find_if(m_jobServices.constBegin(), m_jobServices.constEnd(), [&serviceName](const QString &item) { - return item == serviceName; - }); - if (it == m_jobServices.constEnd()) { - m_serviceWatcher->removeWatchedService(serviceName); - } + unwatchJob(jobToBeRemoved); delete jobToBeRemoved; if (activeRow > -1) { @@ -440,12 +434,22 @@ void JobsModelPrivate::updateApplicationPercentage(const QString &desktopEntry) QDBusConnection::sessionBus().send(message); } +void JobsModelPrivate::unwatchJob(Job *job) +{ + const QString serviceName = m_jobServices.take(job); + // Check if there's any jobs left for this service, otherwise stop watching it + auto it = std::find_if(m_jobServices.constBegin(), m_jobServices.constEnd(), [&serviceName](const QString &item) { + return item == serviceName; + }); + if (it == m_jobServices.constEnd()) { + m_serviceWatcher->removeWatchedService(serviceName); + } +} + void JobsModelPrivate::onServiceUnregistered(const QString &serviceName) { qCDebug(NOTIFICATIONMANAGER) << "JobView service unregistered" << serviceName; - m_serviceWatcher->removeWatchedService(serviceName); - const QList jobs = m_jobServices.keys(serviceName); for (Job *job : jobs) { // Mark all non-finished jobs as failed @@ -456,6 +460,8 @@ void JobsModelPrivate::onServiceUnregistered(const QString &serviceName) job->setErrorText(i18n("Application closed unexpectedly.")); job->setState(Notifications::JobStateStopped); } + + Q_ASSERT(!m_serviceWatcher->watchedServices().contains(serviceName)); } void JobsModelPrivate::scheduleUpdate(Job *job, Notifications::Roles role) diff --git a/libnotificationmanager/jobsmodel_p.h b/libnotificationmanager/jobsmodel_p.h index 8ed608dfe..f29c3794c 100644 --- a/libnotificationmanager/jobsmodel_p.h +++ b/libnotificationmanager/jobsmodel_p.h @@ -80,6 +80,7 @@ public: // stuff used by public class QVector m_jobViews; private: + void unwatchJob(Job *job); void onServiceUnregistered(const QString &serviceName); void updateApplicationPercentage(const QString &desktopEntry); diff --git a/sddm-theme/Main.qml b/sddm-theme/Main.qml index 46ab3932a..dfa00594a 100644 --- a/sddm-theme/Main.qml +++ b/sddm-theme/Main.qml @@ -360,9 +360,16 @@ PlasmaCore.ColorScope { notificationMessage: root.notificationMessage loginScreenUiVisible: loginScreenRoot.uiVisible - userListModel: QtObject { - property string name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Type in Username and Password") - property string iconSource: "" + // using a model rather than a QObject list to avoid QTBUG-75900 + userListModel: ListModel { + ListElement { + name: "" + iconSource: "" + } + Component.onCompleted: { + // as we can't bind inside ListElement + setProperty(0, "name", i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Type in Username and Password")); + } } onLoginRequest: {