diff --git a/smsapp/CMakeLists.txt b/smsapp/CMakeLists.txt index ba4a0c6f..eb159a7a 100644 --- a/smsapp/CMakeLists.txt +++ b/smsapp/CMakeLists.txt @@ -1,53 +1,54 @@ qt5_add_resources(KCSMS_SRCS resources.qrc) find_package(KF5People) add_library(kdeconnectsmshelper smshelper.cpp gsmasciimap.cpp ) set_target_properties(kdeconnectsmshelper PROPERTIES VERSION ${KDECONNECT_VERSION} SOVERSION ${KDECONNECT_VERSION_MAJOR} ) generate_export_header(kdeconnectsmshelper EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/kdeconnectsms_export.h BASE_NAME KDEConnectSmsAppLib) target_include_directories(kdeconnectsmshelper PUBLIC ${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) target_link_libraries(kdeconnectsmshelper PUBLIC Qt5::Core Qt5::DBus KF5::People kdeconnectinterfaces ) # If ever this library is actually used by someone else, we should export these headers set(libkdeconnectsmshelper_HEADERS smshelper.h ${CMAKE_CURRENT_BINARY_DIR}/kdeconnectsms_export.h ) add_executable(kdeconnect-sms main.cpp conversationlistmodel.cpp conversationmodel.cpp + conversationssortfilterproxymodel.cpp ${KCSMS_SRCS}) target_include_directories(kdeconnect-sms PUBLIC ${CMAKE_BINARY_DIR}) target_link_libraries(kdeconnect-sms kdeconnectsmshelper kdeconnectinterfaces Qt5::Quick Qt5::Widgets KF5::CoreAddons KF5::DBusAddons KF5::I18n KF5::People ) install(TARGETS kdeconnect-sms ${INSTALL_TARGETS_DEFAULT_ARGS}) install(TARGETS kdeconnectsmshelper ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) install(PROGRAMS org.kde.kdeconnect.sms.desktop DESTINATION ${KDE_INSTALL_APPDIR}) diff --git a/smsapp/conversationlistmodel.cpp b/smsapp/conversationlistmodel.cpp index 807368b9..254a574d 100644 --- a/smsapp/conversationlistmodel.cpp +++ b/smsapp/conversationlistmodel.cpp @@ -1,300 +1,265 @@ /** * Copyright (C) 2018 Aleix Pol Gonzalez * Copyright (C) 2018 Simon Redman * * 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 "conversationlistmodel.h" #include #include #include #include #include "interfaces/conversationmessage.h" #include "interfaces/dbusinterfaces.h" #include "smshelper.h" Q_LOGGING_CATEGORY(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL, "kdeconnect.sms.conversations_list") #define INVALID_THREAD_ID -1 #define INVALID_DATE -1 -OurSortFilterProxyModel::OurSortFilterProxyModel() -{ - setFilterRole(ConversationListModel::DateRole); -} - -OurSortFilterProxyModel::~OurSortFilterProxyModel(){} - -void OurSortFilterProxyModel::setOurFilterRole(int role) -{ - setFilterRole(role); -} - -bool OurSortFilterProxyModel::lessThan(const QModelIndex& leftIndex, const QModelIndex& rightIndex) const -{ - QVariant leftDataTimeStamp = sourceModel()->data(leftIndex, ConversationListModel::DateRole); - QVariant rightDataTimeStamp = sourceModel()->data(rightIndex, ConversationListModel::DateRole); - - if (leftDataTimeStamp == rightDataTimeStamp) { - QVariant leftDataName = sourceModel()->data(leftIndex, Qt::DisplayRole); - QVariant rightDataName = sourceModel()->data(rightIndex, Qt::DisplayRole); - return leftDataName.toString().toLower() > rightDataName.toString().toLower(); - } - return leftDataTimeStamp < rightDataTimeStamp; -} - -bool OurSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const -{ - QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); - - if (filterRole() == Qt::DisplayRole) { - return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); - } - return sourceModel()->data(index, ConversationListModel::DateRole) != INVALID_THREAD_ID; -} - ConversationListModel::ConversationListModel(QObject* parent) : QStandardItemModel(parent) , m_conversationsInterface(nullptr) { //qCDebug(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "Constructing" << this; auto roles = roleNames(); roles.insert(FromMeRole, "fromMe"); roles.insert(SenderRole, "sender"); roles.insert(DateRole, "date"); roles.insert(AddressesRole, "addresses"); roles.insert(ConversationIdRole, "conversationId"); roles.insert(MultitargetRole, "isMultitarget"); setItemRoleNames(roles); ConversationMessage::registerDbusType(); } ConversationListModel::~ConversationListModel() { } void ConversationListModel::setDeviceId(const QString& deviceId) { if (deviceId == m_deviceId) { return; } if (deviceId.isEmpty()) { return; } qCDebug(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "setDeviceId" << deviceId << "of" << this; if (m_conversationsInterface) { disconnect(m_conversationsInterface, SIGNAL(conversationCreated(QDBusVariant)), this, SLOT(handleCreatedConversation(QDBusVariant))); disconnect(m_conversationsInterface, SIGNAL(conversationUpdated(QDBusVariant)), this, SLOT(handleConversationUpdated(QDBusVariant))); delete m_conversationsInterface; m_conversationsInterface = nullptr; } // This method still gets called *with a valid deviceID* when the device is not connected while the component is setting up // Detect that case and don't do anything. DeviceDbusInterface device(deviceId); if (!(device.isValid() && device.isReachable())) { return; } m_deviceId = deviceId; Q_EMIT deviceIdChanged(); m_conversationsInterface = new DeviceConversationsDbusInterface(deviceId, this); connect(m_conversationsInterface, SIGNAL(conversationCreated(QDBusVariant)), this, SLOT(handleCreatedConversation(QDBusVariant))); connect(m_conversationsInterface, SIGNAL(conversationUpdated(QDBusVariant)), this, SLOT(handleConversationUpdated(QDBusVariant))); refresh(); } void ConversationListModel::refresh() { if (m_deviceId.isEmpty()) { qWarning() << "refreshing null device"; return; } prepareConversationsList(); m_conversationsInterface->requestAllConversationThreads(); } void ConversationListModel::prepareConversationsList() { if (!m_conversationsInterface->isValid()) { qCWarning(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << "Tried to prepareConversationsList with an invalid interface!"; return; } QDBusPendingReply validThreadIDsReply = m_conversationsInterface->activeConversations(); setWhenAvailable(validThreadIDsReply, [this](const QVariantList& convs) { clear(); // If we clear before we receive the reply, there might be a (several second) visual gap! for (const QVariant& headMessage : convs) { QDBusArgument data = headMessage.value(); ConversationMessage message; data >> message; createRowFromMessage(message); } displayContacts(); }, this); } void ConversationListModel::handleCreatedConversation(const QDBusVariant& msg) { ConversationMessage message = ConversationMessage::fromDBus(msg); createRowFromMessage(message); } void ConversationListModel::handleConversationUpdated(const QDBusVariant& msg) { ConversationMessage message = ConversationMessage::fromDBus(msg); createRowFromMessage(message); } void ConversationListModel::printDBusError(const QDBusError& error) { qCWarning(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) << error; } QStandardItem * ConversationListModel::conversationForThreadId(qint32 threadId) { for(int i=0, c=rowCount(); idata(ConversationIdRole) == threadId) return it; } return nullptr; } QStandardItem * ConversationListModel::getConversationForAddress(const QString& address) { for(int i = 0; i < rowCount(); ++i) { const auto& it = item(i, 0); if (!it->data(MultitargetRole).toBool()) { if (SmsHelper::isPhoneNumberMatch(it->data(SenderRole).toString(), address)) { return it; } } } return nullptr; } void ConversationListModel::createRowFromMessage(const ConversationMessage& message) { if (message.type() == -1) { // The Android side currently hacks in -1 if something weird comes up // TODO: Remove this hack when MMS support is implemented return; } /** The address of everyone involved in this conversation, which we should not display (check if they are known contacts first) */ QList rawAddresses = message.addresses(); if (rawAddresses.isEmpty()) { qWarning() << "no addresses!" << message.body(); return; } bool toadd = false; QStandardItem* item = conversationForThreadId(message.threadID()); //Check if we have a contact with which to associate this message, needed if there is no conversation with the contact and we received a message from them if (!item && !message.isMultitarget()) { item = getConversationForAddress(rawAddresses[0].address()); if (item) { item->setData(message.threadID(), ConversationIdRole); } } if (!item) { toadd = true; item = new QStandardItem(); QString displayNames = SmsHelper::getTitleForAddresses(rawAddresses); QIcon displayIcon = SmsHelper::getIconForAddresses(rawAddresses); item->setText(displayNames); item->setIcon(displayIcon); item->setData(message.threadID(), ConversationIdRole); item->setData(rawAddresses[0].address(), SenderRole); } // TODO: Upgrade to support other kinds of media // Get the body that we should display QString displayBody = message.containsTextBody() ? message.body() : i18n("(Unsupported Message Type)"); // Prepend the sender's name if (message.isOutgoing()) { displayBody = i18n("You: %1", displayBody); } else { // If the message is incoming, the sender is the first Address QString senderAddress = item->data(SenderRole).toString(); const auto sender = SmsHelper::lookupPersonByAddress(senderAddress); QString senderName = sender == nullptr? senderAddress : SmsHelper::lookupPersonByAddress(senderAddress)->name(); displayBody = i18n("%1: %2", senderName, displayBody); } // Update the message if the data is newer // This will be true if a conversation receives a new message, but false when the user // does something to trigger past conversation history loading bool oldDateExists; qint64 oldDate = item->data(DateRole).toLongLong(&oldDateExists); if (!oldDateExists || message.date() >= oldDate) { // If there was no old data or incoming data is newer, update the record item->setData(QVariant::fromValue(message.addresses()), AddressesRole); item->setData(message.isOutgoing(), FromMeRole); item->setData(displayBody, Qt::ToolTipRole); item->setData(message.date(), DateRole); item->setData(message.isMultitarget(), MultitargetRole); } if (toadd) appendRow(item); } void ConversationListModel::displayContacts() { const QList> personDataList = SmsHelper::getAllPersons(); for(const auto& person : personDataList) { const QVariantList allPhoneNumbers = person->contactCustomProperty(QStringLiteral("all-phoneNumber")).toList(); for (const QVariant& rawPhoneNumber : allPhoneNumbers) { //check for any duplicate phoneNumber and eliminate it if (!getConversationForAddress(rawPhoneNumber.toString())) { QStandardItem* item = new QStandardItem(); item->setText(person->name()); item->setIcon(person->photo()); QList addresses; addresses.append(ConversationAddress(rawPhoneNumber.toString())); item->setData(QVariant::fromValue(addresses), AddressesRole); QString displayBody = i18n("%1", rawPhoneNumber.toString()); item->setData(displayBody, Qt::ToolTipRole); item->setData(false, MultitargetRole); item->setData(qint64(INVALID_THREAD_ID), ConversationIdRole); item->setData(qint64(INVALID_DATE), DateRole); item->setData(rawPhoneNumber.toString(), SenderRole); appendRow(item); } } } } diff --git a/smsapp/conversationlistmodel.h b/smsapp/conversationlistmodel.h index 6daac5ae..9575174a 100644 --- a/smsapp/conversationlistmodel.h +++ b/smsapp/conversationlistmodel.h @@ -1,124 +1,83 @@ /** * Copyright (C) 2018 Aleix Pol Gonzalez * Copyright (C) 2018 Simon Redman * * 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 CONVERSATIONLISTMODEL_H #define CONVERSATIONLISTMODEL_H #include #include -#include #include "interfaces/conversationmessage.h" #include "interfaces/dbusinterfaces.h" Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_SMS_CONVERSATIONS_LIST_MODEL) - -class OurSortFilterProxyModel : public QSortFilterProxyModel, public QQmlParserStatus -{ - Q_OBJECT - Q_INTERFACES(QQmlParserStatus) - Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder) -public: - - Qt::SortOrder sortOrder() const { return m_sortOrder; } - void setSortOrder(Qt::SortOrder sortOrder) { - if (m_sortOrder != sortOrder) { - m_sortOrder = sortOrder; - sortNow(); - } - } - void classBegin() override {} - void componentComplete() override { - m_completed = true; - sortNow(); - } - - Q_INVOKABLE void setOurFilterRole(int role); - - OurSortFilterProxyModel(); - ~OurSortFilterProxyModel(); - -protected: - bool lessThan(const QModelIndex& leftIndex, const QModelIndex& rightIndex) const override; - bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; - -private: - void sortNow() { - if (m_completed && dynamicSortFilter()) - sort(0, m_sortOrder); - } - - bool m_completed = false; - Qt::SortOrder m_sortOrder = Qt::AscendingOrder; -}; - class ConversationListModel : public QStandardItemModel { Q_OBJECT Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged) public: ConversationListModel(QObject* parent = nullptr); ~ConversationListModel(); enum Roles { /* Roles which apply while working as a single message */ FromMeRole = Qt::UserRole, SenderRole, // The sender of the message. Undefined if this is an outgoing message DateRole, // The date of this message /* Roles which apply while working as the head of a conversation */ AddressesRole, // The Addresses involved in the conversation ConversationIdRole, // The ThreadID of the conversation MultitargetRole, // Indicate that this conversation is multitarget }; Q_ENUM(Roles) QString deviceId() const { return m_deviceId; } void setDeviceId(const QString &/*deviceId*/); Q_SCRIPTABLE void refresh(); public Q_SLOTS: void handleCreatedConversation(const QDBusVariant& msg); void handleConversationUpdated(const QDBusVariant& msg); void createRowFromMessage(const ConversationMessage& message); void printDBusError(const QDBusError& error); void displayContacts(); Q_SIGNALS: void deviceIdChanged(); private: /** * Get all conversations currently known by the conversationsInterface, if any */ void prepareConversationsList(); QStandardItem* conversationForThreadId(qint32 threadId); QStandardItem* getConversationForAddress(const QString& address); DeviceConversationsDbusInterface* m_conversationsInterface; QString m_deviceId; }; #endif // CONVERSATIONLISTMODEL_H diff --git a/smsapp/conversationssortfilterproxymodel.cpp b/smsapp/conversationssortfilterproxymodel.cpp new file mode 100644 index 00000000..4aea66c3 --- /dev/null +++ b/smsapp/conversationssortfilterproxymodel.cpp @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2018 Aleix Pol Gonzalez + * Copyright (C) 2018 Simon Redman + * + * 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 "conversationssortfilterproxymodel.h" +#include "conversationlistmodel.h" + +#include +#include + +Q_LOGGING_CATEGORY(KDECONNECT_SMS_CONVERSATIONS_SORT_FILTER_PROXY_MODEL, "kdeconnect.sms.conversations_sort_filter_proxy") + +#define INVALID_THREAD_ID -1 + +ConversationsSortFilterProxyModel::ConversationsSortFilterProxyModel() +{ + setFilterRole(ConversationListModel::DateRole); +} + +ConversationsSortFilterProxyModel::~ConversationsSortFilterProxyModel(){} + +void ConversationsSortFilterProxyModel::setOurFilterRole(int role) +{ + setFilterRole(role); +} + +bool ConversationsSortFilterProxyModel::lessThan(const QModelIndex& leftIndex, const QModelIndex& rightIndex) const +{ + QVariant leftDataTimeStamp = sourceModel()->data(leftIndex, ConversationListModel::DateRole); + QVariant rightDataTimeStamp = sourceModel()->data(rightIndex, ConversationListModel::DateRole); + + if (leftDataTimeStamp == rightDataTimeStamp) { + QVariant leftDataName = sourceModel()->data(leftIndex, Qt::DisplayRole); + QVariant rightDataName = sourceModel()->data(rightIndex, Qt::DisplayRole); + return leftDataName.toString().toLower() > rightDataName.toString().toLower(); + } + return leftDataTimeStamp < rightDataTimeStamp; +} + +bool ConversationsSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + + if (filterRole() == Qt::DisplayRole) { + return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); + } + return sourceModel()->data(index, ConversationListModel::DateRole) != INVALID_THREAD_ID; +} diff --git a/smsapp/conversationssortfilterproxymodel.h b/smsapp/conversationssortfilterproxymodel.h new file mode 100644 index 00000000..912e68f5 --- /dev/null +++ b/smsapp/conversationssortfilterproxymodel.h @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2018 Aleix Pol Gonzalez + * Copyright (C) 2018 Simon Redman + * + * 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 CONVERSATIONSSORTFILTERPROXYMODEL_H +#define CONVERSATIONSSORTFILTERPROXYMODEL_H + +#include +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(KDECONNECT_SMS_CONVERSATIONS_SORT_FILTER_PROXY_MODEL) + +class ConversationsSortFilterProxyModel : public QSortFilterProxyModel, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder) +public: + + Qt::SortOrder sortOrder() const { return m_sortOrder; } + void setSortOrder(Qt::SortOrder sortOrder) { + if (m_sortOrder != sortOrder) { + m_sortOrder = sortOrder; + sortNow(); + } + } + void classBegin() override {} + void componentComplete() override { + m_completed = true; + sortNow(); + } + + Q_INVOKABLE void setOurFilterRole(int role); + + ConversationsSortFilterProxyModel(); + ~ConversationsSortFilterProxyModel(); + +protected: + bool lessThan(const QModelIndex& leftIndex, const QModelIndex& rightIndex) const override; + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + +private: + void sortNow() { + if (m_completed && dynamicSortFilter()) + sort(0, m_sortOrder); + } + + bool m_completed = false; + Qt::SortOrder m_sortOrder = Qt::AscendingOrder; +}; + +#endif // CONVERSATIONSSORTFILTERPROXYMODEL_H diff --git a/smsapp/main.cpp b/smsapp/main.cpp index 9b9af703..feb010f6 100644 --- a/smsapp/main.cpp +++ b/smsapp/main.cpp @@ -1,79 +1,80 @@ /** * Copyright (C) 2018 Aleix Pol Gonzalez * Copyright (C) 2018 Simon Redman * * 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 "conversationmodel.h" #include "conversationlistmodel.h" +#include "conversationssortfilterproxymodel.h" #include "kdeconnect-version.h" #include #include #include #include #include #include #include #include int main(int argc, char *argv[]) { QApplication app(argc, argv); KAboutData aboutData(QStringLiteral("kdeconnect.sms"), i18n("SMS Instant Messaging"), QStringLiteral(KDECONNECT_VERSION_STRING), i18n("KDE Connect SMS"), KAboutLicense::GPL_V3, i18n("(C) 2018-2019, KDE Connect Team")); aboutData.addAuthor(i18n("Simon Redman"), {}, QStringLiteral("simon@ergotech.com")); aboutData.addAuthor(i18n("Aleix Pol Gonzalez"), {}, QStringLiteral("aleixpol@kde.org")); aboutData.addAuthor(i18n("Nicolas Fella"), {}, QStringLiteral("nicolas.fella@gmx.de")); KAboutData::setApplicationData(aboutData); QString initialMessage, deviceid; { QCommandLineParser parser; aboutData.setupCommandLine(&parser); parser.addOption(QCommandLineOption(QStringLiteral("device"), i18n("Select a device"), i18n("id"))); parser.addOption(QCommandLineOption(QStringLiteral("message"), i18n("Send a message"), i18n("message"))); parser.process(app); aboutData.processCommandLine(&parser); initialMessage = parser.value(QStringLiteral("message")); deviceid = parser.value(QStringLiteral("device")); } KDBusService service(KDBusService::Unique); - qmlRegisterType("org.kde.kdeconnect.sms", 1, 0, "QSortFilterProxyModel"); + qmlRegisterType("org.kde.kdeconnect.sms", 1, 0, "QSortFilterProxyModel"); qmlRegisterType("org.kde.kdeconnect.sms", 1, 0, "ConversationModel"); qmlRegisterType("org.kde.kdeconnect.sms", 1, 0, "ConversationListModel"); QQmlApplicationEngine engine; engine.rootContext()->setContextObject(new KLocalizedContext(&engine)); engine.rootContext()->setContextProperties({ { QStringLiteral("initialMessage"), initialMessage }, { QStringLiteral("initialDevice"), deviceid }, { QStringLiteral("aboutData"), QVariant::fromValue(KAboutData::applicationData()) } }); engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); return app.exec(); }