diff --git a/interfaces/notificationsmodel.cpp b/interfaces/notificationsmodel.cpp index 80df2792..5c8c0922 100644 --- a/interfaces/notificationsmodel.cpp +++ b/interfaces/notificationsmodel.cpp @@ -1,257 +1,263 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "notificationsmodel.h" #include "interfaces_debug.h" #include #include #include #include //#include "modeltest.h" NotificationsModel::NotificationsModel(QObject* parent) : QAbstractListModel(parent) , m_dbusInterface(nullptr) { //new ModelTest(this, this); connect(this, &QAbstractItemModel::rowsInserted, this, &NotificationsModel::rowsChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &NotificationsModel::rowsChanged); connect(this, &QAbstractItemModel::dataChanged, this, &NotificationsModel::anyDismissableChanged); connect(this, &QAbstractItemModel::rowsInserted, this, &NotificationsModel::anyDismissableChanged); QDBusServiceWatcher* watcher = new QDBusServiceWatcher(DaemonDbusInterface::activatedService(), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &NotificationsModel::refreshNotificationList); connect(watcher, &QDBusServiceWatcher::serviceUnregistered, this, &NotificationsModel::clearNotifications); } QHash NotificationsModel::roleNames() const { //Role names for QML QHash names = QAbstractItemModel::roleNames(); names.insert(DbusInterfaceRole, "dbusInterface"); names.insert(AppNameModelRole, "appName"); names.insert(IdModelRole, "notificationId"); names.insert(DismissableModelRole, "dismissable"); names.insert(RepliableModelRole, "repliable"); names.insert(IconPathModelRole, "appIcon"); + names.insert(TitleModelRole, "title"); + names.insert(TextModelRole, "notitext"); return names; } NotificationsModel::~NotificationsModel() { } QString NotificationsModel::deviceId() const { return m_deviceId; } void NotificationsModel::setDeviceId(const QString& deviceId) { m_deviceId = deviceId; if (m_dbusInterface) { delete m_dbusInterface; } m_dbusInterface = new DeviceNotificationsDbusInterface(deviceId, this); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceNotificationsInterface::notificationPosted, this, &NotificationsModel::notificationAdded); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceNotificationsInterface::notificationRemoved, this, &NotificationsModel::notificationRemoved); connect(m_dbusInterface, &OrgKdeKdeconnectDeviceNotificationsInterface::allNotificationsRemoved, this, &NotificationsModel::clearNotifications); refreshNotificationList(); Q_EMIT deviceIdChanged(deviceId); } void NotificationsModel::notificationAdded(const QString& id) { int currentSize = m_notificationList.size(); beginInsertRows(QModelIndex(), currentSize, currentSize); NotificationDbusInterface* dbusInterface = new NotificationDbusInterface(m_deviceId, id, this); m_notificationList.append(dbusInterface); endInsertRows(); } void NotificationsModel::notificationRemoved(const QString& id) { for (int i = 0; i < m_notificationList.size(); ++i) { if (m_notificationList[i]->notificationId() == id) { beginRemoveRows(QModelIndex(), i, i); m_notificationList.removeAt(i); endRemoveRows(); return; } } qCWarning(KDECONNECT_INTERFACES) << "Attempted to remove unknown notification: " << id; } void NotificationsModel::refreshNotificationList() { if (!m_dbusInterface) { return; } clearNotifications(); if (!m_dbusInterface->isValid()) { qCWarning(KDECONNECT_INTERFACES) << "dbus interface not valid"; return; } QDBusPendingReply pendingNotificationIds = m_dbusInterface->activeNotifications(); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingNotificationIds, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &NotificationsModel::receivedNotifications); } void NotificationsModel::receivedNotifications(QDBusPendingCallWatcher* watcher) { watcher->deleteLater(); clearNotifications(); QDBusPendingReply pendingNotificationIds = *watcher; if (pendingNotificationIds.isError()) { qCWarning(KDECONNECT_INTERFACES) << pendingNotificationIds.error(); return; } const QStringList notificationIds = pendingNotificationIds.value(); if (notificationIds.isEmpty()) { return; } beginInsertRows(QModelIndex(), 0, notificationIds.size() - 1); for (const QString& notificationId : notificationIds) { NotificationDbusInterface* dbusInterface = new NotificationDbusInterface(m_deviceId, notificationId, this); m_notificationList.append(dbusInterface); } endInsertRows(); } QVariant NotificationsModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= m_notificationList.count() || !m_notificationList[index.row()]->isValid()) { return QVariant(); } if (!m_dbusInterface || !m_dbusInterface->isValid()) { return QVariant(); } NotificationDbusInterface* notification = m_notificationList[index.row()]; //FIXME: This function gets called lots of times, producing lots of dbus calls. Add a cache? switch (role) { case IconModelRole: return QIcon::fromTheme(QStringLiteral("device-notifier")); case IdModelRole: return notification->internalId(); case NameModelRole: return notification->ticker(); case ContentModelRole: return QString(); //To implement in the Android side case AppNameModelRole: return notification->appName(); case DbusInterfaceRole: return qVariantFromValue(notification); case DismissableModelRole: return notification->dismissable(); case RepliableModelRole: return !notification->replyId().isEmpty(); case IconPathModelRole: return notification->iconPath(); + case TitleModelRole: + return notification->title(); + case TextModelRole: + return notification->text(); default: return QVariant(); } } NotificationDbusInterface* NotificationsModel::getNotification(const QModelIndex& index) const { if (!index.isValid()) { return nullptr; } int row = index.row(); if (row < 0 || row >= m_notificationList.size()) { return nullptr; } return m_notificationList[row]; } int NotificationsModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { //Return size 0 if we are a child because this is not a tree return 0; } return m_notificationList.count(); } bool NotificationsModel::isAnyDimissable() const { for (NotificationDbusInterface* notification : qAsConst(m_notificationList)) { if (notification->dismissable()) { return true; } } return false; } void NotificationsModel::dismissAll() { for (NotificationDbusInterface* notification : qAsConst(m_notificationList)) { if (notification->dismissable()) { notification->dismiss(); } } } void NotificationsModel::clearNotifications() { if (!m_notificationList.isEmpty()) { beginRemoveRows(QModelIndex(), 0, m_notificationList.size() - 1); qDeleteAll(m_notificationList); m_notificationList.clear(); endRemoveRows(); } } diff --git a/interfaces/notificationsmodel.h b/interfaces/notificationsmodel.h index 09235670..23ef8464 100644 --- a/interfaces/notificationsmodel.h +++ b/interfaces/notificationsmodel.h @@ -1,87 +1,89 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef NOTIFICATIONSMODEL_H #define NOTIFICATIONSMODEL_H #include #include #include #include #include "interfaces/dbusinterfaces.h" class KDECONNECTINTERFACES_EXPORT NotificationsModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) Q_PROPERTY(int count READ rowCount NOTIFY rowsChanged) Q_PROPERTY(bool isAnyDimissable READ isAnyDimissable NOTIFY anyDismissableChanged STORED false) public: enum ModelRoles { IconModelRole = Qt::DecorationRole, NameModelRole = Qt::DisplayRole, ContentModelRole = Qt::UserRole, AppNameModelRole = Qt::UserRole + 1, IdModelRole, DismissableModelRole, RepliableModelRole, IconPathModelRole, - DbusInterfaceRole + DbusInterfaceRole, + TitleModelRole, + TextModelRole }; explicit NotificationsModel(QObject* parent = nullptr); ~NotificationsModel() override; QString deviceId() const; void setDeviceId(const QString& deviceId); QVariant data(const QModelIndex& index, int role) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; NotificationDbusInterface* getNotification(const QModelIndex& index) const; Q_INVOKABLE bool isAnyDimissable() const; QHash roleNames() const override; public Q_SLOTS: void dismissAll(); private Q_SLOTS: void notificationAdded(const QString& id); void notificationRemoved(const QString& id); void refreshNotificationList(); void receivedNotifications(QDBusPendingCallWatcher* watcher); void clearNotifications(); Q_SIGNALS: void deviceIdChanged(const QString& value); void anyDismissableChanged(); void rowsChanged(); private: DeviceNotificationsDbusInterface* m_dbusInterface; QList m_notificationList; QString m_deviceId; }; #endif // DEVICESMODEL_H diff --git a/plasmoid/package/contents/ui/DeviceDelegate.qml b/plasmoid/package/contents/ui/DeviceDelegate.qml index eed553f4..b2eafa7a 100644 --- a/plasmoid/package/contents/ui/DeviceDelegate.qml +++ b/plasmoid/package/contents/ui/DeviceDelegate.qml @@ -1,260 +1,260 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.1 import QtQuick.Layouts 1.1 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kdeconnect 1.0 import QtQuick.Controls.Styles 1.4 PlasmaComponents.ListItem { id: root readonly property QtObject device: DeviceDbusInterfaceFactory.create(model.deviceId) RemoteKeyboard { id: remoteKeyboard device: root.device onRemoteStateChanged: { remoteKeyboardInput.available = remoteKeyboard.remoteState; } onKeyPressReceived: { // console.log("XXX received keypress key=" + key + " special=" + specialKey + " shift=" + shift + " ctrl=" + ctrl + " text=" + remoteKeyboardInput.text + " cursorPos=" + remoteKeyboardInput.cursorPosition); // interpret some special keys: if (specialKey == 12 || specialKey == 14) // Return/Esc -> clear remoteKeyboardInput.text = ""; else if (specialKey == 4 // Left && remoteKeyboardInput.cursorPosition > 0) --remoteKeyboardInput.cursorPosition; else if (specialKey == 6 // Right && remoteKeyboardInput.cursorPosition < remoteKeyboardInput.text.length) ++remoteKeyboardInput.cursorPosition; else if (specialKey == 1) { // Backspace -> delete left var pos = remoteKeyboardInput.cursorPosition; if (pos > 0) { remoteKeyboardInput.text = remoteKeyboardInput.text.substring(0, pos-1) + remoteKeyboardInput.text.substring(pos, remoteKeyboardInput.text.length); remoteKeyboardInput.cursorPosition = pos - 1; } } else if (specialKey == 13) { // Delete -> delete right var pos = remoteKeyboardInput.cursorPosition; if (pos < remoteKeyboardInput.text.length) { remoteKeyboardInput.text = remoteKeyboardInput.text.substring(0, pos) + remoteKeyboardInput.text.substring(pos+1, remoteKeyboardInput.text.length); remoteKeyboardInput.cursorPosition = pos; // seems to be set to text.length automatically! } } else if (specialKey == 10) // Home remoteKeyboardInput.cursorPosition = 0; else if (specialKey == 11) // End remoteKeyboardInput.cursorPosition = remoteKeyboardInput.text.length; else { // echo visible keys var sanitized = ""; for (var i = 0; i < key.length; i++) { if (key.charCodeAt(i) > 31) sanitized += key.charAt(i); } if (sanitized.length > 0 && !ctrl && !alt) { // insert sanitized at current pos: var pos = remoteKeyboardInput.cursorPosition; remoteKeyboardInput.text = remoteKeyboardInput.text.substring(0, pos) + sanitized + remoteKeyboardInput.text.substring(pos, remoteKeyboardInput.text.length); remoteKeyboardInput.cursorPosition = pos + 1; // seems to be set to text.length automatically! } } // console.log("XXX After received keypress key=" + key + " special=" + specialKey + " shift=" + shift + " ctrl=" + ctrl + " text=" + remoteKeyboardInput.text + " cursorPos=" + remoteKeyboardInput.cursorPosition); } } Column { width: parent.width RowLayout { Item { //spacer to make the label centre aligned in a row yet still elide and everything implicitWidth: (ring.visible? ring.width : 0) + (browse.visible? browse.width : 0) + parent.spacing } Battery { id: battery device: root.device } PlasmaComponents.Label { horizontalAlignment: Text.AlignHCenter elide: Text.ElideRight text: (battery.available && battery.charge > -1) ? i18n("%1 (%2)", display, battery.displayString) : display Layout.fillWidth: true textFormat: Text.PlainText } //Find my phone PlasmaComponents.Button { FindMyPhone { id: findmyphone device: root.device } id: ring iconSource: "irc-voice" visible: findmyphone.available tooltip: i18n("Ring my phone") onClicked: { findmyphone.ring() } } //SFTP PlasmaComponents.Button { Sftp { id: sftp device: root.device } id: browse iconSource: "document-open-folder" visible: sftp.available tooltip: i18n("Browse this device") onClicked: { sftp.browse() } } height: browse.height width: parent.width } //RemoteKeyboard PlasmaComponents.ListItem { visible: remoteKeyboardInput.available width: parent.width Row { width: parent.width spacing: 5 PlasmaComponents.Label { id: remoteKeyboardLabel text: i18n("Remote Keyboard") } PlasmaComponents.TextField { id: remoteKeyboardInput property bool available: remoteKeyboard.remoteState textColor: "black" height: parent.height width: parent.width - 5 - remoteKeyboardLabel.width verticalAlignment: TextInput.AlignVCenter readOnly: !available enabled: available style: TextFieldStyle { textColor: "black" background: Rectangle { radius: 2 border.color: "gray" border.width: 1 color: remoteKeyboardInput.available ? "white" : "lightgray" } } Keys.onPressed: { if (remoteKeyboard.available) remoteKeyboard.sendEvent(event); event.accepted = true; } } } } //Notifications PlasmaComponents.ListItem { visible: notificationsModel.count>0 enabled: true PlasmaComponents.Label { text: i18n("Notifications:") } PlasmaComponents.ToolButton { enabled: true visible: notificationsModel.isAnyDimissable; anchors.right: parent.right iconSource: "window-close" onClicked: notificationsModel.dismissAll(); } } Repeater { id: notificationsView model: NotificationsModel { id: notificationsModel deviceId: root.device.id() } delegate: PlasmaComponents.ListItem { id: listitem enabled: true onClicked: checked = !checked PlasmaCore.IconItem { id: notificationIcon source: appIcon width: (valid && appIcon.length) ? dismissButton.width : 0 height: width anchors.left: parent.left } PlasmaComponents.Label { - text: appName + ": " + display + text: appName + ": " + (title.length>0 ? (appName==title?notitext:title+": "+notitext) : display) anchors.right: replyButton.left anchors.left: notificationIcon.right elide: listitem.checked ? Text.ElideNone : Text.ElideRight maximumLineCount: listitem.checked ? 0 : 1 wrapMode: Text.WordWrap } PlasmaComponents.ToolButton { id: replyButton visible: repliable enabled: repliable anchors.right: dismissButton.left iconSource: "mail-reply-sender" onClicked: dbusInterface.reply(); } PlasmaComponents.ToolButton { id: dismissButton visible: notificationsModel.isAnyDimissable; enabled: dismissable anchors.right: parent.right iconSource: "window-close" onClicked: dbusInterface.dismiss(); } } } //NOTE: More information could be displayed here } }