[OpenDocString] kdeconnect-kde (cpp)
conversationmodel.cpp
ConversationModel::ConversationModel(QObject *parent)
    : QStandardItemModel(parent)
    , m_conversationsInterface(nullptr)
{
    auto roles = roleNames();
    roles.insert(FromMeRole, "fromMe");
    roles.insert(DateRole, "date");
    roles.insert(SenderRole, "sender");
    roles.insert(AvatarRole, "avatar");
    roles.insert(AttachmentsRole, "attachments");
    setItemRoleNames(roles);
}
Constructs a conversation model. The parent object is the parent of the item, it contains all the basic roles of the item and its children.
ConversationModel::~ConversationModel()
{
}
This implements the singleton design pattern and returns a reference to a conversation model object.
qint64 ConversationModel::threadId() const
{
    return m_threadId;
}
Returns the thread id stored in the internal list.
void ConversationModel::setThreadId(const qint64 &threadId)
{
    if (m_threadId == threadId)
        return;

    m_threadId = threadId;
    clear();
    knownMessageIDs.clear();
    if (m_threadId != INVALID_THREAD_ID && !m_deviceId.isEmpty()) {
        requestMoreMessages();
        m_thumbnailsProvider->clear();
    }
}
Sets the thread id by clearing the known message IDs, and requests more messages if the threadId is not the INVALID_THREAD_ID. Then it requests more messages if the threadId is not the INVALID_THREAD_ID.
void ConversationModel::setDeviceId(const QString &deviceId)
{
    if (deviceId == m_deviceId)
        return;

    qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "setDeviceId"
                                               << "of" << this;
    if (m_conversationsInterface) {
        disconnect(m_conversationsInterface, SIGNAL(conversationUpdated(QDBusVariant)), this, SLOT(handleConversationUpdate(QDBusVariant)));
        disconnect(m_conversationsInterface, SIGNAL(conversationLoaded(qint64, quint64)), this, SLOT(handleConversationLoaded(qint64, quint64)));
        disconnect(m_conversationsInterface, SIGNAL(conversationCreated(QDBusVariant)), this, SLOT(handleConversationCreated(QDBusVariant)));
        delete m_conversationsInterface;
    }

    m_deviceId = deviceId;

    m_conversationsInterface = new DeviceConversationsDbusInterface(deviceId, this);
    connect(m_conversationsInterface, SIGNAL(conversationUpdated(QDBusVariant)), this, SLOT(handleConversationUpdate(QDBusVariant)));
    connect(m_conversationsInterface, SIGNAL(conversationLoaded(qint64, quint64)), this, SLOT(handleConversationLoaded(qint64, quint64)));
    connect(m_conversationsInterface, SIGNAL(conversationCreated(QDBusVariant)), this, SLOT(handleConversationCreated(QDBusVariant)));

    connect(m_conversationsInterface, SIGNAL(attachmentReceived(QString, QString)), this, SIGNAL(filePathReceived(QString, QString)));

    QQmlApplicationEngine *engine = qobject_cast(QQmlEngine::contextForObject(this)->engine());
    m_thumbnailsProvider = dynamic_cast(engine->imageProvider(QStringLiteral("thumbnailsProvider")));

    // Clear any previous data on device change
    m_thumbnailsProvider->clear();
}
Sets the device id, and creates a DeviceConversationsDbusInterface object, and sets the device id, and connects the conversationUpdated, conversationLoaded events to the device, and sets the m_thumbnailsProvider pointer to a dynamic pointer to a thumbnailsProvider.
void ConversationModel::setAddressList(const QList &addressList)
{
    m_addressList = addressList;
}
Sets the address list to the internal list.
bool ConversationModel::sendReplyToConversation(const QString &textMessage, QList attachmentUrls)
{
    QVariantList fileUrls;
    for (const auto &url : attachmentUrls) {
        fileUrls << QVariant::fromValue(url.toLocalFile());
    }

    m_conversationsInterface->replyToConversation(m_threadId, textMessage, fileUrls);
    return true;
}
This sends a reply to a list of files, given a text message and a list of attachment urls.
bool ConversationModel::startNewConversation(const QString &textMessage, const QList &addressList, QList attachmentUrls)
{
    QVariantList addresses;

    for (const auto &address : addressList) {
        addresses << QVariant::fromValue(address);
    }

    QVariantList fileUrls;
    for (const auto &url : attachmentUrls) {
        fileUrls << QVariant::fromValue(url.toLocalFile());
    }

    m_conversationsInterface->sendWithoutConversation(addresses, textMessage, fileUrls);
    return true;
}
This sends a text message to all addresses and attachments, without creating a conversation.
void ConversationModel::requestMoreMessages(const quint32 &howMany)
{
    if (m_threadId == INVALID_THREAD_ID) {
        return;
    }
    const auto &numMessages = knownMessageIDs.size();
    m_conversationsInterface->requestConversation(m_threadId, numMessages, numMessages + howMany);
}
This requests that there are messages available. By setting the howMany parameter to 0, the method requests the next few messages. By setting the howMany parameter to 1, the number of messages to send, and the number of messages to the conversation. By setting the howMany parameter to -1, the function requests the next few messages. By setting the howMany parameter to -1, the number of messages to send, the function requests the next few messages. By returning the number of messages the thread wants, the number of messages to send, the number of messages we request, and the number of messages we have queued, and the number of messages that are waiting for. By returning - 1, the number of messages to send, where the conversation is done. By setting the value of the function returns -1, the number of messages to be sent to the client, which will indicate the number of messages to send, the number of messages to the client.
void ConversationModel::createRowFromMessage(const ConversationMessage &message, int pos)
{
    if (message.threadID() != m_threadId) {
        // Because of the asynchronous nature of the current implementation of this model, if the
        // user clicks quickly between threads or for some other reason a message comes when we're
        // not expecting it, we should not display it in the wrong place
        qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "Got a message for a thread" << message.threadID() << "but we are currently viewing" << m_threadId
                                                   << "Discarding.";
        return;
    }

    if (knownMessageIDs.contains(message.uID())) {
        qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "Ignoring duplicate message with ID" << message.uID();
        return;
    }

    ConversationAddress sender;
    if (!message.addresses().isEmpty()) {
        sender = message.addresses().first();
    } else {
        qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "Conversation with ID " << message.threadID() << " did not have any addresses";
    }

    QString senderName = message.isIncoming() ? SmsHelper::getTitleForAddresses({sender}) : QString();
    QString displayBody = message.body();

    auto item = new QStandardItem;
    item->setText(displayBody);
    item->setData(message.isOutgoing(), FromMeRole);
    item->setData(message.date(), DateRole);
    item->setData(senderName, SenderRole);

    QList attachmentInfoList;
    const QList attachmentList = message.attachments();

    for (const Attachment &attachment : attachmentList) {
        AttachmentInfo attachmentInfo(attachment);
        attachmentInfoList.append(QVariant::fromValue(attachmentInfo));

        if (attachment.mimeType().startsWith(QLatin1String("image")) || attachment.mimeType().startsWith(QLatin1String("video"))) {
            // The message contains thumbnail as Base64 String, convert it back into image thumbnail
            const QByteArray byteArray = attachment.base64EncodedFile().toUtf8();
            QPixmap thumbnail;
            thumbnail.loadFromData(QByteArray::fromBase64(byteArray));

            m_thumbnailsProvider->addImage(attachment.uniqueIdentifier(), thumbnail.toImage());
        }
    }

    item->setData(attachmentInfoList, AttachmentsRole);

    insertRow(pos, item);
    knownMessageIDs.insert(message.uID());
}
This creates a row of data from a given message object, and adds it to the model. It takes the position of the message object in the model, and checks if the message is already in the model. If the message is already in the model, it skips the entry.
void ConversationModel::handleConversationUpdate(const QDBusVariant &msg)
{
    ConversationMessage message = ConversationMessage::fromDBus(msg);

    if (message.threadID() != m_threadId) {
        // If a conversation which we are not currently viewing was updated, discard the information
        qCDebug(KDECONNECT_SMS_CONVERSATION_MODEL) << "Saw update for thread" << message.threadID() << "but we are currently viewing" << m_threadId;
        return;
    }
    createRowFromMessage(message, 0);
}
This creates a row of data for the given message, if the message is not currently viewing, and the thread is not the same as the current thread. Then it creates the row based on the message, and sets the corresponding flag on the message.
void ConversationModel::handleConversationCreated(const QDBusVariant &msg)
{
    ConversationMessage message = ConversationMessage::fromDBus(msg);

    if (m_threadId == INVALID_THREAD_ID && SmsHelper::isPhoneNumberMatch(m_addressList[0].address(), message.addresses().first().address())
        && !message.isMultitarget()) {
        m_threadId = message.threadID();
        createRowFromMessage(message, 0);
    }
}
This creates a row of data if the first address of the message matches the first address of the message and the message is not a multitarget message.
void ConversationModel::handleConversationLoaded(qint64 threadID, quint64 numMessages)
{
    Q_UNUSED(numMessages)
    if (threadID != m_threadId) {
        return;
    }
    // If we get this flag, it means that the phone will not be responding with any more messages
    // so we should not be showing a loading indicator
    Q_EMIT loadingFinished();
}
This implements the logic that is executed when the thread has been loaded. It is assumed that the thread is the first user of the conversation, and that the phone will not respond with any more messages. If the thread is the first user of the conversation, it is ignored.
QString ConversationModel::getCharCountInfo(const QString &message) const
{
    SmsCharCount count = SmsHelper::getCharCount(message);

    if (count.messages > 1) {
        // Show remaining char count and message count
        return QString::number(count.remaining) + QLatin1Char('/') + QString::number(count.messages);
    }
    if (count.messages == 1 && count.remaining < 10) {
        // Show only remaining char count
        return QString::number(count.remaining);
    } else {
        // Do not show anything
        return QString();
    }
}
Returns a QString object that represents the number of characters that can be displayed. It takes the message object from the internal state of the application, and returns it. If the message is 1 and there are more messages than 10, it returns a QString object containing the number of characters that can be displayed. It returns a QString object that represents the number of characters that can be displayed.
void ConversationModel::requestAttachmentPath(const qint64 &partID, const QString &uniqueIdentifier)
{
    m_conversationsInterface->requestAttachmentFile(partID, uniqueIdentifier);
}
This requests that the attachment file path be unique by the given part id and unique identifier.